Validating senders balance before importing transaction to queue

This commit is contained in:
Tomasz Drwięga 2016-03-16 10:40:33 +01:00
parent be32e79a7a
commit fdba8de600
7 changed files with 94 additions and 47 deletions

View File

@ -70,7 +70,14 @@ pub enum TransactionError {
/// Minimal expected gas price /// Minimal expected gas price
minimal: U256, minimal: U256,
/// Transaction gas price /// Transaction gas price
got: U256 got: U256,
},
/// Sender doesn't have enough funds to pay for this transaction
InsufficientBalance {
/// Senders balance
balance: U256,
/// Transaction cost
cost: U256,
}, },
/// Transaction's gas limit (aka gas) is invalid. /// Transaction's gas limit (aka gas) is invalid.
InvalidGasLimit(OutOfBounds<U256>), InvalidGasLimit(OutOfBounds<U256>),

View File

@ -62,11 +62,11 @@ extern crate rayon;
mod miner; mod miner;
mod transaction_queue; mod transaction_queue;
pub use transaction_queue::TransactionQueue; pub use transaction_queue::{TransactionQueue, AccountDetails};
pub use miner::{Miner}; pub use miner::{Miner};
use std::sync::Mutex; use std::sync::Mutex;
use util::{H256, U256, Address, Bytes}; use util::{H256, Address, Bytes};
use ethcore::client::{BlockChainClient}; use ethcore::client::{BlockChainClient};
use ethcore::block::{ClosedBlock}; use ethcore::block::{ClosedBlock};
use ethcore::error::{Error}; use ethcore::error::{Error};
@ -79,8 +79,8 @@ pub trait MinerService : Send + Sync {
fn status(&self) -> MinerStatus; fn status(&self) -> MinerStatus;
/// Imports transactions to transaction queue. /// Imports transactions to transaction queue.
fn import_transactions<T>(&self, transactions: Vec<SignedTransaction>, fetch_nonce: T) -> Result<(), Error> fn import_transactions<T>(&self, transactions: Vec<SignedTransaction>, fetch_account: T) -> Result<(), Error>
where T: Fn(&Address) -> U256; where T: Fn(&Address) -> AccountDetails;
/// Returns hashes of transactions currently in pending /// Returns hashes of transactions currently in pending
fn pending_transactions_hashes(&self) -> Vec<H256>; fn pending_transactions_hashes(&self) -> Vec<H256>;

View File

@ -26,7 +26,7 @@ use ethcore::client::{BlockChainClient, BlockId};
use ethcore::block::{ClosedBlock}; use ethcore::block::{ClosedBlock};
use ethcore::error::{Error}; use ethcore::error::{Error};
use ethcore::transaction::SignedTransaction; use ethcore::transaction::SignedTransaction;
use super::{MinerService, MinerStatus, TransactionQueue}; use super::{MinerService, MinerStatus, TransactionQueue, AccountDetails};
/// Keeps track of transactions using priority queue and holds currently mined block. /// Keeps track of transactions using priority queue and holds currently mined block.
pub struct Miner { pub struct Miner {
@ -72,7 +72,7 @@ impl Miner {
/// Get the extra_data that we will seal blocks wuth. /// Get the extra_data that we will seal blocks wuth.
fn gas_floor_target(&self) -> U256 { fn gas_floor_target(&self) -> U256 {
self.gas_floor_target.read().unwrap().clone() *self.gas_floor_target.read().unwrap()
} }
/// Set the author that we will seal blocks as. /// Set the author that we will seal blocks as.
@ -111,10 +111,10 @@ impl MinerService for Miner {
} }
} }
fn import_transactions<T>(&self, transactions: Vec<SignedTransaction>, fetch_nonce: T) -> Result<(), Error> fn import_transactions<T>(&self, transactions: Vec<SignedTransaction>, fetch_account: T) -> Result<(), Error>
where T: Fn(&Address) -> U256 { where T: Fn(&Address) -> AccountDetails {
let mut transaction_queue = self.transaction_queue.lock().unwrap(); let mut transaction_queue = self.transaction_queue.lock().unwrap();
transaction_queue.add_all(transactions, fetch_nonce) transaction_queue.add_all(transactions, fetch_account)
} }
fn pending_transactions_hashes(&self) -> Vec<H256> { fn pending_transactions_hashes(&self) -> Vec<H256> {
@ -185,7 +185,10 @@ impl MinerService for Miner {
let _sender = tx.sender(); let _sender = tx.sender();
} }
let mut transaction_queue = self.transaction_queue.lock().unwrap(); let mut transaction_queue = self.transaction_queue.lock().unwrap();
let _ = transaction_queue.add_all(txs, |a| chain.nonce(a)); let _ = transaction_queue.add_all(txs, |a| AccountDetails {
nonce: chain.nonce(a),
balance: chain.balance(a)
});
}); });
} }
// First import all transactions and after that remove old ones // First import all transactions and after that remove old ones
@ -207,7 +210,10 @@ impl MinerService for Miner {
in_chain.for_each(|txs| { in_chain.for_each(|txs| {
let hashes = txs.iter().map(|tx| tx.hash()).collect::<Vec<H256>>(); let hashes = txs.iter().map(|tx| tx.hash()).collect::<Vec<H256>>();
let mut transaction_queue = self.transaction_queue.lock().unwrap(); let mut transaction_queue = self.transaction_queue.lock().unwrap();
transaction_queue.remove_all(&hashes, |a| chain.nonce(a)); transaction_queue.remove_all(&hashes, |a| AccountDetails {
nonce: chain.nonce(a),
balance: chain.balance(a)
});
}); });
} }

