From 92e5982127548aed46b92354bde2c4916e0352a7 Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Sat, 25 Feb 2017 00:27:48 +0100 Subject: [PATCH] generate transaction proofs from provider --- ethcore/light/src/client/mod.rs | 6 +- ethcore/light/src/net/mod.rs | 29 +++++++- ethcore/light/src/net/request_credits.rs | 10 ++- ethcore/light/src/net/request_set.rs | 1 + ethcore/light/src/provider.rs | 30 +++++++- ethcore/light/src/types/les_request.rs | 33 ++++++++- ethcore/src/client/client.rs | 92 ++++++++++++------------ ethcore/src/client/test_client.rs | 4 ++ ethcore/src/client/traits.rs | 4 ++ ethcore/src/state/backend.rs | 18 +++-- ethcore/src/state/mod.rs | 13 ++++ util/src/hashdb.rs | 10 +++ 12 files changed, 195 insertions(+), 55 deletions(-) diff --git a/ethcore/light/src/client/mod.rs b/ethcore/light/src/client/mod.rs index a113b4367..9626f9f6c 100644 --- a/ethcore/light/src/client/mod.rs +++ b/ethcore/light/src/client/mod.rs @@ -31,7 +31,7 @@ use ethcore::service::ClientIoMessage; use ethcore::encoded; use io::IoChannel; -use util::{Bytes, H256, Mutex, RwLock}; +use util::{Bytes, DBValue, H256, Mutex, RwLock}; use self::header_chain::HeaderChain; @@ -293,6 +293,10 @@ impl ::provider::Provider for Client { None } + fn transaction_proof(&self, _req: ::request::TransactionProof) -> Option> { + None + } + fn ready_transactions(&self) -> Vec<::ethcore::transaction::PendingTransaction> { Vec::new() } diff --git a/ethcore/light/src/net/mod.rs b/ethcore/light/src/net/mod.rs index 2ffaffa64..ad1eaac00 100644 --- a/ethcore/light/src/net/mod.rs +++ b/ethcore/light/src/net/mod.rs @@ -19,7 +19,7 @@ //! This uses a "Provider" to answer requests. //! See https://github.com/ethcore/parity/wiki/Light-Ethereum-Subprotocol-(LES) -use ethcore::transaction::UnverifiedTransaction; +use ethcore::transaction::{Action, UnverifiedTransaction}; use ethcore::receipt::Receipt; use io::TimerToken; @@ -73,7 +73,7 @@ pub const PROTOCOL_VERSIONS: &'static [u8] = &[1]; pub const MAX_PROTOCOL_VERSION: u8 = 1; /// Packet count for LES. -pub const PACKET_COUNT: u8 = 15; +pub const PACKET_COUNT: u8 = 17; // packet ID definitions. mod packet { @@ -109,6 +109,10 @@ mod packet { // request and response for header proofs in a CHT. pub const GET_HEADER_PROOFS: u8 = 0x0d; pub const HEADER_PROOFS: u8 = 0x0e; + + // request and response for transaction proof. + pub const GET_TRANSACTION_PROOF: u8 = 0x0f; + pub const TRANSACTION_PROOF: u8 = 0x10; } // timeouts for different kinds of requests. all values are in milliseconds. @@ -121,6 +125,7 @@ mod timeout { pub const PROOFS: i64 = 4000; pub const CONTRACT_CODES: i64 = 5000; pub const HEADER_PROOFS: i64 = 3500; + pub const TRANSACTION_PROOF: i64 = 5000; } /// A request id. @@ -370,6 +375,7 @@ impl LightProtocol { request::Kind::StateProofs => packet::GET_PROOFS, request::Kind::Codes => packet::GET_CONTRACT_CODES, request::Kind::HeaderProofs => packet::GET_HEADER_PROOFS, + request::Kind::TransactionProof => packet::GET_TRANSACTION_PROOF, }; io.send(*peer_id, packet_id, packet_data); @@ -1320,6 +1326,25 @@ fn encode_request(req: &Request, req_id: usize) -> Vec { .append(&proof_req.from_level); } + stream.out() + } + Request::TransactionProof(ref request) => { + let mut stream = RlpStream::new_list(2); + stream.append(&req_id).begin_list(7) + .append(&request.at) + .append(&request.from); + + match request.action { + Action::Create => stream.append_empty_data(), + Action::Call(ref to) => stream.append(to), + }; + + stream + .append(&request.gas) + .append(&request.gas_price) + .append(&request.value) + .append(&request.data); + stream.out() } } diff --git a/ethcore/light/src/net/request_credits.rs b/ethcore/light/src/net/request_credits.rs index 4f5d79504..3a1cb9996 100644 --- a/ethcore/light/src/net/request_credits.rs +++ b/ethcore/light/src/net/request_credits.rs @@ -81,12 +81,13 @@ impl Credits { /// A cost table, mapping requests to base and per-request costs. #[derive(Debug, Clone, PartialEq, Eq)] pub struct CostTable { - headers: Cost, + headers: Cost, // cost per header bodies: Cost, receipts: Cost, state_proofs: Cost, contract_codes: Cost, header_proofs: Cost, + transaction_proof: Cost, // cost per gas. } impl Default for CostTable { @@ -99,6 +100,7 @@ impl Default for CostTable { state_proofs: Cost(250000.into(), 25000.into()), contract_codes: Cost(200000.into(), 20000.into()), header_proofs: Cost(150000.into(), 15000.into()), + transaction_proof: Cost(100000.into(), 2.into()), } } } @@ -133,6 +135,7 @@ impl RlpDecodable for CostTable { let mut state_proofs = None; let mut contract_codes = None; let mut header_proofs = None; + let mut transaction_proof = None; for row in rlp.iter() { let msg_id: u8 = row.val_at(0)?; @@ -150,6 +153,7 @@ impl RlpDecodable for CostTable { packet::GET_PROOFS => state_proofs = Some(cost), packet::GET_CONTRACT_CODES => contract_codes = Some(cost), packet::GET_HEADER_PROOFS => header_proofs = Some(cost), + packet::GET_TRANSACTION_PROOF => transaction_proof = Some(cost), _ => return Err(DecoderError::Custom("Unrecognized message in cost table")), } } @@ -161,6 +165,7 @@ impl RlpDecodable for CostTable { state_proofs: state_proofs.ok_or(DecoderError::Custom("No proofs cost specified"))?, contract_codes: contract_codes.ok_or(DecoderError::Custom("No contract codes specified"))?, header_proofs: header_proofs.ok_or(DecoderError::Custom("No header proofs cost specified"))?, + transaction_proof: transaction_proof.ok_or(DecoderError::Custom("No transaction proof gas cost specified"))?, }) } } @@ -197,6 +202,7 @@ impl FlowParams { state_proofs: free_cost.clone(), contract_codes: free_cost.clone(), header_proofs: free_cost.clone(), + transaction_proof: free_cost, } } } @@ -220,6 +226,7 @@ impl FlowParams { request::Kind::StateProofs => &self.costs.state_proofs, request::Kind::Codes => &self.costs.contract_codes, request::Kind::HeaderProofs => &self.costs.header_proofs, + request::Kind::TransactionProof => &self.costs.transaction_proof, }; let amount: U256 = amount.into(); @@ -241,6 +248,7 @@ impl FlowParams { request::Kind::StateProofs => &self.costs.state_proofs, request::Kind::Codes => &self.costs.contract_codes, request::Kind::HeaderProofs => &self.costs.header_proofs, + request::Kind::TransactionProof => &self.costs.transaction_proof, }; let start = credits.current(); diff --git a/ethcore/light/src/net/request_set.rs b/ethcore/light/src/net/request_set.rs index c9f278776..57eb232cc 100644 --- a/ethcore/light/src/net/request_set.rs +++ b/ethcore/light/src/net/request_set.rs @@ -101,6 +101,7 @@ impl RequestSet { request::Kind::StateProofs => timeout::PROOFS, request::Kind::Codes => timeout::CONTRACT_CODES, request::Kind::HeaderProofs => timeout::HEADER_PROOFS, + request::Kind::TransactionProof => timeout::TRANSACTION_PROOF, }; base + Duration::milliseconds(kind_timeout) <= now diff --git a/ethcore/light/src/provider.rs b/ethcore/light/src/provider.rs index caade3857..2ef7f1f04 100644 --- a/ethcore/light/src/provider.rs +++ b/ethcore/light/src/provider.rs @@ -24,7 +24,7 @@ use ethcore::client::{BlockChainClient, ProvingBlockChainClient}; use ethcore::transaction::PendingTransaction; use ethcore::ids::BlockId; use ethcore::encoded; -use util::{Bytes, RwLock, H256}; +use util::{Bytes, DBValue, RwLock, H256}; use cht::{self, BlockInfo}; use client::{LightChainClient, AsLightClient}; @@ -193,6 +193,10 @@ pub trait Provider: Send + Sync { /// Provide pending transactions. fn ready_transactions(&self) -> Vec; + + /// Provide a proof-of-execution for the given transaction proof request. + /// Returns a vector of all state items necessary to execute the transaction. + fn transaction_proof(&self, req: request::TransactionProof) -> Option>; } // Implementation of a light client data provider for a client. @@ -283,6 +287,26 @@ impl Provider for T { } } + fn transaction_proof(&self, req: request::TransactionProof) -> Option> { + use ethcore::transaction::Transaction; + + let id = BlockId::Hash(req.at); + let nonce = match self.nonce(&req.from, id.clone()) { + Some(nonce) => nonce, + None => return None, + }; + let transaction = Transaction { + nonce: nonce, + gas: req.gas, + gas_price: req.gas_price, + action: req.action, + value: req.value, + data: req.data, + }.fake_sign(req.from); + + self.prove_transaction(transaction, id) + } + fn ready_transactions(&self) -> Vec { BlockChainClient::ready_transactions(self) } @@ -343,6 +367,10 @@ impl Provider for LightProvider { None } + fn transaction_proof(&self, req: request::TransactionProof) -> Option> { + None + } + fn ready_transactions(&self) -> Vec { let chain_info = self.chain_info(); self.txqueue.read().ready_transactions(chain_info.best_block_number, chain_info.best_block_timestamp) diff --git a/ethcore/light/src/types/les_request.rs b/ethcore/light/src/types/les_request.rs index b4940980e..dbff19eb5 100644 --- a/ethcore/light/src/types/les_request.rs +++ b/ethcore/light/src/types/les_request.rs @@ -16,7 +16,8 @@ //! LES request types. -use util::H256; +use ethcore::transaction::Action; +use util::{Address, H256, U256, Uint}; /// Either a hash or a number. #[derive(Debug, Clone, PartialEq, Eq)] @@ -134,6 +135,26 @@ pub struct HeaderProofs { pub requests: Vec, } +/// A request for proof of (simulated) transaction execution. +#[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "ipc", binary)] +pub struct TransactionProof { + /// Block hash to request for. + pub at: H256, + /// Address to treat as the caller. + pub from: Address, + /// Action to take: either a call or a create. + pub action: Action, + /// Amount of gas to request proof-of-execution for. + pub gas: U256, + /// Price for each gas. + pub gas_price: U256, + /// Value to simulate sending. + pub value: U256, + /// Transaction data. + pub data: Vec, +} + /// Kinds of requests. #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[cfg_attr(feature = "ipc", binary)] @@ -150,6 +171,8 @@ pub enum Kind { Codes, /// Requesting header proofs (from the CHT). HeaderProofs, + /// Requesting proof of transaction execution. + TransactionProof, } /// Encompasses all possible types of requests in a single structure. @@ -168,6 +191,8 @@ pub enum Request { Codes(ContractCodes), /// Requesting header proofs. HeaderProofs(HeaderProofs), + /// Requesting proof of transaction execution. + TransactionProof(TransactionProof), } impl Request { @@ -180,10 +205,12 @@ impl Request { Request::StateProofs(_) => Kind::StateProofs, Request::Codes(_) => Kind::Codes, Request::HeaderProofs(_) => Kind::HeaderProofs, + Request::TransactionProof(_) => Kind::TransactionProof, } } /// Get the amount of requests being made. + /// In the case of `TransactionProof`, this is the amount of gas being requested. pub fn amount(&self) -> usize { match *self { Request::Headers(ref req) => req.max, @@ -192,6 +219,10 @@ impl Request { Request::StateProofs(ref req) => req.requests.len(), Request::Codes(ref req) => req.code_requests.len(), Request::HeaderProofs(ref req) => req.requests.len(), + Request::TransactionProof(ref req) => match req.gas > usize::max_value().into() { + true => usize::max_value(), + false => req.gas.low_u64() as usize, + } } } } diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index 7f209bad1..d8849ded3 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -24,7 +24,7 @@ use time::precise_time_ns; // util use util::{Bytes, PerfTimer, Itertools, Mutex, RwLock, MutexGuard, Hashable}; -use util::{journaldb, TrieFactory, Trie}; +use util::{journaldb, DBValue, TrieFactory, Trie}; use util::{U256, H256, Address, H2048, Uint, FixedHash}; use util::trie::TrieSpec; use util::kvdb::*; @@ -34,7 +34,7 @@ use io::*; use views::BlockView; use error::{ImportError, ExecutionError, CallError, BlockError, ImportResult, Error as EthcoreError}; use header::BlockNumber; -use state::{State, CleanupMode}; +use state::{self, State, CleanupMode}; use spec::Spec; use basic_types::Seal; use engines::Engine; @@ -309,17 +309,23 @@ impl Client { /// The env info as of the best block. fn latest_env_info(&self) -> EnvInfo { - let header = self.best_block_header(); + self.env_info(BlockId::Latest).expect("Best block header always stored; qed") + } - EnvInfo { - number: header.number(), - author: header.author(), - timestamp: header.timestamp(), - difficulty: header.difficulty(), - last_hashes: self.build_last_hashes(header.hash()), - gas_used: U256::default(), - gas_limit: header.gas_limit(), - } + /// The env info as of a given block. + /// returns `None` if the block unknown. + fn env_info(&self, id: BlockId) -> Option { + self.block_header(id).map(|header| { + EnvInfo { + number: header.number(), + author: header.author(), + timestamp: header.timestamp(), + difficulty: header.difficulty(), + last_hashes: self.build_last_hashes(header.parent_hash()), + gas_used: U256::default(), + gas_limit: header.gas_limit(), + } + }) } fn build_last_hashes(&self, parent_hash: H256) -> Arc { @@ -874,17 +880,8 @@ impl snapshot::DatabaseRestore for Client { impl BlockChainClient for Client { fn call(&self, t: &SignedTransaction, block: BlockId, analytics: CallAnalytics) -> Result { - let header = self.block_header(block).ok_or(CallError::StatePruned)?; - let last_hashes = self.build_last_hashes(header.parent_hash()); - let env_info = EnvInfo { - number: header.number(), - author: header.author(), - timestamp: header.timestamp(), - difficulty: header.difficulty(), - last_hashes: last_hashes, - gas_used: U256::zero(), - gas_limit: U256::max_value(), - }; + let env_info = self.env_info(block).ok_or(CallError::StatePruned)?; + // that's just a copy of the state. let mut state = self.state_at(block).ok_or(CallError::StatePruned)?; let original_state = if analytics.state_diffing { Some(state.clone()) } else { None }; @@ -910,17 +907,13 @@ impl BlockChainClient for Client { fn estimate_gas(&self, t: &SignedTransaction, block: BlockId) -> Result { const UPPER_CEILING: u64 = 1_000_000_000_000u64; - let header = self.block_header(block).ok_or(CallError::StatePruned)?; - let last_hashes = self.build_last_hashes(header.parent_hash()); - let env_info = EnvInfo { - number: header.number(), - author: header.author(), - timestamp: header.timestamp(), - difficulty: header.difficulty(), - last_hashes: last_hashes, - gas_used: U256::zero(), - gas_limit: UPPER_CEILING.into(), + let (mut upper, env_info) = { + let mut env_info = self.env_info(block).ok_or(CallError::StatePruned)?; + let initial_upper = env_info.gas_limit; + env_info.gas_limit = UPPER_CEILING.into(); + (initial_upper, env_info) }; + // that's just a copy of the state. let original_state = self.state_at(block).ok_or(CallError::StatePruned)?; let sender = t.sender(); @@ -946,7 +939,6 @@ impl BlockChainClient for Client { .unwrap_or(false)) }; - let mut upper = header.gas_limit(); if !cond(upper)? { // impossible at block gas limit - try `UPPER_CEILING` instead. // TODO: consider raising limit by powers of two. @@ -989,7 +981,7 @@ impl BlockChainClient for Client { fn replay(&self, id: TransactionId, analytics: CallAnalytics) -> Result { let address = self.transaction_address(id).ok_or(CallError::TransactionNotFound)?; - let header = self.block_header(BlockId::Hash(address.block_hash)).ok_or(CallError::StatePruned)?; + let mut env_info = self.env_info(BlockId::Hash(address.block_hash)).ok_or(CallError::StatePruned)?; let body = self.block_body(BlockId::Hash(address.block_hash)).ok_or(CallError::StatePruned)?; let mut state = self.state_at_beginning(BlockId::Hash(address.block_hash)).ok_or(CallError::StatePruned)?; let mut txs = body.transactions(); @@ -999,16 +991,6 @@ impl BlockChainClient for Client { } let options = TransactOptions { tracing: analytics.transaction_tracing, vm_tracing: analytics.vm_tracing, check_nonce: false }; - let last_hashes = self.build_last_hashes(header.hash()); - let mut env_info = EnvInfo { - number: header.number(), - author: header.author(), - timestamp: header.timestamp(), - difficulty: header.difficulty(), - last_hashes: last_hashes, - gas_used: U256::default(), - gas_limit: header.gas_limit(), - }; const PROOF: &'static str = "Transactions fetched from blockchain; blockchain transactions are valid; qed"; let rest = txs.split_off(address.index); for t in txs { @@ -1620,6 +1602,26 @@ impl ::client::ProvingBlockChainClient for Client { .and_then(|x| x) .unwrap_or_else(Vec::new) } + + fn prove_transaction(&self, transaction: SignedTransaction, id: BlockId) -> Option> { + let (state, env_info) = match (self.state_at(id), self.env_info(id)) { + (Some(s), Some(e)) => (s, e), + _ => return None, + }; + let mut jdb = self.state_db.lock().journal_db().boxed_clone(); + let backend = state::backend::Proving::new(jdb.as_hashdb_mut()); + + let mut state = state.replace_backend(backend); + let options = TransactOptions { tracing: false, vm_tracing: false, check_nonce: false }; + let res = Executive::new(&mut state, &env_info, &*self.engine, &self.factories.vm).transact(&transaction, options); + + + match res { + Err(ExecutionError::Internal(_)) => return None, + _ => return Some(state.drop().1.extract_proof()), + } + } + } impl Drop for Client { diff --git a/ethcore/src/client/test_client.rs b/ethcore/src/client/test_client.rs index dc9cb5944..5d436f4c5 100644 --- a/ethcore/src/client/test_client.rs +++ b/ethcore/src/client/test_client.rs @@ -765,6 +765,10 @@ impl ProvingBlockChainClient for TestBlockChainClient { fn code_by_hash(&self, _: H256, _: BlockId) -> Bytes { Vec::new() } + + fn prove_transaction(&self, _: SignedTransaction, _: BlockId) -> Option> { + None + } } impl EngineClient for TestBlockChainClient { diff --git a/ethcore/src/client/traits.rs b/ethcore/src/client/traits.rs index dce708b3a..13abb33f9 100644 --- a/ethcore/src/client/traits.rs +++ b/ethcore/src/client/traits.rs @@ -16,6 +16,7 @@ use std::collections::BTreeMap; use util::{U256, Address, H256, H2048, Bytes, Itertools}; +use util::hashdb::DBValue; use util::stats::Histogram; use blockchain::TreeRoute; use verification::queue::QueueInfo as BlockQueueInfo; @@ -336,4 +337,7 @@ pub trait ProvingBlockChainClient: BlockChainClient { /// Get code by address hash. fn code_by_hash(&self, account_key: H256, id: BlockId) -> Bytes; + + /// Prove execution of a transaction at the given block. + fn prove_transaction(&self, transaction: SignedTransaction, id: BlockId) -> Option>; } diff --git a/ethcore/src/state/backend.rs b/ethcore/src/state/backend.rs index dfb4465fa..041eb71b4 100644 --- a/ethcore/src/state/backend.rs +++ b/ethcore/src/state/backend.rs @@ -98,7 +98,6 @@ impl Backend for NoCache { /// See module docs for more details. /// /// This doesn't cache anything or rely on the canonical state caches. -#[derive(Debug, Clone, PartialEq)] pub struct Proving { base: H, // state we're proving values from. changed: MemoryDB, // changed state via insertions. @@ -107,8 +106,9 @@ pub struct Proving { impl HashDB for Proving { fn keys(&self) -> HashMap { - self.base.as_hashdb().keys() - .extend(self.changed.keys()) + let mut keys = self.base.as_hashdb().keys(); + keys.extend(self.changed.keys()); + keys } fn get(&self, key: &H256) -> Option { @@ -141,7 +141,7 @@ impl HashDB for Proving { } } -impl Backend for Proving { +impl Backend for Proving { fn as_hashdb(&self) -> &HashDB { self } @@ -183,3 +183,13 @@ impl Proving { self.proof.into_inner().into_iter().collect() } } + +impl Clone for Proving { + fn clone(&self) -> Self { + Proving { + base: self.base.clone(), + changed: self.changed.clone(), + proof: Mutex::new(self.proof.lock().clone()), + } + } +} diff --git a/ethcore/src/state/mod.rs b/ethcore/src/state/mod.rs index e25d7d404..ce711ffbd 100644 --- a/ethcore/src/state/mod.rs +++ b/ethcore/src/state/mod.rs @@ -264,6 +264,19 @@ impl State { Ok(state) } + /// Swap the current backend for another. + // TODO: [rob] find a less hacky way to avoid duplication of `Client::state_at`. + pub fn replace_backend(self, backend: T) -> State { + State { + db: backend, + root: self.root, + cache: self.cache, + checkpoints: self.checkpoints, + account_start_nonce: self.account_start_nonce, + factories: self.factories, + } + } + /// Create a recoverable checkpoint of this state. pub fn checkpoint(&mut self) { self.checkpoints.get_mut().push(HashMap::new()); diff --git a/util/src/hashdb.rs b/util/src/hashdb.rs index 3b1939cae..8217413ef 100644 --- a/util/src/hashdb.rs +++ b/util/src/hashdb.rs @@ -125,3 +125,13 @@ impl AsHashDB for T { self } } + +impl<'a> AsHashDB for &'a mut HashDB { + fn as_hashdb(&self) -> &HashDB { + &**self + } + + fn as_hashdb_mut(&mut self) -> &mut HashDB { + &mut **self + } +}