Merge pull request #62 from gavofyork/ethash

Block complete. Needs tests.
This commit is contained in:
Arkadiy Paronyan 2016-01-10 14:13:32 +01:00
commit 1bcb6f8ee3
11 changed files with 235 additions and 78 deletions

View File

@ -17,6 +17,8 @@ pub struct Block {
archive: Vec<Entry>, archive: Vec<Entry>,
archive_set: HashSet<H256>, archive_set: HashSet<H256>,
uncles: Vec<Header>,
} }
impl Block { impl Block {
@ -26,6 +28,7 @@ impl Block {
state: state, state: state,
archive: Vec::new(), archive: Vec::new(),
archive_set: HashSet::new(), archive_set: HashSet::new(),
uncles: Vec::new(),
} }
} }
@ -45,6 +48,9 @@ pub trait IsBlock {
/// Get all information on transactions in this block. /// Get all information on transactions in this block.
fn archive(&self) -> &Vec<Entry> { &self.block().archive } fn archive(&self) -> &Vec<Entry> { &self.block().archive }
/// Get all uncles in this block.
fn uncles(&self) -> &Vec<Header> { &self.block().uncles }
} }
impl IsBlock for Block { impl IsBlock for Block {
@ -67,7 +73,7 @@ pub struct OpenBlock<'engine> {
/// There is no function available to push a transaction. If you want that you'll need to `reopen()` it. /// There is no function available to push a transaction. If you want that you'll need to `reopen()` it.
pub struct ClosedBlock<'engine> { pub struct ClosedBlock<'engine> {
open_block: OpenBlock<'engine>, open_block: OpenBlock<'engine>,
uncles: Bytes, uncle_bytes: Bytes,
} }
/// A block that has a valid seal. /// A block that has a valid seal.
@ -75,23 +81,52 @@ pub struct ClosedBlock<'engine> {
/// The block's header has valid seal arguments. The block cannot be reversed into a ClosedBlock or OpenBlock. /// The block's header has valid seal arguments. The block cannot be reversed into a ClosedBlock or OpenBlock.
pub struct SealedBlock { pub struct SealedBlock {
block: Block, block: Block,
_bytes: Bytes, uncle_bytes: Bytes,
} }
impl<'engine> OpenBlock<'engine> { impl<'engine> OpenBlock<'engine> {
/// Create a new OpenBlock ready for transaction pushing. /// Create a new OpenBlock ready for transaction pushing.
pub fn new<'a>(engine: &'a Engine, db: OverlayDB, parent: &Header, last_hashes: LastHashes) -> OpenBlock<'a> { pub fn new<'a>(engine: &'a Engine, db: OverlayDB, parent: &Header, last_hashes: LastHashes, author: Address, extra_data: Bytes) -> OpenBlock<'a> {
let mut r = OpenBlock { let mut r = OpenBlock {
block: Block::new(State::from_existing(db, parent.state_root.clone(), engine.account_start_nonce())), block: Block::new(State::from_existing(db, parent.state_root.clone(), engine.account_start_nonce())),
engine: engine, engine: engine,
last_hashes: last_hashes, last_hashes: last_hashes,
}; };
r.block.header.set_author(author);
r.block.header.set_extra_data(extra_data);
engine.populate_from_parent(&mut r.block.header, parent); engine.populate_from_parent(&mut r.block.header, parent);
engine.on_new_block(&mut r.block); engine.on_new_block(&mut r.block);
r r
} }
/// Alter the author for the block.
pub fn set_author(&mut self, author: Address) { self.block.header.set_author(author); }
/// Alter the extra_data for the block.
pub fn set_extra_data(&mut self, extra_data: Bytes) -> Result<(), BlockError> {
if extra_data.len() > self.engine.maximum_extra_data_size() {
Err(BlockError::ExtraDataOutOfBounds(OutOfBounds{min: 0, max: self.engine.maximum_extra_data_size(), found: extra_data.len()}))
} else {
self.block.header.set_extra_data(extra_data);
Ok(())
}
}
/// Add an uncle to the block, if possible.
///
/// NOTE Will check chain constraints and the uncle number but will NOT check
/// that the header itself is actually valid.
pub fn push_uncle(&mut self, valid_uncle_header: Header) -> Result<(), BlockError> {
if self.block.uncles.len() >= self.engine.maximum_uncle_count() {
return Err(BlockError::TooManyUncles);
}
// TODO: check number
// TODO: check not a direct ancestor (use last_hashes for that)
self.block.uncles.push(valid_uncle_header);
Ok(())
}
/// Get the environment info concerning this block. /// Get the environment info concerning this block.
pub fn env_info(&self) -> EnvInfo { pub fn env_info(&self) -> EnvInfo {
// TODO: memoise. // TODO: memoise.
@ -106,12 +141,14 @@ impl<'engine> OpenBlock<'engine> {
} }
} }
/// Push a transaction into the block. It will be executed, and archived together with the receipt. /// Push a transaction into the block.
pub fn push_transaction(&mut self, t: Transaction, h: Option<H256>) -> Result<&Receipt, EthcoreError> { ///
/// If valid, it will be executed, and archived together with the receipt.
pub fn push_transaction(&mut self, t: Transaction, h: Option<H256>) -> Result<&Receipt, Error> {
let env_info = self.env_info(); let env_info = self.env_info();
match self.block.state.apply(&env_info, self.engine, &t, true) { match self.block.state.apply(&env_info, self.engine, &t, true) {
Ok(x) => { Ok(x) => {
self.block.archive_set.insert(h.unwrap_or_else(||t.sha3())); self.block.archive_set.insert(h.unwrap_or_else(||t.hash()));
self.block.archive.push(Entry { transaction: t, receipt: x.receipt }); self.block.archive.push(Entry { transaction: t, receipt: x.receipt });
Ok(&self.block.archive.last().unwrap().receipt) Ok(&self.block.archive.last().unwrap().receipt)
} }
@ -120,17 +157,14 @@ impl<'engine> OpenBlock<'engine> {
} }
/// Turn this into a `ClosedBlock`. A BlockChain must be provided in order to figure out the uncles. /// Turn this into a `ClosedBlock`. A BlockChain must be provided in order to figure out the uncles.
pub fn close(self, uncles: Vec<Header>, author: Address, extra_data: Bytes) -> ClosedBlock<'engine> { pub fn close(self) -> ClosedBlock<'engine> {
let mut s = self; let mut s = self;
// populate rest of header.
s.engine.on_close_block(&mut s.block); s.engine.on_close_block(&mut s.block);
s.block.header.author = author; s.block.header.transactions_root = ordered_trie_root(s.block.archive.iter().map(|ref e| e.transaction.rlp_bytes()).collect());
// s.header.transactions_root = ...; let uncle_bytes = s.block.uncles.iter().fold(RlpStream::new_list(s.block.uncles.len()), |mut s, u| {s.append(&u.rlp(Seal::With)); s} ).out();
let uncle_bytes = uncles.iter().fold(RlpStream::new_list(uncles.len()), |mut s, u| {s.append(&u.rlp(Seal::With)); s} ).out();
s.block.header.uncles_hash = uncle_bytes.sha3(); s.block.header.uncles_hash = uncle_bytes.sha3();
s.block.header.extra_data = extra_data;
s.block.header.state_root = s.block.state.root().clone(); s.block.header.state_root = s.block.state.root().clone();
// s.header.receipts_root = ...; s.block.header.receipts_root = ordered_trie_root(s.block.archive.iter().map(|ref e| e.receipt.rlp_bytes()).collect());
s.block.header.log_bloom = s.block.archive.iter().fold(LogBloom::zero(), |mut b, e| {b |= &e.receipt.log_bloom; b}); s.block.header.log_bloom = s.block.archive.iter().fold(LogBloom::zero(), |mut b, e| {b |= &e.receipt.log_bloom; b});
s.block.header.gas_used = s.block.archive.last().map(|t| t.receipt.gas_used).unwrap_or(U256::from(0)); s.block.header.gas_used = s.block.archive.last().map(|t| t.receipt.gas_used).unwrap_or(U256::from(0));
s.block.header.note_dirty(); s.block.header.note_dirty();
@ -143,29 +177,47 @@ impl<'engine> IsBlock for OpenBlock<'engine> {
fn block(&self) -> &Block { &self.block } fn block(&self) -> &Block { &self.block }
} }
impl<'engine> ClosedBlock<'engine> {
fn new<'a>(open_block: OpenBlock<'a>, uncles: Bytes) -> ClosedBlock<'a> {
ClosedBlock {
open_block: open_block,
uncles: uncles,
}
}
/// Get the hash of the header without seal arguments.
pub fn preseal_hash(&self) -> H256 { unimplemented!(); }
/// Turn this into a `ClosedBlock`. A BlockChain must be provided in order to figure ou the uncles.
pub fn seal(self, _seal_fields: Vec<Bytes>) -> SealedBlock { unimplemented!(); }
/// Turn this back into an `OpenBlock`.
pub fn reopen(self) -> OpenBlock<'engine> { unimplemented!(); }
}
impl<'engine> IsBlock for ClosedBlock<'engine> { impl<'engine> IsBlock for ClosedBlock<'engine> {
fn block(&self) -> &Block { &self.open_block.block } fn block(&self) -> &Block { &self.open_block.block }
} }
impl<'engine> ClosedBlock<'engine> {
fn new<'a>(open_block: OpenBlock<'a>, uncle_bytes: Bytes) -> ClosedBlock<'a> {
ClosedBlock {
open_block: open_block,
uncle_bytes: uncle_bytes,
}
}
/// Get the hash of the header without seal arguments.
pub fn hash(&self) -> H256 { self.header().rlp_sha3(Seal::Without) }
/// Provide a valid seal in order to turn this into a `SealedBlock`.
///
/// NOTE: This does not check the validity of `seal` with the engine.
pub fn seal(self, seal: Vec<Bytes>) -> Result<SealedBlock, BlockError> {
let mut s = self;
if seal.len() != s.open_block.engine.seal_fields() {
return Err(BlockError::InvalidSealArity(Mismatch{expected: s.open_block.engine.seal_fields(), found: seal.len()}));
}
s.open_block.block.header.set_seal(seal);
Ok(SealedBlock { block: s.open_block.block, uncle_bytes: s.uncle_bytes })
}
/// Turn this back into an `OpenBlock`.
pub fn reopen(self) -> OpenBlock<'engine> { self.open_block }
}
impl SealedBlock { impl SealedBlock {
/// Get the RLP-encoding of the block.
pub fn rlp_bytes(&self) -> Bytes {
let mut block_rlp = RlpStream::new_list(3);
self.block.header.stream_rlp(&mut block_rlp, Seal::With);
block_rlp.append_list(self.block.archive.len());
for e in self.block.archive.iter() { e.transaction.rlp_append(&mut block_rlp); }
block_rlp.append_raw(&self.uncle_bytes, 1);
block_rlp.out()
}
} }
impl IsBlock for SealedBlock { impl IsBlock for SealedBlock {
@ -175,12 +227,11 @@ impl IsBlock for SealedBlock {
#[test] #[test]
fn open_block() { fn open_block() {
use spec::*; use spec::*;
use ethereum::*; let engine = Spec::new_test().to_engine().unwrap();
let engine = new_morden().to_engine().unwrap();
let genesis_header = engine.spec().genesis_header(); let genesis_header = engine.spec().genesis_header();
let mut db = OverlayDB::new_temp(); let mut db = OverlayDB::new_temp();
engine.spec().ensure_db_good(&mut db); engine.spec().ensure_db_good(&mut db);
let b = OpenBlock::new(engine.deref(), db, &genesis_header, vec![genesis_header.hash()]); let b = OpenBlock::new(engine.deref(), db, &genesis_header, vec![genesis_header.hash()], Address::zero(), vec![]);
let b = b.close(vec![], Address::zero(), vec![]); let b = b.close();
assert_eq!(b.state().balance(&Address::zero()), U256::from_str("4563918244F40000").unwrap()); let _ = b.seal(vec![]);
} }

View File

@ -1,5 +1,6 @@
pub use util::*; pub use util::*;
pub use basic_types::*; pub use basic_types::*;
pub use error::*;
pub use env_info::*; pub use env_info::*;
pub use evm_schedule::*; pub use evm_schedule::*;
pub use views::*; pub use views::*;
@ -7,4 +8,4 @@ pub use builtin::*;
pub use header::*; pub use header::*;
pub use account::*; pub use account::*;
pub use transaction::*; pub use transaction::*;
pub use receipt::*; pub use receipt::*;

View File

@ -11,7 +11,7 @@ pub trait Engine {
fn version(&self) -> SemanticVersion { SemanticVersion::new(0, 0, 0) } fn version(&self) -> SemanticVersion { SemanticVersion::new(0, 0, 0) }
/// The number of additional header fields required for this engine. /// The number of additional header fields required for this engine.
fn seal_fields(&self) -> u32 { 0 } fn seal_fields(&self) -> usize { 0 }
/// Default values of the additional fields RLP-encoded in a raw (non-list) harness. /// Default values of the additional fields RLP-encoded in a raw (non-list) harness.
fn seal_rlp(&self) -> Bytes { vec![] } fn seal_rlp(&self) -> Bytes { vec![] }
@ -25,7 +25,8 @@ pub trait Engine {
fn evm_schedule(&self, env_info: &EnvInfo) -> EvmSchedule; fn evm_schedule(&self, env_info: &EnvInfo) -> EvmSchedule;
/// Some intrinsic operation parameters; by default they take their value from the `spec()`'s `engine_params`. /// Some intrinsic operation parameters; by default they take their value from the `spec()`'s `engine_params`.
fn maximum_extra_data_size(&self, _env_info: &EnvInfo) -> usize { decode(&self.spec().engine_params.get("maximumExtraDataSize").unwrap()) } fn maximum_extra_data_size(&self) -> usize { decode(&self.spec().engine_params.get("maximumExtraDataSize").unwrap()) }
fn maximum_uncle_count(&self) -> usize { 2 }
fn account_start_nonce(&self) -> U256 { decode(&self.spec().engine_params.get("accountStartNonce").unwrap()) } fn account_start_nonce(&self) -> U256 { decode(&self.spec().engine_params.get("accountStartNonce").unwrap()) }
/// Block transformation functions, before and after the transactions. /// Block transformation functions, before and after the transactions.
@ -36,12 +37,12 @@ pub trait Engine {
/// `parent` (the parent header) and `block` (the header's full block) may be provided for additional /// `parent` (the parent header) and `block` (the header's full block) may be provided for additional
/// checks. Returns either a null `Ok` or a general error detailing the problem with import. /// checks. Returns either a null `Ok` or a general error detailing the problem with import.
// TODO: consider including State in the params. // TODO: consider including State in the params.
fn verify_block(&self, _header: &Header, _parent: Option<&Header>, _block: Option<&[u8]>) -> Result<(), EthcoreError> { Ok(()) } fn verify_block(&self, _header: &Header, _parent: Option<&Header>, _block: Option<&[u8]>) -> Result<(), Error> { Ok(()) }
/// Additional verification for transactions in blocks. /// Additional verification for transactions in blocks.
// TODO: Add flags for which bits of the transaction to check. // TODO: Add flags for which bits of the transaction to check.
// TODO: consider including State in the params. // TODO: consider including State in the params.
fn verify_transaction(&self, _t: &Transaction, _header: &Header) -> Result<(), EthcoreError> { Ok(()) } fn verify_transaction(&self, _t: &Transaction, _header: &Header) -> Result<(), Error> { Ok(()) }
/// Don't forget to call Super::populateFromParent when subclassing & overriding. /// Don't forget to call Super::populateFromParent when subclassing & overriding.
// TODO: consider including State in the params. // TODO: consider including State in the params.

52
src/error.rs Normal file
View File

@ -0,0 +1,52 @@
//! General error types for use in ethcore.
use util::*;
#[derive(Debug)]
pub struct Mismatch<T: fmt::Debug> {
pub expected: T,
pub found: T,
}
#[derive(Debug)]
pub struct OutOfBounds<T: fmt::Debug> {
pub min: T,
pub max: T,
pub found: T,
}
#[derive(Debug)]
pub enum BlockError {
TooManyUncles,
UncleWrongGeneration,
ExtraDataOutOfBounds(OutOfBounds<usize>),
InvalidSealArity(Mismatch<usize>),
}
#[derive(Debug)]
/// General error type which should be capable of representing all errors in ethcore.
pub enum Error {
Util(UtilError),
Block(BlockError),
UnknownEngineName(String),
}
impl From<BlockError> for Error {
fn from(err: BlockError) -> Error {
Error::Block(err)
}
}
// TODO: uncomment below once https://github.com/rust-lang/rust/issues/27336 sorted.
/*#![feature(concat_idents)]
macro_rules! assimilate {
($name:ident) => (
impl From<concat_idents!($name, Error)> for Error {
fn from(err: concat_idents!($name, Error)) -> Error {
Error:: $name (err)
}
}
)
}
assimilate!(FromHex);
assimilate!(BaseData);*/

View File

@ -34,7 +34,7 @@ fn on_close_block() {
let genesis_header = engine.spec().genesis_header(); let genesis_header = engine.spec().genesis_header();
let mut db = OverlayDB::new_temp(); let mut db = OverlayDB::new_temp();
engine.spec().ensure_db_good(&mut db); engine.spec().ensure_db_good(&mut db);
let b = OpenBlock::new(engine.deref(), db, &genesis_header, vec![genesis_header.hash()]); let b = OpenBlock::new(engine.deref(), db, &genesis_header, vec![genesis_header.hash()], Address::zero(), vec![]);
let b = b.close(vec![], Address::zero(), vec![]); let b = b.close();
assert_eq!(b.state().balance(&Address::zero()), U256::from_str("4563918244F40000").unwrap()); assert_eq!(b.state().balance(&Address::zero()), U256::from_str("4563918244F40000").unwrap());
} }

View File

@ -9,6 +9,7 @@ use basic_types::*;
/// Doesn't do all that much on its own. /// Doesn't do all that much on its own.
#[derive(Debug)] #[derive(Debug)]
pub struct Header { pub struct Header {
// TODO: make all private.
pub parent_hash: H256, pub parent_hash: H256,
pub timestamp: U256, pub timestamp: U256,
pub number: U256, pub number: U256,
@ -27,7 +28,7 @@ pub struct Header {
pub difficulty: U256, pub difficulty: U256,
pub seal: Vec<Bytes>, pub seal: Vec<Bytes>,
pub hash: RefCell<Option<H256>>, //TODO: make this private pub hash: RefCell<Option<H256>>,
} }
pub enum Seal { pub enum Seal {
@ -60,6 +61,16 @@ impl Header {
} }
} }
pub fn author(&self) -> &Address { &self.author }
pub fn extra_data(&self) -> &Bytes { &self.extra_data }
pub fn seal(&self) -> &Vec<Bytes> { &self.seal }
// TODO: seal_at, set_seal_at &c.
pub fn set_author(&mut self, a: Address) { if a != self.author { self.author = a; self.note_dirty(); } }
pub fn set_extra_data(&mut self, a: Bytes) { if a != self.extra_data { self.extra_data = a; self.note_dirty(); } }
pub fn set_seal(&mut self, a: Vec<Bytes>) { self.seal = a; self.note_dirty(); }
/// Get the hash of this header (sha3 of the RLP). /// Get the hash of this header (sha3 of the RLP).
pub fn hash(&self) -> H256 { pub fn hash(&self) -> H256 {
let mut hash = self.hash.borrow_mut(); let mut hash = self.hash.borrow_mut();
@ -163,28 +174,6 @@ impl Encodable for Header {
}) })
} }
} }
/*
trait RlpStandard {
fn append(&self, s: &mut RlpStream);
}
impl RlpStandard for Header {
fn append(&self, s: &mut RlpStream) {
s.append_list(13);
s.append(self.parent_hash);
s.append_raw(self.seal[0]);
s.append_standard(self.x);
}
fn populate(&mut self, s: &Rlp) {
}
}
impl RlpStream {
fn append_standard<O>(&mut self, o: &O) where O: RlpStandard {
o.append(self);
}
}
*/
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {

View File

@ -86,6 +86,7 @@ extern crate ethcore_util as util;
pub mod common; pub mod common;
pub mod basic_types; pub mod basic_types;
pub mod error;
pub mod env_info; pub mod env_info;
pub mod engine; pub mod engine;
pub mod state; pub mod state;

View File

@ -1,10 +1,47 @@
use util::*; use util::*;
use basic_types::LogBloom; use basic_types::LogBloom;
/// A single log's entry.
pub struct LogEntry {
pub address: Address,
pub topics: Vec<H256>,
pub data: Bytes,
}
impl RlpStandard for LogEntry {
fn rlp_append(&self, s: &mut RlpStream) {
s.append_list(3);
s.append(&self.address);
s.append(&self.topics);
s.append(&self.data);
}
}
impl LogEntry {
pub fn bloom(&self) -> LogBloom {
self.topics.iter().fold(LogBloom::from_bloomed(&self.address.sha3()), |b, t| b.with_bloomed(&t.sha3()))
}
}
/// Information describing execution of a transaction. /// Information describing execution of a transaction.
pub struct Receipt { pub struct Receipt {
// TODO
pub state_root: H256, pub state_root: H256,
pub gas_used: U256, pub gas_used: U256,
pub log_bloom: LogBloom, pub log_bloom: LogBloom,
pub logs: Vec<LogEntry>,
}
impl RlpStandard for Receipt {
fn rlp_append(&self, s: &mut RlpStream) {
s.append_list(4);
s.append(&self.state_root);
s.append(&self.gas_used);
s.append(&self.log_bloom);
// TODO: make work:
//s.append(&self.logs);
s.append_list(self.logs.len());
for l in self.logs.iter() {
l.rlp_append(s);
}
}
} }

View File

@ -73,11 +73,11 @@ pub struct Spec {
impl Spec { impl Spec {
/// Convert this object into a boxed Engine of the right underlying type. /// Convert this object into a boxed Engine of the right underlying type.
// TODO avoid this hard-coded nastiness - use dynamic-linked plugin framework instead. // TODO avoid this hard-coded nastiness - use dynamic-linked plugin framework instead.
pub fn to_engine(self) -> Result<Box<Engine>, EthcoreError> { pub fn to_engine(self) -> Result<Box<Engine>, Error> {
match self.engine_name.as_ref() { match self.engine_name.as_ref() {
"NullEngine" => Ok(NullEngine::new_boxed(self)), "NullEngine" => Ok(NullEngine::new_boxed(self)),
"Ethash" => Ok(super::ethereum::Ethash::new_boxed(self)), "Ethash" => Ok(super::ethereum::Ethash::new_boxed(self)),
_ => Err(EthcoreError::UnknownName) _ => Err(Error::UnknownEngineName(self.engine_name.clone()))
} }
} }

View File

@ -1,8 +1,4 @@
use util::*; use common::*;
use account::Account;
use transaction::Transaction;
use receipt::Receipt;
use env_info::EnvInfo;
use engine::Engine; use engine::Engine;
/// Information concerning the result of the `State::apply` operation. /// Information concerning the result of the `State::apply` operation.
@ -10,7 +6,7 @@ pub struct ApplyInfo {
pub receipt: Receipt, pub receipt: Receipt,
} }
pub type ApplyResult = Result<ApplyInfo, EthcoreError>; pub type ApplyResult = Result<ApplyInfo, Error>;
/// Representation of the entire state of all accounts in the system. /// Representation of the entire state of all accounts in the system.
pub struct State { pub struct State {

View File

@ -9,6 +9,8 @@ pub struct Transaction {
to: Option<Address>, to: Option<Address>,
value: U256, value: U256,
data: Bytes, data: Bytes,
hash: RefCell<Option<H256>>, //TODO: make this private
} }
impl Transaction { impl Transaction {
@ -21,10 +23,36 @@ impl Transaction {
pub fn is_message_call(&self) -> bool { pub fn is_message_call(&self) -> bool {
!self.is_contract_creation() !self.is_contract_creation()
} }
}
/// Get the hash of this transaction. impl RlpStandard for Transaction {
pub fn sha3(&self) -> H256 { fn rlp_append(&self, s: &mut RlpStream) {
unimplemented!(); s.append_list(6);
s.append(&self.nonce);
s.append(&self.gas_price);
s.append(&self.gas);
s.append(&self.to);
s.append(&self.value);
s.append(&self.data);
}
}
impl Transaction {
/// Get the hash of this header (sha3 of the RLP).
pub fn hash(&self) -> H256 {
let mut hash = self.hash.borrow_mut();
match &mut *hash {
&mut Some(ref h) => h.clone(),
hash @ &mut None => {
*hash = Some(self.rlp_sha3());
hash.as_ref().unwrap().clone()
}
}
}
/// Note that some fields have changed. Resets the memoised hash.
pub fn note_dirty(&self) {
*self.hash.borrow_mut() = None;
} }
} }
@ -52,6 +80,7 @@ impl Decodable for Transaction {
to: try!(Decodable::decode(&d[3])), to: try!(Decodable::decode(&d[3])),
value: try!(Decodable::decode(&d[4])), value: try!(Decodable::decode(&d[4])),
data: try!(Decodable::decode(&d[5])), data: try!(Decodable::decode(&d[5])),
hash: RefCell::new(None)
}; };
Ok(transaction) Ok(transaction)