diff --git a/Cargo.lock b/Cargo.lock index d4f626d92..4a52e1294 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -447,6 +447,9 @@ dependencies = [ "rlp 0.3.0", "rlp_derive", "rustc-hex 1.0.0", + "serde", + "serde_json", + "serde_repr", "unexpected", ] @@ -4274,6 +4277,17 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_repr" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dc6b7951b17b051f3210b063f12cc17320e2fe30ae05b0fe2a3abb068551c76" +dependencies = [ + "proc-macro2 1.0.20", + "quote 1.0.7", + "syn 1.0.40", +] + [[package]] name = "sha-1" version = "0.8.1" diff --git a/ethcore/blockchain/src/blockchain.rs b/ethcore/blockchain/src/blockchain.rs index c5872ac1f..e952d1938 100644 --- a/ethcore/blockchain/src/blockchain.rs +++ b/ethcore/blockchain/src/blockchain.rs @@ -34,7 +34,7 @@ use common_types::{ }, header::{ExtendedHeader, Header}, log_entry::{LocalizedLogEntry, LogEntry}, - receipt::Receipt, + receipt::TypedReceipt, transaction::LocalizedTransaction, tree_route::TreeRoute, view, @@ -460,13 +460,13 @@ impl BlockProvider for BlockChain { warn!("Block {} ({}) has different number of receipts ({}) to transactions ({}). Database corrupt?", number, hash, receipts.len(), hashes.len()); assert!(false); } - let mut log_index = receipts.iter().fold(0, |sum, receipt| sum + receipt.logs.len()); + let mut log_index = receipts.iter().fold(0, |sum, receipt| sum + receipt.receipt().logs.len()); let receipts_len = receipts.len(); hashes.reverse(); receipts.reverse(); receipts.into_iter() - .map(|receipt| receipt.logs) + .map(|receipt| receipt.receipt().logs.clone()) .zip(hashes) .enumerate() .flat_map(move |(index, (mut logs, tx_hash))| { @@ -895,7 +895,7 @@ impl BlockChain { &self, batch: &mut DBTransaction, block: encoded::Block, - receipts: Vec, + receipts: Vec, parent_td: Option, is_best: bool, is_ancient: bool, @@ -1265,7 +1265,7 @@ impl BlockChain { &self, batch: &mut DBTransaction, block: encoded::Block, - receipts: Vec, + receipts: Vec, extras: ExtrasInsert, ) -> ImportRoute { let parent_hash = block.header_view().parent_hash(); @@ -1283,7 +1283,7 @@ impl BlockChain { &self, batch: &mut DBTransaction, block: encoded::Block, - receipts: Vec, + receipts: Vec, route: TreeRoute, extras: ExtrasInsert, ) -> ImportRoute { @@ -1659,7 +1659,7 @@ impl BlockChain { /// This function returns modified block receipts. fn prepare_block_receipts_update( &self, - receipts: Vec, + receipts: Vec, info: &BlockInfo, ) -> HashMap { let mut block_receipts = HashMap::new(); @@ -1921,8 +1921,8 @@ mod tests { use crate::generator::{BlockBuilder, BlockGenerator, BlockOptions}; use common_types::{ - receipt::{Receipt, TransactionOutcome}, - transaction::{Action, Transaction}, + receipt::{LegacyReceipt, TransactionOutcome, TypedReceipt}, + transaction::{Action, Transaction, TypedTransaction}, }; use ethkey::Secret; use keccak_hash::keccak; @@ -1975,7 +1975,7 @@ mod tests { db: &Arc, bc: &BlockChain, block: encoded::Block, - receipts: Vec, + receipts: Vec, ) -> ImportRoute { insert_block_commit(db, bc, block, receipts, true) } @@ -1984,7 +1984,7 @@ mod tests { db: &Arc, bc: &BlockChain, block: encoded::Block, - receipts: Vec, + receipts: Vec, commit: bool, ) -> ImportRoute { let mut batch = db.key_value().transaction(); @@ -2000,7 +2000,7 @@ mod tests { batch: &mut DBTransaction, bc: &BlockChain, block: encoded::Block, - receipts: Vec, + receipts: Vec, ) -> ImportRoute { let fork_choice = { let header = block.header_view(); @@ -2157,7 +2157,7 @@ mod tests { #[test] fn test_fork_transaction_addresses() { - let t1 = Transaction { + let t1 = TypedTransaction::Legacy(Transaction { nonce: 0.into(), gas_price: 0.into(), gas: 100_000.into(), @@ -2166,7 +2166,7 @@ mod tests { data: "601080600c6000396000f3006000355415600957005b60203560003555" .from_hex() .unwrap(), - } + }) .sign(&secret(), None); let t1_hash = t1.hash(); @@ -2211,7 +2211,7 @@ mod tests { #[test] fn test_overwriting_transaction_addresses() { - let t1 = Transaction { + let t1 = TypedTransaction::Legacy(Transaction { nonce: 0.into(), gas_price: 0.into(), gas: 100_000.into(), @@ -2220,10 +2220,10 @@ mod tests { data: "601080600c6000396000f3006000355415600957005b60203560003555" .from_hex() .unwrap(), - } + }) .sign(&secret(), None); - let t2 = Transaction { + let t2 = TypedTransaction::Legacy(Transaction { nonce: 1.into(), gas_price: 0.into(), gas: 100_000.into(), @@ -2232,10 +2232,10 @@ mod tests { data: "601080600c6000396000f3006000355415600957005b60203560003555" .from_hex() .unwrap(), - } + }) .sign(&secret(), None); - let t3 = Transaction { + let t3 = TypedTransaction::Legacy(Transaction { nonce: 2.into(), gas_price: 0.into(), gas: 100_000.into(), @@ -2244,7 +2244,7 @@ mod tests { data: "601080600c6000396000f3006000355415600957005b60203560003555" .from_hex() .unwrap(), - } + }) .sign(&secret(), None); let genesis = BlockBuilder::genesis(); @@ -2509,7 +2509,7 @@ mod tests { #[test] fn test_logs() { - let t1 = Transaction { + let t1 = TypedTransaction::Legacy(Transaction { nonce: 0.into(), gas_price: 0.into(), gas: 100_000.into(), @@ -2518,9 +2518,9 @@ mod tests { data: "601080600c6000396000f3006000355415600957005b60203560003555" .from_hex() .unwrap(), - } + }) .sign(&secret(), None); - let t2 = Transaction { + let t2 = TypedTransaction::Legacy(Transaction { nonce: 0.into(), gas_price: 0.into(), gas: 100_000.into(), @@ -2529,9 +2529,9 @@ mod tests { data: "601080600c6000396000f3006000355415600957005b60203560003555" .from_hex() .unwrap(), - } + }) .sign(&secret(), None); - let t3 = Transaction { + let t3 = TypedTransaction::Legacy(Transaction { nonce: 0.into(), gas_price: 0.into(), gas: 100_000.into(), @@ -2540,9 +2540,9 @@ mod tests { data: "601080600c6000396000f3006000355415600957005b60203560003555" .from_hex() .unwrap(), - } + }) .sign(&secret(), None); - let t4 = Transaction { + let t4 = TypedTransaction::Legacy(Transaction { nonce: 0.into(), gas_price: 0.into(), gas: 100_000.into(), @@ -2551,7 +2551,7 @@ mod tests { data: "601080600c6000396000f3006000355415600957005b60203560003555" .from_hex() .unwrap(), - } + }) .sign(&secret(), None); let tx_hash1 = t1.hash(); let tx_hash2 = t2.hash(); @@ -2580,7 +2580,7 @@ mod tests { &bc, b1.last().encoded(), vec![ - Receipt { + TypedReceipt::Legacy(LegacyReceipt { outcome: TransactionOutcome::StateRoot(H256::default()), gas_used: 10_000.into(), log_bloom: Default::default(), @@ -2596,8 +2596,8 @@ mod tests { data: vec![2], }, ], - }, - Receipt { + }), + TypedReceipt::Legacy(LegacyReceipt { outcome: TransactionOutcome::StateRoot(H256::default()), gas_used: 10_000.into(), log_bloom: Default::default(), @@ -2606,14 +2606,14 @@ mod tests { topics: vec![], data: vec![3], }], - }, + }), ], ); insert_block( &db, &bc, b2.last().encoded(), - vec![Receipt { + vec![TypedReceipt::Legacy(LegacyReceipt { outcome: TransactionOutcome::StateRoot(H256::default()), gas_used: 10_000.into(), log_bloom: Default::default(), @@ -2622,13 +2622,13 @@ mod tests { topics: vec![], data: vec![4], }], - }], + })], ); insert_block( &db, &bc, b3.last().encoded(), - vec![Receipt { + vec![TypedReceipt::Legacy(LegacyReceipt { outcome: TransactionOutcome::StateRoot(H256::default()), gas_used: 10_000.into(), log_bloom: Default::default(), @@ -2637,7 +2637,7 @@ mod tests { topics: vec![], data: vec![5], }], - }], + })], ); // when diff --git a/ethcore/blockchain/src/generator.rs b/ethcore/blockchain/src/generator.rs index fc3fbd20d..817ecf223 100644 --- a/ethcore/blockchain/src/generator.rs +++ b/ethcore/blockchain/src/generator.rs @@ -22,17 +22,16 @@ use std::collections::VecDeque; use common_types::{ encoded, header::Header, - transaction::{Action, SignedTransaction, Transaction}, + transaction::{Action, SignedTransaction, Transaction, TypedTransaction}, view, views::BlockView, }; use keccak_hash::keccak; -use rlp::encode; -use rlp_derive::RlpEncodable; +use rlp::{encode, RlpStream}; use triehash_ethereum::ordered_trie_root; /// Helper structure, used for encoding blocks. -#[derive(Default, Clone, RlpEncodable)] +#[derive(Default, Clone)] pub struct Block { /// Block header pub header: Header, @@ -42,6 +41,15 @@ pub struct Block { pub uncles: Vec
, } +impl rlp::Encodable for Block { + fn rlp_append(&self, s: &mut RlpStream) { + s.begin_list(3); + s.append(&self.header); + SignedTransaction::rlp_append_list(s, &self.transactions); + s.append_list(&self.uncles); + } +} + impl Block { /// Get a copy of the header #[inline] @@ -154,14 +162,14 @@ impl BlockBuilder { let data = std::iter::repeat_with(|| rand::random::()) .take(data_len as usize) .collect::>(); - Transaction { + TypedTransaction::Legacy(Transaction { nonce: 0.into(), gas_price: 0.into(), gas: 100_000.into(), action: Action::Create, value: 100.into(), data, - } + }) .sign(&keccak("").into(), None) }) .take(count); @@ -205,7 +213,7 @@ impl BlockBuilder { let metadata = get_metadata(); let block_number = parent_number + 1; let transactions = metadata.transactions; - let transactions_root = ordered_trie_root(transactions.iter().map(rlp::encode)); + let transactions_root = ordered_trie_root(transactions.iter().map(|tx| tx.encode())); block.header.set_parent_hash(parent_hash); block.header.set_number(block_number); diff --git a/ethcore/db/src/keys.rs b/ethcore/db/src/keys.rs index bd7655990..ded42045f 100644 --- a/ethcore/db/src/keys.rs +++ b/ethcore/db/src/keys.rs @@ -18,12 +18,14 @@ use std::{io::Write, ops}; -use common_types::{engines::epoch::Transition as EpochTransition, receipt::Receipt, BlockNumber}; +use common_types::{ + engines::epoch::Transition as EpochTransition, receipt::TypedReceipt, BlockNumber, +}; use ethereum_types::{H256, H264, U256}; use heapsize::HeapSizeOf; use kvdb::PREFIX_LEN as DB_PREFIX_LEN; use rlp; -use rlp_derive::{RlpDecodable, RlpDecodableWrapper, RlpEncodable, RlpEncodableWrapper}; +use rlp_derive::{RlpDecodable, RlpEncodable}; use crate::db::Key; @@ -235,15 +237,27 @@ impl HeapSizeOf for TransactionAddress { } /// Contains all block receipts. -#[derive(Clone, RlpEncodableWrapper, RlpDecodableWrapper)] +#[derive(Clone)] pub struct BlockReceipts { /// Block receipts - pub receipts: Vec, + pub receipts: Vec, +} + +impl rlp::Encodable for BlockReceipts { + fn rlp_append(&self, s: &mut rlp::RlpStream) { + TypedReceipt::rlp_append_list(s, &self.receipts); + } +} + +impl rlp::Decodable for BlockReceipts { + fn decode(rlp: &rlp::Rlp) -> Result { + Ok(BlockReceipts::new(TypedReceipt::decode_rlp_list(rlp)?)) + } } impl BlockReceipts { /// Create new block receipts wrapper. - pub fn new(receipts: Vec) -> Self { + pub fn new(receipts: Vec) -> Self { BlockReceipts { receipts: receipts } } } diff --git a/ethcore/res/ethereum/yolo3_test.json b/ethcore/res/ethereum/yolo3_test.json index e50c2ace3..bb04f7748 100644 --- a/ethcore/res/ethereum/yolo3_test.json +++ b/ethcore/res/ethereum/yolo3_test.json @@ -44,7 +44,8 @@ "eip1884Transition": "0x0", "eip2028Transition": "0x0", "eip2315Transition": "0x0", - "eip2929Transition": "0x0" + "eip2929Transition": "0x0", + "eip2930Transition": "0x0" }, "genesis": { "seal": { diff --git a/ethcore/src/block.rs b/ethcore/src/block.rs index 57a3414d3..eee50c46d 100644 --- a/ethcore/src/block.rs +++ b/ethcore/src/block.rs @@ -48,10 +48,10 @@ use verification::PreverifiedBlock; use vm::{EnvInfo, LastHashes}; use hash::keccak; -use rlp::{encode_list, Encodable, RlpStream}; +use rlp::{encode_list, RlpStream}; use types::{ header::{ExtendedHeader, Header}, - receipt::{Receipt, TransactionOutcome}, + receipt::{TransactionOutcome, TypedReceipt}, transaction::{Error as TransactionError, SignedTransaction}, }; @@ -99,7 +99,7 @@ pub struct ExecutedBlock { /// Uncles. pub uncles: Vec
, /// Transaction receipts. - pub receipts: Vec, + pub receipts: Vec, /// Hashes of already executed transactions. pub transactions_set: HashSet, /// Underlaying state. @@ -253,7 +253,7 @@ impl<'x> OpenBlock<'x> { &mut self, t: SignedTransaction, h: Option, - ) -> Result<&Receipt, Error> { + ) -> Result<&TypedReceipt, Error> { if self.block.transactions_set.contains(&t.hash()) { return Err(TransactionError::AlreadyImported.into()); } @@ -360,13 +360,13 @@ impl<'x> OpenBlock<'x> { // t_nb 8.5.3 fill open block header with all other fields s.block.header.set_transactions_root(ordered_trie_root( - s.block.transactions.iter().map(|e| e.rlp_bytes()), + s.block.transactions.iter().map(|e| e.encode()), )); let uncle_bytes = encode_list(&s.block.uncles); s.block.header.set_uncles_hash(keccak(&uncle_bytes)); s.block.header.set_state_root(s.block.state.root().clone()); s.block.header.set_receipts_root(ordered_trie_root( - s.block.receipts.iter().map(|r| r.rlp_bytes()), + s.block.receipts.iter().map(|r| r.encode()), )); s.block .header @@ -453,7 +453,7 @@ impl LockedBlock { receipt.outcome = TransactionOutcome::Unknown; } self.block.header.set_receipts_root(ordered_trie_root( - self.block.receipts.iter().map(|r| r.rlp_bytes()), + self.block.receipts.iter().map(|r| r.encode()), )); } @@ -503,7 +503,7 @@ impl SealedBlock { pub fn rlp_bytes(&self) -> Bytes { let mut block_rlp = RlpStream::new_list(3); block_rlp.append(&self.block.header); - block_rlp.append_list(&self.block.transactions); + SignedTransaction::rlp_append_list(&mut block_rlp, &self.block.transactions); block_rlp.append_list(&self.block.uncles); block_rlp.out() } diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index 6a025ec2c..0a61d2007 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -41,7 +41,7 @@ use itertools::Itertools; use kvdb::{DBTransaction, DBValue, KeyValueDB}; use parking_lot::{Mutex, RwLock}; use rand::OsRng; -use rlp::PayloadInfo; +use rlp::{PayloadInfo, Rlp}; use rustc_hex::FromHex; use trie::{Trie, TrieFactory, TrieSpec}; use types::{ @@ -51,8 +51,11 @@ use types::{ filter::Filter, header::{ExtendedHeader, Header}, log_entry::LocalizedLogEntry, - receipt::{LocalizedReceipt, Receipt}, - transaction::{self, Action, LocalizedTransaction, SignedTransaction, UnverifiedTransaction}, + receipt::{LocalizedReceipt, TypedReceipt}, + transaction::{ + self, Action, LocalizedTransaction, SignedTransaction, TypedTransaction, + UnverifiedTransaction, + }, BlockNumber, }; use vm::{EnvInfo, LastHashes}; @@ -524,7 +527,8 @@ impl Importer { db: &dyn KeyValueDB, chain: &BlockChain, ) -> EthcoreResult<()> { - let receipts = ::rlp::decode_list(receipts_bytes); + let receipts = TypedReceipt::decode_rlp_list(&Rlp::new(receipts_bytes)) + .unwrap_or_else(|e| panic!("Receipt bytes should be valid: {:?}", e)); let _import_lock = self.import_lock.lock(); { @@ -714,7 +718,7 @@ impl Importer { &self, header: &Header, block_bytes: &[u8], - receipts: &[Receipt], + receipts: &[TypedReceipt], state_db: &StateDB, client: &Client, ) -> EthcoreResult> { @@ -1436,7 +1440,7 @@ impl Client { data: Bytes, ) -> SignedTransaction { let from = Address::default(); - transaction::Transaction { + TypedTransaction::Legacy(transaction::Transaction { nonce: self .nonce(&from, block_id) .unwrap_or_else(|| self.engine.account_start_nonce(0)), @@ -1445,7 +1449,7 @@ impl Client { gas_price: U256::default(), value: U256::default(), data: data, - } + }) .fake_sign(from) } @@ -1889,7 +1893,7 @@ impl Call for Client { let exec = |gas| { let mut tx = t.as_unsigned().clone(); - tx.gas = gas; + tx.tx_mut().gas = gas; let tx = tx.fake_sign(sender); let mut clone = state.clone(); @@ -1920,6 +1924,7 @@ impl Call for Client { } } let lower = t + .tx() .gas_required(&self.engine.schedule(env_info.number)) .into(); if cond(lower) { @@ -2571,18 +2576,18 @@ impl BlockChainClient for Client { } else { self.importer.miner.sensible_gas_price() }; - let transaction = transaction::Transaction { + let transaction = TypedTransaction::Legacy(transaction::Transaction { nonce: self.latest_nonce(&authoring_params.author), action: Action::Call(address), gas: self.importer.miner.sensible_gas_limit(), gas_price, value: U256::zero(), data: data, - }; + }); let chain_id = self.engine.signing_chain_id(&self.latest_env_info()); let signature = self .engine - .sign(transaction.hash(chain_id)) + .sign(transaction.signature_hash(chain_id)) .map_err(|e| transaction::Error::InvalidSignature(e.to_string()))?; let signed = SignedTransaction::new(transaction.with_signature(signature, chain_id))?; self.importer @@ -2602,10 +2607,15 @@ impl IoClient for Client { self.queue_transactions .queue(&self.io_channel.read(), len, move |client| { trace_time!("import_queued_transactions"); - + let best_block_number = client.best_block_header().number(); let txs: Vec = transactions .iter() - .filter_map(|bytes| client.engine.decode_transaction(bytes).ok()) + .filter_map(|bytes| { + client + .engine + .decode_transaction(bytes, best_block_number) + .ok() + }) .collect(); client.notify(|notify| { @@ -2954,7 +2964,7 @@ impl ProvingBlockChainClient for Client { _ => return None, }; - env_info.gas_limit = transaction.gas.clone(); + env_info.gas_limit = transaction.tx().gas.clone(); let mut jdb = self.state_db.read().journal_db().boxed_clone(); state::prove_transaction_virtual( @@ -3112,7 +3122,7 @@ impl ImportExportBlocks for Client { fn transaction_receipt( machine: &::machine::EthereumMachine, mut tx: LocalizedTransaction, - receipt: Receipt, + receipt: TypedReceipt, prior_gas_used: U256, prior_no_of_logs: usize, ) -> LocalizedReceipt { @@ -3121,27 +3131,31 @@ fn transaction_receipt( let block_hash = tx.block_hash; let block_number = tx.block_number; let transaction_index = tx.transaction_index; + let transaction_type = tx.tx_type(); + + let receipt = receipt.receipt().clone(); LocalizedReceipt { from: sender, - to: match tx.action { + to: match tx.tx().action { Action::Create => None, Action::Call(ref address) => Some(address.clone().into()), }, transaction_hash: transaction_hash, transaction_index: transaction_index, + transaction_type: transaction_type, block_hash: block_hash, block_number: block_number, cumulative_gas_used: receipt.gas_used, gas_used: receipt.gas_used - prior_gas_used, - contract_address: match tx.action { + contract_address: match tx.tx().action { Action::Call(_) => None, Action::Create => Some( contract_address( machine.create_address_scheme(block_number), &sender, - &tx.nonce, - &tx.data, + &tx.tx().nonce, + &tx.tx().data, ) .0, ), @@ -3161,7 +3175,7 @@ fn transaction_receipt( }) .collect(), log_bloom: receipt.log_bloom, - outcome: receipt.outcome, + outcome: receipt.outcome.clone(), } } @@ -3457,8 +3471,8 @@ mod tests { use hash::keccak; use types::{ log_entry::{LocalizedLogEntry, LogEntry}, - receipt::{LocalizedReceipt, Receipt, TransactionOutcome}, - transaction::{Action, LocalizedTransaction, Transaction}, + receipt::{LegacyReceipt, LocalizedReceipt, TransactionOutcome, TypedReceipt}, + transaction::{Action, LocalizedTransaction, Transaction, TypedTransaction}, }; // given @@ -3470,14 +3484,14 @@ mod tests { let block_hash = 5.into(); let state_root = 99.into(); let gas_used = 10.into(); - let raw_tx = Transaction { + let raw_tx = TypedTransaction::Legacy(Transaction { nonce: 0.into(), gas_price: 0.into(), gas: 21000.into(), action: Action::Call(10.into()), value: 0.into(), data: vec![], - }; + }); let tx1 = raw_tx.clone().sign(secret, None); let transaction = LocalizedTransaction { signed: tx1.clone().into(), @@ -3498,12 +3512,12 @@ mod tests { data: vec![], }, ]; - let receipt = Receipt { + let receipt = TypedReceipt::Legacy(LegacyReceipt { outcome: TransactionOutcome::StateRoot(state_root), gas_used: gas_used, log_bloom: Default::default(), logs: logs.clone(), - }; + }); // when let receipt = transaction_receipt(&machine, transaction, receipt, 5.into(), 1); @@ -3513,12 +3527,13 @@ mod tests { receipt, LocalizedReceipt { from: tx1.sender().into(), - to: match tx1.action { + to: match tx1.tx().action { Action::Create => None, Action::Call(ref address) => Some(address.clone().into()), }, transaction_hash: tx1.hash(), transaction_index: 1, + transaction_type: tx1.tx_type(), block_hash: block_hash, block_number: block_number, cumulative_gas_used: gas_used, diff --git a/ethcore/src/client/evm_test_client.rs b/ethcore/src/client/evm_test_client.rs index 8a9327f75..d55e9faf4 100644 --- a/ethcore/src/client/evm_test_client.rs +++ b/ethcore/src/client/evm_test_client.rs @@ -281,7 +281,7 @@ impl<'a> EvmTestClient<'a> { tracer: T, vm_tracer: V, ) -> std::result::Result, TransactErr> { - let initial_gas = transaction.gas; + let initial_gas = transaction.tx().gas; // Verify transaction let is_ok = transaction.verify_basic(true, None); if let Err(error) = is_ok { @@ -342,18 +342,18 @@ impl<'a> EvmTestClient<'a> { Ok(result) => Ok(TransactSuccess { state_root, gas_left: initial_gas - result.receipt.gas_used, - outcome: result.receipt.outcome, + outcome: result.receipt.outcome.clone(), output: result.output, trace: result.trace, vm_trace: result.vm_trace, - logs: result.receipt.logs, - contract_address: if let transaction::Action::Create = transaction.action { + logs: result.receipt.logs.clone(), + contract_address: if let transaction::Action::Create = transaction.tx().action { Some( executive::contract_address( scheme, &transaction.sender(), - &transaction.nonce, - &transaction.data, + &transaction.tx().nonce, + &transaction.tx().data, ) .0, ) diff --git a/ethcore/src/client/test_client.rs b/ethcore/src/client/test_client.rs index f48fc477a..2de1b6292 100644 --- a/ethcore/src/client/test_client.rs +++ b/ethcore/src/client/test_client.rs @@ -36,7 +36,7 @@ use itertools::Itertools; use kvdb::DBValue; use kvdb_memorydb; use parking_lot::RwLock; -use rlp::{Rlp, RlpStream}; +use rlp::RlpStream; use rustc_hex::FromHex; use types::{ basic_account::BasicAccount, @@ -45,8 +45,11 @@ use types::{ header::Header, log_entry::LocalizedLogEntry, pruning_info::PruningInfo, - receipt::{LocalizedReceipt, Receipt, TransactionOutcome}, - transaction::{self, Action, LocalizedTransaction, SignedTransaction, Transaction}, + receipt::{LegacyReceipt, LocalizedReceipt, TransactionOutcome, TypedReceipt}, + transaction::{ + self, Action, LocalizedTransaction, SignedTransaction, Transaction, TypedTransaction, + TypedTxId, + }, view, views::BlockView, BlockNumber, @@ -296,16 +299,16 @@ impl TestBlockChainClient { for _ in 0..num_transactions { // Update nonces value - let tx = Transaction { + let tx = TypedTransaction::Legacy(Transaction { action: Action::Create, value: U256::from(100), data: "3331600055".from_hex().unwrap(), gas: U256::from(100_000), gas_price: U256::from(200_000_000_000u64), nonce: nonce, - }; + }); let signed_tx = tx.sign(keypair.secret(), None); - txs.append(&signed_tx); + signed_tx.rlp_append(&mut txs); nonce += U256::one(); } @@ -369,14 +372,14 @@ impl TestBlockChainClient { /// Inserts a transaction with given gas price to miners transactions queue. pub fn insert_transaction_with_gas_price_to_queue(&self, gas_price: U256) -> H256 { let keypair = Random.generate().unwrap(); - let tx = Transaction { + let tx = TypedTransaction::Legacy(Transaction { action: Action::Create, value: U256::from(100), data: "3331600055".from_hex().unwrap(), gas: U256::from(100_000), gas_price: gas_price, nonce: U256::zero(), - }; + }); let signed_tx = tx.sign(keypair.secret(), None); self.set_balance(signed_tx.sender(), 10_000_000_000_000_000_000u64.into()); let hash = signed_tx.hash(); @@ -938,10 +941,13 @@ impl BlockChainClient for TestBlockChainClient { fn block_receipts(&self, hash: &H256) -> Option { // starts with 'f' ? if *hash > H256::from("f000000000000000000000000000000000000000000000000000000000000000") { - let receipt = BlockReceipts::new(vec![Receipt::new( - TransactionOutcome::StateRoot(H256::zero()), - U256::zero(), - vec![], + let receipt = BlockReceipts::new(vec![TypedReceipt::new( + TypedTxId::Legacy, + LegacyReceipt::new( + TransactionOutcome::StateRoot(H256::zero()), + U256::zero(), + vec![], + ), )]); return Some(receipt); } @@ -1027,16 +1033,20 @@ impl BlockChainClient for TestBlockChainClient { } fn transact_contract(&self, address: Address, data: Bytes) -> Result<(), transaction::Error> { - let transaction = Transaction { + let transaction = TypedTransaction::Legacy(Transaction { nonce: self.latest_nonce(&self.miner.authoring_params().author), action: Action::Call(address), gas: self.spec.gas_limit, gas_price: U256::zero(), value: U256::default(), data: data, - }; + }); let chain_id = Some(self.spec.chain_id()); - let sig = self.spec.engine.sign(transaction.hash(chain_id)).unwrap(); + let sig = self + .spec + .engine + .sign(transaction.signature_hash(chain_id)) + .unwrap(); let signed = SignedTransaction::new(transaction.with_signature(sig, chain_id)).unwrap(); self.miner.import_own_transaction(self, signed.into()) } @@ -1051,7 +1061,7 @@ impl IoClient for TestBlockChainClient { // import right here let txs = transactions .into_iter() - .filter_map(|bytes| Rlp::new(&bytes).as_val().ok()) + .filter_map(|bytes| TypedTransaction::decode(&bytes).ok()) .collect(); self.miner.import_external_transactions(self, txs); } diff --git a/ethcore/src/engines/authority_round/mod.rs b/ethcore/src/engines/authority_round/mod.rs index e73065587..dc232c2f9 100644 --- a/ethcore/src/engines/authority_round/mod.rs +++ b/ethcore/src/engines/authority_round/mod.rs @@ -1765,7 +1765,7 @@ mod tests { use test_helpers::{generate_dummy_client_with_spec, get_temp_state_db, TestNotify}; use types::{ header::Header, - transaction::{Action, Transaction}, + transaction::{Action, Transaction, TypedTransaction}, }; fn aura(f: F) -> Arc @@ -2287,14 +2287,14 @@ mod tests { ) .unwrap(); b2.push_transaction( - Transaction { + TypedTransaction::Legacy(Transaction { action: Action::Create, nonce: U256::from(0), gas_price: U256::from(3000), gas: U256::from(53_000), value: U256::from(1), data: vec![], - } + }) .fake_sign(addr2), None, ) diff --git a/ethcore/src/engines/mod.rs b/ethcore/src/engines/mod.rs index ca362859d..7c49fea56 100644 --- a/ethcore/src/engines/mod.rs +++ b/ethcore/src/engines/mod.rs @@ -605,8 +605,10 @@ pub trait EthEngine: Engine<::machine::EthereumMachine> { fn decode_transaction( &self, transaction: &[u8], + best_block_number: BlockNumber, ) -> Result { - self.machine().decode_transaction(transaction) + let schedule = self.schedule(best_block_number); + self.machine().decode_transaction(transaction, &schedule) } } diff --git a/ethcore/src/engines/validator_set/safe_contract.rs b/ethcore/src/engines/validator_set/safe_contract.rs index 569510a8d..39256c360 100644 --- a/ethcore/src/engines/validator_set/safe_contract.rs +++ b/ethcore/src/engines/validator_set/safe_contract.rs @@ -25,7 +25,7 @@ use kvdb::DBValue; use memory_cache::MemoryLruCache; use parking_lot::RwLock; use rlp::{Rlp, RlpStream}; -use types::{header::Header, ids::BlockId, log_entry::LogEntry, receipt::Receipt}; +use types::{header::Header, ids::BlockId, log_entry::LogEntry, receipt::TypedReceipt}; use unexpected::Mismatch; use super::{simple_list::SimpleList, SystemCall, ValidatorSet}; @@ -91,7 +91,7 @@ fn check_first_proof( old_header: Header, state_items: &[DBValue], ) -> Result, String> { - use types::transaction::{Action, Transaction}; + use types::transaction::{Action, Transaction, TypedTransaction}; // TODO: match client contract_call_tx more cleanly without duplication. const PROVIDED_GAS: u64 = 50_000_000; @@ -116,14 +116,14 @@ fn check_first_proof( let (data, decoder) = validator_set::functions::get_validators::call(); let from = Address::default(); - let tx = Transaction { + let tx = TypedTransaction::Legacy(Transaction { nonce: machine.account_start_nonce(number), action: Action::Call(contract_address), gas: PROVIDED_GAS.into(), gas_price: U256::default(), value: U256::default(), data, - } + }) .fake_sign(from); let res = ::state::check_proof( @@ -161,14 +161,15 @@ fn decode_first_proof(rlp: &Rlp) -> Result<(Header, Vec), ::error::Erro // inter-contract proofs are a header and receipts. // checking will involve ensuring that the receipts match the header and // extracting the validator set from the receipts. -fn encode_proof(header: &Header, receipts: &[Receipt]) -> Bytes { +fn encode_proof(header: &Header, receipts: &[TypedReceipt]) -> Bytes { let mut stream = RlpStream::new_list(2); - stream.append(header).append_list(receipts); + stream.append(header); + TypedReceipt::rlp_append_list(&mut stream, receipts); stream.drain() } -fn decode_proof(rlp: &Rlp) -> Result<(Header, Vec), ::error::Error> { - Ok((rlp.val_at(0)?, rlp.list_at(1)?)) +fn decode_proof(rlp: &Rlp) -> Result<(Header, Vec), ::error::Error> { + Ok((rlp.val_at(0)?, TypedReceipt::decode_rlp_list(&rlp.at(1)?)?)) } // given a provider and caller, generate proof. this will just be a state proof @@ -265,7 +266,7 @@ impl ValidatorSafeContract { &self, bloom: Bloom, header: &Header, - receipts: &[Receipt], + receipts: &[TypedReceipt], ) -> Option { let check_log = |log: &LogEntry| { log.address == self.contract_address @@ -406,7 +407,7 @@ impl ValidatorSet for ValidatorSafeContract { // ensure receipts match header. // TODO: optimize? these were just decoded. - let found_root = ::triehash::ordered_trie_root(receipts.iter().map(::rlp::encode)); + let found_root = ::triehash::ordered_trie_root(receipts.iter().map(|r| r.encode())); if found_root != *old_header.receipts_root() { return Err(::error::BlockError::InvalidReceiptsRoot(Mismatch { expected: *old_header.receipts_root(), @@ -491,7 +492,7 @@ mod tests { use test_helpers::{generate_dummy_client_with_spec, generate_dummy_client_with_spec_and_data}; use types::{ ids::BlockId, - transaction::{Action, Transaction}, + transaction::{Action, Transaction, TypedTransaction}, }; use verification::queue::kind::blocks::Unverified; @@ -537,7 +538,7 @@ mod tests { client.miner().set_author(miner::Author::Sealer(signer)); // Remove "1" validator. - let tx = Transaction { + let tx = TypedTransaction::Legacy(Transaction { nonce: 0.into(), gas_price: 0.into(), gas: 500_000.into(), @@ -546,7 +547,7 @@ mod tests { data: "bfc708a000000000000000000000000082a978b3f5962a5b0957d9ee9eef472ee55b42f1" .from_hex() .unwrap(), - } + }) .sign(&s0, Some(chain_id)); client .miner() @@ -555,7 +556,7 @@ mod tests { EngineClient::update_sealing(&*client, ForceUpdateSealing::No); assert_eq!(client.chain_info().best_block_number, 1); // Add "1" validator back in. - let tx = Transaction { + let tx = TypedTransaction::Legacy(Transaction { nonce: 1.into(), gas_price: 0.into(), gas: 500_000.into(), @@ -564,7 +565,7 @@ mod tests { data: "4d238c8e00000000000000000000000082a978b3f5962a5b0957d9ee9eef472ee55b42f1" .from_hex() .unwrap(), - } + }) .sign(&s0, Some(chain_id)); client .miner() @@ -582,14 +583,14 @@ mod tests { // Switch back to the added validator, since the state is updated. let signer = Box::new((tap.clone(), v1, "".into())); client.miner().set_author(miner::Author::Sealer(signer)); - let tx = Transaction { + let tx = TypedTransaction::Legacy(Transaction { nonce: 2.into(), gas_price: 0.into(), gas: 21000.into(), action: Action::Call(Address::default()), value: 0.into(), data: Vec::new(), - } + }) .sign(&s0, Some(chain_id)); client .miner() diff --git a/ethcore/src/executive.rs b/ethcore/src/executive.rs index 85394fa3d..dddc53ec7 100644 --- a/ethcore/src/executive.rs +++ b/ethcore/src/executive.rs @@ -28,7 +28,7 @@ use state::{Backend as StateBackend, CleanupMode, State, Substate}; use std::{cmp, sync::Arc}; use trace::{self, Tracer, VMTracer}; use transaction_ext::Transaction; -use types::transaction::{Action, SignedTransaction}; +use types::transaction::{Action, SignedTransaction, TypedTransaction}; use vm::{ self, AccessList, ActionParams, ActionValue, CleanDustMode, CreateContractAddress, EnvInfo, ResumeCall, ResumeCreate, ReturnData, Schedule, TrapError, @@ -1109,7 +1109,10 @@ impl<'a, B: 'a + StateBackend> Executive<'a, B> { { let sender = t.sender(); let balance = self.state.balance(&sender)?; - let needed_balance = t.value.saturating_add(t.gas.saturating_mul(t.gas_price)); + let needed_balance = t + .tx() + .value + .saturating_add(t.tx().gas.saturating_mul(t.tx().gas_price)); if balance < needed_balance { // give the sender a sufficient balance self.state @@ -1132,16 +1135,51 @@ impl<'a, B: 'a + StateBackend> Executive<'a, B> { T: Tracer, V: VMTracer, { + let schedule = self.schedule; + + // check if particualar transaction type is enabled at this block number in schedule + match t.as_unsigned() { + TypedTransaction::AccessList(_) => { + if !schedule.eip2930 { + return Err(ExecutionError::TransactionMalformed( + "OptionalAccessList EIP-2930 or EIP-2929 not enabled".into(), + )); + } + } + TypedTransaction::Legacy(_) => (), //legacy transactions are allways valid + }; + let sender = t.sender(); let nonce = self.state.nonce(&sender)?; - let schedule = self.schedule; - let base_gas_required = U256::from(t.gas_required(&schedule)); + let mut base_gas_required = U256::from(t.tx().gas_required(&schedule)); - if t.gas < base_gas_required { + let mut access_list = AccessList::new(schedule.eip2929); + + if schedule.eip2929 { + for (address, _) in self.machine.builtins() { + access_list.insert_address(*address); + } + if schedule.eip2930 { + // optional access list + if let TypedTransaction::AccessList(al_tx) = t.as_unsigned() { + for item in al_tx.access_list.iter() { + access_list.insert_address(item.0); + base_gas_required += vm::schedule::EIP2930_ACCESS_LIST_ADDRESS_COST.into(); + for key in item.1.iter() { + access_list.insert_storage_key(item.0, *key); + base_gas_required += + vm::schedule::EIP2930_ACCESS_LIST_STORAGE_KEY_COST.into(); + } + } + } + } + } + + if t.tx().gas < base_gas_required { return Err(ExecutionError::NotEnoughBaseGas { required: base_gas_required, - got: t.gas, + got: t.tx().gas, }); } @@ -1153,29 +1191,29 @@ impl<'a, B: 'a + StateBackend> Executive<'a, B> { return Err(ExecutionError::SenderMustExist); } - let init_gas = t.gas - base_gas_required; + let init_gas = t.tx().gas - base_gas_required; // validate transaction nonce - if check_nonce && t.nonce != nonce { + if check_nonce && t.tx().nonce != nonce { return Err(ExecutionError::InvalidNonce { expected: nonce, - got: t.nonce, + got: t.tx().nonce, }); } // validate if transaction fits into given block - if self.info.gas_used + t.gas > self.info.gas_limit { + if self.info.gas_used + t.tx().gas > self.info.gas_limit { return Err(ExecutionError::BlockGasLimitReached { gas_limit: self.info.gas_limit, gas_used: self.info.gas_used, - gas: t.gas, + gas: t.tx().gas, }); } // TODO: we might need bigints here, or at least check overflows. let balance = self.state.balance(&sender)?; - let gas_cost = t.gas.full_mul(t.gas_price); - let total_cost = U512::from(t.value) + gas_cost; + let gas_cost = t.tx().gas.full_mul(t.tx().gas_price); + let total_cost = U512::from(t.tx().value) + gas_cost; // avoid unaffordable transactions let balance512 = U512::from(balance); @@ -1186,13 +1224,6 @@ impl<'a, B: 'a + StateBackend> Executive<'a, B> { }); } - let mut access_list = AccessList::new(schedule.eip2929); - if schedule.eip2929 { - for (address, _) in self.machine.builtins() { - access_list.insert_address(*address); - } - } - let mut substate = Substate::from_access_list(&access_list); // NOTE: there can be no invalid transactions from this point. @@ -1205,13 +1236,13 @@ impl<'a, B: 'a + StateBackend> Executive<'a, B> { &mut substate.to_cleanup_mode(&schedule), )?; - let (result, output) = match t.action { + let (result, output) = match t.tx().action { Action::Create => { let (new_address, code_hash) = contract_address( self.machine.create_address_scheme(self.info.number), &sender, &nonce, - &t.data, + &t.tx().data, ); let params = ActionParams { code_address: new_address.clone(), @@ -1220,9 +1251,9 @@ impl<'a, B: 'a + StateBackend> Executive<'a, B> { sender: sender.clone(), origin: sender.clone(), gas: init_gas, - gas_price: t.gas_price, - value: ActionValue::Transfer(t.value), - code: Some(Arc::new(t.data.clone())), + gas_price: t.tx().gas_price, + value: ActionValue::Transfer(t.tx().value), + code: Some(Arc::new(t.tx().data.clone())), data: None, call_type: CallType::None, params_type: vm::ParamsType::Embedded, @@ -1242,11 +1273,11 @@ impl<'a, B: 'a + StateBackend> Executive<'a, B> { sender: sender.clone(), origin: sender.clone(), gas: init_gas, - gas_price: t.gas_price, - value: ActionValue::Transfer(t.value), + gas_price: t.tx().gas_price, + value: ActionValue::Transfer(t.tx().value), code: self.state.code(address)?, code_hash: self.state.code_hash(address)?, - data: Some(t.data.clone()), + data: Some(t.tx().data.clone()), call_type: CallType::Call, params_type: vm::ParamsType::Separate, access_list: access_list, @@ -1445,12 +1476,12 @@ impl<'a, B: 'a + StateBackend> Executive<'a, B> { Ok(FinalizationResult { gas_left, .. }) => gas_left, _ => 0.into(), }; - let refunded = cmp::min(refunds_bound, (t.gas - gas_left_prerefund) >> 1); + let refunded = cmp::min(refunds_bound, (t.tx().gas - gas_left_prerefund) >> 1); let gas_left = gas_left_prerefund + refunded; - let gas_used = t.gas.saturating_sub(gas_left); - let (refund_value, overflow_1) = gas_left.overflowing_mul(t.gas_price); - let (fees_value, overflow_2) = gas_used.overflowing_mul(t.gas_price); + let gas_used = t.tx().gas.saturating_sub(gas_left); + let (refund_value, overflow_1) = gas_left.overflowing_mul(t.tx().gas_price); + let (fees_value, overflow_2) = gas_used.overflowing_mul(t.tx().gas_price); if overflow_1 || overflow_2 { return Err(ExecutionError::TransactionMalformed( "U256 Overflow".to_string(), @@ -1458,7 +1489,7 @@ impl<'a, B: 'a + StateBackend> Executive<'a, B> { } trace!("exec::finalize: t.gas={}, sstore_refunds={}, suicide_refunds={}, refunds_bound={}, gas_left_prerefund={}, refunded={}, gas_left={}, gas_used={}, refund_value={}, fees_value={}\n", - t.gas, sstore_refunds, suicide_refunds, refunds_bound, gas_left_prerefund, refunded, gas_left, gas_used, refund_value, fees_value); + t.tx().gas, sstore_refunds, suicide_refunds, refunds_bound, gas_left_prerefund, refunded, gas_left, gas_used, refund_value, fees_value); let sender = t.sender(); trace!( @@ -1487,7 +1518,11 @@ impl<'a, B: 'a + StateBackend> Executive<'a, B> { // perform garbage-collection let min_balance = if schedule.kill_dust != CleanDustMode::Off { - Some(U256::from(schedule.tx_gas).overflowing_mul(t.gas_price).0) + Some( + U256::from(schedule.tx_gas) + .overflowing_mul(t.tx().gas_price) + .0, + ) } else { None }; @@ -1502,10 +1537,10 @@ impl<'a, B: 'a + StateBackend> Executive<'a, B> { Err(vm::Error::Internal(msg)) => Err(ExecutionError::Internal(msg)), Err(exception) => Ok(Executed { exception: Some(exception), - gas: t.gas, - gas_used: t.gas, + gas: t.tx().gas, + gas_used: t.tx().gas, refunded: U256::zero(), - cumulative_gas_used: self.info.gas_used + t.gas, + cumulative_gas_used: self.info.gas_used + t.tx().gas, logs: vec![], contracts_created: vec![], output: output, @@ -1519,7 +1554,7 @@ impl<'a, B: 'a + StateBackend> Executive<'a, B> { } else { Some(vm::Error::Reverted) }, - gas: t.gas, + gas: t.tx().gas, gas_used: gas_used, refunded: refunded, cumulative_gas_used: self.info.gas_used + gas_used, @@ -1551,7 +1586,7 @@ mod tests { trace, ExecutiveTracer, ExecutiveVMTracer, FlatTrace, MemoryDiff, NoopTracer, NoopVMTracer, StorageDiff, Tracer, VMExecutedOperation, VMOperation, VMTrace, VMTracer, }; - use types::transaction::{Action, Transaction}; + use types::transaction::{Action, Transaction, TypedTransaction}; use vm::{ActionParams, ActionValue, CallType, CreateContractAddress, EnvInfo}; fn make_frontier_machine(max_depth: usize) -> EthereumMachine { @@ -2445,14 +2480,14 @@ mod tests { evm_test_ignore! {test_transact_simple: test_transact_simple_int} fn test_transact_simple(factory: Factory) { let keypair = Random.generate().unwrap(); - let t = Transaction { + let t = TypedTransaction::Legacy(Transaction { action: Action::Create, value: U256::from(17), data: "3331600055".from_hex().unwrap(), gas: U256::from(100_000), gas_price: U256::zero(), nonce: U256::zero(), - } + }) .sign(keypair.secret(), None); let sender = t.sender(); let contract = contract_address( @@ -2496,14 +2531,14 @@ mod tests { evm_test! {test_transact_invalid_nonce: test_transact_invalid_nonce_int} fn test_transact_invalid_nonce(factory: Factory) { let keypair = Random.generate().unwrap(); - let t = Transaction { + let t = TypedTransaction::Legacy(Transaction { action: Action::Create, value: U256::from(17), data: "3331600055".from_hex().unwrap(), gas: U256::from(100_000), gas_price: U256::zero(), nonce: U256::one(), - } + }) .sign(keypair.secret(), None); let sender = t.sender(); @@ -2535,14 +2570,14 @@ mod tests { evm_test! {test_transact_gas_limit_reached: test_transact_gas_limit_reached_int} fn test_transact_gas_limit_reached(factory: Factory) { let keypair = Random.generate().unwrap(); - let t = Transaction { + let t = TypedTransaction::Legacy(Transaction { action: Action::Create, value: U256::from(17), data: "3331600055".from_hex().unwrap(), gas: U256::from(80_001), gas_price: U256::zero(), nonce: U256::zero(), - } + }) .sign(keypair.secret(), None); let sender = t.sender(); @@ -2580,14 +2615,14 @@ mod tests { evm_test! {test_not_enough_cash: test_not_enough_cash_int} fn test_not_enough_cash(factory: Factory) { let keypair = Random.generate().unwrap(); - let t = Transaction { + let t = TypedTransaction::Legacy(Transaction { action: Action::Create, value: U256::from(18), data: "3331600055".from_hex().unwrap(), gas: U256::from(100_000), gas_price: U256::one(), nonce: U256::zero(), - } + }) .sign(keypair.secret(), None); let sender = t.sender(); diff --git a/ethcore/src/json_tests/transaction.rs b/ethcore/src/json_tests/transaction.rs index 1146e24e3..1d22bccb8 100644 --- a/ethcore/src/json_tests/transaction.rs +++ b/ethcore/src/json_tests/transaction.rs @@ -17,10 +17,12 @@ use super::test_common::*; use client::EvmTestClient; use ethjson; -use rlp::Rlp; use std::path::Path; use transaction_ext::Transaction; -use types::{header::Header, transaction::UnverifiedTransaction}; +use types::{ + header::Header, + transaction::{TypedTransaction, UnverifiedTransaction}, +}; pub fn json_transaction_test( path: &Path, @@ -65,8 +67,7 @@ pub fn json_transaction_test( }; let rlp: Vec = test.rlp.clone().into(); - let res = Rlp::new(&rlp) - .as_val() + let res = TypedTransaction::decode(&rlp) .map_err(::error::Error::from) .and_then(|t: UnverifiedTransaction| { let mut header: Header = Default::default(); @@ -74,12 +75,13 @@ pub fn json_transaction_test( header.set_number(BLOCK_NUMBER); let minimal = t + .tx() .gas_required(&spec.engine.schedule(header.number())) .into(); - if t.gas < minimal { + if t.tx().gas < minimal { return Err(::types::transaction::Error::InsufficientGas { minimal, - got: t.gas, + got: t.tx().gas, } .into()); } diff --git a/ethcore/src/machine/impls.rs b/ethcore/src/machine/impls.rs index 890b2854f..5bfaba1d3 100644 --- a/ethcore/src/machine/impls.rs +++ b/ethcore/src/machine/impls.rs @@ -23,11 +23,11 @@ use std::{ }; use ethereum_types::{Address, H256, U256}; -use rlp::Rlp; use types::{ header::Header, transaction::{ - self, SignedTransaction, UnverifiedTransaction, SYSTEM_ADDRESS, UNSIGNED_SENDER, + self, SignedTransaction, TypedTransaction, UnverifiedTransaction, SYSTEM_ADDRESS, + UNSIGNED_SENDER, }, BlockNumber, }; @@ -455,17 +455,27 @@ impl EthereumMachine { pub fn decode_transaction( &self, transaction: &[u8], + schedule: &Schedule, ) -> Result { - let rlp = Rlp::new(&transaction); - if rlp.as_raw().len() > self.params().max_transaction_size { + if transaction.len() > self.params().max_transaction_size { debug!( "Rejected oversized transaction of {} bytes", - rlp.as_raw().len() + transaction.len() ); return Err(transaction::Error::TooBig); } - rlp.as_val() - .map_err(|e| transaction::Error::InvalidRlp(e.to_string())) + + let tx = TypedTransaction::decode(transaction) + .map_err(|e| transaction::Error::InvalidRlp(e.to_string()))?; + + match tx.tx_type() { + transaction::TypedTxId::AccessList if schedule.eip2930 => { + return Err(transaction::Error::TransactionTypeNotEnabled) + } + _ => (), + }; + + Ok(tx) } } @@ -476,7 +486,7 @@ pub struct AuxiliaryData<'a> { /// The full block bytes, including the header. pub bytes: Option<&'a [u8]>, /// The block receipts. - pub receipts: Option<&'a [::types::receipt::Receipt]>, + pub receipts: Option<&'a [::types::receipt::TypedReceipt]>, } /// Type alias for a function we can make calls through synchronously. @@ -550,7 +560,7 @@ mod tests { fn should_disallow_unsigned_transactions() { let rlp = "ea80843b9aca0083015f90948921ebb5f79e9e3920abe571004d0b1d5119c154865af3107a400080038080"; let transaction: UnverifiedTransaction = - ::rlp::decode(&::rustc_hex::FromHex::from_hex(rlp).unwrap()).unwrap(); + TypedTransaction::decode(&::rustc_hex::FromHex::from_hex(rlp).unwrap()).unwrap(); let spec = ::ethereum::new_ropsten_test(); let ethparams = get_default_ethash_extensions(); diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index c65f97a1a..919059d20 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -1195,15 +1195,16 @@ impl miner::MinerService for Miner { let receipt = &receipts[index]; RichReceipt { from: tx.sender(), - to: match tx.action { + to: match tx.tx().action { Action::Create => None, Action::Call(ref address) => Some(*address), }, transaction_hash: tx.hash(), + transaction_type: tx.tx_type(), transaction_index: index, cumulative_gas_used: receipt.gas_used, gas_used: receipt.gas_used - prev_gas, - contract_address: match tx.action { + contract_address: match tx.tx().action { Action::Call(_) => None, Action::Create => { let sender = tx.sender(); @@ -1212,8 +1213,8 @@ impl miner::MinerService for Miner { self.engine .create_address_scheme(pending.header.number()), &sender, - &tx.nonce, - &tx.data, + &tx.tx().nonce, + &tx.tx().data, ) .0, ) @@ -1509,7 +1510,7 @@ mod tests { use client::{ChainInfo, EachBlockWith, ImportSealedBlock, TestBlockChainClient}; use miner::{MinerService, PendingOrdering}; use test_helpers::{generate_dummy_client, generate_dummy_client_with_spec}; - use types::transaction::Transaction; + use types::transaction::{Transaction, TypedTransaction}; #[test] fn should_prepare_block_to_seal() { @@ -1583,14 +1584,14 @@ mod tests { fn transaction_with_chain_id(chain_id: u64) -> SignedTransaction { let keypair = Random.generate().unwrap(); - Transaction { + TypedTransaction::Legacy(Transaction { action: Action::Create, value: U256::zero(), data: "3331600055".from_hex().unwrap(), gas: U256::from(100_000), gas_price: U256::zero(), nonce: U256::zero(), - } + }) .sign(keypair.secret(), Some(chain_id)) } diff --git a/ethcore/src/miner/pool_client.rs b/ethcore/src/miner/pool_client.rs index b3eb6d229..d4e9f198a 100644 --- a/ethcore/src/miner/pool_client.rs +++ b/ethcore/src/miner/pool_client.rs @@ -195,7 +195,8 @@ where &self, transaction: &[u8], ) -> Result { - self.engine.decode_transaction(transaction) + let number = self.chain.best_block_header().number(); + self.engine.decode_transaction(transaction, number) } } diff --git a/ethcore/src/snapshot/block.rs b/ethcore/src/snapshot/block.rs index 5d29fccd1..c602f6088 100644 --- a/ethcore/src/snapshot/block.rs +++ b/ethcore/src/snapshot/block.rs @@ -21,7 +21,7 @@ use ethereum_types::H256; use hash::keccak; use rlp::{DecoderError, Rlp, RlpStream}; use triehash::ordered_trie_root; -use types::{block::Block, header::Header, views::BlockView}; +use types::{block::Block, header::Header, transaction::TypedTransaction, views::BlockView}; const HEADER_FIELDS: usize = 8; const BLOCK_FIELDS: usize = 2; @@ -62,9 +62,9 @@ impl AbridgedBlock { .append(&header.extra_data()); // write block values. - stream - .append_list(&block_view.transactions()) - .append_list(&block_view.uncles()); + + TypedTransaction::rlp_append_list(&mut stream, &block_view.transactions()); + stream.append_list(&block_view.uncles()); // write seal fields. for field in seal_fields { @@ -97,10 +97,17 @@ impl AbridgedBlock { header.set_timestamp(rlp.val_at(6)?); header.set_extra_data(rlp.val_at(7)?); - let transactions = rlp.list_at(8)?; + let transactions = TypedTransaction::decode_rlp_list(&rlp.at(8)?)?; let uncles: Vec
= rlp.list_at(9)?; - header.set_transactions_root(ordered_trie_root(rlp.at(8)?.iter().map(|r| r.as_raw()))); + header.set_transactions_root(ordered_trie_root(rlp.at(8)?.iter().map(|r| { + if r.is_list() { + r.as_raw() + } else { + // We already checked if list is valid with decode_rlp_list above + r.data().expect("To raw rlp list to be valid") + } + }))); header.set_receipts_root(receipts_root); let mut uncles_rlp = RlpStream::new(); @@ -131,7 +138,7 @@ mod tests { use ethereum_types::{Address, H256, U256}; use types::{ block::Block, - transaction::{Action, Transaction}, + transaction::{Action, Transaction, TypedTransaction}, view, views::BlockView, }; @@ -165,24 +172,24 @@ mod tests { fn with_transactions() { let mut b = Block::default(); - let t1 = Transaction { + let t1 = TypedTransaction::Legacy(Transaction { action: Action::Create, nonce: U256::from(42), gas_price: U256::from(3000), gas: U256::from(50_000), value: U256::from(1), data: b"Hello!".to_vec(), - } + }) .fake_sign(Address::from(0x69)); - let t2 = Transaction { + let t2 = TypedTransaction::Legacy(Transaction { action: Action::Create, nonce: U256::from(88), gas_price: U256::from(12345), gas: U256::from(300000), value: U256::from(1000000000), data: "Eep!".into(), - } + }) .fake_sign(Address::from(0x55)); b.transactions.push(t1.into()); @@ -191,7 +198,7 @@ mod tests { let receipts_root = b.header.receipts_root().clone(); b.header .set_transactions_root(::triehash::ordered_trie_root( - b.transactions.iter().map(::rlp::encode), + b.transactions.iter().map(|tx| tx.encode()), )); let encoded = encode_block(&b); diff --git a/ethcore/src/snapshot/consensus/authority.rs b/ethcore/src/snapshot/consensus/authority.rs index 6d4c1daf0..9c74e9ec0 100644 --- a/ethcore/src/snapshot/consensus/authority.rs +++ b/ethcore/src/snapshot/consensus/authority.rs @@ -36,7 +36,9 @@ use ethereum_types::{H256, U256}; use itertools::{Itertools, Position}; use kvdb::KeyValueDB; use rlp::{Rlp, RlpStream}; -use types::{encoded, header::Header, ids::BlockId, receipt::Receipt}; +use types::{ + encoded, header::Header, ids::BlockId, receipt::TypedReceipt, transaction::TypedTransaction, +}; /// Snapshot creation and restoration for PoA chains. /// Chunk format: @@ -114,9 +116,9 @@ impl SnapshotComponents for PoaSnapshot { rlps.push({ let mut stream = RlpStream::new_list(5); + stream.append(&block.header); + TypedTransaction::rlp_append_list(&mut stream, &block.transactions); stream - .append(&block.header) - .append_list(&block.transactions) .append_list(&block.uncles) .append(&receipts) .append(&parent_td); @@ -349,11 +351,11 @@ impl Rebuilder for ChunkRebuilder { let last_rlp = rlp.at(num_items - 1)?; let block = Block { header: last_rlp.val_at(0)?, - transactions: last_rlp.list_at(1)?, + transactions: TypedTransaction::decode_rlp_list(&last_rlp.at(1)?)?, uncles: last_rlp.list_at(2)?, }; let block_data = block.rlp_bytes(); - let receipts: Vec = last_rlp.list_at(3)?; + let receipts = TypedReceipt::decode_rlp_list(&last_rlp.at(3)?)?; { let hash = block.header.hash(); diff --git a/ethcore/src/snapshot/consensus/work.rs b/ethcore/src/snapshot/consensus/work.rs index 8bbb06fca..46d4edd85 100644 --- a/ethcore/src/snapshot/consensus/work.rs +++ b/ethcore/src/snapshot/consensus/work.rs @@ -291,8 +291,15 @@ impl Rebuilder for PowRebuilder { let pair = rlp.at(idx)?; let abridged_rlp = pair.at(0)?.as_raw().to_owned(); let abridged_block = AbridgedBlock::from_raw(abridged_rlp); - let receipts: Vec<::types::receipt::Receipt> = pair.list_at(1)?; - let receipts_root = ordered_trie_root(pair.at(1)?.iter().map(|r| r.as_raw())); + let receipts = ::types::receipt::TypedReceipt::decode_rlp_list(&pair.at(1)?)?; + let receipts_root = ordered_trie_root(pair.at(1)?.iter().map(|r| { + if r.is_list() { + r.as_raw() + } else { + // We have allready checked validity by decoding rlp list in line above + r.data().expect("Expect for raw receipts list to be valid.") + } + })); let block = abridged_block.to_block(parent_hash, cur_number, receipts_root)?; let block_bytes = encoded::Block::new(block.rlp_bytes()); diff --git a/ethcore/src/snapshot/tests/proof_of_authority.rs b/ethcore/src/snapshot/tests/proof_of_authority.rs index 229a9f434..a68e90cb9 100644 --- a/ethcore/src/snapshot/tests/proof_of_authority.rs +++ b/ethcore/src/snapshot/tests/proof_of_authority.rs @@ -25,7 +25,7 @@ use snapshot::tests::helpers as snapshot_helpers; use spec::Spec; use tempdir::TempDir; use test_helpers::generate_dummy_client_with_spec; -use types::transaction::{Action, SignedTransaction, Transaction}; +use types::transaction::{Action, SignedTransaction, Transaction, TypedTransaction}; use ethereum_types::Address; use test_helpers; @@ -128,14 +128,14 @@ fn make_chain( // and force sealing. let make_useless_transactions = || { let mut nonce = nonce.borrow_mut(); - let transaction = Transaction { + let transaction = TypedTransaction::Legacy(Transaction { nonce: *nonce, gas_price: 1.into(), gas: 21_000.into(), action: Action::Call(Address::new()), value: 1.into(), data: Vec::new(), - } + }) .sign(&*RICH_SECRET, client.signing_chain_id()); *nonce = *nonce + 1; @@ -171,14 +171,14 @@ fn make_chain( let data = test_validator_set::functions::set_validators::encode_input(new_set.clone()); let mut nonce = nonce.borrow_mut(); - let transaction = Transaction { + let transaction = TypedTransaction::Legacy(Transaction { nonce: *nonce, gas_price: 0.into(), gas: 1_000_000.into(), action: Action::Call(*address), value: 0.into(), data, - } + }) .sign(&*RICH_SECRET, client.signing_chain_id()); *nonce = *nonce + 1; diff --git a/ethcore/src/snapshot/tests/service.rs b/ethcore/src/snapshot/tests/service.rs index 7ca2cd1ba..90bdc1923 100644 --- a/ethcore/src/snapshot/tests/service.rs +++ b/ethcore/src/snapshot/tests/service.rs @@ -301,7 +301,7 @@ fn recover_aborted_recovery() { generate_dummy_client_with_spec_and_data(Spec::new_null, NUM_BLOCKS, 5, &gas_prices); let spec = Spec::new_null(); - let tempdir = TempDir::new("").unwrap(); + let tempdir = TempDir::new("oe_snapshot").unwrap(); let db_config = DatabaseConfig::with_columns(::db::NUM_COLUMNS); let client_db = new_db(); let client2 = Client::new( diff --git a/ethcore/src/spec/spec.rs b/ethcore/src/spec/spec.rs index 85a7589e4..bdc60714f 100644 --- a/ethcore/src/spec/spec.rs +++ b/ethcore/src/spec/spec.rs @@ -137,6 +137,8 @@ pub struct CommonParams { pub eip2315_transition: BlockNumber, /// Number of first block where EIP-2929 rules begin. pub eip2929_transition: BlockNumber, + /// Number of first block where EIP-2930 rules begin. + pub eip2930_transition: BlockNumber, /// Number of first block where dust cleanup rules (EIP-168 and EIP169) begin. pub dust_protection_transition: BlockNumber, /// Nonce cap increase per block. Nonce cap is only checked if dust protection is enabled. @@ -212,6 +214,7 @@ impl CommonParams { schedule.eip1706 = block_number >= self.eip1706_transition; schedule.have_subs = block_number >= self.eip2315_transition; schedule.eip2929 = block_number >= self.eip2929_transition; + schedule.eip2930 = block_number >= self.eip2930_transition; if block_number >= self.eip1884_transition { schedule.have_selfbalance = true; @@ -370,6 +373,9 @@ impl From for CommonParams { eip2929_transition: p .eip2929_transition .map_or_else(BlockNumber::max_value, Into::into), + eip2930_transition: p + .eip2930_transition + .map_or_else(BlockNumber::max_value, Into::into), dust_protection_transition: p .dust_protection_transition .map_or_else(BlockNumber::max_value, Into::into), @@ -956,7 +962,7 @@ impl Spec { /// initialize genesis epoch data, using in-memory database for /// constructor. pub fn genesis_epoch_data(&self) -> Result, String> { - use types::transaction::{Action, Transaction}; + use types::transaction::{Action, Transaction, TypedTransaction}; let genesis = self.genesis_header(); @@ -983,14 +989,14 @@ impl Spec { }; let from = Address::default(); - let tx = Transaction { + let tx = TypedTransaction::Legacy(Transaction { nonce: self.engine.account_start_nonce(0), action: Action::Call(a), gas: U256::max_value(), gas_price: U256::default(), value: U256::default(), data: d, - } + }) .fake_sign(from); let res = ::state::prove_transaction_virtual( diff --git a/ethcore/src/state/mod.rs b/ethcore/src/state/mod.rs index 66da05f27..5759f5f4f 100644 --- a/ethcore/src/state/mod.rs +++ b/ethcore/src/state/mod.rs @@ -38,10 +38,11 @@ use state_db::StateDB; use trace::{self, FlatTrace, VMTrace}; use types::{ basic_account::BasicAccount, - receipt::{Receipt, TransactionOutcome}, + receipt::{LegacyReceipt, TransactionOutcome, TypedReceipt}, state_diff::StateDiff, transaction::SignedTransaction, }; + use vm::EnvInfo; use bytes::Bytes; @@ -63,7 +64,7 @@ pub use self::{account::Account, backend::Backend, substate::Substate}; /// Used to return information about an `State::apply` operation. pub struct ApplyOutcome { /// The receipt for the applied transaction. - pub receipt: Receipt, + pub receipt: TypedReceipt, /// The output of the applied transaction. pub output: Bytes, /// The trace for the applied transaction, empty if tracing was not produced. @@ -955,7 +956,10 @@ impl State { }; let output = e.output; - let receipt = Receipt::new(outcome, e.cumulative_gas_used, e.logs); + let receipt = TypedReceipt::new( + t.tx_type(), + LegacyReceipt::new(outcome, e.cumulative_gas_used, e.logs), + ); trace!(target: "state", "Transaction receipt: {:?}", receipt); Ok(ApplyOutcome { @@ -1602,7 +1606,7 @@ mod tests { info.gas_limit = 1_000_000.into(); let machine = make_frontier_machine(5); - let t = Transaction { + let t = TypedTransaction::Legacy(Transaction { nonce: 0.into(), gas_price: 0.into(), gas: 100_000.into(), @@ -1610,7 +1614,7 @@ mod tests { value: 100.into(), data: FromHex::from_hex("601080600c6000396000f3006000355415600957005b60203560003555") .unwrap(), - } + }) .sign(&secret(), None); state @@ -1667,14 +1671,14 @@ mod tests { info.gas_limit = 1_000_000.into(); let machine = make_frontier_machine(5); - let t = Transaction { + let t = TypedTransaction::Legacy(Transaction { nonce: 0.into(), gas_price: 0.into(), gas: 100_000.into(), action: Action::Create, value: 100.into(), data: FromHex::from_hex("5b600056").unwrap(), - } + }) .sign(&secret(), None); state @@ -1706,14 +1710,14 @@ mod tests { info.gas_limit = 1_000_000.into(); let machine = make_frontier_machine(5); - let t = Transaction { + let t = TypedTransaction::Legacy(Transaction { nonce: 0.into(), gas_price: 0.into(), gas: 100_000.into(), action: Action::Call(0xa.into()), value: 100.into(), data: vec![], - } + }) .sign(&secret(), None); state @@ -1753,14 +1757,14 @@ mod tests { info.gas_limit = 1_000_000.into(); let machine = make_frontier_machine(5); - let t = Transaction { + let t = TypedTransaction::Legacy(Transaction { nonce: 0.into(), gas_price: 0.into(), gas: 100_000.into(), action: Action::Call(0xa.into()), value: 100.into(), data: vec![], - } + }) .sign(&secret(), None); state @@ -1797,14 +1801,14 @@ mod tests { info.gas_limit = 1_000_000.into(); let machine = Spec::new_test_machine(); - let t = Transaction { + let t = TypedTransaction::Legacy(Transaction { nonce: 0.into(), gas_price: 0.into(), gas: 100_000.into(), action: Action::Call(0x1.into()), value: 0.into(), data: vec![], - } + }) .sign(&secret(), None); let result = state.apply(&info, &machine, &t, true).unwrap(); @@ -1839,14 +1843,14 @@ mod tests { info.gas_limit = 1_000_000.into(); let machine = Spec::new_test_machine(); - let t = Transaction { + let t = TypedTransaction::Legacy(Transaction { nonce: 0.into(), gas_price: 0.into(), gas: 100_000.into(), action: Action::Call(0xa.into()), value: 0.into(), data: vec![], - } + }) .sign(&secret(), None); state @@ -1887,14 +1891,14 @@ mod tests { info.gas_limit = 1_000_000.into(); let machine = Spec::new_test_machine(); - let t = Transaction { + let t = TypedTransaction::Legacy(Transaction { nonce: 0.into(), gas_price: 0.into(), gas: 100_000.into(), action: Action::Call(0xa.into()), value: 0.into(), data: vec![], - } + }) .sign(&secret(), None); state @@ -1957,14 +1961,14 @@ mod tests { info.number = 0x789b0; let machine = Spec::new_test_machine(); - let t = Transaction { + let t = TypedTransaction::Legacy(Transaction { nonce: 0.into(), gas_price: 0.into(), gas: 100_000.into(), action: Action::Call(0xa.into()), value: 0.into(), data: vec![], - } + }) .sign(&secret(), None); state @@ -2029,14 +2033,14 @@ mod tests { info.gas_limit = 1_000_000.into(); let machine = make_frontier_machine(5); - let t = Transaction { + let t = TypedTransaction::Legacy(Transaction { nonce: 0.into(), gas_price: 0.into(), gas: 100_000.into(), action: Action::Call(0xa.into()), value: 100.into(), data: vec![], - } + }) .sign(&secret(), None); state @@ -2073,14 +2077,14 @@ mod tests { info.gas_limit = 1_000_000.into(); let machine = make_frontier_machine(5); - let t = Transaction { + let t = TypedTransaction::Legacy(Transaction { nonce: 0.into(), gas_price: 0.into(), gas: 100_000.into(), action: Action::Call(0xa.into()), value: 100.into(), data: vec![], - } + }) .sign(&secret(), None); state @@ -2145,14 +2149,14 @@ mod tests { info.gas_limit = 1_000_000.into(); let machine = make_frontier_machine(5); - let t = Transaction { + let t = TypedTransaction::Legacy(Transaction { nonce: 0.into(), gas_price: 0.into(), gas: 100_000.into(), action: Action::Call(0xa.into()), value: 100.into(), data: vec![], - } + }) .sign(&secret(), None); state @@ -2210,14 +2214,14 @@ mod tests { info.gas_limit = 1_000_000.into(); let machine = make_frontier_machine(5); - let t = Transaction { + let t = TypedTransaction::Legacy(Transaction { nonce: 0.into(), gas_price: 0.into(), gas: 100_000.into(), action: Action::Call(0xa.into()), value: 100.into(), data: vec![], - } + }) .sign(&secret(), None); state @@ -2260,14 +2264,14 @@ mod tests { info.gas_limit = 1_000_000.into(); let machine = make_frontier_machine(5); - let t = Transaction { + let t = TypedTransaction::Legacy(Transaction { nonce: 0.into(), gas_price: 0.into(), gas: 100_000.into(), action: Action::Call(0xa.into()), value: 100.into(), data: vec![], //600480600b6000396000f35b600056 - } + }) .sign(&secret(), None); state @@ -2328,14 +2332,14 @@ mod tests { info.gas_limit = 1_000_000.into(); let machine = make_frontier_machine(5); - let t = Transaction { + let t = TypedTransaction::Legacy(Transaction { nonce: 0.into(), gas_price: 0.into(), gas: 100_000.into(), action: Action::Call(0xa.into()), value: 100.into(), data: vec![], - } + }) .sign(&secret(), None); state @@ -2421,14 +2425,14 @@ mod tests { info.gas_limit = 1_000_000.into(); let machine = make_frontier_machine(5); - let t = Transaction { + let t = TypedTransaction::Legacy(Transaction { nonce: 0.into(), gas_price: 0.into(), gas: 100_000.into(), action: Action::Call(0xa.into()), value: 100.into(), data: vec![], //600480600b6000396000f35b600056 - } + }) .sign(&secret(), None); state @@ -2512,14 +2516,14 @@ mod tests { info.gas_limit = 1_000_000.into(); let machine = make_frontier_machine(5); - let t = Transaction { + let t = TypedTransaction::Legacy(Transaction { nonce: 0.into(), gas_price: 0.into(), gas: 100_000.into(), action: Action::Call(0xa.into()), value: 100.into(), data: vec![], - } + }) .sign(&secret(), None); state diff --git a/ethcore/src/test_helpers.rs b/ethcore/src/test_helpers.rs index 2bf3a8410..a8b54e234 100644 --- a/ethcore/src/test_helpers.rs +++ b/ethcore/src/test_helpers.rs @@ -36,7 +36,7 @@ use tempdir::TempDir; use types::{ encoded, header::Header, - transaction::{Action, SignedTransaction, Transaction}, + transaction::{Action, SignedTransaction, Transaction, TypedTransaction}, view, views::BlockView, }; @@ -104,7 +104,7 @@ pub fn create_test_block_with_data( rlp.append(header); rlp.begin_list(transactions.len()); for t in transactions { - rlp.append_raw(&rlp::encode(t), 1); + t.rlp_append(&mut rlp); } rlp.append_list(&uncles); rlp.out() @@ -197,14 +197,14 @@ where // first block we don't have any balance, so can't send any transactions. for _ in 0..txs_per_block { b.push_transaction( - Transaction { + TypedTransaction::Legacy(Transaction { nonce: n.into(), gas_price: tx_gas_prices[n % tx_gas_prices.len()], gas: 100000.into(), action: Action::Create, data: vec![], value: U256::zero(), - } + }) .sign(kp.secret(), Some(test_spec.chain_id())), None, ) diff --git a/ethcore/src/tests/client.rs b/ethcore/src/tests/client.rs index 58ae5eeac..a5f0b2d88 100644 --- a/ethcore/src/tests/client.rs +++ b/ethcore/src/tests/client.rs @@ -45,7 +45,7 @@ use types::{ data_format::DataFormat, filter::Filter, ids::BlockId, - transaction::{Action, Condition, PendingTransaction, Transaction}, + transaction::{Action, Condition, PendingTransaction, Transaction, TypedTransaction}, view, views::BlockView, }; @@ -362,26 +362,26 @@ fn does_not_propagate_delayed_transactions() { let key = KeyPair::from_secret(keccak("test").into()).unwrap(); let secret = key.secret(); let tx0 = PendingTransaction::new( - Transaction { + TypedTransaction::Legacy(Transaction { nonce: 0.into(), gas_price: 0.into(), gas: 21000.into(), action: Action::Call(Address::default()), value: 0.into(), data: Vec::new(), - } + }) .sign(secret, None), Some(Condition::Number(2)), ); let tx1 = PendingTransaction::new( - Transaction { + TypedTransaction::Legacy(Transaction { nonce: 1.into(), gas_price: 0.into(), gas: 21000.into(), action: Action::Call(Address::default()), value: 0.into(), data: Vec::new(), - } + }) .sign(secret, None), None, ); @@ -443,14 +443,14 @@ fn transaction_proof() { client.import_sealed_block(b).unwrap(); // account change is in the journal overlay } - let transaction = Transaction { + let transaction = TypedTransaction::Legacy(Transaction { nonce: 0.into(), gas_price: 0.into(), gas: 21000.into(), action: Action::Call(Address::default()), value: 5.into(), data: Vec::new(), - } + }) .fake_sign(address); let proof = client diff --git a/ethcore/src/tests/trace.rs b/ethcore/src/tests/trace.rs index 4b21dd06f..3835a6a03 100644 --- a/ethcore/src/tests/trace.rs +++ b/ethcore/src/tests/trace.rs @@ -29,7 +29,7 @@ use test_helpers::{self, get_temp_state_db}; use trace::{trace::Action::Reward, LocalizedTrace, RewardType}; use types::{ header::Header, - transaction::{Action, Transaction}, + transaction::{Action, Transaction, TypedTransaction}, view, views::BlockView, }; @@ -171,14 +171,14 @@ fn can_trace_block_and_uncle_reward() { for _ in 0..1 { block .push_transaction( - Transaction { + TypedTransaction::Legacy(Transaction { nonce: n.into(), gas_price: 10000.into(), gas: 100000.into(), action: Action::Create, data: vec![], value: U256::zero(), - } + }) .sign(kp.secret(), Some(spec.network_id())), None, ) diff --git a/ethcore/src/tx_filter.rs b/ethcore/src/tx_filter.rs index d4e7abb93..2a7da20e9 100644 --- a/ethcore/src/tx_filter.rs +++ b/ethcore/src/tx_filter.rs @@ -83,7 +83,7 @@ impl TransactionFilter { let mut permission_cache = self.permission_cache.lock(); let mut contract_version_cache = self.contract_version_cache.lock(); - let (tx_type, to) = match transaction.action { + let (tx_type, to) = match transaction.tx().action { Action::Create => (tx_permissions::CREATE, Address::new()), Action::Call(address) => { if client @@ -98,7 +98,7 @@ impl TransactionFilter { }; let sender = transaction.sender(); - let value = transaction.value; + let value = transaction.tx().value; let key = (*parent_hash, sender); if let Some(permissions) = permission_cache.get_mut(&key) { @@ -181,7 +181,7 @@ mod test { use std::sync::Arc; use tempdir::TempDir; use test_helpers; - use types::transaction::{Action, Transaction}; + use types::transaction::{Action, Transaction, TypedTransaction}; /// Contract code: https://gist.github.com/VladLupashevskyi/84f18eabb1e4afadf572cf92af3e7e7f #[test] @@ -230,28 +230,30 @@ mod test { .unwrap(); let filter = TransactionFilter::from_params(spec.params()).unwrap(); - let mut basic_tx = Transaction::default(); - basic_tx.action = Action::Call(Address::from("d41c057fd1c78805aac12b0a94a405c0461a6fbb")); - let create_tx = Transaction::default(); - let mut call_tx = Transaction::default(); - call_tx.action = Action::Call(Address::from("0000000000000000000000000000000000000005")); - - let mut basic_tx_with_ether_and_to_key7 = Transaction::default(); - basic_tx_with_ether_and_to_key7.action = + let mut basic_tx = TypedTransaction::Legacy(Transaction::default()); + basic_tx.tx_mut().action = Action::Call(Address::from("d41c057fd1c78805aac12b0a94a405c0461a6fbb")); - basic_tx_with_ether_and_to_key7.value = U256::from(123123); - let mut call_tx_with_ether = Transaction::default(); - call_tx_with_ether.action = + let create_tx = TypedTransaction::Legacy(Transaction::default()); + let mut call_tx = TypedTransaction::Legacy(Transaction::default()); + call_tx.tx_mut().action = Action::Call(Address::from("0000000000000000000000000000000000000005")); - call_tx_with_ether.value = U256::from(123123); - let mut basic_tx_to_key6 = Transaction::default(); - basic_tx_to_key6.action = + let mut basic_tx_with_ether_and_to_key7 = TypedTransaction::Legacy(Transaction::default()); + basic_tx_with_ether_and_to_key7.tx_mut().action = + Action::Call(Address::from("d41c057fd1c78805aac12b0a94a405c0461a6fbb")); + basic_tx_with_ether_and_to_key7.tx_mut().value = U256::from(123123); + let mut call_tx_with_ether = TypedTransaction::Legacy(Transaction::default()); + call_tx_with_ether.tx_mut().action = + Action::Call(Address::from("0000000000000000000000000000000000000005")); + call_tx_with_ether.tx_mut().value = U256::from(123123); + + let mut basic_tx_to_key6 = TypedTransaction::Legacy(Transaction::default()); + basic_tx_to_key6.tx_mut().action = Action::Call(Address::from("e57bfe9f44b819898f47bf37e5af72a0783e1141")); - let mut basic_tx_with_ether_and_to_key6 = Transaction::default(); - basic_tx_with_ether_and_to_key6.action = + let mut basic_tx_with_ether_and_to_key6 = TypedTransaction::Legacy(Transaction::default()); + basic_tx_with_ether_and_to_key6.tx_mut().action = Action::Call(Address::from("e57bfe9f44b819898f47bf37e5af72a0783e1141")); - basic_tx_with_ether_and_to_key6.value = U256::from(123123); + basic_tx_with_ether_and_to_key6.tx_mut().value = U256::from(123123); let genesis = client.block_hash(BlockId::Latest).unwrap(); let block_number = 1; @@ -444,11 +446,13 @@ mod test { .unwrap(); let filter = TransactionFilter::from_params(spec.params()).unwrap(); - let mut basic_tx = Transaction::default(); - basic_tx.action = Action::Call(Address::from("000000000000000000000000000000000000032")); - let create_tx = Transaction::default(); - let mut call_tx = Transaction::default(); - call_tx.action = Action::Call(Address::from("0000000000000000000000000000000000000005")); + let mut basic_tx = TypedTransaction::Legacy(Transaction::default()); + basic_tx.tx_mut().action = + Action::Call(Address::from("000000000000000000000000000000000000032")); + let create_tx = TypedTransaction::Legacy(Transaction::default()); + let mut call_tx = TypedTransaction::Legacy(Transaction::default()); + call_tx.tx_mut().action = + Action::Call(Address::from("0000000000000000000000000000000000000005")); let genesis = client.block_hash(BlockId::Latest).unwrap(); let block_number = 1; diff --git a/ethcore/src/verification/queue/kind.rs b/ethcore/src/verification/queue/kind.rs index e789137a7..8bbcc46bd 100644 --- a/ethcore/src/verification/queue/kind.rs +++ b/ethcore/src/verification/queue/kind.rs @@ -80,7 +80,10 @@ pub mod blocks { use engines::EthEngine; use error::{BlockError, Error, ErrorKind}; - use types::{header::Header, transaction::UnverifiedTransaction}; + use types::{ + header::Header, + transaction::{TypedTransaction, UnverifiedTransaction}, + }; use verification::{verify_block_basic, verify_block_unordered, PreverifiedBlock}; use bytes::Bytes; @@ -151,7 +154,7 @@ pub mod blocks { let (header, transactions, uncles) = { let rlp = Rlp::new(&bytes); let header = rlp.val_at(0)?; - let transactions = rlp.list_at(1)?; + let transactions = TypedTransaction::decode_rlp_list(&rlp.at(1)?)?; let uncles = rlp.list_at(2)?; (header, transactions, uncles) }; diff --git a/ethcore/src/verification/verification.rs b/ethcore/src/verification/verification.rs index 9cdc83754..0cafaf841 100644 --- a/ethcore/src/verification/verification.rs +++ b/ethcore/src/verification/verification.rs @@ -133,7 +133,7 @@ pub fn verify_block_unordered( let t = engine.verify_transaction_unordered(t, &header)?; // t_nb 5.3.2 check if nonce is more then max nonce (EIP-168 and EIP169) if let Some(max_nonce) = nonce_cap { - if t.nonce >= max_nonce { + if t.tx().nonce >= max_nonce { return Err(BlockError::TooManyTransactions(t.sender()).into()); } } @@ -493,7 +493,16 @@ fn verify_parent(header: &Header, parent: &Header, engine: &dyn EthEngine) -> Re fn verify_block_integrity(block: &Unverified) -> Result<(), Error> { let block_rlp = Rlp::new(&block.bytes); let tx = block_rlp.at(1)?; - let expected_root = ordered_trie_root(tx.iter().map(|r| r.as_raw())); + let expected_root = ordered_trie_root(tx.iter().map(|r| { + if r.is_list() { + r.as_raw() + } else { + // This is already checked in Unverified structure and that is why we are okay to asume that data is valid. + r.data().expect( + "Unverified block should already check if raw list of transactions is valid", + ) + } + })); if &expected_root != block.header.transactions_root() { bail!(BlockError::InvalidTransactionsRoot(Mismatch { expected: expected_root, @@ -531,7 +540,7 @@ mod tests { use types::{ encoded, log_entry::{LocalizedLogEntry, LogEntry}, - transaction::{Action, SignedTransaction, Transaction, UnverifiedTransaction}, + transaction::{Action, SignedTransaction, Transaction, TypedTransaction}, }; fn check_ok(result: Result<(), Error>) { @@ -764,34 +773,34 @@ mod tests { let keypair = Random.generate().unwrap(); - let tr1 = Transaction { + let tr1 = TypedTransaction::Legacy(Transaction { action: Action::Create, value: U256::from(0), data: Bytes::new(), gas: U256::from(30_000), gas_price: U256::from(40_000), nonce: U256::one(), - } + }) .sign(keypair.secret(), None); - let tr2 = Transaction { + let tr2 = TypedTransaction::Legacy(Transaction { action: Action::Create, value: U256::from(0), data: Bytes::new(), gas: U256::from(30_000), gas_price: U256::from(40_000), nonce: U256::from(2), - } + }) .sign(keypair.secret(), None); - let tr3 = Transaction { + let tr3 = TypedTransaction::Legacy(Transaction { action: Action::Call(0x0.into()), value: U256::from(0), data: Bytes::new(), gas: U256::from(30_000), gas_price: U256::from(0), nonce: U256::zero(), - } + }) .null_sign(0); let good_transactions = [tr1.clone(), tr2.clone()]; @@ -834,16 +843,10 @@ mod tests { let mut uncles_rlp = RlpStream::new(); uncles_rlp.append_list(&good_uncles); let good_uncles_hash = keccak(uncles_rlp.as_raw()); - let good_transactions_root = ordered_trie_root( - good_transactions - .iter() - .map(|t| ::rlp::encode::(t)), - ); - let eip86_transactions_root = ordered_trie_root( - eip86_transactions - .iter() - .map(|t| ::rlp::encode::(t)), - ); + let good_transactions_root = + ordered_trie_root(good_transactions.iter().map(|t| t.encode())); + let eip86_transactions_root = + ordered_trie_root(eip86_transactions.iter().map(|t| t.encode())); let mut parent = good.clone(); parent.set_number(9); @@ -1114,14 +1117,14 @@ mod tests { let keypair = Random.generate().unwrap(); let bad_transactions: Vec<_> = (0..3) .map(|i| { - Transaction { + TypedTransaction::Legacy(Transaction { action: Action::Create, value: U256::zero(), data: Vec::new(), gas: 0.into(), gas_price: U256::zero(), nonce: i.into(), - } + }) .sign(keypair.secret(), None) }) .collect(); diff --git a/ethcore/sync/src/block_sync.rs b/ethcore/sync/src/block_sync.rs index 1e7f00a33..7172bdfe9 100644 --- a/ethcore/sync/src/block_sync.rs +++ b/ethcore/sync/src/block_sync.rs @@ -762,7 +762,7 @@ mod tests { use triehash_ethereum::ordered_trie_root; use types::{ header::Header as BlockHeader, - transaction::{SignedTransaction, Transaction}, + transaction::{SignedTransaction, Transaction, TypedTransaction}, }; fn dummy_header(number: u64, parent_hash: H256) -> BlockHeader { @@ -778,7 +778,7 @@ mod tests { fn dummy_signed_tx() -> SignedTransaction { let keypair = Random.generate().unwrap(); - Transaction::default().sign(keypair.secret(), None) + TypedTransaction::Legacy(Transaction::default()).sign(keypair.secret(), None) } fn import_headers( @@ -949,8 +949,16 @@ mod tests { ::rlp::EMPTY_LIST_RLP.to_vec() }; - let txs = encode_list(&[dummy_signed_tx()]); - let tx_root = ordered_trie_root(Rlp::new(&txs).iter().map(|r| r.as_raw())); + let mut rlp_strem = RlpStream::new(); + SignedTransaction::rlp_append_list(&mut rlp_strem, &[dummy_signed_tx()]); + let txs = rlp_strem.drain(); + let tx_root = ordered_trie_root(Rlp::new(&txs).iter().map(|r| { + if r.is_list() { + r.as_raw() + } else { + r.data().expect("It is expected that raw rlp list is valid") + } + })); let mut rlp = RlpStream::new_list(2); rlp.append_raw(&txs, 1); @@ -1019,14 +1027,20 @@ mod tests { // Construct the receipts. Receipt root for the first two blocks is the same. // // The RLP-encoded integers are clearly not receipts, but the BlockDownloader treats - // all receipts as byte blobs, so it does not matter. + // all receipts as byte blobs, so it does not matter. It is just important that they are + // represended as list (0xc1) so that they passes as legacy list let receipts_rlp = if i < 2 { - encode_list(&[0u32]) + vec![0xC1, 0] } else { - encode_list(&[i as u32]) + vec![0xC1, i as u8] }; - let receipts_root = - ordered_trie_root(Rlp::new(&receipts_rlp).iter().map(|r| r.as_raw())); + let receipts_root = ordered_trie_root(Rlp::new(&receipts_rlp).iter().map(|r| { + if r.is_list() { + r.as_raw() + } else { + r.data().expect("expect proper test data") + } + })); receipts.push(receipts_rlp); // Construct the block header. diff --git a/ethcore/sync/src/blocks.rs b/ethcore/sync/src/blocks.rs index 9ae323cbe..a4e5b61d8 100644 --- a/ethcore/sync/src/blocks.rs +++ b/ethcore/sync/src/blocks.rs @@ -23,7 +23,10 @@ use network; use rlp::{DecoderError, Rlp, RlpStream}; use std::collections::{hash_map, BTreeMap, HashMap, HashSet}; use triehash_ethereum::ordered_trie_root; -use types::{header::Header as BlockHeader, transaction::UnverifiedTransaction}; +use types::{ + header::Header as BlockHeader, + transaction::{TypedTransaction, UnverifiedTransaction}, +}; known_heap_size!(0, HeaderId); @@ -65,7 +68,7 @@ impl SyncBody { let result = SyncBody { transactions_bytes: transactions_rlp.as_raw().to_vec(), - transactions: transactions_rlp.as_list()?, + transactions: TypedTransaction::decode_rlp_list(&transactions_rlp)?, uncles_bytes: uncles_rlp.as_raw().to_vec(), uncles: uncles_rlp.as_list()?, }; @@ -454,11 +457,14 @@ impl BlockCollection { fn insert_body(&mut self, body: SyncBody) -> Result { let header_id = { - let tx_root = ordered_trie_root( - Rlp::new(&body.transactions_bytes) - .iter() - .map(|r| r.as_raw()), - ); + let tx_root = ordered_trie_root(Rlp::new(&body.transactions_bytes).iter().map(|r| { + if r.is_list() { + r.as_raw() + } else { + // this list is already decoded and passed validation, for this we are okay to expect proper data + r.data().expect("Expect raw transaction list to be valid") + } + })); let uncles = keccak(&body.uncles_bytes); HeaderId { transactions_root: tx_root, @@ -491,7 +497,22 @@ impl BlockCollection { fn insert_receipt(&mut self, r: Bytes) -> Result, network::Error> { let receipt_root = { let receipts = Rlp::new(&r); - ordered_trie_root(receipts.iter().map(|r| r.as_raw())) + //check receipts data before calculating trie root + let mut temp_receipts: Vec<&[u8]> = Vec::new(); + for receipt_byte in receipts.iter() { + if receipt_byte.is_list() { + temp_receipts.push(receipt_byte.as_raw()) + } else { + temp_receipts.push( + receipt_byte + .data() + .map_err(|e| network::ErrorKind::Rlp(e))?, + ); + } + } + + // calculate trie root and use it as hash + ordered_trie_root(temp_receipts.iter()) }; self.downloading_receipts.remove(&receipt_root); match self.receipt_ids.entry(receipt_root) { diff --git a/ethcore/sync/src/chain/handler.rs b/ethcore/sync/src/chain/handler.rs index 2b3dbf655..2aa9c5c6e 100644 --- a/ethcore/sync/src/chain/handler.rs +++ b/ethcore/sync/src/chain/handler.rs @@ -837,9 +837,12 @@ impl SyncHandler { let item_count = r.item_count()?; trace!(target: "sync", "{:02} -> Transactions ({} entries)", peer_id, item_count); let mut transactions = Vec::with_capacity(item_count); - for i in 0..item_count { - let rlp = r.at(i)?; - let tx = rlp.as_raw().to_vec(); + for i in r.iter() { + let tx = if i.is_list() { + i.as_raw().to_vec() // legacy transaction. just add it raw + } else { + i.data()?.to_vec() // typed transaction. remove header from start and send only payload. + }; transactions.push(tx); } io.chain().queue_transactions(transactions, peer_id); diff --git a/ethcore/sync/src/chain/propagator.rs b/ethcore/sync/src/chain/propagator.rs index a70fc813d..de79c1207 100644 --- a/ethcore/sync/src/chain/propagator.rs +++ b/ethcore/sync/src/chain/propagator.rs @@ -21,7 +21,7 @@ use ethereum_types::H256; use fastmap::H256FastSet; use network::{client_version::ClientCapabilities, PeerId}; use rand::Rng; -use rlp::{Encodable, RlpStream}; +use rlp::RlpStream; use sync_io::SyncIo; use types::{blockchain_info::BlockChainInfo, transaction::SignedTransaction, BlockNumber}; @@ -121,7 +121,7 @@ impl SyncPropagator { let (transactions, service_transactions): (Vec<_>, Vec<_>) = transactions .iter() .map(|tx| tx.signed()) - .partition(|tx| !tx.gas_price.is_zero()); + .partition(|tx| !tx.tx().gas_price.is_zero()); // usual transactions could be propagated to all peers let mut affected_peers = HashSet::new(); @@ -171,7 +171,7 @@ impl SyncPropagator { let all_transactions_rlp = { let mut packet = RlpStream::new_list(transactions.len()); for tx in &transactions { - packet.append(&**tx); + tx.rlp_append(&mut packet); } packet.out() }; @@ -238,13 +238,8 @@ impl SyncPropagator { for tx in &transactions { let hash = tx.hash(); if to_send.contains(&hash) { - let mut transaction = RlpStream::new(); - tx.rlp_append(&mut transaction); - let appended = packet.append_raw_checked( - &transaction.drain(), - 1, - MAX_TRANSACTION_PACKET_SIZE, - ); + let appended = + packet.append_raw_checked(&tx.encode(), 1, MAX_TRANSACTION_PACKET_SIZE); if !appended { // Maximal packet size reached just proceed with sending debug!(target: "sync", "Transaction packet size limit reached. Sending incomplete set of {}/{} transactions.", pushed, to_send.len()); @@ -373,6 +368,7 @@ mod tests { use rlp::Rlp; use std::collections::VecDeque; use tests::{helpers::TestIo, snapshot::TestSnapshotService}; + use types::transaction::TypedTransaction; use super::{ super::{tests::*, *}, @@ -707,7 +703,9 @@ mod tests { return None; } - rlp.at(0).ok().and_then(|r| r.as_val().ok()) + rlp.at(0) + .ok() + .and_then(|r| TypedTransaction::decode_rlp(&r).ok()) }) .collect(); assert_eq!(sent_transactions.len(), 2); diff --git a/ethcore/sync/src/tests/consensus.rs b/ethcore/sync/src/tests/consensus.rs index 089c7c585..6af66ec29 100644 --- a/ethcore/sync/src/tests/consensus.rs +++ b/ethcore/sync/src/tests/consensus.rs @@ -26,18 +26,18 @@ use ethkey::{KeyPair, Secret}; use hash::keccak; use io::{IoChannel, IoHandler}; use std::sync::Arc; -use types::transaction::{Action, PendingTransaction, Transaction}; +use types::transaction::{Action, PendingTransaction, Transaction, TypedTransaction}; use SyncConfig; fn new_tx(secret: &Secret, nonce: U256, chain_id: u64) -> PendingTransaction { - let signed = Transaction { + let signed = TypedTransaction::Legacy(Transaction { nonce: nonce.into(), gas_price: 0.into(), gas: 21000.into(), action: Action::Call(Address::default()), value: 0.into(), data: Vec::new(), - } + }) .sign(secret, Some(chain_id)); PendingTransaction::new(signed, None) } diff --git a/ethcore/types/Cargo.toml b/ethcore/types/Cargo.toml index 1d831278f..5fa288cc9 100644 --- a/ethcore/types/Cargo.toml +++ b/ethcore/types/Cargo.toml @@ -14,6 +14,9 @@ parity-bytes = "0.1" rlp = { version = "0.3.0", features = ["ethereum"] } rlp_derive = { path = "../../util/rlp-derive" } unexpected = { path = "../../util/unexpected" } +serde = "1.0" +serde_json = "1.0" +serde_repr = "0.1" [dev-dependencies] rustc-hex = "1.0" diff --git a/ethcore/types/src/block.rs b/ethcore/types/src/block.rs index dea92ad5b..221d4d64e 100644 --- a/ethcore/types/src/block.rs +++ b/ethcore/types/src/block.rs @@ -35,7 +35,7 @@ use bytes::Bytes; use header::Header; use rlp::{Decodable, DecoderError, Rlp, RlpStream}; -use transaction::UnverifiedTransaction; +use transaction::{TypedTransaction, UnverifiedTransaction}; /// A block, encoded as it is on the block chain. #[derive(Default, Debug, Clone, PartialEq)] @@ -53,7 +53,7 @@ impl Block { pub fn rlp_bytes(&self) -> Bytes { let mut block_rlp = RlpStream::new_list(3); block_rlp.append(&self.header); - block_rlp.append_list(&self.transactions); + TypedTransaction::rlp_append_list(&mut block_rlp, &self.transactions); block_rlp.append_list(&self.uncles); block_rlp.out() } @@ -69,7 +69,7 @@ impl Decodable for Block { } Ok(Block { header: rlp.val_at(0)?, - transactions: rlp.list_at(1)?, + transactions: TypedTransaction::decode_rlp_list(&rlp.at(1)?)?, uncles: rlp.list_at(2)?, }) } diff --git a/ethcore/types/src/lib.rs b/ethcore/types/src/lib.rs index cfd301eb7..833e5a61c 100644 --- a/ethcore/types/src/lib.rs +++ b/ethcore/types/src/lib.rs @@ -41,6 +41,7 @@ extern crate heapsize; extern crate keccak_hash as hash; extern crate parity_bytes as bytes; extern crate rlp; +extern crate serde_repr; extern crate unexpected; #[macro_use] diff --git a/ethcore/types/src/receipt.rs b/ethcore/types/src/receipt.rs index 916ab6025..6e361e4ac 100644 --- a/ethcore/types/src/receipt.rs +++ b/ethcore/types/src/receipt.rs @@ -16,9 +16,14 @@ //! Receipt +use super::transaction::TypedTxId; use ethereum_types::{Address, Bloom, H160, H256, U256}; use heapsize::HeapSizeOf; -use rlp::{Decodable, DecoderError, Encodable, Rlp, RlpStream}; +use rlp::{DecoderError, Rlp, RlpStream}; +use std::{ + convert::TryInto, + ops::{Deref, DerefMut}, +}; use log_entry::{LocalizedLogEntry, LogEntry}; use BlockNumber; @@ -36,7 +41,7 @@ pub enum TransactionOutcome { /// Information describing execution of a transaction. #[derive(Debug, Clone, PartialEq, Eq)] -pub struct Receipt { +pub struct LegacyReceipt { /// The total gas used in the block following execution of the transaction. pub gas_used: U256, /// The OR-wide combination of all logs' blooms for this transaction. @@ -47,10 +52,9 @@ pub struct Receipt { pub outcome: TransactionOutcome, } -impl Receipt { - /// Create a new receipt. +impl LegacyReceipt { pub fn new(outcome: TransactionOutcome, gas_used: U256, logs: Vec) -> Self { - Self { + LegacyReceipt { gas_used, log_bloom: logs.iter().fold(Bloom::default(), |mut b, l| { b.accrue_bloom(&l.bloom()); @@ -60,10 +64,32 @@ impl Receipt { outcome, } } -} + pub fn decode(rlp: &Rlp) -> Result { + match rlp.item_count()? { + 3 => Ok(LegacyReceipt { + outcome: TransactionOutcome::Unknown, + gas_used: rlp.val_at(0)?, + log_bloom: rlp.val_at(1)?, + logs: rlp.list_at(2)?, + }), + 4 => Ok(LegacyReceipt { + gas_used: rlp.val_at(1)?, + log_bloom: rlp.val_at(2)?, + logs: rlp.list_at(3)?, + outcome: { + let first = rlp.at(0)?; + if first.is_data() && first.data()?.len() <= 1 { + TransactionOutcome::StatusCode(first.as_val()?) + } else { + TransactionOutcome::StateRoot(first.as_val()?) + } + }, + }), + _ => Err(DecoderError::RlpIncorrectListLen), + } + } -impl Encodable for Receipt { - fn rlp_append(&self, s: &mut RlpStream) { + pub fn rlp_append(&self, s: &mut RlpStream) { match self.outcome { TransactionOutcome::Unknown => { s.begin_list(3); @@ -83,42 +109,142 @@ impl Encodable for Receipt { } } -impl Decodable for Receipt { - fn decode(rlp: &Rlp) -> Result { - if rlp.item_count()? == 3 { - Ok(Receipt { - outcome: TransactionOutcome::Unknown, - gas_used: rlp.val_at(0)?, - log_bloom: rlp.val_at(1)?, - logs: rlp.list_at(2)?, - }) +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum TypedReceipt { + Legacy(LegacyReceipt), + AccessList(LegacyReceipt), +} + +impl TypedReceipt { + /// Create a new receipt. + pub fn new(type_id: TypedTxId, legacy_receipt: LegacyReceipt) -> Self { + //curently we are using same receipt for both legacy and typed transaction + match type_id { + TypedTxId::AccessList => Self::AccessList(legacy_receipt), + TypedTxId::Legacy => Self::Legacy(legacy_receipt), + } + } + + pub fn tx_type(&self) -> TypedTxId { + match self { + Self::Legacy(_) => TypedTxId::Legacy, + Self::AccessList(_) => TypedTxId::AccessList, + } + } + + pub fn receipt(&self) -> &LegacyReceipt { + match self { + Self::Legacy(receipt) => receipt, + Self::AccessList(receipt) => receipt, + } + } + + pub fn receipt_mut(&mut self) -> &mut LegacyReceipt { + match self { + Self::Legacy(receipt) => receipt, + Self::AccessList(receipt) => receipt, + } + } + + fn decode(tx: &[u8]) -> Result { + if tx.is_empty() { + // at least one byte needs to be present + return Err(DecoderError::RlpIncorrectListLen); + } + let id = tx[0].try_into(); + if id.is_err() { + return Err(DecoderError::Custom("Unknown transaction")); + } + //other transaction types + match id.unwrap() { + TypedTxId::AccessList => { + let rlp = Rlp::new(&tx[1..]); + Ok(Self::AccessList(LegacyReceipt::decode(&rlp)?)) + } + TypedTxId::Legacy => Ok(Self::Legacy(LegacyReceipt::decode(&Rlp::new(tx))?)), + } + } + + pub fn decode_rlp(rlp: &Rlp) -> Result { + if rlp.is_list() { + //legacy transaction wrapped around RLP encoding + Ok(Self::Legacy(LegacyReceipt::decode(rlp)?)) } else { - Ok(Receipt { - gas_used: rlp.val_at(1)?, - log_bloom: rlp.val_at(2)?, - logs: rlp.list_at(3)?, - outcome: { - let first = rlp.at(0)?; - if first.is_data() && first.data()?.len() <= 1 { - TransactionOutcome::StatusCode(first.as_val()?) - } else { - TransactionOutcome::StateRoot(first.as_val()?) - } - }, - }) + Self::decode(rlp.data()?) + } + } + + pub fn decode_rlp_list(rlp: &Rlp) -> Result, DecoderError> { + if !rlp.is_list() { + // at least one byte needs to be present + return Err(DecoderError::RlpIncorrectListLen); + } + let mut output = Vec::with_capacity(rlp.item_count()?); + for tx in rlp.iter() { + output.push(Self::decode_rlp(&tx)?); + } + Ok(output) + } + + pub fn rlp_append(&self, s: &mut RlpStream) { + match self { + Self::Legacy(receipt) => receipt.rlp_append(s), + Self::AccessList(receipt) => { + let mut rlps = RlpStream::new(); + receipt.rlp_append(&mut rlps); + s.append(&[&[TypedTxId::AccessList as u8], rlps.as_raw()].concat()); + } + } + } + + pub fn rlp_append_list(s: &mut RlpStream, list: &[TypedReceipt]) { + s.begin_list(list.len()); + for rec in list.iter() { + rec.rlp_append(s) + } + } + + pub fn encode(&self) -> Vec { + match self { + Self::Legacy(receipt) => { + let mut s = RlpStream::new(); + receipt.rlp_append(&mut s); + s.drain() + } + Self::AccessList(receipt) => { + let mut rlps = RlpStream::new(); + receipt.rlp_append(&mut rlps); + [&[TypedTxId::AccessList as u8], rlps.as_raw()].concat() + } } } } -impl HeapSizeOf for Receipt { +impl Deref for TypedReceipt { + type Target = LegacyReceipt; + + fn deref(&self) -> &Self::Target { + self.receipt() + } +} + +impl DerefMut for TypedReceipt { + fn deref_mut(&mut self) -> &mut LegacyReceipt { + self.receipt_mut() + } +} + +impl HeapSizeOf for TypedReceipt { fn heap_size_of_children(&self) -> usize { - self.logs.heap_size_of_children() + self.receipt().logs.heap_size_of_children() } } /// Receipt with additional info. #[derive(Debug, Clone, PartialEq)] pub struct RichReceipt { + /// Transaction type + pub transaction_type: TypedTxId, /// Transaction hash. pub transaction_hash: H256, /// Transaction index. @@ -146,6 +272,8 @@ pub struct RichReceipt { /// Receipt with additional info. #[derive(Debug, Clone, PartialEq)] pub struct LocalizedReceipt { + /// Transaction type + pub transaction_type: TypedTxId, /// Transaction hash. pub transaction_hash: H256, /// Transaction index. @@ -176,59 +304,91 @@ pub struct LocalizedReceipt { #[cfg(test)] mod tests { - use super::{Receipt, TransactionOutcome}; + use super::{LegacyReceipt, TransactionOutcome, TypedReceipt, TypedTxId}; use log_entry::LogEntry; #[test] fn test_no_state_root() { let expected = ::rustc_hex::FromHex::from_hex("f9014183040caeb9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000f838f794dcf421d093428b096ca501a7cd1a740855a7976fc0a00000000000000000000000000000000000000000000000000000000000000000").unwrap(); - let r = Receipt::new( - TransactionOutcome::Unknown, - 0x40cae.into(), - vec![LogEntry { - address: "dcf421d093428b096ca501a7cd1a740855a7976f".into(), - topics: vec![], - data: vec![0u8; 32], - }], + let r = TypedReceipt::new( + TypedTxId::Legacy, + LegacyReceipt::new( + TransactionOutcome::Unknown, + 0x40cae.into(), + vec![LogEntry { + address: "dcf421d093428b096ca501a7cd1a740855a7976f".into(), + topics: vec![], + data: vec![0u8; 32], + }], + ), ); - assert_eq!(&::rlp::encode(&r)[..], &expected[..]); + assert_eq!(r.encode(), expected); } #[test] - fn test_basic() { + fn test_basic_legacy() { let expected = ::rustc_hex::FromHex::from_hex("f90162a02f697d671e9ae4ee24a43c4b0d7e15f1cb4ba6de1561120d43b9a4e8c4a8a6ee83040caeb9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000f838f794dcf421d093428b096ca501a7cd1a740855a7976fc0a00000000000000000000000000000000000000000000000000000000000000000").unwrap(); - let r = Receipt::new( - TransactionOutcome::StateRoot( - "2f697d671e9ae4ee24a43c4b0d7e15f1cb4ba6de1561120d43b9a4e8c4a8a6ee".into(), + let r = TypedReceipt::new( + TypedTxId::Legacy, + LegacyReceipt::new( + TransactionOutcome::StateRoot( + "2f697d671e9ae4ee24a43c4b0d7e15f1cb4ba6de1561120d43b9a4e8c4a8a6ee".into(), + ), + 0x40cae.into(), + vec![LogEntry { + address: "dcf421d093428b096ca501a7cd1a740855a7976f".into(), + topics: vec![], + data: vec![0u8; 32], + }], ), - 0x40cae.into(), - vec![LogEntry { - address: "dcf421d093428b096ca501a7cd1a740855a7976f".into(), - topics: vec![], - data: vec![0u8; 32], - }], ); - let encoded = ::rlp::encode(&r); - assert_eq!(&encoded[..], &expected[..]); - let decoded: Receipt = ::rlp::decode(&encoded).expect("decoding receipt failed"); + let encoded = r.encode(); + assert_eq!(encoded, expected); + let decoded = TypedReceipt::decode(&encoded).expect("decoding receipt failed"); + assert_eq!(decoded, r); + } + + #[test] + fn test_basic_access_list() { + let expected = ::rustc_hex::FromHex::from_hex("01f90162a02f697d671e9ae4ee24a43c4b0d7e15f1cb4ba6de1561120d43b9a4e8c4a8a6ee83040caeb9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000f838f794dcf421d093428b096ca501a7cd1a740855a7976fc0a00000000000000000000000000000000000000000000000000000000000000000").unwrap(); + let r = TypedReceipt::new( + TypedTxId::AccessList, + LegacyReceipt::new( + TransactionOutcome::StateRoot( + "2f697d671e9ae4ee24a43c4b0d7e15f1cb4ba6de1561120d43b9a4e8c4a8a6ee".into(), + ), + 0x40cae.into(), + vec![LogEntry { + address: "dcf421d093428b096ca501a7cd1a740855a7976f".into(), + topics: vec![], + data: vec![0u8; 32], + }], + ), + ); + let encoded = r.encode(); + assert_eq!(&encoded, &expected); + let decoded = TypedReceipt::decode(&encoded).expect("decoding receipt failed"); assert_eq!(decoded, r); } #[test] fn test_status_code() { let expected = ::rustc_hex::FromHex::from_hex("f901428083040caeb9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000f838f794dcf421d093428b096ca501a7cd1a740855a7976fc0a00000000000000000000000000000000000000000000000000000000000000000").unwrap(); - let r = Receipt::new( - TransactionOutcome::StatusCode(0), - 0x40cae.into(), - vec![LogEntry { - address: "dcf421d093428b096ca501a7cd1a740855a7976f".into(), - topics: vec![], - data: vec![0u8; 32], - }], + let r = TypedReceipt::new( + TypedTxId::Legacy, + LegacyReceipt::new( + TransactionOutcome::StatusCode(0), + 0x40cae.into(), + vec![LogEntry { + address: "dcf421d093428b096ca501a7cd1a740855a7976f".into(), + topics: vec![], + data: vec![0u8; 32], + }], + ), ); - let encoded = ::rlp::encode(&r); + let encoded = r.encode(); assert_eq!(&encoded[..], &expected[..]); - let decoded: Receipt = ::rlp::decode(&encoded).expect("decoding receipt failed"); + let decoded = TypedReceipt::decode(&encoded).expect("decoding receipt failed"); assert_eq!(decoded, r); } } diff --git a/ethcore/types/src/transaction/error.rs b/ethcore/types/src/transaction/error.rs index 4d62868c2..c4813eba3 100644 --- a/ethcore/types/src/transaction/error.rs +++ b/ethcore/types/src/transaction/error.rs @@ -84,6 +84,8 @@ pub enum Error { TooBig, /// Invalid RLP encoding InvalidRlp(String), + /// Transaciton is still not enabled. + TransactionTypeNotEnabled, } impl From for Error { @@ -133,6 +135,9 @@ impl fmt::Display for Error { } TooBig => "Transaction too big".into(), InvalidRlp(ref err) => format!("Transaction has invalid RLP structure: {}.", err), + TransactionTypeNotEnabled => { + format!("Transaction type is not enabled for current block") + } }; f.write_fmt(format_args!("Transaction error ({})", msg)) diff --git a/ethcore/types/src/transaction/mod.rs b/ethcore/types/src/transaction/mod.rs index 5af69712d..3a8afd07d 100644 --- a/ethcore/types/src/transaction/mod.rs +++ b/ethcore/types/src/transaction/mod.rs @@ -18,5 +18,6 @@ mod error; mod transaction; +mod transaction_id; -pub use self::{error::Error, transaction::*}; +pub use self::{error::Error, transaction::*, transaction_id::*}; diff --git a/ethcore/types/src/transaction/transaction.rs b/ethcore/types/src/transaction/transaction.rs index f4de8fbcd..ae8bf75b0 100644 --- a/ethcore/types/src/transaction/transaction.rs +++ b/ethcore/types/src/transaction/transaction.rs @@ -16,14 +16,17 @@ //! Transaction data structure. -use std::ops::Deref; - use ethereum_types::{Address, H160, H256, U256}; use ethjson; use ethkey::{self, public_to_address, recover, Public, Secret, Signature}; use hash::keccak; use heapsize::HeapSizeOf; -use rlp::{self, DecoderError, Encodable, Rlp, RlpStream}; +use rlp::{self, DecoderError, Rlp, RlpStream}; +use std::{convert::TryInto, ops::Deref}; + +pub type AccessList = Vec<(H160, Vec)>; + +use super::TypedTxId; use transaction::error; @@ -90,17 +93,18 @@ pub enum Condition { /// Replay protection logic for v part of transaction's signature pub mod signature { /// Adds chain id into v - pub fn add_chain_replay_protection(v: u64, chain_id: Option) -> u64 { - v + if let Some(n) = chain_id { - 35 + n * 2 - } else { - 27 - } + pub fn add_chain_replay_protection(v: u8, chain_id: Option) -> u64 { + v as u64 + + if let Some(n) = chain_id { + 35 + n * 2 + } else { + 27 + } } /// Returns refined v /// 0 if `v` would have been 27 under "Electrum" notation, 1 if 28 or 4 if invalid. - pub fn check_replay_protection(v: u64) -> u8 { + pub fn extract_standard_v(v: u64) -> u8 { match v { v if v == 27 => 0, v if v == 28 => 1, @@ -108,6 +112,14 @@ pub mod signature { _ => 4, } } + + pub fn extract_chain_id_from_legacy_v(v: u64) -> Option { + if v >= 35 { + Some((v - 35) / 2 as u64) + } else { + None + } + } } /// A set of information describing an externally-originating message call @@ -129,20 +141,96 @@ pub struct Transaction { } impl Transaction { - /// Append object with a without signature into RLP stream - pub fn rlp_append_unsigned_transaction(&self, s: &mut RlpStream, chain_id: Option) { - s.begin_list(if chain_id.is_none() { 6 } else { 9 }); + /// The message hash of the transaction. This hash is used for signing transaction + pub fn signature_hash(&self, chain_id: Option) -> H256 { + keccak(self.encode(chain_id, None)) + } + + /// encode raw transaction + fn encode(&self, chain_id: Option, signature: Option<&SignatureComponents>) -> Vec { + let mut stream = RlpStream::new(); + self.encode_rlp(&mut stream, chain_id, signature); + stream.drain() + } + + pub fn rlp_append( + &self, + rlp: &mut RlpStream, + chain_id: Option, + signature: &SignatureComponents, + ) { + self.encode_rlp(rlp, chain_id, Some(signature)); + } + + fn encode_rlp( + &self, + rlp: &mut RlpStream, + chain_id: Option, + signature: Option<&SignatureComponents>, + ) { + let list_size = if chain_id.is_some() || signature.is_some() { + 9 + } else { + 6 + }; + rlp.begin_list(list_size); + + self.rlp_append_data_open(rlp); + + //append signature if given. If not, try to append chainId. + if let Some(signature) = signature { + signature.rlp_append_with_chain_id(rlp, chain_id); + } else { + if let Some(n) = chain_id { + rlp.append(&n); + rlp.append(&0u8); + rlp.append(&0u8); + } + } + } + + fn rlp_append_data_open(&self, s: &mut RlpStream) { s.append(&self.nonce); s.append(&self.gas_price); s.append(&self.gas); s.append(&self.action); s.append(&self.value); s.append(&self.data); - if let Some(n) = chain_id { - s.append(&n); - s.append(&0u8); - s.append(&0u8); + } + + fn decode(d: &Rlp) -> Result { + if d.item_count()? != 9 { + return Err(DecoderError::RlpIncorrectListLen); } + let hash = keccak(d.as_raw()); + + let transaction = TypedTransaction::Legacy(Self::decode_data(d, 0)?); + + // take V from signatuere and decompose it into chain_id and standard V. + let legacy_v: u64 = d.val_at(6)?; + + let signature = SignatureComponents { + standard_v: signature::extract_standard_v(legacy_v), + r: d.val_at(7)?, + s: d.val_at(8)?, + }; + Ok(UnverifiedTransaction::new( + transaction, + signature::extract_chain_id_from_legacy_v(legacy_v), + signature, + hash, + )) + } + + fn decode_data(d: &Rlp, offset: usize) -> Result { + Ok(Transaction { + nonce: d.val_at(offset)?, + gas_price: d.val_at(offset + 1)?, + gas: d.val_at(offset + 2)?, + action: d.val_at(offset + 3)?, + value: d.val_at(offset + 4)?, + data: d.val_at(offset + 5)?, + }) } } @@ -152,64 +240,180 @@ impl HeapSizeOf for Transaction { } } -#[cfg(any(test, feature = "test-helpers"))] -impl From for SignedTransaction { - fn from(t: ethjson::state::Transaction) -> Self { - let to: Option = t.to.into(); - let secret = t.secret.map(|s| Secret::from(s.0)); - let tx = Transaction { - nonce: t.nonce.into(), - gas_price: t.gas_price.into(), - gas: t.gas_limit.into(), - action: match to { - Some(to) => Action::Call(to.into()), - None => Action::Create, - }, - value: t.value.into(), - data: t.data.into(), +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct AccessListTx { + pub transaction: Transaction, + //optional access list + pub access_list: AccessList, +} + +impl AccessListTx { + pub fn new(transaction: Transaction, access_list: AccessList) -> AccessListTx { + AccessListTx { + transaction, + access_list, + } + } + + pub fn tx_type(&self) -> TypedTxId { + TypedTxId::AccessList + } + + pub fn tx(&self) -> &Transaction { + &self.transaction + } + + pub fn tx_mut(&mut self) -> &mut Transaction { + &mut self.transaction + } + + // decode bytes by this payload spec: rlp([3, [chainId, nonce, gasPrice, gasLimit, to, value, data, access_list, senderV, senderR, senderS]]) + pub fn decode(tx: &[u8]) -> Result { + let tx_rlp = &Rlp::new(tx); + + // we need to have 11 items in this list + if tx_rlp.item_count()? != 11 { + return Err(DecoderError::RlpIncorrectListLen); + } + + let chain_id = Some(tx_rlp.val_at(0)?); + //let chain_id = if chain_id == 0 { None } else { Some(chain_id) }; + + // first part of list is same as legacy transaction and we are reusing that part. + let transaction = Transaction::decode_data(&tx_rlp, 1)?; + + // access list we get from here + let accl_rlp = tx_rlp.at(7)?; + + // access_list pattern: [[{20 bytes}, [{32 bytes}...]]...] + let mut accl: AccessList = Vec::new(); + + for i in 0..accl_rlp.item_count()? { + let accounts = accl_rlp.at(i)?; + + // check if there is list of 2 items + if accounts.item_count()? != 2 { + return Err(DecoderError::Custom("Unknown access list length")); + } + accl.push((accounts.val_at(0)?, accounts.list_at(1)?)); + } + + // we get signature part from here + let signature = SignatureComponents { + standard_v: tx_rlp.val_at(8)?, + r: tx_rlp.val_at(9)?, + s: tx_rlp.val_at(10)?, }; - match secret { - Some(s) => tx.sign(&s, None), - None => tx.null_sign(1), - } - } -} -impl From for UnverifiedTransaction { - fn from(t: ethjson::transaction::Transaction) -> Self { - let to: Option = t.to.into(); - UnverifiedTransaction { - unsigned: Transaction { - nonce: t.nonce.into(), - gas_price: t.gas_price.into(), - gas: t.gas_limit.into(), - action: match to { - Some(to) => Action::Call(to.into()), - None => Action::Create, - }, - value: t.value.into(), - data: t.data.into(), - }, - r: t.r.into(), - s: t.s.into(), - v: t.v.into(), - hash: 0.into(), - } - .compute_hash() + // and here we create UnverifiedTransaction and calculate its hash + Ok(UnverifiedTransaction::new( + TypedTransaction::AccessList(AccessListTx { + transaction, + access_list: accl, + }), + chain_id, + signature, + 0.into(), + ) + .compute_hash()) } -} -impl Transaction { - /// The message hash of the transaction. - pub fn hash(&self, chain_id: Option) -> H256 { + fn encode_payload( + &self, + tx_type: Option, //used only for signature hashing for EIP-2930 + chain_id: Option, + signature: Option<&SignatureComponents>, + ) -> RlpStream { let mut stream = RlpStream::new(); - self.rlp_append_unsigned_transaction(&mut stream, chain_id); - keccak(stream.as_raw()) + + let mut list_size = if signature.is_some() { 11 } else { 8 }; + list_size += if tx_type.is_some() { 1 } else { 0 }; + stream.begin_list(list_size); + + // append tx_type at beggining of list, this is used only for data hash for signature. + if let Some(tx_type) = tx_type { + stream.append(&tx_type); + } + + // append chain_id. from EIP-2930: chainId is defined to be an integer of arbitrary size. + stream.append(&(if let Some(n) = chain_id { n } else { 0 })); + + // append legacy transaction + self.transaction.rlp_append_data_open(&mut stream); + + // access list + stream.begin_list(self.access_list.len()); + for access in self.access_list.iter() { + stream.begin_list(2); + stream.append(&access.0); + stream.begin_list(access.1.len()); + for storage_key in access.1.iter() { + stream.append(storage_key); + } + } + + // append signature + if let Some(signature) = signature { + signature.rlp_append(&mut stream); + } + stream + } + + // encode by this payload spec: 0x01 | rlp([1, [chain_id, nonce, gasPrice, gasLimit, to, value, data, access_list, senderV, senderR, senderS]]) + pub fn encode( + &self, + chain_id: Option, + signature: Option<&SignatureComponents>, + ) -> Vec { + let stream = self.encode_payload(None, chain_id, signature); + // make as vector of bytes + [&[TypedTxId::AccessList as u8], stream.as_raw()].concat() + } + + pub fn rlp_append( + &self, + rlp: &mut RlpStream, + chain_id: Option, + signature: &SignatureComponents, + ) { + rlp.append(&self.encode(chain_id, Some(signature))); + } + + pub fn signature_hash(&self, chain_id: Option) -> H256 { + keccak( + &self + .encode_payload(Some(TypedTxId::AccessList as u8), chain_id, None) + .as_raw(), + ) + } +} + +#[derive(Debug, Clone, Eq, PartialEq)] +pub enum TypedTransaction { + Legacy(Transaction), // old legacy RLP encoded transaction + AccessList(AccessListTx), // EIP-2930 Transaction with a list of addresses and storage keys that the transaction plans to access. + // Accesses outside the list are possible, but become more expensive. +} + +impl TypedTransaction { + pub fn tx_type(&self) -> TypedTxId { + match self { + Self::Legacy(_) => TypedTxId::Legacy, + Self::AccessList(_) => TypedTxId::AccessList, + } + } + + /// The message hash of the transaction. + pub fn signature_hash(&self, chain_id: Option) -> H256 { + match self { + Self::Legacy(tx) => tx.signature_hash(chain_id), + Self::AccessList(tx) => tx.signature_hash(chain_id), + } } /// Signs the transaction as coming from `sender`. pub fn sign(self, secret: &Secret, chain_id: Option) -> SignedTransaction { - let sig = ::ethkey::sign(secret, &self.hash(chain_id)) + let sig = ::ethkey::sign(secret, &self.signature_hash(chain_id)) .expect("data is valid and context has signing capabilities; qed"); SignedTransaction::new(self.with_signature(sig, chain_id)) .expect("secret is valid so it's recoverable") @@ -219,22 +423,12 @@ impl Transaction { pub fn with_signature(self, sig: Signature, chain_id: Option) -> UnverifiedTransaction { UnverifiedTransaction { unsigned: self, - r: sig.r().into(), - s: sig.s().into(), - v: signature::add_chain_replay_protection(sig.v() as u64, chain_id), - hash: 0.into(), - } - .compute_hash() - } - - /// Useful for test incorrectly signed transactions. - #[cfg(test)] - pub fn invalid_sign(self) -> UnverifiedTransaction { - UnverifiedTransaction { - unsigned: self, - r: U256::one(), - s: U256::one(), - v: 0, + chain_id, + signature: SignatureComponents { + r: sig.r().into(), + s: sig.s().into(), + standard_v: sig.v().into(), + }, hash: 0.into(), } .compute_hash() @@ -245,9 +439,12 @@ impl Transaction { SignedTransaction { transaction: UnverifiedTransaction { unsigned: self, - r: U256::one(), - s: U256::one(), - v: 0, + chain_id: None, + signature: SignatureComponents { + r: U256::one(), + s: U256::one(), + standard_v: 4, + }, hash: 0.into(), } .compute_hash(), @@ -264,9 +461,12 @@ impl Transaction { SignedTransaction { transaction: UnverifiedTransaction { unsigned: self, - r: U256::zero(), - s: U256::zero(), - v: chain_id, + chain_id: Some(chain_id), + signature: SignatureComponents { + r: U256::zero(), + s: U256::zero(), + standard_v: 0, + }, hash: 0.into(), } .compute_hash(), @@ -274,20 +474,216 @@ impl Transaction { public: None, } } + + /// Useful for test incorrectly signed transactions. + #[cfg(test)] + pub fn invalid_sign(self) -> UnverifiedTransaction { + UnverifiedTransaction { + unsigned: self, + chain_id: None, + signature: SignatureComponents { + r: U256::one(), + s: U256::one(), + standard_v: 0, + }, + hash: 0.into(), + } + .compute_hash() + } + + // Next functions are for encoded/decode + + pub fn tx(&self) -> &Transaction { + match self { + Self::Legacy(tx) => tx, + Self::AccessList(ocl) => ocl.tx(), + } + } + + pub fn tx_mut(&mut self) -> &mut Transaction { + match self { + Self::Legacy(tx) => tx, + Self::AccessList(ocl) => ocl.tx_mut(), + } + } + + fn decode_new(tx: &[u8]) -> Result { + if tx.is_empty() { + // at least one byte needs to be present + return Err(DecoderError::RlpIncorrectListLen); + } + let id = tx[0].try_into(); + if id.is_err() { + return Err(DecoderError::Custom("Unknown transaction")); + } + // other transaction types + match id.unwrap() { + TypedTxId::AccessList => AccessListTx::decode(&tx[1..]), + TypedTxId::Legacy => return Err(DecoderError::Custom("Unknown transaction legacy")), + } + } + + pub fn decode(tx: &[u8]) -> Result { + if tx.is_empty() { + // at least one byte needs to be present + return Err(DecoderError::RlpIncorrectListLen); + } + let header = tx[0]; + // type of transaction can be obtained from first byte. If first bit is 1 it means we are dealing with RLP list. + // if it is 0 it means that we are dealing with custom transaction defined in EIP-2718. + if (header & 0x80) != 0x00 { + Transaction::decode(&Rlp::new(tx)) + } else { + Self::decode_new(tx) + } + } + + pub fn decode_rlp_list(rlp: &Rlp) -> Result, DecoderError> { + if !rlp.is_list() { + // at least one byte needs to be present + return Err(DecoderError::RlpIncorrectListLen); + } + let mut output = Vec::with_capacity(rlp.item_count()?); + for tx in rlp.iter() { + output.push(Self::decode_rlp(&tx)?); + } + Ok(output) + } + + pub fn decode_rlp(tx: &Rlp) -> Result { + if tx.is_list() { + //legacy transaction wrapped around RLP encoding + Transaction::decode(tx) + } else { + Self::decode_new(tx.data()?) + } + } + + fn rlp_append( + &self, + s: &mut RlpStream, + chain_id: Option, + signature: &SignatureComponents, + ) { + match self { + Self::Legacy(tx) => tx.rlp_append(s, chain_id, signature), + Self::AccessList(opt) => opt.rlp_append(s, chain_id, signature), + } + } + + pub fn rlp_append_list(s: &mut RlpStream, tx_list: &[UnverifiedTransaction]) { + s.begin_list(tx_list.len()); + for tx in tx_list.iter() { + tx.unsigned.rlp_append(s, tx.chain_id, &tx.signature); + } + } + + fn encode(&self, chain_id: Option, signature: &SignatureComponents) -> Vec { + let signature = Some(signature); + match self { + Self::Legacy(tx) => tx.encode(chain_id, signature), + Self::AccessList(opt) => opt.encode(chain_id, signature), + } + } +} + +impl HeapSizeOf for TypedTransaction { + fn heap_size_of_children(&self) -> usize { + match self { + TypedTransaction::Legacy(legacy) => legacy.heap_size_of_children(), + TypedTransaction::AccessList(oal) => oal.tx().heap_size_of_children(), + } + } +} + +/// Components that constitute transaction signature +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct SignatureComponents { + /// The V field of the signature; the LS bit described which half of the curve our point falls + /// in. It can be 0 or 1. + standard_v: u8, + /// The R field of the signature; helps describe the point on the curve. + r: U256, + /// The S field of the signature; helps describe the point on the curve. + s: U256, +} + +impl SignatureComponents { + pub fn rlp_append(&self, s: &mut RlpStream) { + s.append(&self.standard_v); + s.append(&self.r); + s.append(&self.s); + } + + pub fn rlp_append_with_chain_id(&self, s: &mut RlpStream, chain_id: Option) { + s.append(&signature::add_chain_replay_protection( + self.standard_v, + chain_id, + )); + s.append(&self.r); + s.append(&self.s); + } +} + +#[cfg(any(test, feature = "test-helpers"))] +impl From for SignedTransaction { + fn from(t: ethjson::state::Transaction) -> Self { + let to: Option = t.to.into(); + let secret = t.secret.map(|s| Secret::from(s.0)); + let tx = TypedTransaction::Legacy(Transaction { + nonce: t.nonce.into(), + gas_price: t.gas_price.into(), + gas: t.gas_limit.into(), + action: match to { + Some(to) => Action::Call(to.into()), + None => Action::Create, + }, + value: t.value.into(), + data: t.data.into(), + }); + match secret { + Some(s) => tx.sign(&s, None), + None => tx.null_sign(1), + } + } +} + +impl From for UnverifiedTransaction { + fn from(t: ethjson::transaction::Transaction) -> Self { + let to: Option = t.to.into(); + UnverifiedTransaction { + unsigned: TypedTransaction::Legacy(Transaction { + nonce: t.nonce.into(), + gas_price: t.gas_price.into(), + gas: t.gas_limit.into(), + action: match to { + Some(to) => Action::Call(to.into()), + None => Action::Create, + }, + value: t.value.into(), + data: t.data.into(), + }), + chain_id: signature::extract_chain_id_from_legacy_v(t.v.into()), + signature: SignatureComponents { + r: t.r.into(), + s: t.s.into(), + standard_v: signature::extract_standard_v(t.v.into()), + }, + hash: 0.into(), + } + .compute_hash() + } } /// Signed transaction information without verified signature. #[derive(Debug, Clone, Eq, PartialEq)] pub struct UnverifiedTransaction { /// Plain Transaction. - unsigned: Transaction, - /// The V field of the signature; the LS bit described which half of the curve our point falls - /// in. The MS bits describe which chain this transaction is for. If 27/28, its for all chains. - v: u64, - /// The R field of the signature; helps describe the point on the curve. - r: U256, - /// The S field of the signature; helps describe the point on the curve. - s: U256, + unsigned: TypedTransaction, + /// Transaction signature + signature: SignatureComponents, + /// chain_id recover from signature in legacy transaction. For TypedTransaction it is probably separate field. + chain_id: Option, /// Hash of the transaction hash: H256, } @@ -299,96 +695,75 @@ impl HeapSizeOf for UnverifiedTransaction { } impl Deref for UnverifiedTransaction { - type Target = Transaction; + type Target = TypedTransaction; fn deref(&self) -> &Self::Target { &self.unsigned } } -impl rlp::Decodable for UnverifiedTransaction { - fn decode(d: &Rlp) -> Result { - if d.item_count()? != 9 { - return Err(DecoderError::RlpIncorrectListLen); - } - let hash = keccak(d.as_raw()); - Ok(UnverifiedTransaction { - unsigned: Transaction { - nonce: d.val_at(0)?, - gas_price: d.val_at(1)?, - gas: d.val_at(2)?, - action: d.val_at(3)?, - value: d.val_at(4)?, - data: d.val_at(5)?, - }, - v: d.val_at(6)?, - r: d.val_at(7)?, - s: d.val_at(8)?, - hash, - }) - } -} - -impl rlp::Encodable for UnverifiedTransaction { - fn rlp_append(&self, s: &mut RlpStream) { - self.rlp_append_sealed_transaction(s) - } -} - impl UnverifiedTransaction { - /// Used to compute hash of created transactions + pub fn rlp_append(&self, s: &mut RlpStream) { + self.unsigned.rlp_append(s, self.chain_id, &self.signature); + } + + pub fn encode(&self) -> Vec { + self.unsigned.encode(self.chain_id, &self.signature) + } + + /// Used to compute hash of created transactions. fn compute_hash(mut self) -> UnverifiedTransaction { - let hash = keccak(&*self.rlp_bytes()); + let hash = keccak(&*self.encode()); self.hash = hash; self } + /// Used by TypedTransaction to create UnverifiedTransaction. + fn new( + transaction: TypedTransaction, + chain_id: Option, + signature: SignatureComponents, + hash: H256, + ) -> UnverifiedTransaction { + UnverifiedTransaction { + unsigned: transaction, + chain_id, + signature, + hash, + } + } /// Checks if the signature is empty. pub fn is_unsigned(&self) -> bool { - self.r.is_zero() && self.s.is_zero() - } - - /// Append object with a signature into RLP stream - fn rlp_append_sealed_transaction(&self, s: &mut RlpStream) { - s.begin_list(9); - s.append(&self.nonce); - s.append(&self.gas_price); - s.append(&self.gas); - s.append(&self.action); - s.append(&self.value); - s.append(&self.data); - s.append(&self.v); - s.append(&self.r); - s.append(&self.s); + self.signature.r.is_zero() && self.signature.s.is_zero() } /// Reference to unsigned part of this transaction. - pub fn as_unsigned(&self) -> &Transaction { + pub fn as_unsigned(&self) -> &TypedTransaction { &self.unsigned } /// Returns standardized `v` value (0, 1 or 4 (invalid)) pub fn standard_v(&self) -> u8 { - signature::check_replay_protection(self.v) + self.signature.standard_v } /// The `v` value that appears in the RLP. pub fn original_v(&self) -> u64 { - self.v + signature::add_chain_replay_protection(self.signature.standard_v, self.chain_id) } /// The chain ID, or `None` if this is a global transaction. pub fn chain_id(&self) -> Option { - match self.v { - v if self.is_unsigned() => Some(v), - v if v >= 35 => Some((v - 35) / 2), - _ => None, - } + self.chain_id } /// Construct a signature object from the sig. pub fn signature(&self) -> Signature { - Signature::from_rsv(&self.r.into(), &self.s.into(), self.standard_v()) + Signature::from_rsv( + &self.signature.r.into(), + &self.signature.s.into(), + self.standard_v(), + ) } /// Checks whether the signature has a low 's' value. @@ -409,7 +784,7 @@ impl UnverifiedTransaction { pub fn recover_public(&self) -> Result { Ok(recover( &self.signature(), - &self.unsigned.hash(self.chain_id()), + &self.unsigned.signature_hash(self.chain_id()), )?) } @@ -448,12 +823,6 @@ impl HeapSizeOf for SignedTransaction { } } -impl rlp::Encodable for SignedTransaction { - fn rlp_append(&self, s: &mut RlpStream) { - self.transaction.rlp_append_sealed_transaction(s) - } -} - impl Deref for SignedTransaction { type Target = UnverifiedTransaction; fn deref(&self) -> &Self::Target { @@ -501,6 +870,13 @@ impl SignedTransaction { pub fn deconstruct(self) -> (UnverifiedTransaction, Address, Option) { (self.transaction, self.sender, self.public) } + + pub fn rlp_append_list(s: &mut RlpStream, tx_list: &[SignedTransaction]) { + s.begin_list(tx_list.len()); + for tx in tx_list.iter() { + tx.unsigned.rlp_append(s, tx.chain_id, &tx.signature); + } + } } /// Signed Transaction that is a part of canon blockchain. @@ -588,18 +964,17 @@ mod tests { #[test] fn sender_test() { let bytes = ::rustc_hex::FromHex::from_hex("f85f800182520894095e7baea6a6c7c4c2dfeb977efac326af552d870a801ba048b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d495a36649353a0efffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab949f53faa07bd2c804").unwrap(); - let t: UnverifiedTransaction = - rlp::decode(&bytes).expect("decoding UnverifiedTransaction failed"); - assert_eq!(t.data, b""); - assert_eq!(t.gas, U256::from(0x5208u64)); - assert_eq!(t.gas_price, U256::from(0x01u64)); - assert_eq!(t.nonce, U256::from(0x00u64)); - if let Action::Call(ref to) = t.action { + let t = TypedTransaction::decode(&bytes).expect("decoding UnverifiedTransaction failed"); + assert_eq!(t.tx().data, b""); + assert_eq!(t.tx().gas, U256::from(0x5208u64)); + assert_eq!(t.tx().gas_price, U256::from(0x01u64)); + assert_eq!(t.tx().nonce, U256::from(0x00u64)); + if let Action::Call(ref to) = t.tx().action { assert_eq!(*to, "095e7baea6a6c7c4c2dfeb977efac326af552d87".into()); } else { panic!(); } - assert_eq!(t.value, U256::from(0x0au64)); + assert_eq!(t.tx().value, U256::from(0x0au64)); assert_eq!( public_to_address(&t.recover_public().unwrap()), "0f65fe9276bc9a24ae7083ae28e2660ef72df99e".into() @@ -626,16 +1001,16 @@ mod tests { use ethkey::{Generator, Random}; let key = Random.generate().unwrap(); - let t = Transaction { + let t = TypedTransaction::Legacy(Transaction { action: Action::Create, nonce: U256::from(42), gas_price: U256::from(3000), gas: U256::from(50_000), value: U256::from(1), data: b"Hello!".to_vec(), - }; + }); - let hash = t.hash(Some(0)); + let hash = t.signature_hash(Some(0)); let sig = ::ethkey::sign(&key.secret(), &hash).unwrap(); let u = t.with_signature(sig, Some(0)); @@ -647,14 +1022,14 @@ mod tests { use ethkey::{Generator, Random}; let key = Random.generate().unwrap(); - let t = Transaction { + let t = TypedTransaction::Legacy(Transaction { action: Action::Create, nonce: U256::from(42), gas_price: U256::from(3000), gas: U256::from(50_000), value: U256::from(1), data: b"Hello!".to_vec(), - } + }) .sign(&key.secret(), None); assert_eq!(Address::from(keccak(key.public())), t.sender()); assert_eq!(t.chain_id(), None); @@ -662,14 +1037,14 @@ mod tests { #[test] fn fake_signing() { - let t = Transaction { + let t = TypedTransaction::Legacy(Transaction { action: Action::Create, nonce: U256::from(42), gas_price: U256::from(3000), gas: U256::from(50_000), value: U256::from(1), data: b"Hello!".to_vec(), - } + }) .fake_sign(Address::from(0x69)); assert_eq!(Address::from(0x69), t.sender()); assert_eq!(t.chain_id(), None); @@ -682,7 +1057,7 @@ mod tests { #[test] fn should_reject_null_signature() { use std::str::FromStr; - let t = Transaction { + let t = TypedTransaction::Legacy(Transaction { nonce: U256::zero(), gas_price: U256::from(10000000000u64), gas: U256::from(21000), @@ -691,9 +1066,11 @@ mod tests { ), value: U256::from(1), data: vec![], - } + }) .null_sign(1); + println!("transaction {:?}", t); + let res = SignedTransaction::new(t.transaction); match res { Err(ethkey::Error::InvalidSignature) => {} @@ -705,26 +1082,94 @@ mod tests { fn should_recover_from_chain_specific_signing() { use ethkey::{Generator, Random}; let key = Random.generate().unwrap(); - let t = Transaction { + let t = TypedTransaction::Legacy(Transaction { action: Action::Create, nonce: U256::from(42), gas_price: U256::from(3000), gas: U256::from(50_000), value: U256::from(1), data: b"Hello!".to_vec(), - } + }) .sign(&key.secret(), Some(69)); assert_eq!(Address::from(keccak(key.public())), t.sender()); assert_eq!(t.chain_id(), Some(69)); } + #[test] + fn should_encode_decode_access_list_tx() { + use ethkey::{Generator, Random}; + let key = Random.generate().unwrap(); + let t = TypedTransaction::AccessList(AccessListTx::new( + Transaction { + action: Action::Create, + nonce: U256::from(42), + gas_price: U256::from(3000), + gas: U256::from(50_000), + value: U256::from(1), + data: b"Hello!".to_vec(), + }, + vec![ + (H160::from(10), vec![H256::from(102), H256::from(103)]), + (H160::from(400), vec![]), + ], + )) + .sign(&key.secret(), Some(69)); + let encoded = t.encode(); + + let t_new = + TypedTransaction::decode(&encoded).expect("Error on UnverifiedTransaction decoder"); + if t_new.unsigned != t.unsigned { + assert!(true, "encoded/decoded tx differs from original"); + } + } + + #[test] + fn should_decode_access_list_in_rlp() { + use rustc_hex::FromHex; + let encoded_tx = "b8cb01f8a7802a820bb882c35080018648656c6c6f21f872f85994000000000000000000000000000000000000000af842a00000000000000000000000000000000000000000000000000000000000000066a00000000000000000000000000000000000000000000000000000000000000067d6940000000000000000000000000000000000000190c080a00ea0f1fda860320f51e182fe68ea90a8e7611653d3975b9301580adade6b8aa4a023530a1a96e0f15f90959baf1cd2d9114f7c7568ac7d77f4413c0a6ca6cdac74"; + let _ = TypedTransaction::decode_rlp(&Rlp::new(&FromHex::from_hex(encoded_tx).unwrap())) + .expect("decoding tx data failed"); + } + + #[test] + fn should_decode_access_list_solo() { + use rustc_hex::FromHex; + let encoded_tx = "01f8630103018261a894b94f5374fce5edbc8e2a8697c15331677e6ebf0b0a825544c001a0cb51495c66325615bcd591505577c9dde87bd59b04be2e6ba82f6d7bdea576e3a049e4f02f37666bd91a052a56e91e71e438590df861031ee9a321ce058df3dc2b"; + let _ = TypedTransaction::decode(&FromHex::from_hex(encoded_tx).unwrap()) + .expect("decoding tx data failed"); + } + + #[test] + fn test_rlp_data() { + let mut rlp_list = RlpStream::new(); + rlp_list.begin_list(3); + rlp_list.append(&100u8); + rlp_list.append(&"0000000"); + rlp_list.append(&5u8); + let rlp_list = Rlp::new(rlp_list.as_raw()); + println!("rlp list data: {:?}", rlp_list.as_raw()); + + let mut rlp = RlpStream::new(); + rlp.append(&"1111111"); + let rlp = Rlp::new(rlp.as_raw()); + println!("rlp list data: {:?}", rlp.data()); + } + + #[test] + fn should_agree_with_geth_test() { + use rustc_hex::FromHex; + let encoded_tx = "01f8630103018261a894b94f5374fce5edbc8e2a8697c15331677e6ebf0b0a825544c001a0cb51495c66325615bcd591505577c9dde87bd59b04be2e6ba82f6d7bdea576e3a049e4f02f37666bd91a052a56e91e71e438590df861031ee9a321ce058df3dc2b"; + let _ = TypedTransaction::decode(&FromHex::from_hex(encoded_tx).unwrap()) + .expect("decoding tx data failed"); + } + #[test] fn should_agree_with_vitalik() { use rustc_hex::FromHex; let test_vector = |tx_data: &str, address: &'static str| { - let signed = - rlp::decode(&FromHex::from_hex(tx_data).unwrap()).expect("decoding tx data failed"); + let signed = TypedTransaction::decode(&FromHex::from_hex(tx_data).unwrap()) + .expect("decoding tx data failed"); let signed = SignedTransaction::new(signed).unwrap(); assert_eq!(signed.sender(), address.into()); println!("chainid: {:?}", signed.chain_id()); diff --git a/ethcore/types/src/transaction/transaction_id.rs b/ethcore/types/src/transaction/transaction_id.rs new file mode 100644 index 000000000..833a678cd --- /dev/null +++ b/ethcore/types/src/transaction/transaction_id.rs @@ -0,0 +1,45 @@ +// Copyright 2020-2020 Parity Technologies (UK) Ltd. +// This file is part of OpenEthereum. + +// OpenEthereum 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. + +// OpenEthereum 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 OpenEthereum. If not, see . + +//! Transaction Id. + +use serde_repr::*; +use std::convert::TryFrom; + +#[derive(Serialize_repr, Eq, Hash, Deserialize_repr, Debug, Clone, PartialEq)] +#[repr(u8)] +pub enum TypedTxId { + AccessList = 0x01, + Legacy = 0x80, // With 0x80 we are sure that all other types will not overlap +} + +impl Default for TypedTxId { + fn default() -> TypedTxId { + TypedTxId::Legacy + } +} + +impl TryFrom for TypedTxId { + type Error = (); + + fn try_from(v: u8) -> Result { + match v { + x if x == TypedTxId::AccessList as u8 => Ok(TypedTxId::AccessList), + x if (x & 0x80) != 0x00 => Ok(TypedTxId::Legacy), + _ => Err(()), + } + } +} diff --git a/ethcore/types/src/views/block.rs b/ethcore/types/src/views/block.rs index 8db0a364c..237cf8694 100644 --- a/ethcore/types/src/views/block.rs +++ b/ethcore/types/src/views/block.rs @@ -21,7 +21,7 @@ use bytes::Bytes; use ethereum_types::H256; use hash::keccak; use header::Header; -use transaction::{LocalizedTransaction, UnverifiedTransaction}; +use transaction::{LocalizedTransaction, TypedTransaction, UnverifiedTransaction}; use views::{HeaderView, TransactionView}; /// View onto block rlp. @@ -77,7 +77,12 @@ impl<'a> BlockView<'a> { /// Return List of transactions in given block. pub fn transactions(&self) -> Vec { - self.rlp.list_at(1) + TypedTransaction::decode_rlp_list(&self.rlp.at(1).rlp).unwrap_or_else(|e| { + panic!( + "block transactions, view rlp is trusted and should be valid: {:?}", + e + ) + }) } /// Return List of transactions with additional localization info. @@ -126,10 +131,14 @@ impl<'a> BlockView<'a> { /// Returns transaction at given index without deserializing unnecessary data. pub fn transaction_at(&self, index: usize) -> Option { - self.transactions_rlp() - .iter() - .nth(index) - .map(|rlp| rlp.as_val()) + self.transactions_rlp().iter().nth(index).map(|rlp| { + TypedTransaction::decode_rlp(&rlp.rlp).unwrap_or_else(|e| { + panic!( + "block transaction_at, view rlp is trusted and should be valid.{:?}", + e + ) + }) + }) } /// Returns localized transaction at given index. diff --git a/ethcore/types/src/views/body.rs b/ethcore/types/src/views/body.rs index 8805f029a..0fcc8eb38 100644 --- a/ethcore/types/src/views/body.rs +++ b/ethcore/types/src/views/body.rs @@ -21,7 +21,7 @@ use bytes::Bytes; use ethereum_types::H256; use hash::keccak; use header::Header; -use transaction::{LocalizedTransaction, UnverifiedTransaction}; +use transaction::{LocalizedTransaction, TypedTransaction, UnverifiedTransaction}; use views::{HeaderView, TransactionView}; use BlockNumber; @@ -58,7 +58,12 @@ impl<'a> BodyView<'a> { /// Return List of transactions in given block. pub fn transactions(&self) -> Vec { - self.rlp.list_at(0) + TypedTransaction::decode_rlp_list(&self.rlp.at(0).rlp).unwrap_or_else(|e| { + panic!( + "body transactions, view rlp is trusted and should be valid: {:?}", + e + ) + }) } /// Return List of transactions with additional localization info. @@ -107,10 +112,14 @@ impl<'a> BodyView<'a> { /// Returns transaction at given index without deserializing unnecessary data. pub fn transaction_at(&self, index: usize) -> Option { - self.transactions_rlp() - .iter() - .nth(index) - .map(|rlp| rlp.as_val()) + self.transactions_rlp().iter().nth(index).map(|rlp| { + TypedTransaction::decode_rlp(&rlp.rlp).unwrap_or_else(|e| { + panic!( + "body transaction_a, view rlp is trusted and should be valid: {:?}", + e + ) + }) + }) } /// Returns localized transaction at given index. diff --git a/ethcore/vm/src/access_list.rs b/ethcore/vm/src/access_list.rs index a4a434368..3d3545604 100644 --- a/ethcore/vm/src/access_list.rs +++ b/ethcore/vm/src/access_list.rs @@ -213,7 +213,7 @@ mod tests { access_list.insert_address(Address::from(1)); access_list.insert_storage_key(Address::from(2), H256::from(3)); - let mut access_list_call = access_list.clone(); + let access_list_call = access_list.clone(); assert_eq!(true, access_list_call.contains_address(&Address::from(1))); assert_eq!( true, diff --git a/ethcore/vm/src/schedule.rs b/ethcore/vm/src/schedule.rs index b6b27bd21..cd258b69c 100644 --- a/ethcore/vm/src/schedule.rs +++ b/ethcore/vm/src/schedule.rs @@ -24,6 +24,10 @@ pub const EIP2929_COLD_ACCOUNT_ACCESS_COST: usize = 2600; pub const EIP2929_WARM_STORAGE_READ_COST: usize = 100; // Gas per sstore reset pub const EIP2929_SSTORE_RESET_GAS: usize = 5000 - EIP2929_COLD_SLOAD_COST; +/// Gas per received storage key +pub const EIP2930_ACCESS_LIST_STORAGE_KEY_COST: usize = 1900; +/// Gas per received address +pub const EIP2930_ACCESS_LIST_ADDRESS_COST: usize = 2400; /// Definition of the cost schedule and other parameterisations for the EVM. #[derive(Debug)] @@ -149,6 +153,8 @@ pub struct Schedule { pub wasm: Option, /// Enable EIP-2929 rules pub eip2929: bool, + /// Enable EIP-2930 rules for optional access list transactions. it depends on EIP-2929 + pub eip2930: bool, } /// Wasm cost table @@ -295,6 +301,7 @@ impl Schedule { keep_unsigned_nonce: false, wasm: None, eip2929: false, + eip2930: false, } } @@ -343,6 +350,7 @@ impl Schedule { schedule.eip1283 = true; schedule.eip2929 = true; + schedule.eip2930 = true; schedule.cold_sload_cost = EIP2929_COLD_SLOAD_COST; schedule.cold_account_access_cost = EIP2929_COLD_ACCOUNT_ACCESS_COST; @@ -421,6 +429,7 @@ impl Schedule { keep_unsigned_nonce: false, wasm: None, eip2929: false, + eip2930: false, } } diff --git a/evmbin/src/info.rs b/evmbin/src/info.rs index d2b0b6bc7..9ebe02a4e 100644 --- a/evmbin/src/info.rs +++ b/evmbin/src/info.rs @@ -141,7 +141,7 @@ pub fn run_transaction( let result = run( &spec, trie_spec, - transaction.gas, + transaction.tx().gas, pre_state, |mut client| { let result = client.transact(env_info, transaction, trace::NoopTracer, informant); diff --git a/json/src/spec/params.rs b/json/src/spec/params.rs index 6366ebcb7..15c1747d4 100644 --- a/json/src/spec/params.rs +++ b/json/src/spec/params.rs @@ -108,6 +108,8 @@ pub struct Params { /// See `CommonParams` docs. pub eip2929_transition: Option, /// See `CommonParams` docs. + pub eip2930_transition: Option, + /// See `CommonParams` docs. pub dust_protection_transition: Option, /// See `CommonParams` docs. pub nonce_cap_increment: Option, diff --git a/miner/local-store/src/lib.rs b/miner/local-store/src/lib.rs index b05efe2e0..9dd80dc35 100644 --- a/miner/local-store/src/lib.rs +++ b/miner/local-store/src/lib.rs @@ -20,9 +20,9 @@ use std::{fmt, sync::Arc, time::Duration}; use io::IoHandler; use kvdb::KeyValueDB; -use rlp::Rlp; use types::transaction::{ - Condition as TransactionCondition, PendingTransaction, SignedTransaction, UnverifiedTransaction, + Condition as TransactionCondition, PendingTransaction, SignedTransaction, TypedTransaction, + UnverifiedTransaction, }; extern crate common_types as types; @@ -98,7 +98,7 @@ struct TransactionEntry { impl TransactionEntry { fn into_pending(self) -> Option { - let tx: UnverifiedTransaction = match Rlp::new(&self.rlp_bytes).as_val() { + let tx: UnverifiedTransaction = match TypedTransaction::decode(&self.rlp_bytes) { Err(e) => { warn!(target: "local_store", "Invalid persistent transaction stored: {}", e); return None; @@ -120,7 +120,7 @@ impl TransactionEntry { impl From for TransactionEntry { fn from(pending: PendingTransaction) -> Self { TransactionEntry { - rlp_bytes: ::rlp::encode(&pending.transaction), + rlp_bytes: pending.transaction.encode(), condition: pending.condition.map(Into::into), } } @@ -239,7 +239,7 @@ mod tests { use ethkey::{Brain, Generator}; use std::sync::Arc; - use types::transaction::{Condition, PendingTransaction, Transaction}; + use types::transaction::{Condition, PendingTransaction, Transaction, TypedTransaction}; // we want to test: round-trip of good transactions. // failure to roundtrip bad transactions (but that it doesn't panic) @@ -271,8 +271,8 @@ mod tests { let keypair = Brain::new("abcd".into()).generate().unwrap(); let transactions: Vec<_> = (0..10u64) .map(|nonce| { - let mut tx = Transaction::default(); - tx.nonce = nonce.into(); + let mut tx = TypedTransaction::Legacy(Transaction::default()); + tx.tx_mut().nonce = nonce.into(); let signed = tx.sign(keypair.secret(), None); let condition = match nonce { @@ -308,8 +308,8 @@ mod tests { let keypair = Brain::new("abcd".into()).generate().unwrap(); let mut transactions: Vec<_> = (0..10u64) .map(|nonce| { - let mut tx = Transaction::default(); - tx.nonce = nonce.into(); + let mut tx = TypedTransaction::Legacy(Transaction::default()); + tx.tx_mut().nonce = nonce.into(); let signed = tx.sign(keypair.secret(), None); @@ -318,8 +318,8 @@ mod tests { .collect(); transactions.push({ - let mut tx = Transaction::default(); - tx.nonce = 10.into(); + let mut tx = TypedTransaction::Legacy(Transaction::default()); + tx.tx_mut().nonce = 10.into(); let signed = tx.fake_sign(Default::default()); PendingTransaction::new(signed, None) diff --git a/miner/src/pool/listener.rs b/miner/src/pool/listener.rs index 35f94d600..6cb190ca9 100644 --- a/miner/src/pool/listener.rs +++ b/miner/src/pool/listener.rs @@ -79,11 +79,11 @@ impl txpool::Listener for Logger { "[{hash:?}] Sender: {sender}, nonce: {nonce}, gasPrice: {gas_price}, gas: {gas}, value: {value}, dataLen: {data}))", hash = tx.hash(), sender = tx.sender(), - nonce = tx.signed().nonce, - gas_price = tx.signed().gas_price, - gas = tx.signed().gas, - value = tx.signed().value, - data = tx.signed().data.len(), + nonce = tx.signed().tx().nonce, + gas_price = tx.signed().tx().gas_price, + gas = tx.signed().tx().gas, + value = tx.signed().tx().value, + data = tx.signed().tx().data.len(), ); if let Some(old) = old { @@ -150,7 +150,7 @@ mod tests { assert_eq!( *received.lock(), vec![ - "13aff4201ac1dc49daf6a7cf07b558ed956511acbaabf9502bdacc353953766d" + "de96bdcdf864c95eb7f81eff1e3290be24a0f327732e0c4251c1896a565a80db" .parse() .unwrap() ] @@ -158,14 +158,14 @@ mod tests { } fn new_tx() -> Arc { - let signed = transaction::Transaction { + let signed = transaction::TypedTransaction::Legacy(transaction::Transaction { action: transaction::Action::Create, data: vec![1, 2, 3], nonce: 5.into(), gas: 21_000.into(), gas_price: 5.into(), value: 0.into(), - } + }) .fake_sign(5.into()); Arc::new(Transaction::from_pending_block_transaction(signed)) diff --git a/miner/src/pool/local_transactions.rs b/miner/src/pool/local_transactions.rs index 89f147624..8c0037c58 100644 --- a/miner/src/pool/local_transactions.rs +++ b/miner/src/pool/local_transactions.rs @@ -340,14 +340,14 @@ mod tests { fn new_tx>(nonce: T) -> Arc { let keypair = Random.generate().unwrap(); - let signed = transaction::Transaction { + let signed = transaction::TypedTransaction::Legacy(transaction::Transaction { action: transaction::Action::Create, value: U256::from(100), data: Default::default(), gas: U256::from(10), gas_price: U256::from(1245), nonce: nonce.into(), - } + }) .sign(keypair.secret(), None); let mut tx = Transaction::from_pending_block_transaction(signed); diff --git a/miner/src/pool/mod.rs b/miner/src/pool/mod.rs index 306189d13..8d11c43d9 100644 --- a/miner/src/pool/mod.rs +++ b/miner/src/pool/mod.rs @@ -192,11 +192,11 @@ impl ScoredTransaction for VerifiedTransaction { /// Gets transaction gas price. fn gas_price(&self) -> &U256 { - &self.transaction.gas_price + &self.transaction.tx().gas_price } /// Gets transaction nonce. fn nonce(&self) -> U256 { - self.transaction.nonce + self.transaction.tx().nonce } } diff --git a/miner/src/pool/queue.rs b/miner/src/pool/queue.rs index 92bdf2f07..d9a72e3da 100644 --- a/miner/src/pool/queue.rs +++ b/miner/src/pool/queue.rs @@ -519,7 +519,7 @@ impl TransactionQueue { .read() .pending_from_sender(state_readiness, address) .last() - .map(|tx| tx.signed().nonce.saturating_add(U256::from(1))) + .map(|tx| tx.signed().tx().nonce.saturating_add(U256::from(1))) } /// Retrieve a transaction from the pool. @@ -573,7 +573,7 @@ impl TransactionQueue { /// Returns gas price of currently the worst transaction in the pool. pub fn current_worst_gas_price(&self) -> U256 { match self.pool.read().worst_transaction() { - Some(tx) => tx.signed().gas_price, + Some(tx) => tx.signed().tx().gas_price, None => self.options.read().minimal_gas_price, } } @@ -660,7 +660,7 @@ mod tests { ); for tx in pending { - assert!(tx.signed().nonce > 0.into()); + assert!(tx.signed().tx().nonce > 0.into()); } } } diff --git a/miner/src/pool/ready.rs b/miner/src/pool/ready.rs index 75d54bf8f..d8914be5c 100644 --- a/miner/src/pool/ready.rs +++ b/miner/src/pool/ready.rs @@ -71,7 +71,7 @@ impl txpool::Ready for State { fn is_ready(&mut self, tx: &VerifiedTransaction) -> txpool::Readiness { // Check max nonce match self.max_nonce { - Some(nonce) if tx.transaction.nonce > nonce => { + Some(nonce) if tx.transaction.tx().nonce > nonce => { return txpool::Readiness::Future; } _ => {} @@ -81,7 +81,7 @@ impl txpool::Ready for State { let state = &self.state; let state_nonce = || state.account_nonce(sender); let nonce = self.nonces.entry(*sender).or_insert_with(state_nonce); - match tx.transaction.nonce.cmp(nonce) { + match tx.transaction.tx().nonce.cmp(nonce) { // Before marking as future check for stale ids cmp::Ordering::Greater => match self.stale_id { Some(id) if tx.insertion_id() < id => txpool::Readiness::Stale, @@ -150,8 +150,8 @@ impl Option> txpool::Ready for Opt let nonce = self .nonces .entry(*sender) - .or_insert_with(|| state(sender).unwrap_or_else(|| tx.transaction.nonce)); - match tx.transaction.nonce.cmp(nonce) { + .or_insert_with(|| state(sender).unwrap_or_else(|| tx.transaction.tx().nonce)); + match tx.transaction.tx().nonce.cmp(nonce) { cmp::Ordering::Greater => txpool::Readiness::Future, cmp::Ordering::Less => txpool::Readiness::Stale, cmp::Ordering::Equal => { diff --git a/miner/src/pool/scoring.rs b/miner/src/pool/scoring.rs index 1b5e16417..d27ada124 100644 --- a/miner/src/pool/scoring.rs +++ b/miner/src/pool/scoring.rs @@ -67,7 +67,7 @@ impl NonceAndGasPrice { return true; } - &old.transaction.gas_price > new.gas_price() + &old.transaction.tx().gas_price > new.gas_price() } } diff --git a/miner/src/pool/tests/client.rs b/miner/src/pool/tests/client.rs index fc0413690..05d99f3ed 100644 --- a/miner/src/pool/tests/client.rs +++ b/miner/src/pool/tests/client.rs @@ -18,7 +18,9 @@ use std::sync::{atomic, Arc}; use ethereum_types::{Address, H256, U256}; use rlp::Rlp; -use types::transaction::{self, SignedTransaction, Transaction, UnverifiedTransaction}; +use types::transaction::{ + self, SignedTransaction, Transaction, TypedTransaction, UnverifiedTransaction, +}; use pool::{self, client::AccountDetails}; @@ -150,7 +152,7 @@ impl pool::client::Client for TestClient { if rlp.as_raw().len() > self.max_transaction_size { return Err(transaction::Error::TooBig); } - rlp.as_val() + TypedTransaction::decode(transaction) .map_err(|e| transaction::Error::InvalidRlp(e.to_string())) } } diff --git a/miner/src/pool/tests/mod.rs b/miner/src/pool/tests/mod.rs index fb25d66ac..8d0fc9482 100644 --- a/miner/src/pool/tests/mod.rs +++ b/miner/src/pool/tests/mod.rs @@ -69,7 +69,7 @@ fn should_return_correct_nonces_when_dropped_because_of_limit() { ); let (tx1, tx2) = Tx::gas_price(2).signed_pair(); let sender = tx1.sender(); - let nonce = tx1.nonce; + let nonce = tx1.tx().nonce; // when let r1 = txq.import(TestClient::new(), vec![tx1].retracted()); @@ -129,7 +129,7 @@ fn should_never_drop_local_transactions_from_different_senders() { ); let (tx1, tx2) = Tx::gas_price(2).signed_pair(); let sender = tx1.sender(); - let nonce = tx1.nonce; + let nonce = tx1.tx().nonce; // when let r1 = txq.import(TestClient::new(), vec![tx1].local()); @@ -662,12 +662,14 @@ fn should_not_replace_same_transaction_if_the_fee_is_less_than_minimal_bump() { assert_eq!( txq.pending(client.clone(), PendingSettings::all_prioritized(0, 0))[0] .signed() + .tx() .gas_price, U256::from(20) ); assert_eq!( txq.pending(client.clone(), PendingSettings::all_prioritized(0, 0))[1] .signed() + .tx() .gas_price, U256::from(2) ); @@ -688,7 +690,7 @@ fn should_return_correct_nonce_when_transactions_from_given_address_exist() { let txq = new_queue(); let tx = Tx::default().signed(); let from = tx.sender(); - let nonce = tx.nonce; + let nonce = tx.tx().nonce; // when txq.import(TestClient::new(), vec![tx.local()]); diff --git a/miner/src/pool/tests/tx.rs b/miner/src/pool/tests/tx.rs index 2e46fe28e..51c276d44 100644 --- a/miner/src/pool/tests/tx.rs +++ b/miner/src/pool/tests/tx.rs @@ -17,7 +17,9 @@ use ethereum_types::{H256, U256}; use ethkey::{Generator, Random}; use rustc_hex::FromHex; -use types::transaction::{self, SignedTransaction, Transaction, UnverifiedTransaction}; +use types::transaction::{ + self, SignedTransaction, Transaction, TypedTransaction, UnverifiedTransaction, +}; use pool::{verifier, VerifiedTransaction}; @@ -76,20 +78,20 @@ impl Tx { (tx1, tx2) } - pub fn unsigned(self) -> Transaction { - Transaction { + pub fn unsigned(self) -> TypedTransaction { + TypedTransaction::Legacy(Transaction { action: transaction::Action::Create, value: U256::from(100), data: "3331600055".from_hex().unwrap(), gas: self.gas.into(), gas_price: self.gas_price.into(), nonce: self.nonce.into(), - } + }) } pub fn big_one(self) -> SignedTransaction { let keypair = Random.generate().unwrap(); - let tx = Transaction { + let tx = TypedTransaction::Legacy(Transaction { action: transaction::Action::Create, value: U256::from(100), data: include_str!("../res/big_transaction.data") @@ -98,7 +100,7 @@ impl Tx { gas: self.gas.into(), gas_price: self.gas_price.into(), nonce: self.nonce.into(), - }; + }); tx.sign(keypair.secret(), None) } } diff --git a/miner/src/pool/verifier.rs b/miner/src/pool/verifier.rs index cf4e116de..aa1999121 100644 --- a/miner/src/pool/verifier.rs +++ b/miner/src/pool/verifier.rs @@ -31,7 +31,6 @@ use std::{ }; use ethereum_types::{H256, U256}; -use rlp::Encodable; use txpool; use types::transaction; @@ -97,21 +96,21 @@ impl Transaction { /// Return transaction gas price pub fn gas_price(&self) -> &U256 { match *self { - Transaction::Unverified(ref tx) => &tx.gas_price, - Transaction::Retracted(ref tx) => &tx.gas_price, - Transaction::Local(ref tx) => &tx.gas_price, + Transaction::Unverified(ref tx) => &tx.tx().gas_price, + Transaction::Retracted(ref tx) => &tx.tx().gas_price, + Transaction::Local(ref tx) => &tx.tx().gas_price, } } fn gas(&self) -> &U256 { match *self { - Transaction::Unverified(ref tx) => &tx.gas, - Transaction::Retracted(ref tx) => &tx.gas, - Transaction::Local(ref tx) => &tx.gas, + Transaction::Unverified(ref tx) => &tx.tx().gas, + Transaction::Retracted(ref tx) => &tx.tx().gas, + Transaction::Local(ref tx) => &tx.tx().gas, } } - fn transaction(&self) -> &transaction::Transaction { + fn transaction(&self) -> &transaction::TypedTransaction { match *self { Transaction::Unverified(ref tx) => &*tx, Transaction::Retracted(ref tx) => &*tx, @@ -198,7 +197,7 @@ impl txpool::Verifier }); } - let minimal_gas = self.client.required_gas(tx.transaction()); + let minimal_gas = self.client.required_gas(tx.transaction().tx()); if tx.gas() < &minimal_gas { trace!(target: "txqueue", "[{:?}] Rejected transaction with insufficient gas: {} < {}", @@ -240,10 +239,10 @@ impl txpool::Verifier "[{:?}] Rejected tx early, cause it doesn't have any chance to get to the pool: (gas price: {} < {})", hash, tx.gas_price(), - vtx.transaction.gas_price, + vtx.transaction.tx().gas_price, ); return Err(transaction::Error::TooCheapToReplace { - prev: Some(vtx.transaction.gas_price), + prev: Some(vtx.transaction.tx().gas_price), new: Some(*tx.gas_price()), }); } @@ -273,7 +272,7 @@ impl txpool::Verifier }; // Verify RLP payload - if let Err(err) = self.client.decode_transaction(&transaction.rlp_bytes()) { + if let Err(err) = self.client.decode_transaction(&transaction.encode()) { debug!(target: "txqueue", "[{:?}] Rejected transaction's rlp payload", err); bail!(err) } @@ -281,7 +280,7 @@ impl txpool::Verifier let sender = transaction.sender(); let account_details = self.client.account_details(&sender); - if transaction.gas_price < self.options.minimal_gas_price { + if transaction.tx().gas_price < self.options.minimal_gas_price { let transaction_type = self.client.transaction_type(&transaction); if let TransactionType::Service = transaction_type { debug!(target: "txqueue", "Service tx {:?} below minimal gas price accepted", hash); @@ -292,18 +291,21 @@ impl txpool::Verifier target: "txqueue", "[{:?}] Rejected tx below minimal gas price threshold: {} < {}", hash, - transaction.gas_price, + transaction.tx().gas_price, self.options.minimal_gas_price, ); bail!(transaction::Error::InsufficientGasPrice { minimal: self.options.minimal_gas_price, - got: transaction.gas_price, + got: transaction.tx().gas_price, }); } } - let (full_gas_price, overflow_1) = transaction.gas_price.overflowing_mul(transaction.gas); - let (cost, overflow_2) = transaction.value.overflowing_add(full_gas_price); + let (full_gas_price, overflow_1) = transaction + .tx() + .gas_price + .overflowing_mul(transaction.tx().gas); + let (cost, overflow_2) = transaction.tx().value.overflowing_add(full_gas_price); if overflow_1 || overflow_2 { trace!( target: "txqueue", @@ -329,12 +331,12 @@ impl txpool::Verifier }); } - if transaction.nonce < account_details.nonce { + if transaction.tx().nonce < account_details.nonce { debug!( target: "txqueue", "[{:?}] Rejected tx with old nonce ({} < {})", hash, - transaction.nonce, + transaction.tx().nonce, account_details.nonce, ); bail!(transaction::Error::Old); diff --git a/miner/src/service_transaction_checker.rs b/miner/src/service_transaction_checker.rs index e6917568c..f426571fa 100644 --- a/miner/src/service_transaction_checker.rs +++ b/miner/src/service_transaction_checker.rs @@ -45,7 +45,7 @@ impl ServiceTransactionChecker { ) -> Result { let sender = tx.sender(); // Skip checking the contract if the transaction does not have zero gas price - if !tx.gas_price.is_zero() { + if !tx.tx().gas_price.is_zero() { return Ok(false); } diff --git a/rpc/src/v1/helpers/dispatch/full.rs b/rpc/src/v1/helpers/dispatch/full.rs index fda6d4701..36874e6d1 100644 --- a/rpc/src/v1/helpers/dispatch/full.rs +++ b/rpc/src/v1/helpers/dispatch/full.rs @@ -120,6 +120,7 @@ impl Dispatcher }; Box::new(future::ok(FilledTransactionRequest { + tx_type: request.tx_type, from, used_default_from: request.from.is_none(), to: request.to, @@ -133,6 +134,7 @@ impl Dispatcher value: request.value.unwrap_or_else(|| 0.into()), data: request.data.unwrap_or_else(Vec::new), condition: request.condition, + access_list: request.access_list, })) } diff --git a/rpc/src/v1/helpers/dispatch/signing.rs b/rpc/src/v1/helpers/dispatch/signing.rs index 55b03b2fa..d07cb3ecc 100644 --- a/rpc/src/v1/helpers/dispatch/signing.rs +++ b/rpc/src/v1/helpers/dispatch/signing.rs @@ -21,7 +21,10 @@ use bytes::Bytes; use crypto::DEFAULT_MAC; use ethereum_types::{Address, H256, U256}; use ethkey::Signature; -use types::transaction::{Action, SignedTransaction, Transaction}; +use jsonrpc_core::{Error, ErrorCode}; +use types::transaction::{ + AccessListTx, Action, SignedTransaction, Transaction, TypedTransaction, TypedTxId, +}; use jsonrpc_core::Result; use v1::helpers::{errors, FilledTransactionRequest}; @@ -48,16 +51,28 @@ impl super::Accounts for Signer { nonce: U256, password: SignWith, ) -> Result> { - let t = Transaction { - nonce: nonce, + let legacy_tx = Transaction { + nonce, action: filled.to.map_or(Action::Create, Action::Call), gas: filled.gas, gas_price: filled.gas_price, value: filled.value, data: filled.data, }; + let t = match filled.tx_type { + TypedTxId::Legacy => TypedTransaction::Legacy(legacy_tx), + TypedTxId::AccessList => { + if filled.access_list.is_none() { + return Err(Error::new(ErrorCode::InvalidParams)); + } + TypedTransaction::AccessList(AccessListTx::new( + legacy_tx, + filled.access_list.unwrap(), + )) + } + }; - let hash = t.hash(chain_id); + let hash = t.signature_hash(chain_id); let signature = signature(&*self.accounts, filled.from, hash, password)?; Ok(signature.map(|sig| { diff --git a/rpc/src/v1/helpers/errors.rs b/rpc/src/v1/helpers/errors.rs index bb3d71c70..c9d04995f 100644 --- a/rpc/src/v1/helpers/errors.rs +++ b/rpc/src/v1/helpers/errors.rs @@ -399,7 +399,8 @@ pub fn transaction_message(error: &TransactionError) -> String { CodeBanned => "Code is banned in local queue.".into(), NotAllowed => "Transaction is not permitted.".into(), TooBig => "Transaction is too big, see chain specification for the limit.".into(), - InvalidRlp(ref descr) => format!("Invalid RLP data: {}", descr), + InvalidRlp(ref descr) => format!("Invalid RLP data: {}", descr), + TransactionTypeNotEnabled => format!("Transaction type is not enabled for current block"), } } diff --git a/rpc/src/v1/helpers/external_signer/signing_queue.rs b/rpc/src/v1/helpers/external_signer/signing_queue.rs index 745304836..deec5c3b4 100644 --- a/rpc/src/v1/helpers/external_signer/signing_queue.rs +++ b/rpc/src/v1/helpers/external_signer/signing_queue.rs @@ -256,6 +256,7 @@ mod test { fn request() -> ConfirmationPayload { ConfirmationPayload::SendTransaction(FilledTransactionRequest { + tx_type: Default::default(), from: Address::from(1), used_default_from: false, to: Some(Address::from(2)), @@ -265,6 +266,7 @@ mod test { data: vec![], nonce: None, condition: None, + access_list: None, }) } diff --git a/rpc/src/v1/helpers/fake_sign.rs b/rpc/src/v1/helpers/fake_sign.rs index de9ae5708..b692f502f 100644 --- a/rpc/src/v1/helpers/fake_sign.rs +++ b/rpc/src/v1/helpers/fake_sign.rs @@ -15,7 +15,9 @@ // along with OpenEthereum. If not, see . use std::cmp::min; -use types::transaction::{Action, SignedTransaction, Transaction}; +use types::transaction::{ + AccessListTx, Action, SignedTransaction, Transaction, TypedTransaction, TypedTxId, +}; use ethereum_types::U256; use jsonrpc_core::Error; @@ -25,14 +27,20 @@ pub fn sign_call(request: CallRequest) -> Result { let max_gas = U256::from(500_000_000); let gas = min(request.gas.unwrap_or(max_gas), max_gas); let from = request.from.unwrap_or_default(); - - Ok(Transaction { + let tx_legacy = Transaction { nonce: request.nonce.unwrap_or_default(), action: request.to.map_or(Action::Create, Action::Call), gas, gas_price: request.gas_price.unwrap_or_default(), value: request.value.unwrap_or_default(), data: request.data.unwrap_or_default(), - } - .fake_sign(from)) + }; + let tx_typed = match request.tx_type { + TypedTxId::Legacy => TypedTransaction::Legacy(tx_legacy), + TypedTxId::AccessList => TypedTransaction::AccessList(AccessListTx::new( + tx_legacy, + request.access_list.unwrap_or_default(), + )), + }; + Ok(tx_typed.fake_sign(from)) } diff --git a/rpc/src/v1/helpers/requests.rs b/rpc/src/v1/helpers/requests.rs index eeb1654d8..848a8d415 100644 --- a/rpc/src/v1/helpers/requests.rs +++ b/rpc/src/v1/helpers/requests.rs @@ -16,12 +16,15 @@ use bytes::Bytes; use ethereum_types::{Address, H256, U256}; +use types::transaction::{AccessList, TypedTxId}; use v1::types::{Origin, TransactionCondition}; /// Transaction request coming from RPC #[derive(Debug, Clone, Default, Eq, PartialEq, Hash)] pub struct TransactionRequest { + /// type of transaction. + pub tx_type: TypedTxId, /// Sender pub from: Option
, /// Recipient @@ -38,11 +41,15 @@ pub struct TransactionRequest { pub nonce: Option, /// Delay until this condition is met. pub condition: Option, + /// Access list + pub access_list: Option, } /// Transaction request coming from RPC with default values filled in. #[derive(Debug, Clone, Default, Eq, PartialEq, Hash)] pub struct FilledTransactionRequest { + /// type of transaction. + pub tx_type: TypedTxId, /// Sender pub from: Address, /// Indicates if the sender was filled by default value. @@ -61,11 +68,14 @@ pub struct FilledTransactionRequest { pub nonce: Option, /// Delay until this condition is met. pub condition: Option, + /// Access list + pub access_list: Option, } impl From for TransactionRequest { fn from(r: FilledTransactionRequest) -> Self { TransactionRequest { + tx_type: r.tx_type, from: Some(r.from), to: r.to, gas_price: Some(r.gas_price), @@ -74,6 +84,7 @@ impl From for TransactionRequest { data: Some(r.data), nonce: r.nonce, condition: r.condition, + access_list: r.access_list, } } } @@ -81,6 +92,8 @@ impl From for TransactionRequest { /// Call request #[derive(Debug, Default, PartialEq)] pub struct CallRequest { + /// type of transaction. + pub tx_type: TypedTxId, /// From pub from: Option
, /// To @@ -95,6 +108,8 @@ pub struct CallRequest { pub data: Option>, /// Nonce pub nonce: Option, + /// Access list + pub access_list: Option, } /// Confirmation object diff --git a/rpc/src/v1/impls/eth.rs b/rpc/src/v1/impls/eth.rs index 613a2815b..2896edf8d 100644 --- a/rpc/src/v1/impls/eth.rs +++ b/rpc/src/v1/impls/eth.rs @@ -24,7 +24,6 @@ use std::{ use ethereum_types::{Address, H160, H256, H64, U256, U64}; use parking_lot::Mutex; -use rlp::Rlp; use ethash::{self, SeedHashCompute}; use ethcore::{ @@ -42,7 +41,7 @@ use types::{ encoded, filter::Filter as EthcoreFilter, header::Header, - transaction::{LocalizedTransaction, SignedTransaction}, + transaction::{LocalizedTransaction, SignedTransaction, TypedTransaction}, BlockNumber as EthBlockNumber, }; @@ -1065,8 +1064,7 @@ where } fn send_raw_transaction(&self, raw: Bytes) -> Result { - Rlp::new(&raw.into_vec()) - .as_val() + TypedTransaction::decode(&raw.into_vec()) .map_err(errors::rlp) .and_then(|tx| SignedTransaction::new(tx).map_err(errors::transaction)) .and_then(|signed_transaction| { diff --git a/rpc/src/v1/impls/signer.rs b/rpc/src/v1/impls/signer.rs index b7dca6998..049ed2ab6 100644 --- a/rpc/src/v1/impls/signer.rs +++ b/rpc/src/v1/impls/signer.rs @@ -22,8 +22,7 @@ use ethereum_types::U256; use ethkey; use parity_runtime::Executor; use parking_lot::Mutex; -use rlp::Rlp; -use types::transaction::{PendingTransaction, SignedTransaction}; +use types::transaction::{PendingTransaction, SignedTransaction, TypedTransaction}; use jsonrpc_core::{ futures::{future, future::Either, Future, IntoFuture}, @@ -161,17 +160,17 @@ impl SignerClient { where F: FnOnce(PendingTransaction) -> Result, { - let signed_transaction = Rlp::new(&bytes.0).as_val().map_err(errors::rlp)?; + let signed_transaction = TypedTransaction::decode(&bytes.0).map_err(errors::rlp)?; let signed_transaction = SignedTransaction::new(signed_transaction) .map_err(|e| errors::invalid_params("Invalid signature.", e))?; let sender = signed_transaction.sender(); // Verification let sender_matches = sender == request.from; - let data_matches = signed_transaction.data == request.data; - let value_matches = signed_transaction.value == request.value; + let data_matches = signed_transaction.tx().data == request.data; + let value_matches = signed_transaction.tx().value == request.value; let nonce_matches = match request.nonce { - Some(nonce) => signed_transaction.nonce == nonce, + Some(nonce) => signed_transaction.tx().nonce == nonce, None => true, }; diff --git a/rpc/src/v1/impls/traces.rs b/rpc/src/v1/impls/traces.rs index d685e40ca..329e0547e 100644 --- a/rpc/src/v1/impls/traces.rs +++ b/rpc/src/v1/impls/traces.rs @@ -22,8 +22,7 @@ use ethcore::client::{ BlockChainClient, BlockId, Call, CallAnalytics, StateClient, StateInfo, TraceId, TransactionId, }; use ethereum_types::H256; -use rlp::Rlp; -use types::transaction::SignedTransaction; +use types::transaction::{SignedTransaction, TypedTransaction}; use jsonrpc_core::Result; use v1::{ @@ -194,9 +193,8 @@ where ) -> Result { let block = block.unwrap_or_default(); - let tx = Rlp::new(&raw_transaction.into_vec()) - .as_val() - .map_err(|e| errors::invalid_params("Transaction is not valid RLP", e))?; + let tx = TypedTransaction::decode(&raw_transaction.0) + .map_err(|e| errors::invalid_params("Transaction is not in valid Format", e))?; let signed = SignedTransaction::new(tx).map_err(errors::transaction)?; let id = match block { diff --git a/rpc/src/v1/tests/mocked/eth.rs b/rpc/src/v1/tests/mocked/eth.rs index c50a61f9d..00ea5c6c2 100644 --- a/rpc/src/v1/tests/mocked/eth.rs +++ b/rpc/src/v1/tests/mocked/eth.rs @@ -30,14 +30,13 @@ use ethereum_types::{Address, Bloom, H160, H256, U256}; use miner::external::ExternalMiner; use parity_runtime::Runtime; use parking_lot::Mutex; -use rlp; use rustc_hex::{FromHex, ToHex}; use sync::SyncState; use types::{ ids::{BlockId, TransactionId}, log_entry::{LocalizedLogEntry, LogEntry}, receipt::{LocalizedReceipt, RichReceipt, TransactionOutcome}, - transaction::{Action, Transaction}, + transaction::{Action, Transaction, TypedTransaction, TypedTxId}, }; use jsonrpc_core::IoHandler; @@ -694,13 +693,12 @@ fn rpc_eth_transaction_count_by_number_pending() { #[test] fn rpc_eth_pending_transaction_by_hash() { use ethereum_types::H256; - use rlp; use types::transaction::SignedTransaction; let tester = EthTester::default(); { let bytes = FromHex::from_hex("f85f800182520894095e7baea6a6c7c4c2dfeb977efac326af552d870a801ba048b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d495a36649353a0efffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab949f53faa07bd2c804").unwrap(); - let tx = rlp::decode(&bytes).expect("decoding failure"); + let tx = TypedTransaction::decode(&bytes).expect("decoding failure"); let tx = SignedTransaction::new(tx).unwrap(); tester .miner @@ -1050,6 +1048,23 @@ fn rpc_eth_estimate_gas_default_block() { fn rpc_eth_send_raw_transaction_error() { let tester = EthTester::default(); + let req = r#"{ + "jsonrpc": "2.0", + "method": "eth_sendRawTransaction", + "params": [ + "0x1123" + ], + "id": 1 + }"#; + let res = r#"{"jsonrpc":"2.0","error":{"code":-32602,"message":"Invalid RLP.","data":"Custom(\"Unknown transaction\")"},"id":1}"#.into(); + + assert_eq!(tester.io.handle_request_sync(&req), Some(res)); +} + +#[test] +fn rpc_eth_send_raw_01_transaction_error() { + let tester = EthTester::default(); + let req = r#"{ "jsonrpc": "2.0", "method": "eth_sendRawTransaction", @@ -1075,7 +1090,7 @@ fn rpc_eth_send_raw_transaction() { .unlock_account_permanently(address, "abcd".into()) .unwrap(); - let t = Transaction { + let t = TypedTransaction::Legacy(Transaction { nonce: U256::zero(), gas_price: U256::from(0x9184e72a000u64), gas: U256::from(0x76c0), @@ -1084,14 +1099,14 @@ fn rpc_eth_send_raw_transaction() { ), value: U256::from(0x9184e72au64), data: vec![], - }; + }); let signature = tester .accounts_provider - .sign(address, None, t.hash(None)) + .sign(address, None, t.signature_hash(None)) .unwrap(); let t = t.with_signature(signature, None); - let rlp = rlp::encode(&t).to_hex(); + let rlp = t.encode().to_hex(); let req = r#"{ "jsonrpc": "2.0", @@ -1118,6 +1133,7 @@ fn rpc_eth_transaction_receipt() { to: Some(H160::from_str("d46e8dd67c5d32be8058bb8eb970870f07244567").unwrap()), transaction_hash: H256::zero(), transaction_index: 0, + transaction_type: TypedTxId::Legacy, block_hash: H256::from_str( "ed76641c68a1c641aee09a94b3b471f4dc0316efe5ac19cf488e2674cf8d05b5", ) @@ -1204,6 +1220,7 @@ fn rpc_eth_pending_receipt() { ) .unwrap(), transaction_index: 0, + transaction_type: TypedTxId::Legacy, cumulative_gas_used: U256::from(0x20), gas_used: U256::from(0x10), contract_address: None, diff --git a/rpc/src/v1/tests/mocked/parity.rs b/rpc/src/v1/tests/mocked/parity.rs index f3ec851ad..ea1b7c31a 100644 --- a/rpc/src/v1/tests/mocked/parity.rs +++ b/rpc/src/v1/tests/mocked/parity.rs @@ -21,7 +21,10 @@ use ethstore::ethkey::{Generator, Random}; use miner::pool::local_transactions::Status as LocalTransactionStatus; use std::sync::Arc; use sync::ManageNetwork; -use types::receipt::{LocalizedReceipt, TransactionOutcome}; +use types::{ + receipt::{LocalizedReceipt, TransactionOutcome}, + transaction::TypedTxId, +}; use super::manage_network::TestManageNetwork; use jsonrpc_core::IoHandler; @@ -368,16 +371,17 @@ fn rpc_parity_transactions_stats() { #[test] fn rpc_parity_local_transactions() { + use types::transaction::{Transaction, TypedTransaction}; let deps = Dependencies::new(); let io = deps.default_client(); - let tx = ::types::transaction::Transaction { + let tx = TypedTransaction::Legacy(Transaction { value: 5.into(), gas: 3.into(), gas_price: 2.into(), action: ::types::transaction::Action::Create, data: vec![1, 2, 3], nonce: 0.into(), - } + }) .fake_sign(3.into()); let tx = Arc::new(::miner::pool::VerifiedTransaction::from_pending_block_transaction(tx)); deps.miner @@ -466,6 +470,7 @@ fn rpc_parity_block_receipts() { TransactionId::Hash(1.into()), LocalizedReceipt { transaction_hash: 1.into(), + transaction_type: TypedTxId::Legacy, transaction_index: 0, block_hash: 3.into(), block_number: 0, diff --git a/rpc/src/v1/tests/mocked/parity_set.rs b/rpc/src/v1/tests/mocked/parity_set.rs index 18a3f930c..bea2000a3 100644 --- a/rpc/src/v1/tests/mocked/parity_set.rs +++ b/rpc/src/v1/tests/mocked/parity_set.rs @@ -178,7 +178,7 @@ fn rpc_parity_set_hash_content() { #[test] fn rpc_parity_remove_transaction() { - use types::transaction::{Action, Transaction}; + use types::transaction::{Action, Transaction, TypedTransaction}; let miner = miner_service(); let client = client_service(); @@ -187,14 +187,14 @@ fn rpc_parity_remove_transaction() { let mut io = IoHandler::new(); io.extend_with(parity_set_client(&client, &miner, &network).to_delegate()); - let tx = Transaction { + let tx = TypedTransaction::Legacy(Transaction { nonce: 1.into(), gas_price: 0x9184e72a000u64.into(), gas: 0x76c0.into(), action: Action::Call(5.into()), value: 0x9184e72au64.into(), data: vec![], - }; + }); let signed = tx.fake_sign(2.into()); let hash = signed.hash(); @@ -202,7 +202,7 @@ fn rpc_parity_remove_transaction() { .to_owned() + &format!("0x{:x}", hash) + r#""], "id": 1}"#; - let response = r#"{"jsonrpc":"2.0","result":{"blockHash":null,"blockNumber":null,"chainId":null,"condition":null,"creates":null,"from":"0x0000000000000000000000000000000000000002","gas":"0x76c0","gasPrice":"0x9184e72a000","hash":"0xa2e0da8a8064e0b9f93e95a53c2db6d01280efb8ac72a708d25487e67dd0f8fc","input":"0x","nonce":"0x1","publicKey":null,"r":"0x1","raw":"0xe9018609184e72a0008276c0940000000000000000000000000000000000000005849184e72a80800101","s":"0x1","standardV":"0x4","to":"0x0000000000000000000000000000000000000005","transactionIndex":null,"v":"0x0","value":"0x9184e72a"},"id":1}"#; + let response = r#"{"jsonrpc":"2.0","result":{"blockHash":null,"blockNumber":null,"chainId":null,"condition":null,"creates":null,"from":"0x0000000000000000000000000000000000000002","gas":"0x76c0","gasPrice":"0x9184e72a000","hash":"0x49569012bc8523519642c337fded3f20ba987beab31e14c67223b3d31359956f","input":"0x","nonce":"0x1","publicKey":null,"r":"0x1","raw":"0xe9018609184e72a0008276c0940000000000000000000000000000000000000005849184e72a801f0101","s":"0x1","standardV":"0x4","to":"0x0000000000000000000000000000000000000005","transactionIndex":null,"v":"0x1f","value":"0x9184e72a"},"id":1}"#; miner.pending_transactions.lock().insert(hash, signed); assert_eq!(io.handle_request_sync(&request), Some(response.to_owned())); diff --git a/rpc/src/v1/tests/mocked/personal.rs b/rpc/src/v1/tests/mocked/personal.rs index c3dd68198..3a7743220 100644 --- a/rpc/src/v1/tests/mocked/personal.rs +++ b/rpc/src/v1/tests/mocked/personal.rs @@ -24,7 +24,7 @@ use hash::keccak; use jsonrpc_core::IoHandler; use parity_runtime::Runtime; use parking_lot::Mutex; -use types::transaction::{Action, Transaction}; +use types::transaction::{Action, Transaction, TypedTransaction}; use ethkey::Secret; use serde_json::to_value; @@ -91,9 +91,6 @@ fn setup_with(c: Config) -> PersonalTester { tester } -#[cfg(test)] -use rustc_hex::ToHex; - #[test] fn accounts() { let tester = setup(); @@ -265,7 +262,7 @@ fn sign_and_send_test(method: &str) { "id": 1 }"#; - let t = Transaction { + let t = TypedTransaction::Legacy(Transaction { nonce: U256::zero(), gas_price: U256::from(0x9184e72a000u64), gas: U256::from(0x76c0), @@ -274,12 +271,15 @@ fn sign_and_send_test(method: &str) { ), value: U256::from(0x9184e72au64), data: vec![], - }; + }); tester .accounts .unlock_account_temporarily(address, "password123".into()) .unwrap(); - let signature = tester.accounts.sign(address, None, t.hash(None)).unwrap(); + let signature = tester + .accounts + .sign(address, None, t.signature_hash(None)) + .unwrap(); let t = t.with_signature(signature, None); let response = r#"{"jsonrpc":"2.0","result":""#.to_owned() @@ -293,7 +293,7 @@ fn sign_and_send_test(method: &str) { tester.miner.increment_nonce(&address); - let t = Transaction { + let t = TypedTransaction::Legacy(Transaction { nonce: U256::one(), gas_price: U256::from(0x9184e72a000u64), gas: U256::from(0x76c0), @@ -302,12 +302,15 @@ fn sign_and_send_test(method: &str) { ), value: U256::from(0x9184e72au64), data: vec![], - }; + }); tester .accounts .unlock_account_temporarily(address, "password123".into()) .unwrap(); - let signature = tester.accounts.sign(address, None, t.hash(None)).unwrap(); + let signature = tester + .accounts + .sign(address, None, t.signature_hash(None)) + .unwrap(); let t = t.with_signature(signature, None); let response = r#"{"jsonrpc":"2.0","result":""#.to_owned() diff --git a/rpc/src/v1/tests/mocked/signer.rs b/rpc/src/v1/tests/mocked/signer.rs index 3d9f6f9bf..6ef3eb1e7 100644 --- a/rpc/src/v1/tests/mocked/signer.rs +++ b/rpc/src/v1/tests/mocked/signer.rs @@ -22,8 +22,7 @@ use accounts::AccountProvider; use ethcore::client::TestBlockChainClient; use parity_runtime::Runtime; use parking_lot::Mutex; -use rlp::encode; -use types::transaction::{Action, SignedTransaction, Transaction}; +use types::transaction::{Action, SignedTransaction, Transaction, TypedTransaction}; use jsonrpc_core::IoHandler; use serde_json; @@ -92,6 +91,7 @@ fn should_return_list_of_items_to_confirm() { .signer .add_request( ConfirmationPayload::SendTransaction(FilledTransactionRequest { + tx_type: Default::default(), from: Address::from(1), used_default_from: false, to: Some(Address::from_str("d46e8dd67c5d32be8058bb8eb970870f07244567").unwrap()), @@ -101,6 +101,7 @@ fn should_return_list_of_items_to_confirm() { data: vec![], nonce: None, condition: None, + access_list: None, }), Origin::Unknown, ) @@ -137,6 +138,7 @@ fn should_reject_transaction_from_queue_without_dispatching() { .signer .add_request( ConfirmationPayload::SendTransaction(FilledTransactionRequest { + tx_type: Default::default(), from: Address::from(1), used_default_from: false, to: Some(Address::from_str("d46e8dd67c5d32be8058bb8eb970870f07244567").unwrap()), @@ -146,6 +148,7 @@ fn should_reject_transaction_from_queue_without_dispatching() { data: vec![], nonce: None, condition: None, + access_list: None, }), Origin::Unknown, ) @@ -173,6 +176,7 @@ fn should_not_remove_transaction_if_password_is_invalid() { .signer .add_request( ConfirmationPayload::SendTransaction(FilledTransactionRequest { + tx_type: Default::default(), from: Address::from(1), used_default_from: false, to: Some(Address::from_str("d46e8dd67c5d32be8058bb8eb970870f07244567").unwrap()), @@ -182,6 +186,7 @@ fn should_not_remove_transaction_if_password_is_invalid() { data: vec![], nonce: None, condition: None, + access_list: None, }), Origin::Unknown, ) @@ -237,6 +242,7 @@ fn should_confirm_transaction_and_dispatch() { .signer .add_request( ConfirmationPayload::SendTransaction(FilledTransactionRequest { + tx_type: Default::default(), from: address, used_default_from: false, to: Some(recipient), @@ -246,24 +252,28 @@ fn should_confirm_transaction_and_dispatch() { data: vec![], nonce: None, condition: None, + access_list: None, }), Origin::Unknown, ) .unwrap(); - let t = Transaction { + let t = TypedTransaction::Legacy(Transaction { nonce: U256::zero(), gas_price: U256::from(0x1000), gas: U256::from(0x50505), action: Action::Call(recipient), value: U256::from(0x1), data: vec![], - }; + }); tester .accounts .unlock_account_temporarily(address, "test".into()) .unwrap(); - let signature = tester.accounts.sign(address, None, t.hash(None)).unwrap(); + let signature = tester + .accounts + .sign(address, None, t.signature_hash(None)) + .unwrap(); let t = t.with_signature(signature, None); assert_eq!(tester.signer.requests().len(), 1); @@ -297,6 +307,7 @@ fn should_alter_the_sender_and_nonce() { .signer .add_request( ConfirmationPayload::SendTransaction(FilledTransactionRequest { + tx_type: Default::default(), from: 0.into(), used_default_from: false, to: Some(recipient), @@ -306,24 +317,25 @@ fn should_alter_the_sender_and_nonce() { data: vec![], nonce: Some(10.into()), condition: None, + access_list: None, }), Origin::Unknown, ) .unwrap(); - let t = Transaction { + let t = TypedTransaction::Legacy(Transaction { nonce: U256::zero(), gas_price: U256::from(0x1000), gas: U256::from(0x50505), action: Action::Call(recipient), value: U256::from(0x1), data: vec![], - }; + }); let address = tester.accounts.new_account(&"test".into()).unwrap(); let signature = tester .accounts - .sign(address, Some("test".into()), t.hash(None)) + .sign(address, Some("test".into()), t.signature_hash(None)) .unwrap(); let t = t.with_signature(signature, None); @@ -361,6 +373,7 @@ fn should_confirm_transaction_with_token() { .signer .add_request( ConfirmationPayload::SendTransaction(FilledTransactionRequest { + tx_type: Default::default(), from: address, used_default_from: false, to: Some(recipient), @@ -370,22 +383,23 @@ fn should_confirm_transaction_with_token() { data: vec![], nonce: None, condition: None, + access_list: None, }), Origin::Unknown, ) .unwrap(); - let t = Transaction { + let t = TypedTransaction::Legacy(Transaction { nonce: U256::zero(), gas_price: U256::from(0x1000), gas: U256::from(10_000_000), action: Action::Call(recipient), value: U256::from(0x1), data: vec![], - }; + }); let (signature, token) = tester .accounts - .sign_with_token(address, "test".into(), t.hash(None)) + .sign_with_token(address, "test".into(), t.signature_hash(None)) .unwrap(); let t = t.with_signature(signature, None); @@ -427,6 +441,7 @@ fn should_confirm_transaction_with_rlp() { .signer .add_request( ConfirmationPayload::SendTransaction(FilledTransactionRequest { + tx_type: Default::default(), from: address, used_default_from: false, to: Some(recipient), @@ -436,25 +451,26 @@ fn should_confirm_transaction_with_rlp() { data: vec![], nonce: None, condition: None, + access_list: None, }), Origin::Unknown, ) .unwrap(); - let t = Transaction { + let t = TypedTransaction::Legacy(Transaction { nonce: U256::zero(), gas_price: U256::from(0x1000), gas: U256::from(10_000_000), action: Action::Call(recipient), value: U256::from(0x1), data: vec![], - }; + }); let signature = tester .accounts - .sign(address, Some("test".into()), t.hash(None)) + .sign(address, Some("test".into()), t.signature_hash(None)) .unwrap(); let t = t.with_signature(signature, None); - let rlp = encode(&t); + let rlp = t.encode(); assert_eq!(tester.signer.requests().len(), 1); @@ -491,6 +507,7 @@ fn should_return_error_when_sender_does_not_match() { .signer .add_request( ConfirmationPayload::SendTransaction(FilledTransactionRequest { + tx_type: Default::default(), from: Address::default(), used_default_from: false, to: Some(recipient), @@ -500,26 +517,30 @@ fn should_return_error_when_sender_does_not_match() { data: vec![], nonce: None, condition: None, + access_list: None, }), Origin::Unknown, ) .unwrap(); - let t = Transaction { + let t = TypedTransaction::Legacy(Transaction { nonce: U256::zero(), gas_price: U256::from(0x1000), gas: U256::from(10_000_000), action: Action::Call(recipient), value: U256::from(0x1), data: vec![], - }; + }); tester .accounts .unlock_account_temporarily(address, "test".into()) .unwrap(); - let signature = tester.accounts.sign(address, None, t.hash(None)).unwrap(); + let signature = tester + .accounts + .sign(address, None, t.signature_hash(None)) + .unwrap(); let t = t.with_signature(signature, None); - let rlp = encode(&t); + let rlp = t.encode(); assert_eq!(tester.signer.requests().len(), 1); @@ -553,6 +574,7 @@ fn should_confirm_sign_transaction_with_rlp() { .signer .add_request( ConfirmationPayload::SignTransaction(FilledTransactionRequest { + tx_type: Default::default(), from: address, used_default_from: false, to: Some(recipient), @@ -562,26 +584,27 @@ fn should_confirm_sign_transaction_with_rlp() { data: vec![], nonce: None, condition: None, + access_list: None, }), Origin::Unknown, ) .unwrap(); assert_eq!(tester.signer.requests().len(), 1); - let t = Transaction { + let t = TypedTransaction::Legacy(Transaction { nonce: U256::zero(), gas_price: U256::from(0x1000), gas: U256::from(10_000_000), action: Action::Call(recipient), value: U256::from(0x1), data: vec![], - }; + }); let signature = tester .accounts - .sign(address, Some("test".into()), t.hash(None)) + .sign(address, Some("test".into()), t.signature_hash(None)) .unwrap(); let t = SignedTransaction::new(t.with_signature(signature.clone(), None)).unwrap(); - let rlp = encode(&t); + let rlp = t.encode(); // when let request = r#"{ diff --git a/rpc/src/v1/tests/mocked/signing.rs b/rpc/src/v1/tests/mocked/signing.rs index 9d8734a3c..fdeb9b496 100644 --- a/rpc/src/v1/tests/mocked/signing.rs +++ b/rpc/src/v1/tests/mocked/signing.rs @@ -14,7 +14,6 @@ // You should have received a copy of the GNU General Public License // along with OpenEthereum. If not, see . -use rlp; use std::{str::FromStr, sync::Arc, thread, time::Duration}; use jsonrpc_core::{futures::Future, IoHandler, Success}; @@ -40,7 +39,7 @@ use ethstore::ethkey::{Generator, Random}; use parity_runtime::{Executor, Runtime}; use parking_lot::Mutex; use serde_json; -use types::transaction::{Action, SignedTransaction, Transaction}; +use types::transaction::{Action, SignedTransaction, Transaction, TypedTransaction}; struct SigningTester { pub runtime: Runtime, @@ -379,7 +378,7 @@ fn should_add_sign_transaction_to_the_queue() { "id": 1 }"#; - let t = Transaction { + let t = TypedTransaction::Legacy(Transaction { nonce: U256::one(), gas_price: U256::from(0x9184e72a000u64), gas: U256::from(0x76c0), @@ -388,15 +387,15 @@ fn should_add_sign_transaction_to_the_queue() { ), value: U256::from(0x9184e72au64), data: vec![], - }; + }); let signature = tester .accounts - .sign(address, Some("test".into()), t.hash(None)) + .sign(address, Some("test".into()), t.signature_hash(None)) .unwrap(); let t = t.with_signature(signature, None); let t = SignedTransaction::new(t).unwrap(); let signature = t.signature(); - let rlp = rlp::encode(&t); + let rlp = t.encode(); let response = r#"{"jsonrpc":"2.0","result":{"#.to_owned() + r#""raw":"0x"# @@ -459,7 +458,7 @@ fn should_dispatch_transaction_if_account_is_unlock() { .unlock_account_permanently(acc, "test".into()) .unwrap(); - let t = Transaction { + let t = TypedTransaction::Legacy(Transaction { nonce: U256::zero(), gas_price: U256::from(0x9184e72a000u64), gas: U256::from(0x76c0), @@ -468,8 +467,11 @@ fn should_dispatch_transaction_if_account_is_unlock() { ), value: U256::from(0x9184e72au64), data: vec![], - }; - let signature = tester.accounts.sign(acc, None, t.hash(None)).unwrap(); + }); + let signature = tester + .accounts + .sign(acc, None, t.signature_hash(None)) + .unwrap(); let t = t.with_signature(signature, None); // when diff --git a/rpc/src/v1/tests/mocked/signing_unsafe.rs b/rpc/src/v1/tests/mocked/signing_unsafe.rs index bb67b42b8..c52127736 100644 --- a/rpc/src/v1/tests/mocked/signing_unsafe.rs +++ b/rpc/src/v1/tests/mocked/signing_unsafe.rs @@ -21,9 +21,7 @@ use ethcore::client::TestBlockChainClient; use ethereum_types::{Address, U256}; use parity_runtime::Runtime; use parking_lot::Mutex; -use rlp; -use rustc_hex::ToHex; -use types::transaction::{Action, Transaction}; +use types::transaction::{Action, Transaction, TypedTransaction}; use jsonrpc_core::IoHandler; use v1::{ @@ -117,7 +115,7 @@ fn rpc_eth_send_transaction() { "id": 1 }"#; - let t = Transaction { + let t = TypedTransaction::Legacy(Transaction { nonce: U256::zero(), gas_price: U256::from(0x9184e72a000u64), gas: U256::from(0x76c0), @@ -126,10 +124,10 @@ fn rpc_eth_send_transaction() { ), value: U256::from(0x9184e72au64), data: vec![], - }; + }); let signature = tester .accounts_provider - .sign(address, None, t.hash(None)) + .sign(address, None, t.signature_hash(None)) .unwrap(); let t = t.with_signature(signature, None); @@ -141,7 +139,7 @@ fn rpc_eth_send_transaction() { tester.miner.increment_nonce(&address); - let t = Transaction { + let t = TypedTransaction::Legacy(Transaction { nonce: U256::one(), gas_price: U256::from(0x9184e72a000u64), gas: U256::from(0x76c0), @@ -150,10 +148,10 @@ fn rpc_eth_send_transaction() { ), value: U256::from(0x9184e72au64), data: vec![], - }; + }); let signature = tester .accounts_provider - .sign(address, None, t.hash(None)) + .sign(address, None, t.signature_hash(None)) .unwrap(); let t = t.with_signature(signature, None); @@ -166,6 +164,7 @@ fn rpc_eth_send_transaction() { #[test] fn rpc_eth_sign_transaction() { + use rustc_hex::ToHex; let tester = EthTester::default(); let address = tester.accounts_provider.new_account(&"".into()).unwrap(); tester @@ -188,7 +187,7 @@ fn rpc_eth_sign_transaction() { "id": 1 }"#; - let t = Transaction { + let t = TypedTransaction::Legacy(Transaction { nonce: U256::one(), gas_price: U256::from(0x9184e72a000u64), gas: U256::from(0x76c0), @@ -197,14 +196,14 @@ fn rpc_eth_sign_transaction() { ), value: U256::from(0x9184e72au64), data: vec![], - }; + }); let signature = tester .accounts_provider - .sign(address, None, t.hash(None)) + .sign(address, None, t.signature_hash(None)) .unwrap(); let t = t.with_signature(signature, None); let signature = t.signature(); - let rlp = rlp::encode(&t); + let rlp = t.encode(); let response = r#"{"jsonrpc":"2.0","result":{"#.to_owned() + r#""raw":"0x"# diff --git a/rpc/src/v1/types/call_request.rs b/rpc/src/v1/types/call_request.rs index ac465d3ca..d27125eed 100644 --- a/rpc/src/v1/types/call_request.rs +++ b/rpc/src/v1/types/call_request.rs @@ -15,6 +15,7 @@ // along with OpenEthereum. If not, see . use ethereum_types::{H160, U256}; +use types::transaction::{AccessList, TypedTxId}; use v1::{helpers::CallRequest as Request, types::Bytes}; /// Call request @@ -22,6 +23,9 @@ use v1::{helpers::CallRequest as Request, types::Bytes}; #[serde(deny_unknown_fields)] #[serde(rename_all = "camelCase")] pub struct CallRequest { + /// transaction type + #[serde(default)] + pub tx_type: TypedTxId, /// From pub from: Option, /// To @@ -36,11 +40,14 @@ pub struct CallRequest { pub data: Option, /// Nonce pub nonce: Option, + /// Access list + pub access_list: Option, } impl Into for CallRequest { fn into(self) -> Request { Request { + tx_type: self.tx_type, from: self.from.map(Into::into), to: self.to.map(Into::into), gas_price: self.gas_price.map(Into::into), @@ -48,6 +55,7 @@ impl Into for CallRequest { value: self.value.map(Into::into), data: self.data.map(Into::into), nonce: self.nonce.map(Into::into), + access_list: self.access_list.map(Into::into), } } } @@ -76,6 +84,7 @@ mod tests { assert_eq!( deserialized, CallRequest { + tx_type: Default::default(), from: Some(H160::from(1)), to: Some(H160::from(2)), gas_price: Some(U256::from(1)), @@ -83,6 +92,7 @@ mod tests { value: Some(U256::from(3)), data: Some(vec![0x12, 0x34, 0x56].into()), nonce: Some(U256::from(4)), + access_list: None, } ); } @@ -100,13 +110,15 @@ mod tests { let deserialized: CallRequest = serde_json::from_str(s).unwrap(); assert_eq!(deserialized, CallRequest { + tx_type: Default::default(), from: Some(H160::from_str("b60e8dd61c5d32be8058bb8eb970870f07233155").unwrap()), to: Some(H160::from_str("d46e8dd67c5d32be8058bb8eb970870f07244567").unwrap()), gas_price: Some(U256::from_str("9184e72a000").unwrap()), gas: Some(U256::from_str("76c0").unwrap()), value: Some(U256::from_str("9184e72a").unwrap()), data: Some("d46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675".from_hex().unwrap().into()), - nonce: None + nonce: None, + access_list: None, }); } @@ -118,6 +130,7 @@ mod tests { assert_eq!( deserialized, CallRequest { + tx_type: Default::default(), from: Some(H160::from(1)), to: None, gas_price: None, @@ -125,6 +138,7 @@ mod tests { value: None, data: None, nonce: None, + access_list: None, } ); } diff --git a/rpc/src/v1/types/confirmations.rs b/rpc/src/v1/types/confirmations.rs index b64f1a5a3..200c0afcb 100644 --- a/rpc/src/v1/types/confirmations.rs +++ b/rpc/src/v1/types/confirmations.rs @@ -331,6 +331,7 @@ mod tests { id: 15.into(), payload: helpers::ConfirmationPayload::SendTransaction( helpers::FilledTransactionRequest { + tx_type: Default::default(), from: 0.into(), used_default_from: false, to: None, @@ -340,6 +341,7 @@ mod tests { data: vec![1, 2, 3], nonce: Some(1.into()), condition: None, + access_list: None, }, ), origin: Origin::Signer { session: 5.into() }, @@ -360,6 +362,7 @@ mod tests { id: 15.into(), payload: helpers::ConfirmationPayload::SignTransaction( helpers::FilledTransactionRequest { + tx_type: Default::default(), from: 0.into(), used_default_from: false, to: None, @@ -369,6 +372,7 @@ mod tests { data: vec![1, 2, 3], nonce: Some(1.into()), condition: None, + access_list: None, }, ), origin: Origin::Unknown, diff --git a/rpc/src/v1/types/receipt.rs b/rpc/src/v1/types/receipt.rs index a524d1915..9a978af49 100644 --- a/rpc/src/v1/types/receipt.rs +++ b/rpc/src/v1/types/receipt.rs @@ -15,13 +15,19 @@ // along with OpenEthereum. If not, see . use ethereum_types::{Bloom as H2048, H160, H256, U256, U64}; -use types::receipt::{LocalizedReceipt, Receipt as EthReceipt, RichReceipt, TransactionOutcome}; +use types::{ + receipt::{LocalizedReceipt, RichReceipt, TransactionOutcome, TypedReceipt}, + transaction::TypedTxId, +}; use v1::types::Log; /// Receipt #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] pub struct Receipt { + /// Transaction Type + #[serde(skip_serializing)] + pub transaction_type: TypedTxId, /// Transaction Hash pub transaction_hash: Option, /// Transaction index @@ -75,6 +81,7 @@ impl From for Receipt { Receipt { to: r.to.map(Into::into), from: Some(r.from), + transaction_type: r.transaction_type, transaction_hash: Some(r.transaction_hash), transaction_index: Some(r.transaction_index.into()), block_hash: Some(r.block_hash), @@ -95,6 +102,7 @@ impl From for Receipt { Receipt { from: Some(r.from), to: r.to.map(Into::into), + transaction_type: r.transaction_type, transaction_hash: Some(r.transaction_hash), transaction_index: Some(r.transaction_index.into()), block_hash: None, @@ -110,11 +118,14 @@ impl From for Receipt { } } -impl From for Receipt { - fn from(r: EthReceipt) -> Self { +impl From for Receipt { + fn from(r: TypedReceipt) -> Self { + let transaction_type = r.tx_type(); + let r = r.receipt().clone(); Receipt { from: None, to: None, + transaction_type, transaction_hash: None, transaction_index: None, block_hash: None, @@ -142,6 +153,7 @@ mod tests { let receipt = Receipt { from: None, to: None, + transaction_type: Default::default(), transaction_hash: Some(0.into()), transaction_index: Some(0.into()), block_hash: Some( diff --git a/rpc/src/v1/types/transaction.rs b/rpc/src/v1/types/transaction.rs index a0d78ff44..86bb7278c 100644 --- a/rpc/src/v1/types/transaction.rs +++ b/rpc/src/v1/types/transaction.rs @@ -20,7 +20,10 @@ use ethcore::{contract_address, CreateContractAddress}; use ethereum_types::{H160, H256, H512, U256, U64}; use miner; use serde::{ser::SerializeStruct, Serialize, Serializer}; -use types::transaction::{Action, LocalizedTransaction, PendingTransaction, SignedTransaction}; +use types::transaction::{ + AccessList, Action, LocalizedTransaction, PendingTransaction, SignedTransaction, + TypedTransaction, +}; use v1::types::{Bytes, TransactionCondition}; /// Transaction @@ -67,6 +70,12 @@ pub struct Transaction { pub s: U256, /// Transaction activates at specified block. pub condition: Option, + /// transaction type + #[serde(skip_serializing)] + pub transaction_type: u8, + /// optional access list + #[serde(skip_serializing)] + pub access_list: AccessList, } /// Local Transaction Status @@ -176,26 +185,35 @@ impl Transaction { pub fn from_localized(mut t: LocalizedTransaction) -> Transaction { let signature = t.signature(); let scheme = CreateContractAddress::FromSenderAndNonce; + + let access_list = if let TypedTransaction::AccessList(al) = t.as_unsigned() { + al.access_list.clone() + } else { + Vec::new() + }; + Transaction { hash: t.hash(), - nonce: t.nonce, + nonce: t.tx().nonce, block_hash: Some(t.block_hash), block_number: Some(t.block_number.into()), transaction_index: Some(t.transaction_index.into()), from: t.sender(), - to: match t.action { + to: match t.tx().action { Action::Create => None, Action::Call(ref address) => Some(*address), }, - value: t.value, - gas_price: t.gas_price, - gas: t.gas, - input: Bytes::new(t.data.clone()), - creates: match t.action { - Action::Create => Some(contract_address(scheme, &t.sender(), &t.nonce, &t.data).0), + value: t.tx().value, + gas_price: t.tx().gas_price, + gas: t.tx().gas, + input: Bytes::new(t.tx().data.clone()), + creates: match t.tx().action { + Action::Create => { + Some(contract_address(scheme, &t.sender(), &t.tx().nonce, &t.tx().data).0) + } Action::Call(_) => None, }, - raw: ::rlp::encode(&t.signed).into(), + raw: Bytes::new(t.signed.encode()), public_key: t.recover_public().ok().map(Into::into), chain_id: t.chain_id().map(U64::from), standard_v: t.standard_v().into(), @@ -203,6 +221,8 @@ impl Transaction { r: signature.r().into(), s: signature.s().into(), condition: None, + transaction_type: t.signed.tx_type() as u8, + access_list, } } @@ -210,26 +230,33 @@ impl Transaction { pub fn from_signed(t: SignedTransaction) -> Transaction { let signature = t.signature(); let scheme = CreateContractAddress::FromSenderAndNonce; + let access_list = if let TypedTransaction::AccessList(al) = t.as_unsigned() { + al.access_list.clone() + } else { + Vec::new() + }; Transaction { hash: t.hash(), - nonce: t.nonce, + nonce: t.tx().nonce, block_hash: None, block_number: None, transaction_index: None, from: t.sender(), - to: match t.action { + to: match t.tx().action { Action::Create => None, Action::Call(ref address) => Some(*address), }, - value: t.value, - gas_price: t.gas_price, - gas: t.gas, - input: Bytes::new(t.data.clone()), - creates: match t.action { - Action::Create => Some(contract_address(scheme, &t.sender(), &t.nonce, &t.data).0), + value: t.tx().value, + gas_price: t.tx().gas_price, + gas: t.tx().gas, + input: Bytes::new(t.tx().data.clone()), + creates: match t.tx().action { + Action::Create => { + Some(contract_address(scheme, &t.sender(), &t.tx().nonce, &t.tx().data).0) + } Action::Call(_) => None, }, - raw: ::rlp::encode(&t).into(), + raw: t.encode().into(), public_key: t.public_key().map(Into::into), chain_id: t.chain_id().map(U64::from), standard_v: t.standard_v().into(), @@ -237,6 +264,8 @@ impl Transaction { r: signature.r().into(), s: signature.s().into(), condition: None, + transaction_type: t.tx_type() as u8, + access_list, } } @@ -265,7 +294,7 @@ impl LocalTransactionStatus { Canceled(tx) => LocalTransactionStatus::Canceled(convert(tx)), Replaced { old, new } => LocalTransactionStatus::Replaced( convert(old), - new.signed().gas_price, + new.signed().tx().gas_price, new.signed().hash(), ), } diff --git a/rpc/src/v1/types/transaction_request.rs b/rpc/src/v1/types/transaction_request.rs index 8b896dc3c..799524408 100644 --- a/rpc/src/v1/types/transaction_request.rs +++ b/rpc/src/v1/types/transaction_request.rs @@ -18,6 +18,7 @@ use ansi_term::Colour; use ethereum_types::{H160, U256}; +use types::transaction::{AccessList, TypedTxId}; use v1::{ helpers, types::{Bytes, TransactionCondition}, @@ -30,6 +31,10 @@ use std::fmt; #[serde(deny_unknown_fields)] #[serde(rename_all = "camelCase")] pub struct TransactionRequest { + /// type of transaction. If none we assume it is legacy + #[serde(default)] + #[serde(skip_serializing)] + pub tx_type: TypedTxId, /// Sender pub from: Option, /// Recipient @@ -46,6 +51,9 @@ pub struct TransactionRequest { pub nonce: Option, /// Delay until this block condition. pub condition: Option, + /// Access list + #[serde(skip_serializing)] + pub access_list: Option, } pub fn format_ether(i: U256) -> String { @@ -97,6 +105,7 @@ impl fmt::Display for TransactionRequest { impl From for TransactionRequest { fn from(r: helpers::TransactionRequest) -> Self { TransactionRequest { + tx_type: r.tx_type, from: r.from.map(Into::into), to: r.to.map(Into::into), gas_price: r.gas_price.map(Into::into), @@ -105,6 +114,7 @@ impl From for TransactionRequest { data: r.data.map(Into::into), nonce: r.nonce.map(Into::into), condition: r.condition.map(Into::into), + access_list: r.access_list.map(Into::into), } } } @@ -112,6 +122,7 @@ impl From for TransactionRequest { impl From for TransactionRequest { fn from(r: helpers::FilledTransactionRequest) -> Self { TransactionRequest { + tx_type: r.tx_type, from: Some(r.from), to: r.to, gas_price: Some(r.gas_price), @@ -120,6 +131,7 @@ impl From for TransactionRequest { data: Some(r.data.into()), nonce: r.nonce, condition: r.condition, + access_list: r.access_list, } } } @@ -127,6 +139,7 @@ impl From for TransactionRequest { impl Into for TransactionRequest { fn into(self) -> helpers::TransactionRequest { helpers::TransactionRequest { + tx_type: self.tx_type, from: self.from.map(Into::into), to: self.to.map(Into::into), gas_price: self.gas_price.map(Into::into), @@ -135,6 +148,7 @@ impl Into for TransactionRequest { data: self.data.map(Into::into), nonce: self.nonce.map(Into::into), condition: self.condition.map(Into::into), + access_list: self.access_list.map(Into::into), } } } @@ -165,6 +179,7 @@ mod tests { assert_eq!( deserialized, TransactionRequest { + tx_type: Default::default(), from: Some(H160::from(1)), to: Some(H160::from(2)), gas_price: Some(U256::from(1)), @@ -173,6 +188,7 @@ mod tests { data: Some(vec![0x12, 0x34, 0x56].into()), nonce: Some(U256::from(4)), condition: Some(TransactionCondition::Number(0x13)), + access_list: None, } ); } @@ -190,6 +206,7 @@ mod tests { let deserialized: TransactionRequest = serde_json::from_str(s).unwrap(); assert_eq!(deserialized, TransactionRequest { + tx_type: Default::default(), from: Some(H160::from_str("b60e8dd61c5d32be8058bb8eb970870f07233155").unwrap()), to: Some(H160::from_str("d46e8dd67c5d32be8058bb8eb970870f07244567").unwrap()), gas_price: Some(U256::from_str("9184e72a000").unwrap()), @@ -197,7 +214,8 @@ mod tests { value: Some(U256::from_str("9184e72a").unwrap()), data: Some("d46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675".from_hex().unwrap().into()), nonce: None, - condition: None, + condition: None, + access_list: None, }); } @@ -209,6 +227,7 @@ mod tests { assert_eq!( deserialized, TransactionRequest { + tx_type: Default::default(), from: Some(H160::from(1).into()), to: None, gas_price: None, @@ -217,6 +236,7 @@ mod tests { data: None, nonce: None, condition: None, + access_list: None, } ); } @@ -236,6 +256,7 @@ mod tests { assert_eq!( deserialized, TransactionRequest { + tx_type: Default::default(), from: Some(H160::from_str("b5f7502a2807cb23615c7456055e1d65b2508625").unwrap()), to: Some(H160::from_str("895d32f2db7d01ebb50053f9e48aacf26584fe40").unwrap()), gas_price: Some(U256::from_str("0ba43b7400").unwrap()), @@ -244,6 +265,7 @@ mod tests { data: Some(vec![0x85, 0x95, 0xba, 0xb1].into()), nonce: None, condition: None, + access_list: None, } ); } diff --git a/secret-store/src/trusted_client.rs b/secret-store/src/trusted_client.rs index 1098443df..4aa2c6771 100644 --- a/secret-store/src/trusted_client.rs +++ b/secret-store/src/trusted_client.rs @@ -16,7 +16,7 @@ use bytes::Bytes; use call_contract::RegistryInfo; -use common_types::transaction::{Action, SignedTransaction, Transaction}; +use common_types::transaction::{Action, SignedTransaction, Transaction, TypedTransaction}; use ethcore::{ client::{BlockChainClient, BlockId, ChainInfo, Client, Nonce}, miner::{Miner, MinerService}, @@ -89,16 +89,18 @@ impl TrustedClient { .upgrade() .ok_or_else(|| Error::Internal("cannot submit tx when miner is offline".into()))?; let engine = client.engine(); - let transaction = Transaction { + let transaction = TypedTransaction::Legacy(Transaction { nonce: client.latest_nonce(&self.self_key_pair.address()), action: Action::Call(contract), gas: miner.authoring_params().gas_range_target.0, gas_price: miner.sensible_gas_price(), value: Default::default(), data: tx_data, - }; + }); let chain_id = engine.signing_chain_id(&client.latest_env_info()); - let signature = self.self_key_pair.sign(&transaction.hash(chain_id))?; + let signature = self + .self_key_pair + .sign(&transaction.signature_hash(chain_id))?; let signed = SignedTransaction::new(transaction.with_signature(signature, chain_id))?; miner .import_own_transaction(&*client, signed.into())