[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:
Niklas Adolfsson 2018-09-11 19:20:59 +02:00 committed by Afri Schoedon
parent 65bf1086a2
commit 6e7d8f90b5

View File

@ -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 {