Merge branch 'master' of github.com:gavofyork/ethcore into verification
This commit is contained in:
commit
daab45f599
7
src/basic_types.rs
Normal file
7
src/basic_types.rs
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
use util::*;
|
||||||
|
|
||||||
|
/// Type for a 2048-bit log-bloom, as used by our blocks.
|
||||||
|
pub type LogBloom = H2048;
|
||||||
|
|
||||||
|
/// Constant 2048-bit datum for 0. Often used as a default.
|
||||||
|
pub static ZERO_LOGBLOOM: LogBloom = H2048([0x00; 256]);
|
126
src/block.rs
126
src/block.rs
@ -1,9 +1,5 @@
|
|||||||
use util::*;
|
use common::*;
|
||||||
use transaction::*;
|
|
||||||
use receipt::*;
|
|
||||||
use engine::*;
|
use engine::*;
|
||||||
use header::*;
|
|
||||||
use env_info::*;
|
|
||||||
use state::*;
|
use state::*;
|
||||||
|
|
||||||
/// A transaction/receipt execution entry.
|
/// A transaction/receipt execution entry.
|
||||||
@ -21,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 {
|
||||||
@ -30,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(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -49,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 {
|
||||||
@ -71,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: Vec<Header>,
|
uncle_bytes: Bytes,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A block that has a valid seal.
|
/// A block that has a valid seal.
|
||||||
@ -79,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.
|
||||||
@ -105,17 +136,19 @@ impl<'engine> OpenBlock<'engine> {
|
|||||||
timestamp: self.block.header.timestamp.clone(),
|
timestamp: self.block.header.timestamp.clone(),
|
||||||
difficulty: self.block.header.difficulty.clone(),
|
difficulty: self.block.header.difficulty.clone(),
|
||||||
last_hashes: self.last_hashes.clone(),
|
last_hashes: self.last_hashes.clone(),
|
||||||
gas_used: if let Some(ref t) = self.block.archive.last() {t.receipt.gas_used} else {U256::from(0)},
|
gas_used: self.block.archive.last().map(|t| t.receipt.gas_used).unwrap_or(U256::from(0)),
|
||||||
gas_limit: self.block.header.gas_limit.clone(),
|
gas_limit: self.block.header.gas_limit.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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)
|
||||||
}
|
}
|
||||||
@ -123,30 +156,68 @@ impl<'engine> OpenBlock<'engine> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Turn this into a `ClosedBlock`. A BlockChain must be provided in order to figure ou 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>) -> ClosedBlock<'engine> { unimplemented!(); }
|
pub fn close(self) -> ClosedBlock<'engine> {
|
||||||
|
let mut s = self;
|
||||||
|
s.engine.on_close_block(&mut s.block);
|
||||||
|
s.block.header.transactions_root = ordered_trie_root(s.block.archive.iter().map(|ref e| e.transaction.rlp_bytes()).collect());
|
||||||
|
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();
|
||||||
|
s.block.header.uncles_hash = uncle_bytes.sha3();
|
||||||
|
s.block.header.state_root = s.block.state.root().clone();
|
||||||
|
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.gas_used = s.block.archive.last().map(|t| t.receipt.gas_used).unwrap_or(U256::from(0));
|
||||||
|
s.block.header.note_dirty();
|
||||||
|
|
||||||
|
ClosedBlock::new(s, uncle_bytes)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'engine> IsBlock for OpenBlock<'engine> {
|
impl<'engine> IsBlock for OpenBlock<'engine> {
|
||||||
fn block(&self) -> &Block { &self.block }
|
fn block(&self) -> &Block { &self.block }
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'engine> ClosedBlock<'engine> {
|
|
||||||
/// 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 {
|
||||||
@ -155,11 +226,12 @@ impl IsBlock for SealedBlock {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn open_block() {
|
fn open_block() {
|
||||||
use super::*;
|
|
||||||
use spec::*;
|
use spec::*;
|
||||||
let engine = Spec::new_test().to_engine().unwrap();
|
let engine = Spec::new_test().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();
|
||||||
|
let _ = b.seal(vec![]);
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
pub use util::*;
|
pub use util::*;
|
||||||
|
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::*;
|
||||||
@ -6,3 +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::*;
|
@ -12,7 +12,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![] }
|
||||||
|
|
||||||
@ -27,6 +27,7 @@ pub trait Engine {
|
|||||||
|
|
||||||
/// 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) -> 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.
|
||||||
@ -37,12 +38,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, _mode: VerificationMode, _header: &Header, _parent: Option<&Header>, _block: Option<&[u8]>) -> Result<(), VerificationError> { Ok(()) }
|
fn verify_block(&self, _mode: VerificationMode, _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<(), VerificationError> { 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
52
src/error.rs
Normal 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);*/
|
@ -24,7 +24,7 @@ impl Engine for Ethash {
|
|||||||
/// Apply the block reward on finalisation of the block.
|
/// Apply the block reward on finalisation of the block.
|
||||||
fn on_close_block(&self, block: &mut Block) {
|
fn on_close_block(&self, block: &mut Block) {
|
||||||
let a = block.header().author.clone();
|
let a = block.header().author.clone();
|
||||||
block.state_mut().add_balance(&a, &decode(&self.spec().engine_params.get("block_reward").unwrap()));
|
block.state_mut().add_balance(&a, &decode(&self.spec().engine_params.get("blockReward").unwrap()));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn verify_block(&self, mode: VerificationMode, header: &Header, parent: Option<&Header>, block: Option<&[u8]>) -> Result<(), VerificationError> {
|
fn verify_block(&self, mode: VerificationMode, header: &Header, parent: Option<&Header>, block: Option<&[u8]>) -> Result<(), VerificationError> {
|
||||||
@ -113,11 +113,9 @@ impl Ethash {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: test for on_close_block.
|
|
||||||
#[test]
|
#[test]
|
||||||
fn playpen() {
|
fn on_close_block() {
|
||||||
use super::*;
|
use super::*;
|
||||||
use state::*;
|
|
||||||
let engine = new_morden().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();
|
||||||
|
@ -56,7 +56,7 @@ mod tests {
|
|||||||
let genesis = morden.genesis_block();
|
let genesis = morden.genesis_block();
|
||||||
assert_eq!(BlockView::new(&genesis).header_view().sha3(), H256::from_str("0cd786a2425d16f152c658316c423e6ce1181e15c3295826d7c9904cba9ce303").unwrap());
|
assert_eq!(BlockView::new(&genesis).header_view().sha3(), H256::from_str("0cd786a2425d16f152c658316c423e6ce1181e15c3295826d7c9904cba9ce303").unwrap());
|
||||||
|
|
||||||
morden.to_engine();
|
let _ = morden.to_engine();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -67,6 +67,6 @@ mod tests {
|
|||||||
let genesis = frontier.genesis_block();
|
let genesis = frontier.genesis_block();
|
||||||
assert_eq!(BlockView::new(&genesis).header_view().sha3(), H256::from_str("d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3").unwrap());
|
assert_eq!(BlockView::new(&genesis).header_view().sha3(), H256::from_str("d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3").unwrap());
|
||||||
|
|
||||||
frontier.to_engine();
|
let _ = frontier.to_engine();
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,14 +1,5 @@
|
|||||||
use util::*;
|
use util::*;
|
||||||
|
use basic_types::*;
|
||||||
/// Type for a 2048-bit log-bloom, as used by our blocks.
|
|
||||||
pub type LogBloom = H2048;
|
|
||||||
|
|
||||||
/// Constant address for point 0. Often used as a default.
|
|
||||||
pub static ZERO_ADDRESS: Address = Address([0x00; 20]);
|
|
||||||
/// Constant 256-bit datum for 0. Often used as a default.
|
|
||||||
pub static ZERO_H256: H256 = H256([0x00; 32]);
|
|
||||||
/// Constant 2048-bit datum for 0. Often used as a default.
|
|
||||||
pub static ZERO_LOGBLOOM: LogBloom = H2048([0x00; 256]);
|
|
||||||
|
|
||||||
/// A block header.
|
/// A block header.
|
||||||
///
|
///
|
||||||
@ -18,6 +9,7 @@ pub static ZERO_LOGBLOOM: LogBloom = H2048([0x00; 256]);
|
|||||||
/// 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,
|
||||||
@ -36,7 +28,12 @@ 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 {
|
||||||
|
With,
|
||||||
|
Without,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Header {
|
impl Header {
|
||||||
@ -64,19 +61,64 @@ 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).
|
||||||
pub fn hash(&self) -> H256 {
|
pub fn hash(&self) -> H256 {
|
||||||
let mut hash = self.hash.borrow_mut();
|
let mut hash = self.hash.borrow_mut();
|
||||||
match &mut *hash {
|
match &mut *hash {
|
||||||
&mut Some(ref h) => h.clone(),
|
&mut Some(ref h) => h.clone(),
|
||||||
hash @ &mut None => {
|
hash @ &mut None => {
|
||||||
let mut stream = RlpStream::new();
|
*hash = Some(self.rlp_sha3(Seal::With));
|
||||||
stream.append(self);
|
hash.as_ref().unwrap().clone()
|
||||||
let h = stream.as_raw().sha3();
|
|
||||||
*hash = Some(h.clone());
|
|
||||||
h.clone()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Note that some fields have changed. Resets the memoised hash.
|
||||||
|
pub fn note_dirty(&self) {
|
||||||
|
*self.hash.borrow_mut() = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: get hash without seal.
|
||||||
|
|
||||||
|
// TODO: make these functions traity
|
||||||
|
pub fn stream_rlp(&self, s: &mut RlpStream, with_seal: Seal) {
|
||||||
|
s.append_list(13 + match with_seal { Seal::With => self.seal.len(), _ => 0 });
|
||||||
|
s.append(&self.parent_hash);
|
||||||
|
s.append(&self.uncles_hash);
|
||||||
|
s.append(&self.author);
|
||||||
|
s.append(&self.state_root);
|
||||||
|
s.append(&self.transactions_root);
|
||||||
|
s.append(&self.receipts_root);
|
||||||
|
s.append(&self.log_bloom);
|
||||||
|
s.append(&self.difficulty);
|
||||||
|
s.append(&self.number);
|
||||||
|
s.append(&self.gas_limit);
|
||||||
|
s.append(&self.gas_used);
|
||||||
|
s.append(&self.timestamp);
|
||||||
|
s.append(&self.extra_data);
|
||||||
|
match with_seal {
|
||||||
|
Seal::With => for b in self.seal.iter() { s.append_raw(&b, 1); },
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn rlp(&self, with_seal: Seal) -> Bytes {
|
||||||
|
let mut s = RlpStream::new();
|
||||||
|
self.stream_rlp(&mut s, with_seal);
|
||||||
|
s.out()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn rlp_sha3(&self, with_seal: Seal) -> H256 { self.rlp(with_seal).sha3() }
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Decodable for Header {
|
impl Decodable for Header {
|
||||||
@ -132,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 {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
#![feature(cell_extras)]
|
#![feature(cell_extras)]
|
||||||
|
#![feature(augmented_assignments)]
|
||||||
//! Ethcore's ethereum implementation
|
//! Ethcore's ethereum implementation
|
||||||
//!
|
//!
|
||||||
//! ### Rust version
|
//! ### Rust version
|
||||||
@ -85,6 +85,8 @@ extern crate evmjit;
|
|||||||
extern crate ethcore_util as util;
|
extern crate ethcore_util as util;
|
||||||
|
|
||||||
pub mod common;
|
pub mod common;
|
||||||
|
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;
|
||||||
|
@ -1,8 +1,47 @@
|
|||||||
use util::*;
|
use util::*;
|
||||||
|
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 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
64
src/state.rs
64
src/state.rs
@ -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 {
|
||||||
@ -79,6 +75,11 @@ impl State {
|
|||||||
self.require_or_from(contract, false, || Account::new_contract(U256::from(0u8)), |r| r.reset_code());
|
self.require_or_from(contract, false, || Account::new_contract(U256::from(0u8)), |r| r.reset_code());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Remove an existing account.
|
||||||
|
pub fn kill_account(&mut self, account: &Address) {
|
||||||
|
self.cache.borrow_mut().insert(account.clone(), None);
|
||||||
|
}
|
||||||
|
|
||||||
/// Get the balance of account `a`.
|
/// Get the balance of account `a`.
|
||||||
pub fn balance(&self, a: &Address) -> U256 {
|
pub fn balance(&self, a: &Address) -> U256 {
|
||||||
self.get(a, false).as_ref().map(|account| account.balance().clone()).unwrap_or(U256::from(0u8))
|
self.get(a, false).as_ref().map(|account| account.balance().clone()).unwrap_or(U256::from(0u8))
|
||||||
@ -228,12 +229,11 @@ use util::hash::*;
|
|||||||
use util::trie::*;
|
use util::trie::*;
|
||||||
use util::rlp::*;
|
use util::rlp::*;
|
||||||
use util::uint::*;
|
use util::uint::*;
|
||||||
use std::str::FromStr;
|
|
||||||
use account::*;
|
use account::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn code_from_database() {
|
fn code_from_database() {
|
||||||
let a = Address::from_str("0000000000000000000000000000000000000000").unwrap();
|
let a = Address::zero();
|
||||||
let (r, db) = {
|
let (r, db) = {
|
||||||
let mut s = State::new_temp();
|
let mut s = State::new_temp();
|
||||||
s.require_or_from(&a, false, ||Account::new_contract(U256::from(42u32)), |_|{});
|
s.require_or_from(&a, false, ||Account::new_contract(U256::from(42u32)), |_|{});
|
||||||
@ -250,7 +250,7 @@ fn code_from_database() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn storage_at_from_database() {
|
fn storage_at_from_database() {
|
||||||
let a = Address::from_str("0000000000000000000000000000000000000000").unwrap();
|
let a = Address::zero();
|
||||||
let (r, db) = {
|
let (r, db) = {
|
||||||
let mut s = State::new_temp();
|
let mut s = State::new_temp();
|
||||||
s.set_storage(&a, H256::from(&U256::from(01u64)), H256::from(&U256::from(69u64)));
|
s.set_storage(&a, H256::from(&U256::from(01u64)), H256::from(&U256::from(69u64)));
|
||||||
@ -264,7 +264,7 @@ fn storage_at_from_database() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn get_from_database() {
|
fn get_from_database() {
|
||||||
let a = Address::from_str("0000000000000000000000000000000000000000").unwrap();
|
let a = Address::zero();
|
||||||
let (r, db) = {
|
let (r, db) = {
|
||||||
let mut s = State::new_temp();
|
let mut s = State::new_temp();
|
||||||
s.inc_nonce(&a);
|
s.inc_nonce(&a);
|
||||||
@ -279,11 +279,45 @@ fn get_from_database() {
|
|||||||
assert_eq!(s.nonce(&a), U256::from(1u64));
|
assert_eq!(s.nonce(&a), U256::from(1u64));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn remove() {
|
||||||
|
let a = Address::zero();
|
||||||
|
let mut s = State::new_temp();
|
||||||
|
s.inc_nonce(&a);
|
||||||
|
assert_eq!(s.nonce(&a), U256::from(1u64));
|
||||||
|
s.kill_account(&a);
|
||||||
|
assert_eq!(s.nonce(&a), U256::from(0u64));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn remove_from_database() {
|
||||||
|
let a = Address::zero();
|
||||||
|
let (r, db) = {
|
||||||
|
let mut s = State::new_temp();
|
||||||
|
s.inc_nonce(&a);
|
||||||
|
s.commit();
|
||||||
|
assert_eq!(s.nonce(&a), U256::from(1u64));
|
||||||
|
s.drop()
|
||||||
|
};
|
||||||
|
|
||||||
|
let (r, db) = {
|
||||||
|
let mut s = State::from_existing(db, r, U256::from(0u8));
|
||||||
|
assert_eq!(s.nonce(&a), U256::from(1u64));
|
||||||
|
s.kill_account(&a);
|
||||||
|
s.commit();
|
||||||
|
assert_eq!(s.nonce(&a), U256::from(0u64));
|
||||||
|
s.drop()
|
||||||
|
};
|
||||||
|
|
||||||
|
let s = State::from_existing(db, r, U256::from(0u8));
|
||||||
|
assert_eq!(s.nonce(&a), U256::from(0u64));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn alter_balance() {
|
fn alter_balance() {
|
||||||
let mut s = State::new_temp();
|
let mut s = State::new_temp();
|
||||||
let a = Address::from_str("0000000000000000000000000000000000000000").unwrap();
|
let a = Address::zero();
|
||||||
let b = Address::from_str("0000000000000000000000000000000000000001").unwrap();
|
let b = address_from_u64(1u64);
|
||||||
s.add_balance(&a, &U256::from(69u64));
|
s.add_balance(&a, &U256::from(69u64));
|
||||||
assert_eq!(s.balance(&a), U256::from(69u64));
|
assert_eq!(s.balance(&a), U256::from(69u64));
|
||||||
s.commit();
|
s.commit();
|
||||||
@ -303,7 +337,7 @@ fn alter_balance() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn alter_nonce() {
|
fn alter_nonce() {
|
||||||
let mut s = State::new_temp();
|
let mut s = State::new_temp();
|
||||||
let a = Address::from_str("0000000000000000000000000000000000000000").unwrap();
|
let a = Address::zero();
|
||||||
s.inc_nonce(&a);
|
s.inc_nonce(&a);
|
||||||
assert_eq!(s.nonce(&a), U256::from(1u64));
|
assert_eq!(s.nonce(&a), U256::from(1u64));
|
||||||
s.inc_nonce(&a);
|
s.inc_nonce(&a);
|
||||||
@ -319,7 +353,7 @@ fn alter_nonce() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn balance_nonce() {
|
fn balance_nonce() {
|
||||||
let mut s = State::new_temp();
|
let mut s = State::new_temp();
|
||||||
let a = Address::from_str("0000000000000000000000000000000000000000").unwrap();
|
let a = Address::zero();
|
||||||
assert_eq!(s.balance(&a), U256::from(0u64));
|
assert_eq!(s.balance(&a), U256::from(0u64));
|
||||||
assert_eq!(s.nonce(&a), U256::from(0u64));
|
assert_eq!(s.nonce(&a), U256::from(0u64));
|
||||||
s.commit();
|
s.commit();
|
||||||
@ -330,7 +364,7 @@ fn balance_nonce() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn ensure_cached() {
|
fn ensure_cached() {
|
||||||
let mut s = State::new_temp();
|
let mut s = State::new_temp();
|
||||||
let a = Address::from_str("0000000000000000000000000000000000000000").unwrap();
|
let a = Address::zero();
|
||||||
s.require(&a, false);
|
s.require(&a, false);
|
||||||
s.commit();
|
s.commit();
|
||||||
assert_eq!(s.root().hex(), "0ce23f3c809de377b008a4a3ee94a0834aac8bec1f86e28ffe4fdb5a15b0c785");
|
assert_eq!(s.root().hex(), "0ce23f3c809de377b008a4a3ee94a0834aac8bec1f86e28ffe4fdb5a15b0c785");
|
||||||
|
@ -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)
|
||||||
|
Loading…
Reference in New Issue
Block a user