Gas price statistics. (#1291)

* Gas price statistics.

Affects eth_gasPrice.
Added ethcore_gasPriceStatistics.

Closes #1265

* Fix a bug in eth_gasPrice

* Fix tests.

* Revert minor alteration.

* Tests for gas_price_statistics.

- Tests;
- Additional infrastructure for generating test blocks with
transactions.
This commit is contained in:
Gav Wood
2016-06-16 12:44:08 +02:00
committed by arkpar
parent 6026dd3657
commit 335bce85e8
12 changed files with 225 additions and 52 deletions

View File

@@ -770,8 +770,7 @@ impl<V> MiningBlockChainClient for Client<V> where V: Verifier {
author,
gas_floor_target,
extra_data,
).expect("OpenBlock::new only fails if parent state root invalid. State root of best block's header is never invalid. \
Therefore creating an OpenBlock with the best block's header will not fail.");
).expect("OpenBlock::new only fails if parent state root invalid; state root of best block's header is never invalid; qed");
// Add uncles
self.chain

View File

@@ -34,6 +34,7 @@ pub use env_info::{LastHashes, EnvInfo};
use util::bytes::Bytes;
use util::hash::{Address, H256, H2048};
use util::numbers::U256;
use util::Itertools;
use blockchain::TreeRoute;
use block_queue::BlockQueueInfo;
use block::OpenBlock;
@@ -41,6 +42,7 @@ use header::{BlockNumber, Header};
use transaction::{LocalizedTransaction, SignedTransaction};
use log_entry::LocalizedLogEntry;
use filter::Filter;
use views::BlockView;
use error::{ImportResult, ExecutionError};
use receipt::LocalizedReceipt;
use trace::LocalizedTrace;
@@ -193,6 +195,32 @@ pub trait BlockChainClient : Sync + Send {
/// list all transactions
fn all_transactions(&self) -> Vec<SignedTransaction>;
/// Get the gas price distribution.
fn gas_price_statistics(&self, sample_size: usize, distribution_size: usize) -> Result<Vec<U256>, ()> {
let mut h = self.chain_info().best_block_hash;
let mut corpus = Vec::new();
for _ in 0..sample_size {
let block_bytes = self.block(BlockID::Hash(h)).expect("h is either the best_block_hash or an ancestor; qed");
let block = BlockView::new(&block_bytes);
let header = block.header_view();
if header.number() == 0 {
break;
}
block.transaction_views().iter().foreach(|t| corpus.push(t.gas_price()));
h = header.parent_hash().clone();
}
corpus.sort();
let n = corpus.len();
if n > 0 {
Ok((0..(distribution_size + 1))
.map(|i| corpus[i * (n - 1) / distribution_size])
.collect::<Vec<_>>()
)
} else {
Err(())
}
}
}
/// Extended client interface used for mining

View File

@@ -244,6 +244,11 @@ impl Spec {
pub fn new_test() -> Spec {
Spec::load(include_bytes!("../../res/null_morden.json"))
}
/// Create a new Spec which is a NullEngine consensus with a premine of address whose secret is sha3('').
pub fn new_null() -> Spec {
Spec::load(include_bytes!("../../res/null.json"))
}
}
#[cfg(test)]

View File

