buffer flow basics, implement cost table
This commit is contained in:
		
							parent
							
								
									d573ef3cc2
								
							
						
					
					
						commit
						051effe9f8
					
				| @ -12,4 +12,5 @@ ethcore = { path = ".." } | ||||
| ethcore-util = { path = "../../util" } | ||||
| ethcore-network = { path = "../../util/network" } | ||||
| ethcore-io = { path = "../../util/io" } | ||||
| rlp = { path = "../../util/rlp" } | ||||
| rlp = { path = "../../util/rlp" } | ||||
| time = "0.1" | ||||
| @ -38,6 +38,7 @@ extern crate ethcore_network as network; | ||||
| extern crate ethcore_io as io; | ||||
| extern crate ethcore; | ||||
| extern crate rlp; | ||||
| extern crate time; | ||||
| 
 | ||||
| #[macro_use] | ||||
| extern crate log; | ||||
| @ -24,19 +24,245 @@ | ||||
| //! flow costs and recharge rates.
 | ||||
| 
 | ||||
| use request::{self, Request}; | ||||
| use super::packet; | ||||
| 
 | ||||
| /// Manages buffer flow costs for specific requests.
 | ||||
| pub struct FlowManager; | ||||
| use rlp::*; | ||||
| use util::U256; | ||||
| use time::{Duration, SteadyTime}; | ||||
| 
 | ||||
