Merge branch 'master' into finduncles
This commit is contained in:
commit
d63e535b3c
11
Cargo.lock
generated
11
Cargo.lock
generated
@ -81,6 +81,15 @@ name = "cfg-if"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.2.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"num 0.1.31 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"time 0.1.34 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clippy"
|
||||
version = "0.0.44"
|
||||
@ -236,6 +245,7 @@ version = "0.9.99"
|
||||
dependencies = [
|
||||
"arrayvec 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"bigint 0.1.0",
|
||||
"chrono 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"clippy 0.0.44 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"crossbeam 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"elastic-array 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@ -275,6 +285,7 @@ dependencies = [
|
||||
"heapsize 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rustc-serialize 0.3.18 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"time 0.1.34 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
|
@ -16,6 +16,7 @@
|
||||
|
||||
//! Blockchain database client.
|
||||
|
||||
use std::marker::PhantomData;
|
||||
use util::*;
|
||||
use util::panics::*;
|
||||
use blockchain::{BlockChain, BlockProvider};
|
||||
@ -35,6 +36,7 @@ use transaction::LocalizedTransaction;
|
||||
use extras::TransactionAddress;
|
||||
use filter::Filter;
|
||||
use log_entry::LocalizedLogEntry;
|
||||
use util::keys::store::SecretStore;
|
||||
pub use block_queue::{BlockQueueConfig, BlockQueueInfo};
|
||||
pub use blockchain::{TreeRoute, BlockChainConfig, CacheSize as BlockChainCacheSize};
|
||||
|
||||
@ -188,7 +190,7 @@ impl ClientReport {
|
||||
|
||||
/// Blockchain database client backed by a persistent database. Owns and manages a blockchain and a block queue.
|
||||
/// Call `import_block()` to import a block asynchronously; `flush_queue()` flushes the queue.
|
||||
pub struct Client {
|
||||
pub struct Client<V = CanonVerifier> where V: Verifier {
|
||||
chain: Arc<RwLock<BlockChain>>,
|
||||
engine: Arc<Box<Engine>>,
|
||||
state_db: Mutex<JournalDB>,
|
||||
@ -201,14 +203,23 @@ pub struct Client {
|
||||
sealing_block: Mutex<Option<ClosedBlock>>,
|
||||
author: RwLock<Address>,
|
||||
extra_data: RwLock<Bytes>,
|
||||
verifier: PhantomData<V>,
|
||||
secret_store: Arc<RwLock<SecretStore>>,
|
||||
}
|
||||
|
||||
const HISTORY: u64 = 30;
|
||||
const CLIENT_DB_VER_STR: &'static str = "4.0";
|
||||
|
||||
impl Client {
|
||||
impl Client<CanonVerifier> {
|
||||
/// Create a new client with given spec and DB path.
|
||||
pub fn new(config: ClientConfig, spec: Spec, path: &Path, message_channel: IoChannel<NetSyncMessage> ) -> Result<Arc<Client>, Error> {
|
||||
Client::<CanonVerifier>::new_with_verifier(config, spec, path, message_channel)
|
||||
}
|
||||
}
|
||||
|
||||
impl<V> Client<V> where V: Verifier {
|
||||
/// Create a new client with given spec and DB path and custom verifier.
|
||||
pub fn new_with_verifier(config: ClientConfig, spec: Spec, path: &Path, message_channel: IoChannel<NetSyncMessage> ) -> Result<Arc<Client>, Error> {
|
||||
let mut dir = path.to_path_buf();
|
||||
dir.push(H64::from(spec.genesis_header().hash()).hex());
|
||||
//TODO: sec/fat: pruned/full versioning
|
||||
@ -229,6 +240,9 @@ impl Client {
|
||||
let panic_handler = PanicHandler::new_in_arc();
|
||||
panic_handler.forward_from(&block_queue);
|
||||
|
||||
let secret_store = Arc::new(RwLock::new(SecretStore::new()));
|
||||
secret_store.write().unwrap().try_import_existing();
|
||||
|
||||
Ok(Arc::new(Client {
|
||||
chain: chain,
|
||||
engine: engine,
|
||||
@ -240,6 +254,8 @@ impl Client {
|
||||
sealing_block: Mutex::new(None),
|
||||
author: RwLock::new(Address::new()),
|
||||
extra_data: RwLock::new(Vec::new()),
|
||||
verifier: PhantomData,
|
||||
secret_store: secret_store,
|
||||
}))
|
||||
}
|
||||
|
||||
@ -264,6 +280,11 @@ impl Client {
|
||||
last_hashes
|
||||
}
|
||||
|
||||
/// Secret store (key manager)
|
||||
pub fn secret_store(&self) -> &Arc<RwLock<SecretStore>> {
|
||||
&self.secret_store
|
||||
}
|
||||
|
||||
fn check_and_close_block(&self, block: &PreverifiedBlock) -> Result<ClosedBlock, ()> {
|
||||
let engine = self.engine.deref().deref();
|
||||
let header = &block.header;
|
||||
@ -302,7 +323,7 @@ impl Client {
|
||||
|
||||
// Final Verification
|
||||
let closed_block = enact_result.unwrap();
|
||||
if let Err(e) = verify_block_final(&header, closed_block.block().header()) {
|
||||
if let Err(e) = V::verify_block_final(&header, closed_block.block().header()) {
|
||||
warn!(target: "client", "Stage 4 block verification failed for #{} ({})\nError: {:?}", header.number(), header.hash(), e);
|
||||
return Err(());
|
||||
}
|
||||
@ -503,7 +524,7 @@ impl Client {
|
||||
|
||||
// TODO: need MinerService MinerIoHandler
|
||||
|
||||
impl BlockChainClient for Client {
|
||||
impl<V> BlockChainClient for Client<V> where V: Verifier {
|
||||
fn block_header(&self, id: BlockId) -> Option<Bytes> {
|
||||
let chain = self.chain.read().unwrap();
|
||||
Self::block_hash(&chain, id).and_then(|hash| chain.block(&hash).map(|bytes| BlockView::new(&bytes).rlp().at(0).as_raw().to_vec()))
|
||||
|
28
ethcore/src/verification/canon_verifier.rs
Normal file
28
ethcore/src/verification/canon_verifier.rs
Normal file
@ -0,0 +1,28 @@
|
||||
// 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
use error::Error;
|
||||
use header::Header;
|
||||
use super::Verifier;
|
||||
use super::verification;
|
||||
|
||||
pub struct CanonVerifier;
|
||||
|
||||
impl Verifier for CanonVerifier {
|
||||
fn verify_block_final(expected: &Header, got: &Header) -> Result<(), Error> {
|
||||
verification::verify_block_final(expected, got)
|
||||
}
|
||||
}
|
25
ethcore/src/verification/mod.rs
Normal file
25
ethcore/src/verification/mod.rs
Normal file
@ -0,0 +1,25 @@
|
||||
// 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
pub mod verification;
|
||||
pub mod verifier;
|
||||
mod canon_verifier;
|
||||
mod noop_verifier;
|
||||
|
||||
pub use self::verification::*;
|
||||
pub use self::verifier::Verifier;
|
||||
pub use self::canon_verifier::CanonVerifier;
|
||||
pub use self::noop_verifier::NoopVerifier;
|
27
ethcore/src/verification/noop_verifier.rs
Normal file
27
ethcore/src/verification/noop_verifier.rs
Normal file
@ -0,0 +1,27 @@
|
||||
// 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
use error::Error;
|
||||
use header::Header;
|
||||
use super::Verifier;
|
||||
|
||||
pub struct NoopVerifier;
|
||||
|
||||
impl Verifier for NoopVerifier {
|
||||
fn verify_block_final(_expected: &Header, _got: &Header) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
@ -61,7 +61,7 @@ pub fn verify_block_unordered(header: Header, bytes: Bytes, engine: &Engine) ->
|
||||
for u in Rlp::new(&bytes).at(2).iter().map(|rlp| rlp.as_val::<Header>()) {
|
||||
try!(engine.verify_block_unordered(&u, None));
|
||||
}
|
||||
// Verify transactions.
|
||||
// Verify transactions.
|
||||
let mut transactions = Vec::new();
|
||||
{
|
||||
let v = BlockView::new(&bytes);
|
||||
@ -141,7 +141,7 @@ pub fn verify_block_family<BC>(header: &Header, bytes: &[u8], engine: &Engine, b
|
||||
let uncle_parent = try!(bc.block_header(&uncle.parent_hash).ok_or_else(|| Error::from(BlockError::UnknownUncleParent(uncle.parent_hash.clone()))));
|
||||
for _ in 0..depth {
|
||||
match bc.block_details(&expected_uncle_parent) {
|
||||
Some(details) => {
|
||||
Some(details) => {
|
||||
expected_uncle_parent = details.parent;
|
||||
},
|
||||
None => break
|
||||
@ -468,7 +468,7 @@ mod tests {
|
||||
header.number = 9;
|
||||
check_fail(family_test(&create_test_block_with_data(&header, &good_transactions, &good_uncles), engine.deref(), &bc),
|
||||
InvalidNumber(Mismatch { expected: parent.number + 1, found: header.number }));
|
||||
|
||||
|
||||
header = good.clone();
|
||||
let mut bad_uncles = good_uncles.clone();
|
||||
bad_uncles.push(good_uncle1.clone());
|
23
ethcore/src/verification/verifier.rs
Normal file
23
ethcore/src/verification/verifier.rs
Normal file
@ -0,0 +1,23 @@
|
||||
// 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
use error::Error;
|
||||
use header::Header;
|
||||
|
||||
/// Should be used to verify blocks.
|
||||
pub trait Verifier: Send + Sync {
|
||||
fn verify_block_final(expected: &Header, got: &Header) -> Result<(), Error>;
|
||||
}
|
@ -28,7 +28,9 @@ macro_rules! take_weak {
|
||||
mod web3;
|
||||
mod eth;
|
||||
mod net;
|
||||
mod personal;
|
||||
|
||||
pub use self::web3::Web3Client;
|
||||
pub use self::eth::{EthClient, EthFilterClient};
|
||||
pub use self::net::NetClient;
|
||||
pub use self::personal::PersonalClient;
|
||||
|
78
rpc/src/v1/impls/personal.rs
Normal file
78
rpc/src/v1/impls/personal.rs
Normal file
@ -0,0 +1,78 @@
|
||||
// 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Account management (personal) rpc implementation
|
||||
use std::sync::{Arc, Weak};
|
||||
use jsonrpc_core::*;
|
||||
use v1::traits::Personal;
|
||||
use util::keys::store::*;
|
||||
use util::Address;
|
||||
use std::sync::RwLock;
|
||||
|
||||
/// Account management (personal) rpc implementation.
|
||||
pub struct PersonalClient {
|
||||
secret_store: Weak<RwLock<SecretStore>>,
|
||||
}
|
||||
|
||||
impl PersonalClient {
|
||||
/// Creates new PersonalClient
|
||||
pub fn new(store: &Arc<RwLock<SecretStore>>) -> Self {
|
||||
PersonalClient {
|
||||
secret_store: Arc::downgrade(store),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Personal for PersonalClient {
|
||||
fn accounts(&self, _: Params) -> Result<Value, Error> {
|
||||
let store_wk = take_weak!(self.secret_store);
|
||||
let store = store_wk.read().unwrap();
|
||||
match store.accounts() {
|
||||
Ok(account_list) => {
|
||||
Ok(Value::Array(account_list.iter()
|
||||
.map(|&(account, _)| Value::String(format!("{:?}", account)))
|
||||
.collect::<Vec<Value>>())
|
||||
)
|
||||
}
|
||||
Err(_) => Err(Error::internal_error())
|
||||
}
|
||||
}
|
||||
|
||||
fn new_account(&self, params: Params) -> Result<Value, Error> {
|
||||
from_params::<(String, )>(params).and_then(
|
||||
|(pass, )| {
|
||||
let store_wk = take_weak!(self.secret_store);
|
||||
let mut store = store_wk.write().unwrap();
|
||||
match store.new_account(&pass) {
|
||||
Ok(address) => Ok(Value::String(format!("{:?}", address))),
|
||||
Err(_) => Err(Error::internal_error())
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
fn unlock_account(&self, params: Params) -> Result<Value, Error> {
|
||||
from_params::<(Address, String, u64)>(params).and_then(
|
||||
|(account, account_pass, _)|{
|
||||
let store_wk = take_weak!(self.secret_store);
|
||||
let store = store_wk.read().unwrap();
|
||||
match store.unlock_account(&account, &account_pass) {
|
||||
Ok(_) => Ok(Value::Bool(true)),
|
||||
Err(_) => Ok(Value::Bool(false)),
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -15,7 +15,7 @@
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Ethcore rpc v1.
|
||||
//!
|
||||
//!
|
||||
//! Compliant with ethereum rpc.
|
||||
|
||||
pub mod traits;
|
||||
@ -25,5 +25,5 @@ mod types;
|
||||
mod tests;
|
||||
mod helpers;
|
||||
|
||||
pub use self::traits::{Web3, Eth, EthFilter, Net};
|
||||
pub use self::traits::{Web3, Eth, EthFilter, Personal, Net};
|
||||
pub use self::impls::*;
|
||||
|
@ -23,7 +23,9 @@ macro_rules! rpc_unimplemented {
|
||||
pub mod web3;
|
||||
pub mod eth;
|
||||
pub mod net;
|
||||
pub mod personal;
|
||||
|
||||
pub use self::web3::Web3;
|
||||
pub use self::eth::{Eth, EthFilter};
|
||||
pub use self::net::Net;
|
||||
pub use self::personal::Personal;
|
||||
|
41
rpc/src/v1/traits/personal.rs
Normal file
41
rpc/src/v1/traits/personal.rs
Normal file
@ -0,0 +1,41 @@
|
||||
// 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Personal rpc interface.
|
||||
use std::sync::Arc;
|
||||
use jsonrpc_core::*;
|
||||
|
||||
/// Personal rpc interface.
|
||||
pub trait Personal: Sized + Send + Sync + 'static {
|
||||
|
||||
/// Lists all stored accounts
|
||||
fn accounts(&self, _: Params) -> Result<Value, Error> { rpc_unimplemented!() }
|
||||
|
||||
/// Creates new account (it becomes new current unlocked account)
|
||||
fn new_account(&self, _: Params) -> Result<Value, Error> { rpc_unimplemented!() }
|
||||
|
||||
/// Unlocks specified account for use (can only be one unlocked account at one moment)
|
||||
fn unlock_account(&self, _: Params) -> Result<Value, Error> { rpc_unimplemented!() }
|
||||
|
||||
/// Should be used to convert object to io delegate.
|
||||
fn to_delegate(self) -> IoDelegate<Self> {
|
||||
let mut delegate = IoDelegate::new(Arc::new(self));
|
||||
delegate.add_method("personal_listAccounts", Personal::accounts);
|
||||
delegate.add_method("personal_newAccount", Personal::new_account);
|
||||
delegate.add_method("personal_unlockAccount", Personal::unlock_account);
|
||||
delegate
|
||||
}
|
||||
}
|
@ -9,13 +9,14 @@ authors = ["Ethcore <admin@ethcore.io"]
|
||||
|
||||
[dependencies]
|
||||
ethcore-util = { path = "../util" }
|
||||
ethcore = { path = ".." }
|
||||
ethcore = { path = "../ethcore" }
|
||||
clippy = { version = "0.0.44", optional = true }
|
||||
log = "0.3"
|
||||
env_logger = "0.3"
|
||||
time = "0.1.34"
|
||||
rand = "0.3.13"
|
||||
heapsize = "0.3"
|
||||
rustc-serialize = "0.3"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
|
@ -70,6 +70,8 @@ use io::NetSyncIo;
|
||||
mod chain;
|
||||
mod io;
|
||||
mod range_collection;
|
||||
// TODO [todr] Made public to suppress dead code warnings
|
||||
pub mod transaction_queue;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
683
sync/src/transaction_queue.rs
Normal file
683
sync/src/transaction_queue.rs
Normal file
@ -0,0 +1,683 @@
|
||||
// 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
// TODO [todr] - own transactions should have higher priority
|
||||
|
||||
//! Transaction Queue
|
||||
|
||||
use std::cmp::{Ordering};
|
||||
use std::collections::{HashMap, BTreeSet};
|
||||
use util::numbers::{Uint, U256};
|
||||
use util::hash::{Address, H256};
|
||||
use util::table::*;
|
||||
use ethcore::transaction::*;
|
||||
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct TransactionOrder {
|
||||
nonce_height: U256,
|
||||
gas_price: U256,
|
||||
hash: H256,
|
||||
}
|
||||
|
||||
impl TransactionOrder {
|
||||
fn for_transaction(tx: &VerifiedTransaction, base_nonce: U256) -> Self {
|
||||
TransactionOrder {
|
||||
nonce_height: tx.nonce() - base_nonce,
|
||||
gas_price: tx.transaction.gas_price,
|
||||
hash: tx.hash(),
|
||||
}
|
||||
}
|
||||
|
||||
fn update_height(mut self, nonce: U256, base_nonce: U256) -> Self {
|
||||
self.nonce_height = nonce - base_nonce;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for TransactionOrder {}
|
||||
impl PartialEq for TransactionOrder {
|
||||
fn eq(&self, other: &TransactionOrder) -> bool {
|
||||
self.cmp(other) == Ordering::Equal
|
||||
}
|
||||
}
|
||||
impl PartialOrd for TransactionOrder {
|
||||
fn partial_cmp(&self, other: &TransactionOrder) -> Option<Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
impl Ord for TransactionOrder {
|
||||
fn cmp(&self, b: &TransactionOrder) -> Ordering {
|
||||
// First check nonce_height
|
||||
if self.nonce_height != b.nonce_height {
|
||||
return self.nonce_height.cmp(&b.nonce_height);
|
||||
}
|
||||
|
||||
// Then compare gas_prices
|
||||
let a_gas = self.gas_price;
|
||||
let b_gas = b.gas_price;
|
||||
if a_gas != b_gas {
|
||||
return a_gas.cmp(&b_gas);
|
||||
}
|
||||
|
||||
// Compare hashes
|
||||
self.hash.cmp(&b.hash)
|
||||
}
|
||||
}
|
||||
|
||||
struct VerifiedTransaction {
|
||||
transaction: SignedTransaction
|
||||
}
|
||||
impl VerifiedTransaction {
|
||||
fn new(transaction: SignedTransaction) -> Self {
|
||||
VerifiedTransaction {
|
||||
transaction: transaction
|
||||
}
|
||||
}
|
||||
|
||||
fn hash(&self) -> H256 {
|
||||
self.transaction.hash()
|
||||
}
|
||||
|
||||
fn nonce(&self) -> U256 {
|
||||
self.transaction.nonce
|
||||
}
|
||||
|
||||
fn sender(&self) -> Address {
|
||||
self.transaction.sender().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
struct TransactionSet {
|
||||
by_priority: BTreeSet<TransactionOrder>,
|
||||
by_address: Table<Address, U256, TransactionOrder>,
|
||||
limit: usize,
|
||||
}
|
||||
|
||||
impl TransactionSet {
|
||||
fn insert(&mut self, sender: Address, nonce: U256, order: TransactionOrder) {
|
||||
self.by_priority.insert(order.clone());
|
||||
self.by_address.insert(sender, nonce, order);
|
||||
}
|
||||
|
||||
fn enforce_limit(&mut self, by_hash: &HashMap<H256, VerifiedTransaction>) {
|
||||
let len = self.by_priority.len();
|
||||
if len <= self.limit {
|
||||
return;
|
||||
}
|
||||
|
||||
let to_drop : Vec<&VerifiedTransaction> = {
|
||||
self.by_priority
|
||||
.iter()
|
||||
.skip(self.limit)
|
||||
.map(|order| by_hash.get(&order.hash).expect("Inconsistency in queue detected."))
|
||||
.collect()
|
||||
};
|
||||
|
||||
for tx in to_drop {
|
||||
self.drop(&tx.sender(), &tx.nonce());
|
||||
}
|
||||
}
|
||||
|
||||
fn drop(&mut self, sender: &Address, nonce: &U256) -> Option<TransactionOrder> {
|
||||
if let Some(tx_order) = self.by_address.remove(sender, nonce) {
|
||||
self.by_priority.remove(&tx_order);
|
||||
return Some(tx_order);
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn clear(&mut self) {
|
||||
self.by_priority.clear();
|
||||
self.by_address.clear();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
/// Current status of the queue
|
||||
pub struct TransactionQueueStatus {
|
||||
/// Number of pending transactions (ready to go to block)
|
||||
pub pending: usize,
|
||||
/// Number of future transactions (waiting for transactions with lower nonces first)
|
||||
pub future: usize,
|
||||
}
|
||||
|
||||
/// TransactionQueue implementation
|
||||
pub struct TransactionQueue {
|
||||
/// Priority queue for transactions that can go to block
|
||||
current: TransactionSet,
|
||||
/// Priority queue for transactions that has been received but are not yet valid to go to block
|
||||
future: TransactionSet,
|
||||
/// All transactions managed by queue indexed by hash
|
||||
by_hash: HashMap<H256, VerifiedTransaction>,
|
||||
/// Last nonce of transaction in current (to quickly check next expected transaction)
|
||||
last_nonces: HashMap<Address, U256>,
|
||||
}
|
||||
|
||||
impl TransactionQueue {
|
||||
/// Creates new instance of this Queue
|
||||
pub fn new() -> Self {
|
||||
Self::with_limits(1024, 1024)
|
||||
}
|
||||
|
||||
/// Create new instance of this Queue with specified limits
|
||||
pub fn with_limits(current_limit: usize, future_limit: usize) -> Self {
|
||||
let current = TransactionSet {
|
||||
by_priority: BTreeSet::new(),
|
||||
by_address: Table::new(),
|
||||
limit: current_limit,
|
||||
};
|
||||
let future = TransactionSet {
|
||||
by_priority: BTreeSet::new(),
|
||||
by_address: Table::new(),
|
||||
limit: future_limit,
|
||||
};
|
||||
|
||||
TransactionQueue {
|
||||
current: current,
|
||||
future: future,
|
||||
by_hash: HashMap::new(),
|
||||
last_nonces: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns current status for this queue
|
||||
pub fn status(&self) -> TransactionQueueStatus {
|
||||
TransactionQueueStatus {
|
||||
pending: self.current.by_priority.len(),
|
||||
future: self.future.by_priority.len(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds all signed transactions to queue to be verified and imported
|
||||
pub fn add_all<T>(&mut self, txs: Vec<SignedTransaction>, fetch_nonce: T)
|
||||
where T: Fn(&Address) -> U256 {
|
||||
for tx in txs.into_iter() {
|
||||
self.add(tx, &fetch_nonce);
|
||||
}
|
||||
}
|
||||
|
||||
/// Add signed transaction to queue to be verified and imported
|
||||
pub fn add<T>(&mut self, tx: SignedTransaction, fetch_nonce: &T)
|
||||
where T: Fn(&Address) -> U256 {
|
||||
self.import_tx(VerifiedTransaction::new(tx), fetch_nonce);
|
||||
}
|
||||
|
||||
/// Removes all transactions identified by hashes given in slice
|
||||
///
|
||||
/// If gap is introduced marks subsequent transactions as future
|
||||
pub fn remove_all<T>(&mut self, txs: &[H256], fetch_nonce: T)
|
||||
where T: Fn(&Address) -> U256 {
|
||||
for tx in txs {
|
||||
self.remove(&tx, &fetch_nonce);
|
||||
}
|
||||
}
|
||||
|
||||
/// Removes transaction identified by hashes from queue.
|
||||
///
|
||||
/// If gap is introduced marks subsequent transactions as future
|
||||
pub fn remove<T>(&mut self, hash: &H256, fetch_nonce: &T)
|
||||
where T: Fn(&Address) -> U256 {
|
||||
let transaction = self.by_hash.remove(hash);
|
||||
if transaction.is_none() {
|
||||
// We don't know this transaction
|
||||
return;
|
||||
}
|
||||
let transaction = transaction.unwrap();
|
||||
let sender = transaction.sender();
|
||||
let nonce = transaction.nonce();
|
||||
|
||||
println!("Removing tx: {:?}", transaction.transaction);
|
||||
// Remove from future
|
||||
self.future.drop(&sender, &nonce);
|
||||
|
||||
// Remove from current
|
||||
let order = self.current.drop(&sender, &nonce);
|
||||
if order.is_none() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Let's remove transactions where tx.nonce < current_nonce
|
||||
// and if there are any future transactions matching current_nonce+1 - move to current
|
||||
let current_nonce = fetch_nonce(&sender);
|
||||
// We will either move transaction to future or remove it completely
|
||||
// so there will be no transactions from this sender in current
|
||||
self.last_nonces.remove(&sender);
|
||||
|
||||
let all_nonces_from_sender = match self.current.by_address.row(&sender) {
|
||||
Some(row_map) => row_map.keys().cloned().collect::<Vec<U256>>(),
|
||||
None => vec![],
|
||||
};
|
||||
|
||||
for k in all_nonces_from_sender {
|
||||
// Goes to future or is removed
|
||||
let order = self.current.drop(&sender, &k).unwrap();
|
||||
if k >= current_nonce {
|
||||
println!("Moving to future: {:?}", order);
|
||||
self.future.insert(sender.clone(), k, order.update_height(k, current_nonce));
|
||||
} else {
|
||||
self.by_hash.remove(&order.hash);
|
||||
}
|
||||
}
|
||||
self.future.enforce_limit(&self.by_hash);
|
||||
|
||||
// And now lets check if there is some chain of transactions in future
|
||||
// that should be placed in current
|
||||
if let Some(new_current_top) = self.move_future_txs(sender.clone(), current_nonce - U256::one(), current_nonce) {
|
||||
self.last_nonces.insert(sender, new_current_top);
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns top transactions from the queue
|
||||
pub fn top_transactions(&self, size: usize) -> Vec<SignedTransaction> {
|
||||
self.current.by_priority
|
||||
.iter()
|
||||
.take(size)
|
||||
.map(|t| self.by_hash.get(&t.hash).expect("Transaction Queue Inconsistency"))
|
||||
.map(|t| t.transaction.clone())
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Removes all elements (in any state) from the queue
|
||||
pub fn clear(&mut self) {
|
||||
self.current.clear();
|
||||
self.future.clear();
|
||||
self.by_hash.clear();
|
||||
self.last_nonces.clear();
|
||||
}
|
||||
|
||||
fn move_future_txs(&mut self, address: Address, current_nonce: U256, first_nonce: U256) -> Option<U256> {
|
||||
println!("Moving from future for: {:?} base: {:?}", current_nonce, first_nonce);
|
||||
let mut current_nonce = current_nonce + U256::one();
|
||||
{
|
||||
let by_nonce = self.future.by_address.row_mut(&address);
|
||||
if let None = by_nonce {
|
||||
return None;
|
||||
}
|
||||
let mut by_nonce = by_nonce.unwrap();
|
||||
while let Some(order) = by_nonce.remove(¤t_nonce) {
|
||||
// remove also from priority and hash
|
||||
self.future.by_priority.remove(&order);
|
||||
// Put to current
|
||||
println!("Moved: {:?}", order);
|
||||
let order = order.update_height(current_nonce.clone(), first_nonce);
|
||||
self.current.insert(address.clone(), current_nonce, order);
|
||||
current_nonce = current_nonce + U256::one();
|
||||
}
|
||||
}
|
||||
self.future.by_address.clear_if_empty(&address);
|
||||
// Returns last inserted nonce
|
||||
Some(current_nonce - U256::one())
|
||||
}
|
||||
|
||||
fn import_tx<T>(&mut self, tx: VerifiedTransaction, fetch_nonce: &T)
|
||||
where T: Fn(&Address) -> U256 {
|
||||
let nonce = tx.nonce();
|
||||
let address = tx.sender();
|
||||
|
||||
let next_nonce = self.last_nonces
|
||||
.get(&address)
|
||||
.cloned()
|
||||
.map_or_else(|| fetch_nonce(&address), |n| n + U256::one());
|
||||
|
||||
println!("Expected next: {:?}, got: {:?}", next_nonce, nonce);
|
||||
// Check height
|
||||
if nonce > next_nonce {
|
||||
let order = TransactionOrder::for_transaction(&tx, next_nonce);
|
||||
// Insert to by_hash
|
||||
self.by_hash.insert(tx.hash(), tx);
|
||||
// We have a gap - put to future
|
||||
self.future.insert(address, nonce, order);
|
||||
self.future.enforce_limit(&self.by_hash);
|
||||
return;
|
||||
} else if next_nonce > nonce {
|
||||
// Droping transaction
|
||||
return;
|
||||
}
|
||||
|
||||
let base_nonce = fetch_nonce(&address);
|
||||
let order = TransactionOrder::for_transaction(&tx, base_nonce);
|
||||
// Insert to by_hash
|
||||
self.by_hash.insert(tx.hash(), tx);
|
||||
|
||||
// Insert to current
|
||||
self.current.insert(address.clone(), nonce, order);
|
||||
// But maybe there are some more items waiting in future?
|
||||
let new_last_nonce = self.move_future_txs(address.clone(), nonce, base_nonce);
|
||||
self.last_nonces.insert(address.clone(), new_last_nonce.unwrap_or(nonce));
|
||||
// Enforce limit
|
||||
self.current.enforce_limit(&self.by_hash);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
extern crate rustc_serialize;
|
||||
use self::rustc_serialize::hex::FromHex;
|
||||
use std::collections::{HashMap, BTreeSet};
|
||||
use util::crypto::KeyPair;
|
||||
use util::numbers::{U256, Uint};
|
||||
use util::hash::{Address};
|
||||
use util::table::*;
|
||||
use ethcore::transaction::*;
|
||||
use super::*;
|
||||
use super::{TransactionSet, TransactionOrder, VerifiedTransaction};
|
||||
|
||||
fn new_unsigned_tx(nonce: U256) -> Transaction {
|
||||
Transaction {
|
||||
action: Action::Create,
|
||||
value: U256::from(100),
|
||||
data: "3331600055".from_hex().unwrap(),
|
||||
gas: U256::from(100_000),
|
||||
gas_price: U256::one(),
|
||||
nonce: nonce
|
||||
}
|
||||
}
|
||||
|
||||
fn new_tx() -> SignedTransaction {
|
||||
let keypair = KeyPair::create().unwrap();
|
||||
new_unsigned_tx(U256::from(123)).sign(&keypair.secret())
|
||||
}
|
||||
|
||||
fn default_nonce(_address: &Address) -> U256 {
|
||||
U256::from(123)
|
||||
}
|
||||
|
||||
fn new_txs(second_nonce: U256) -> (SignedTransaction, SignedTransaction) {
|
||||
let keypair = KeyPair::create().unwrap();
|
||||
let secret = &keypair.secret();
|
||||
let nonce = U256::from(123);
|
||||
let tx = new_unsigned_tx(nonce);
|
||||
let tx2 = new_unsigned_tx(nonce + second_nonce);
|
||||
|
||||
(tx.sign(secret), tx2.sign(secret))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_create_transaction_set() {
|
||||
// given
|
||||
let mut set = TransactionSet {
|
||||
by_priority: BTreeSet::new(),
|
||||
by_address: Table::new(),
|
||||
limit: 1
|
||||
};
|
||||
let (tx1, tx2) = new_txs(U256::from(1));
|
||||
let tx1 = VerifiedTransaction::new(tx1);
|
||||
let tx2 = VerifiedTransaction::new(tx2);
|
||||
let by_hash = {
|
||||
let mut x = HashMap::new();
|
||||
let tx1 = VerifiedTransaction::new(tx1.transaction.clone());
|
||||
let tx2 = VerifiedTransaction::new(tx2.transaction.clone());
|
||||
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());
|
||||
let order2 = TransactionOrder::for_transaction(&tx2, U256::zero());
|
||||
set.insert(tx2.sender(), tx2.nonce(), order2.clone());
|
||||
assert_eq!(set.by_priority.len(), 2);
|
||||
assert_eq!(set.by_address.len(), 2);
|
||||
|
||||
// when
|
||||
set.enforce_limit(&by_hash);
|
||||
|
||||
// then
|
||||
assert_eq!(set.by_priority.len(), 1);
|
||||
assert_eq!(set.by_address.len(), 1);
|
||||
assert_eq!(set.by_priority.iter().next().unwrap().clone(), order1);
|
||||
set.clear();
|
||||
assert_eq!(set.by_priority.len(), 0);
|
||||
assert_eq!(set.by_address.len(), 0);
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn should_import_tx() {
|
||||
// given
|
||||
let mut txq = TransactionQueue::new();
|
||||
let tx = new_tx();
|
||||
|
||||
// when
|
||||
txq.add(tx, &default_nonce);
|
||||
|
||||
// then
|
||||
let stats = txq.status();
|
||||
assert_eq!(stats.pending, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_import_txs_from_same_sender() {
|
||||
// given
|
||||
let mut txq = TransactionQueue::new();
|
||||
|
||||
let (tx, tx2) = new_txs(U256::from(1));
|
||||
|
||||
// when
|
||||
txq.add(tx.clone(), &default_nonce);
|
||||
txq.add(tx2.clone(), &default_nonce);
|
||||
|
||||
// then
|
||||
let top = txq.top_transactions(5);
|
||||
assert_eq!(top[0], tx);
|
||||
assert_eq!(top[1], tx2);
|
||||
assert_eq!(top.len(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_put_transaction_to_futures_if_gap_detected() {
|
||||
// given
|
||||
let mut txq = TransactionQueue::new();
|
||||
|
||||
let (tx, tx2) = new_txs(U256::from(2));
|
||||
|
||||
// when
|
||||
txq.add(tx.clone(), &default_nonce);
|
||||
txq.add(tx2.clone(), &default_nonce);
|
||||
|
||||
// then
|
||||
let stats = txq.status();
|
||||
assert_eq!(stats.pending, 1);
|
||||
assert_eq!(stats.future, 1);
|
||||
let top = txq.top_transactions(5);
|
||||
assert_eq!(top.len(), 1);
|
||||
assert_eq!(top[0], tx);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_move_transactions_if_gap_filled() {
|
||||
// given
|
||||
let mut txq = TransactionQueue::new();
|
||||
let kp = KeyPair::create().unwrap();
|
||||
let secret = kp.secret();
|
||||
let tx = new_unsigned_tx(U256::from(123)).sign(&secret);
|
||||
let tx1 = new_unsigned_tx(U256::from(124)).sign(&secret);
|
||||
let tx2 = new_unsigned_tx(U256::from(125)).sign(&secret);
|
||||
|
||||
txq.add(tx, &default_nonce);
|
||||
assert_eq!(txq.status().pending, 1);
|
||||
txq.add(tx2, &default_nonce);
|
||||
assert_eq!(txq.status().future, 1);
|
||||
|
||||
// when
|
||||
txq.add(tx1, &default_nonce);
|
||||
|
||||
// then
|
||||
let stats = txq.status();
|
||||
assert_eq!(stats.pending, 3);
|
||||
assert_eq!(stats.future, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_remove_transaction() {
|
||||
// given
|
||||
let mut txq2 = TransactionQueue::new();
|
||||
let (tx, tx2) = new_txs(U256::from(3));
|
||||
txq2.add(tx.clone(), &default_nonce);
|
||||
txq2.add(tx2.clone(), &default_nonce);
|
||||
assert_eq!(txq2.status().pending, 1);
|
||||
assert_eq!(txq2.status().future, 1);
|
||||
|
||||
// when
|
||||
txq2.remove(&tx.hash(), &default_nonce);
|
||||
txq2.remove(&tx2.hash(), &default_nonce);
|
||||
|
||||
|
||||
// then
|
||||
let stats = txq2.status();
|
||||
assert_eq!(stats.pending, 0);
|
||||
assert_eq!(stats.future, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_move_transactions_to_future_if_gap_introduced() {
|
||||
// given
|
||||
let mut txq = TransactionQueue::new();
|
||||
let (tx, tx2) = new_txs(U256::from(1));
|
||||
let tx3 = new_tx();
|
||||
txq.add(tx2.clone(), &default_nonce);
|
||||
assert_eq!(txq.status().future, 1);
|
||||
txq.add(tx3.clone(), &default_nonce);
|
||||
txq.add(tx.clone(), &default_nonce);
|
||||
assert_eq!(txq.status().pending, 3);
|
||||
|
||||
// when
|
||||
txq.remove(&tx.hash(), &default_nonce);
|
||||
|
||||
// then
|
||||
let stats = txq.status();
|
||||
assert_eq!(stats.future, 1);
|
||||
assert_eq!(stats.pending, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_clear_queue() {
|
||||
// given
|
||||
let mut txq = TransactionQueue::new();
|
||||
let (tx, tx2) = new_txs(U256::one());
|
||||
|
||||
// add
|
||||
txq.add(tx2.clone(), &default_nonce);
|
||||
txq.add(tx.clone(), &default_nonce);
|
||||
let stats = txq.status();
|
||||
assert_eq!(stats.pending, 2);
|
||||
|
||||
// when
|
||||
txq.clear();
|
||||
|
||||
// then
|
||||
let stats = txq.status();
|
||||
assert_eq!(stats.pending, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_drop_old_transactions_when_hitting_the_limit() {
|
||||
// given
|
||||
let mut txq = TransactionQueue::with_limits(1, 1);
|
||||
let (tx, tx2) = new_txs(U256::one());
|
||||
txq.add(tx.clone(), &default_nonce);
|
||||
assert_eq!(txq.status().pending, 1);
|
||||
|
||||
// when
|
||||
txq.add(tx2.clone(), &default_nonce);
|
||||
|
||||
// then
|
||||
let t = txq.top_transactions(2);
|
||||
assert_eq!(txq.status().pending, 1);
|
||||
assert_eq!(t.len(), 1);
|
||||
assert_eq!(t[0], tx);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_limit_future_transactions() {
|
||||
let mut txq = TransactionQueue::with_limits(10, 1);
|
||||
let (tx1, tx2) = new_txs(U256::from(4));
|
||||
let (tx3, tx4) = new_txs(U256::from(4));
|
||||
txq.add(tx1.clone(), &default_nonce);
|
||||
txq.add(tx3.clone(), &default_nonce);
|
||||
assert_eq!(txq.status().pending, 2);
|
||||
|
||||
// when
|
||||
txq.add(tx2.clone(), &default_nonce);
|
||||
assert_eq!(txq.status().future, 1);
|
||||
txq.add(tx4.clone(), &default_nonce);
|
||||
|
||||
// then
|
||||
assert_eq!(txq.status().future, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_drop_transactions_with_old_nonces() {
|
||||
let mut txq = TransactionQueue::new();
|
||||
let tx = new_tx();
|
||||
let last_nonce = tx.nonce.clone() + U256::one();
|
||||
let fetch_last_nonce = |_a: &Address| last_nonce;
|
||||
|
||||
// when
|
||||
txq.add(tx, &fetch_last_nonce);
|
||||
|
||||
// then
|
||||
let stats = txq.status();
|
||||
assert_eq!(stats.pending, 0);
|
||||
assert_eq!(stats.future, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_accept_same_transaction_twice() {
|
||||
// given
|
||||
let mut txq = TransactionQueue::new();
|
||||
let (tx1, tx2) = new_txs(U256::from(1));
|
||||
txq.add(tx1.clone(), &default_nonce);
|
||||
txq.add(tx2.clone(), &default_nonce);
|
||||
assert_eq!(txq.status().pending, 2);
|
||||
|
||||
// when
|
||||
txq.remove(&tx1.hash(), &default_nonce);
|
||||
assert_eq!(txq.status().pending, 0);
|
||||
assert_eq!(txq.status().future, 1);
|
||||
txq.add(tx1.clone(), &default_nonce);
|
||||
|
||||
// then
|
||||
let stats = txq.status();
|
||||
assert_eq!(stats.future, 0);
|
||||
assert_eq!(stats.pending, 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_not_move_to_future_if_state_nonce_is_higher() {
|
||||
// given
|
||||
let next_nonce = |a: &Address| default_nonce(a) + U256::one();
|
||||
let mut txq = TransactionQueue::new();
|
||||
let (tx, tx2) = new_txs(U256::from(1));
|
||||
let tx3 = new_tx();
|
||||
txq.add(tx2.clone(), &default_nonce);
|
||||
assert_eq!(txq.status().future, 1);
|
||||
txq.add(tx3.clone(), &default_nonce);
|
||||
txq.add(tx.clone(), &default_nonce);
|
||||
assert_eq!(txq.status().pending, 3);
|
||||
|
||||
// when
|
||||
txq.remove(&tx.hash(), &next_nonce);
|
||||
|
||||
// then
|
||||
let stats = txq.status();
|
||||
assert_eq!(stats.future, 0);
|
||||
assert_eq!(stats.pending, 2);
|
||||
}
|
||||
|
||||
}
|
@ -36,6 +36,7 @@ libc = "0.2.7"
|
||||
vergen = "0.1"
|
||||
target_info = "0.1"
|
||||
bigint = { path = "bigint" }
|
||||
chrono = "0.2"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
|
@ -778,6 +778,35 @@ macro_rules! construct_uint {
|
||||
}
|
||||
}
|
||||
|
||||
impl serde::Deserialize for $name {
|
||||
fn deserialize<D>(deserializer: &mut D) -> Result<$name, D::Error>
|
||||
where D: serde::Deserializer {
|
||||
struct UintVisitor;
|
||||
|
||||
impl serde::de::Visitor for UintVisitor {
|
||||
type Value = $name;
|
||||
|
||||
fn visit_str<E>(&mut self, value: &str) -> Result<Self::Value, E> where E: serde::Error {
|
||||
// 0x + len
|
||||
if value.len() != 2 + $n_words / 8 {
|
||||
return Err(serde::Error::custom("Invalid length."));
|
||||
}
|
||||
|
||||
match $name::from_str(&value[2..]) {
|
||||
Ok(val) => Ok(val),
|
||||
Err(_) => { return Err(serde::Error::custom("Invalid length.")); }
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_string<E>(&mut self, value: String) -> Result<Self::Value, E> where E: serde::Error {
|
||||
self.visit_str(value.as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
deserializer.deserialize(UintVisitor)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u64> for $name {
|
||||
fn from(value: u64) -> $name {
|
||||
let mut ret = [0; $n_words];
|
||||
|
@ -461,17 +461,17 @@ enum KeyFileLoadError {
|
||||
pub struct KeyDirectory {
|
||||
/// Directory path for key management.
|
||||
path: String,
|
||||
cache: RefCell<HashMap<Uuid, KeyFileContent>>,
|
||||
cache_usage: RefCell<VecDeque<Uuid>>,
|
||||
cache: RwLock<HashMap<Uuid, KeyFileContent>>,
|
||||
cache_usage: RwLock<VecDeque<Uuid>>,
|
||||
}
|
||||
|
||||
impl KeyDirectory {
|
||||
/// Initializes new cache directory context with a given `path`
|
||||
pub fn new(path: &Path) -> KeyDirectory {
|
||||
KeyDirectory {
|
||||
cache: RefCell::new(HashMap::new()),
|
||||
cache: RwLock::new(HashMap::new()),
|
||||
path: path.to_str().expect("Initialized key directory with empty path").to_owned(),
|
||||
cache_usage: RefCell::new(VecDeque::new()),
|
||||
cache_usage: RwLock::new(VecDeque::new()),
|
||||
}
|
||||
}
|
||||
|
||||
@ -484,7 +484,7 @@ impl KeyDirectory {
|
||||
let json_bytes = json_text.into_bytes();
|
||||
try!(file.write(&json_bytes));
|
||||
}
|
||||
let mut cache = self.cache.borrow_mut();
|
||||
let mut cache = self.cache.write().unwrap();
|
||||
let id = key_file.id.clone();
|
||||
cache.insert(id.clone(), key_file);
|
||||
Ok(id.clone())
|
||||
@ -495,14 +495,14 @@ impl KeyDirectory {
|
||||
pub fn get(&self, id: &Uuid) -> Option<KeyFileContent> {
|
||||
let path = self.key_path(id);
|
||||
{
|
||||
let mut usage = self.cache_usage.borrow_mut();
|
||||
let mut usage = self.cache_usage.write().unwrap();
|
||||
usage.push_back(id.clone());
|
||||
}
|
||||
|
||||
if !self.cache.borrow().contains_key(id) {
|
||||
if !self.cache.read().unwrap().contains_key(id) {
|
||||
match KeyDirectory::load_key(&path) {
|
||||
Ok(loaded_key) => {
|
||||
self.cache.borrow_mut().insert(id.to_owned(), loaded_key);
|
||||
self.cache.write().unwrap().insert(id.to_owned(), loaded_key);
|
||||
}
|
||||
Err(error) => {
|
||||
warn!(target: "sstore", "error loading key {:?}: {:?}", id, error);
|
||||
@ -512,7 +512,7 @@ impl KeyDirectory {
|
||||
}
|
||||
|
||||
// todo: replace with Ref::map when it stabilized to avoid copies
|
||||
Some(self.cache.borrow().get(id)
|
||||
Some(self.cache.read().unwrap().get(id)
|
||||
.expect("Key should be there, we have just inserted or checked it.")
|
||||
.clone())
|
||||
}
|
||||
@ -524,7 +524,7 @@ impl KeyDirectory {
|
||||
|
||||
/// Removes keys that never been requested during last `MAX_USAGE_TRACK` times
|
||||
pub fn collect_garbage(&mut self) {
|
||||
let mut cache_usage = self.cache_usage.borrow_mut();
|
||||
let mut cache_usage = self.cache_usage.write().unwrap();
|
||||
|
||||
let total_usages = cache_usage.len();
|
||||
let untracked_usages = max(total_usages as i64 - MAX_CACHE_USAGE_TRACK as i64, 0) as usize;
|
||||
@ -532,31 +532,31 @@ impl KeyDirectory {
|
||||
cache_usage.drain(..untracked_usages);
|
||||
}
|
||||
|
||||
if self.cache.borrow().len() <= MAX_CACHE_USAGE_TRACK { return; }
|
||||
if self.cache.read().unwrap().len() <= MAX_CACHE_USAGE_TRACK { return; }
|
||||
|
||||
let uniqs: HashSet<&Uuid> = cache_usage.iter().collect();
|
||||
let removes:Vec<Uuid> = {
|
||||
let cache = self.cache.borrow();
|
||||
let cache = self.cache.read().unwrap();
|
||||
cache.keys().cloned().filter(|key| !uniqs.contains(key)).collect()
|
||||
};
|
||||
if removes.is_empty() { return; }
|
||||
let mut cache = self.cache.borrow_mut();
|
||||
let mut cache = self.cache.write().unwrap();
|
||||
for key in removes { cache.remove(&key); }
|
||||
}
|
||||
|
||||
/// Reports how many keys are currently cached.
|
||||
pub fn cache_size(&self) -> usize {
|
||||
self.cache.borrow().len()
|
||||
self.cache.read().unwrap().len()
|
||||
}
|
||||
|
||||
/// Removes key file from key directory
|
||||
pub fn delete(&mut self, id: &Uuid) -> Result<(), ::std::io::Error> {
|
||||
let path = self.key_path(id);
|
||||
|
||||
if !self.cache.borrow().contains_key(id) {
|
||||
if !self.cache.read().unwrap().contains_key(id) {
|
||||
return match fs::remove_file(&path) {
|
||||
Ok(_) => {
|
||||
self.cache.borrow_mut().remove(&id);
|
||||
self.cache.write().unwrap().remove(&id);
|
||||
Ok(())
|
||||
},
|
||||
Err(e) => Err(e)
|
||||
|
@ -22,6 +22,7 @@ use rcrypto::pbkdf2::*;
|
||||
use rcrypto::scrypt::*;
|
||||
use rcrypto::hmac::*;
|
||||
use crypto;
|
||||
use chrono::*;
|
||||
|
||||
const KEY_LENGTH: u32 = 32;
|
||||
const KEY_ITERATIONS: u32 = 10240;
|
||||
@ -55,9 +56,26 @@ pub enum EncryptedHashMapError {
|
||||
InvalidValueFormat(FromBytesError),
|
||||
}
|
||||
|
||||
/// Error retrieving value from encrypted hashmap
|
||||
#[derive(Debug)]
|
||||
pub enum SigningError {
|
||||
/// Account passed does not exist
|
||||
NoAccount,
|
||||
/// Account passed is not unlocked
|
||||
AccountNotUnlocked,
|
||||
/// Invalid secret in store
|
||||
InvalidSecret
|
||||
}
|
||||
|
||||
/// Represent service for storing encrypted arbitrary data
|
||||
pub struct SecretStore {
|
||||
directory: KeyDirectory
|
||||
directory: KeyDirectory,
|
||||
unlocks: RwLock<HashMap<Address, AccountUnlock>>,
|
||||
}
|
||||
|
||||
struct AccountUnlock {
|
||||
secret: H256,
|
||||
expires: DateTime<UTC>,
|
||||
}
|
||||
|
||||
impl SecretStore {
|
||||
@ -72,7 +90,8 @@ impl SecretStore {
|
||||
/// new instance of Secret Store in specific directory
|
||||
pub fn new_in(path: &Path) -> SecretStore {
|
||||
SecretStore {
|
||||
directory: KeyDirectory::new(path)
|
||||
directory: KeyDirectory::new(path),
|
||||
unlocks: RwLock::new(HashMap::new()),
|
||||
}
|
||||
}
|
||||
|
||||
@ -120,9 +139,57 @@ impl SecretStore {
|
||||
#[cfg(test)]
|
||||
fn new_test(path: &::devtools::RandomTempPath) -> SecretStore {
|
||||
SecretStore {
|
||||
directory: KeyDirectory::new(path.as_path())
|
||||
directory: KeyDirectory::new(path.as_path()),
|
||||
unlocks: RwLock::new(HashMap::new()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Unlocks account for use
|
||||
pub fn unlock_account(&self, account: &Address, pass: &str) -> Result<(), EncryptedHashMapError> {
|
||||
let secret_id = try!(self.account(&account).ok_or(EncryptedHashMapError::UnknownIdentifier));
|
||||
let secret = try!(self.get(&secret_id, pass));
|
||||
{
|
||||
let mut write_lock = self.unlocks.write().unwrap();
|
||||
let mut unlock = write_lock.entry(*account)
|
||||
.or_insert_with(|| AccountUnlock { secret: secret, expires: UTC::now() });
|
||||
unlock.secret = secret;
|
||||
unlock.expires = UTC::now() + Duration::minutes(20);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Creates new account
|
||||
pub fn new_account(&mut self, pass: &str) -> Result<Address, ::std::io::Error> {
|
||||
let secret = H256::random();
|
||||
let key_id = H128::random();
|
||||
self.insert(key_id.clone(), secret, pass);
|
||||
|
||||
let mut key_file = self.directory.get(&key_id).expect("the key was just inserted");
|
||||
let address = Address::random();
|
||||
key_file.account = Some(address);
|
||||
try!(self.directory.save(key_file));
|
||||
Ok(address)
|
||||
}
|
||||
|
||||
/// Signs message with unlocked account
|
||||
pub fn sign(&self, account: &Address, message: &H256) -> Result<crypto::Signature, SigningError> {
|
||||
let read_lock = self.unlocks.read().unwrap();
|
||||
let unlock = try!(read_lock.get(account).ok_or(SigningError::AccountNotUnlocked));
|
||||
match crypto::KeyPair::from_secret(unlock.secret) {
|
||||
Ok(pair) => match pair.sign(message) {
|
||||
Ok(signature) => Ok(signature),
|
||||
Err(_) => Err(SigningError::InvalidSecret)
|
||||
},
|
||||
Err(_) => Err(SigningError::InvalidSecret)
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns secret for unlocked account
|
||||
pub fn account_secret(&self, account: &Address) -> Result<crypto::Secret, SigningError> {
|
||||
let read_lock = self.unlocks.read().unwrap();
|
||||
let unlock = try!(read_lock.get(account).ok_or(SigningError::AccountNotUnlocked));
|
||||
Ok(unlock.secret as crypto::Secret)
|
||||
}
|
||||
}
|
||||
|
||||
fn derive_key_iterations(password: &str, salt: &H256, c: u32) -> (Bytes, Bytes) {
|
||||
@ -369,6 +436,40 @@ mod tests {
|
||||
assert_eq!(4, sstore.directory.list().unwrap().len())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_create_account() {
|
||||
let temp = RandomTempPath::create_dir();
|
||||
let mut sstore = SecretStore::new_test(&temp);
|
||||
sstore.new_account("123").unwrap();
|
||||
assert_eq!(1, sstore.accounts().unwrap().len());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_unlock_account() {
|
||||
let temp = RandomTempPath::create_dir();
|
||||
let mut sstore = SecretStore::new_test(&temp);
|
||||
let address = sstore.new_account("123").unwrap();
|
||||
|
||||
let secret = sstore.unlock_account(&address, "123");
|
||||
assert!(secret.is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_sign_data() {
|
||||
let temp = RandomTempPath::create_dir();
|
||||
let address = {
|
||||
let mut sstore = SecretStore::new_test(&temp);
|
||||
sstore.new_account("334").unwrap()
|
||||
};
|
||||
let signature = {
|
||||
let sstore = SecretStore::new_test(&temp);
|
||||
sstore.unlock_account(&address, "334").unwrap();
|
||||
sstore.sign(&address, &H256::random()).unwrap()
|
||||
};
|
||||
|
||||
assert!(signature != x!(0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_import_account() {
|
||||
use keys::directory::{KeyFileContent, KeyFileCrypto};
|
||||
|
@ -111,6 +111,7 @@ extern crate rustc_version;
|
||||
extern crate target_info;
|
||||
extern crate vergen;
|
||||
extern crate bigint;
|
||||
extern crate chrono;
|
||||
|
||||
pub mod standard;
|
||||
#[macro_use]
|
||||
|
Loading…
Reference in New Issue
Block a user