From 72b604b8e87145c96ace89918c6a54f0d63d44cc Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sun, 20 Mar 2016 19:20:37 +0100 Subject: [PATCH 01/13] Avoid tracing DELEGATECALL and CALLCODE. Plus tests for it. --- ethcore/res/null_homestead_morden.json | 35 +++++++++++ ethcore/res/null_morden.json | 2 +- ethcore/src/engine.rs | 10 ++++ ethcore/src/ethereum/ethash.rs | 20 +++---- ethcore/src/evm/interpreter.rs | 32 +++++----- ethcore/src/executive.rs | 11 +++- ethcore/src/null_engine.rs | 11 +++- ethcore/src/spec/spec.rs | 3 + ethcore/src/state.rs | 82 ++++++++++++++++++++++++++ 9 files changed, 177 insertions(+), 29 deletions(-) create mode 100644 ethcore/res/null_homestead_morden.json diff --git a/ethcore/res/null_homestead_morden.json b/ethcore/res/null_homestead_morden.json new file mode 100644 index 000000000..abd3f4de9 --- /dev/null +++ b/ethcore/res/null_homestead_morden.json @@ -0,0 +1,35 @@ +{ + "name": "Morden", + "engineName": "NullEngine", + "params": { + "accountStartNonce": "0x0100000", + "frontierCompatibilityModeLimit": "0x0", + "maximumExtraDataSize": "0x20", + "tieBreakingGas": false, + "minGasLimit": "0x1388", + "gasLimitBoundDivisor": "0x0400", + "minimumDifficulty": "0x020000", + "difficultyBoundDivisor": "0x0800", + "durationLimit": "0x0d", + "blockReward": "0x4563918244F40000", + "registrar": "", + "networkID" : "0x2" + }, + "genesis": { + "nonce": "0x00006d6f7264656e", + "difficulty": "0x20000", + "mixHash": "0x00000000000000000000000000000000000000647572616c65787365646c6578", + "author": "0x0000000000000000000000000000000000000000", + "timestamp": "0x00", + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "extraData": "0x", + "gasLimit": "0x2fefd8" + }, + "accounts": { + "0000000000000000000000000000000000000001": { "balance": "1", "nonce": "1048576", "builtin": { "name": "ecrecover", "pricing": { "linear": { "base": 3000, "word": 0 } } } }, + "0000000000000000000000000000000000000002": { "balance": "1", "nonce": "1048576", "builtin": { "name": "sha256", "pricing": { "linear": { "base": 60, "word": 12 } } } }, + "0000000000000000000000000000000000000003": { "balance": "1", "nonce": "1048576", "builtin": { "name": "ripemd160", "pricing": { "linear": { "base": 600, "word": 120 } } } }, + "0000000000000000000000000000000000000004": { "balance": "1", "nonce": "1048576", "builtin": { "name": "identity", "pricing": { "linear": { "base": 15, "word": 3 } } } }, + "102e61f5d8f9bc71d0ad4a084df4e65e05ce0e1c": { "balance": "1606938044258990275541962092341162602522202993782792835301376", "nonce": "1048576" } + } +} diff --git a/ethcore/res/null_morden.json b/ethcore/res/null_morden.json index 70b48fbdb..86148d640 100644 --- a/ethcore/res/null_morden.json +++ b/ethcore/res/null_morden.json @@ -3,7 +3,7 @@ "engineName": "NullEngine", "params": { "accountStartNonce": "0x0100000", - "frontierCompatibilityModeLimit": "0xfffa2990", + "frontierCompatibilityModeLimit": "0x789b0", "maximumExtraDataSize": "0x20", "tieBreakingGas": false, "minGasLimit": "0x1388", diff --git a/ethcore/src/engine.rs b/ethcore/src/engine.rs index 0b2ce8ae2..7698af529 100644 --- a/ethcore/src/engine.rs +++ b/ethcore/src/engine.rs @@ -103,4 +103,14 @@ pub trait Engine : Sync + Send { fn execute_builtin(&self, a: &Address, input: &[u8], output: &mut [u8]) { self.spec().builtins.get(a).unwrap().execute(input, output); } // TODO: sealing stuff - though might want to leave this for later. + + /// Get a named parameter from the `spec()`'s `engine_params` item and convert to u64, or 0 if that fails. + fn u64_param(&self, name: &str) -> u64 { + self.spec().engine_params.get(name).map_or(0u64, |a| decode(&a)) + } + + /// Get a named parameter from the `spec()`'s `engine_params` item and convert to U256, or 0 if that fails. + fn u256_param(&self, name: &str) -> U256 { + self.spec().engine_params.get(name).map_or(x!(0), |a| decode(&a)) + } } diff --git a/ethcore/src/ethereum/ethash.rs b/ethcore/src/ethereum/ethash.rs index 6f854921d..d2c56ebf1 100644 --- a/ethcore/src/ethereum/ethash.rs +++ b/ethcore/src/ethereum/ethash.rs @@ -57,16 +57,6 @@ impl Ethash { u256_params: RwLock::new(HashMap::new()) } } - - fn u64_param(&self, name: &str) -> u64 { - *self.u64_params.write().unwrap().entry(name.to_owned()).or_insert_with(|| - self.spec().engine_params.get(name).map_or(0u64, |a| decode(&a))) - } - - fn u256_param(&self, name: &str) -> U256 { - *self.u256_params.write().unwrap().entry(name.to_owned()).or_insert_with(|| - self.spec().engine_params.get(name).map_or(x!(0), |a| decode(&a))) - } } impl Engine for Ethash { @@ -199,6 +189,16 @@ impl Engine for Ethash { fn verify_transaction(&self, t: &SignedTransaction, _header: &Header) -> Result<(), Error> { t.sender().map(|_|()) // Perform EC recovery and cache sender } + + fn u64_param(&self, name: &str) -> u64 { + *self.u64_params.write().unwrap().entry(name.to_owned()).or_insert_with(|| + self.spec().engine_params.get(name).map_or(0u64, |a| decode(&a))) + } + + fn u256_param(&self, name: &str) -> U256 { + *self.u256_params.write().unwrap().entry(name.to_owned()).or_insert_with(|| + self.spec().engine_params.get(name).map_or(x!(0), |a| decode(&a))) + } } #[cfg_attr(feature="dev", allow(wrong_self_convention))] // to_ethash should take self diff --git a/ethcore/src/evm/interpreter.rs b/ethcore/src/evm/interpreter.rs index b29fc0d41..eb29ef257 100644 --- a/ethcore/src/evm/interpreter.rs +++ b/ethcore/src/evm/interpreter.rs @@ -348,12 +348,13 @@ impl evm::Evm for Interpreter { impl Interpreter { #[cfg_attr(feature="dev", allow(cyclomatic_complexity))] - fn get_gas_cost_mem(&self, - ext: &evm::Ext, - instruction: Instruction, - mem: &mut Memory, - stack: &Stack - ) -> Result<(U256, usize), evm::Error> { + fn get_gas_cost_mem( + &self, + ext: &evm::Ext, + instruction: Instruction, + mem: &mut Memory, + stack: &Stack + ) -> Result<(U256, usize), evm::Error> { let schedule = ext.schedule(); let info = instructions::get_info(instruction); @@ -522,15 +523,16 @@ impl Interpreter { } #[cfg_attr(feature="dev", allow(too_many_arguments))] - fn exec_instruction(&self, - gas: Gas, - params: &ActionParams, - ext: &mut evm::Ext, - instruction: Instruction, - code: &mut CodeReader, - mem: &mut Memory, - stack: &mut Stack - ) -> Result { + fn exec_instruction( + &self, + gas: Gas, + params: &ActionParams, + ext: &mut evm::Ext, + instruction: Instruction, + code: &mut CodeReader, + mem: &mut Memory, + stack: &mut Stack + ) -> Result { match instruction { instructions::JUMP => { let jump = stack.pop_back(); diff --git a/ethcore/src/executive.rs b/ethcore/src/executive.rs index 6ea39ec3b..c862545f4 100644 --- a/ethcore/src/executive.rs +++ b/ethcore/src/executive.rs @@ -196,6 +196,7 @@ impl<'a> Executive<'a> { if (self.depth + 1) % MAX_VM_DEPTH_FOR_THREAD != 0 { let mut ext = self.as_externalities(OriginInfo::from(¶ms), unconfirmed_substate, output_policy); let vm_factory = self.engine.vm_factory(); + trace!(target: "executive", "ext.schedule.have_delegate_call: {}", ext.schedule().have_delegate_call); return vm_factory.create().exec(params, &mut ext); } @@ -247,24 +248,30 @@ impl<'a> Executive<'a> { } } else if params.code.is_some() { // if destination is a contract, do normal message call + + // don't trace is it's DELEGATECALL or CALLCODE. + let should_trace = if let ActionValue::Transfer(_) = params.value { + params.code_address == params.address + } else { false }; // part of substate that may be reverted let mut unconfirmed_substate = Substate::new(substate.subtraces.is_some()); // transaction tracing stuff. None if there's no tracing. - let mut trace_info = substate.subtraces.as_ref().map(|_| (TraceAction::from_call(¶ms), self.depth)); + let mut trace_info = if should_trace { substate.subtraces.as_ref().map(|_| (TraceAction::from_call(¶ms), self.depth)) } else { None }; let mut trace_output = trace_info.as_ref().map(|_| vec![]); let res = { self.exec_vm(params, &mut unconfirmed_substate, OutputPolicy::Return(output, trace_output.as_mut())) }; + trace!(target: "executive", "res={:?}", res); + // if there's tracing, make up trace_info's result with trace_output and some arithmetic. if let Some((TraceAction::Call(ref mut c), _)) = trace_info { c.result = res.as_ref().ok().map(|gas_left| (c.gas - *gas_left, trace_output.expect("trace_info is Some: qed"))); } - trace!(target: "executive", "sstore-clears={}\n", unconfirmed_substate.sstore_clears_count); trace!(target: "executive", "substate={:?}; unconfirmed_substate={:?}\n", substate, unconfirmed_substate); self.enact_result(&res, substate, unconfirmed_substate, trace_info); diff --git a/ethcore/src/null_engine.rs b/ethcore/src/null_engine.rs index af7255617..99231add4 100644 --- a/ethcore/src/null_engine.rs +++ b/ethcore/src/null_engine.rs @@ -41,7 +41,16 @@ impl Engine for NullEngine { fn vm_factory(&self) -> &Factory { &self.factory } + fn name(&self) -> &str { "NullEngine" } + fn spec(&self) -> &Spec { &self.spec } - fn schedule(&self, _env_info: &EnvInfo) -> Schedule { Schedule::new_frontier() } + + fn schedule(&self, env_info: &EnvInfo) -> Schedule { + if env_info.number < self.u64_param("frontierCompatibilityModeLimit") { + Schedule::new_frontier() + } else { + Schedule::new_homestead() + } + } } diff --git a/ethcore/src/spec/spec.rs b/ethcore/src/spec/spec.rs index c7e2e4e9f..f97d8c8dc 100644 --- a/ethcore/src/spec/spec.rs +++ b/ethcore/src/spec/spec.rs @@ -333,6 +333,9 @@ impl Spec { /// Create a new Spec which conforms to the Morden chain except that it's a NullEngine consensus. pub fn new_test() -> Spec { Self::from_json_utf8(include_bytes!("../../res/null_morden.json")) } + + /// Create a new Spec which conforms to the Morden chain except that it's a NullEngine consensus. + pub fn new_homestead_test() -> Spec { Self::from_json_utf8(include_bytes!("../../res/null_homestead_morden.json")) } } #[cfg(test)] diff --git a/ethcore/src/state.rs b/ethcore/src/state.rs index 78084e6db..c2924d1bb 100644 --- a/ethcore/src/state.rs +++ b/ethcore/src/state.rs @@ -532,6 +532,87 @@ fn should_not_trace_subcall_transaction_to_builtin() { assert_eq!(result.trace, expected_trace); } +#[test] +fn should_not_trace_callcode() { + init_log(); + + let temp = RandomTempPath::new(); + let mut state = get_temp_state_in(temp.as_path()); + + let mut info = EnvInfo::default(); + info.gas_limit = x!(1_000_000); + let engine = Spec::new_test().to_engine().unwrap(); + + let t = Transaction { + nonce: x!(0), + gas_price: x!(0), + gas: x!(100_000), + action: Action::Call(x!(0xa)), + value: x!(0), + data: vec![], + }.sign(&"".sha3()); + + state.init_code(&x!(0xa), FromHex::from_hex("60006000600060006000600b611000f2").unwrap()); + state.init_code(&x!(0xb), FromHex::from_hex("6000").unwrap()); + let result = state.apply(&info, engine.deref(), &t, true).unwrap(); + + let expected_trace = Some(Trace { + depth: 0, + action: TraceAction::Call(TraceCall { + from: x!("9cce34f7ab185c7aba1b7c8140d620b4bda941d6"), + to: x!(0xa), + value: x!(0), + gas: x!(79000), + input: vec![], + result: Some((x!(64), vec![])) + }), + subs: vec![] + }); + assert_eq!(result.trace, expected_trace); +} + +#[test] +fn should_not_trace_delegatecall() { + init_log(); + + let temp = RandomTempPath::new(); + let mut state = get_temp_state_in(temp.as_path()); + + let mut info = EnvInfo::default(); + info.gas_limit = x!(1_000_000); + info.number = 0x789b0; + let engine = Spec::new_test().to_engine().unwrap(); + + println!("schedule.have_delegate_call: {:?}", engine.schedule(&info).have_delegate_call); + + let t = Transaction { + nonce: x!(0), + gas_price: x!(0), + gas: x!(100_000), + action: Action::Call(x!(0xa)), + value: x!(0), + data: vec![], + }.sign(&"".sha3()); + + state.init_code(&x!(0xa), FromHex::from_hex("6000600060006000600b618000f4").unwrap()); + state.init_code(&x!(0xb), FromHex::from_hex("6000").unwrap()); + let result = state.apply(&info, engine.deref(), &t, true).unwrap(); + + let expected_trace = Some(Trace { + depth: 0, + action: TraceAction::Call(TraceCall { + from: x!("9cce34f7ab185c7aba1b7c8140d620b4bda941d6"), + to: x!(0xa), + value: x!(0), + gas: x!(79000), + input: vec![], + result: Some((x!(61), vec![])) + }), + subs: vec![] + }); + assert_eq!(result.trace, expected_trace); +} + #[test] fn should_trace_failed_call_transaction() { init_log(); @@ -572,6 +653,7 @@ fn should_trace_failed_call_transaction() { assert_eq!(result.trace, expected_trace); } + #[test] fn should_trace_call_with_subcall_transaction() { init_log(); From c4d45e0cf049fc8a92ba9888cbbc22678178d766 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Mon, 21 Mar 2016 11:24:03 +0100 Subject: [PATCH 02/13] Trace basic calls! And tests. --- ethcore/src/executive.rs | 60 ++++++++++-------- ethcore/src/state.rs | 127 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 162 insertions(+), 25 deletions(-) diff --git a/ethcore/src/executive.rs b/ethcore/src/executive.rs index c862545f4..8c34b8e39 100644 --- a/ethcore/src/executive.rs +++ b/ethcore/src/executive.rs @@ -246,41 +246,49 @@ impl<'a> Executive<'a> { Err(evm::Error::OutOfGas) } } - } else if params.code.is_some() { + } else { // if destination is a contract, do normal message call - // don't trace is it's DELEGATECALL or CALLCODE. + // don't trace if it's DELEGATECALL or CALLCODE. let should_trace = if let ActionValue::Transfer(_) = params.value { - params.code_address == params.address + params.code_address == params.address && substate.subtraces.is_some() } else { false }; - // part of substate that may be reverted - let mut unconfirmed_substate = Substate::new(substate.subtraces.is_some()); - // transaction tracing stuff. None if there's no tracing. - let mut trace_info = if should_trace { substate.subtraces.as_ref().map(|_| (TraceAction::from_call(¶ms), self.depth)) } else { None }; - let mut trace_output = trace_info.as_ref().map(|_| vec![]); + let (mut trace_info, mut trace_output) = if should_trace { + (Some((TraceAction::from_call(¶ms), self.depth)), Some(vec![])) + } else { (None, None) }; - let res = { - self.exec_vm(params, &mut unconfirmed_substate, OutputPolicy::Return(output, trace_output.as_mut())) - }; + if params.code.is_some() { + // part of substate that may be reverted + let mut unconfirmed_substate = Substate::new(substate.subtraces.is_some()); - trace!(target: "executive", "res={:?}", res); + let res = { + self.exec_vm(params, &mut unconfirmed_substate, OutputPolicy::Return(output, trace_output.as_mut())) + }; - // if there's tracing, make up trace_info's result with trace_output and some arithmetic. - if let Some((TraceAction::Call(ref mut c), _)) = trace_info { - c.result = res.as_ref().ok().map(|gas_left| (c.gas - *gas_left, trace_output.expect("trace_info is Some: qed"))); + trace!(target: "executive", "res={:?}", res); + + // if there's tracing, make up trace_info's result with trace_output and some arithmetic. + if let Some((TraceAction::Call(ref mut c), _)) = trace_info { + c.result = res.as_ref().ok().map(|gas_left| (c.gas - *gas_left, trace_output.expect("trace_info is Some: so should_trace: qed"))); + } + + trace!(target: "executive", "substate={:?}; unconfirmed_substate={:?}\n", substate, unconfirmed_substate); + + self.enact_result(&res, substate, unconfirmed_substate, trace_info); + trace!(target: "executive", "enacted: substate={:?}\n", substate); + res + } else { + // otherwise it's just a basic transaction, only do tracing, if necessary. + trace!(target: "executive", "Basic message (send funds) should_trace={}", should_trace); + self.state.clear_snapshot(); + if let Some((TraceAction::Call(ref mut c), _)) = trace_info { + c.result = Some((x!(0), vec![])); + } + substate.accrue_trace(if should_trace {Some(vec![])} else {None}, trace_info); + Ok(x!(0)) } - - trace!(target: "executive", "substate={:?}; unconfirmed_substate={:?}\n", substate, unconfirmed_substate); - - self.enact_result(&res, substate, unconfirmed_substate, trace_info); - trace!(target: "executive", "enacted: substate={:?}\n", substate); - res - } else { - // otherwise, nothing - self.state.clear_snapshot(); - Ok(params.gas) } } @@ -537,6 +545,7 @@ mod tests { //let next_address = contract_address(&address, &U256::zero()); let mut params = ActionParams::default(); params.address = address.clone(); + params.code_address = address.clone(); params.sender = sender.clone(); params.origin = sender.clone(); params.gas = U256::from(100_000); @@ -634,6 +643,7 @@ mod tests { assert_eq!(substate.subtraces, expected_trace); assert_eq!(gas_left, U256::from(96_776)); } + evm_test!{test_create_contract_value_too_high: test_create_contract_value_too_high_jit, test_create_contract_value_too_high_int} fn test_create_contract_value_too_high(factory: Factory) { // code: diff --git a/ethcore/src/state.rs b/ethcore/src/state.rs index c2924d1bb..7f8454a85 100644 --- a/ethcore/src/state.rs +++ b/ethcore/src/state.rs @@ -469,6 +469,44 @@ fn should_trace_call_transaction() { assert_eq!(result.trace, expected_trace); } +#[test] +fn should_trace_basic_call_transaction() { + init_log(); + + let temp = RandomTempPath::new(); + let mut state = get_temp_state_in(temp.as_path()); + + let mut info = EnvInfo::default(); + info.gas_limit = x!(1_000_000); + let engine = TestEngine::new(5, Factory::default()); + + let t = Transaction { + nonce: x!(0), + gas_price: x!(0), + gas: x!(100_000), + action: Action::Call(x!(0xa)), + value: x!(100), + data: vec![], + }.sign(&"".sha3()); + + state.add_balance(t.sender().as_ref().unwrap(), &x!(100)); + let result = state.apply(&info, &engine, &t, true).unwrap(); + let expected_trace = Some(Trace { + depth: 0, + action: TraceAction::Call(TraceCall { + from: x!("9cce34f7ab185c7aba1b7c8140d620b4bda941d6"), + to: x!(0xa), + value: x!(100), + gas: x!(79000), + input: vec![], + result: Some((x!(0), vec![])) + }), + subs: vec![] + }); + + assert_eq!(result.trace, expected_trace); +} + #[test] fn should_not_trace_call_transaction_to_builtin() { init_log(); @@ -705,6 +743,95 @@ fn should_trace_call_with_subcall_transaction() { assert_eq!(result.trace, expected_trace); } +#[test] +fn should_trace_call_with_basic_subcall_transaction() { + init_log(); + + let temp = RandomTempPath::new(); + let mut state = get_temp_state_in(temp.as_path()); + + let mut info = EnvInfo::default(); + info.gas_limit = x!(1_000_000); + let engine = TestEngine::new(5, Factory::default()); + + let t = Transaction { + nonce: x!(0), + gas_price: x!(0), + gas: x!(100_000), + action: Action::Call(x!(0xa)), + value: x!(100), + data: vec![], + }.sign(&"".sha3()); + + state.init_code(&x!(0xa), FromHex::from_hex("60006000600060006045600b6000f1").unwrap()); + state.add_balance(t.sender().as_ref().unwrap(), &x!(100)); + let result = state.apply(&info, &engine, &t, true).unwrap(); + let expected_trace = Some(Trace { + depth: 0, + action: TraceAction::Call(TraceCall { + from: x!("9cce34f7ab185c7aba1b7c8140d620b4bda941d6"), + to: x!(0xa), + value: x!(100), + gas: x!(79000), + input: vec![], + result: Some((x!(34061), vec![])) + }), + subs: vec![Trace { + depth: 1, + action: TraceAction::Call(TraceCall { + from: x!(0xa), + to: x!(0xb), + value: x!(69), + gas: x!(2300), + input: vec![], + result: Some((x!(0), vec![])) + }), + subs: vec![] + }] + }); + + assert_eq!(result.trace, expected_trace); +} + +#[test] +fn should_not_trace_call_with_invalid_basic_subcall_transaction() { + init_log(); + + let temp = RandomTempPath::new(); + let mut state = get_temp_state_in(temp.as_path()); + + let mut info = EnvInfo::default(); + info.gas_limit = x!(1_000_000); + let engine = TestEngine::new(5, Factory::default()); + + let t = Transaction { + nonce: x!(0), + gas_price: x!(0), + gas: x!(100_000), + action: Action::Call(x!(0xa)), + value: x!(100), + data: vec![], + }.sign(&"".sha3()); + + state.init_code(&x!(0xa), FromHex::from_hex("600060006000600060ff600b6000f1").unwrap()); // not enough funds. + state.add_balance(t.sender().as_ref().unwrap(), &x!(100)); + let result = state.apply(&info, &engine, &t, true).unwrap(); + let expected_trace = Some(Trace { + depth: 0, + action: TraceAction::Call(TraceCall { + from: x!("9cce34f7ab185c7aba1b7c8140d620b4bda941d6"), + to: x!(0xa), + value: x!(100), + gas: x!(79000), + input: vec![], + result: Some((x!(31761), vec![])) + }), + subs: vec![] + }); + + assert_eq!(result.trace, expected_trace); +} + #[test] fn should_trace_failed_subcall_transaction() { init_log(); From 068c0f3782c73cc0de0d8d30452495aa1a718fb1 Mon Sep 17 00:00:00 2001 From: debris Date: Mon, 21 Mar 2016 11:47:50 +0100 Subject: [PATCH 03/13] test for eth_getTransactionReceipt --- ethcore/src/client/ids.rs | 4 +- ethcore/src/client/test_client.rs | 12 +++++- ethcore/src/log_entry.rs | 2 +- ethcore/src/receipt.rs | 1 + rpc/src/v1/tests/eth.rs | 64 ++++++++++++++++++++++++++++++- rpc/src/v1/types/log.rs | 16 ++++---- rpc/src/v1/types/receipt.rs | 38 ++++++++++++++++++ 7 files changed, 122 insertions(+), 15 deletions(-) diff --git a/ethcore/src/client/ids.rs b/ethcore/src/client/ids.rs index 303657a76..9bd59b177 100644 --- a/ethcore/src/client/ids.rs +++ b/ethcore/src/client/ids.rs @@ -20,7 +20,7 @@ use util::hash::H256; use header::BlockNumber; /// Uniquely identifies block. -#[derive(Debug, PartialEq, Clone)] +#[derive(Debug, PartialEq, Clone, Hash, Eq)] pub enum BlockId { /// Block's sha3. /// Querying by hash is always faster. @@ -34,7 +34,7 @@ pub enum BlockId { } /// Uniquely identifies transaction. -#[derive(Debug, PartialEq, Clone)] +#[derive(Debug, PartialEq, Clone, Hash, Eq)] pub enum TransactionId { /// Transaction's sha3. Hash(H256), diff --git a/ethcore/src/client/test_client.rs b/ethcore/src/client/test_client.rs index 0adaae7e5..2b9f1051a 100644 --- a/ethcore/src/client/test_client.rs +++ b/ethcore/src/client/test_client.rs @@ -52,6 +52,8 @@ pub struct TestBlockChainClient { pub code: RwLock>, /// Execution result. pub execution_result: RwLock>, + /// Transaction receipts. + pub receipts: RwLock>, } #[derive(Clone)] @@ -87,12 +89,18 @@ impl TestBlockChainClient { storage: RwLock::new(HashMap::new()), code: RwLock::new(HashMap::new()), execution_result: RwLock::new(None), + receipts: RwLock::new(HashMap::new()), }; client.add_blocks(1, EachBlockWith::Nothing); // add genesis block client.genesis_hash = client.last_hash.read().unwrap().clone(); client } + /// Set the transaction receipt result + pub fn set_transaction_receipt(&self, id: TransactionId, receipt: LocalizedReceipt) { + self.receipts.write().unwrap().insert(id, receipt); + } + /// Set the execution result. pub fn set_execution_result(&self, result: Executed) { *self.execution_result.write().unwrap() = Some(result); @@ -224,8 +232,8 @@ impl BlockChainClient for TestBlockChainClient { unimplemented!(); } - fn transaction_receipt(&self, _id: TransactionId) -> Option { - unimplemented!(); + fn transaction_receipt(&self, id: TransactionId) -> Option { + self.receipts.read().unwrap().get(&id).cloned() } fn blocks_with_bloom(&self, _bloom: &H2048, _from_block: BlockId, _to_block: BlockId) -> Option> { diff --git a/ethcore/src/log_entry.rs b/ethcore/src/log_entry.rs index cf74a6df9..63e07730e 100644 --- a/ethcore/src/log_entry.rs +++ b/ethcore/src/log_entry.rs @@ -78,7 +78,7 @@ impl FromJson for LogEntry { } /// Log localized in a blockchain. -#[derive(Default, Debug, PartialEq)] +#[derive(Default, Debug, PartialEq, Clone)] pub struct LocalizedLogEntry { /// Plain log entry. pub entry: LogEntry, diff --git a/ethcore/src/receipt.rs b/ethcore/src/receipt.rs index 7f0b0d8dd..2fbd3f14c 100644 --- a/ethcore/src/receipt.rs +++ b/ethcore/src/receipt.rs @@ -76,6 +76,7 @@ impl HeapSizeOf for Receipt { } /// Receipt with additional info. +#[derive(Debug, Clone, PartialEq)] pub struct LocalizedReceipt { /// Transaction hash. pub transaction_hash: H256, diff --git a/rpc/src/v1/tests/eth.rs b/rpc/src/v1/tests/eth.rs index 72235b390..4564076eb 100644 --- a/rpc/src/v1/tests/eth.rs +++ b/rpc/src/v1/tests/eth.rs @@ -14,12 +14,15 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . +use std::str::FromStr; use std::collections::HashMap; use std::sync::{Arc, RwLock}; use jsonrpc_core::IoHandler; -use util::hash::{Address, H256}; +use util::hash::{Address, H256, FixedHash}; use util::numbers::{Uint, U256}; -use ethcore::client::{TestBlockChainClient, EachBlockWith, Executed}; +use ethcore::client::{TestBlockChainClient, EachBlockWith, Executed, TransactionId}; +use ethcore::log_entry::{LocalizedLogEntry, LogEntry}; +use ethcore::receipt::LocalizedReceipt; use v1::{Eth, EthClient}; use v1::tests::helpers::{TestAccount, TestAccountProvider, TestSyncProvider, Config, TestMinerService, TestExternalMiner}; @@ -382,6 +385,63 @@ fn rpc_eth_sign() { unimplemented!() } +#[test] +fn rpc_eth_transaction_receipt() { + let receipt = LocalizedReceipt { + transaction_hash: H256::zero(), + transaction_index: 0, + block_hash: H256::from_str("ed76641c68a1c641aee09a94b3b471f4dc0316efe5ac19cf488e2674cf8d05b5").unwrap(), + block_number: 0x4510c, + cumulative_gas_used: U256::from(0x20), + gas_used: U256::from(0x10), + contract_address: None, + logs: vec![LocalizedLogEntry { + entry: LogEntry { + address: Address::from_str("33990122638b9132ca29c723bdf037f1a891a70c").unwrap(), + topics: vec![ + H256::from_str("a6697e974e6a320f454390be03f74955e8978f1a6971ea6730542e37b66179bc").unwrap(), + H256::from_str("4861736852656700000000000000000000000000000000000000000000000000").unwrap() + ], + data: vec![], + }, + block_hash: H256::from_str("ed76641c68a1c641aee09a94b3b471f4dc0316efe5ac19cf488e2674cf8d05b5").unwrap(), + block_number: 0x4510c, + transaction_hash: H256::new(), + transaction_index: 0, + log_index: 1, + }] + }; + + let hash = H256::from_str("b903239f8543d04b5dc1ba6579132b143087c68db1b2168786408fcbce568238").unwrap(); + let tester = EthTester::default(); + tester.client.set_transaction_receipt(TransactionId::Hash(hash), receipt); + + let request = r#"{ + "jsonrpc": "2.0", + "method": "eth_getTransactionReceipt", + "params": ["0xb903239f8543d04b5dc1ba6579132b143087c68db1b2168786408fcbce568238"], + "id": 1 + }"#; + let response = r#"{"jsonrpc":"2.0","result":{"blockHash":"0xed76641c68a1c641aee09a94b3b471f4dc0316efe5ac19cf488e2674cf8d05b5","blockNumber":"0x04510c","contractAddress":null,"cumulativeGasUsed":"0x20","gasUsed":"0x10","logs":[{"address":"0x33990122638b9132ca29c723bdf037f1a891a70c","blockHash":"0xed76641c68a1c641aee09a94b3b471f4dc0316efe5ac19cf488e2674cf8d05b5","blockNumber":"0x04510c","data":"0x","logIndex":"0x01","topics":["0xa6697e974e6a320f454390be03f74955e8978f1a6971ea6730542e37b66179bc","0x4861736852656700000000000000000000000000000000000000000000000000"],"transactionHash":"0x0000000000000000000000000000000000000000000000000000000000000000","transactionIndex":"0x00"}],"transactionHash":"0x0000000000000000000000000000000000000000000000000000000000000000","transactionIndex":"0x00"},"id":1}"#; + + assert_eq!(tester.io.handle_request(request), Some(response.to_owned())); +} + +#[test] +fn rpc_eth_transaction_receipt_null() { + let tester = EthTester::default(); + + let request = r#"{ + "jsonrpc": "2.0", + "method": "eth_getTransactionReceipt", + "params": ["0xb903239f8543d04b5dc1ba6579132b143087c68db1b2168786408fcbce568238"], + "id": 1 + }"#; + let response = r#"{"jsonrpc":"2.0","result":null,"id":1}"#; + + assert_eq!(tester.io.handle_request(request), Some(response.to_owned())); +} + #[test] fn rpc_eth_compilers() { let request = r#"{"jsonrpc": "2.0", "method": "eth_getCompilers", "params": [], "id": 1}"#; diff --git a/rpc/src/v1/types/log.rs b/rpc/src/v1/types/log.rs index 0f5fdee15..c8dfb1aec 100644 --- a/rpc/src/v1/types/log.rs +++ b/rpc/src/v1/types/log.rs @@ -20,19 +20,19 @@ use v1::types::Bytes; #[derive(Debug, Serialize)] pub struct Log { - address: Address, - topics: Vec, - data: Bytes, + pub address: Address, + pub topics: Vec, + pub data: Bytes, #[serde(rename="blockHash")] - block_hash: H256, + pub block_hash: H256, #[serde(rename="blockNumber")] - block_number: U256, + pub block_number: U256, #[serde(rename="transactionHash")] - transaction_hash: H256, + pub transaction_hash: H256, #[serde(rename="transactionIndex")] - transaction_index: U256, + pub transaction_index: U256, #[serde(rename="logIndex")] - log_index: U256, + pub log_index: U256, } impl From for Log { diff --git a/rpc/src/v1/types/receipt.rs b/rpc/src/v1/types/receipt.rs index fa34d5df5..4bcfa3eb5 100644 --- a/rpc/src/v1/types/receipt.rs +++ b/rpc/src/v1/types/receipt.rs @@ -53,4 +53,42 @@ impl From for Receipt { } } +#[cfg(test)] +mod tests { + use serde_json; + use std::str::FromStr; + use util::numbers::*; + use v1::types::{Bytes, Log, Receipt}; + + #[test] + fn receipt_serialization() { + let s = r#"{"transactionHash":"0x0000000000000000000000000000000000000000000000000000000000000000","transactionIndex":"0x00","blockHash":"0xed76641c68a1c641aee09a94b3b471f4dc0316efe5ac19cf488e2674cf8d05b5","blockNumber":"0x04510c","cumulativeGasUsed":"0x20","gasUsed":"0x10","contractAddress":null,"logs":[{"address":"0x33990122638b9132ca29c723bdf037f1a891a70c","topics":["0xa6697e974e6a320f454390be03f74955e8978f1a6971ea6730542e37b66179bc","0x4861736852656700000000000000000000000000000000000000000000000000"],"data":"0x","blockHash":"0xed76641c68a1c641aee09a94b3b471f4dc0316efe5ac19cf488e2674cf8d05b5","blockNumber":"0x04510c","transactionHash":"0x0000000000000000000000000000000000000000000000000000000000000000","transactionIndex":"0x00","logIndex":"0x01"}]}"#; + + let receipt = Receipt { + transaction_hash: H256::zero(), + transaction_index: U256::zero(), + block_hash: H256::from_str("ed76641c68a1c641aee09a94b3b471f4dc0316efe5ac19cf488e2674cf8d05b5").unwrap(), + block_number: U256::from(0x4510c), + cumulative_gas_used: U256::from(0x20), + gas_used: U256::from(0x10), + contract_address: None, + logs: vec![Log { + address: Address::from_str("33990122638b9132ca29c723bdf037f1a891a70c").unwrap(), + topics: vec![ + H256::from_str("a6697e974e6a320f454390be03f74955e8978f1a6971ea6730542e37b66179bc").unwrap(), + H256::from_str("4861736852656700000000000000000000000000000000000000000000000000").unwrap() + ], + data: Bytes::new(vec![]), + block_hash: H256::from_str("ed76641c68a1c641aee09a94b3b471f4dc0316efe5ac19cf488e2674cf8d05b5").unwrap(), + block_number: U256::from(0x4510c), + transaction_hash: H256::new(), + transaction_index: U256::zero(), + log_index: U256::one() + }] + }; + + let serialized = serde_json::to_string(&receipt).unwrap(); + assert_eq!(serialized, s); + } +} From 8ed8652296d549e6812b2b3d7299d5c8893d13f4 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Mon, 21 Mar 2016 11:53:52 +0100 Subject: [PATCH 04/13] Reuse should_Trace. --- ethcore/src/executive.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ethcore/src/executive.rs b/ethcore/src/executive.rs index 8c34b8e39..a7bdb6a55 100644 --- a/ethcore/src/executive.rs +++ b/ethcore/src/executive.rs @@ -261,7 +261,7 @@ impl<'a> Executive<'a> { if params.code.is_some() { // part of substate that may be reverted - let mut unconfirmed_substate = Substate::new(substate.subtraces.is_some()); + let mut unconfirmed_substate = Substate::new(should_trace); let res = { self.exec_vm(params, &mut unconfirmed_substate, OutputPolicy::Return(output, trace_output.as_mut())) From 8906b78b078d2fcd4baac30243c9c31a294f4f6d Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Mon, 21 Mar 2016 11:56:11 +0100 Subject: [PATCH 05/13] Revert break. --- ethcore/src/executive.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ethcore/src/executive.rs b/ethcore/src/executive.rs index a7bdb6a55..0081cdf1e 100644 --- a/ethcore/src/executive.rs +++ b/ethcore/src/executive.rs @@ -287,7 +287,7 @@ impl<'a> Executive<'a> { c.result = Some((x!(0), vec![])); } substate.accrue_trace(if should_trace {Some(vec![])} else {None}, trace_info); - Ok(x!(0)) + Ok(params.gas) } } } From 0e5395013a667820d495beb5654f799c6de739ae Mon Sep 17 00:00:00 2001 From: debris Date: Mon, 21 Mar 2016 12:00:30 +0100 Subject: [PATCH 06/13] implemented eth_sendRawTransaction --- rpc/src/v1/impls/eth.rs | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/rpc/src/v1/impls/eth.rs b/rpc/src/v1/impls/eth.rs index d7ee478bf..96f33fe2f 100644 --- a/rpc/src/v1/impls/eth.rs +++ b/rpc/src/v1/impls/eth.rs @@ -23,13 +23,13 @@ use ethminer::{MinerService, AccountDetails}; use jsonrpc_core::*; use util::numbers::*; use util::sha3::*; -use util::rlp::encode; +use util::rlp::{encode, UntrustedRlp, View}; use ethcore::client::*; use ethcore::block::IsBlock; use ethcore::views::*; use ethcore::ethereum::Ethash; use ethcore::ethereum::denominations::shannon; -use ethcore::transaction::Transaction as EthTransaction; +use ethcore::transaction::{Transaction as EthTransaction, SignedTransaction}; use v1::traits::{Eth, EthFilter}; use v1::types::{Block, BlockTransactions, BlockNumber, Bytes, SyncStatus, SyncInfo, Transaction, TransactionRequest, OptionalValue, Index, Filter, Log, Receipt}; use v1::helpers::{PollFilter, PollManager, ExternalMinerService, ExternalMiner}; @@ -408,6 +408,33 @@ impl Eth for EthClient }) } + fn send_raw_transaction(&self, params: Params) -> Result { + from_params::<(Bytes, )>(params) + .and_then(|(raw_transaction, )| { + let decoded: Result = UntrustedRlp::new(&raw_transaction.to_vec()).as_val(); + match decoded { + Ok(signed_tx) => { + let miner = take_weak!(self.miner); + let client = take_weak!(self.client); + + let hash = signed_tx.hash(); + let import = miner.import_transactions(vec![signed_tx], |a: &Address| AccountDetails { + nonce: client.nonce(a), + balance: client.balance(a), + }); + match import.into_iter().collect::, _>>() { + Ok(_) => to_value(&hash), + Err(e) => { + warn!("Error sending transaction: {:?}", e); + to_value(&U256::zero()) + } + } + }, + Err(_) => { to_value(&U256::zero()) } + } + }) + } + fn call(&self, params: Params) -> Result { from_params::<(TransactionRequest, BlockNumber)>(params) .and_then(|(transaction_request, _block_number)| { From 2ab9d021586dbfaacb946be519ec1134e68edb8a Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Mon, 21 Mar 2016 12:39:13 +0100 Subject: [PATCH 07/13] Fix test. --- ethcore/src/state.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ethcore/src/state.rs b/ethcore/src/state.rs index 7f8454a85..21b0ad6ed 100644 --- a/ethcore/src/state.rs +++ b/ethcore/src/state.rs @@ -774,7 +774,7 @@ fn should_trace_call_with_basic_subcall_transaction() { value: x!(100), gas: x!(79000), input: vec![], - result: Some((x!(34061), vec![])) + result: Some((x!(31761), vec![])) }), subs: vec![Trace { depth: 1, From f2a0e244916a8b836a24102c316a729fa5ca4240 Mon Sep 17 00:00:00 2001 From: debris Date: Mon, 21 Mar 2016 20:29:35 +0100 Subject: [PATCH 08/13] removed outdated comment --- rpc/src/v1/impls/eth.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/rpc/src/v1/impls/eth.rs b/rpc/src/v1/impls/eth.rs index 96f33fe2f..fd35cb963 100644 --- a/rpc/src/v1/impls/eth.rs +++ b/rpc/src/v1/impls/eth.rs @@ -61,7 +61,6 @@ impl EthClient } } - impl EthClient where C: BlockChainClient, S: SyncProvider, @@ -371,7 +370,6 @@ impl Eth for EthClient } fn submit_hashrate(&self, params: Params) -> Result { - // TODO: Index should be U256. from_params::<(U256, H256)>(params).and_then(|(rate, id)| { self.external_miner.submit_hashrate(rate, id); to_value(&true) From 0cdac6de3cec85633afd3e28417d094dc979919f Mon Sep 17 00:00:00 2001 From: debris Date: Tue, 22 Mar 2016 16:07:42 +0100 Subject: [PATCH 09/13] uncle --- ethcore/src/client/client.rs | 9 ++++++-- ethcore/src/client/ids.rs | 8 +++++++ ethcore/src/client/mod.rs | 7 ++++-- ethcore/src/client/test_client.rs | 6 ++++- rpc/src/v1/impls/eth.rs | 38 +++++++++++++++++++++++++++---- 5 files changed, 58 insertions(+), 10 deletions(-) diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index b2254e285..61e3b4cd8 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -21,7 +21,7 @@ use util::*; use util::panics::*; use views::BlockView; use error::*; -use header::{BlockNumber}; +use header::{BlockNumber, Header}; use state::State; use spec::Spec; use engine::Engine; @@ -36,7 +36,7 @@ use filter::Filter; use log_entry::LocalizedLogEntry; use block_queue::{BlockQueue, BlockQueueInfo}; use blockchain::{BlockChain, BlockProvider, TreeRoute, ImportRoute}; -use client::{BlockId, TransactionId, ClientConfig, BlockChainClient}; +use client::{BlockId, TransactionId, UncleId, ClientConfig, BlockChainClient}; use env_info::EnvInfo; use executive::{Executive, Executed}; use receipt::LocalizedReceipt; @@ -549,6 +549,11 @@ impl BlockChainClient for Client where V: Verifier { self.transaction_address(id).and_then(|address| self.chain.transaction(&address)) } + fn uncle(&self, id: UncleId) -> Option
{ + let index = id.1; + self.block(id.0).and_then(|block| BlockView::new(&block).uncle_at(index)) + } + fn transaction_receipt(&self, id: TransactionId) -> Option { self.transaction_address(id).and_then(|address| { let t = self.chain.block(&address.block_hash) diff --git a/ethcore/src/client/ids.rs b/ethcore/src/client/ids.rs index 9bd59b177..79302354f 100644 --- a/ethcore/src/client/ids.rs +++ b/ethcore/src/client/ids.rs @@ -42,3 +42,11 @@ pub enum TransactionId { /// Querying by block position is always faster. Location(BlockId, usize) } + +/// Uniquely identifies Uncle. +pub struct UncleId ( + /// Block id. + pub BlockId, + /// Position in block. + pub usize +); diff --git a/ethcore/src/client/mod.rs b/ethcore/src/client/mod.rs index 74b05652f..65733f3bf 100644 --- a/ethcore/src/client/mod.rs +++ b/ethcore/src/client/mod.rs @@ -23,7 +23,7 @@ mod test_client; pub use self::client::*; pub use self::config::{ClientConfig, BlockQueueConfig, BlockChainConfig}; -pub use self::ids::{BlockId, TransactionId}; +pub use self::ids::{BlockId, TransactionId, UncleId}; pub use self::test_client::{TestBlockChainClient, EachBlockWith}; pub use executive::Executed; @@ -34,7 +34,7 @@ use util::numbers::U256; use blockchain::TreeRoute; use block_queue::BlockQueueInfo; use block::{ClosedBlock, SealedBlock}; -use header::BlockNumber; +use header::{BlockNumber, Header}; use transaction::{LocalizedTransaction, SignedTransaction}; use log_entry::LocalizedLogEntry; use filter::Filter; @@ -77,6 +77,9 @@ pub trait BlockChainClient : Sync + Send { /// Get transaction with given hash. fn transaction(&self, id: TransactionId) -> Option; + /// Get uncle with given id. + fn uncle(&self, id: UncleId) -> Option
; + /// Get transaction receipt with given hash. fn transaction_receipt(&self, id: TransactionId) -> Option; diff --git a/ethcore/src/client/test_client.rs b/ethcore/src/client/test_client.rs index 2b9f1051a..1c3068f12 100644 --- a/ethcore/src/client/test_client.rs +++ b/ethcore/src/client/test_client.rs @@ -19,7 +19,7 @@ use util::*; use transaction::{Transaction, LocalizedTransaction, SignedTransaction, Action}; use blockchain::TreeRoute; -use client::{BlockChainClient, BlockChainInfo, BlockStatus, BlockId, TransactionId}; +use client::{BlockChainClient, BlockChainInfo, BlockStatus, BlockId, TransactionId, UncleId}; use header::{Header as BlockHeader, BlockNumber}; use filter::Filter; use log_entry::LocalizedLogEntry; @@ -232,6 +232,10 @@ impl BlockChainClient for TestBlockChainClient { unimplemented!(); } + fn uncle(&self, _id: UncleId) -> Option { + unimplemented!(); + } + fn transaction_receipt(&self, id: TransactionId) -> Option { self.receipts.read().unwrap().get(&id).cloned() } diff --git a/rpc/src/v1/impls/eth.rs b/rpc/src/v1/impls/eth.rs index fd35cb963..6d8bd4b07 100644 --- a/rpc/src/v1/impls/eth.rs +++ b/rpc/src/v1/impls/eth.rs @@ -126,9 +126,36 @@ impl EthClient } } - fn uncle(&self, _block: BlockId, _index: usize) -> Result { - // TODO: implement! - Ok(Value::Null) + fn uncle(&self, id: UncleId) -> Result { + let client = take_weak!(self.client); + match client.uncle(id).and_then(|u| client.block_total_difficulty(BlockId::Hash(u.hash())).map(|diff| (diff, u))) { + Some((difficulty, uncle)) => { + let block = Block { + hash: OptionalValue::Value(uncle.hash()), + parent_hash: uncle.parent_hash, + uncles_hash: uncle.uncles_hash, + author: uncle.author, + miner: uncle.author, + state_root: uncle.state_root, + transactions_root: uncle.transactions_root, + number: OptionalValue::Value(U256::from(uncle.number)), + gas_used: uncle.gas_used, + gas_limit: uncle.gas_limit, + logs_bloom: uncle.log_bloom, + timestamp: U256::from(uncle.timestamp), + difficulty: uncle.difficulty, + total_difficulty: difficulty, + receipts_root: uncle.receipts_root, + extra_data: Bytes::new(uncle.extra_data), + // todo: + nonce: H64::from(0), + uncles: vec![], + transactions: BlockTransactions::Hashes(vec![]), + }; + to_value(&block) + }, + None => Ok(Value::Null) + } } } @@ -303,12 +330,12 @@ impl Eth for EthClient fn uncle_by_block_hash_and_index(&self, params: Params) -> Result { from_params::<(H256, Index)>(params) - .and_then(|(hash, index)| self.uncle(BlockId::Hash(hash), index.value())) + .and_then(|(hash, index)| self.uncle(UncleId(BlockId::Hash(hash), index.value()))) } fn uncle_by_block_number_and_index(&self, params: Params) -> Result { from_params::<(BlockNumber, Index)>(params) - .and_then(|(number, index)| self.uncle(number.into(), index.value())) + .and_then(|(number, index)| self.uncle(UncleId(number.into(), index.value()))) } fn compilers(&self, params: Params) -> Result { @@ -434,6 +461,7 @@ impl Eth for EthClient } fn call(&self, params: Params) -> Result { + println!("params: {:?}", params); from_params::<(TransactionRequest, BlockNumber)>(params) .and_then(|(transaction_request, _block_number)| { let accounts = take_weak!(self.accounts); From a0cbe7cd7e4242b2d83ae44bc8a469ed9498edf1 Mon Sep 17 00:00:00 2001 From: debris Date: Tue, 22 Mar 2016 17:17:50 +0100 Subject: [PATCH 10/13] fixed eth_call, eth_sendTransaction and eth_estimateGas --- rpc/src/v1/impls/eth.rs | 107 +++++++++++++++--------- rpc/src/v1/types/call_request.rs | 106 +++++++++++++++++++++++ rpc/src/v1/types/mod.rs.in | 2 + rpc/src/v1/types/transaction_request.rs | 60 +------------ util/src/crypto.rs | 8 ++ 5 files changed, 185 insertions(+), 98 deletions(-) create mode 100644 rpc/src/v1/types/call_request.rs diff --git a/rpc/src/v1/impls/eth.rs b/rpc/src/v1/impls/eth.rs index 6d8bd4b07..963afbecb 100644 --- a/rpc/src/v1/impls/eth.rs +++ b/rpc/src/v1/impls/eth.rs @@ -24,17 +24,26 @@ use jsonrpc_core::*; use util::numbers::*; use util::sha3::*; use util::rlp::{encode, UntrustedRlp, View}; +use util::crypto::KeyPair; use ethcore::client::*; use ethcore::block::IsBlock; use ethcore::views::*; use ethcore::ethereum::Ethash; use ethcore::ethereum::denominations::shannon; -use ethcore::transaction::{Transaction as EthTransaction, SignedTransaction}; +use ethcore::transaction::{Transaction as EthTransaction, SignedTransaction, Action}; use v1::traits::{Eth, EthFilter}; -use v1::types::{Block, BlockTransactions, BlockNumber, Bytes, SyncStatus, SyncInfo, Transaction, TransactionRequest, OptionalValue, Index, Filter, Log, Receipt}; +use v1::types::{Block, BlockTransactions, BlockNumber, Bytes, SyncStatus, SyncInfo, Transaction, TransactionRequest, CallRequest, OptionalValue, Index, Filter, Log, Receipt}; use v1::helpers::{PollFilter, PollManager, ExternalMinerService, ExternalMiner}; use util::keys::store::AccountProvider; +fn default_gas() -> U256 { + U256::from(21_000) +} + +fn default_gas_price() -> U256 { + shannon() * U256::from(50) +} + /// Eth rpc implementation. pub struct EthClient where C: BlockChainClient, @@ -157,6 +166,35 @@ impl EthClient None => Ok(Value::Null) } } + + fn sign_call(client: &Arc, accounts: &Arc, request: CallRequest) -> Option { + match request.from { + Some(ref from) => { + let transaction = EthTransaction { + nonce: request.nonce.unwrap_or_else(|| client.nonce(from)), + action: request.to.map_or(Action::Create, Action::Call), + gas: request.gas.unwrap_or_else(default_gas), + gas_price: request.gas_price.unwrap_or_else(default_gas_price), + value: request.value.unwrap_or_else(U256::zero), + data: request.data.map_or_else(Vec::new, |d| d.to_vec()) + }; + + accounts.account_secret(from).ok().map(|secret| transaction.sign(&secret)) + }, + None => { + let transaction = EthTransaction { + nonce: request.nonce.unwrap_or_else(U256::zero), + action: request.to.map_or(Action::Create, Action::Call), + gas: request.gas.unwrap_or_else(default_gas), + gas_price: request.gas_price.unwrap_or_else(default_gas_price), + value: request.value.unwrap_or_else(U256::zero), + data: request.data.map_or_else(Vec::new, |d| d.to_vec()) + }; + + KeyPair::create().ok().map(|kp| transaction.sign(kp.secret())) + } + } + } } impl Eth for EthClient @@ -217,7 +255,7 @@ impl Eth for EthClient fn gas_price(&self, params: Params) -> Result { match params { - Params::None => to_value(&(shannon() * U256::from(50))), + Params::None => to_value(&default_gas_price()), _ => Err(Error::invalid_params()) } } @@ -405,14 +443,22 @@ impl Eth for EthClient fn send_transaction(&self, params: Params) -> Result { from_params::<(TransactionRequest, )>(params) - .and_then(|(transaction_request, )| { + .and_then(|(request, )| { let accounts = take_weak!(self.accounts); - match accounts.account_secret(&transaction_request.from) { + match accounts.account_secret(&request.from) { Ok(secret) => { let miner = take_weak!(self.miner); let client = take_weak!(self.client); - let transaction: EthTransaction = transaction_request.into(); + let transaction = EthTransaction { + nonce: request.nonce.unwrap_or_else(|| client.nonce(&request.from)), + action: request.to.map_or(Action::Create, Action::Call), + gas: request.gas.unwrap_or_else(default_gas), + gas_price: request.gas_price.unwrap_or_else(default_gas_price), + value: request.value.unwrap_or_else(U256::zero), + data: request.data.map_or_else(Vec::new, |d| d.to_vec()) + }; + let signed_transaction = transaction.sign(&secret); let hash = signed_transaction.hash(); @@ -461,47 +507,30 @@ impl Eth for EthClient } fn call(&self, params: Params) -> Result { - println!("params: {:?}", params); - from_params::<(TransactionRequest, BlockNumber)>(params) - .and_then(|(transaction_request, _block_number)| { + from_params::<(CallRequest, BlockNumber)>(params) + .and_then(|(request, _block_number)| { + let client = take_weak!(self.client); let accounts = take_weak!(self.accounts); - match accounts.account_secret(&transaction_request.from) { - Ok(secret) => { - let client = take_weak!(self.client); + let signed = Self::sign_call(&client, &accounts, request); + let output = signed.map(|tx| client.call(&tx) + .map(|e| Bytes::new(e.output)) + .unwrap_or(Bytes::default())); - let transaction: EthTransaction = transaction_request.into(); - let signed_transaction = transaction.sign(&secret); - - let output = client.call(&signed_transaction) - .map(|e| Bytes::new(e.output)) - .unwrap_or(Bytes::default()); - - to_value(&output) - }, - Err(_) => { to_value(&Bytes::default()) } - } + to_value(&output) }) } fn estimate_gas(&self, params: Params) -> Result { - from_params::<(TransactionRequest, BlockNumber)>(params) - .and_then(|(transaction_request, _block_number)| { + from_params::<(CallRequest, BlockNumber)>(params) + .and_then(|(request, _block_number)| { + let client = take_weak!(self.client); let accounts = take_weak!(self.accounts); - match accounts.account_secret(&transaction_request.from) { - Ok(secret) => { - let client = take_weak!(self.client); + let signed = Self::sign_call(&client, &accounts, request); + let output = signed.map(|tx| client.call(&tx) + .map(|e| e.gas_used + e.refunded) + .unwrap_or(U256::zero())); - let transaction: EthTransaction = transaction_request.into(); - let signed_transaction = transaction.sign(&secret); - - let gas_used = client.call(&signed_transaction) - .map(|e| e.gas_used + e.refunded) - .unwrap_or(U256::zero()); - - to_value(&gas_used) - }, - Err(_) => { to_value(&U256::zero()) } - } + to_value(&output) }) } } diff --git a/rpc/src/v1/types/call_request.rs b/rpc/src/v1/types/call_request.rs new file mode 100644 index 000000000..a47d40eb3 --- /dev/null +++ b/rpc/src/v1/types/call_request.rs @@ -0,0 +1,106 @@ +// Copyright 2015, 2016 Ethcore (UK) Ltd. +// This file is part of Parity. + +// Parity 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. + +// Parity 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 Parity. If not, see . + +use util::hash::Address; +use util::numbers::U256; +use v1::types::Bytes; + +#[derive(Debug, Default, PartialEq, Deserialize)] +pub struct CallRequest { + pub from: Option
, + pub to: Option
, + #[serde(rename="gasPrice")] + pub gas_price: Option, + pub gas: Option, + pub value: Option, + pub data: Option, + pub nonce: Option, +} + +#[cfg(test)] +mod tests { + use std::str::FromStr; + use rustc_serialize::hex::FromHex; + use serde_json; + use util::numbers::{Uint, U256}; + use util::hash::Address; + use ethcore::transaction::{Transaction, Action}; + use v1::types::Bytes; + use super::*; + + #[test] + fn transaction_request_deserialize() { + let s = r#"{ + "from":"0x0000000000000000000000000000000000000001", + "to":"0x0000000000000000000000000000000000000002", + "gasPrice":"0x1", + "gas":"0x2", + "value":"0x3", + "data":"0x123456", + "nonce":"0x4" + }"#; + let deserialized: CallRequest = serde_json::from_str(s).unwrap(); + + assert_eq!(deserialized, CallRequest { + from: Some(Address::from(1)), + to: Some(Address::from(2)), + gas_price: Some(U256::from(1)), + gas: Some(U256::from(2)), + value: Some(U256::from(3)), + data: Some(Bytes::new(vec![0x12, 0x34, 0x56])), + nonce: Some(U256::from(4)), + }); + } + + #[test] + fn transaction_request_deserialize2() { + let s = r#"{ + "from": "0xb60e8dd61c5d32be8058bb8eb970870f07233155", + "to": "0xd46e8dd67c5d32be8058bb8eb970870f07244567", + "gas": "0x76c0", + "gasPrice": "0x9184e72a000", + "value": "0x9184e72a", + "data": "0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675" + }"#; + let deserialized: CallRequest = serde_json::from_str(s).unwrap(); + + assert_eq!(deserialized, CallRequest { + from: Some(Address::from_str("b60e8dd61c5d32be8058bb8eb970870f07233155").unwrap()), + to: Some(Address::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(Bytes::new("d46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675".from_hex().unwrap())), + nonce: None + }); + } + + #[test] + fn transaction_request_deserialize_empty() { + let s = r#"{"from":"0x0000000000000000000000000000000000000001"}"#; + let deserialized: CallRequest = serde_json::from_str(s).unwrap(); + + assert_eq!(deserialized, CallRequest { + from: Some(Address::from(1)), + to: None, + gas_price: None, + gas: None, + value: None, + data: None, + nonce: None, + }); + } +} diff --git a/rpc/src/v1/types/mod.rs.in b/rpc/src/v1/types/mod.rs.in index 0121e4aea..06b07b146 100644 --- a/rpc/src/v1/types/mod.rs.in +++ b/rpc/src/v1/types/mod.rs.in @@ -24,6 +24,7 @@ mod optionals; mod sync; mod transaction; mod transaction_request; +mod call_request; mod receipt; pub use self::block::{Block, BlockTransactions}; @@ -36,5 +37,6 @@ pub use self::optionals::OptionalValue; pub use self::sync::{SyncStatus, SyncInfo}; pub use self::transaction::Transaction; pub use self::transaction_request::TransactionRequest; +pub use self::call_request::CallRequest; pub use self::receipt::Receipt; diff --git a/rpc/src/v1/types/transaction_request.rs b/rpc/src/v1/types/transaction_request.rs index ed4dc19a2..a9ed8a3f4 100644 --- a/rpc/src/v1/types/transaction_request.rs +++ b/rpc/src/v1/types/transaction_request.rs @@ -15,8 +15,7 @@ // along with Parity. If not, see . use util::hash::Address; -use util::numbers::{Uint, U256}; -use ethcore::transaction::{Action, Transaction}; +use util::numbers::U256; use v1::types::Bytes; #[derive(Debug, Default, PartialEq, Deserialize)] @@ -31,19 +30,6 @@ pub struct TransactionRequest { pub nonce: Option, } -impl Into for TransactionRequest { - fn into(self) -> Transaction { - Transaction { - nonce: self.nonce.unwrap_or_else(U256::zero), - action: self.to.map_or(Action::Create, Action::Call), - gas: self.gas.unwrap_or_else(U256::zero), - gas_price: self.gas_price.unwrap_or_else(U256::zero), - value: self.value.unwrap_or_else(U256::zero), - data: self.data.map_or_else(Vec::new, |d| d.to_vec()), - } - } -} - #[cfg(test)] mod tests { use std::str::FromStr; @@ -55,50 +41,6 @@ mod tests { use v1::types::Bytes; use super::*; - #[test] - fn transaction_request_into_transaction() { - let tr = TransactionRequest { - from: Address::default(), - to: Some(Address::from(10)), - gas_price: Some(U256::from(20)), - gas: Some(U256::from(10_000)), - value: Some(U256::from(1)), - data: Some(Bytes::new(vec![10, 20])), - nonce: Some(U256::from(12)), - }; - - assert_eq!(Transaction { - nonce: U256::from(12), - action: Action::Call(Address::from(10)), - gas: U256::from(10_000), - gas_price: U256::from(20), - value: U256::from(1), - data: vec![10, 20], - }, tr.into()); - } - - #[test] - fn empty_transaction_request_into_transaction() { - let tr = TransactionRequest { - from: Address::default(), - to: None, - gas_price: None, - gas: None, - value: None, - data: None, - nonce: None, - }; - - assert_eq!(Transaction { - nonce: U256::zero(), - action: Action::Create, - gas: U256::zero(), - gas_price: U256::zero(), - value: U256::zero(), - data: vec![], - }, tr.into()); - } - #[test] fn transaction_request_deserialize() { let s = r#"{ diff --git a/util/src/crypto.rs b/util/src/crypto.rs index 66e9f2edb..e5096a317 100644 --- a/util/src/crypto.rs +++ b/util/src/crypto.rs @@ -20,6 +20,7 @@ use numbers::*; use bytes::*; use secp256k1::{key, Secp256k1}; use rand::os::OsRng; +use sha3::Hashable; /// Secret key for secp256k1 EC operations. 256 bit generic "hash" data. pub type Secret = H256; @@ -135,15 +136,22 @@ impl KeyPair { public: p, }) } + /// Returns public key pub fn public(&self) -> &Public { &self.public } + /// Returns private key pub fn secret(&self) -> &Secret { &self.secret } + /// Returns address. + pub fn address(&self) -> Address { + Address::from(self.public.sha3()) + } + /// Sign a message with our secret key. pub fn sign(&self, message: &H256) -> Result { ec::sign(&self.secret, message) } } From 7624bcf49ec4eb0e3dccdef8daa51989546b2e4b Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Tue, 22 Mar 2016 18:43:06 +0100 Subject: [PATCH 11/13] Increase threads to 4. --- parity/main.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/parity/main.rs b/parity/main.rs index e029124c4..946e409bb 100644 --- a/parity/main.rs +++ b/parity/main.rs @@ -269,7 +269,9 @@ fn setup_rpc_server( } } } - Some(server.start_http(url, cors_domain, 1)) + // 4 is the number of threads which also happens to be the maximum number of concurrent + // connections our jsonrpc can manage. + Some(server.start_http(url, cors_domain, 4)) } #[cfg(not(feature = "rpc"))] From 0e026ed11ff7e3330c84965c6228d43aeffd298c Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Tue, 22 Mar 2016 19:12:17 +0100 Subject: [PATCH 12/13] Fix author reporting. num_cpus for JSONRPC threads. --- Cargo.lock | 1 + Cargo.toml | 1 + miner/src/lib.rs | 8 +++++++- miner/src/miner.rs | 8 ++++++++ parity/main.rs | 5 ++--- rpc/src/v1/impls/eth.rs | 4 ++-- 6 files changed, 21 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 56bd823c1..14ebabb6e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -15,6 +15,7 @@ dependencies = [ "ethsync 1.1.0", "fdlimit 0.1.0", "log 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "num_cpus 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", "number_prefix 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", "rpassword 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-serialize 0.3.18 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/Cargo.toml b/Cargo.toml index e468799ea..f281cb854 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,7 @@ time = "0.1" ctrlc = { git = "https://github.com/tomusdrw/rust-ctrlc.git" } fdlimit = { path = "util/fdlimit" } daemonize = "0.2" +num_cpus = "0.2" number_prefix = "0.2" rpassword = "0.1" clippy = { version = "0.0.54", optional = true } diff --git a/miner/src/lib.rs b/miner/src/lib.rs index ca25e3993..06faf057e 100644 --- a/miner/src/lib.rs +++ b/miner/src/lib.rs @@ -65,7 +65,7 @@ pub use transaction_queue::{TransactionQueue, AccountDetails}; pub use miner::{Miner}; use std::sync::Mutex; -use util::{H256, Address, Bytes}; +use util::{H256, Address, FixedHash, Bytes}; use ethcore::client::{BlockChainClient}; use ethcore::block::{ClosedBlock}; use ethcore::error::{Error}; @@ -77,6 +77,12 @@ pub trait MinerService : Send + Sync { /// Returns miner's status. fn status(&self) -> MinerStatus; + /// Get the author that we will seal blocks as. + fn author(&self) -> Address { Address::zero() } + + /// Get the extra_data that we will seal blocks wuth. + fn extra_data(&self) -> Bytes { vec![] } + /// Imports transactions to transaction queue. fn import_transactions(&self, transactions: Vec, fetch_account: T) -> Vec> where T: Fn(&Address) -> AccountDetails; diff --git a/miner/src/miner.rs b/miner/src/miner.rs index e1b314d57..d4f6c8a00 100644 --- a/miner/src/miner.rs +++ b/miner/src/miner.rs @@ -146,6 +146,14 @@ impl MinerService for Miner { } } + fn author(&self) -> Address { + *self.author.read().unwrap() + } + + fn extra_data(&self) -> Bytes { + self.extra_data.read().unwrap().clone() + } + fn import_transactions(&self, transactions: Vec, fetch_account: T) -> Vec> where T: Fn(&Address) -> AccountDetails { let mut transaction_queue = self.transaction_queue.lock().unwrap(); diff --git a/parity/main.rs b/parity/main.rs index 946e409bb..731bba9a1 100644 --- a/parity/main.rs +++ b/parity/main.rs @@ -20,6 +20,7 @@ #![cfg_attr(feature="dev", feature(plugin))] #![cfg_attr(feature="dev", plugin(clippy))] extern crate docopt; +extern crate num_cpus; extern crate rustc_serialize; extern crate ethcore_util as util; extern crate ethcore; @@ -269,9 +270,7 @@ fn setup_rpc_server( } } } - // 4 is the number of threads which also happens to be the maximum number of concurrent - // connections our jsonrpc can manage. - Some(server.start_http(url, cors_domain, 4)) + Some(server.start_http(url, cors_domain, ::num_cpus::get())) } #[cfg(not(feature = "rpc"))] diff --git a/rpc/src/v1/impls/eth.rs b/rpc/src/v1/impls/eth.rs index d7ee478bf..df21623fd 100644 --- a/rpc/src/v1/impls/eth.rs +++ b/rpc/src/v1/impls/eth.rs @@ -168,8 +168,8 @@ impl Eth for EthClient // TODO: do not hardcode author. fn author(&self, params: Params) -> Result { match params { - Params::None => to_value(&Address::new()), - _ => Err(Error::invalid_params()) + Params::None => to_value(&take_weak!(self.miner).author()), + _ => Err(Error::invalid_params()), } } From c2d2e4162467e88f96b34ae754ec8f494db3c422 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Wed, 23 Mar 2016 12:23:50 +0100 Subject: [PATCH 13/13] Fixing future-current transactions clash --- miner/src/transaction_queue.rs | 100 +++++++++++++++++++++++++++++++-- 1 file changed, 94 insertions(+), 6 deletions(-) diff --git a/miner/src/transaction_queue.rs b/miner/src/transaction_queue.rs index a07395349..9abe80f1c 100644 --- a/miner/src/transaction_queue.rs +++ b/miner/src/transaction_queue.rs @@ -192,7 +192,12 @@ impl TransactionSet { /// Inserts `TransactionOrder` to this set fn insert(&mut self, sender: Address, nonce: U256, order: TransactionOrder) -> Option { self.by_priority.insert(order.clone()); - self.by_address.insert(sender, nonce, order) + let r = self.by_address.insert(sender, nonce, order); + // If transaction was replaced remove it from priority queue + if let Some(ref order) = r { + self.by_priority.remove(order); + } + r } /// Remove low priority transactions if there is more then specified by given `limit`. @@ -371,7 +376,6 @@ impl TransactionQueue { })); } - let vtx = try!(VerifiedTransaction::new(tx)); let account = fetch_account(&vtx.sender()); @@ -571,9 +575,20 @@ impl TransactionQueue { } Self::replace_transaction(tx, state_nonce, &mut self.current, &mut self.by_hash); + // Keep track of highest nonce stored in current self.last_nonces.insert(address, nonce); - // But maybe there are some more items waiting in future? + // Update nonces of transactions in future + self.update_future(&address, state_nonce); + // Maybe there are some more items waiting in future? self.move_matching_future_to_current(address, nonce + U256::one(), state_nonce); + // There might be exactly the same transaction waiting in future + // same (sender, nonce), but above function would not move it. + if let Some(order) = self.future.drop(&address, &nonce) { + // Let's insert that transaction to current (if it has higher gas_price) + let future_tx = self.by_hash.remove(&order.hash).unwrap(); + Self::replace_transaction(future_tx, state_nonce, &mut self.current, &mut self.by_hash); + } + // Also enforce the limit self.current.enforce_limit(&mut self.by_hash); trace!(target: "miner", "status: {:?}", self.status()); @@ -597,13 +612,11 @@ impl TransactionQueue { let new_fee = order.gas_price; if old_fee.cmp(&new_fee) == Ordering::Greater { // Put back old transaction since it has greater priority (higher gas_price) - set.by_address.insert(address, nonce, old); + set.insert(address, nonce, old); // and remove new one - set.by_priority.remove(&order); by_hash.remove(&hash); } else { // Make sure we remove old transaction entirely - set.by_priority.remove(&old); by_hash.remove(&old.hash); } } @@ -643,6 +656,18 @@ mod test { } } + /// Returns two transactions with identical (sender, nonce) but different hashes + fn new_similar_txs() -> (SignedTransaction, SignedTransaction) { + let keypair = KeyPair::create().unwrap(); + let secret = &keypair.secret(); + let nonce = U256::from(123); + let tx = new_unsigned_tx(nonce); + let mut tx2 = new_unsigned_tx(nonce); + tx2.gas_price = U256::from(2); + + (tx.sign(secret), tx2.sign(secret)) + } + fn new_txs(second_nonce: U256) -> (SignedTransaction, SignedTransaction) { let keypair = KeyPair::create().unwrap(); let secret = &keypair.secret(); @@ -693,6 +718,69 @@ mod test { assert_eq!(set.by_address.len(), 0); } + #[test] + fn should_replace_transaction_in_set() { + let mut set = TransactionSet { + by_priority: BTreeSet::new(), + by_address: Table::new(), + limit: 1 + }; + // Create two transactions with same nonce + // (same hash) + let (tx1, tx2) = new_txs(U256::from(0)); + let tx1 = VerifiedTransaction::new(tx1).unwrap(); + let tx2 = VerifiedTransaction::new(tx2).unwrap(); + let by_hash = { + let mut x = HashMap::new(); + let tx1 = VerifiedTransaction::new(tx1.transaction.clone()).unwrap(); + let tx2 = VerifiedTransaction::new(tx2.transaction.clone()).unwrap(); + x.insert(tx1.hash(), tx1); + x.insert(tx2.hash(), tx2); + x + }; + // Insert both transactions + let order1 = TransactionOrder::for_transaction(&tx1, U256::zero()); + set.insert(tx1.sender(), tx1.nonce(), order1.clone()); + assert_eq!(set.by_priority.len(), 1); + assert_eq!(set.by_address.len(), 1); + // Two different orders (imagine nonce changed in the meantime) + let order2 = TransactionOrder::for_transaction(&tx2, U256::one()); + set.insert(tx2.sender(), tx2.nonce(), order2.clone()); + assert_eq!(set.by_priority.len(), 1); + assert_eq!(set.by_address.len(), 1); + + // then + assert_eq!(by_hash.len(), 1); + assert_eq!(set.by_priority.len(), 1); + assert_eq!(set.by_address.len(), 1); + assert_eq!(set.by_priority.iter().next().unwrap().clone(), order2); + } + + #[test] + fn should_handle_same_transaction_imported_twice_with_different_state_nonces() { + // given + let mut txq = TransactionQueue::new(); + let (tx, tx2) = new_similar_txs(); + let prev_nonce = |a: &Address| AccountDetails{ nonce: default_nonce(a).nonce - U256::one(), balance: + !U256::zero() }; + + // First insert one transaction to future + let res = txq.add(tx, &prev_nonce); + assert!(res.is_ok()); + assert_eq!(txq.status().future, 1); + + // now import second transaction to current + let res = txq.add(tx2.clone(), &default_nonce); + + // and then there should be only one transaction in current (the one with higher gas_price) + assert!(res.is_ok()); + assert_eq!(txq.status().pending, 1); + assert_eq!(txq.status().future, 0); + assert_eq!(txq.current.by_priority.len(), 1); + assert_eq!(txq.current.by_address.len(), 1); + assert_eq!(txq.top_transactions()[0], tx2); + } + #[test] fn should_import_tx() {