View File

@ -232,8 +232,6 @@ impl TransactionSet {
} }
} }
// Will be used when rpc merged
#[allow(dead_code)]
#[derive(Debug)] #[derive(Debug)]
/// Current status of the queue /// Current status of the queue
pub struct TransactionQueueStatus { pub struct TransactionQueueStatus {
@ -243,6 +241,14 @@ pub struct TransactionQueueStatus {
pub future: usize, pub future: usize,
} }
/// Details of account
pub struct AccountDetails {
/// Most recent account nonce
pub nonce: U256,
/// Current account balance
pub balance: U256,
}
/// TransactionQueue implementation /// TransactionQueue implementation
pub struct TransactionQueue { pub struct TransactionQueue {
/// Gas Price threshold for transactions that can be imported to this queue (defaults to 0) /// Gas Price threshold for transactions that can be imported to this queue (defaults to 0)
@ -308,49 +314,63 @@ impl TransactionQueue {
} }
/// Adds all signed transactions to queue to be verified and imported /// Adds all signed transactions to queue to be verified and imported
pub fn add_all<T>(&mut self, txs: Vec<SignedTransaction>, fetch_nonce: T) -> Result<(), Error> pub fn add_all<T>(&mut self, txs: Vec<SignedTransaction>, fetch_account: T) -> Result<(), Error>
where T: Fn(&Address) -> U256 { where T: Fn(&Address) -> AccountDetails {
for tx in txs.into_iter() { for tx in txs.into_iter() {
try!(self.add(tx, &fetch_nonce)); try!(self.add(tx, &fetch_account));
} }
Ok(()) Ok(())
} }
/// Add signed transaction to queue to be verified and imported /// Add signed transaction to queue to be verified and imported
pub fn add<T>(&mut self, tx: SignedTransaction, fetch_nonce: &T) -> Result<(), Error> pub fn add<T>(&mut self, tx: SignedTransaction, fetch_account: &T) -> Result<(), Error>
where T: Fn(&Address) -> U256 { where T: Fn(&Address) -> AccountDetails {
trace!(target: "miner", "Importing: {:?}", tx.hash());
if tx.gas_price < self.minimal_gas_price { if tx.gas_price < self.minimal_gas_price {
trace!(target: "sync", trace!(target: "miner",
"Dropping transaction below minimal gas price threshold: {:?} (gp: {} < {})", "Dropping transaction below minimal gas price threshold: {:?} (gp: {} < {})",
tx.hash(), tx.gas_price, self.minimal_gas_price tx.hash(), tx.gas_price, self.minimal_gas_price
); );
return Err(Error::Transaction(TransactionError::InsufficientGasPrice{ return Err(Error::Transaction(TransactionError::InsufficientGasPrice{
minimal: self.minimal_gas_price, minimal: self.minimal_gas_price,
got: tx.gas_price got: tx.gas_price,
})); }));
} }
self.import_tx(try!(VerifiedTransaction::new(tx)), fetch_nonce); let vtx = try!(VerifiedTransaction::new(tx));
let account = fetch_account(&vtx.sender());
if account.balance < vtx.transaction.value {
trace!(target: "miner", "Dropping transaction without sufficient balance: {:?} ({} < {})",
vtx.hash(), account.balance, vtx.transaction.value);
return Err(Error::Transaction(TransactionError::InsufficientBalance {
cost: vtx.transaction.value,
balance: account.balance
}));
}
self.import_tx(vtx, account.nonce);
Ok(()) Ok(())
} }
/// Removes all transactions identified by hashes given in slice /// Removes all transactions identified by hashes given in slice
/// ///
/// If gap is introduced marks subsequent transactions as future /// If gap is introduced marks subsequent transactions as future
pub fn remove_all<T>(&mut self, transaction_hashes: &[H256], fetch_nonce: T) pub fn remove_all<T>(&mut self, transaction_hashes: &[H256], fetch_account: T)
where T: Fn(&Address) -> U256 { where T: Fn(&Address) -> AccountDetails {
for hash in transaction_hashes { for hash in transaction_hashes {
self.remove(&hash, &fetch_nonce); self.remove(&hash, &fetch_account);
} }
} }
/// Removes transaction identified by hashes from queue. /// Removes transaction identified by hashes from queue.
/// ///
/// If gap is introduced marks subsequent transactions as future /// If gap is introduced marks subsequent transactions as future
pub fn remove<T>(&mut self, transaction_hash: &H256, fetch_nonce: &T) pub fn remove<T>(&mut self, transaction_hash: &H256, fetch_account: &T)
where T: Fn(&Address) -> U256 { where T: Fn(&Address) -> AccountDetails {
let transaction = self.by_hash.remove(transaction_hash); let transaction = self.by_hash.remove(transaction_hash);
if transaction.is_none() { if transaction.is_none() {
@ -361,7 +381,7 @@ impl TransactionQueue {
let transaction = transaction.unwrap(); let transaction = transaction.unwrap();
let sender = transaction.sender(); let sender = transaction.sender();
let nonce = transaction.nonce(); let nonce = transaction.nonce();
let current_nonce = fetch_nonce(&sender); let current_nonce = fetch_account(&sender).nonce;
// Remove from future // Remove from future
@ -403,6 +423,7 @@ impl TransactionQueue {
if k >= current_nonce { if k >= current_nonce {
self.future.insert(*sender, k, order.update_height(k, current_nonce)); self.future.insert(*sender, k, order.update_height(k, current_nonce));
} else { } else {
trace!(target: "miner", "Dropping old transaction: {:?} (nonce: {} < {})", order.hash, k, current_nonce);
// Remove the transaction completely // Remove the transaction completely
self.by_hash.remove(&order.hash); self.by_hash.remove(&order.hash);
} }
@ -423,6 +444,7 @@ impl TransactionQueue {
if k >= current_nonce { if k >= current_nonce {
self.future.insert(*sender, k, order.update_height(k, current_nonce)); self.future.insert(*sender, k, order.update_height(k, current_nonce));
} else { } else {
trace!(target: "miner", "Dropping old transaction: {:?} (nonce: {} < {})", order.hash, k, current_nonce);
self.by_hash.remove(&order.hash); self.by_hash.remove(&order.hash);
} }
} }
@ -488,13 +510,11 @@ impl TransactionQueue {
/// ///
/// It ignores transactions that has already been imported (same `hash`) and replaces the transaction /// It ignores transactions that has already been imported (same `hash`) and replaces the transaction
/// iff `(address, nonce)` is the same but `gas_price` is higher. /// iff `(address, nonce)` is the same but `gas_price` is higher.
fn import_tx<T>(&mut self, tx: VerifiedTransaction, fetch_nonce: &T) fn import_tx(&mut self, tx: VerifiedTransaction, state_nonce: U256) {
where T: Fn(&Address) -> U256 {
if self.by_hash.get(&tx.hash()).is_some() { if self.by_hash.get(&tx.hash()).is_some() {
// Transaction is already imported. // Transaction is already imported.
trace!(target: "sync", "Dropping already imported transaction with hash: {:?}", tx.hash()); trace!(target: "miner", "Dropping already imported transaction: {:?}", tx.hash());
return; return;
} }
@ -502,7 +522,6 @@ impl TransactionQueue {
let address = tx.sender(); let address = tx.sender();
let nonce = tx.nonce(); let nonce = tx.nonce();
let state_nonce = fetch_nonce(&address);
let next_nonce = self.last_nonces let next_nonce = self.last_nonces
.get(&address) .get(&address)
.cloned() .cloned()
@ -516,7 +535,7 @@ impl TransactionQueue {
return; return;
} else if nonce < state_nonce { } else if nonce < state_nonce {
// Droping transaction // Droping transaction
trace!(target: "sync", "Dropping transaction with nonce: {} - expecting: {}", nonce, next_nonce); trace!(target: "miner", "Dropping old transaction: {:?} (nonce: {} < {})", tx.hash(), nonce, next_nonce);
return; return;
} }
@ -525,6 +544,8 @@ impl TransactionQueue {
// But maybe there are some more items waiting in future? // But maybe there are some more items waiting in future?
self.move_matching_future_to_current(address, nonce + U256::one(), state_nonce); self.move_matching_future_to_current(address, nonce + U256::one(), state_nonce);
self.current.enforce_limit(&mut self.by_hash); self.current.enforce_limit(&mut self.by_hash);
trace!(target: "miner", "status: {:?}", self.status());
} }
/// Replaces transaction in given set (could be `future` or `current`). /// Replaces transaction in given set (could be `future` or `current`).
@ -583,8 +604,11 @@ mod test {
new_unsigned_tx(U256::from(123)).sign(&keypair.secret()) new_unsigned_tx(U256::from(123)).sign(&keypair.secret())
} }
fn default_nonce(_address: &Address) -> U256 { fn default_nonce(_address: &Address) -> AccountDetails {
U256::from(123) AccountDetails {
nonce: U256::from(123),
balance: !U256::zero()
}
} }
fn new_txs(second_nonce: U256) -> (SignedTransaction, SignedTransaction) { fn new_txs(second_nonce: U256) -> (SignedTransaction, SignedTransaction) {
@ -953,7 +977,8 @@ mod test {
#[test] #[test]
fn should_not_move_to_future_if_state_nonce_is_higher() { fn should_not_move_to_future_if_state_nonce_is_higher() {
// given // given
let next_nonce = |a: &Address| default_nonce(a) + U256::one(); let next_nonce = |a: &Address| AccountDetails{ nonce: default_nonce(a).nonce + U256::one(), balance:
!U256::zero() };
let mut txq = TransactionQueue::new(); let mut txq = TransactionQueue::new();
let (tx, tx2) = new_txs(U256::from(1)); let (tx, tx2) = new_txs(U256::from(1));
let tx3 = new_tx(); let tx3 = new_tx();
@ -1028,8 +1053,10 @@ mod test {
#[test] #[test]
fn should_recalculate_height_when_removing_from_future() { fn should_recalculate_height_when_removing_from_future() {
// given // given
let previous_nonce = |a: &Address| default_nonce(a) - U256::one(); let previous_nonce = |a: &Address| AccountDetails{ nonce: default_nonce(a).nonce - U256::one(), balance:
let next_nonce = |a: &Address| default_nonce(a) + U256::one(); !U256::zero() };
let next_nonce = |a: &Address| AccountDetails{ nonce: default_nonce(a).nonce + U256::one(), balance:
!U256::zero() };
let mut txq = TransactionQueue::new(); let mut txq = TransactionQueue::new();
let (tx1, tx2) = new_txs(U256::one()); let (tx1, tx2) = new_txs(U256::one());
txq.add(tx1.clone(), &previous_nonce).unwrap(); txq.add(tx1.clone(), &previous_nonce).unwrap();

View File

@ -19,7 +19,7 @@ use std::collections::HashSet;
use std::sync::{Arc, Weak, Mutex}; use std::sync::{Arc, Weak, Mutex};
use std::ops::Deref; use std::ops::Deref;
use ethsync::{SyncProvider, SyncState}; use ethsync::{SyncProvider, SyncState};
use ethminer::{MinerService}; use ethminer::{MinerService, AccountDetails};
use jsonrpc_core::*; use jsonrpc_core::*;
use util::numbers::*; use util::numbers::*;
use util::sha3::*; use util::sha3::*;
@ -370,7 +370,10 @@ impl<C, S, A, M, EM> Eth for EthClient<C, S, A, M, EM>
let signed_transaction = transaction.sign(&secret); let signed_transaction = transaction.sign(&secret);
let hash = signed_transaction.hash(); let hash = signed_transaction.hash();
let import = miner.import_transactions(vec![signed_transaction], |a: &Address| client.nonce(a)); let import = miner.import_transactions(vec![signed_transaction], |a: &Address| AccountDetails {
nonce: client.nonce(a),
balance: client.balance(a)
});
match import { match import {
Ok(_) => to_value(&hash), Ok(_) => to_value(&hash),
Err(e) => { Err(e) => {

View File

@ -20,7 +20,7 @@ use ethcore::error::Error;
use ethcore::client::BlockChainClient; use ethcore::client::BlockChainClient;
use ethcore::block::ClosedBlock; use ethcore::block::ClosedBlock;
use ethcore::transaction::SignedTransaction; use ethcore::transaction::SignedTransaction;
use ethminer::{MinerService, MinerStatus}; use ethminer::{MinerService, MinerStatus, AccountDetails};
pub struct TestMinerService; pub struct TestMinerService;
@ -30,7 +30,8 @@ impl MinerService for TestMinerService {
fn status(&self) -> MinerStatus { unimplemented!(); } fn status(&self) -> MinerStatus { unimplemented!(); }
/// Imports transactions to transaction queue. /// Imports transactions to transaction queue.
fn import_transactions<T>(&self, _transactions: Vec<SignedTransaction>, _fetch_nonce: T) -> Result<(), Error> where T: Fn(&Address) -> U256 { unimplemented!(); } fn import_transactions<T>(&self, _transactions: Vec<SignedTransaction>, _fetch_account: T) -> Result<(), Error>
where T: Fn(&Address) -> AccountDetails { unimplemented!(); }
/// Returns hashes of transactions currently in pending /// Returns hashes of transactions currently in pending
fn pending_transactions_hashes(&self) -> Vec<H256> { unimplemented!(); } fn pending_transactions_hashes(&self) -> Vec<H256> { unimplemented!(); }

View File

@ -38,7 +38,7 @@ use range_collection::{RangeCollection, ToUsize, FromUsize};
use ethcore::error::*; use ethcore::error::*;
use ethcore::transaction::SignedTransaction; use ethcore::transaction::SignedTransaction;
use ethcore::block::Block; use ethcore::block::Block;
use ethminer::{Miner, MinerService}; use ethminer::{Miner, MinerService, AccountDetails};
use io::SyncIo; use io::SyncIo;
use time; use time;
use super::SyncConfig; use super::SyncConfig;
@ -940,8 +940,11 @@ impl ChainSync {
transactions.push(tx); transactions.push(tx);
} }
let chain = io.chain(); let chain = io.chain();
let fetch_nonce = |a: &Address| chain.nonce(a); let fetch_account = |a: &Address| AccountDetails {
let _ = self.miner.import_transactions(transactions, fetch_nonce); nonce: chain.nonce(a),
balance: chain.balance(a)
};
let _ = self.miner.import_transactions(transactions, fetch_account);
Ok(()) Ok(())
} }