[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 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 sync::LightSync; | ||||
| use ethcore::ids::BlockId; | ||||
| 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 transaction::{Action, SignedTransaction, PendingTransaction, Transaction, Error as TransactionError}; | ||||
| 
 | ||||
| use jsonrpc_core::{BoxFuture, Result, Error}; | ||||
| 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() { | ||||
| 						v.push(t.gas_price()) | ||||
| 					} | ||||
| 
 | ||||
| 					v | ||||
| 				}) | ||||
| 			}) | ||||
| @ -302,40 +302,42 @@ impl LightDispatcher { | ||||
| 		) | ||||
| 	} | ||||
| 
 | ||||
| 	/// Get an account's next nonce.
 | ||||
| 	pub fn next_nonce(&self, addr: Address) -> BoxFuture<U256> { | ||||
| 		// 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)) | ||||
| 		} | ||||
| 
 | ||||
| 	/// Get an account's state
 | ||||
| 	fn account(&self, addr: Address) -> BoxFuture<Option<BasicAccount>> { | ||||
| 		let best_header = self.client.best_block_header(); | ||||
| 		let account_start_nonce = self.client.engine().account_start_nonce(best_header.number()); | ||||
| 		let nonce_future = self.sync.with_context(|ctx| self.on_demand.request(ctx, request::Account { | ||||
| 		let account_future = self.sync.with_context(|ctx| self.on_demand.request(ctx, request::Account { | ||||
| 			header: best_header.into(), | ||||
| 			address: addr, | ||||
| 		}).expect("no back-references; therefore all back-references valid; qed")); | ||||
| 
 | ||||
| 		match nonce_future { | ||||
| 			Some(x) => Box::new( | ||||
| 				x.map(move |acc| acc.map_or(account_start_nonce, |acc| acc.nonce)) | ||||
| 					.map_err(|_| errors::no_light_peers()) | ||||
| 			), | ||||
| 			None => Box::new(future::err(errors::network_disabled())) | ||||
| 		match account_future { | ||||
| 			Some(response) => Box::new(response.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 { | ||||
| 	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> | ||||
| 	{ | ||||
| 		const DEFAULT_GAS_PRICE: U256 = U256([0, 0, 0, 21_000_000]); | ||||
| 
 | ||||
| 		let gas_limit = self.client.best_block_header().gas_limit(); | ||||
| 		let request_gas_price = request.gas_price.clone(); | ||||
| 		let request_nonce = request.nonce.clone(); | ||||
| 		let from = request.from.unwrap_or(default_sender); | ||||
| 
 | ||||
| 		let with_gas_price = move |gas_price| { | ||||
| @ -368,39 +370,37 @@ impl Dispatcher for LightDispatcher { | ||||
| 			}).map(with_gas_price)) | ||||
| 		}; | ||||
| 
 | ||||
| 		match (request_nonce, force_nonce) { | ||||
| 			(_, false) | (Some(_), true) => Box::new(gas_price), | ||||
| 			(None, true) => { | ||||
| 				let next_nonce = self.next_nonce(from); | ||||
| 				Box::new(gas_price.and_then(move |mut filled| next_nonce | ||||
| 					.map_err(|_| errors::no_light_peers()) | ||||
| 					.map(move |nonce| { | ||||
| 						filled.nonce = Some(nonce); | ||||
| 						filled | ||||
| 					}) | ||||
| 				)) | ||||
| 			}, | ||||
| 		} | ||||
| 		let future_account = self.account(from); | ||||
| 
 | ||||
| 		Box::new(gas_price.and_then(move |mut filled| { | ||||
| 			future_account | ||||
| 				.and_then(move |maybe_account| { | ||||
| 					let cost = filled.value.saturating_add(filled.gas.saturating_mul(filled.gas_price)); | ||||
| 					match maybe_account { | ||||
| 						Some(ref account) if cost > account.balance => { | ||||
| 							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) | ||||
| 		-> BoxFuture<WithToken<SignedTransaction>> | ||||
| 	{ | ||||
| 		let chain_id = self.client.signing_chain_id(); | ||||
| 
 | ||||
| 		// fast path for pre-filled nonce.
 | ||||
| 		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) | ||||
| 			})) | ||||
| 		let nonce = filled.nonce.expect("nonce is always provided; qed"); | ||||
| 		Box::new(future::done(sign_transaction(&*accounts, filled, chain_id, nonce, password))) | ||||
| 	} | ||||
| 
 | ||||
| 	fn enrich(&self, signed_transaction: SignedTransaction) -> RpcRichRawTransaction { | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user