@@ -112,6 +112,18 @@ fn can_collect_garbage() {
assert!(client.blockchain_cache_info().blocks < 100 * 1024);
}
#[test]
fn can_generate_gas_price_statistics() {
let client_result = generate_dummy_client_with_data(16, 1, &vec_into![0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]);
let client = client_result.reference();
let s = client.gas_price_statistics(8, 8).unwrap();
assert_eq!(s, vec_into![8, 8, 9, 10, 11, 12, 13, 14, 15]);
let s = client.gas_price_statistics(16, 8).unwrap();
assert_eq!(s, vec_into![0, 1, 3, 5, 7, 9, 11, 13, 15]);
let s = client.gas_price_statistics(32, 8).unwrap();
assert_eq!(s, vec_into![0, 1, 3, 5, 7, 9, 11, 13, 15]);
}
#[test]
fn can_handle_long_fork() {
let client_result = generate_dummy_client(1200);

View File

@@ -17,6 +17,7 @@
use client::{BlockChainClient, Client, ClientConfig};
use common::*;
use spec::*;
use block::{OpenBlock};
use blockchain::{BlockChain, Config as BlockChainConfig};
use state::*;
use evm::Schedule;
@@ -85,6 +86,7 @@ impl Engine for TestEngine {
}
}
// TODO: move everything over to get_null_spec.
pub fn get_test_spec() -> Spec {
Spec::new_test()
}
@@ -126,7 +128,7 @@ fn create_unverifiable_block(order: u32, parent_hash: H256) -> Bytes {
create_test_block(&create_unverifiable_block_header(order, parent_hash))
}
pub fn create_test_block_with_data(header: &Header, transactions: &[&SignedTransaction], uncles: &[Header]) -> Bytes {
pub fn create_test_block_with_data(header: &Header, transactions: &[SignedTransaction], uncles: &[Header]) -> Bytes {
let mut rlp = RlpStream::new_list(3);
rlp.append(header);
rlp.begin_list(transactions.len());
@@ -138,33 +140,74 @@ pub fn create_test_block_with_data(header: &Header, transactions: &[&SignedTrans
}
pub fn generate_dummy_client(block_number: u32) -> GuardedTempResult<Arc<Client>> {
generate_dummy_client_with_spec_and_data(Spec::new_test, block_number, 0, &(vec![]))
}
pub fn generate_dummy_client_with_data(block_number: u32, txs_per_block: usize, tx_gas_prices: &[U256]) -> GuardedTempResult<Arc<Client>> {
generate_dummy_client_with_spec_and_data(Spec::new_null, block_number, txs_per_block, tx_gas_prices)
}
pub fn generate_dummy_client_with_spec_and_data<F>(get_test_spec: F, block_number: u32, txs_per_block: usize, tx_gas_prices: &[U256]) -> GuardedTempResult<Arc<Client>> where F: Fn()->Spec {
let dir = RandomTempPath::new();
let client = Client::new(ClientConfig::default(), get_test_spec(), dir.as_path(), Arc::new(Miner::default()), IoChannel::disconnected()).unwrap();
let test_spec = get_test_spec();
let client = Client::new(ClientConfig::default(), get_test_spec(), dir.as_path(), Arc::new(Miner::default()), IoChannel::disconnected()).unwrap();
let test_engine = &test_spec.engine;
let state_root = test_spec.genesis_header().state_root;
let mut rolling_hash = test_spec.genesis_header().hash();
let mut rolling_block_number = 1;
let mut db_result = get_temp_journal_db();
let mut db = db_result.take();
test_spec.ensure_db_good(db.as_hashdb_mut());
let vm_factory = Default::default();
let genesis_header = test_spec.genesis_header();
let mut rolling_timestamp = 40;
let mut last_hashes = vec![];
let mut last_header = genesis_header.clone();
let kp = KeyPair::from_secret("".sha3()).unwrap() ;
let author = kp.address();
let mut n = 0;
for _ in 0..block_number {
let mut header = Header::new();
last_hashes.push(last_header.hash());
header.gas_limit = test_engine.params().min_gas_limit;
header.difficulty = U256::from(0x20000);
header.timestamp = rolling_timestamp;
header.number = rolling_block_number;
header.parent_hash = rolling_hash;
header.state_root = state_root.clone();
// forge block.
let mut b = OpenBlock::new(
test_engine.deref(),
&vm_factory,
false,
db,
&last_header,
last_hashes.clone(),
author.clone(),
3141562.into(),
vec![]
).unwrap();
b.set_difficulty(U256::from(0x20000));
rolling_timestamp += 10;
b.set_timestamp(rolling_timestamp);
rolling_hash = header.hash();
rolling_block_number = rolling_block_number + 1;
rolling_timestamp = rolling_timestamp + 10;
// first block we don't have any balance, so can't send any transactions.
for _ in 0..txs_per_block {
b.push_transaction(Transaction {
nonce: n.into(),
gas_price: tx_gas_prices[n % tx_gas_prices.len()],
gas: 100000.into(),
action: Action::Create,
data: vec![],
value: U256::zero(),
}.sign(kp.secret()), None).unwrap();
n += 1;
}
if let Err(e) = client.import_block(create_test_block(&header)) {
let b = b.close_and_lock().seal(test_engine.deref(), vec![]).unwrap();
if let Err(e) = client.import_block(b.rlp_bytes()) {
panic!("error importing block which is valid by definition: {:?}", e);
}
last_header = BlockView::new(&b.rlp_bytes()).header();
db = b.drain();
}
client.flush_queue();
client.import_verified_blocks(&IoChannel::disconnected());

View File

@@ -361,7 +361,7 @@ mod tests {
nonce: U256::from(2)
}.sign(&keypair.secret());
let good_transactions = [ &tr1, &tr2 ];
let good_transactions = [ tr1.clone(), tr2.clone() ];
let diff_inc = U256::from(0x40);