[light] Validate account balance
before importing transactions (#9417)
* `light::verify_transaction` basic tx validation * update wasm tests * Provide `cached_nonce` in `parity_next_nonce` RPC * nits * Improve error handeling Two separate errors for distinguishing between `account not found` and `insufficient balance`. However, when `next_nonce()` is called and the account is not found then provide `local_best_next_nonce`! * Ensure only one n/w request is performed Refactored to code again: * Removed `fn cached_next_nonce` * Removed extra n/w request in `sign` to check balance * Refactored `fill_optional_field` to request nonce and check account balance * nits * grumbles needless clone * Prevent integer overflow with saturating add & mul * Call `sign_transaction()` directly from `sign()` Because the change in `fill_optional_fields` always fill the nonce it is now possible to call `sign_transaction` directly instead of creating a `ProspectiveSigner` "object".
This commit is contained in:
parent
65bf1086a2
commit
6e7d8f90b5
@ -30,14 +30,15 @@ use bytes::Bytes;
|
|||||||
use parking_lot::{Mutex, RwLock};
|
use parking_lot::{Mutex, RwLock};
|
||||||
use stats::Corpus;
|
use stats::Corpus;
|
||||||
|
|
||||||
|
use crypto::DEFAULT_MAC;
|
||||||
|
use ethcore::account_provider::AccountProvider;
|
||||||
|
use ethcore::basic_account::BasicAccount;
|
||||||
|
use ethcore::client::BlockChainClient;
|
||||||
|
use ethcore::ids::BlockId;
|
||||||
|
use ethcore::miner::{self, MinerService};
|
||||||
use ethkey::{Password, Signature};
|
use ethkey::{Password, Signature};
|
||||||
use sync::LightSync;
|
use sync::LightSync;
|
||||||
use ethcore::ids::BlockId;
|
use transaction::{Action, SignedTransaction, PendingTransaction, Transaction, Error as TransactionError};
|
||||||
use ethcore::client::BlockChainClient;
|
|
||||||
use ethcore::miner::{self, MinerService};
|
|
||||||
use ethcore::account_provider::AccountProvider;
|
|
||||||
use crypto::DEFAULT_MAC;
|
|
||||||
use transaction::{Action, SignedTransaction, PendingTransaction, Transaction};
|
|
||||||
|
|
||||||
use jsonrpc_core::{BoxFuture, Result, Error};
|
use jsonrpc_core::{BoxFuture, Result, Error};
|
||||||
use jsonrpc_core::futures::{future, Future, Poll, Async};
|
use jsonrpc_core::futures::{future, Future, Poll, Async};
|
||||||
@ -218,7 +219,6 @@ pub fn fetch_gas_price_corpus(
|
|||||||
for t in block.transaction_views().iter() {
|
for t in block.transaction_views().iter() {
|
||||||
v.push(t.gas_price())
|
v.push(t.gas_price())
|
||||||
}
|
}
|
||||||
|
|
||||||
v
|
v
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -302,40 +302,42 @@ impl LightDispatcher {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get an account's next nonce.
|
/// Get an account's state
|
||||||
pub fn next_nonce(&self, addr: Address) -> BoxFuture<U256> {
|
fn account(&self, addr: Address) -> BoxFuture<Option<BasicAccount>> {
|
||||||
// fast path where we don't go to network; nonce provided or can be gotten from queue.
|
|
||||||
let maybe_nonce = self.transaction_queue.read().next_nonce(&addr);
|
|
||||||
if let Some(nonce) = maybe_nonce {
|
|
||||||
return Box::new(future::ok(nonce))
|
|
||||||
}
|
|
||||||
|
|
||||||
let best_header = self.client.best_block_header();
|
let best_header = self.client.best_block_header();
|
||||||
let account_start_nonce = self.client.engine().account_start_nonce(best_header.number());
|
let account_future = self.sync.with_context(|ctx| self.on_demand.request(ctx, request::Account {
|
||||||
let nonce_future = self.sync.with_context(|ctx| self.on_demand.request(ctx, request::Account {
|
|
||||||
header: best_header.into(),
|
header: best_header.into(),
|
||||||
address: addr,
|
address: addr,
|
||||||
}).expect("no back-references; therefore all back-references valid; qed"));
|
}).expect("no back-references; therefore all back-references valid; qed"));
|
||||||
|
|
||||||
match nonce_future {
|
match account_future {
|
||||||
Some(x) => Box::new(
|
Some(response) => Box::new(response.map_err(|_| errors::no_light_peers())),
|
||||||
x.map(move |acc| acc.map_or(account_start_nonce, |acc| acc.nonce))
|
None => Box::new(future::err(errors::network_disabled())),
|
||||||
.map_err(|_| errors::no_light_peers())
|
|
||||||
),
|
|
||||||
None => Box::new(future::err(errors::network_disabled()))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get an account's next nonce.
|
||||||
|
pub fn next_nonce(&self, addr: Address) -> BoxFuture<U256> {
|
||||||
|
let account_start_nonce = self.client.engine().account_start_nonce(self.client.best_block_header().number());
|
||||||
|
Box::new(self.account(addr)
|
||||||
|
.and_then(move |maybe_account| {
|
||||||
|
future::ok(maybe_account.map_or(account_start_nonce, |account| account.nonce))
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Dispatcher for LightDispatcher {
|
impl Dispatcher for LightDispatcher {
|
||||||
fn fill_optional_fields(&self, request: TransactionRequest, default_sender: Address, force_nonce: bool)
|
// Ignore the `force_nonce` flag in order to always query the network when fetching the nonce and
|
||||||
|
// the account state. If the nonce is specified in the transaction use that nonce instead but do the
|
||||||
|
// network request anyway to the account state (balance)
|
||||||
|
fn fill_optional_fields(&self, request: TransactionRequest, default_sender: Address, _force_nonce: bool)
|
||||||
-> BoxFuture<FilledTransactionRequest>
|
-> BoxFuture<FilledTransactionRequest>
|
||||||
{
|
{
|
||||||
const DEFAULT_GAS_PRICE: U256 = U256([0, 0, 0, 21_000_000]);
|
const DEFAULT_GAS_PRICE: U256 = U256([0, 0, 0, 21_000_000]);
|
||||||
|
|
||||||
let gas_limit = self.client.best_block_header().gas_limit();
|
let gas_limit = self.client.best_block_header().gas_limit();
|
||||||
let request_gas_price = request.gas_price.clone();
|
let request_gas_price = request.gas_price.clone();
|
||||||
let request_nonce = request.nonce.clone();
|
|
||||||
let from = request.from.unwrap_or(default_sender);
|
let from = request.from.unwrap_or(default_sender);
|
||||||
|
|
||||||
let with_gas_price = move |gas_price| {
|
let with_gas_price = move |gas_price| {
|
||||||
@ -368,39 +370,37 @@ impl Dispatcher for LightDispatcher {
|
|||||||
}).map(with_gas_price))
|
}).map(with_gas_price))
|
||||||
};
|
};
|
||||||
|
|
||||||
match (request_nonce, force_nonce) {
|
let future_account = self.account(from);
|
||||||
(_, false) | (Some(_), true) => Box::new(gas_price),
|
|
||||||
(None, true) => {
|
Box::new(gas_price.and_then(move |mut filled| {
|
||||||
let next_nonce = self.next_nonce(from);
|
future_account
|
||||||
Box::new(gas_price.and_then(move |mut filled| next_nonce
|
.and_then(move |maybe_account| {
|
||||||
.map_err(|_| errors::no_light_peers())
|
let cost = filled.value.saturating_add(filled.gas.saturating_mul(filled.gas_price));
|
||||||
.map(move |nonce| {
|
match maybe_account {
|
||||||
filled.nonce = Some(nonce);
|
Some(ref account) if cost > account.balance => {
|
||||||
filled
|
Err(errors::transaction(TransactionError::InsufficientBalance {
|
||||||
})
|
balance: account.balance,
|
||||||
))
|
cost,
|
||||||
},
|
}))
|
||||||
}
|
}
|
||||||
|
Some(account) => {
|
||||||
|
if filled.nonce.is_none() {
|
||||||
|
filled.nonce = Some(account.nonce);
|
||||||
|
}
|
||||||
|
Ok(filled)
|
||||||
|
}
|
||||||
|
None => Err(errors::account("Account not found", "")),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sign(&self, accounts: Arc<AccountProvider>, filled: FilledTransactionRequest, password: SignWith)
|
fn sign(&self, accounts: Arc<AccountProvider>, filled: FilledTransactionRequest, password: SignWith)
|
||||||
-> BoxFuture<WithToken<SignedTransaction>>
|
-> BoxFuture<WithToken<SignedTransaction>>
|
||||||
{
|
{
|
||||||
let chain_id = self.client.signing_chain_id();
|
let chain_id = self.client.signing_chain_id();
|
||||||
|
let nonce = filled.nonce.expect("nonce is always provided; qed");
|
||||||
// fast path for pre-filled nonce.
|
Box::new(future::done(sign_transaction(&*accounts, filled, chain_id, nonce, password)))
|
||||||
if let Some(nonce) = filled.nonce {
|
|
||||||
return Box::new(future::done(sign_transaction(&*accounts, filled, chain_id, nonce, password)))
|
|
||||||
}
|
|
||||||
|
|
||||||
let nonces = self.nonces.clone();
|
|
||||||
Box::new(self.next_nonce(filled.from)
|
|
||||||
.map_err(|_| errors::no_light_peers())
|
|
||||||
.and_then(move |nonce| {
|
|
||||||
let reserved = nonces.lock().reserve(filled.from, nonce);
|
|
||||||
|
|
||||||
ProspectiveSigner::new(accounts, filled, chain_id, reserved, password)
|
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn enrich(&self, signed_transaction: SignedTransaction) -> RpcRichRawTransaction {
|
fn enrich(&self, signed_transaction: SignedTransaction) -> RpcRichRawTransaction {
|
||||||
|
Loading…
Reference in New Issue
Block a user