2017-01-25 18:51:41 +01:00
|
|
|
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
2016-11-06 19:04:30 +01:00
|
|
|
// This file is part of Parity.
|
|
|
|
|
|
|
|
// Parity is free software: you can redistribute it and/or modify
|
|
|
|
// it under the terms of the GNU General Public License as published by
|
|
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
|
|
// (at your option) any later version.
|
|
|
|
|
|
|
|
// Parity is distributed in the hope that it will be useful,
|
|
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
// GNU General Public License for more details.
|
|
|
|
|
|
|
|
// You should have received a copy of the GNU General Public License
|
|
|
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
2017-03-08 14:39:44 +01:00
|
|
|
//! Request credit management.
|
2016-11-06 19:04:30 +01:00
|
|
|
//!
|
2017-03-08 14:39:44 +01:00
|
|
|
//! 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.
|
2016-11-06 19:04:30 +01:00
|
|
|
//!
|
2017-03-08 14:39:44 +01:00
|
|
|
//! This module provides an interface for configuration of
|
|
|
|
//! costs and recharge rates of request credits.
|
2016-12-08 23:21:47 +01:00
|
|
|
//!
|
2017-01-11 14:39:03 +01:00
|
|
|
//! Current default costs are picked completely arbitrarily, not based
|
2016-12-08 23:21:47 +01:00
|
|
|
//! on any empirical timings or mathematical models.
|
2016-11-06 19:04:30 +01:00
|
|
|
|
2016-12-05 17:09:05 +01:00
|
|
|
use request;
|
2016-11-07 15:40:34 +01:00
|
|
|
use super::packet;
|
2016-11-09 18:05:00 +01:00
|
|
|
use super::error::Error;
|
2016-11-06 19:04:30 +01:00
|
|
|
|
2016-11-07 15:40:34 +01:00
|
|
|
use rlp::*;
|
|
|
|
use util::U256;
|
|
|
|
use time::{Duration, SteadyTime};
|
2016-11-06 19:04:30 +01:00
|
|
|
|
2016-11-07 15:40:34 +01:00
|
|
|
/// A request cost specification.
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
|
|
pub struct Cost(pub U256, pub U256);
|
|
|
|
|
2017-03-08 14:39:44 +01:00
|
|
|
/// Credits value.
|
2016-11-07 15:40:34 +01:00
|
|
|
///
|
|
|
|
/// 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)]
|
2017-03-08 14:39:44 +01:00
|
|
|
pub struct Credits {
|
2016-11-07 15:40:34 +01:00
|
|
|
estimate: U256,
|
|
|
|
recharge_point: SteadyTime,
|
|
|
|
}
|
|
|
|
|
2017-03-08 14:39:44 +01:00
|
|
|
impl Credits {
|
|
|
|
/// Get the current amount of credits..
|
2016-11-09 18:05:00 +01:00
|
|
|
pub fn current(&self) -> U256 { self.estimate.clone() }
|
|
|
|
|
2016-11-07 15:40:34 +01:00
|
|
|
/// 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();
|
|
|
|
}
|
|
|
|
|
2017-03-08 14:39:44 +01:00
|
|
|
/// Attempt to apply the given cost to the amount of credits.
|
2016-11-09 18:05:00 +01:00
|
|
|
///
|
2016-11-07 15:40:34 +01:00
|
|
|
/// If successful, the cost will be deducted successfully.
|
2016-11-09 18:05:00 +01:00
|
|
|
///
|
2016-11-07 15:40:34 +01:00
|
|
|
/// If unsuccessful, the structure will be unaltered an an
|
|
|
|
/// error will be produced.
|
2016-11-09 18:05:00 +01:00
|
|
|
pub fn deduct_cost(&mut self, cost: U256) -> Result<(), Error> {
|
2016-11-07 15:40:34 +01:00
|
|
|
match cost > self.estimate {
|
2017-03-08 14:39:44 +01:00
|
|
|
true => Err(Error::NoCredits),
|
2016-11-07 15:40:34 +01:00
|
|
|
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 {
|
2017-03-08 14:39:44 +01:00
|
|
|
headers: Cost, // cost per header
|
2016-11-07 15:40:34 +01:00
|
|
|
bodies: Cost,
|
|
|
|
receipts: Cost,
|
|
|
|
state_proofs: Cost,
|
|
|
|
contract_codes: Cost,
|
|
|
|
header_proofs: Cost,
|
2017-03-08 14:39:44 +01:00
|
|
|
transaction_proof: Cost, // cost per gas.
|
2016-11-07 15:40:34 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
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()),
|
2017-03-08 14:39:44 +01:00
|
|
|
transaction_proof: Cost(100000.into(), 2.into()),
|
2016-11-07 15:40:34 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2017-03-08 14:39:44 +01:00
|
|
|
s.begin_list(7);
|
2016-11-07 15:40:34 +01:00
|
|
|
|
|
|
|
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);
|
2017-03-08 14:39:44 +01:00
|
|
|
append_cost(s, packet::GET_TRANSACTION_PROOF, &self.transaction_proof);
|
2016-11-06 19:04:30 +01:00
|
|
|
}
|
2016-11-07 15:40:34 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
2017-03-08 14:39:44 +01:00
|
|
|
let mut transaction_proof = None;
|
2016-11-07 15:40:34 +01:00
|
|
|
|
|
|
|
for row in rlp.iter() {
|
2016-12-27 12:53:56 +01:00
|
|
|
let msg_id: u8 = row.val_at(0)?;
|
2016-11-07 15:40:34 +01:00
|
|
|
let cost = {
|
2016-12-27 12:53:56 +01:00
|
|
|
let base = row.val_at(1)?;
|
|
|
|
let per = row.val_at(2)?;
|
2016-11-06 19:04:30 +01:00
|
|
|
|
2016-11-07 15:40:34 +01:00
|
|
|
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),
|
2017-03-08 14:39:44 +01:00
|
|
|
packet::GET_TRANSACTION_PROOF => transaction_proof = Some(cost),
|
2016-11-07 15:40:34 +01:00
|
|
|
_ => return Err(DecoderError::Custom("Unrecognized message in cost table")),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(CostTable {
|
2016-12-27 12:53:56 +01:00
|
|
|
headers: headers.ok_or(DecoderError::Custom("No headers cost specified"))?,
|
|
|
|
bodies: bodies.ok_or(DecoderError::Custom("No bodies cost specified"))?,
|
|
|
|
receipts: receipts.ok_or(DecoderError::Custom("No receipts cost specified"))?,
|
|
|
|
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"))?,
|
2017-03-08 14:39:44 +01:00
|
|
|
transaction_proof: transaction_proof.ok_or(DecoderError::Custom("No transaction proof gas cost specified"))?,
|
2016-11-07 15:40:34 +01:00
|
|
|
})
|
2016-11-06 19:04:30 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-03-08 14:39:44 +01:00
|
|
|
/// Handles costs, recharge, limits of request credits.
|
2016-11-07 15:40:34 +01:00
|
|
|
#[derive(Debug, Clone, PartialEq)]
|
|
|
|
pub struct FlowParams {
|
|
|
|
costs: CostTable,
|
|
|
|
limit: U256,
|
|
|
|
recharge: U256,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl FlowParams {
|
|
|
|
/// Create new flow parameters from a request cost table,
|
2017-03-08 14:39:44 +01:00
|
|
|
/// credit limit, and (minimum) rate of recharge.
|
2016-11-08 16:57:10 +01:00
|
|
|
pub fn new(limit: U256, costs: CostTable, recharge: U256) -> Self {
|
2016-11-07 15:40:34 +01:00
|
|
|
FlowParams {
|
|
|
|
costs: costs,
|
|
|
|
limit: limit,
|
|
|
|
recharge: recharge,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-01-11 14:39:03 +01:00
|
|
|
/// Create effectively infinite flow params.
|
|
|
|
pub fn free() -> Self {
|
|
|
|
let free_cost = Cost(0.into(), 0.into());
|
|
|
|
FlowParams {
|
|
|
|
limit: (!0u64).into(),
|
|
|
|
recharge: 1.into(),
|
|
|
|
costs: CostTable {
|
|
|
|
headers: free_cost.clone(),
|
|
|
|
bodies: free_cost.clone(),
|
|
|
|
receipts: free_cost.clone(),
|
|
|
|
state_proofs: free_cost.clone(),
|
|
|
|
contract_codes: free_cost.clone(),
|
|
|
|
header_proofs: free_cost.clone(),
|
2017-03-08 14:39:44 +01:00
|
|
|
transaction_proof: free_cost,
|
2017-01-11 14:39:03 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-03-08 14:39:44 +01:00
|
|
|
/// Get a reference to the credit limit.
|
2016-11-08 16:57:10 +01:00
|
|
|
pub fn limit(&self) -> &U256 { &self.limit }
|
|
|
|
|
|
|
|
/// Get a reference to the cost table.
|
|
|
|
pub fn cost_table(&self) -> &CostTable { &self.costs }
|
|
|
|
|
|
|
|
/// Get a reference to the recharge rate.
|
|
|
|
pub fn recharge_rate(&self) -> &U256 { &self.recharge }
|
|
|
|
|
2016-11-07 15:40:34 +01:00
|
|
|
/// Compute the actual cost of a request, given the kind of request
|
|
|
|
/// and number of requests made.
|
2016-11-09 18:05:00 +01:00
|
|
|
pub fn compute_cost(&self, kind: request::Kind, amount: usize) -> U256 {
|
2016-11-07 15:40:34 +01:00
|
|
|
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,
|
2017-03-08 14:39:44 +01:00
|
|
|
request::Kind::TransactionProof => &self.costs.transaction_proof,
|
2016-11-07 15:40:34 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
let amount: U256 = amount.into();
|
|
|
|
cost.0 + (amount * cost.1)
|
|
|
|
}
|
|
|
|
|
2017-01-11 14:39:03 +01:00
|
|
|
/// Compute the maximum number of costs of a specific kind which can be made
|
2017-03-08 14:39:44 +01:00
|
|
|
/// with the given amount of credits
|
2016-11-18 19:12:20 +01:00
|
|
|
/// Saturates at `usize::max()`. This is not a problem in practice because
|
|
|
|
/// this amount of requests is already prohibitively large.
|
2017-03-08 14:39:44 +01:00
|
|
|
pub fn max_amount(&self, credits: &Credits, kind: request::Kind) -> usize {
|
2016-11-18 19:12:20 +01:00
|
|
|
use util::Uint;
|
|
|
|
use std::usize;
|
|
|
|
|
|
|
|
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,
|
2017-03-08 14:39:44 +01:00
|
|
|
request::Kind::TransactionProof => &self.costs.transaction_proof,
|
2016-11-18 19:12:20 +01:00
|
|
|
};
|
|
|
|
|
2017-03-08 14:39:44 +01:00
|
|
|
let start = credits.current();
|
2016-11-18 19:12:20 +01:00
|
|
|
|
|
|
|
if start <= cost.0 {
|
|
|
|
return 0;
|
|
|
|
} else if cost.1 == U256::zero() {
|
|
|
|
return usize::MAX;
|
|
|
|
}
|
|
|
|
|
|
|
|
let max = (start - cost.0) / cost.1;
|
|
|
|
if max >= usize::MAX.into() {
|
|
|
|
usize::MAX
|
|
|
|
} else {
|
|
|
|
max.as_u64() as usize
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-03-08 14:39:44 +01:00
|
|
|
/// Create initial credits..
|
|
|
|
pub fn create_credits(&self) -> Credits {
|
|
|
|
Credits {
|
2016-11-07 15:40:34 +01:00
|
|
|
estimate: self.limit,
|
|
|
|
recharge_point: SteadyTime::now(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-03-08 14:39:44 +01:00
|
|
|
/// Recharge the given credits based on time passed since last
|
2016-11-07 15:40:34 +01:00
|
|
|
/// update.
|
2017-03-08 14:39:44 +01:00
|
|
|
pub fn recharge(&self, credits: &mut Credits) {
|
2016-11-07 15:40:34 +01:00
|
|
|
let now = SteadyTime::now();
|
|
|
|
|
|
|
|
// recompute and update only in terms of full seconds elapsed
|
|
|
|
// in order to keep the estimate as an underestimate.
|
2017-03-08 14:39:44 +01:00
|
|
|
let elapsed = (now - credits.recharge_point).num_seconds();
|
|
|
|
credits.recharge_point = credits.recharge_point + Duration::seconds(elapsed);
|
2016-11-07 15:40:34 +01:00
|
|
|
|
|
|
|
let elapsed: U256 = elapsed.into();
|
|
|
|
|
2017-03-08 14:39:44 +01:00
|
|
|
credits.estimate = ::std::cmp::min(self.limit, credits.estimate + (elapsed * self.recharge));
|
2016-11-07 15:40:34 +01:00
|
|
|
}
|
2016-11-15 18:19:16 +01:00
|
|
|
|
2017-03-08 14:39:44 +01:00
|
|
|
/// Refund some credits which were previously deducted.
|
2016-11-15 18:19:16 +01:00
|
|
|
/// Does not update the recharge timestamp.
|
2017-03-08 14:39:44 +01:00
|
|
|
pub fn refund(&self, credits: &mut Credits, refund_amount: U256) {
|
|
|
|
credits.estimate = credits.estimate + refund_amount;
|
2016-11-15 18:19:16 +01:00
|
|
|
|
2017-03-08 14:39:44 +01:00
|
|
|
if credits.estimate > self.limit {
|
|
|
|
credits.estimate = self.limit
|
2016-11-15 18:19:16 +01:00
|
|
|
}
|
|
|
|
}
|
2016-11-07 15:40:34 +01:00
|
|
|
}
|
|
|
|
|
2016-12-08 23:21:47 +01:00
|
|
|
impl Default for FlowParams {
|
|
|
|
fn default() -> Self {
|
|
|
|
FlowParams {
|
|
|
|
limit: 50_000_000.into(),
|
|
|
|
costs: CostTable::default(),
|
|
|
|
recharge: 100_000.into(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-11-07 15:40:34 +01:00
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
#[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]
|
2017-03-08 14:39:44 +01:00
|
|
|
fn credits_mechanism() {
|
2016-11-07 15:40:34 +01:00
|
|
|
use std::thread;
|
|
|
|
use std::time::Duration;
|
|
|
|
|
2016-11-08 16:57:10 +01:00
|
|
|
let flow_params = FlowParams::new(100.into(), Default::default(), 20.into());
|
2017-03-08 14:39:44 +01:00
|
|
|
let mut credits = flow_params.create_credits();
|
2016-11-07 15:40:34 +01:00
|
|
|
|
2017-03-08 14:39:44 +01:00
|
|
|
assert!(credits.deduct_cost(101.into()).is_err());
|
|
|
|
assert!(credits.deduct_cost(10.into()).is_ok());
|
2016-11-07 15:40:34 +01:00
|
|
|
|
|
|
|
thread::sleep(Duration::from_secs(1));
|
|
|
|
|
2017-03-08 14:39:44 +01:00
|
|
|
flow_params.recharge(&mut credits);
|
2016-11-07 15:40:34 +01:00
|
|
|
|
2017-03-08 14:39:44 +01:00
|
|
|
assert_eq!(credits.estimate, 100.into());
|
2016-11-07 15:40:34 +01:00
|
|
|
}
|
2017-01-11 14:39:03 +01:00
|
|
|
}
|