Initial implementation of dynamic gas pricer.

This commit is contained in:
Gav Wood
2016-07-08 17:26:06 +02:00
parent 57c14eedfa
commit 25e6b2b827
12 changed files with 228 additions and 90 deletions

View File

@@ -170,6 +170,10 @@ Sealing/Mining Options:
amount in USD, a web service or 'auto' to use each
web service in turn and fallback on the last known
good value [default: auto].
--price-update-period T T will be allowed to pass between each gas price
update. T may be daily, hourly, a number of seconds,
or a time string of the form "2 days", "30 minutes"
etc. [default: hourly].
--gas-floor-target GAS Amount of gas per block to target when sealing a new
block [default: 4700000].
--gas-cap GAS A cap on how large we will raise the gas limit per
@@ -335,6 +339,7 @@ pub struct Args {
pub flag_author: Option<String>,
pub flag_usd_per_tx: String,
pub flag_usd_per_eth: String,
pub flag_price_update_period: String,
pub flag_gas_floor_target: String,
pub flag_gas_cap: String,
pub flag_extra_data: Option<String>,

View File

@@ -29,11 +29,10 @@ use util::log::Colour::*;
use ethcore::account_provider::AccountProvider;
use util::network_settings::NetworkSettings;
use ethcore::client::{append_path, get_db_path, Mode, ClientConfig, DatabaseCompactionProfile, Switch, VMType};
use ethcore::miner::{MinerOptions, PendingSet};
use ethcore::miner::{MinerOptions, PendingSet, GasPricer, GasPriceCalibratorOptions};
use ethcore::ethereum;
use ethcore::spec::Spec;
use ethsync::SyncConfig;
use price_info::PriceInfo;
use rpc::IpcConfiguration;
pub struct Configuration {
@@ -154,35 +153,54 @@ impl Configuration {
})
}
pub fn gas_price(&self) -> U256 {
fn to_duration(s: &str) -> Duration {
let bad = |_| {
die!("{}: Invalid duration given. See parity --help for more information.", s)
};
Duration::from_secs(match s {
"daily" => 24 * 60 * 60,
"twice-daily" => 12 * 60 * 60,
"hourly" => 60 * 60,
"half-hourly" => 30 * 60,
"1second" | "1 second" | "second" => 1,
"1minute" | "1 minute" | "minute" => 60,
"1hour" | "1 hour" | "hour" => 60 * 60,
"1day" | "1 day" | "day" => 24 * 60 * 60,
x if x.ends_with("seconds") => FromStr::from_str(&x[0..x.len() - 7]).unwrap_or_else(bad),
x if x.ends_with("minutes") => FromStr::from_str(&x[0..x.len() - 7]).unwrap_or_else(bad) * 60,
x if x.ends_with("hours") => FromStr::from_str(&x[0..x.len() - 5]).unwrap_or_else(bad) * 60 * 60,
x if x.ends_with("days") => FromStr::from_str(&x[0..x.len() - 4]).unwrap_or_else(bad) * 24 * 60 * 60,
x => FromStr::from_str(x).unwrap_or_else(bad),
})
}
pub fn gas_pricer(&self) -> GasPricer {
match self.args.flag_gasprice.as_ref() {
Some(d) => {
U256::from_dec_str(d).unwrap_or_else(|_| {
GasPricer::Fixed(U256::from_dec_str(d).unwrap_or_else(|_| {
die!("{}: Invalid gas price given. Must be a decimal unsigned 256-bit number.", d)
})
}))
}
_ => {
let usd_per_tx: f32 = FromStr::from_str(&self.args.flag_usd_per_tx).unwrap_or_else(|_| {
die!("{}: Invalid basic transaction price given in USD. Must be a decimal number.", self.args.flag_usd_per_tx)
});
let usd_per_eth = match self.args.flag_usd_per_eth.as_str() {
"auto" => PriceInfo::get().map_or_else(|| {
let last_known_good = 9.69696;
// TODO: use #1083 to read last known good value.
last_known_good
}, |x| x.ethusd),
"etherscan" => PriceInfo::get().map_or_else(|| {
die!("Unable to retrieve USD value of ETH from etherscan. Rerun with a different value for --usd-per-eth.")
}, |x| x.ethusd),
x => FromStr::from_str(x).unwrap_or_else(|_| die!("{}: Invalid ether price given in USD. Must be a decimal number.", x))
};
// TODO: use #1083 to write last known good value as use_per_eth.
let wei_per_usd: f32 = 1.0e18 / usd_per_eth;
let gas_per_tx: f32 = 21000.0;
let wei_per_gas: f32 = wei_per_usd * usd_per_tx / gas_per_tx;
info!("Using a conversion rate of Ξ1 = {} ({} wei/gas)", format!("US${}", usd_per_eth).apply(White.bold()), format!("{}", wei_per_gas).apply(Yellow.bold()));
U256::from_dec_str(&format!("{:.0}", wei_per_gas)).unwrap()
match self.args.flag_usd_per_eth.as_str() {
"auto" => {
GasPricer::new_calibrated(GasPriceCalibratorOptions {
usd_per_tx: usd_per_tx,
recalibration_period: Self::to_duration(self.args.flag_price_update_period.as_str()),
})
},
x => {
let usd_per_eth: f32 = FromStr::from_str(x).unwrap_or_else(|_| die!("{}: Invalid ether price given in USD. Must be a decimal number.", x));
let wei_per_usd: f32 = 1.0e18 / usd_per_eth;
let gas_per_tx: f32 = 21000.0;
let wei_per_gas: f32 = wei_per_usd * usd_per_tx / gas_per_tx;
info!("Using a fixed conversion rate of Ξ1 = {} ({} wei/gas)", format!("US${}", usd_per_eth).apply(White.bold()), format!("{}", wei_per_gas).apply(Yellow.bold()));
GasPricer::Fixed(U256::from_dec_str(&format!("{:.0}", wei_per_gas)).unwrap())
}
}
}
}
}

View File

@@ -49,14 +49,12 @@ impl IoHandler<NetSyncMessage> for ClientIoHandler {
fn message(&self, _io: &IoContext<NetSyncMessage>, message: &NetSyncMessage) {
match *message {
NetworkIoMessage::User(SyncMessage::StartNetwork) => {
info!("Starting network");
if let Some(network) = self.network.upgrade() {
network.start().unwrap_or_else(|e| warn!("Error starting network: {:?}", e));
EthSync::register(&*network, self.sync.clone()).unwrap_or_else(|e| warn!("Error registering eth protocol handler: {}", e));
}
},
NetworkIoMessage::User(SyncMessage::StopNetwork) => {
info!("Stopping network");
if let Some(network) = self.network.upgrade() {
network.stop().unwrap_or_else(|e| warn!("Error stopping network: {:?}", e));
}

View File

@@ -55,7 +55,6 @@ extern crate ethcore_signer;
#[macro_use]
mod die;
mod price_info;
mod upgrade;
mod hypervisor;
mod setup_log;
@@ -222,12 +221,11 @@ fn execute_client(conf: Configuration, spec: Spec, client_config: ClientConfig)
let account_service = Arc::new(conf.account_service());
// Miner
let miner = Miner::new(conf.miner_options(), conf.spec(), Some(account_service.clone()));
let miner = Miner::new(conf.miner_options(), conf.gas_pricer(), conf.spec(), Some(account_service.clone()));
miner.set_author(conf.author().unwrap_or_default());
miner.set_gas_floor_target(conf.gas_floor_target());
miner.set_gas_ceil_target(conf.gas_ceil_target());
miner.set_extra_data(conf.extra_data());
miner.set_minimal_gas_price(conf.gas_price());
miner.set_transactions_limit(conf.args.flag_tx_queue_size);
// Build client

View File

@@ -1,47 +0,0 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// 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/>.
use rustc_serialize::json::Json;
use std::io::Read;
use hyper::Client;
use hyper::header::Connection;
use std::str::FromStr;
pub struct PriceInfo {
pub ethusd: f32,
}
impl PriceInfo {
pub fn get() -> Option<PriceInfo> {
let mut body = String::new();
// TODO: Handle each error type properly
let mut client = Client::new();
client.set_read_timeout(Some(::std::time::Duration::from_secs(3)));
client.get("http://api.etherscan.io/api?module=stats&action=ethprice")
.header(Connection::close())
.send()
.ok()
.and_then(|mut s| s.read_to_string(&mut body).ok())
.and_then(|_| Json::from_str(&body).ok())
.and_then(|json| json.find_path(&["result", "ethusd"])
.and_then(|obj| match *obj {
Json::String(ref s) => Some(PriceInfo {
ethusd: FromStr::from_str(s).unwrap()
}),
_ => None
}))
}
}