Skip to content
This repository was archived by the owner on Nov 6, 2020. It is now read-only.

Commit 6e7d8f9

Browse files
niklasad15chdn
authored andcommitted
[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".
1 parent 65bf108 commit 6e7d8f9

File tree

1 file changed

+52
-52
lines changed

1 file changed

+52
-52
lines changed

rpc/src/v1/helpers/dispatch.rs

Lines changed: 52 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,15 @@ use bytes::Bytes;
3030
use parking_lot::{Mutex, RwLock};
3131
use stats::Corpus;
3232

33-
use ethkey::{Password, Signature};
34-
use sync::LightSync;
35-
use ethcore::ids::BlockId;
33+
use crypto::DEFAULT_MAC;
34+
use ethcore::account_provider::AccountProvider;
35+
use ethcore::basic_account::BasicAccount;
3636
use ethcore::client::BlockChainClient;
37+
use ethcore::ids::BlockId;
3738
use ethcore::miner::{self, MinerService};
38-
use ethcore::account_provider::AccountProvider;
39-
use crypto::DEFAULT_MAC;
40-
use transaction::{Action, SignedTransaction, PendingTransaction, Transaction};
39+
use ethkey::{Password, Signature};
40+
use sync::LightSync;
41+
use transaction::{Action, SignedTransaction, PendingTransaction, Transaction, Error as TransactionError};
4142

4243
use jsonrpc_core::{BoxFuture, Result, Error};
4344
use jsonrpc_core::futures::{future, Future, Poll, Async};
@@ -218,7 +219,6 @@ pub fn fetch_gas_price_corpus(
218219
for t in block.transaction_views().iter() {
219220
v.push(t.gas_price())
220221
}
221-
222222
v
223223
})
224224
})
@@ -302,40 +302,42 @@ impl LightDispatcher {
302302
)
303303
}
304304

305-
/// Get an account's next nonce.
306-
pub fn next_nonce(&self, addr: Address) -> BoxFuture<U256> {
307-
// fast path where we don't go to network; nonce provided or can be gotten from queue.
308-
let maybe_nonce = self.transaction_queue.read().next_nonce(&addr);
309-
if let Some(nonce) = maybe_nonce {
310-
return Box::new(future::ok(nonce))
311-
}
312-
305+
/// Get an account's state
306+
fn account(&self, addr: Address) -> BoxFuture<Option<BasicAccount>> {
313307
let best_header = self.client.best_block_header();
314-
let account_start_nonce = self.client.engine().account_start_nonce(best_header.number());
315-
let nonce_future = self.sync.with_context(|ctx| self.on_demand.request(ctx, request::Account {
308+
let account_future = self.sync.with_context(|ctx| self.on_demand.request(ctx, request::Account {
316309
header: best_header.into(),
317310
address: addr,
318311
}).expect("no back-references; therefore all back-references valid; qed"));
319312

320-
match nonce_future {
321-
Some(x) => Box::new(
322-
x.map(move |acc| acc.map_or(account_start_nonce, |acc| acc.nonce))
323-
.map_err(|_| errors::no_light_peers())
324-
),
325-
None => Box::new(future::err(errors::network_disabled()))
313+
match account_future {
314+
Some(response) => Box::new(response.map_err(|_| errors::no_light_peers())),
315+
None => Box::new(future::err(errors::network_disabled())),
326316
}
327317
}
318+
319+
/// Get an account's next nonce.
320+
pub fn next_nonce(&self, addr: Address) -> BoxFuture<U256> {
321+
let account_start_nonce = self.client.engine().account_start_nonce(self.client.best_block_header().number());
322+
Box::new(self.account(addr)
323+
.and_then(move |maybe_account| {
324+
future::ok(maybe_account.map_or(account_start_nonce, |account| account.nonce))
325+
})
326+
)
327+
}
328328
}
329329

330330
impl Dispatcher for LightDispatcher {
331-
fn fill_optional_fields(&self, request: TransactionRequest, default_sender: Address, force_nonce: bool)
331+
// Ignore the `force_nonce` flag in order to always query the network when fetching the nonce and
332+
// the account state. If the nonce is specified in the transaction use that nonce instead but do the
333+
// network request anyway to the account state (balance)
334+
fn fill_optional_fields(&self, request: TransactionRequest, default_sender: Address, _force_nonce: bool)
332335
-> BoxFuture<FilledTransactionRequest>
333336
{
334337
const DEFAULT_GAS_PRICE: U256 = U256([0, 0, 0, 21_000_000]);
335338

336339
let gas_limit = self.client.best_block_header().gas_limit();
337340
let request_gas_price = request.gas_price.clone();
338-
let request_nonce = request.nonce.clone();
339341
let from = request.from.unwrap_or(default_sender);
340342

341343
let with_gas_price = move |gas_price| {
@@ -368,39 +370,37 @@ impl Dispatcher for LightDispatcher {
368370
}).map(with_gas_price))
369371
};
370372

371-
match (request_nonce, force_nonce) {
372-
(_, false) | (Some(_), true) => Box::new(gas_price),
373-
(None, true) => {
374-
let next_nonce = self.next_nonce(from);
375-
Box::new(gas_price.and_then(move |mut filled| next_nonce
376-
.map_err(|_| errors::no_light_peers())
377-
.map(move |nonce| {
378-
filled.nonce = Some(nonce);
379-
filled
380-
})
381-
))
382-
},
383-
}
373+
let future_account = self.account(from);
374+
375+
Box::new(gas_price.and_then(move |mut filled| {
376+
future_account
377+
.and_then(move |maybe_account| {
378+
let cost = filled.value.saturating_add(filled.gas.saturating_mul(filled.gas_price));
379+
match maybe_account {
380+
Some(ref account) if cost > account.balance => {
381+
Err(errors::transaction(TransactionError::InsufficientBalance {
382+
balance: account.balance,
383+
cost,
384+
}))
385+
}
386+
Some(account) => {
387+
if filled.nonce.is_none() {
388+
filled.nonce = Some(account.nonce);
389+
}
390+
Ok(filled)
391+
}
392+
None => Err(errors::account("Account not found", "")),
393+
}
394+
})
395+
}))
384396
}
385397

386398
fn sign(&self, accounts: Arc<AccountProvider>, filled: FilledTransactionRequest, password: SignWith)
387399
-> BoxFuture<WithToken<SignedTransaction>>
388400
{
389401
let chain_id = self.client.signing_chain_id();
390-
391-
// fast path for pre-filled nonce.
392-
if let Some(nonce) = filled.nonce {
393-
return Box::new(future::done(sign_transaction(&*accounts, filled, chain_id, nonce, password)))
394-
}
395-
396-
let nonces = self.nonces.clone();
397-
Box::new(self.next_nonce(filled.from)
398-
.map_err(|_| errors::no_light_peers())
399-
.and_then(move |nonce| {
400-
let reserved = nonces.lock().reserve(filled.from, nonce);
401-
402-
ProspectiveSigner::new(accounts, filled, chain_id, reserved, password)
403-
}))
402+
let nonce = filled.nonce.expect("nonce is always provided; qed");
403+
Box::new(future::done(sign_transaction(&*accounts, filled, chain_id, nonce, password)))
404404
}
405405

406406
fn enrich(&self, signed_transaction: SignedTransaction) -> RpcRichRawTransaction {

0 commit comments

Comments
 (0)