Remote transaction execution (#4684)
* return errors on database corruption * fix tests, json tests * fix remainder of build * buffer flow -> request credits * proving state backend * generate transaction proofs from provider * network messages for transaction proof * transaction proof test * test for transaction proof message * fix call bug * request transaction proofs from on_demand * most of proved_execution rpc * proved execution future
This commit is contained in:
committed by
Gav Wood
parent
5bbcf0482b
commit
8a3b5c6332
@@ -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::{AncestryIter, HeaderChain};
|
||||
|
||||
@@ -230,22 +230,32 @@ impl Client {
|
||||
}
|
||||
|
||||
/// Get a handle to the verification engine.
|
||||
pub fn engine(&self) -> &Engine {
|
||||
&*self.engine
|
||||
pub fn engine(&self) -> &Arc<Engine> {
|
||||
&self.engine
|
||||
}
|
||||
|
||||
fn latest_env_info(&self) -> EnvInfo {
|
||||
let header = self.best_block_header();
|
||||
/// Get the latest environment info.
|
||||
pub fn latest_env_info(&self) -> EnvInfo {
|
||||
self.env_info(BlockId::Latest)
|
||||
.expect("Best block header and recent hashes always stored; qed")
|
||||
}
|
||||
|
||||
EnvInfo {
|
||||
/// Get environment info for a given block.
|
||||
pub fn env_info(&self, id: BlockId) -> Option<EnvInfo> {
|
||||
let header = match self.block_header(id) {
|
||||
Some(hdr) => hdr,
|
||||
None => return None,
|
||||
};
|
||||
|
||||
Some(EnvInfo {
|
||||
number: header.number(),
|
||||
author: header.author(),
|
||||
timestamp: header.timestamp(),
|
||||
difficulty: header.difficulty(),
|
||||
last_hashes: self.build_last_hashes(header.hash()),
|
||||
last_hashes: self.build_last_hashes(header.parent_hash()),
|
||||
gas_used: Default::default(),
|
||||
gas_limit: header.gas_limit(),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn build_last_hashes(&self, mut parent_hash: H256) -> Arc<Vec<H256>> {
|
||||
@@ -344,6 +354,10 @@ impl ::provider::Provider for Client {
|
||||
None
|
||||
}
|
||||
|
||||
fn transaction_proof(&self, _req: ::request::TransactionProof) -> Option<Vec<DBValue>> {
|
||||
None
|
||||
}
|
||||
|
||||
fn ready_transactions(&self) -> Vec<::ethcore::transaction::PendingTransaction> {
|
||||
Vec::new()
|
||||
}
|
||||
|
||||
@@ -44,8 +44,8 @@ pub enum Error {
|
||||
Rlp(DecoderError),
|
||||
/// A network error.
|
||||
Network(NetworkError),
|
||||
/// Out of buffer.
|
||||
BufferEmpty,
|
||||
/// Out of credits.
|
||||
NoCredits,
|
||||
/// Unrecognized packet code.
|
||||
UnrecognizedPacket(u8),
|
||||
/// Unexpected handshake.
|
||||
@@ -72,7 +72,7 @@ impl Error {
|
||||
match *self {
|
||||
Error::Rlp(_) => Punishment::Disable,
|
||||
Error::Network(_) => Punishment::None,
|
||||
Error::BufferEmpty => Punishment::Disable,
|
||||
Error::NoCredits => Punishment::Disable,
|
||||
Error::UnrecognizedPacket(_) => Punishment::Disconnect,
|
||||
Error::UnexpectedHandshake => Punishment::Disconnect,
|
||||
Error::WrongNetwork => Punishment::Disable,
|
||||
@@ -103,7 +103,7 @@ impl fmt::Display for Error {
|
||||
match *self {
|
||||
Error::Rlp(ref err) => err.fmt(f),
|
||||
Error::Network(ref err) => err.fmt(f),
|
||||
Error::BufferEmpty => write!(f, "Out of buffer"),
|
||||
Error::NoCredits => write!(f, "Out of request credits"),
|
||||
Error::UnrecognizedPacket(code) => write!(f, "Unrecognized packet: 0x{:x}", code),
|
||||
Error::UnexpectedHandshake => write!(f, "Unexpected handshake"),
|
||||
Error::WrongNetwork => write!(f, "Wrong network"),
|
||||
|
||||
@@ -19,14 +19,14 @@
|
||||
//! 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;
|
||||
use network::{NetworkProtocolHandler, NetworkContext, PeerId};
|
||||
use rlp::{RlpStream, Stream, UntrustedRlp, View};
|
||||
use util::hash::H256;
|
||||
use util::{Bytes, Mutex, RwLock, U256};
|
||||
use util::{Bytes, DBValue, Mutex, RwLock, U256};
|
||||
use time::{Duration, SteadyTime};
|
||||
|
||||
use std::collections::HashMap;
|
||||
@@ -37,7 +37,7 @@ use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use provider::Provider;
|
||||
use request::{self, HashOrNumber, Request};
|
||||
|
||||
use self::buffer_flow::{Buffer, FlowParams};
|
||||
use self::request_credits::{Credits, FlowParams};
|
||||
use self::context::{Ctx, TickCtx};
|
||||
use self::error::Punishment;
|
||||
use self::request_set::RequestSet;
|
||||
@@ -51,7 +51,7 @@ mod request_set;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
pub mod buffer_flow;
|
||||
pub mod request_credits;
|
||||
|
||||
pub use self::error::Error;
|
||||
pub use self::context::{BasicContext, EventContext, IoContext};
|
||||
@@ -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.
|
||||
@@ -143,10 +148,10 @@ struct PendingPeer {
|
||||
/// Relevant data to each peer. Not accessible publicly, only `pub` due to
|
||||
/// limitations of the privacy system.
|
||||
pub struct Peer {
|
||||
local_buffer: Buffer, // their buffer relative to us
|
||||
local_credits: Credits, // their credits relative to us
|
||||
status: Status,
|
||||
capabilities: Capabilities,
|
||||
remote_flow: Option<(Buffer, FlowParams)>,
|
||||
remote_flow: Option<(Credits, FlowParams)>,
|
||||
sent_head: H256, // last chain head we've given them.
|
||||
last_update: SteadyTime,
|
||||
pending_requests: RequestSet,
|
||||
@@ -155,21 +160,21 @@ pub struct Peer {
|
||||
|
||||
impl Peer {
|
||||
// check the maximum cost of a request, returning an error if there's
|
||||
// not enough buffer left.
|
||||
// not enough credits left.
|
||||
// returns the calculated maximum cost.
|
||||
fn deduct_max(&mut self, flow_params: &FlowParams, kind: request::Kind, max: usize) -> Result<U256, Error> {
|
||||
flow_params.recharge(&mut self.local_buffer);
|
||||
flow_params.recharge(&mut self.local_credits);
|
||||
|
||||
let max_cost = flow_params.compute_cost(kind, max);
|
||||
self.local_buffer.deduct_cost(max_cost)?;
|
||||
self.local_credits.deduct_cost(max_cost)?;
|
||||
Ok(max_cost)
|
||||
}
|
||||
|
||||
// refund buffer for a request. returns new buffer amount.
|
||||
// refund credits for a request. returns new amount of credits.
|
||||
fn refund(&mut self, flow_params: &FlowParams, amount: U256) -> U256 {
|
||||
flow_params.refund(&mut self.local_buffer, amount);
|
||||
flow_params.refund(&mut self.local_credits, amount);
|
||||
|
||||
self.local_buffer.current()
|
||||
self.local_credits.current()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -206,6 +211,8 @@ pub trait Handler: Send + Sync {
|
||||
/// Called when a peer responds with header proofs. Each proof should be a block header coupled
|
||||
/// with a series of trie nodes is ascending order by distance from the root.
|
||||
fn on_header_proofs(&self, _ctx: &EventContext, _req_id: ReqId, _proofs: &[(Bytes, Vec<Bytes>)]) { }
|
||||
/// Called when a peer responds with a transaction proof. Each proof is a vector of state items.
|
||||
fn on_transaction_proof(&self, _ctx: &EventContext, _req_id: ReqId, _state_items: &[DBValue]) { }
|
||||
/// Called to "tick" the handler periodically.
|
||||
fn tick(&self, _ctx: &BasicContext) { }
|
||||
/// Called on abort. This signals to handlers that they should clean up
|
||||
@@ -218,7 +225,7 @@ pub trait Handler: Send + Sync {
|
||||
pub struct Params {
|
||||
/// Network id.
|
||||
pub network_id: u64,
|
||||
/// Buffer flow parameters.
|
||||
/// Request credits parameters.
|
||||
pub flow_params: FlowParams,
|
||||
/// Initial capabilities.
|
||||
pub capabilities: Capabilities,
|
||||
@@ -334,14 +341,14 @@ impl LightProtocol {
|
||||
|
||||
/// Check the maximum amount of requests of a specific type
|
||||
/// which a peer would be able to serve. Returns zero if the
|
||||
/// peer is unknown or has no buffer flow parameters.
|
||||
/// peer is unknown or has no credit parameters.
|
||||
fn max_requests(&self, peer: PeerId, kind: request::Kind) -> usize {
|
||||
self.peers.read().get(&peer).and_then(|peer| {
|
||||
let mut peer = peer.lock();
|
||||
match peer.remote_flow {
|
||||
Some((ref mut buf, ref flow)) => {
|
||||
flow.recharge(buf);
|
||||
Some(flow.max_amount(&*buf, kind))
|
||||
Some((ref mut c, ref flow)) => {
|
||||
flow.recharge(c);
|
||||
Some(flow.max_amount(&*c, kind))
|
||||
}
|
||||
None => None,
|
||||
}
|
||||
@@ -351,7 +358,7 @@ impl LightProtocol {
|
||||
/// Make a request to a peer.
|
||||
///
|
||||
/// Fails on: nonexistent peer, network error, peer not server,
|
||||
/// insufficient buffer. Does not check capabilities before sending.
|
||||
/// insufficient credits. Does not check capabilities before sending.
|
||||
/// On success, returns a request id which can later be coordinated
|
||||
/// with an event.
|
||||
pub fn request_from(&self, io: &IoContext, peer_id: &PeerId, request: Request) -> Result<ReqId, Error> {
|
||||
@@ -360,10 +367,10 @@ impl LightProtocol {
|
||||
let mut peer = peer.lock();
|
||||
|
||||
match peer.remote_flow {
|
||||
Some((ref mut buf, ref flow)) => {
|
||||
flow.recharge(buf);
|
||||
Some((ref mut c, ref flow)) => {
|
||||
flow.recharge(c);
|
||||
let max = flow.compute_cost(request.kind(), request.amount());
|
||||
buf.deduct_cost(max)?;
|
||||
c.deduct_cost(max)?;
|
||||
}
|
||||
None => return Err(Error::NotServer),
|
||||
}
|
||||
@@ -380,6 +387,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);
|
||||
@@ -464,7 +472,7 @@ impl LightProtocol {
|
||||
// - check whether request kinds match
|
||||
fn pre_verify_response(&self, peer: &PeerId, kind: request::Kind, raw: &UntrustedRlp) -> Result<IdGuard, Error> {
|
||||
let req_id = ReqId(raw.val_at(0)?);
|
||||
let cur_buffer: U256 = raw.val_at(1)?;
|
||||
let cur_credits: U256 = raw.val_at(1)?;
|
||||
|
||||
trace!(target: "les", "pre-verifying response from peer {}, kind={:?}", peer, kind);
|
||||
|
||||
@@ -480,9 +488,9 @@ impl LightProtocol {
|
||||
(Some(request), Some(flow_info)) => {
|
||||
had_req = true;
|
||||
|
||||
let &mut (ref mut buf, ref mut flow) = flow_info;
|
||||
let actual_buffer = ::std::cmp::min(cur_buffer, *flow.limit());
|
||||
buf.update_to(actual_buffer);
|
||||
let &mut (ref mut c, ref mut flow) = flow_info;
|
||||
let actual_credits = ::std::cmp::min(cur_credits, *flow.limit());
|
||||
c.update_to(actual_credits);
|
||||
|
||||
if request.kind() != kind {
|
||||
Some(Error::UnsolicitedResponse)
|
||||
@@ -539,6 +547,9 @@ impl LightProtocol {
|
||||
packet::GET_HEADER_PROOFS => self.get_header_proofs(peer, io, rlp),
|
||||
packet::HEADER_PROOFS => self.header_proofs(peer, io, rlp),
|
||||
|
||||
packet::GET_TRANSACTION_PROOF => self.get_transaction_proof(peer, io, rlp),
|
||||
packet::TRANSACTION_PROOF => self.transaction_proof(peer, io, rlp),
|
||||
|
||||
packet::SEND_TRANSACTIONS => self.relay_transactions(peer, io, rlp),
|
||||
|
||||
other => {
|
||||
@@ -685,10 +696,10 @@ impl LightProtocol {
|
||||
return Err(Error::BadProtocolVersion);
|
||||
}
|
||||
|
||||
let remote_flow = flow_params.map(|params| (params.create_buffer(), params));
|
||||
let remote_flow = flow_params.map(|params| (params.create_credits(), params));
|
||||
|
||||
self.peers.write().insert(*peer, Mutex::new(Peer {
|
||||
local_buffer: self.flow_params.create_buffer(),
|
||||
local_credits: self.flow_params.create_credits(),
|
||||
status: status.clone(),
|
||||
capabilities: capabilities.clone(),
|
||||
remote_flow: remote_flow,
|
||||
@@ -793,10 +804,10 @@ impl LightProtocol {
|
||||
let actual_cost = self.flow_params.compute_cost(request::Kind::Headers, response.len());
|
||||
assert!(max_cost >= actual_cost, "Actual cost exceeded maximum computed cost.");
|
||||
|
||||
let cur_buffer = peer.refund(&self.flow_params, max_cost - actual_cost);
|
||||
let cur_credits = peer.refund(&self.flow_params, max_cost - actual_cost);
|
||||
io.respond(packet::BLOCK_HEADERS, {
|
||||
let mut stream = RlpStream::new_list(3);
|
||||
stream.append(&req_id).append(&cur_buffer).begin_list(response.len());
|
||||
stream.append(&req_id).append(&cur_credits).begin_list(response.len());
|
||||
|
||||
for header in response {
|
||||
stream.append_raw(&header.into_inner(), 1);
|
||||
@@ -855,11 +866,11 @@ impl LightProtocol {
|
||||
let actual_cost = self.flow_params.compute_cost(request::Kind::Bodies, response_len);
|
||||
assert!(max_cost >= actual_cost, "Actual cost exceeded maximum computed cost.");
|
||||
|
||||
let cur_buffer = peer.refund(&self.flow_params, max_cost - actual_cost);
|
||||
let cur_credits = peer.refund(&self.flow_params, max_cost - actual_cost);
|
||||
|
||||
io.respond(packet::BLOCK_BODIES, {
|
||||
let mut stream = RlpStream::new_list(3);
|
||||
stream.append(&req_id).append(&cur_buffer).begin_list(response.len());
|
||||
stream.append(&req_id).append(&cur_credits).begin_list(response.len());
|
||||
|
||||
for body in response {
|
||||
match body {
|
||||
@@ -921,11 +932,11 @@ impl LightProtocol {
|
||||
let actual_cost = self.flow_params.compute_cost(request::Kind::Receipts, response_len);
|
||||
assert!(max_cost >= actual_cost, "Actual cost exceeded maximum computed cost.");
|
||||
|
||||
let cur_buffer = peer.refund(&self.flow_params, max_cost - actual_cost);
|
||||
let cur_credits = peer.refund(&self.flow_params, max_cost - actual_cost);
|
||||
|
||||
io.respond(packet::RECEIPTS, {
|
||||
let mut stream = RlpStream::new_list(3);
|
||||
stream.append(&req_id).append(&cur_buffer).begin_list(response.len());
|
||||
stream.append(&req_id).append(&cur_credits).begin_list(response.len());
|
||||
|
||||
for receipts in response {
|
||||
stream.append_raw(&receipts, 1);
|
||||
@@ -995,11 +1006,11 @@ impl LightProtocol {
|
||||
let actual_cost = self.flow_params.compute_cost(request::Kind::StateProofs, response_len);
|
||||
assert!(max_cost >= actual_cost, "Actual cost exceeded maximum computed cost.");
|
||||
|
||||
let cur_buffer = peer.refund(&self.flow_params, max_cost - actual_cost);
|
||||
let cur_credits = peer.refund(&self.flow_params, max_cost - actual_cost);
|
||||
|
||||
io.respond(packet::PROOFS, {
|
||||
let mut stream = RlpStream::new_list(3);
|
||||
stream.append(&req_id).append(&cur_buffer).begin_list(response.len());
|
||||
stream.append(&req_id).append(&cur_credits).begin_list(response.len());
|
||||
|
||||
for proof in response {
|
||||
stream.append_raw(&proof, 1);
|
||||
@@ -1067,11 +1078,11 @@ impl LightProtocol {
|
||||
let actual_cost = self.flow_params.compute_cost(request::Kind::Codes, response_len);
|
||||
assert!(max_cost >= actual_cost, "Actual cost exceeded maximum computed cost.");
|
||||
|
||||
let cur_buffer = peer.refund(&self.flow_params, max_cost - actual_cost);
|
||||
let cur_credits = peer.refund(&self.flow_params, max_cost - actual_cost);
|
||||
|
||||
io.respond(packet::CONTRACT_CODES, {
|
||||
let mut stream = RlpStream::new_list(3);
|
||||
stream.append(&req_id).append(&cur_buffer).begin_list(response.len());
|
||||
stream.append(&req_id).append(&cur_credits).begin_list(response.len());
|
||||
|
||||
for code in response {
|
||||
stream.append(&code);
|
||||
@@ -1140,11 +1151,11 @@ impl LightProtocol {
|
||||
let actual_cost = self.flow_params.compute_cost(request::Kind::HeaderProofs, response_len);
|
||||
assert!(max_cost >= actual_cost, "Actual cost exceeded maximum computed cost.");
|
||||
|
||||
let cur_buffer = peer.refund(&self.flow_params, max_cost - actual_cost);
|
||||
let cur_credits = peer.refund(&self.flow_params, max_cost - actual_cost);
|
||||
|
||||
io.respond(packet::HEADER_PROOFS, {
|
||||
let mut stream = RlpStream::new_list(3);
|
||||
stream.append(&req_id).append(&cur_buffer).begin_list(response.len());
|
||||
stream.append(&req_id).append(&cur_credits).begin_list(response.len());
|
||||
|
||||
for proof in response {
|
||||
stream.append_raw(&proof, 1);
|
||||
@@ -1182,6 +1193,90 @@ impl LightProtocol {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Receive a request for proof-of-execution.
|
||||
fn get_transaction_proof(&self, peer: &PeerId, io: &IoContext, raw: UntrustedRlp) -> Result<(), Error> {
|
||||
// refuse to execute more than this amount of gas at once.
|
||||
// this is appx. the point at which the proof of execution would no longer fit in
|
||||
// a single Devp2p packet.
|
||||
const MAX_GAS: usize = 50_000_000;
|
||||
use util::Uint;
|
||||
|
||||
let peers = self.peers.read();
|
||||
let peer = match peers.get(peer) {
|
||||
Some(peer) => peer,
|
||||
None => {
|
||||
debug!(target: "les", "Ignoring request from unknown peer");
|
||||
return Ok(())
|
||||
}
|
||||
};
|
||||
let mut peer = peer.lock();
|
||||
|
||||
let req_id: u64 = raw.val_at(0)?;
|
||||
|
||||
let req = {
|
||||
let req_rlp = raw.at(1)?;
|
||||
request::TransactionProof {
|
||||
at: req_rlp.val_at(0)?,
|
||||
from: req_rlp.val_at(1)?,
|
||||
action: if req_rlp.at(2)?.is_empty() {
|
||||
Action::Create
|
||||
} else {
|
||||
Action::Call(req_rlp.val_at(2)?)
|
||||
},
|
||||
gas: ::std::cmp::min(req_rlp.val_at(3)?, MAX_GAS.into()),
|
||||
gas_price: req_rlp.val_at(4)?,
|
||||
value: req_rlp.val_at(5)?,
|
||||
data: req_rlp.val_at(6)?,
|
||||
}
|
||||
};
|
||||
|
||||
// always charge the peer for all the gas.
|
||||
peer.deduct_max(&self.flow_params, request::Kind::TransactionProof, req.gas.low_u64() as usize)?;
|
||||
|
||||
let response = match self.provider.transaction_proof(req) {
|
||||
Some(res) => res,
|
||||
None => vec![],
|
||||
};
|
||||
|
||||
let cur_credits = peer.local_credits.current();
|
||||
|
||||
io.respond(packet::TRANSACTION_PROOF, {
|
||||
let mut stream = RlpStream::new_list(3);
|
||||
stream.append(&req_id).append(&cur_credits).begin_list(response.len());
|
||||
|
||||
for state_item in response {
|
||||
stream.append(&&state_item[..]);
|
||||
}
|
||||
|
||||
stream.out()
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Receive a response for proof-of-execution.
|
||||
fn transaction_proof(&self, peer: &PeerId, io: &IoContext, raw: UntrustedRlp) -> Result<(), Error> {
|
||||
let id_guard = self.pre_verify_response(peer, request::Kind::HeaderProofs, &raw)?;
|
||||
let raw_proof: Vec<DBValue> = raw.at(2)?.iter()
|
||||
.map(|rlp| {
|
||||
let mut db_val = DBValue::new();
|
||||
db_val.append_slice(rlp.data()?);
|
||||
Ok(db_val)
|
||||
})
|
||||
.collect::<Result<Vec<_>, ::rlp::DecoderError>>()?;
|
||||
|
||||
let req_id = id_guard.defuse();
|
||||
for handler in &self.handlers {
|
||||
handler.on_transaction_proof(&Ctx {
|
||||
peer: *peer,
|
||||
io: io,
|
||||
proto: self,
|
||||
}, req_id, &raw_proof);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Receive a set of transactions to relay.
|
||||
fn relay_transactions(&self, peer: &PeerId, io: &IoContext, data: UntrustedRlp) -> Result<(), Error> {
|
||||
const MAX_TRANSACTIONS: usize = 256;
|
||||
@@ -1330,6 +1425,25 @@ fn encode_request(req: &Request, req_id: usize) -> Vec<u8> {
|
||||
.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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,14 +14,14 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! LES buffer flow management.
|
||||
//! Request credit management.
|
||||
//!
|
||||
//! Every request in the LES protocol leads to a reduction
|
||||
//! of the requester's buffer value as a rate-limiting mechanism.
|
||||
//! This buffer value will recharge at a set rate.
|
||||
//! Every request in the light protocol leads to a reduction
|
||||
//! of the requester's amount of credits as a rate-limiting mechanism.
|
||||
//! The amount of credits will recharge at a set rate.
|
||||
//!
|
||||
//! This module provides an interface for configuration of buffer
|
||||
//! flow costs and recharge rates.
|
||||
//! This module provides an interface for configuration of
|
||||
//! costs and recharge rates of request credits.
|
||||
//!
|
||||
//! Current default costs are picked completely arbitrarily, not based
|
||||
//! on any empirical timings or mathematical models.
|
||||
@@ -38,19 +38,19 @@ use time::{Duration, SteadyTime};
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Cost(pub U256, pub U256);
|
||||
|
||||
/// Buffer value.
|
||||
/// Credits value.
|
||||
///
|
||||
/// Produced and recharged using `FlowParams`.
|
||||
/// Definitive updates can be made as well -- these will reset the recharge
|
||||
/// point to the time of the update.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Buffer {
|
||||
pub struct Credits {
|
||||
estimate: U256,
|
||||
recharge_point: SteadyTime,
|
||||
}
|
||||
|
||||
impl Buffer {
|
||||
/// Get the current buffer value.
|
||||
impl Credits {
|
||||
/// Get the current amount of credits..
|
||||
pub fn current(&self) -> U256 { self.estimate.clone() }
|
||||
|
||||
/// Make a definitive update.
|
||||
@@ -61,7 +61,7 @@ impl Buffer {
|
||||
self.recharge_point = SteadyTime::now();
|
||||
}
|
||||
|
||||
/// Attempt to apply the given cost to the buffer.
|
||||
/// Attempt to apply the given cost to the amount of credits.
|
||||
///
|
||||
/// If successful, the cost will be deducted successfully.
|
||||
///
|
||||
@@ -69,7 +69,7 @@ impl Buffer {
|
||||
/// error will be produced.
|
||||
pub fn deduct_cost(&mut self, cost: U256) -> Result<(), Error> {
|
||||
match cost > self.estimate {
|
||||
true => Err(Error::BufferEmpty),
|
||||
true => Err(Error::NoCredits),
|
||||
false => {
|
||||
self.estimate = self.estimate - cost;
|
||||
Ok(())
|
||||
@@ -81,12 +81,13 @@ impl Buffer {
|
||||
/// 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()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -112,7 +114,7 @@ impl RlpEncodable for CostTable {
|
||||
.append(&cost.1);
|
||||
}
|
||||
|
||||
s.begin_list(6);
|
||||
s.begin_list(7);
|
||||
|
||||
append_cost(s, packet::GET_BLOCK_HEADERS, &self.headers);
|
||||
append_cost(s, packet::GET_BLOCK_BODIES, &self.bodies);
|
||||
@@ -120,6 +122,7 @@ impl RlpEncodable for CostTable {
|
||||
append_cost(s, packet::GET_PROOFS, &self.state_proofs);
|
||||
append_cost(s, packet::GET_CONTRACT_CODES, &self.contract_codes);
|
||||
append_cost(s, packet::GET_HEADER_PROOFS, &self.header_proofs);
|
||||
append_cost(s, packet::GET_TRANSACTION_PROOF, &self.transaction_proof);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,6 +136,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 +154,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,11 +166,12 @@ 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"))?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// A buffer-flow manager handles costs, recharge, limits
|
||||
/// Handles costs, recharge, limits of request credits.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct FlowParams {
|
||||
costs: CostTable,
|
||||
@@ -175,7 +181,7 @@ pub struct FlowParams {
|
||||
|
||||
impl FlowParams {
|
||||
/// Create new flow parameters from a request cost table,
|
||||
/// buffer limit, and (minimum) rate of recharge.
|
||||
/// credit limit, and (minimum) rate of recharge.
|
||||
pub fn new(limit: U256, costs: CostTable, recharge: U256) -> Self {
|
||||
FlowParams {
|
||||
costs: costs,
|
||||
@@ -197,11 +203,12 @@ impl FlowParams {
|
||||
state_proofs: free_cost.clone(),
|
||||
contract_codes: free_cost.clone(),
|
||||
header_proofs: free_cost.clone(),
|
||||
transaction_proof: free_cost,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a reference to the buffer limit.
|
||||
/// Get a reference to the credit limit.
|
||||
pub fn limit(&self) -> &U256 { &self.limit }
|
||||
|
||||
/// Get a reference to the cost table.
|
||||
@@ -220,6 +227,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();
|
||||
@@ -227,10 +235,10 @@ impl FlowParams {
|
||||
}
|
||||
|
||||
/// Compute the maximum number of costs of a specific kind which can be made
|
||||
/// with the given buffer.
|
||||
/// with the given amount of credits
|
||||
/// Saturates at `usize::max()`. This is not a problem in practice because
|
||||
/// this amount of requests is already prohibitively large.
|
||||
pub fn max_amount(&self, buffer: &Buffer, kind: request::Kind) -> usize {
|
||||
pub fn max_amount(&self, credits: &Credits, kind: request::Kind) -> usize {
|
||||
use util::Uint;
|
||||
use std::usize;
|
||||
|
||||
@@ -241,9 +249,10 @@ 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 = buffer.current();
|
||||
let start = credits.current();
|
||||
|
||||
if start <= cost.0 {
|
||||
return 0;
|
||||
@@ -259,36 +268,36 @@ impl FlowParams {
|
||||
}
|
||||
}
|
||||
|
||||
/// Create initial buffer parameter.
|
||||
pub fn create_buffer(&self) -> Buffer {
|
||||
Buffer {
|
||||
/// Create initial credits..
|
||||
pub fn create_credits(&self) -> Credits {
|
||||
Credits {
|
||||
estimate: self.limit,
|
||||
recharge_point: SteadyTime::now(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Recharge the buffer based on time passed since last
|
||||
/// Recharge the given credits based on time passed since last
|
||||
/// update.
|
||||
pub fn recharge(&self, buf: &mut Buffer) {
|
||||
pub fn recharge(&self, credits: &mut Credits) {
|
||||
let now = SteadyTime::now();
|
||||
|
||||
// recompute and update only in terms of full seconds elapsed
|
||||
// in order to keep the estimate as an underestimate.
|
||||
let elapsed = (now - buf.recharge_point).num_seconds();
|
||||
buf.recharge_point = buf.recharge_point + Duration::seconds(elapsed);
|
||||
let elapsed = (now - credits.recharge_point).num_seconds();
|
||||
credits.recharge_point = credits.recharge_point + Duration::seconds(elapsed);
|
||||
|
||||
let elapsed: U256 = elapsed.into();
|
||||
|
||||
buf.estimate = ::std::cmp::min(self.limit, buf.estimate + (elapsed * self.recharge));
|
||||
credits.estimate = ::std::cmp::min(self.limit, credits.estimate + (elapsed * self.recharge));
|
||||
}
|
||||
|
||||
/// Refund some buffer which was previously deducted.
|
||||
/// Refund some credits which were previously deducted.
|
||||
/// Does not update the recharge timestamp.
|
||||
pub fn refund(&self, buf: &mut Buffer, refund_amount: U256) {
|
||||
buf.estimate = buf.estimate + refund_amount;
|
||||
pub fn refund(&self, credits: &mut Credits, refund_amount: U256) {
|
||||
credits.estimate = credits.estimate + refund_amount;
|
||||
|
||||
if buf.estimate > self.limit {
|
||||
buf.estimate = self.limit
|
||||
if credits.estimate > self.limit {
|
||||
credits.estimate = self.limit
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -318,20 +327,20 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn buffer_mechanism() {
|
||||
fn credits_mechanism() {
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
let flow_params = FlowParams::new(100.into(), Default::default(), 20.into());
|
||||
let mut buffer = flow_params.create_buffer();
|
||||
let mut credits = flow_params.create_credits();
|
||||
|
||||
assert!(buffer.deduct_cost(101.into()).is_err());
|
||||
assert!(buffer.deduct_cost(10.into()).is_ok());
|
||||
assert!(credits.deduct_cost(101.into()).is_err());
|
||||
assert!(credits.deduct_cost(10.into()).is_ok());
|
||||
|
||||
thread::sleep(Duration::from_secs(1));
|
||||
|
||||
flow_params.recharge(&mut buffer);
|
||||
flow_params.recharge(&mut credits);
|
||||
|
||||
assert_eq!(buffer.estimate, 100.into());
|
||||
assert_eq!(credits.estimate, 100.into());
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
use rlp::{DecoderError, RlpDecodable, RlpEncodable, RlpStream, Stream, UntrustedRlp, View};
|
||||
use util::{H256, U256};
|
||||
|
||||
use super::buffer_flow::FlowParams;
|
||||
use super::request_credits::FlowParams;
|
||||
|
||||
// recognized handshake/announcement keys.
|
||||
// unknown keys are to be skipped, known keys have a defined order.
|
||||
@@ -207,7 +207,7 @@ impl Capabilities {
|
||||
/// Attempt to parse a handshake message into its three parts:
|
||||
/// - chain status
|
||||
/// - serving capabilities
|
||||
/// - buffer flow parameters
|
||||
/// - request credit parameters
|
||||
pub fn parse_handshake(rlp: UntrustedRlp) -> Result<(Status, Capabilities, Option<FlowParams>), DecoderError> {
|
||||
let mut parser = Parser {
|
||||
pos: 0,
|
||||
@@ -300,7 +300,7 @@ pub struct Announcement {
|
||||
pub serve_chain_since: Option<u64>,
|
||||
/// optional new transaction-relay capability. false means "no change"
|
||||
pub tx_relay: bool,
|
||||
// TODO: changes in buffer flow?
|
||||
// TODO: changes in request credits.
|
||||
}
|
||||
|
||||
/// Parse an announcement.
|
||||
@@ -372,7 +372,7 @@ pub fn write_announcement(announcement: &Announcement) -> Vec<u8> {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use super::super::buffer_flow::FlowParams;
|
||||
use super::super::request_credits::FlowParams;
|
||||
use util::{U256, H256, FixedHash};
|
||||
use rlp::{RlpStream, Stream ,UntrustedRlp, View};
|
||||
|
||||
|
||||
@@ -20,11 +20,11 @@
|
||||
use ethcore::blockchain_info::BlockChainInfo;
|
||||
use ethcore::client::{EachBlockWith, TestBlockChainClient};
|
||||
use ethcore::ids::BlockId;
|
||||
use ethcore::transaction::PendingTransaction;
|
||||
use ethcore::transaction::{Action, PendingTransaction};
|
||||
use ethcore::encoded;
|
||||
use network::{PeerId, NodeId};
|
||||
|
||||
use net::buffer_flow::FlowParams;
|
||||
use net::request_credits::FlowParams;
|
||||
use net::context::IoContext;
|
||||
use net::status::{Capabilities, Status, write_handshake};
|
||||
use net::{encode_request, LightProtocol, Params, packet, Peer};
|
||||
@@ -32,7 +32,7 @@ use provider::Provider;
|
||||
use request::{self, Request, Headers};
|
||||
|
||||
use rlp::*;
|
||||
use util::{Bytes, H256, U256};
|
||||
use util::{Address, Bytes, DBValue, H256, U256};
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
@@ -127,6 +127,10 @@ impl Provider for TestProvider {
|
||||
None
|
||||
}
|
||||
|
||||
fn transaction_proof(&self, _req: request::TransactionProof) -> Option<Vec<DBValue>> {
|
||||
None
|
||||
}
|
||||
|
||||
fn ready_transactions(&self) -> Vec<PendingTransaction> {
|
||||
self.0.client.ready_transactions()
|
||||
}
|
||||
@@ -203,7 +207,7 @@ fn genesis_mismatch() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn buffer_overflow() {
|
||||
fn credit_overflow() {
|
||||
let flow_params = make_flow_params();
|
||||
let capabilities = capabilities();
|
||||
|
||||
@@ -268,11 +272,11 @@ fn get_block_headers() {
|
||||
let headers: Vec<_> = (0..10).map(|i| provider.client.block_header(BlockId::Number(i + 1)).unwrap()).collect();
|
||||
assert_eq!(headers.len(), 10);
|
||||
|
||||
let new_buf = *flow_params.limit() - flow_params.compute_cost(request::Kind::Headers, 10);
|
||||
let new_creds = *flow_params.limit() - flow_params.compute_cost(request::Kind::Headers, 10);
|
||||
|
||||
let mut response_stream = RlpStream::new_list(3);
|
||||
|
||||
response_stream.append(&req_id).append(&new_buf).begin_list(10);
|
||||
response_stream.append(&req_id).append(&new_creds).begin_list(10);
|
||||
for header in headers {
|
||||
response_stream.append_raw(&header.into_inner(), 1);
|
||||
}
|
||||
@@ -317,11 +321,11 @@ fn get_block_bodies() {
|
||||
let bodies: Vec<_> = (0..10).map(|i| provider.client.block_body(BlockId::Number(i + 1)).unwrap()).collect();
|
||||
assert_eq!(bodies.len(), 10);
|
||||
|
||||
let new_buf = *flow_params.limit() - flow_params.compute_cost(request::Kind::Bodies, 10);
|
||||
let new_creds = *flow_params.limit() - flow_params.compute_cost(request::Kind::Bodies, 10);
|
||||
|
||||
let mut response_stream = RlpStream::new_list(3);
|
||||
|
||||
response_stream.append(&req_id).append(&new_buf).begin_list(10);
|
||||
response_stream.append(&req_id).append(&new_creds).begin_list(10);
|
||||
for body in bodies {
|
||||
response_stream.append_raw(&body.into_inner(), 1);
|
||||
}
|
||||
@@ -371,11 +375,11 @@ fn get_block_receipts() {
|
||||
.map(|hash| provider.client.block_receipts(hash).unwrap())
|
||||
.collect();
|
||||
|
||||
let new_buf = *flow_params.limit() - flow_params.compute_cost(request::Kind::Receipts, receipts.len());
|
||||
let new_creds = *flow_params.limit() - flow_params.compute_cost(request::Kind::Receipts, receipts.len());
|
||||
|
||||
let mut response_stream = RlpStream::new_list(3);
|
||||
|
||||
response_stream.append(&req_id).append(&new_buf).begin_list(receipts.len());
|
||||
response_stream.append(&req_id).append(&new_creds).begin_list(receipts.len());
|
||||
for block_receipts in receipts {
|
||||
response_stream.append_raw(&block_receipts, 1);
|
||||
}
|
||||
@@ -420,11 +424,11 @@ fn get_state_proofs() {
|
||||
vec![::util::sha3::SHA3_NULL_RLP.to_vec()],
|
||||
];
|
||||
|
||||
let new_buf = *flow_params.limit() - flow_params.compute_cost(request::Kind::StateProofs, 2);
|
||||
let new_creds = *flow_params.limit() - flow_params.compute_cost(request::Kind::StateProofs, 2);
|
||||
|
||||
let mut response_stream = RlpStream::new_list(3);
|
||||
|
||||
response_stream.append(&req_id).append(&new_buf).begin_list(2);
|
||||
response_stream.append(&req_id).append(&new_creds).begin_list(2);
|
||||
for proof in proofs {
|
||||
response_stream.begin_list(proof.len());
|
||||
for node in proof {
|
||||
@@ -472,11 +476,11 @@ fn get_contract_code() {
|
||||
key2.iter().chain(key2.iter()).cloned().collect(),
|
||||
];
|
||||
|
||||
let new_buf = *flow_params.limit() - flow_params.compute_cost(request::Kind::Codes, 2);
|
||||
let new_creds = *flow_params.limit() - flow_params.compute_cost(request::Kind::Codes, 2);
|
||||
|
||||
let mut response_stream = RlpStream::new_list(3);
|
||||
|
||||
response_stream.append(&req_id).append(&new_buf).begin_list(2);
|
||||
response_stream.append(&req_id).append(&new_creds).begin_list(2);
|
||||
for code in codes {
|
||||
response_stream.append(&code);
|
||||
}
|
||||
@@ -488,6 +492,56 @@ fn get_contract_code() {
|
||||
proto.handle_packet(&expected, &1, packet::GET_CONTRACT_CODES, &request_body);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn proof_of_execution() {
|
||||
let flow_params = FlowParams::new(5_000_000.into(), Default::default(), 0.into());
|
||||
let capabilities = capabilities();
|
||||
|
||||
let (provider, proto) = setup(flow_params.clone(), capabilities.clone());
|
||||
|
||||
let cur_status = status(provider.client.chain_info());
|
||||
|
||||
{
|
||||
let packet_body = write_handshake(&cur_status, &capabilities, Some(&flow_params));
|
||||
proto.on_connect(&1, &Expect::Send(1, packet::STATUS, packet_body.clone()));
|
||||
proto.handle_packet(&Expect::Nothing, &1, packet::STATUS, &packet_body);
|
||||
}
|
||||
|
||||
let req_id = 112;
|
||||
let mut request = Request::TransactionProof (request::TransactionProof {
|
||||
at: H256::default(),
|
||||
from: Address::default(),
|
||||
action: Action::Call(Address::default()),
|
||||
gas: 100.into(),
|
||||
gas_price: 0.into(),
|
||||
value: 0.into(),
|
||||
data: Vec::new(),
|
||||
});
|
||||
|
||||
// first: a valid amount to request execution of.
|
||||
let request_body = encode_request(&request, req_id);
|
||||
let response = {
|
||||
let new_creds = *flow_params.limit() - flow_params.compute_cost(request::Kind::TransactionProof, 100);
|
||||
|
||||
let mut response_stream = RlpStream::new_list(3);
|
||||
response_stream.append(&req_id).append(&new_creds).begin_list(0);
|
||||
|
||||
response_stream.out()
|
||||
};
|
||||
|
||||
let expected = Expect::Respond(packet::TRANSACTION_PROOF, response);
|
||||
proto.handle_packet(&expected, &1, packet::GET_TRANSACTION_PROOF, &request_body);
|
||||
|
||||
// next: way too much requested gas.
|
||||
if let Request::TransactionProof(ref mut req) = request {
|
||||
req.gas = 100_000_000.into();
|
||||
}
|
||||
let req_id = 113;
|
||||
let request_body = encode_request(&request, req_id);
|
||||
let expected = Expect::Punish(1);
|
||||
proto.handle_packet(&expected, &1, packet::GET_TRANSACTION_PROOF, &request_body);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn id_guard() {
|
||||
use super::request_set::RequestSet;
|
||||
@@ -515,10 +569,10 @@ fn id_guard() {
|
||||
pending_requests.insert(req_id_2, req, ::time::SteadyTime::now());
|
||||
|
||||
proto.peers.write().insert(peer_id, ::util::Mutex::new(Peer {
|
||||
local_buffer: flow_params.create_buffer(),
|
||||
local_credits: flow_params.create_credits(),
|
||||
status: status(provider.client.chain_info()),
|
||||
capabilities: capabilities.clone(),
|
||||
remote_flow: Some((flow_params.create_buffer(), flow_params)),
|
||||
remote_flow: Some((flow_params.create_credits(), flow_params)),
|
||||
sent_head: provider.client.chain_info().best_block_hash,
|
||||
last_update: ::time::SteadyTime::now(),
|
||||
pending_requests: pending_requests,
|
||||
|
||||
@@ -24,12 +24,14 @@ use std::sync::Arc;
|
||||
use ethcore::basic_account::BasicAccount;
|
||||
use ethcore::encoded;
|
||||
use ethcore::receipt::Receipt;
|
||||
use ethcore::state::ProvedExecution;
|
||||
use ethcore::executed::{Executed, ExecutionError};
|
||||
|
||||
use futures::{Async, Poll, Future};
|
||||
use futures::sync::oneshot::{self, Sender, Receiver};
|
||||
use network::PeerId;
|
||||
use rlp::{RlpStream, Stream};
|
||||
use util::{Bytes, RwLock, Mutex, U256};
|
||||
use util::{Bytes, DBValue, RwLock, Mutex, U256};
|
||||
use util::sha3::{SHA3_NULL_RLP, SHA3_EMPTY_LIST_RLP};
|
||||
|
||||
use net::{Handler, Status, Capabilities, Announcement, EventContext, BasicContext, ReqId};
|
||||
@@ -59,6 +61,7 @@ enum Pending {
|
||||
BlockReceipts(request::BlockReceipts, Sender<Vec<Receipt>>),
|
||||
Account(request::Account, Sender<BasicAccount>),
|
||||
Code(request::Code, Sender<Bytes>),
|
||||
TxProof(request::TransactionProof, Sender<Result<Executed, ExecutionError>>),
|
||||
}
|
||||
|
||||
/// On demand request service. See module docs for more details.
|
||||
@@ -418,6 +421,50 @@ impl OnDemand {
|
||||
self.orphaned_requests.write().push(pending)
|
||||
}
|
||||
|
||||
/// Request proof-of-execution for a transaction.
|
||||
pub fn transaction_proof(&self, ctx: &BasicContext, req: request::TransactionProof) -> Receiver<Result<Executed, ExecutionError>> {
|
||||
let (sender, receiver) = oneshot::channel();
|
||||
|
||||
self.dispatch_transaction_proof(ctx, req, sender);
|
||||
|
||||
receiver
|
||||
}
|
||||
|
||||
fn dispatch_transaction_proof(&self, ctx: &BasicContext, req: request::TransactionProof, sender: Sender<Result<Executed, ExecutionError>>) {
|
||||
let num = req.header.number();
|
||||
let les_req = LesRequest::TransactionProof(les_request::TransactionProof {
|
||||
at: req.header.hash(),
|
||||
from: req.tx.sender(),
|
||||
gas: req.tx.gas,
|
||||
gas_price: req.tx.gas_price,
|
||||
action: req.tx.action.clone(),
|
||||
value: req.tx.value,
|
||||
data: req.tx.data.clone(),
|
||||
});
|
||||
let pending = Pending::TxProof(req, sender);
|
||||
|
||||
// we're looking for a peer with serveStateSince(num)
|
||||
for (id, peer) in self.peers.read().iter() {
|
||||
if peer.capabilities.serve_state_since.as_ref().map_or(false, |x| *x >= num) {
|
||||
match ctx.request_from(*id, les_req.clone()) {
|
||||
Ok(req_id) => {
|
||||
trace!(target: "on_demand", "Assigning request to peer {}", id);
|
||||
self.pending_requests.write().insert(
|
||||
req_id,
|
||||
pending
|
||||
);
|
||||
return
|
||||
}
|
||||
Err(e) =>
|
||||
trace!(target: "on_demand", "Failed to make request of peer {}: {:?}", id, e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
trace!(target: "on_demand", "No suitable peer for request");
|
||||
self.orphaned_requests.write().push(pending)
|
||||
}
|
||||
|
||||
// dispatch orphaned requests, and discard those for which the corresponding
|
||||
// receiver has been dropped.
|
||||
fn dispatch_orphaned(&self, ctx: &BasicContext) {
|
||||
@@ -468,6 +515,8 @@ impl OnDemand {
|
||||
if !check_hangup(&mut sender) { self.dispatch_account(ctx, req, sender) },
|
||||
Pending::Code(req, mut sender) =>
|
||||
if !check_hangup(&mut sender) { self.dispatch_code(ctx, req, sender) },
|
||||
Pending::TxProof(req, mut sender) =>
|
||||
if !check_hangup(&mut sender) { self.dispatch_transaction_proof(ctx, req, sender) }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -690,6 +739,36 @@ impl Handler for OnDemand {
|
||||
}
|
||||
}
|
||||
|
||||
fn on_transaction_proof(&self, ctx: &EventContext, req_id: ReqId, items: &[DBValue]) {
|
||||
let peer = ctx.peer();
|
||||
let req = match self.pending_requests.write().remove(&req_id) {
|
||||
Some(req) => req,
|
||||
None => return,
|
||||
};
|
||||
|
||||
match req {
|
||||
Pending::TxProof(req, sender) => {
|
||||
match req.check_response(items) {
|
||||
ProvedExecution::Complete(executed) => {
|
||||
sender.complete(Ok(executed));
|
||||
return
|
||||
}
|
||||
ProvedExecution::Failed(err) => {
|
||||
sender.complete(Err(err));
|
||||
return
|
||||
}
|
||||
ProvedExecution::BadProof => {
|
||||
warn!("Error handling response for transaction proof request");
|
||||
ctx.disable_peer(peer);
|
||||
}
|
||||
}
|
||||
|
||||
self.dispatch_transaction_proof(ctx.as_basic(), req, sender);
|
||||
}
|
||||
_ => panic!("Only transaction proof request dispatches transaction proof requests; qed"),
|
||||
}
|
||||
}
|
||||
|
||||
fn tick(&self, ctx: &BasicContext) {
|
||||
self.dispatch_orphaned(ctx)
|
||||
}
|
||||
|
||||
@@ -16,12 +16,18 @@
|
||||
|
||||
//! Request types, verification, and verification errors.
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use ethcore::basic_account::BasicAccount;
|
||||
use ethcore::encoded;
|
||||
use ethcore::engines::Engine;
|
||||
use ethcore::env_info::EnvInfo;
|
||||
use ethcore::receipt::Receipt;
|
||||
use ethcore::state::{self, ProvedExecution};
|
||||
use ethcore::transaction::SignedTransaction;
|
||||
|
||||
use rlp::{RlpStream, Stream, UntrustedRlp, View};
|
||||
use util::{Address, Bytes, HashDB, H256, U256};
|
||||
use util::{Address, Bytes, DBValue, HashDB, H256, U256};
|
||||
use util::memorydb::MemoryDB;
|
||||
use util::sha3::Hashable;
|
||||
use util::trie::{Trie, TrieDB, TrieError};
|
||||
@@ -231,6 +237,33 @@ impl Code {
|
||||
}
|
||||
}
|
||||
|
||||
/// Request for transaction execution, along with the parts necessary to verify the proof.
|
||||
pub struct TransactionProof {
|
||||
/// The transaction to request proof of.
|
||||
pub tx: SignedTransaction,
|
||||
/// Block header.
|
||||
pub header: encoded::Header,
|
||||
/// Transaction environment info.
|
||||
pub env_info: EnvInfo,
|
||||
/// Consensus engine.
|
||||
pub engine: Arc<Engine>,
|
||||
}
|
||||
|
||||
impl TransactionProof {
|
||||
/// Check the proof, returning the proved execution or indicate that the proof was bad.
|
||||
pub fn check_response(&self, state_items: &[DBValue]) -> ProvedExecution {
|
||||
let root = self.header.state_root();
|
||||
|
||||
state::check_proof(
|
||||
state_items,
|
||||
root,
|
||||
&self.tx,
|
||||
&*self.engine,
|
||||
&self.env_info,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
@@ -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<PendingTransaction>;
|
||||
|
||||
/// 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<Vec<DBValue>>;
|
||||
}
|
||||
|
||||
// Implementation of a light client data provider for a client.
|
||||
@@ -283,6 +287,26 @@ impl<T: ProvingBlockChainClient + ?Sized> Provider for T {
|
||||
}
|
||||
}
|
||||
|
||||
fn transaction_proof(&self, req: request::TransactionProof) -> Option<Vec<DBValue>> {
|
||||
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<PendingTransaction> {
|
||||
BlockChainClient::ready_transactions(self)
|
||||
}
|
||||
@@ -343,6 +367,10 @@ impl<L: AsLightClient + Send + Sync> Provider for LightProvider<L> {
|
||||
None
|
||||
}
|
||||
|
||||
fn transaction_proof(&self, _req: request::TransactionProof) -> Option<Vec<DBValue>> {
|
||||
None
|
||||
}
|
||||
|
||||
fn ready_transactions(&self) -> Vec<PendingTransaction> {
|
||||
let chain_info = self.chain_info();
|
||||
self.txqueue.read().ready_transactions(chain_info.best_block_number, chain_info.best_block_timestamp)
|
||||
|
||||
@@ -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<HeaderProof>,
|
||||
}
|
||||
|
||||
/// 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<u8>,
|
||||
}
|
||||
|
||||
/// 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user