| impl FlowManager { | ||||
| 	/// Estimate the maximum cost of this request.
 | ||||
| 	pub fn estimate_cost(&self, req: &request::Request) -> usize { | ||||
| 		unimplemented!() | ||||
| /// A request cost specification.
 | ||||
| #[derive(Debug, Clone, PartialEq, Eq)] | ||||
| pub struct Cost(pub U256, pub U256); | ||||
| 
 | ||||
| /// An error: insufficient buffer.
 | ||||
| #[derive(Debug, Clone, PartialEq, Eq)] | ||||
| pub struct InsufficientBuffer; | ||||
| 
 | ||||
| /// Buffer 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 { | ||||
| 	estimate: U256, | ||||
| 	recharge_point: SteadyTime, | ||||
| } | ||||
| 
 | ||||
| impl Buffer { | ||||
| 	/// Make a definitive update.
 | ||||
| 	/// This will be the value obtained after receiving
 | ||||
| 	/// a response to a request.
 | ||||
| 	pub fn update_to(&mut self, value: U256) { | ||||
| 		self.estimate = value; | ||||
| 		self.recharge_point = SteadyTime::now(); | ||||
| 	} | ||||
| 
 | ||||
| 	/// Get an exact cost based on request kind and amount of requests fulfilled.
 | ||||
| 	pub fn exact_cost(&self, kind: request::Kind, amount: usize) -> usize { | ||||
| 		unimplemented!() | ||||
| 	/// Attempt to apply the given cost to the buffer.
 | ||||
| 	/// If successful, the cost will be deducted successfully.
 | ||||
| 	/// If unsuccessful, the structure will be unaltered an an
 | ||||
| 	/// error will be produced.
 | ||||
| 	pub fn deduct_cost(&mut self, cost: U256) -> Result<(), InsufficientBuffer> { | ||||
| 		match cost > self.estimate { | ||||
| 			true => Err(InsufficientBuffer), | ||||
| 			false => { | ||||
| 				self.estimate = self.estimate - cost; | ||||
| 				Ok(()) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| /// A cost table, mapping requests to base and per-request costs.
 | ||||
| #[derive(Debug, Clone, PartialEq, Eq)] | ||||
| pub struct CostTable { | ||||
| 	headers: Cost, | ||||
| 	bodies: Cost, | ||||
| 	receipts: Cost, | ||||
| 	state_proofs: Cost, | ||||
| 	contract_codes: Cost, | ||||
| 	header_proofs: Cost, | ||||
| } | ||||
| 
 | ||||
| impl Default for CostTable { | ||||
| 	fn default() -> Self { | ||||
| 		// arbitrarily chosen constants.
 | ||||
| 		CostTable { | ||||
| 			headers: Cost(100000.into(), 10000.into()), | ||||
| 			bodies: Cost(150000.into(), 15000.into()), | ||||
| 			receipts: Cost(50000.into(), 5000.into()), | ||||
| 			state_proofs: Cost(250000.into(), 25000.into()), | ||||
| 			contract_codes: Cost(200000.into(), 20000.into()), | ||||
| 			header_proofs: Cost(150000.into(), 15000.into()), | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| impl RlpEncodable for CostTable { | ||||
| 	fn rlp_append(&self, s: &mut RlpStream) { | ||||
| 		fn append_cost(s: &mut RlpStream, msg_id: u8, cost: &Cost) { | ||||
| 			s.begin_list(3) | ||||
| 				.append(&msg_id) | ||||
| 				.append(&cost.0) | ||||
| 				.append(&cost.1); | ||||
| 		} | ||||
| 
 | ||||
| 		s.begin_list(6); | ||||
| 
 | ||||
| 		append_cost(s, packet::GET_BLOCK_HEADERS, &self.headers); | ||||
| 		append_cost(s, packet::GET_BLOCK_BODIES, &self.bodies); | ||||
| 		append_cost(s, packet::GET_RECEIPTS, &self.receipts); | ||||
| 		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); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| impl RlpDecodable for CostTable { | ||||
| 	fn decode<D>(decoder: &D) -> Result<Self, DecoderError> where D: Decoder { | ||||
| 		let rlp = decoder.as_rlp(); | ||||
| 
 | ||||
| 		let mut headers = None; | ||||
| 		let mut bodies = None; | ||||
| 		let mut receipts = None; | ||||
| 		let mut state_proofs = None; | ||||
| 		let mut contract_codes = None; | ||||
| 		let mut header_proofs = None; | ||||
| 
 | ||||
| 		for row in rlp.iter() { | ||||
| 			let msg_id: u8 = try!(row.val_at(0)); | ||||
| 			let cost = { | ||||
| 				let base = try!(row.val_at(1)); | ||||
| 				let per = try!(row.val_at(2)); | ||||
| 
 | ||||
| 				Cost(base, per) | ||||
| 			}; | ||||
| 
 | ||||
| 			match msg_id { | ||||
| 				packet::GET_BLOCK_HEADERS => headers = Some(cost), | ||||
| 				packet::GET_BLOCK_BODIES => bodies = Some(cost), | ||||
| 				packet::GET_RECEIPTS => receipts = Some(cost), | ||||
| 				packet::GET_PROOFS => state_proofs = Some(cost), | ||||
| 				packet::GET_CONTRACT_CODES => contract_codes = Some(cost), | ||||
| 				packet::GET_HEADER_PROOFS => header_proofs = Some(cost), | ||||
| 				_ => return Err(DecoderError::Custom("Unrecognized message in cost table")), | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		Ok(CostTable { | ||||
| 			headers: try!(headers.ok_or(DecoderError::Custom("No headers cost specified"))), | ||||
| 			bodies: try!(bodies.ok_or(DecoderError::Custom("No bodies cost specified"))), | ||||
| 			receipts: try!(receipts.ok_or(DecoderError::Custom("No receipts cost specified"))), | ||||
| 			state_proofs: try!(state_proofs.ok_or(DecoderError::Custom("No proofs cost specified"))), | ||||
| 			contract_codes: try!(contract_codes.ok_or(DecoderError::Custom("No contract codes specified"))), | ||||
| 			header_proofs: try!(header_proofs.ok_or(DecoderError::Custom("No header proofs cost specified"))), | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| /// A buffer-flow manager handles costs, recharge, limits
 | ||||
| #[derive(Debug, Clone, PartialEq)] | ||||
| pub struct FlowParams { | ||||
| 	costs: CostTable, | ||||
| 	limit: U256, | ||||
| 	recharge: U256, | ||||
| } | ||||
| 
 | ||||
| impl FlowParams { | ||||
| 	/// Create new flow parameters from a request cost table,
 | ||||
| 	/// buffer limit, and (minimum) rate of recharge.
 | ||||
| 	pub fn new(costs: CostTable, limit: U256, recharge: U256) -> Self { | ||||
| 		FlowParams { | ||||
| 			costs: costs, | ||||
| 			limit: limit, | ||||
| 			recharge: recharge, | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	/// Estimate the maximum cost of the request.
 | ||||
| 	pub fn max_cost(&self, req: &Request) -> U256 { | ||||
| 		let amount = match *req { | ||||
| 			Request::Headers(ref req) => req.max as usize, | ||||
| 			Request::Bodies(ref req) => req.block_hashes.len(), | ||||
| 			Request::Receipts(ref req) => req.block_hashes.len(), | ||||
| 			Request::StateProofs(ref req) => req.requests.len(), | ||||
| 			Request::Codes(ref req) => req.code_requests.len(), | ||||
| 			Request::HeaderProofs(ref req) => req.requests.len(), | ||||
| 		}; | ||||
| 
 | ||||
| 		self.actual_cost(req.kind(), amount) | ||||
| 	} | ||||
| 
 | ||||
| 	/// Compute the actual cost of a request, given the kind of request
 | ||||
| 	/// and number of requests made.
 | ||||
| 	pub fn actual_cost(&self, kind: request::Kind, amount: usize) -> U256 { | ||||
| 		let cost = match kind { | ||||
| 			request::Kind::Headers => &self.costs.headers, | ||||
| 			request::Kind::Bodies => &self.costs.bodies, | ||||
| 			request::Kind::Receipts => &self.costs.receipts, | ||||
| 			request::Kind::StateProofs => &self.costs.state_proofs, | ||||
| 			request::Kind::Codes => &self.costs.contract_codes, | ||||
| 			request::Kind::HeaderProofs => &self.costs.header_proofs, | ||||
| 		}; | ||||
| 
 | ||||
| 		let amount: U256 = amount.into(); | ||||
| 		cost.0 + (amount * cost.1) | ||||
| 	} | ||||
| 
 | ||||
| 	/// Create initial buffer parameter.
 | ||||
| 	pub fn create_buffer(&self) -> Buffer { | ||||
| 		Buffer { | ||||
| 			estimate: self.limit, | ||||
| 			recharge_point: SteadyTime::now(), | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	/// Recharge the buffer based on time passed since last
 | ||||
| 	/// update.
 | ||||
| 	pub fn recharge(&self, buf: &mut Buffer) { | ||||
| 		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: U256 = elapsed.into(); | ||||
| 
 | ||||
| 		buf.estimate = ::std::cmp::min(self.limit, buf.estimate + (elapsed * self.recharge)); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| #[cfg(test)] | ||||
| mod tests { | ||||
| 	use super::*; | ||||
| 	use util::U256; | ||||
| 
 | ||||
| 	#[test] | ||||
| 	fn should_serialize_cost_table() { | ||||
| 		let costs = CostTable::default(); | ||||
| 		let serialized = ::rlp::encode(&costs); | ||||
| 
 | ||||
| 		let new_costs: CostTable = ::rlp::decode(&*serialized); | ||||
| 
 | ||||
| 		assert_eq!(costs, new_costs); | ||||
| 	} | ||||
| 
 | ||||
| 	#[test] | ||||
| 	fn buffer_mechanism() { | ||||
| 		use std::thread; | ||||
| 		use std::time::Duration; | ||||
| 
 | ||||
| 		let flow_params = FlowParams::new(Default::default(), 100.into(), 20.into()); | ||||
| 		let mut buffer =  flow_params.create_buffer(); | ||||
| 
 | ||||
| 		assert!(buffer.deduct_cost(101.into()).is_err()); | ||||
| 		assert!(buffer.deduct_cost(10.into()).is_ok()); | ||||
| 
 | ||||
| 		thread::sleep(Duration::from_secs(1)); | ||||
| 
 | ||||
| 		flow_params.recharge(&mut buffer); | ||||
| 
 | ||||
| 		assert_eq!(buffer.estimate, 100.into()); | ||||
| 	} | ||||
| } | ||||
| @ -30,7 +30,7 @@ use std::sync::atomic::{AtomicUsize, Ordering}; | ||||
| 
 | ||||
| use provider::Provider; | ||||
| use request::{self, Request}; | ||||
| use self::buffer_flow::FlowManager; | ||||
| use self::buffer_flow::FlowParams; | ||||
| 
 | ||||
| mod buffer_flow; | ||||
| 
 | ||||
| @ -77,33 +77,6 @@ mod packet { | ||||
| 	// request and response for header proofs in a CHT.
 | ||||
| 	pub const GET_HEADER_PROOFS: u8 = 0x0d; | ||||
| 	pub const HEADER_PROOFS: u8 = 0x0e; | ||||
| 
 | ||||
| 	// broadcast dynamic capabilities.
 | ||||
| 	pub const CAPABILITIES: u8 = 0x0f; | ||||
| 
 | ||||
| 	// request and response for block-level state deltas.
 | ||||
| 	pub const GET_BLOCK_DELTAS: u8 = 0x10; | ||||
| 	pub const BLOCK_DELTAS: u8 = 0x11; | ||||
| 
 | ||||
| 	// request and response for transaction proofs.
 | ||||
| 	pub const GET_TRANSACTION_PROOFS: u8 = 0x12; | ||||
| 	pub const TRANSACTION_PROOFS: u8 = 0x13; | ||||
| } | ||||
| 
 | ||||
| // helper macro for disconnecting peer on error while returning
 | ||||
| // the value if ok.
 | ||||
| // requires that error types are debug.
 | ||||
| macro_rules! try_dc { | ||||
| 	($io: expr, $peer: expr, $e: expr) => { | ||||
| 		match $e { | ||||
| 			Ok(x) => x, | ||||
| 			Err(e) => { | ||||
| 				debug!(target: "les", "disconnecting peer {} due to error {:?}", $peer, e); | ||||
| 				$io.disconnect_peer($peer); | ||||
| 				return; | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| struct Requested { | ||||
| @ -209,26 +182,7 @@ impl LightProtocol { | ||||
| 	fn get_block_headers(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) { | ||||
| 		const MAX_HEADERS: u64 = 512; | ||||
| 
 | ||||
| 		let req_id: u64 = try_dc!(io, *peer, data.val_at(0)); | ||||
| 		let req = request::Headers { | ||||
| 			block: try_dc!(io, *peer, data.at(1).and_then(|block_list| { | ||||
| 				Ok((try!(block_list.val_at(0)), try!(block_list.val_at(1)))) | ||||
| 			})), | ||||
| 			max: ::std::cmp::min(MAX_HEADERS, try_dc!(io, *peer, data.val_at(2))), | ||||
| 			skip: try_dc!(io, *peer, data.val_at(3)), | ||||
| 			reverse: try_dc!(io, *peer, data.val_at(4)), | ||||
| 		}; | ||||
| 
 | ||||
| 		let res = self.provider.block_headers(req); | ||||
| 
 | ||||
| 		let mut res_stream = RlpStream::new_list(2 + res.len()); | ||||
| 		res_stream.append(&req_id); | ||||
| 		res_stream.append(&0u64); // TODO: Buffer Flow.
 | ||||
| 		for raw_header in res { | ||||
| 			res_stream.append_raw(&raw_header, 1); | ||||
| 		} | ||||
| 
 | ||||
| 		try_dc!(io, *peer, io.respond(packet::BLOCK_HEADERS, res_stream.out())) | ||||
| 		unimplemented!() | ||||
| 	} | ||||
| 
 | ||||
| 	// Receive a response for block headers.
 | ||||
| @ -292,31 +246,6 @@ impl LightProtocol { | ||||
| 	fn relay_transactions(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) { | ||||
| 		unimplemented!() | ||||
| 	} | ||||
| 
 | ||||
| 	// Receive updated capabilities from a peer.
 | ||||
| 	fn capabilities(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) { | ||||
| 		unimplemented!() | ||||
| 	} | ||||
| 
 | ||||
| 	// Handle a request for block deltas.
 | ||||
| 	fn get_block_deltas(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) { | ||||
| 		unimplemented!() | ||||
| 	} | ||||
| 
 | ||||
| 	// Receive block deltas.
 | ||||
| 	fn block_deltas(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) { | ||||
| 		unimplemented!() | ||||
| 	} | ||||
| 
 | ||||
| 	// Handle a request for transaction proofs.
 | ||||
| 	fn get_transaction_proofs(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) { | ||||
| 		unimplemented!() | ||||
| 	} | ||||
| 
 | ||||
| 	// Receive transaction proofs.
 | ||||
| 	fn transaction_proofs(&self, peer: &PeerId, io: &NetworkContext, data: UntrustedRlp) { | ||||
| 		unimplemented!() | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| impl NetworkProtocolHandler for LightProtocol { | ||||
| @ -346,16 +275,6 @@ impl NetworkProtocolHandler for LightProtocol { | ||||
| 			packet::CONTRACT_CODES => self.contract_code(peer, io, rlp), | ||||
| 
 | ||||
| 			packet::SEND_TRANSACTIONS => self.relay_transactions(peer, io, rlp), | ||||
| 			packet::CAPABILITIES => self.capabilities(peer, io, rlp), | ||||
| 
 | ||||
| 			packet::GET_HEADER_PROOFS => self.get_header_proofs(peer, io, rlp), | ||||
| 			packet::HEADER_PROOFS => self.header_proofs(peer, io, rlp), | ||||
| 
 | ||||
| 			packet::GET_BLOCK_DELTAS => self.get_block_deltas(peer, io, rlp), | ||||
| 			packet::BLOCK_DELTAS => self.block_deltas(peer, io, rlp), | ||||
| 
 | ||||
| 			packet::GET_TRANSACTION_PROOFS => self.get_transaction_proofs(peer, io, rlp), | ||||
| 			packet::TRANSACTION_PROOFS => self.transaction_proofs(peer, io, rlp), | ||||
| 
 | ||||
| 			other => { | ||||
| 				debug!(target: "les", "Disconnecting peer {} on unexpected packet {}", peer, other); | ||||
|  | ||||
| @ -51,9 +51,9 @@ pub struct Receipts { | ||||
| 	pub block_hashes: Vec<H256>, | ||||
| } | ||||
| 
 | ||||
| /// A request for state proofs.
 | ||||
| /// A request for a state proof
 | ||||
| #[derive(Debug, Clone, PartialEq, Eq)] | ||||
| pub struct StateProofs { | ||||
| pub struct StateProof { | ||||
| 	/// Block hash to query state from.
 | ||||
| 	pub block: H256, | ||||
| 	/// Key of the state trie -- corresponds to account hash.
 | ||||
| @ -65,6 +65,13 @@ pub struct StateProofs { | ||||
| 	pub from_level: u32, // could even safely be u8; trie w/ 32-byte key can be at most 64-levels deep.
 | ||||
| } | ||||
| 
 | ||||
| /// A request for state proofs.
 | ||||
| #[derive(Debug, Clone, PartialEq, Eq)] | ||||
| pub struct StateProofs { | ||||
| 	/// All the proof requests.
 | ||||
| 	pub requests: Vec<StateProof>, | ||||
| } | ||||
| 
 | ||||
| /// A request for contract code.
 | ||||
| #[derive(Debug, Clone, PartialEq, Eq)] | ||||
| pub struct ContractCodes { | ||||
| @ -72,9 +79,9 @@ pub struct ContractCodes { | ||||
| 	pub code_requests: Vec<(H256, H256)>, | ||||
| } | ||||
| 
 | ||||
| /// A request for header proofs from the Canonical Hash Trie.
 | ||||
| /// A request for a header proof from the Canonical Hash Trie.
 | ||||
| #[derive(Debug, Clone, PartialEq, Eq)] | ||||
| pub struct HeaderProofs { | ||||
| pub struct HeaderProof { | ||||
| 	/// Number of the CHT.
 | ||||
| 	pub cht_number: u64, | ||||
| 	/// Block number requested.
 | ||||
| @ -83,6 +90,13 @@ pub struct HeaderProofs { | ||||
| 	pub from_level: u32, | ||||
| } | ||||
| 
 | ||||
| /// A request for header proofs from the CHT.
 | ||||
| #[derive(Debug, Clone, PartialEq, Eq)] | ||||
| pub struct HeaderProofs { | ||||
| 	/// All the proof requests.
 | ||||
| 	pub requests: Vec<HeaderProofs>, | ||||
| } | ||||
| 
 | ||||
| /// Kinds of requests.
 | ||||
| #[derive(Debug, Clone, Copy, PartialEq, Eq)] | ||||
| pub enum Kind { | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user