Merge remote-tracking branch 'parity/master' into auth-round-no-mocknet
This commit is contained in:
@@ -37,7 +37,7 @@ ethkey = { path = "../ethkey" }
|
||||
ethcore-ipc-nano = { path = "../ipc/nano" }
|
||||
rlp = { path = "../util/rlp" }
|
||||
rand = "0.3"
|
||||
lru-cache = "0.0.7"
|
||||
lru-cache = { git = "https://github.com/contain-rs/lru-cache" }
|
||||
ethcore-bloom-journal = { path = "../util/bloom" }
|
||||
byteorder = "0.5"
|
||||
|
||||
|
||||
@@ -10,7 +10,8 @@
|
||||
"durationLimit": "0x0d",
|
||||
"blockReward": "0x4563918244F40000",
|
||||
"registrar" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b",
|
||||
"frontierCompatibilityModeLimit": "0x118c30"
|
||||
"homesteadTransition": "0x118c30",
|
||||
"eip150Transition": "0x2625a0"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
43
ethcore/res/ethereum/eip150_test.json
Normal file
43
ethcore/res/ethereum/eip150_test.json
Normal file
@@ -0,0 +1,43 @@
|
||||
{
|
||||
"name": "Homestead (Test)",
|
||||
"engine": {
|
||||
"Ethash": {
|
||||
"params": {
|
||||
"gasLimitBoundDivisor": "0x0400",
|
||||
"minimumDifficulty": "0x020000",
|
||||
"difficultyBoundDivisor": "0x0800",
|
||||
"durationLimit": "0x0d",
|
||||
"blockReward": "0x4563918244F40000",
|
||||
"registrar" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b",
|
||||
"homesteadTransition": "0x0",
|
||||
"eip150Transition": "0x0"
|
||||
}
|
||||
}
|
||||
},
|
||||
"params": {
|
||||
"accountStartNonce": "0x00",
|
||||
"maximumExtraDataSize": "0x20",
|
||||
"minGasLimit": "0x1388",
|
||||
"networkID" : "0x1"
|
||||
},
|
||||
"genesis": {
|
||||
"seal": {
|
||||
"ethereum": {
|
||||
"nonce": "0x0000000000000042",
|
||||
"mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000"
|
||||
}
|
||||
},
|
||||
"difficulty": "0x400000000",
|
||||
"author": "0x0000000000000000000000000000000000000000",
|
||||
"timestamp": "0x00",
|
||||
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"extraData": "0x11bbe8db4e347b4e8c937c1c8370e4b5ed33adb3db69cbdb7a38e1e50b1b82fa",
|
||||
"gasLimit": "0x1388"
|
||||
},
|
||||
"accounts": {
|
||||
"0000000000000000000000000000000000000001": { "balance": "1", "builtin": { "name": "ecrecover", "pricing": { "linear": { "base": 3000, "word": 0 } } } },
|
||||
"0000000000000000000000000000000000000002": { "balance": "1", "builtin": { "name": "sha256", "pricing": { "linear": { "base": 60, "word": 12 } } } },
|
||||
"0000000000000000000000000000000000000003": { "balance": "1", "builtin": { "name": "ripemd160", "pricing": { "linear": { "base": 600, "word": 120 } } } },
|
||||
"0000000000000000000000000000000000000004": { "balance": "1", "builtin": { "name": "identity", "pricing": { "linear": { "base": 15, "word": 3 } } } }
|
||||
}
|
||||
}
|
||||
@@ -11,10 +11,11 @@
|
||||
"durationLimit": "0x3C",
|
||||
"blockReward": "0x6f05b59d3b200000",
|
||||
"registrar" : "0x6c221ca53705f3497ec90ca7b84c59ae7382fc21",
|
||||
"frontierCompatibilityModeLimit": "0x30d40",
|
||||
"homesteadTransition": "0x30d40",
|
||||
"difficultyHardforkTransition": "0x59d9",
|
||||
"difficultyHardforkBoundDivisor": "0x0200",
|
||||
"bombDefuseTransition": "0x30d40"
|
||||
"bombDefuseTransition": "0x30d40",
|
||||
"eip150Transition": "0x7fffffffffffffff"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -8,11 +8,11 @@
|
||||
"difficultyBoundDivisor": "0x0800",
|
||||
"durationLimit": "0x0d",
|
||||
"blockReward": "0x4563918244F40000",
|
||||
"registrar" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b",
|
||||
"frontierCompatibilityModeLimit": "0x118c30",
|
||||
"registrar" : "0x3bb2bb5c6c9c9b7f4ef430b47dc7e026310042ea",
|
||||
"homesteadTransition": "0x118c30",
|
||||
"daoHardforkTransition": "0x1d4c00",
|
||||
"daoHardforkBeneficiary": "0xbf4ed7b27f1d666546e30d74d50d173d20bca754",
|
||||
"daoHardforkAccounts": [
|
||||
"daoHardforkAccounts": [
|
||||
"0xd4fe7bc31cedb7bfb8a345f31e668033056b2728",
|
||||
"0xb3fb0e5aba0e20e5c49d252dfd30e102b171a425",
|
||||
"0x2c19c7f9ae8b751e37aeb2d93a699722395ae18f",
|
||||
@@ -129,7 +129,8 @@
|
||||
"0x7602b46df5390e432ef1c307d4f2c9ff6d65cc97",
|
||||
"0xbb9bc244d798123fde783fcc1c72d3bb8c189413",
|
||||
"0x807640a13483f8ac783c557fcdf27be11ea4ac7a"
|
||||
]
|
||||
],
|
||||
"eip150Transition": "0x259518"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -163,7 +164,8 @@
|
||||
"enode://a979fb575495b8d6db44f750317d0f4622bf4c2aa3365d6af7c284339968eef29b69ad0dce72a4d8db5ebb4968de0e3bec910127f134779fbcb0cb6d3331163c@52.16.188.185:30303",
|
||||
"enode://de471bccee3d042261d52e9bff31458daecc406142b401d4cd848f677479f73104b9fdeb090af9583d3391b7f10cb2ba9e26865dd5fca4fcdc0fb1e3b723c786@54.94.239.50:30303",
|
||||
"enode://1118980bf48b0a3640bdba04e0fe78b1add18e1cd99bf22d53daac1fd9972ad650df52176e7c7d89d1114cfef2bc23a2959aa54998a46afcf7d91809f0855082@52.74.57.123:30303",
|
||||
"enode://248f12bc8b18d5289358085520ac78cd8076485211e6d96ab0bc93d6cd25442db0ce3a937dc404f64f207b0b9aed50e25e98ce32af5ac7cb321ff285b97de485@zero.parity.io:30303"
|
||||
"enode://4cd540b2c3292e17cff39922e864094bf8b0741fcc8c5dcea14957e389d7944c70278d872902e3d0345927f621547efa659013c400865485ab4bfa0c6596936f@zero.parity.io:30303",
|
||||
"enode://cc92c4c40d612a10c877ca023ef0496c843fbc92b6c6c0d55ce0b863d51d821c4bd70daebb54324a6086374e6dc05708fed39862b275f169cb678e655da9d07d@136.243.154.246:30303"
|
||||
],
|
||||
"accounts": {
|
||||
"0000000000000000000000000000000000000001": { "builtin": { "name": "ecrecover", "pricing": { "linear": { "base": 3000, "word": 0 } } } },
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
"durationLimit": "0x0d",
|
||||
"blockReward": "0x4563918244F40000",
|
||||
"registrar" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b",
|
||||
"frontierCompatibilityModeLimit": "0x118c30",
|
||||
"homesteadTransition": "0x118c30",
|
||||
"daoHardforkTransition": "0x1d4c00",
|
||||
"daoHardforkBeneficiary": "0xbf4ed7b27f1d666546e30d74d50d173d20bca754",
|
||||
"daoHardforkAccounts": [
|
||||
@@ -129,7 +129,8 @@
|
||||
"0x7602b46df5390e432ef1c307d4f2c9ff6d65cc97",
|
||||
"0xbb9bc244d798123fde783fcc1c72d3bb8c189413",
|
||||
"0x807640a13483f8ac783c557fcdf27be11ea4ac7a"
|
||||
]
|
||||
],
|
||||
"eip150Transition": "0x7fffffffffffffff"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -9,7 +9,8 @@
|
||||
"durationLimit": "0x0d",
|
||||
"blockReward": "0x4563918244F40000",
|
||||
"registrar" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b",
|
||||
"frontierCompatibilityModeLimit": "0xffffffffffffffff"
|
||||
"homesteadTransition": "0x7fffffffffffffff",
|
||||
"eip150Transition": "0x7fffffffffffffff"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -9,7 +9,8 @@
|
||||
"durationLimit": "0x0d",
|
||||
"blockReward": "0x4563918244F40000",
|
||||
"registrar" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b",
|
||||
"frontierCompatibilityModeLimit": "0x0"
|
||||
"homesteadTransition": "0x0",
|
||||
"eip150Transition": "0x7fffffffffffffff"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -9,7 +9,8 @@
|
||||
"durationLimit": "0x0d",
|
||||
"blockReward": "0x4563918244F40000",
|
||||
"registrar": "0x52dff57a8a1532e6afb3dc07e2af58bb9eb05b3d",
|
||||
"frontierCompatibilityModeLimit": "0x789b0"
|
||||
"homesteadTransition": "0x789b0",
|
||||
"eip150Transition": "0x1b34d8"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -17,7 +18,9 @@
|
||||
"accountStartNonce": "0x0100000",
|
||||
"maximumExtraDataSize": "0x20",
|
||||
"minGasLimit": "0x1388",
|
||||
"networkID" : "0x2"
|
||||
"networkID" : "0x2",
|
||||
"forkBlock": "0x1b34d8",
|
||||
"forkCanonHash": "0xf376243aeff1f256d970714c3de9fd78fa4e63cf63e32a51fe1169e375d98145"
|
||||
},
|
||||
"genesis": {
|
||||
"seal": {
|
||||
|
||||
@@ -8,7 +8,9 @@
|
||||
"difficultyBoundDivisor": "0x0800",
|
||||
"durationLimit": "0x08",
|
||||
"blockReward": "0x14D1120D7B160000",
|
||||
"registrar": "5e70c0bbcd5636e0f9f9316e9f8633feb64d4050"
|
||||
"registrar": "5e70c0bbcd5636e0f9f9316e9f8633feb64d4050",
|
||||
"homesteadTransition": "0x7fffffffffffffff",
|
||||
"eip150Transition": "0x7fffffffffffffff"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
Submodule ethcore/res/ethereum/tests updated: 8f07dbc829...97066e40cc
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "DAO hard-fork consensus test",
|
||||
"name": "EIP150.1b hard-fork consensus test",
|
||||
"engine": {
|
||||
"Ethash": {
|
||||
"params": {
|
||||
@@ -9,7 +9,7 @@
|
||||
"durationLimit": "0x0d",
|
||||
"blockReward": "0x4563918244F40000",
|
||||
"registrar" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b",
|
||||
"frontierCompatibilityModeLimit": "0x5",
|
||||
"homesteadTransition": "0x5",
|
||||
"daoHardforkTransition": "0x8",
|
||||
"daoHardforkBeneficiary": "0xbf4ed7b27f1d666546e30d74d50d173d20bca754",
|
||||
"daoHardforkAccounts": [
|
||||
@@ -129,7 +129,8 @@
|
||||
"0x7602b46df5390e432ef1c307d4f2c9ff6d65cc97",
|
||||
"0xbb9bc244d798123fde783fcc1c72d3bb8c189413",
|
||||
"0x807640a13483f8ac783c557fcdf27be11ea4ac7a"
|
||||
]
|
||||
],
|
||||
"eip150Transition": "0xa"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -23,7 +23,7 @@ use std::time::{Instant, Duration};
|
||||
use util::{Mutex, RwLock};
|
||||
use ethstore::{SecretStore, Error as SSError, SafeAccount, EthStore};
|
||||
use ethstore::dir::{KeyDirectory};
|
||||
use ethstore::ethkey::{Address, Message, Secret, Random, Generator};
|
||||
use ethstore::ethkey::{Address, Message, Public, Secret, Random, Generator};
|
||||
use ethjson::misc::AccountMeta;
|
||||
pub use ethstore::ethkey::Signature;
|
||||
|
||||
@@ -176,15 +176,23 @@ impl AccountProvider {
|
||||
AccountProvider {
|
||||
unlocked: Mutex::new(HashMap::new()),
|
||||
address_book: Mutex::new(AddressBook::new(Default::default())),
|
||||
sstore: Box::new(EthStore::open(Box::new(NullDir::default())).unwrap())
|
||||
sstore: Box::new(EthStore::open(Box::new(NullDir::default()))
|
||||
.expect("NullDir load always succeeds; qed"))
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates new random account.
|
||||
pub fn new_account(&self, password: &str) -> Result<Address, Error> {
|
||||
let secret = Random.generate().unwrap().secret().clone();
|
||||
self.new_account_and_public(password).map(|d| d.0)
|
||||
}
|
||||
|
||||
/// Creates new random account and returns address and public key
|
||||
pub fn new_account_and_public(&self, password: &str) -> Result<(Address, Public), Error> {
|
||||
let acc = Random.generate().expect("secp context has generation capabilities; qed");
|
||||
let public = acc.public().clone();
|
||||
let secret = acc.secret().clone();
|
||||
let address = try!(self.sstore.insert_account(secret, password));
|
||||
Ok(address)
|
||||
Ok((address, public))
|
||||
}
|
||||
|
||||
/// Inserts new account into underlying store.
|
||||
@@ -280,6 +288,21 @@ impl AccountProvider {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn password(&self, account: &Address) -> Result<String, Error> {
|
||||
let mut unlocked = self.unlocked.lock();
|
||||
let data = try!(unlocked.get(account).ok_or(Error::NotUnlocked)).clone();
|
||||
if let Unlock::Temp = data.unlock {
|
||||
unlocked.remove(account).expect("data exists: so key must exist: qed");
|
||||
}
|
||||
if let Unlock::Timed((ref start, ref duration)) = data.unlock {
|
||||
if start.elapsed() > Duration::from_millis(*duration as u64) {
|
||||
unlocked.remove(account).expect("data exists: so key must exist: qed");
|
||||
return Err(Error::NotUnlocked);
|
||||
}
|
||||
}
|
||||
Ok(data.password.clone())
|
||||
}
|
||||
|
||||
/// Unlocks account permanently.
|
||||
pub fn unlock_account_permanently(&self, account: Address, password: String) -> Result<(), Error> {
|
||||
self.unlock_account(account, password, Unlock::Perm)
|
||||
@@ -301,51 +324,16 @@ impl AccountProvider {
|
||||
unlocked.get(&account).is_some()
|
||||
}
|
||||
|
||||
/// Signs the message. Account must be unlocked.
|
||||
pub fn sign(&self, account: Address, message: Message) -> Result<Signature, Error> {
|
||||
let data = {
|
||||
let mut unlocked = self.unlocked.lock();
|
||||
let data = try!(unlocked.get(&account).ok_or(Error::NotUnlocked)).clone();
|
||||
if let Unlock::Temp = data.unlock {
|
||||
unlocked.remove(&account).expect("data exists: so key must exist: qed");
|
||||
}
|
||||
if let Unlock::Timed((ref start, ref duration)) = data.unlock {
|
||||
if start.elapsed() > Duration::from_millis(*duration as u64) {
|
||||
unlocked.remove(&account).expect("data exists: so key must exist: qed");
|
||||
return Err(Error::NotUnlocked);
|
||||
}
|
||||
}
|
||||
data
|
||||
};
|
||||
|
||||
let signature = try!(self.sstore.sign(&account, &data.password, &message));
|
||||
Ok(signature)
|
||||
/// Signs the message. If password is not provided the account must be unlocked.
|
||||
pub fn sign(&self, account: Address, password: Option<String>, message: Message) -> Result<Signature, Error> {
|
||||
let password = try!(password.map(Ok).unwrap_or_else(|| self.password(&account)));
|
||||
Ok(try!(self.sstore.sign(&account, &password, &message)))
|
||||
}
|
||||
|
||||
/// Decrypts a message. Account must be unlocked.
|
||||
pub fn decrypt(&self, account: Address, shared_mac: &[u8], message: &[u8]) -> Result<Vec<u8>, Error> {
|
||||
let data = {
|
||||
let mut unlocked = self.unlocked.lock();
|
||||
let data = try!(unlocked.get(&account).ok_or(Error::NotUnlocked)).clone();
|
||||
if let Unlock::Temp = data.unlock {
|
||||
unlocked.remove(&account).expect("data exists: so key must exist: qed");
|
||||
}
|
||||
if let Unlock::Timed((ref start, ref duration)) = data.unlock {
|
||||
if start.elapsed() > Duration::from_millis(*duration as u64) {
|
||||
unlocked.remove(&account).expect("data exists: so key must exist: qed");
|
||||
return Err(Error::NotUnlocked);
|
||||
}
|
||||
}
|
||||
data
|
||||
};
|
||||
|
||||
Ok(try!(self.sstore.decrypt(&account, &data.password, shared_mac, message)))
|
||||
}
|
||||
|
||||
/// Unlocks an account, signs the message, and locks it again.
|
||||
pub fn sign_with_password(&self, account: Address, password: String, message: Message) -> Result<Signature, Error> {
|
||||
let signature = try!(self.sstore.sign(&account, &password, &message));
|
||||
Ok(signature)
|
||||
/// Decrypts a message. If password is not provided the account must be unlocked.
|
||||
pub fn decrypt(&self, account: Address, password: Option<String>, shared_mac: &[u8], message: &[u8]) -> Result<Vec<u8>, Error> {
|
||||
let password = try!(password.map(Ok).unwrap_or_else(|| self.password(&account)));
|
||||
Ok(try!(self.sstore.decrypt(&account, &password, shared_mac, message)))
|
||||
}
|
||||
|
||||
/// Returns the underlying `SecretStore` reference if one exists.
|
||||
@@ -386,8 +374,8 @@ mod tests {
|
||||
assert!(ap.insert_account(kp.secret().clone(), "test").is_ok());
|
||||
assert!(ap.unlock_account_temporarily(kp.address(), "test1".into()).is_err());
|
||||
assert!(ap.unlock_account_temporarily(kp.address(), "test".into()).is_ok());
|
||||
assert!(ap.sign(kp.address(), Default::default()).is_ok());
|
||||
assert!(ap.sign(kp.address(), Default::default()).is_err());
|
||||
assert!(ap.sign(kp.address(), None, Default::default()).is_ok());
|
||||
assert!(ap.sign(kp.address(), None, Default::default()).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -397,11 +385,11 @@ mod tests {
|
||||
assert!(ap.insert_account(kp.secret().clone(), "test").is_ok());
|
||||
assert!(ap.unlock_account_permanently(kp.address(), "test1".into()).is_err());
|
||||
assert!(ap.unlock_account_permanently(kp.address(), "test".into()).is_ok());
|
||||
assert!(ap.sign(kp.address(), Default::default()).is_ok());
|
||||
assert!(ap.sign(kp.address(), Default::default()).is_ok());
|
||||
assert!(ap.sign(kp.address(), None, Default::default()).is_ok());
|
||||
assert!(ap.sign(kp.address(), None, Default::default()).is_ok());
|
||||
assert!(ap.unlock_account_temporarily(kp.address(), "test".into()).is_ok());
|
||||
assert!(ap.sign(kp.address(), Default::default()).is_ok());
|
||||
assert!(ap.sign(kp.address(), Default::default()).is_ok());
|
||||
assert!(ap.sign(kp.address(), None, Default::default()).is_ok());
|
||||
assert!(ap.sign(kp.address(), None, Default::default()).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -411,8 +399,8 @@ mod tests {
|
||||
assert!(ap.insert_account(kp.secret().clone(), "test").is_ok());
|
||||
assert!(ap.unlock_account_timed(kp.address(), "test1".into(), 2000).is_err());
|
||||
assert!(ap.unlock_account_timed(kp.address(), "test".into(), 2000).is_ok());
|
||||
assert!(ap.sign(kp.address(), Default::default()).is_ok());
|
||||
assert!(ap.sign(kp.address(), None, Default::default()).is_ok());
|
||||
::std::thread::sleep(Duration::from_millis(2000));
|
||||
assert!(ap.sign(kp.address(), Default::default()).is_err());
|
||||
assert!(ap.sign(kp.address(), None, Default::default()).is_err());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -350,7 +350,7 @@ impl<'x> OpenBlock<'x> {
|
||||
let t = outcome.trace;
|
||||
self.block.traces.as_mut().map(|traces| traces.push(t));
|
||||
self.block.receipts.push(outcome.receipt);
|
||||
Ok(self.block.receipts.last().unwrap())
|
||||
Ok(self.block.receipts.last().expect("receipt just pushed; qed"))
|
||||
}
|
||||
Err(x) => Err(From::from(x))
|
||||
}
|
||||
@@ -404,6 +404,10 @@ impl<'x> OpenBlock<'x> {
|
||||
uncle_bytes: uncle_bytes,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
/// Return mutable block reference. To be used in tests only.
|
||||
pub fn block_mut (&mut self) -> &mut ExecutedBlock { &mut self.block }
|
||||
}
|
||||
|
||||
impl<'x> IsBlock for OpenBlock<'x> {
|
||||
|
||||
@@ -29,3 +29,12 @@ pub struct BestBlock {
|
||||
/// Best block uncompressed bytes
|
||||
pub block: Bytes,
|
||||
}
|
||||
|
||||
/// Best ancient block info. If the blockchain has a gap this keeps track of where it starts.
|
||||
#[derive(Default)]
|
||||
pub struct BestAncientBlock {
|
||||
/// Best block hash.
|
||||
pub hash: H256,
|
||||
/// Best block number.
|
||||
pub number: BlockNumber,
|
||||
}
|
||||
|
||||
@@ -27,7 +27,8 @@ use log_entry::{LogEntry, LocalizedLogEntry};
|
||||
use receipt::Receipt;
|
||||
use blooms::{Bloom, BloomGroup};
|
||||
use blockchain::block_info::{BlockInfo, BlockLocation, BranchBecomingCanonChainData};
|
||||
use blockchain::best_block::BestBlock;
|
||||
use blockchain::best_block::{BestBlock, BestAncientBlock};
|
||||
use types::blockchain_info::BlockChainInfo;
|
||||
use types::tree_route::TreeRoute;
|
||||
use blockchain::update::ExtrasUpdate;
|
||||
use blockchain::{CacheSize, ImportRoute, Config};
|
||||
@@ -43,16 +44,24 @@ pub trait BlockProvider {
|
||||
/// (though not necessarily a part of the canon chain).
|
||||
fn is_known(&self, hash: &H256) -> bool;
|
||||
|
||||
/// Get the first block which this chain holds.
|
||||
/// Get the first block of the best part of the chain.
|
||||
/// Return `None` if there is no gap and the first block is the genesis.
|
||||
/// Any queries of blocks which precede this one are not guaranteed to
|
||||
/// succeed.
|
||||
fn first_block(&self) -> H256;
|
||||
fn first_block(&self) -> Option<H256>;
|
||||
|
||||
/// Get the number of the first block.
|
||||
fn first_block_number(&self) -> BlockNumber {
|
||||
self.block_number(&self.first_block()).expect("First block always stored; qed")
|
||||
fn first_block_number(&self) -> Option<BlockNumber> {
|
||||
self.first_block().map(|b| self.block_number(&b).expect("First block is always set to an existing block or `None`. Existing block always has a number; qed"))
|
||||
}
|
||||
|
||||
/// Get the best block of an first block sequence if there is a gap.
|
||||
fn best_ancient_block(&self) -> Option<H256>;
|
||||
|
||||
/// Get the number of the first block.
|
||||
fn best_ancient_number(&self) -> Option<BlockNumber> {
|
||||
self.best_ancient_block().map(|h| self.block_number(&h).expect("Ancient block is always set to an existing block or `None`. Existing block always has a number; qed"))
|
||||
}
|
||||
/// Get raw block data
|
||||
fn block(&self, hash: &H256) -> Option<Bytes>;
|
||||
|
||||
@@ -123,7 +132,8 @@ pub trait BlockProvider {
|
||||
|
||||
/// Returns the header of the genesis block.
|
||||
fn genesis_header(&self) -> Header {
|
||||
self.block_header(&self.genesis_hash()).unwrap()
|
||||
self.block_header(&self.genesis_hash())
|
||||
.expect("Genesis header always stored; qed")
|
||||
}
|
||||
|
||||
/// Returns numbers of blocks containing given bloom.
|
||||
@@ -160,9 +170,14 @@ impl bc::group::BloomGroupDatabase for BlockChain {
|
||||
pub struct BlockChain {
|
||||
// All locks must be captured in the order declared here.
|
||||
blooms_config: bc::Config,
|
||||
first_block: H256,
|
||||
|
||||
best_block: RwLock<BestBlock>,
|
||||
// Stores best block of the first uninterrupted sequence of blocks. `None` if there are no gaps.
|
||||
// Only updated with `insert_unordered_block`.
|
||||
best_ancient_block: RwLock<Option<BestAncientBlock>>,
|
||||
// Stores the last block of the last sequence of blocks. `None` if there are no gaps.
|
||||
// This is calculated on start and does not get updated.
|
||||
first_block: Option<H256>,
|
||||
|
||||
// block cache
|
||||
block_headers: RwLock<HashMap<H256, Bytes>>,
|
||||
@@ -191,8 +206,16 @@ impl BlockProvider for BlockChain {
|
||||
self.db.exists_with_cache(db::COL_EXTRA, &self.block_details, hash)
|
||||
}
|
||||
|
||||
fn first_block(&self) -> H256 {
|
||||
self.first_block
|
||||
fn first_block(&self) -> Option<H256> {
|
||||
self.first_block.clone()
|
||||
}
|
||||
|
||||
fn best_ancient_block(&self) -> Option<H256> {
|
||||
self.best_ancient_block.read().as_ref().map(|b| b.hash.clone())
|
||||
}
|
||||
|
||||
fn best_ancient_number(&self) -> Option<BlockNumber> {
|
||||
self.best_ancient_block.read().as_ref().map(|b| b.number)
|
||||
}
|
||||
|
||||
/// Get raw block data
|
||||
@@ -332,7 +355,10 @@ impl BlockProvider for BlockChain {
|
||||
.filter_map(|(number, hash)| self.block_receipts(&hash).map(|r| (number, hash, r.receipts)))
|
||||
.filter_map(|(number, hash, receipts)| self.block_body(&hash).map(|ref b| (number, hash, receipts, BodyView::new(b).transaction_hashes())))
|
||||
.flat_map(|(number, hash, mut receipts, mut hashes)| {
|
||||
assert_eq!(receipts.len(), hashes.len());
|
||||
if receipts.len() != hashes.len() {
|
||||
warn!("Block {} ({}) has different number of receipts ({}) to transactions ({}). Database corrupt?", number, hash, receipts.len(), hashes.len());
|
||||
assert!(false);
|
||||
}
|
||||
log_index = receipts.iter().fold(0, |sum, receipt| sum + receipt.logs.len());
|
||||
|
||||
let receipts_len = receipts.len();
|
||||
@@ -397,8 +423,9 @@ impl BlockChain {
|
||||
levels: LOG_BLOOMS_LEVELS,
|
||||
elements_per_index: LOG_BLOOMS_ELEMENTS_PER_INDEX,
|
||||
},
|
||||
first_block: H256::zero(),
|
||||
first_block: None,
|
||||
best_block: RwLock::new(BestBlock::default()),
|
||||
best_ancient_block: RwLock::new(None),
|
||||
block_headers: RwLock::new(HashMap::new()),
|
||||
block_bodies: RwLock::new(HashMap::new()),
|
||||
block_details: RwLock::new(HashMap::new()),
|
||||
@@ -440,7 +467,6 @@ impl BlockChain {
|
||||
batch.write(db::COL_EXTRA, &header.number(), &hash);
|
||||
|
||||
batch.put(db::COL_EXTRA, b"best", &hash);
|
||||
batch.put(db::COL_EXTRA, b"first", &hash);
|
||||
bc.db.write(batch).expect("Low level database error. Some issue with disk?");
|
||||
hash
|
||||
}
|
||||
@@ -452,32 +478,45 @@ impl BlockChain {
|
||||
let best_block_total_difficulty = bc.block_details(&best_block_hash).unwrap().total_difficulty;
|
||||
let best_block_rlp = bc.block(&best_block_hash).unwrap();
|
||||
|
||||
let raw_first = bc.db.get(db::COL_EXTRA, b"first").unwrap().map_or(Vec::new(), |v| v.to_vec());
|
||||
let raw_first = bc.db.get(db::COL_EXTRA, b"first").unwrap().map(|v| v.to_vec());
|
||||
let mut best_ancient = bc.db.get(db::COL_EXTRA, b"ancient").unwrap().map(|h| H256::from_slice(&h));
|
||||
let best_ancient_number;
|
||||
if best_ancient.is_none() && best_block_number > 1 && bc.block_hash(1).is_none() {
|
||||
best_ancient = Some(bc.genesis_hash());
|
||||
best_ancient_number = Some(0);
|
||||
} else {
|
||||
best_ancient_number = best_ancient.as_ref().and_then(|h| bc.block_number(h));
|
||||
}
|
||||
|
||||
// binary search for the first block.
|
||||
if raw_first.is_empty() {
|
||||
let (mut f, mut hash) = (best_block_number, best_block_hash);
|
||||
let mut l = 0;
|
||||
match raw_first {
|
||||
None => {
|
||||
let (mut f, mut hash) = (best_block_number, best_block_hash);
|
||||
let mut l = best_ancient_number.unwrap_or(0);
|
||||
|
||||
loop {
|
||||
if l >= f { break; }
|
||||
loop {
|
||||
if l >= f { break; }
|
||||
|
||||
let step = (f - l) >> 1;
|
||||
let m = l + step;
|
||||
let step = (f - l) >> 1;
|
||||
let m = l + step;
|
||||
|
||||
match bc.block_hash(m) {
|
||||
Some(h) => { f = m; hash = h },
|
||||
None => { l = m + 1 },
|
||||
match bc.block_hash(m) {
|
||||
Some(h) => { f = m; hash = h },
|
||||
None => { l = m + 1 },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut batch = db.transaction();
|
||||
batch.put(db::COL_EXTRA, b"first", &hash);
|
||||
db.write(batch).expect("Low level database error.");
|
||||
|
||||
bc.first_block = hash;
|
||||
} else {
|
||||
bc.first_block = H256::from_slice(&raw_first);
|
||||
if hash != bc.genesis_hash() {
|
||||
trace!("First block calculated: {:?}", hash);
|
||||
let mut batch = db.transaction();
|
||||
batch.put(db::COL_EXTRA, b"first", &hash);
|
||||
db.write(batch).expect("Low level database error.");
|
||||
bc.first_block = Some(hash);
|
||||
}
|
||||
},
|
||||
Some(raw_first) => {
|
||||
bc.first_block = Some(H256::from_slice(&raw_first));
|
||||
},
|
||||
}
|
||||
|
||||
// and write them
|
||||
@@ -488,6 +527,14 @@ impl BlockChain {
|
||||
hash: best_block_hash,
|
||||
block: best_block_rlp,
|
||||
};
|
||||
|
||||
if let (Some(hash), Some(number)) = (best_ancient, best_ancient_number) {
|
||||
let mut best_ancient_block = bc.best_ancient_block.write();
|
||||
*best_ancient_block = Some(BestAncientBlock {
|
||||
hash: hash,
|
||||
number: number,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
bc
|
||||
@@ -641,11 +688,12 @@ impl BlockChain {
|
||||
/// Inserts a verified, known block from the canonical chain.
|
||||
///
|
||||
/// Can be performed out-of-order, but care must be taken that the final chain is in a correct state.
|
||||
/// This is used by snapshot restoration.
|
||||
///
|
||||
/// This is used by snapshot restoration and when downloading missing blocks for the chain gap.
|
||||
/// `is_best` forces the best block to be updated to this block.
|
||||
/// `is_ancient` forces the best block of the first block sequence to be updated to this block.
|
||||
/// Supply a dummy parent total difficulty when the parent block may not be in the chain.
|
||||
/// Returns true if the block is disconnected.
|
||||
pub fn insert_snapshot_block(&self, bytes: &[u8], receipts: Vec<Receipt>, parent_td: Option<U256>, is_best: bool) -> bool {
|
||||
pub fn insert_unordered_block(&self, batch: &mut DBTransaction, bytes: &[u8], receipts: Vec<Receipt>, parent_td: Option<U256>, is_best: bool, is_ancient: bool) -> bool {
|
||||
let block = BlockView::new(bytes);
|
||||
let header = block.header_view();
|
||||
let hash = header.sha3();
|
||||
@@ -656,8 +704,6 @@ impl BlockChain {
|
||||
|
||||
assert!(self.pending_best_block.read().is_none());
|
||||
|
||||
let mut batch = self.db.transaction();
|
||||
|
||||
let block_rlp = UntrustedRlp::new(bytes);
|
||||
let compressed_header = block_rlp.at(0).unwrap().compress(RlpType::Blocks);
|
||||
let compressed_body = UntrustedRlp::new(&Self::block_to_body(bytes)).compress(RlpType::Blocks);
|
||||
@@ -671,13 +717,13 @@ impl BlockChain {
|
||||
if let Some(parent_details) = maybe_parent {
|
||||
// parent known to be in chain.
|
||||
let info = BlockInfo {
|
||||
hash: hash,
|
||||
hash: hash.clone(),
|
||||
number: header.number(),
|
||||
total_difficulty: parent_details.total_difficulty + header.difficulty(),
|
||||
location: BlockLocation::CanonChain,
|
||||
};
|
||||
|
||||
self.prepare_update(&mut batch, ExtrasUpdate {
|
||||
self.prepare_update(batch, ExtrasUpdate {
|
||||
block_hashes: self.prepare_block_hashes_update(bytes, &info),
|
||||
block_details: self.prepare_block_details_update(bytes, &info),
|
||||
block_receipts: self.prepare_block_receipts_update(receipts, &info),
|
||||
@@ -686,7 +732,21 @@ impl BlockChain {
|
||||
info: info,
|
||||
block: bytes
|
||||
}, is_best);
|
||||
self.db.write(batch).unwrap();
|
||||
|
||||
if is_ancient {
|
||||
let mut best_ancient_block = self.best_ancient_block.write();
|
||||
let ancient_number = best_ancient_block.as_ref().map_or(0, |b| b.number);
|
||||
if self.block_hash(header.number() + 1).is_some() {
|
||||
batch.delete(db::COL_EXTRA, b"ancient");
|
||||
*best_ancient_block = None;
|
||||
} else if header.number() > ancient_number {
|
||||
batch.put(db::COL_EXTRA, b"ancient", &hash);
|
||||
*best_ancient_block = Some(BestAncientBlock {
|
||||
hash: hash,
|
||||
number: header.number(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
} else {
|
||||
@@ -711,7 +771,7 @@ impl BlockChain {
|
||||
let mut update = HashMap::new();
|
||||
update.insert(hash, block_details);
|
||||
|
||||
self.prepare_update(&mut batch, ExtrasUpdate {
|
||||
self.prepare_update(batch, ExtrasUpdate {
|
||||
block_hashes: self.prepare_block_hashes_update(bytes, &info),
|
||||
block_details: update,
|
||||
block_receipts: self.prepare_block_receipts_update(receipts, &info),
|
||||
@@ -720,8 +780,6 @@ impl BlockChain {
|
||||
info: info,
|
||||
block: bytes,
|
||||
}, is_best);
|
||||
self.db.write(batch).unwrap();
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
@@ -1207,6 +1265,24 @@ impl BlockChain {
|
||||
body.append_raw(block_rlp.at(2).as_raw(), 1);
|
||||
body.out()
|
||||
}
|
||||
|
||||
/// Returns general blockchain information
|
||||
pub fn chain_info(&self) -> BlockChainInfo {
|
||||
// ensure data consistencly by locking everything first
|
||||
let best_block = self.best_block.read();
|
||||
let best_ancient_block = self.best_ancient_block.read();
|
||||
BlockChainInfo {
|
||||
total_difficulty: best_block.total_difficulty.clone(),
|
||||
pending_total_difficulty: best_block.total_difficulty.clone(),
|
||||
genesis_hash: self.genesis_hash(),
|
||||
best_block_hash: best_block.hash.clone(),
|
||||
best_block_number: best_block.number,
|
||||
first_block_hash: self.first_block(),
|
||||
first_block_number: From::from(self.first_block_number()),
|
||||
ancient_block_hash: best_ancient_block.as_ref().map(|b| b.hash.clone()),
|
||||
ancient_block_number: best_ancient_block.as_ref().map(|b| b.number),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -55,18 +55,23 @@ impl<T> CacheManager<T> where T: Eq + Hash {
|
||||
}
|
||||
|
||||
for _ in 0..COLLECTION_QUEUE_SIZE {
|
||||
let current_size = notify_unused(self.cache_usage.pop_back().unwrap());
|
||||
self.cache_usage.push_front(Default::default());
|
||||
if current_size < self.max_cache_size {
|
||||
break;
|
||||
if let Some(back) = self.cache_usage.pop_back() {
|
||||
let current_size = notify_unused(back);
|
||||
self.cache_usage.push_front(Default::default());
|
||||
if current_size < self.max_cache_size {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn rotate_cache_if_needed(&mut self) {
|
||||
if self.cache_usage.len() == 0 { return }
|
||||
|
||||
if self.cache_usage[0].len() * self.bytes_per_cache_entry > self.pref_cache_size / COLLECTION_QUEUE_SIZE {
|
||||
let cache = self.cache_usage.pop_back().unwrap();
|
||||
self.cache_usage.push_front(cache);
|
||||
if let Some(cache) = self.cache_usage.pop_back() {
|
||||
self.cache_usage.push_front(cache);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ use util::kvdb::*;
|
||||
|
||||
// other
|
||||
use io::*;
|
||||
use views::{HeaderView, BodyView};
|
||||
use views::{HeaderView, BodyView, BlockView};
|
||||
use error::{ImportError, ExecutionError, CallError, BlockError, ImportResult, Error as EthcoreError};
|
||||
use header::BlockNumber;
|
||||
use state::State;
|
||||
@@ -46,7 +46,7 @@ use transaction::{LocalizedTransaction, SignedTransaction, Action};
|
||||
use blockchain::extras::TransactionAddress;
|
||||
use types::filter::Filter;
|
||||
use log_entry::LocalizedLogEntry;
|
||||
use verification::queue::{BlockQueue, QueueInfo as BlockQueueInfo};
|
||||
use verification::queue::BlockQueue;
|
||||
use blockchain::{BlockChain, BlockProvider, TreeRoute, ImportRoute};
|
||||
use client::{
|
||||
BlockID, TransactionID, UncleID, TraceId, ClientConfig, BlockChainClient,
|
||||
@@ -71,9 +71,11 @@ use state_db::StateDB;
|
||||
pub use types::blockchain_info::BlockChainInfo;
|
||||
pub use types::block_status::BlockStatus;
|
||||
pub use blockchain::CacheSize as BlockChainCacheSize;
|
||||
pub use verification::queue::QueueInfo as BlockQueueInfo;
|
||||
|
||||
const MAX_TX_QUEUE_SIZE: usize = 4096;
|
||||
const MAX_QUEUE_SIZE_TO_SLEEP_ON: usize = 2;
|
||||
const MIN_HISTORY_SIZE: u64 = 8;
|
||||
|
||||
impl fmt::Display for BlockChainInfo {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
@@ -141,12 +143,9 @@ pub struct Client {
|
||||
queue_transactions: AtomicUsize,
|
||||
last_hashes: RwLock<VecDeque<H256>>,
|
||||
factories: Factories,
|
||||
history: u64,
|
||||
}
|
||||
|
||||
/// The pruning constant -- how old blocks must be before we
|
||||
/// assume finality of a given candidate.
|
||||
pub const HISTORY: u64 = 1200;
|
||||
|
||||
impl Client {
|
||||
/// Create a new client with given spec and DB path and custom verifier.
|
||||
pub fn new(
|
||||
@@ -170,13 +169,35 @@ impl Client {
|
||||
};
|
||||
|
||||
let journal_db = journaldb::new(db.clone(), config.pruning, ::db::COL_STATE);
|
||||
let mut state_db = StateDB::new(journal_db);
|
||||
let mut state_db = StateDB::new(journal_db, config.state_cache_size);
|
||||
if state_db.journal_db().is_empty() && try!(spec.ensure_db_good(&mut state_db)) {
|
||||
let mut batch = DBTransaction::new(&db);
|
||||
try!(state_db.commit(&mut batch, 0, &spec.genesis_header().hash(), None));
|
||||
try!(state_db.journal_under(&mut batch, 0, &spec.genesis_header().hash()));
|
||||
try!(db.write(batch).map_err(ClientError::Database));
|
||||
}
|
||||
|
||||
trace!("Cleanup journal: DB Earliest = {:?}, Latest = {:?}", state_db.journal_db().earliest_era(), state_db.journal_db().latest_era());
|
||||
|
||||
let history = if config.history < MIN_HISTORY_SIZE {
|
||||
info!(target: "client", "Ignoring pruning history parameter of {}\
|
||||
, falling back to minimum of {}",
|
||||
config.history, MIN_HISTORY_SIZE);
|
||||
MIN_HISTORY_SIZE
|
||||
} else {
|
||||
config.history
|
||||
};
|
||||
|
||||
if let (Some(earliest), Some(latest)) = (state_db.journal_db().earliest_era(), state_db.journal_db().latest_era()) {
|
||||
if latest > earliest && latest - earliest > history {
|
||||
for era in earliest..(latest - history + 1) {
|
||||
trace!("Removing era {}", era);
|
||||
let mut batch = DBTransaction::new(&db);
|
||||
try!(state_db.mark_canonical(&mut batch, era, &chain.block_hash(era).expect("Old block not found in the database")));
|
||||
try!(db.write(batch).map_err(ClientError::Database));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !chain.block_header(&chain.best_block_hash()).map_or(true, |h| state_db.journal_db().contains(h.state_root())) {
|
||||
warn!("State root not found for block #{} ({})", chain.best_block_number(), chain.best_block_hash().hex());
|
||||
}
|
||||
@@ -190,7 +211,7 @@ impl Client {
|
||||
let awake = match config.mode { Mode::Dark(..) => false, _ => true };
|
||||
|
||||
let factories = Factories {
|
||||
vm: EvmFactory::new(config.vm_type.clone()),
|
||||
vm: EvmFactory::new(config.vm_type.clone(), config.jump_table_size),
|
||||
trie: TrieFactory::new(trie_spec),
|
||||
accountdb: Default::default(),
|
||||
};
|
||||
@@ -217,6 +238,7 @@ impl Client {
|
||||
queue_transactions: AtomicUsize::new(0),
|
||||
last_hashes: RwLock::new(VecDeque::new()),
|
||||
factories: factories,
|
||||
history: history,
|
||||
};
|
||||
Ok(Arc::new(client))
|
||||
}
|
||||
@@ -275,7 +297,7 @@ impl Client {
|
||||
let chain = self.chain.read();
|
||||
// Check the block isn't so old we won't be able to enact it.
|
||||
let best_block_number = chain.best_block_number();
|
||||
if best_block_number >= HISTORY && header.number() <= best_block_number - HISTORY {
|
||||
if best_block_number >= self.history && header.number() <= best_block_number - self.history {
|
||||
warn!(target: "client", "Block import failed for #{} ({})\nBlock is ancient (current best block: #{}).", header.number(), header.hash(), best_block_number);
|
||||
return Err(());
|
||||
}
|
||||
@@ -340,16 +362,19 @@ impl Client {
|
||||
|
||||
/// This is triggered by a message coming from a block queue when the block is ready for insertion
|
||||
pub fn import_verified_blocks(&self) -> usize {
|
||||
let max_blocks_to_import = 64;
|
||||
let (imported_blocks, import_results, invalid_blocks, imported, duration) = {
|
||||
let max_blocks_to_import = 4;
|
||||
let (imported_blocks, import_results, invalid_blocks, imported, duration, is_empty) = {
|
||||
let mut imported_blocks = Vec::with_capacity(max_blocks_to_import);
|
||||
let mut invalid_blocks = HashSet::new();
|
||||
let mut import_results = Vec::with_capacity(max_blocks_to_import);
|
||||
|
||||
let _import_lock = self.import_lock.lock();
|
||||
let blocks = self.block_queue.drain(max_blocks_to_import);
|
||||
if blocks.is_empty() {
|
||||
return 0;
|
||||
}
|
||||
let _timer = PerfTimer::new("import_verified_blocks");
|
||||
let start = precise_time_ns();
|
||||
let blocks = self.block_queue.drain(max_blocks_to_import);
|
||||
|
||||
for block in blocks {
|
||||
let header = &block.header;
|
||||
@@ -373,23 +398,19 @@ impl Client {
|
||||
let imported = imported_blocks.len();
|
||||
let invalid_blocks = invalid_blocks.into_iter().collect::<Vec<H256>>();
|
||||
|
||||
{
|
||||
if !invalid_blocks.is_empty() {
|
||||
self.block_queue.mark_as_bad(&invalid_blocks);
|
||||
}
|
||||
if !imported_blocks.is_empty() {
|
||||
self.block_queue.mark_as_good(&imported_blocks);
|
||||
}
|
||||
if !invalid_blocks.is_empty() {
|
||||
self.block_queue.mark_as_bad(&invalid_blocks);
|
||||
}
|
||||
let is_empty = self.block_queue.mark_as_good(&imported_blocks);
|
||||
let duration_ns = precise_time_ns() - start;
|
||||
(imported_blocks, import_results, invalid_blocks, imported, duration_ns)
|
||||
(imported_blocks, import_results, invalid_blocks, imported, duration_ns, is_empty)
|
||||
};
|
||||
|
||||
{
|
||||
if !imported_blocks.is_empty() && self.block_queue.queue_info().is_empty() {
|
||||
if !imported_blocks.is_empty() && is_empty {
|
||||
let (enacted, retracted) = self.calculate_enacted_retracted(&import_results);
|
||||
|
||||
if self.queue_info().is_empty() {
|
||||
if is_empty {
|
||||
self.miner.chain_new_blocks(self, &imported_blocks, &invalid_blocks, &enacted, &retracted);
|
||||
}
|
||||
|
||||
@@ -410,17 +431,33 @@ impl Client {
|
||||
imported
|
||||
}
|
||||
|
||||
/// Import a block with transaction receipts.
|
||||
/// The block is guaranteed to be the next best blocks in the first block sequence.
|
||||
/// Does no sealing or transaction validation.
|
||||
fn import_old_block(&self, block_bytes: Bytes, receipts_bytes: Bytes) -> H256 {
|
||||
let block = BlockView::new(&block_bytes);
|
||||
let hash = block.header().hash();
|
||||
let _import_lock = self.import_lock.lock();
|
||||
{
|
||||
let _timer = PerfTimer::new("import_old_block");
|
||||
let chain = self.chain.read();
|
||||
|
||||
// Commit results
|
||||
let receipts = ::rlp::decode(&receipts_bytes);
|
||||
let mut batch = DBTransaction::new(&self.db.read());
|
||||
chain.insert_unordered_block(&mut batch, &block_bytes, receipts, None, false, true);
|
||||
// Final commit to the DB
|
||||
self.db.read().write_buffered(batch);
|
||||
chain.commit();
|
||||
}
|
||||
self.db.read().flush().expect("DB flush failed.");
|
||||
hash
|
||||
}
|
||||
|
||||
fn commit_block<B>(&self, block: B, hash: &H256, block_data: &[u8]) -> ImportRoute where B: IsBlock + Drain {
|
||||
let number = block.header().number();
|
||||
let parent = block.header().parent_hash().clone();
|
||||
let chain = self.chain.read();
|
||||
// Are we committing an era?
|
||||
let ancient = if number >= HISTORY {
|
||||
let n = number - HISTORY;
|
||||
Some((n, chain.block_hash(n).expect("only verified blocks can be commited; verified block has hash; qed")))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// Commit results
|
||||
let receipts = block.receipts().to_owned();
|
||||
@@ -436,7 +473,17 @@ impl Client {
|
||||
// already-imported block of the same number.
|
||||
// TODO: Prove it with a test.
|
||||
let mut state = block.drain();
|
||||
state.commit(&mut batch, number, hash, ancient).expect("DB commit failed.");
|
||||
|
||||
state.journal_under(&mut batch, number, hash).expect("DB commit failed");
|
||||
|
||||
if number >= self.history {
|
||||
let n = number - self.history;
|
||||
if let Some(ancient_hash) = chain.block_hash(n) {
|
||||
state.mark_canonical(&mut batch, n, &ancient_hash).expect("DB commit failed");
|
||||
} else {
|
||||
debug!(target: "client", "Missing expected hash for block {}", n);
|
||||
}
|
||||
}
|
||||
|
||||
let route = chain.insert_block(&mut batch, block_data, receipts);
|
||||
self.tracedb.read().import(&mut batch, TraceImportRequest {
|
||||
@@ -446,6 +493,7 @@ impl Client {
|
||||
enacted: route.enacted.clone(),
|
||||
retracted: route.retracted.len()
|
||||
});
|
||||
|
||||
let is_canon = route.enacted.last().map_or(false, |h| h == hash);
|
||||
state.sync_cache(&route.enacted, &route.retracted, is_canon);
|
||||
// Final commit to the DB
|
||||
@@ -501,7 +549,7 @@ impl Client {
|
||||
let db = self.state_db.lock().boxed_clone();
|
||||
|
||||
// early exit for pruned blocks
|
||||
if db.is_pruned() && self.chain.read().best_block_number() >= block_number + HISTORY {
|
||||
if db.is_pruned() && self.chain.read().best_block_number() >= block_number + self.history {
|
||||
return None;
|
||||
}
|
||||
|
||||
@@ -606,20 +654,23 @@ impl Client {
|
||||
let best_block_number = self.chain_info().best_block_number;
|
||||
let block_number = try!(self.block_number(at).ok_or(snapshot::Error::InvalidStartingBlock(at)));
|
||||
|
||||
if best_block_number > HISTORY + block_number && db.is_pruned() {
|
||||
if best_block_number > self.history + block_number && db.is_pruned() {
|
||||
return Err(snapshot::Error::OldBlockPrunedDB.into());
|
||||
}
|
||||
|
||||
let history = ::std::cmp::min(self.history, 1000);
|
||||
|
||||
let start_hash = match at {
|
||||
BlockID::Latest => {
|
||||
let start_num = if best_block_number > 1000 {
|
||||
best_block_number - 1000
|
||||
} else {
|
||||
0
|
||||
let start_num = match db.earliest_era() {
|
||||
Some(era) => ::std::cmp::max(era, best_block_number - history),
|
||||
None => best_block_number - history,
|
||||
};
|
||||
|
||||
self.block_hash(BlockID::Number(start_num))
|
||||
.expect("blocks within HISTORY are always stored.")
|
||||
match self.block_hash(BlockID::Number(start_num)) {
|
||||
Some(h) => h,
|
||||
None => return Err(snapshot::Error::InvalidStartingBlock(at).into()),
|
||||
}
|
||||
}
|
||||
_ => match self.block_hash(at) {
|
||||
Some(hash) => hash,
|
||||
@@ -632,6 +683,11 @@ impl Client {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Ask the client what the history parameter is.
|
||||
pub fn pruning_history(&self) -> u64 {
|
||||
self.history
|
||||
}
|
||||
|
||||
fn block_hash(chain: &BlockChain, id: BlockID) -> Option<H256> {
|
||||
match id {
|
||||
BlockID::Hash(hash) => Some(hash),
|
||||
@@ -688,7 +744,8 @@ impl snapshot::DatabaseRestore for Client {
|
||||
let db = self.db.write();
|
||||
try!(db.restore(new_db));
|
||||
|
||||
*state_db = StateDB::new(journaldb::new(db.clone(), self.pruning, ::db::COL_STATE));
|
||||
let cache_size = state_db.cache_size();
|
||||
*state_db = StateDB::new(journaldb::new(db.clone(), self.pruning, ::db::COL_STATE), cache_size);
|
||||
*chain = Arc::new(BlockChain::new(self.config.blockchain.clone(), &[], db.clone()));
|
||||
*tracedb = TraceDB::new(self.config.tracing.clone(), db.clone(), chain.clone());
|
||||
Ok(())
|
||||
@@ -974,6 +1031,20 @@ impl BlockChainClient for Client {
|
||||
Ok(try!(self.block_queue.import(unverified)))
|
||||
}
|
||||
|
||||
fn import_block_with_receipts(&self, block_bytes: Bytes, receipts_bytes: Bytes) -> Result<H256, BlockImportError> {
|
||||
{
|
||||
// check block order
|
||||
let header = BlockView::new(&block_bytes).header_view();
|
||||
if self.chain.read().is_known(&header.hash()) {
|
||||
return Err(BlockImportError::Import(ImportError::AlreadyInChain));
|
||||
}
|
||||
if self.block_status(BlockID::Hash(header.parent_hash())) == BlockStatus::Unknown {
|
||||
return Err(BlockImportError::Block(BlockError::UnknownParent(header.parent_hash())));
|
||||
}
|
||||
}
|
||||
Ok(self.import_old_block(block_bytes, receipts_bytes))
|
||||
}
|
||||
|
||||
fn queue_info(&self) -> BlockQueueInfo {
|
||||
self.block_queue.queue_info()
|
||||
}
|
||||
@@ -983,14 +1054,7 @@ impl BlockChainClient for Client {
|
||||
}
|
||||
|
||||
fn chain_info(&self) -> BlockChainInfo {
|
||||
let chain = self.chain.read();
|
||||
BlockChainInfo {
|
||||
total_difficulty: chain.best_block_total_difficulty(),
|
||||
pending_total_difficulty: chain.best_block_total_difficulty(),
|
||||
genesis_hash: chain.genesis_hash(),
|
||||
best_block_hash: chain.best_block_hash(),
|
||||
best_block_number: From::from(chain.best_block_number())
|
||||
}
|
||||
self.chain.read().chain_info()
|
||||
}
|
||||
|
||||
fn additional_params(&self) -> BTreeMap<String, String> {
|
||||
@@ -1120,21 +1184,22 @@ impl MiningBlockChainClient for Client {
|
||||
}
|
||||
|
||||
fn import_sealed_block(&self, block: SealedBlock) -> ImportResult {
|
||||
let _import_lock = self.import_lock.lock();
|
||||
let _timer = PerfTimer::new("import_sealed_block");
|
||||
let start = precise_time_ns();
|
||||
|
||||
let h = block.header().hash();
|
||||
let number = block.header().number();
|
||||
|
||||
let block_data = block.rlp_bytes();
|
||||
let route = self.commit_block(block, &h, &block_data);
|
||||
trace!(target: "client", "Imported sealed block #{} ({})", number, h);
|
||||
self.state_db.lock().sync_cache(&route.enacted, &route.retracted, false);
|
||||
let start = precise_time_ns();
|
||||
let route = {
|
||||
// scope for self.import_lock
|
||||
let _import_lock = self.import_lock.lock();
|
||||
let _timer = PerfTimer::new("import_sealed_block");
|
||||
|
||||
let number = block.header().number();
|
||||
let block_data = block.rlp_bytes();
|
||||
let route = self.commit_block(block, &h, &block_data);
|
||||
trace!(target: "client", "Imported sealed block #{} ({})", number, h);
|
||||
self.state_db.lock().sync_cache(&route.enacted, &route.retracted, false);
|
||||
route
|
||||
};
|
||||
let (enacted, retracted) = self.calculate_enacted_retracted(&[route]);
|
||||
self.miner.chain_new_blocks(self, &[h.clone()], &[], &enacted, &retracted);
|
||||
|
||||
self.notify(|notify| {
|
||||
notify.new_blocks(
|
||||
vec![h.clone()],
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use std::str::FromStr;
|
||||
use std::path::Path;
|
||||
pub use std::time::Duration;
|
||||
pub use blockchain::Config as BlockChainConfig;
|
||||
pub use trace::Config as TraceConfig;
|
||||
@@ -26,23 +27,26 @@ use util::{journaldb, CompactionProfile};
|
||||
/// Client state db compaction profile
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum DatabaseCompactionProfile {
|
||||
/// Default compaction profile
|
||||
Default,
|
||||
/// Try to determine compaction profile automatically
|
||||
Auto,
|
||||
/// SSD compaction profile
|
||||
SSD,
|
||||
/// HDD or other slow storage io compaction profile
|
||||
HDD,
|
||||
}
|
||||
|
||||
impl Default for DatabaseCompactionProfile {
|
||||
fn default() -> Self {
|
||||
DatabaseCompactionProfile::Default
|
||||
DatabaseCompactionProfile::Auto
|
||||
}
|
||||
}
|
||||
|
||||
impl DatabaseCompactionProfile {
|
||||
/// Returns corresponding compaction profile.
|
||||
pub fn compaction_profile(&self) -> CompactionProfile {
|
||||
pub fn compaction_profile(&self, db_path: &Path) -> CompactionProfile {
|
||||
match *self {
|
||||
DatabaseCompactionProfile::Default => Default::default(),
|
||||
DatabaseCompactionProfile::Auto => CompactionProfile::auto(db_path),
|
||||
DatabaseCompactionProfile::SSD => CompactionProfile::ssd(),
|
||||
DatabaseCompactionProfile::HDD => CompactionProfile::hdd(),
|
||||
}
|
||||
}
|
||||
@@ -53,9 +57,10 @@ impl FromStr for DatabaseCompactionProfile {
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"ssd" | "default" => Ok(DatabaseCompactionProfile::Default),
|
||||
"auto" => Ok(DatabaseCompactionProfile::Auto),
|
||||
"ssd" => Ok(DatabaseCompactionProfile::SSD),
|
||||
"hdd" => Ok(DatabaseCompactionProfile::HDD),
|
||||
_ => Err("Invalid compaction profile given. Expected hdd/ssd (default).".into()),
|
||||
_ => Err("Invalid compaction profile given. Expected default/hdd/ssd.".into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -96,7 +101,7 @@ pub struct ClientConfig {
|
||||
pub pruning: journaldb::Algorithm,
|
||||
/// The name of the client instance.
|
||||
pub name: String,
|
||||
/// State db cache-size if not default
|
||||
/// RocksDB state column cache-size if not default
|
||||
pub db_cache_size: Option<usize>,
|
||||
/// State db compaction profile
|
||||
pub db_compaction: DatabaseCompactionProfile,
|
||||
@@ -106,6 +111,12 @@ pub struct ClientConfig {
|
||||
pub mode: Mode,
|
||||
/// Type of block verifier used by client.
|
||||
pub verifier_type: VerifierType,
|
||||
/// State db cache-size.
|
||||
pub state_cache_size: usize,
|
||||
/// EVM jump-tables cache size.
|
||||
pub jump_table_size: usize,
|
||||
/// State pruning history size.
|
||||
pub history: u64,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -114,13 +125,13 @@ mod test {
|
||||
|
||||
#[test]
|
||||
fn test_default_compaction_profile() {
|
||||
assert_eq!(DatabaseCompactionProfile::default(), DatabaseCompactionProfile::Default);
|
||||
assert_eq!(DatabaseCompactionProfile::default(), DatabaseCompactionProfile::Auto);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parsing_compaction_profile() {
|
||||
assert_eq!(DatabaseCompactionProfile::Default, "ssd".parse().unwrap());
|
||||
assert_eq!(DatabaseCompactionProfile::Default, "default".parse().unwrap());
|
||||
assert_eq!(DatabaseCompactionProfile::Auto, "auto".parse().unwrap());
|
||||
assert_eq!(DatabaseCompactionProfile::SSD, "ssd".parse().unwrap());
|
||||
assert_eq!(DatabaseCompactionProfile::HDD, "hdd".parse().unwrap());
|
||||
}
|
||||
|
||||
|
||||
@@ -140,7 +140,7 @@ impl TestBlockChainClient {
|
||||
queue_size: AtomicUsize::new(0),
|
||||
miner: Arc::new(Miner::with_spec(&spec)),
|
||||
spec: spec,
|
||||
vm_factory: EvmFactory::new(VMType::Interpreter),
|
||||
vm_factory: EvmFactory::new(VMType::Interpreter, 1024 * 1024),
|
||||
latest_block_timestamp: RwLock::new(10_000_000),
|
||||
};
|
||||
client.add_blocks(1, EachBlockWith::Nothing); // add genesis block
|
||||
@@ -308,7 +308,7 @@ pub fn get_temp_state_db() -> GuardedTempResult<StateDB> {
|
||||
let temp = RandomTempPath::new();
|
||||
let db = Database::open(&DatabaseConfig::with_columns(NUM_COLUMNS), temp.as_str()).unwrap();
|
||||
let journal_db = journaldb::new(Arc::new(db), journaldb::Algorithm::EarlyMerge, COL_STATE);
|
||||
let state_db = StateDB::new(journal_db);
|
||||
let state_db = StateDB::new(journal_db, 1024 * 1024);
|
||||
GuardedTempResult {
|
||||
_temp: temp,
|
||||
result: Some(state_db)
|
||||
@@ -570,6 +570,10 @@ impl BlockChainClient for TestBlockChainClient {
|
||||
Ok(h)
|
||||
}
|
||||
|
||||
fn import_block_with_receipts(&self, b: Bytes, _r: Bytes) -> Result<H256, BlockImportError> {
|
||||
self.import_block(b)
|
||||
}
|
||||
|
||||
fn queue_info(&self) -> QueueInfo {
|
||||
QueueInfo {
|
||||
verified_queue_size: self.queue_size.load(AtomicOrder::Relaxed),
|
||||
@@ -595,6 +599,10 @@ impl BlockChainClient for TestBlockChainClient {
|
||||
genesis_hash: self.genesis_hash.clone(),
|
||||
best_block_hash: self.last_hash.read().clone(),
|
||||
best_block_number: self.blocks.read().len() as BlockNumber - 1,
|
||||
first_block_hash: None,
|
||||
first_block_number: None,
|
||||
ancient_block_hash: None,
|
||||
ancient_block_number: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -139,6 +139,9 @@ pub trait BlockChainClient : Sync + Send {
|
||||
/// Import a block into the blockchain.
|
||||
fn import_block(&self, bytes: Bytes) -> Result<H256, BlockImportError>;
|
||||
|
||||
/// Import a block with transaction receipts. Does no sealing and transaction validation.
|
||||
fn import_block_with_receipts(&self, block_bytes: Bytes, receipts_bytes: Bytes) -> Result<H256, BlockImportError>;
|
||||
|
||||
/// Get block queue information.
|
||||
fn queue_info(&self) -> BlockQueueInfo;
|
||||
|
||||
|
||||
@@ -197,7 +197,7 @@ impl Engine for AuthorityRound {
|
||||
if self.is_step_proposer(step, header.author()) {
|
||||
if let Some(ap) = accounts {
|
||||
// Account should be permanently unlocked, otherwise sealing will fail.
|
||||
if let Ok(signature) = ap.sign(*header.author(), header.bare_hash()) {
|
||||
if let Ok(signature) = ap.sign(*header.author(), None, header.bare_hash()) {
|
||||
return Some(vec![encode(&step).to_vec(), encode(&(&*signature as &[u8])).to_vec()]);
|
||||
} else {
|
||||
trace!(target: "authorityround", "generate_seal: FAIL: accounts secret key unavailable");
|
||||
|
||||
@@ -112,7 +112,7 @@ impl Engine for BasicAuthority {
|
||||
let header = block.header();
|
||||
let message = header.bare_hash();
|
||||
// account should be pernamently unlocked, otherwise sealing will fail
|
||||
if let Ok(signature) = ap.sign(*block.header().author(), message) {
|
||||
if let Ok(signature) = ap.sign(*block.header().author(), None, message) {
|
||||
return Some(vec![::rlp::encode(&(&*signature as &[u8])).to_vec()]);
|
||||
} else {
|
||||
trace!(target: "basicauthority", "generate_seal: FAIL: accounts secret key unavailable");
|
||||
|
||||
@@ -127,10 +127,14 @@ pub trait Engine : Sync + Send {
|
||||
fn is_builtin(&self, a: &Address) -> bool { self.builtins().contains_key(a) }
|
||||
/// Determine the code execution cost of the builtin contract with address `a`.
|
||||
/// Panics if `is_builtin(a)` is not true.
|
||||
fn cost_of_builtin(&self, a: &Address, input: &[u8]) -> U256 { self.builtins().get(a).unwrap().cost(input.len()) }
|
||||
fn cost_of_builtin(&self, a: &Address, input: &[u8]) -> U256 {
|
||||
self.builtins().get(a).expect("queried cost of nonexistent builtin").cost(input.len())
|
||||
}
|
||||
/// Execution the builtin contract `a` on `input` and return `output`.
|
||||
/// Panics if `is_builtin(a)` is not true.
|
||||
fn execute_builtin(&self, a: &Address, input: &[u8], output: &mut BytesRef) { self.builtins().get(a).unwrap().execute(input, output); }
|
||||
fn execute_builtin(&self, a: &Address, input: &[u8], output: &mut BytesRef) {
|
||||
self.builtins().get(a).expect("attempted to execute nonexistent builtin").execute(input, output);
|
||||
}
|
||||
|
||||
/// Add a channel for communication with Client which can be used for sealing.
|
||||
fn register_message_channel(&self, _message_channel: IoChannel<ClientIoMessage>) {}
|
||||
|
||||
@@ -41,7 +41,7 @@ pub struct EthashParams {
|
||||
/// Namereg contract address.
|
||||
pub registrar: Address,
|
||||
/// Homestead transition block number.
|
||||
pub frontier_compatibility_mode_limit: u64,
|
||||
pub homestead_transition: u64,
|
||||
/// DAO hard-fork transition block (X).
|
||||
pub dao_hardfork_transition: u64,
|
||||
/// DAO hard-fork refund contract address (C).
|
||||
@@ -54,6 +54,8 @@ pub struct EthashParams {
|
||||
pub difficulty_hardfork_bound_divisor: U256,
|
||||
/// Block on which there is no additional difficulty from the exponential bomb.
|
||||
pub bomb_defuse_transition: u64,
|
||||
/// Bad gas transition block number.
|
||||
pub eip150_transition: u64,
|
||||
}
|
||||
|
||||
impl From<ethjson::spec::EthashParams> for EthashParams {
|
||||
@@ -66,13 +68,14 @@ impl From<ethjson::spec::EthashParams> for EthashParams {
|
||||
duration_limit: p.duration_limit.into(),
|
||||
block_reward: p.block_reward.into(),
|
||||
registrar: p.registrar.map_or_else(Address::new, Into::into),
|
||||
frontier_compatibility_mode_limit: p.frontier_compatibility_mode_limit.map_or(0, Into::into),
|
||||
homestead_transition: p.homestead_transition.map_or(0, Into::into),
|
||||
dao_hardfork_transition: p.dao_hardfork_transition.map_or(0x7fffffffffffffff, Into::into),
|
||||
dao_hardfork_beneficiary: p.dao_hardfork_beneficiary.map_or_else(Address::new, Into::into),
|
||||
dao_hardfork_accounts: p.dao_hardfork_accounts.unwrap_or_else(Vec::new).into_iter().map(Into::into).collect(),
|
||||
difficulty_hardfork_transition: p.difficulty_hardfork_transition.map_or(0x7fffffffffffffff, Into::into),
|
||||
difficulty_hardfork_bound_divisor: p.difficulty_hardfork_bound_divisor.map_or(p.difficulty_bound_divisor.into(), Into::into),
|
||||
bomb_defuse_transition: p.bomb_defuse_transition.map_or(0x7fffffffffffffff, Into::into),
|
||||
eip150_transition: p.eip150_transition.map_or(0, Into::into),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -117,12 +120,14 @@ impl Engine for Ethash {
|
||||
}
|
||||
|
||||
fn schedule(&self, env_info: &EnvInfo) -> Schedule {
|
||||
trace!(target: "client", "Creating schedule. fCML={}", self.ethash_params.frontier_compatibility_mode_limit);
|
||||
trace!(target: "client", "Creating schedule. fCML={}, bGCML={}", self.ethash_params.homestead_transition, self.ethash_params.eip150_transition);
|
||||
|
||||
if env_info.number < self.ethash_params.frontier_compatibility_mode_limit {
|
||||
if env_info.number < self.ethash_params.homestead_transition {
|
||||
Schedule::new_frontier()
|
||||
} else {
|
||||
} else if env_info.number < self.ethash_params.eip150_transition {
|
||||
Schedule::new_homestead()
|
||||
} else {
|
||||
Schedule::new_homestead_gas_fix()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -264,7 +269,7 @@ impl Engine for Ethash {
|
||||
}
|
||||
|
||||
fn verify_transaction_basic(&self, t: &SignedTransaction, header: &Header) -> result::Result<(), Error> {
|
||||
if header.number() >= self.ethash_params.frontier_compatibility_mode_limit {
|
||||
if header.number() >= self.ethash_params.homestead_transition {
|
||||
try!(t.check_low_s());
|
||||
}
|
||||
Ok(())
|
||||
@@ -290,7 +295,7 @@ impl Ethash {
|
||||
false => self.ethash_params.difficulty_bound_divisor,
|
||||
};
|
||||
let duration_limit = self.ethash_params.duration_limit;
|
||||
let frontier_limit = self.ethash_params.frontier_compatibility_mode_limit;
|
||||
let frontier_limit = self.ethash_params.homestead_transition;
|
||||
|
||||
let mut target = if header.number() < frontier_limit {
|
||||
if header.timestamp() >= parent.timestamp() + duration_limit {
|
||||
|
||||
@@ -51,8 +51,11 @@ pub fn new_frontier_test() -> Spec { load(include_bytes!("../../res/ethereum/fro
|
||||
/// Create a new Homestead chain spec as though it never changed from Frontier.
|
||||
pub fn new_homestead_test() -> Spec { load(include_bytes!("../../res/ethereum/homestead_test.json")) }
|
||||
|
||||
/// Create a new Homestead-EIP150 chain spec as though it never changed from Homestead/Frontier.
|
||||
pub fn new_eip150_test() -> Spec { load(include_bytes!("../../res/ethereum/eip150_test.json")) }
|
||||
|
||||
/// Create a new Frontier/Homestead/DAO chain spec with transition points at #5 and #8.
|
||||
pub fn new_daohardfork_test() -> Spec { load(include_bytes!("../../res/ethereum/daohardfork_test.json")) }
|
||||
pub fn new_transition_test() -> Spec { load(include_bytes!("../../res/ethereum/transition_test.json")) }
|
||||
|
||||
/// Create a new Frontier main net chain spec without genesis accounts.
|
||||
pub fn new_mainnet_like() -> Spec { load(include_bytes!("../../res/ethereum/frontier_like_test.json")) }
|
||||
|
||||
@@ -118,11 +118,12 @@ impl Factory {
|
||||
}
|
||||
}
|
||||
|
||||
/// Create new instance of specific `VMType` factory
|
||||
pub fn new(evm: VMType) -> Self {
|
||||
/// Create new instance of specific `VMType` factory, with a size in bytes
|
||||
/// for caching jump destinations.
|
||||
pub fn new(evm: VMType, cache_size: usize) -> Self {
|
||||
Factory {
|
||||
evm: evm,
|
||||
evm_cache: Arc::new(SharedCache::default()),
|
||||
evm_cache: Arc::new(SharedCache::new(cache_size)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,22 +165,22 @@ macro_rules! evm_test(
|
||||
#[ignore]
|
||||
#[cfg(feature = "jit")]
|
||||
fn $name_jit() {
|
||||
$name_test(Factory::new(VMType::Jit));
|
||||
$name_test(Factory::new(VMType::Jit, 1024 * 32));
|
||||
}
|
||||
#[test]
|
||||
fn $name_int() {
|
||||
$name_test(Factory::new(VMType::Interpreter));
|
||||
$name_test(Factory::new(VMType::Interpreter, 1024 * 32));
|
||||
}
|
||||
};
|
||||
($name_test: ident: $name_jit: ident, $name_int: ident) => {
|
||||
#[test]
|
||||
#[cfg(feature = "jit")]
|
||||
fn $name_jit() {
|
||||
$name_test(Factory::new(VMType::Jit));
|
||||
$name_test(Factory::new(VMType::Jit, 1024 * 32));
|
||||
}
|
||||
#[test]
|
||||
fn $name_int() {
|
||||
$name_test(Factory::new(VMType::Interpreter));
|
||||
$name_test(Factory::new(VMType::Interpreter, 1024 * 32));
|
||||
}
|
||||
}
|
||||
);
|
||||
@@ -193,13 +194,13 @@ macro_rules! evm_test_ignore(
|
||||
#[cfg(feature = "jit")]
|
||||
#[cfg(feature = "ignored-tests")]
|
||||
fn $name_jit() {
|
||||
$name_test(Factory::new(VMType::Jit));
|
||||
$name_test(Factory::new(VMType::Jit, 1024 * 32));
|
||||
}
|
||||
#[test]
|
||||
#[ignore]
|
||||
#[cfg(feature = "ignored-tests")]
|
||||
fn $name_int() {
|
||||
$name_test(Factory::new(VMType::Interpreter));
|
||||
$name_test(Factory::new(VMType::Interpreter, 1024 * 32));
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
@@ -19,6 +19,7 @@ use super::u256_to_address;
|
||||
use evm::{self, CostType};
|
||||
use evm::instructions::{self, Instruction, InstructionInfo};
|
||||
use evm::interpreter::stack::Stack;
|
||||
use evm::schedule::Schedule;
|
||||
|
||||
macro_rules! overflowing {
|
||||
($x: expr) => {{
|
||||
@@ -31,7 +32,7 @@ macro_rules! overflowing {
|
||||
#[cfg_attr(feature="dev", allow(enum_variant_names))]
|
||||
enum InstructionCost<Cost: CostType> {
|
||||
Gas(Cost),
|
||||
GasMem(Cost, Cost),
|
||||
GasMem(Cost, Cost, Option<Cost>),
|
||||
GasMemCopy(Cost, Cost, Cost)
|
||||
}
|
||||
|
||||
@@ -56,7 +57,37 @@ impl<Gas: CostType> Gasometer<Gas> {
|
||||
}
|
||||
}
|
||||
|
||||
/// How much gas is provided to a CALL/CREATE, given that we need to deduct `needed` for this operation
|
||||
/// and that we `requested` some.
|
||||
pub fn gas_provided(&self, schedule: &Schedule, needed: Gas, requested: Option<evm::Result<Gas>>) -> evm::Result<Gas> {
|
||||
match schedule.sub_gas_cap_divisor {
|
||||
Some(cap_divisor) if self.current_gas >= needed => {
|
||||
let gas_remaining = self.current_gas - needed;
|
||||
let max_gas_provided = gas_remaining - gas_remaining / Gas::from(cap_divisor);
|
||||
if let Some(Ok(r)) = requested {
|
||||
Ok(min(r, max_gas_provided))
|
||||
} else {
|
||||
Ok(max_gas_provided)
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
if let Some(r) = requested {
|
||||
r
|
||||
} else if self.current_gas >= needed {
|
||||
Ok(self.current_gas - needed)
|
||||
} else {
|
||||
Ok(0.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature="dev", allow(cyclomatic_complexity))]
|
||||
/// Determine how much gas is used by the given instruction, given the machine's state.
|
||||
///
|
||||
/// We guarantee that the final element of the returned tuple (`provided`) will be `Some`
|
||||
/// iff the `instruction` is one of `CREATE`, or any of the `CALL` variants. In this case,
|
||||
/// it will be the amount of gas that the current context provides to the child context.
|
||||
pub fn get_gas_cost_mem(
|
||||
&mut self,
|
||||
ext: &evm::Ext,
|
||||
@@ -64,7 +95,7 @@ impl<Gas: CostType> Gasometer<Gas> {
|
||||
info: &InstructionInfo,
|
||||
stack: &Stack<U256>,
|
||||
current_mem_size: usize,
|
||||
) -> evm::Result<(Gas, Gas, usize)> {
|
||||
) -> evm::Result<(Gas, Gas, usize, Option<Gas>)> {
|
||||
let schedule = ext.schedule();
|
||||
let tier = instructions::get_tier_idx(info.tier);
|
||||
let default_gas = Gas::from(schedule.tier_step_gas[tier]);
|
||||
@@ -90,26 +121,42 @@ impl<Gas: CostType> Gasometer<Gas> {
|
||||
instructions::SLOAD => {
|
||||
InstructionCost::Gas(Gas::from(schedule.sload_gas))
|
||||
},
|
||||
instructions::BALANCE => {
|
||||
InstructionCost::Gas(Gas::from(schedule.balance_gas))
|
||||
},
|
||||
instructions::EXTCODESIZE => {
|
||||
InstructionCost::Gas(Gas::from(schedule.extcodesize_gas))
|
||||
},
|
||||
instructions::SUICIDE => {
|
||||
let mut gas = Gas::from(schedule.suicide_gas);
|
||||
|
||||
let address = u256_to_address(stack.peek(0));
|
||||
if !ext.exists(&address) {
|
||||
gas = overflowing!(gas.overflow_add(schedule.suicide_to_new_account_cost.into()));
|
||||
}
|
||||
|
||||
InstructionCost::Gas(gas)
|
||||
},
|
||||
instructions::MSTORE | instructions::MLOAD => {
|
||||
InstructionCost::GasMem(default_gas, try!(mem_needed_const(stack.peek(0), 32)))
|
||||
InstructionCost::GasMem(default_gas, try!(mem_needed_const(stack.peek(0), 32)), None)
|
||||
},
|
||||
instructions::MSTORE8 => {
|
||||
InstructionCost::GasMem(default_gas, try!(mem_needed_const(stack.peek(0), 1)))
|
||||
InstructionCost::GasMem(default_gas, try!(mem_needed_const(stack.peek(0), 1)), None)
|
||||
},
|
||||
instructions::RETURN => {
|
||||
InstructionCost::GasMem(default_gas, try!(mem_needed(stack.peek(0), stack.peek(1))))
|
||||
InstructionCost::GasMem(default_gas, try!(mem_needed(stack.peek(0), stack.peek(1))), None)
|
||||
},
|
||||
instructions::SHA3 => {
|
||||
let w = overflowing!(add_gas_usize(try!(Gas::from_u256(*stack.peek(1))), 31));
|
||||
let words = w >> 5;
|
||||
let gas = Gas::from(schedule.sha3_gas) + (Gas::from(schedule.sha3_word_gas) * words);
|
||||
InstructionCost::GasMem(gas, try!(mem_needed(stack.peek(0), stack.peek(1))))
|
||||
InstructionCost::GasMem(gas, try!(mem_needed(stack.peek(0), stack.peek(1))), None)
|
||||
},
|
||||
instructions::CALLDATACOPY | instructions::CODECOPY => {
|
||||
InstructionCost::GasMemCopy(default_gas, try!(mem_needed(stack.peek(0), stack.peek(2))), try!(Gas::from_u256(*stack.peek(2))))
|
||||
},
|
||||
instructions::EXTCODECOPY => {
|
||||
InstructionCost::GasMemCopy(default_gas, try!(mem_needed(stack.peek(1), stack.peek(3))), try!(Gas::from_u256(*stack.peek(3))))
|
||||
InstructionCost::GasMemCopy(schedule.extcodecopy_base_gas.into(), try!(mem_needed(stack.peek(1), stack.peek(3))), try!(Gas::from_u256(*stack.peek(3))))
|
||||
},
|
||||
instructions::LOG0...instructions::LOG4 => {
|
||||
let no_of_topics = instructions::get_log_topics(instruction);
|
||||
@@ -117,10 +164,10 @@ impl<Gas: CostType> Gasometer<Gas> {
|
||||
|
||||
let data_gas = overflowing!(try!(Gas::from_u256(*stack.peek(1))).overflow_mul(Gas::from(schedule.log_data_gas)));
|
||||
let gas = overflowing!(data_gas.overflow_add(Gas::from(log_gas)));
|
||||
InstructionCost::GasMem(gas, try!(mem_needed(stack.peek(0), stack.peek(1))))
|
||||
InstructionCost::GasMem(gas, try!(mem_needed(stack.peek(0), stack.peek(1))), None)
|
||||
},
|
||||
instructions::CALL | instructions::CALLCODE => {
|
||||
let mut gas = overflowing!(add_gas_usize(try!(Gas::from_u256(*stack.peek(0))), schedule.call_gas));
|
||||
let mut gas = Gas::from(schedule.call_gas);
|
||||
let mem = cmp::max(
|
||||
try!(mem_needed(stack.peek(5), stack.peek(6))),
|
||||
try!(mem_needed(stack.peek(3), stack.peek(4)))
|
||||
@@ -129,27 +176,49 @@ impl<Gas: CostType> Gasometer<Gas> {
|
||||
let address = u256_to_address(stack.peek(1));
|
||||
|
||||
if instruction == instructions::CALL && !ext.exists(&address) {
|
||||
gas = overflowing!(gas.overflow_add(Gas::from(schedule.call_new_account_gas)));
|
||||
gas = overflowing!(gas.overflow_add(schedule.call_new_account_gas.into()));
|
||||
};
|
||||
|
||||
if !stack.peek(2).is_zero() {
|
||||
gas = overflowing!(gas.overflow_add(Gas::from(schedule.call_value_transfer_gas)));
|
||||
gas = overflowing!(gas.overflow_add(schedule.call_value_transfer_gas.into()));
|
||||
};
|
||||
|
||||
InstructionCost::GasMem(gas,mem)
|
||||
// TODO: refactor to avoid duplicate calculation here and later on.
|
||||
let (mem_gas_cost, _, _) = try!(self.mem_gas_cost(schedule, current_mem_size, &mem));
|
||||
let cost_so_far = overflowing!(gas.overflow_add(mem_gas_cost.into()));
|
||||
let requested = Gas::from_u256(*stack.peek(0));
|
||||
let provided = try!(self.gas_provided(schedule, cost_so_far, Some(requested)));
|
||||
gas = overflowing!(gas.overflow_add(provided));
|
||||
|
||||
InstructionCost::GasMem(gas, mem, Some(provided))
|
||||
},
|
||||
instructions::DELEGATECALL => {
|
||||
let gas = overflowing!(add_gas_usize(try!(Gas::from_u256(*stack.peek(0))), schedule.call_gas));
|
||||
let mut gas = Gas::from(schedule.call_gas);
|
||||
let mem = cmp::max(
|
||||
try!(mem_needed(stack.peek(4), stack.peek(5))),
|
||||
try!(mem_needed(stack.peek(2), stack.peek(3)))
|
||||
);
|
||||
InstructionCost::GasMem(gas, mem)
|
||||
|
||||
// TODO: refactor to avoid duplicate calculation here and later on.
|
||||
let (mem_gas_cost, _, _) = try!(self.mem_gas_cost(schedule, current_mem_size, &mem));
|
||||
let cost_so_far = overflowing!(gas.overflow_add(mem_gas_cost.into()));
|
||||
let requested = Gas::from_u256(*stack.peek(0));
|
||||
let provided = try!(self.gas_provided(schedule, cost_so_far, Some(requested)));
|
||||
gas = overflowing!(gas.overflow_add(provided));
|
||||
|
||||
InstructionCost::GasMem(gas, mem, Some(provided))
|
||||
},
|
||||
instructions::CREATE => {
|
||||
let gas = Gas::from(schedule.create_gas);
|
||||
let mut gas = Gas::from(schedule.create_gas);
|
||||
let mem = try!(mem_needed(stack.peek(1), stack.peek(2)));
|
||||
InstructionCost::GasMem(gas, mem)
|
||||
|
||||
// TODO: refactor to avoid duplicate calculation here and later on.
|
||||
let (mem_gas_cost, _, _) = try!(self.mem_gas_cost(schedule, current_mem_size, &mem));
|
||||
let cost_so_far = overflowing!(gas.overflow_add(mem_gas_cost.into()));
|
||||
let provided = try!(self.gas_provided(schedule, cost_so_far, None));
|
||||
gas = overflowing!(gas.overflow_add(provided));
|
||||
|
||||
InstructionCost::GasMem(gas, mem, Some(provided))
|
||||
},
|
||||
instructions::EXP => {
|
||||
let expon = stack.peek(1);
|
||||
@@ -157,17 +226,17 @@ impl<Gas: CostType> Gasometer<Gas> {
|
||||
let gas = Gas::from(schedule.exp_gas + schedule.exp_byte_gas * bytes);
|
||||
InstructionCost::Gas(gas)
|
||||
},
|
||||
_ => InstructionCost::Gas(default_gas)
|
||||
_ => InstructionCost::Gas(default_gas),
|
||||
};
|
||||
|
||||
match cost {
|
||||
InstructionCost::Gas(gas) => {
|
||||
Ok((gas, self.current_mem_gas, 0))
|
||||
Ok((gas, self.current_mem_gas, 0, None))
|
||||
},
|
||||
InstructionCost::GasMem(gas, mem_size) => {
|
||||
InstructionCost::GasMem(gas, mem_size, provided) => {
|
||||
let (mem_gas_cost, new_mem_gas, new_mem_size) = try!(self.mem_gas_cost(schedule, current_mem_size, &mem_size));
|
||||
let gas = overflowing!(gas.overflow_add(mem_gas_cost));
|
||||
Ok((gas, new_mem_gas, new_mem_size))
|
||||
Ok((gas, new_mem_gas, new_mem_size, provided))
|
||||
},
|
||||
InstructionCost::GasMemCopy(gas, mem_size, copy) => {
|
||||
let (mem_gas_cost, new_mem_gas, new_mem_size) = try!(self.mem_gas_cost(schedule, current_mem_size, &mem_size));
|
||||
@@ -175,7 +244,7 @@ impl<Gas: CostType> Gasometer<Gas> {
|
||||
let copy_gas = Gas::from(schedule.copy_gas) * copy;
|
||||
let gas = overflowing!(gas.overflow_add(copy_gas));
|
||||
let gas = overflowing!(gas.overflow_add(mem_gas_cost));
|
||||
Ok((gas, new_mem_gas, new_mem_size))
|
||||
Ok((gas, new_mem_gas, new_mem_size, None))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,8 +81,6 @@ impl<'a> CodeReader<'a> {
|
||||
|
||||
enum InstructionResult<Gas> {
|
||||
Ok,
|
||||
UseAllGas,
|
||||
GasLeft(Gas),
|
||||
UnusedGas(Gas),
|
||||
JumpToPosition(U256),
|
||||
// gas left, init_orf, init_size
|
||||
@@ -104,7 +102,7 @@ impl<Cost: CostType> evm::Evm for Interpreter<Cost> {
|
||||
|
||||
let mut informant = informant::EvmInformant::new(ext.depth());
|
||||
|
||||
let code = ¶ms.code.as_ref().unwrap();
|
||||
let code = ¶ms.code.as_ref().expect("exec always called with code; qed");
|
||||
let valid_jump_destinations = self.cache.jump_destinations(¶ms.code_hash, code);
|
||||
|
||||
let mut gasometer = Gasometer::<Cost>::new(try!(Cost::from_u256(params.gas)));
|
||||
@@ -120,7 +118,7 @@ impl<Cost: CostType> evm::Evm for Interpreter<Cost> {
|
||||
try!(self.verify_instruction(ext, instruction, info, &stack));
|
||||
|
||||
// Calculate gas cost
|
||||
let (gas_cost, mem_gas, mem_size) = try!(gasometer.get_gas_cost_mem(ext, instruction, info, &stack, self.mem.size()));
|
||||
let (gas_cost, mem_gas, mem_size, provided) = try!(gasometer.get_gas_cost_mem(ext, instruction, info, &stack, self.mem.size()));
|
||||
// TODO: make compile-time removable if too much of a performance hit.
|
||||
let trace_executed = ext.trace_prepare_execute(reader.position - 1, instruction, &gas_cost.as_u256());
|
||||
|
||||
@@ -138,27 +136,21 @@ impl<Cost: CostType> evm::Evm for Interpreter<Cost> {
|
||||
|
||||
// Execute instruction
|
||||
let result = try!(self.exec_instruction(
|
||||
gasometer.current_gas, ¶ms, ext, instruction, &mut reader, &mut stack
|
||||
gasometer.current_gas, ¶ms, ext, instruction, &mut reader, &mut stack, provided
|
||||
));
|
||||
|
||||
evm_debug!({ informant.after_instruction(instruction) });
|
||||
|
||||
if let InstructionResult::UnusedGas(ref gas) = result {
|
||||
gasometer.current_gas = gasometer.current_gas + *gas;
|
||||
}
|
||||
|
||||
if trace_executed {
|
||||
ext.trace_executed(gasometer.current_gas.as_u256(), stack.peek_top(info.ret), mem_written.map(|(o, s)| (o, &(self.mem[o..(o + s)]))), store_written);
|
||||
}
|
||||
|
||||
// Advance
|
||||
match result {
|
||||
InstructionResult::Ok => {},
|
||||
InstructionResult::UnusedGas(gas) => {
|
||||
gasometer.current_gas = gasometer.current_gas + gas;
|
||||
},
|
||||
InstructionResult::UseAllGas => {
|
||||
gasometer.current_gas = Cost::from(0);
|
||||
},
|
||||
InstructionResult::GasLeft(gas_left) => {
|
||||
gasometer.current_gas = gas_left;
|
||||
},
|
||||
InstructionResult::JumpToPosition(position) => {
|
||||
let pos = try!(self.verify_jump(position, &valid_jump_destinations));
|
||||
reader.position = pos;
|
||||
@@ -168,6 +160,7 @@ impl<Cost: CostType> evm::Evm for Interpreter<Cost> {
|
||||
return Ok(GasLeft::NeedsReturn(gas.as_u256(), self.mem.read_slice(off, size)));
|
||||
},
|
||||
InstructionResult::StopExecution => break,
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
informant.done();
|
||||
@@ -250,7 +243,8 @@ impl<Cost: CostType> Interpreter<Cost> {
|
||||
ext: &mut evm::Ext,
|
||||
instruction: Instruction,
|
||||
code: &mut CodeReader,
|
||||
stack: &mut Stack<U256>
|
||||
stack: &mut Stack<U256>,
|
||||
provided: Option<Cost>
|
||||
) -> evm::Result<InstructionResult<Cost>> {
|
||||
match instruction {
|
||||
instructions::JUMP => {
|
||||
@@ -275,31 +269,32 @@ impl<Cost: CostType> Interpreter<Cost> {
|
||||
let endowment = stack.pop_back();
|
||||
let init_off = stack.pop_back();
|
||||
let init_size = stack.pop_back();
|
||||
let create_gas = provided.expect("`provided` comes through Self::exec from `Gasometer::get_gas_cost_mem`; `gas_gas_mem_cost` guarantees `Some` when instruction is `CALL`/`CALLCODE`/`DELEGATECALL`/`CREATE`; this is `CREATE`; qed");
|
||||
|
||||
let contract_code = self.mem.read_slice(init_off, init_size);
|
||||
let can_create = ext.balance(¶ms.address) >= endowment && ext.depth() < ext.schedule().max_depth;
|
||||
|
||||
if !can_create {
|
||||
stack.push(U256::zero());
|
||||
return Ok(InstructionResult::Ok);
|
||||
return Ok(InstructionResult::UnusedGas(create_gas));
|
||||
}
|
||||
|
||||
let create_result = ext.create(&gas.as_u256(), &endowment, contract_code);
|
||||
let create_result = ext.create(&create_gas.as_u256(), &endowment, contract_code);
|
||||
return match create_result {
|
||||
ContractCreateResult::Created(address, gas_left) => {
|
||||
stack.push(address_to_u256(address));
|
||||
Ok(InstructionResult::GasLeft(Cost::from_u256(gas_left).expect("Gas left cannot be greater.")))
|
||||
Ok(InstructionResult::UnusedGas(Cost::from_u256(gas_left).expect("Gas left cannot be greater.")))
|
||||
},
|
||||
ContractCreateResult::Failed => {
|
||||
stack.push(U256::zero());
|
||||
// TODO [todr] Should we just StopExecution here?
|
||||
Ok(InstructionResult::UseAllGas)
|
||||
Ok(InstructionResult::Ok)
|
||||
}
|
||||
};
|
||||
},
|
||||
instructions::CALL | instructions::CALLCODE | instructions::DELEGATECALL => {
|
||||
assert!(ext.schedule().call_value_transfer_gas > ext.schedule().call_stipend, "overflow possible");
|
||||
let call_gas = Cost::from_u256(stack.pop_back()).expect("Gas is already validated.");
|
||||
stack.pop_back();
|
||||
let call_gas = provided.expect("`provided` comes through Self::exec from `Gasometer::get_gas_cost_mem`; `gas_gas_mem_cost` guarantees `Some` when instruction is `CALL`/`CALLCODE`/`DELEGATECALL`/`CREATE`; this is one of `CALL`/`CALLCODE`/`DELEGATECALL`; qed");
|
||||
let code_address = stack.pop_back();
|
||||
let code_address = u256_to_address(&code_address);
|
||||
|
||||
@@ -317,17 +312,17 @@ impl<Cost: CostType> Interpreter<Cost> {
|
||||
// Add stipend (only CALL|CALLCODE when value > 0)
|
||||
let call_gas = call_gas + value.map_or_else(|| Cost::from(0), |val| match val.is_zero() {
|
||||
false => Cost::from(ext.schedule().call_stipend),
|
||||
true => Cost::from(0)
|
||||
true => Cost::from(0),
|
||||
});
|
||||
|
||||
// Get sender & receive addresses, check if we have balance
|
||||
let (sender_address, receive_address, has_balance, call_type) = match instruction {
|
||||
instructions::CALL => {
|
||||
let has_balance = ext.balance(¶ms.address) >= value.unwrap();
|
||||
let has_balance = ext.balance(¶ms.address) >= value.expect("value set for all but delegate call; qed");
|
||||
(¶ms.address, &code_address, has_balance, CallType::Call)
|
||||
},
|
||||
instructions::CALLCODE => {
|
||||
let has_balance = ext.balance(¶ms.address) >= value.unwrap();
|
||||
let has_balance = ext.balance(¶ms.address) >= value.expect("value set for all but delegate call; qed");
|
||||
(¶ms.address, ¶ms.address, has_balance, CallType::CallCode)
|
||||
},
|
||||
instructions::DELEGATECALL => (¶ms.sender, ¶ms.address, true, CallType::DelegateCall),
|
||||
|
||||
@@ -21,25 +21,66 @@ use util::sha3::*;
|
||||
use bit_set::BitSet;
|
||||
use super::super::instructions;
|
||||
|
||||
const CACHE_CODE_ITEMS: usize = 65536;
|
||||
const INITIAL_CAPACITY: usize = 32;
|
||||
const DEFAULT_CACHE_SIZE: usize = 4 * 1024 * 1024;
|
||||
|
||||
/// GLobal cache for EVM interpreter
|
||||
/// Global cache for EVM interpreter
|
||||
pub struct SharedCache {
|
||||
jump_destinations: Mutex<LruCache<H256, Arc<BitSet>>>
|
||||
jump_destinations: Mutex<LruCache<H256, Arc<BitSet>>>,
|
||||
max_size: usize,
|
||||
cur_size: Mutex<usize>,
|
||||
}
|
||||
|
||||
impl SharedCache {
|
||||
/// Get jump destincations bitmap for a contract.
|
||||
/// Create a jump destinations cache with a maximum size in bytes
|
||||
/// to cache.
|
||||
pub fn new(max_size: usize) -> Self {
|
||||
SharedCache {
|
||||
jump_destinations: Mutex::new(LruCache::new(INITIAL_CAPACITY)),
|
||||
max_size: max_size * 8, // dealing with bits here.
|
||||
cur_size: Mutex::new(0),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get jump destinations bitmap for a contract.
|
||||
pub fn jump_destinations(&self, code_hash: &H256, code: &[u8]) -> Arc<BitSet> {
|
||||
if code_hash == &SHA3_EMPTY {
|
||||
return Self::find_jump_destinations(code);
|
||||
}
|
||||
|
||||
if let Some(d) = self.jump_destinations.lock().get_mut(code_hash) {
|
||||
return d.clone();
|
||||
}
|
||||
|
||||
let d = Self::find_jump_destinations(code);
|
||||
self.jump_destinations.lock().insert(code_hash.clone(), d.clone());
|
||||
|
||||
{
|
||||
let mut cur_size = self.cur_size.lock();
|
||||
*cur_size += d.capacity();
|
||||
|
||||
let mut jump_dests = self.jump_destinations.lock();
|
||||
let cap = jump_dests.capacity();
|
||||
|
||||
// grow the cache as necessary; it operates on amount of items
|
||||
// but we're working based on memory usage.
|
||||
if jump_dests.len() == cap && *cur_size < self.max_size {
|
||||
jump_dests.set_capacity(cap * 2);
|
||||
}
|
||||
|
||||
// account for any element displaced from the cache.
|
||||
if let Some(lru) = jump_dests.insert(code_hash.clone(), d.clone()) {
|
||||
*cur_size -= lru.capacity();
|
||||
}
|
||||
|
||||
// remove elements until we are below the memory target.
|
||||
while *cur_size > self.max_size {
|
||||
match jump_dests.remove_lru() {
|
||||
Some((_, v)) => *cur_size -= v.capacity(),
|
||||
_ => break,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
d
|
||||
}
|
||||
|
||||
@@ -57,15 +98,15 @@ impl SharedCache {
|
||||
}
|
||||
position += 1;
|
||||
}
|
||||
|
||||
jump_dests.shrink_to_fit();
|
||||
Arc::new(jump_dests)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for SharedCache {
|
||||
fn default() -> SharedCache {
|
||||
SharedCache {
|
||||
jump_destinations: Mutex::new(LruCache::new(CACHE_CODE_ITEMS)),
|
||||
}
|
||||
fn default() -> Self {
|
||||
SharedCache::new(DEFAULT_CACHE_SIZE)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -359,7 +359,7 @@ impl evm::Evm for JitEvm {
|
||||
data.timestamp = ext.env_info().timestamp as i64;
|
||||
|
||||
self.context = Some(unsafe { evmjit::ContextHandle::new(data, schedule, &mut ext_handle) });
|
||||
let mut context = self.context.as_mut().unwrap();
|
||||
let mut context = self.context.as_mut().expect("context handle set on the prior line; qed");
|
||||
let res = context.exec();
|
||||
|
||||
match res {
|
||||
|
||||
@@ -80,6 +80,19 @@ pub struct Schedule {
|
||||
pub tx_data_non_zero_gas: usize,
|
||||
/// Gas price for copying memory
|
||||
pub copy_gas: usize,
|
||||
/// Price of EXTCODESIZE
|
||||
pub extcodesize_gas: usize,
|
||||
/// Base price of EXTCODECOPY
|
||||
pub extcodecopy_base_gas: usize,
|
||||
/// Price of BALANCE
|
||||
pub balance_gas: usize,
|
||||
/// Price of SUICIDE
|
||||
pub suicide_gas: usize,
|
||||
/// Amount of additional gas to pay when SUICIDE credits a non-existant account
|
||||
pub suicide_to_new_account_cost: usize,
|
||||
/// If Some(x): let limit = GAS * (x - 1) / x; let CALL's gas = min(requested, limit). let CREATE's gas = limit.
|
||||
/// If None: let CALL's gas = (requested > GAS ? [OOG] : GAS). let CREATE's gas = GAS
|
||||
pub sub_gas_cap_divisor: Option<usize>,
|
||||
}
|
||||
|
||||
impl Schedule {
|
||||
@@ -93,6 +106,49 @@ impl Schedule {
|
||||
Self::new(true, true, 53000)
|
||||
}
|
||||
|
||||
/// Schedule for the Homestead-era of the Ethereum main net.
|
||||
pub fn new_homestead_gas_fix() -> Schedule {
|
||||
Schedule{
|
||||
exceptional_failed_code_deposit: true,
|
||||
have_delegate_call: true,
|
||||
stack_limit: 1024,
|
||||
max_depth: 1024,
|
||||
tier_step_gas: [0, 2, 3, 5, 8, 10, 20, 0],
|
||||
exp_gas: 10,
|
||||
exp_byte_gas: 10,
|
||||
sha3_gas: 30,
|
||||
sha3_word_gas: 6,
|
||||
sload_gas: 200,
|
||||
sstore_set_gas: 20000,
|
||||
sstore_reset_gas: 5000,
|
||||
sstore_refund_gas: 15000,
|
||||
jumpdest_gas: 1,
|
||||
log_gas: 375,
|
||||
log_data_gas: 8,
|
||||
log_topic_gas: 375,
|
||||
create_gas: 32000,
|
||||
call_gas: 700,
|
||||
call_stipend: 2300,
|
||||
call_value_transfer_gas: 9000,
|
||||
call_new_account_gas: 25000,
|
||||
suicide_refund_gas: 24000,
|
||||
memory_gas: 3,
|
||||
quad_coeff_div: 512,
|
||||
create_data_gas: 200,
|
||||
tx_gas: 21000,
|
||||
tx_create_gas: 53000,
|
||||
tx_data_zero_gas: 4,
|
||||
tx_data_non_zero_gas: 68,
|
||||
copy_gas: 3,
|
||||
extcodesize_gas: 700,
|
||||
extcodecopy_base_gas: 700,
|
||||
balance_gas: 400,
|
||||
suicide_gas: 5000,
|
||||
suicide_to_new_account_cost: 25000,
|
||||
sub_gas_cap_divisor: Some(64),
|
||||
}
|
||||
}
|
||||
|
||||
fn new(efcd: bool, hdc: bool, tcg: usize) -> Schedule {
|
||||
Schedule{
|
||||
exceptional_failed_code_deposit: efcd,
|
||||
@@ -126,6 +182,12 @@ impl Schedule {
|
||||
tx_data_zero_gas: 4,
|
||||
tx_data_non_zero_gas: 68,
|
||||
copy_gas: 3,
|
||||
extcodesize_gas: 20,
|
||||
extcodecopy_base_gas: 20,
|
||||
balance_gas: 20,
|
||||
suicide_gas: 0,
|
||||
suicide_to_new_account_cost: 0,
|
||||
sub_gas_cap_divisor: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -817,7 +817,7 @@ fn test_signextend(factory: super::Factory) {
|
||||
|
||||
#[test] // JIT just returns out of gas
|
||||
fn test_badinstruction_int() {
|
||||
let factory = super::Factory::new(VMType::Interpreter);
|
||||
let factory = super::Factory::new(VMType::Interpreter, 1024 * 32);
|
||||
let code = "af".from_hex().unwrap();
|
||||
|
||||
let mut params = ActionParams::default();
|
||||
|
||||
@@ -427,8 +427,16 @@ impl<'a> Executive<'a> {
|
||||
trace!("exec::finalize: t.gas={}, sstore_refunds={}, suicide_refunds={}, refunds_bound={}, gas_left_prerefund={}, refunded={}, gas_left={}, gas_used={}, refund_value={}, fees_value={}\n",
|
||||
t.gas, sstore_refunds, suicide_refunds, refunds_bound, gas_left_prerefund, refunded, gas_left, gas_used, refund_value, fees_value);
|
||||
|
||||
trace!("exec::finalize: Refunding refund_value={}, sender={}\n", refund_value, t.sender().unwrap());
|
||||
self.state.add_balance(&t.sender().unwrap(), &refund_value);
|
||||
let sender = match t.sender() {
|
||||
Ok(sender) => sender,
|
||||
Err(e) => {
|
||||
debug!(target: "executive", "attempted to finalize transaction without sender: {}", e);
|
||||
return Err(ExecutionError::Internal);
|
||||
}
|
||||
};
|
||||
|
||||
trace!("exec::finalize: Refunding refund_value={}, sender={}\n", refund_value, sender);
|
||||
self.state.add_balance(&sender, &refund_value);
|
||||
trace!("exec::finalize: Compensating author: fees_value={}, author={}\n", fees_value, &self.info.author);
|
||||
self.state.add_balance(&self.info.author, &fees_value);
|
||||
|
||||
@@ -598,7 +606,7 @@ mod tests {
|
||||
#[test]
|
||||
// Tracing is not suported in JIT
|
||||
fn test_call_to_create() {
|
||||
let factory = Factory::new(VMType::Interpreter);
|
||||
let factory = Factory::new(VMType::Interpreter, 1024 * 32);
|
||||
|
||||
// code:
|
||||
//
|
||||
@@ -697,7 +705,7 @@ mod tests {
|
||||
VMOperation { pc: 33, instruction: 96, gas_cost: 3.into(), executed: Some(VMExecutedOperation { gas_used: 99985.into(), stack_push: vec_into![29], mem_diff: None, store_diff: None }) },
|
||||
VMOperation { pc: 35, instruction: 96, gas_cost: 3.into(), executed: Some(VMExecutedOperation { gas_used: 99982.into(), stack_push: vec_into![3], mem_diff: None, store_diff: None }) },
|
||||
VMOperation { pc: 37, instruction: 96, gas_cost: 3.into(), executed: Some(VMExecutedOperation { gas_used: 99979.into(), stack_push: vec_into![23], mem_diff: None, store_diff: None }) },
|
||||
VMOperation { pc: 39, instruction: 240, gas_cost: 32000.into(), executed: Some(VMExecutedOperation { gas_used: 67979.into(), stack_push: vec_into![U256::from_dec_str("1135198453258042933984631383966629874710669425204").unwrap()], mem_diff: None, store_diff: None }) },
|
||||
VMOperation { pc: 39, instruction: 240, gas_cost: 99979.into(), executed: Some(VMExecutedOperation { gas_used: 64755.into(), stack_push: vec_into![U256::from_dec_str("1135198453258042933984631383966629874710669425204").unwrap()], mem_diff: None, store_diff: None }) },
|
||||
VMOperation { pc: 40, instruction: 96, gas_cost: 3.into(), executed: Some(VMExecutedOperation { gas_used: 64752.into(), stack_push: vec_into![0], mem_diff: None, store_diff: None }) },
|
||||
VMOperation { pc: 42, instruction: 85, gas_cost: 20000.into(), executed: Some(VMExecutedOperation { gas_used: 44752.into(), stack_push: vec_into![], mem_diff: None, store_diff: Some(StorageDiff { location: 0.into(), value: U256::from_dec_str("1135198453258042933984631383966629874710669425204").unwrap() }) }) }
|
||||
],
|
||||
@@ -724,7 +732,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_create_contract() {
|
||||
// Tracing is not supported in JIT
|
||||
let factory = Factory::new(VMType::Interpreter);
|
||||
let factory = Factory::new(VMType::Interpreter, 1024 * 32);
|
||||
// code:
|
||||
//
|
||||
// 60 10 - push 16
|
||||
|
||||
@@ -199,8 +199,9 @@ impl Header {
|
||||
match &mut *hash {
|
||||
&mut Some(ref h) => h.clone(),
|
||||
hash @ &mut None => {
|
||||
*hash = Some(self.rlp_sha3(Seal::With));
|
||||
hash.as_ref().unwrap().clone()
|
||||
let h = self.rlp_sha3(Seal::With);
|
||||
*hash = Some(h.clone());
|
||||
h
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -211,8 +212,9 @@ impl Header {
|
||||
match &mut *hash {
|
||||
&mut Some(ref h) => h.clone(),
|
||||
hash @ &mut None => {
|
||||
*hash = Some(self.rlp_sha3(Seal::Without));
|
||||
hash.as_ref().unwrap().clone()
|
||||
let h = self.rlp_sha3(Seal::Without);
|
||||
*hash = Some(h.clone());
|
||||
h
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,7 +48,8 @@ pub fn json_chain_test(json_data: &[u8], era: ChainEra) -> Vec<String> {
|
||||
let mut spec = match era {
|
||||
ChainEra::Frontier => ethereum::new_frontier_test(),
|
||||
ChainEra::Homestead => ethereum::new_homestead_test(),
|
||||
ChainEra::DaoHardfork => ethereum::new_daohardfork_test(),
|
||||
ChainEra::Eip150 => ethereum::new_eip150_test(),
|
||||
ChainEra::TransitionTest => ethereum::new_transition_test(),
|
||||
};
|
||||
spec.set_genesis_state(state);
|
||||
spec.overwrite_genesis_params(genesis);
|
||||
@@ -116,14 +117,38 @@ mod frontier_era_tests {
|
||||
declare_test!{BlockchainTests_RandomTests_bl201507071825GO, "BlockchainTests/RandomTests/bl201507071825GO"}
|
||||
}
|
||||
|
||||
mod daohardfork_tests {
|
||||
mod transition_tests {
|
||||
use tests::helpers::*;
|
||||
use super::json_chain_test;
|
||||
|
||||
fn do_json_test(json_data: &[u8]) -> Vec<String> {
|
||||
json_chain_test(json_data, ChainEra::DaoHardfork)
|
||||
json_chain_test(json_data, ChainEra::TransitionTest)
|
||||
}
|
||||
|
||||
declare_test!{BlockchainTests_TestNetwork_bcSimpleTransitionTest, "BlockchainTests/TestNetwork/bcSimpleTransitionTest"}
|
||||
declare_test!{BlockchainTests_TestNetwork_bcTheDaoTest, "BlockchainTests/TestNetwork/bcTheDaoTest"}
|
||||
declare_test!{BlockchainTests_TestNetwork_bcEIP150Test, "BlockchainTests/TestNetwork/bcEIP150Test"}
|
||||
}
|
||||
|
||||
mod eip150_blockchain_tests {
|
||||
use tests::helpers::*;
|
||||
use super::json_chain_test;
|
||||
|
||||
fn do_json_test(json_data: &[u8]) -> Vec<String> {
|
||||
json_chain_test(json_data, ChainEra::Eip150)
|
||||
}
|
||||
|
||||
declare_test!{BlockchainTests_EIP150_bcBlockGasLimitTest, "BlockchainTests/EIP150/bcBlockGasLimitTest"}
|
||||
declare_test!{BlockchainTests_EIP150_bcForkStressTest, "BlockchainTests/EIP150/bcForkStressTest"}
|
||||
declare_test!{BlockchainTests_EIP150_bcGasPricerTest, "BlockchainTests/EIP150/bcGasPricerTest"}
|
||||
declare_test!{BlockchainTests_EIP150_bcInvalidHeaderTest, "BlockchainTests/EIP150/bcInvalidHeaderTest"}
|
||||
declare_test!{BlockchainTests_EIP150_bcInvalidRLPTest, "BlockchainTests/EIP150/bcInvalidRLPTest"}
|
||||
declare_test!{BlockchainTests_EIP150_bcMultiChainTest, "BlockchainTests/EIP150/bcMultiChainTest"}
|
||||
declare_test!{BlockchainTests_EIP150_bcRPC_API_Test, "BlockchainTests/EIP150/bcRPC_API_Test"}
|
||||
declare_test!{BlockchainTests_EIP150_bcStateTest, "BlockchainTests/EIP150/bcStateTest"}
|
||||
declare_test!{BlockchainTests_EIP150_bcTotalDifficultyTest, "BlockchainTests/EIP150/bcTotalDifficultyTest"}
|
||||
declare_test!{BlockchainTests_EIP150_bcUncleHeaderValiditiy, "BlockchainTests/EIP150/bcUncleHeaderValiditiy"}
|
||||
declare_test!{BlockchainTests_EIP150_bcUncleTest, "BlockchainTests/EIP150/bcUncleTest"}
|
||||
declare_test!{BlockchainTests_EIP150_bcValidBlockTest, "BlockchainTests/EIP150/bcValidBlockTest"}
|
||||
declare_test!{BlockchainTests_EIP150_bcWalletTest, "BlockchainTests/EIP150/bcWalletTest"}
|
||||
}
|
||||
|
||||
43
ethcore/src/json_tests/eip150_state.rs
Normal file
43
ethcore/src/json_tests/eip150_state.rs
Normal file
@@ -0,0 +1,43 @@
|
||||
// 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 super::test_common::*;
|
||||
use tests::helpers::*;
|
||||
use super::state::json_chain_test;
|
||||
|
||||
fn do_json_test(json_data: &[u8]) -> Vec<String> {
|
||||
json_chain_test(json_data, ChainEra::Eip150)
|
||||
}
|
||||
|
||||
declare_test!{StateTests_EIP150_stEIPSpecificTest, "StateTests/EIP150/stEIPSpecificTest"}
|
||||
declare_test!{StateTests_EIP150_stEIPsingleCodeGasPrices, "StateTests/EIP150/stEIPsingleCodeGasPrices"}
|
||||
declare_test!{StateTests_EIP150_stMemExpandingEIPCalls, "StateTests/EIP150/stMemExpandingEIPCalls"}
|
||||
|
||||
declare_test!{StateTests_EIP150_stCallCodes, "StateTests/EIP150/Homestead/stCallCodes"}
|
||||
declare_test!{StateTests_EIP150_stCallCreateCallCodeTest, "StateTests/EIP150/Homestead/stCallCreateCallCodeTest"}
|
||||
declare_test!{StateTests_EIP150_stDelegatecallTest, "StateTests/EIP150/Homestead/stDelegatecallTest"}
|
||||
declare_test!{StateTests_EIP150_stInitCodeTest, "StateTests/EIP150/Homestead/stInitCodeTest"}
|
||||
declare_test!{StateTests_EIP150_stLogTests, "StateTests/EIP150/Homestead/stLogTests"}
|
||||
declare_test!{heavy => StateTests_EIP150_stMemoryStressTest, "StateTests/EIP150/Homestead/stMemoryStressTest"}
|
||||
declare_test!{heavy => StateTests_EIP150_stMemoryTest, "StateTests/EIP150/Homestead/stMemoryTest"}
|
||||
declare_test!{StateTests_EIP150_stPreCompiledContracts, "StateTests/EIP150/Homestead/stPreCompiledContracts"}
|
||||
declare_test!{heavy => StateTests_EIP150_stQuadraticComplexityTest, "StateTests/EIP150/Homestead/stQuadraticComplexityTest"}
|
||||
declare_test!{StateTests_EIP150_stRecursiveCreate, "StateTests/EIP150/Homestead/stRecursiveCreate"}
|
||||
declare_test!{StateTests_EIP150_stRefundTest, "StateTests/EIP150/Homestead/stRefundTest"}
|
||||
declare_test!{StateTests_EIP150_stSpecialTest, "StateTests/EIP150/Homestead/stSpecialTest"}
|
||||
declare_test!{StateTests_EIP150_stSystemOperationsTest, "StateTests/EIP150/Homestead/stSystemOperationsTest"}
|
||||
declare_test!{StateTests_EIP150_stTransactionTest, "StateTests/EIP150/Homestead/stTransactionTest"}
|
||||
declare_test!{StateTests_EIP150_stWalletTest, "StateTests/EIP150/Homestead/stWalletTest"}
|
||||
@@ -191,7 +191,7 @@ fn do_json_test_for(vm_type: &VMType, json_data: &[u8]) -> Vec<String> {
|
||||
state.populate_from(From::from(vm.pre_state.clone()));
|
||||
let info = From::from(vm.env);
|
||||
let engine = TestEngine::new(1);
|
||||
let vm_factory = Factory::new(vm_type.clone());
|
||||
let vm_factory = Factory::new(vm_type.clone(), 1024 * 32);
|
||||
let params = ActionParams::from(vm.transaction);
|
||||
|
||||
let mut substate = Substate::new();
|
||||
|
||||
@@ -37,6 +37,6 @@ declare_test!{BlockchainTests_Homestead_bcUncleTest, "BlockchainTests/Homestead/
|
||||
declare_test!{BlockchainTests_Homestead_bcValidBlockTest, "BlockchainTests/Homestead/bcValidBlockTest"}
|
||||
declare_test!{BlockchainTests_Homestead_bcWalletTest, "BlockchainTests/Homestead/bcWalletTest"}
|
||||
declare_test!{BlockchainTests_Homestead_bcShanghaiLove, "BlockchainTests/Homestead/bcShanghaiLove"}
|
||||
// Uncomment once the test is correct.
|
||||
// TODO [ToDr] uncomment as soon as eip150 tests are merged to develop branch of ethereum/tests
|
||||
// declare_test!{BlockchainTests_Homestead_bcSuicideIssue, "BlockchainTests/Homestead/bcSuicideIssue"}
|
||||
declare_test!{BlockchainTests_Homestead_bcExploitTest, "BlockchainTests/Homestead/bcExploitTest"}
|
||||
|
||||
@@ -23,4 +23,5 @@ mod state;
|
||||
mod chain;
|
||||
mod homestead_state;
|
||||
mod homestead_chain;
|
||||
mod eip150_state;
|
||||
mod trie;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -30,7 +30,7 @@ use transaction::{Action, SignedTransaction};
|
||||
use receipt::{Receipt, RichReceipt};
|
||||
use spec::Spec;
|
||||
use engines::Engine;
|
||||
use miner::{MinerService, MinerStatus, TransactionQueue, AccountDetails, TransactionOrigin};
|
||||
use miner::{MinerService, MinerStatus, TransactionQueue, PrioritizationStrategy, AccountDetails, TransactionOrigin};
|
||||
use miner::work_notify::WorkPoster;
|
||||
use client::TransactionImportResult;
|
||||
use miner::price_info::PriceInfo;
|
||||
@@ -76,6 +76,8 @@ pub struct MinerOptions {
|
||||
pub tx_gas_limit: U256,
|
||||
/// Maximum size of the transaction queue.
|
||||
pub tx_queue_size: usize,
|
||||
/// Strategy to use for prioritizing transactions in the queue.
|
||||
pub tx_queue_strategy: PrioritizationStrategy,
|
||||
/// Whether we should fallback to providing all the queue's transactions or just pending.
|
||||
pub pending_set: PendingSet,
|
||||
/// How many historical work packages can we store before running out?
|
||||
@@ -94,12 +96,13 @@ impl Default for MinerOptions {
|
||||
reseal_on_external_tx: false,
|
||||
reseal_on_own_tx: true,
|
||||
tx_gas_limit: !U256::zero(),
|
||||
tx_queue_size: 2048,
|
||||
tx_queue_size: 1024,
|
||||
tx_queue_gas_limit: GasLimit::Auto,
|
||||
tx_queue_strategy: PrioritizationStrategy::GasFactorAndGasPrice,
|
||||
pending_set: PendingSet::AlwaysQueue,
|
||||
reseal_min_period: Duration::from_secs(2),
|
||||
work_queue_size: 20,
|
||||
enable_resubmission: true,
|
||||
tx_queue_gas_limit: GasLimit::Auto,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -133,7 +136,7 @@ impl GasPriceCalibrator {
|
||||
let gas_per_tx: f32 = 21000.0;
|
||||
let wei_per_gas: f32 = wei_per_usd * usd_per_tx / gas_per_tx;
|
||||
info!(target: "miner", "Updated conversion rate to Ξ1 = {} ({} wei/gas)", Colour::White.bold().paint(format!("US${}", usd_per_eth)), Colour::Yellow.bold().paint(format!("{}", wei_per_gas)));
|
||||
set_price(U256::from_dec_str(&format!("{:.0}", wei_per_gas)).unwrap());
|
||||
set_price(U256::from(wei_per_gas as u64));
|
||||
}) {
|
||||
self.next_calibration = Instant::now() + self.options.recalibration_period;
|
||||
} else {
|
||||
@@ -212,7 +215,9 @@ impl Miner {
|
||||
GasLimit::Fixed(ref limit) => *limit,
|
||||
_ => !U256::zero(),
|
||||
};
|
||||
let txq = Arc::new(Mutex::new(TransactionQueue::with_limits(options.tx_queue_size, gas_limit, options.tx_gas_limit)));
|
||||
let txq = Arc::new(Mutex::new(TransactionQueue::with_limits(
|
||||
options.tx_queue_strategy, options.tx_queue_size, gas_limit, options.tx_gas_limit
|
||||
)));
|
||||
Miner {
|
||||
transaction_queue: txq,
|
||||
next_allowed_reseal: Mutex::new(Instant::now()),
|
||||
@@ -282,6 +287,7 @@ impl Miner {
|
||||
trace!(target: "miner", "prepare_block: done recalibration.");
|
||||
}
|
||||
|
||||
let _timer = PerfTimer::new("prepare_block");
|
||||
let (transactions, mut open_block, original_work_hash) = {
|
||||
let transactions = {self.transaction_queue.lock().top_transactions()};
|
||||
let mut sealing_work = self.sealing_work.lock();
|
||||
@@ -468,8 +474,8 @@ impl Miner {
|
||||
let mut queue = self.transaction_queue.lock();
|
||||
queue.set_gas_limit(gas_limit);
|
||||
if let GasLimit::Auto = self.options.tx_queue_gas_limit {
|
||||
// Set total tx queue gas limit to be 2x the block gas limit.
|
||||
queue.set_total_gas_limit(gas_limit << 1);
|
||||
// Set total tx queue gas limit to be 20x the block gas limit.
|
||||
queue.set_total_gas_limit(gas_limit * 20.into());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -747,7 +753,7 @@ impl MinerService for Miner {
|
||||
let mut transaction_queue = self.transaction_queue.lock();
|
||||
let import = self.add_transactions_to_queue(
|
||||
chain, vec![transaction], TransactionOrigin::Local, &mut transaction_queue
|
||||
).pop().unwrap();
|
||||
).pop().expect("one result returned per added transaction; one added => one result; qed");
|
||||
|
||||
match import {
|
||||
Ok(ref res) => {
|
||||
@@ -869,7 +875,11 @@ impl MinerService for Miner {
|
||||
gas_used: receipt.gas_used - prev_gas,
|
||||
contract_address: match tx.action {
|
||||
Action::Call(_) => None,
|
||||
Action::Create => Some(contract_address(&tx.sender().unwrap(), &tx.nonce)),
|
||||
Action::Create => {
|
||||
let sender = tx.sender()
|
||||
.expect("transactions in pending block have already been checked for valid sender; qed");
|
||||
Some(contract_address(&sender, &tx.nonce))
|
||||
}
|
||||
},
|
||||
logs: receipt.logs.clone(),
|
||||
}
|
||||
@@ -1035,7 +1045,7 @@ impl MinerService for Miner {
|
||||
mod tests {
|
||||
|
||||
use std::time::Duration;
|
||||
use super::super::MinerService;
|
||||
use super::super::{MinerService, PrioritizationStrategy};
|
||||
use super::*;
|
||||
use util::*;
|
||||
use ethkey::{Generator, Random};
|
||||
@@ -1089,6 +1099,7 @@ mod tests {
|
||||
tx_gas_limit: !U256::zero(),
|
||||
tx_queue_size: 1024,
|
||||
tx_queue_gas_limit: GasLimit::None,
|
||||
tx_queue_strategy: PrioritizationStrategy::GasFactorAndGasPrice,
|
||||
pending_set: PendingSet::AlwaysSealing,
|
||||
work_queue_size: 5,
|
||||
enable_resubmission: true,
|
||||
|
||||
@@ -47,7 +47,7 @@ mod transaction_queue;
|
||||
mod work_notify;
|
||||
mod price_info;
|
||||
|
||||
pub use self::transaction_queue::{TransactionQueue, AccountDetails, TransactionOrigin};
|
||||
pub use self::transaction_queue::{TransactionQueue, PrioritizationStrategy, AccountDetails, TransactionOrigin};
|
||||
pub use self::miner::{Miner, MinerOptions, PendingSet, GasPricer, GasPriceCalibratorOptions, GasLimit};
|
||||
pub use self::external::{ExternalMiner, ExternalMinerService};
|
||||
pub use client::TransactionImportResult;
|
||||
|
||||
@@ -52,7 +52,8 @@ impl<F: Fn(PriceInfo) + Sync + Send + 'static> Handler<HttpStream> for SetPriceH
|
||||
.and_then(|json| json.find_path(&["result", "ethusd"])
|
||||
.and_then(|obj| match *obj {
|
||||
Json::String(ref s) => Some((self.set_price)(PriceInfo {
|
||||
ethusd: FromStr::from_str(s).unwrap()
|
||||
ethusd: FromStr::from_str(s)
|
||||
.expect("Etherscan API will always return properly formatted price; qed")
|
||||
})),
|
||||
_ => None,
|
||||
}));
|
||||
@@ -67,10 +68,14 @@ impl PriceInfo {
|
||||
let client = try!(Client::new().map_err(|_| ()));
|
||||
thread::spawn(move || {
|
||||
let (tx, rx) = mpsc::channel();
|
||||
let _ = client.request(FromStr::from_str("http://api.etherscan.io/api?module=stats&action=ethprice").unwrap(), SetPriceHandler {
|
||||
set_price: set_price,
|
||||
channel: tx,
|
||||
}).ok().and_then(|_| rx.recv().ok());
|
||||
let url = FromStr::from_str("http://api.etherscan.io/api?module=stats&action=ethprice")
|
||||
.expect("string known to be a valid URL; qed");
|
||||
let _ = client.request(
|
||||
url,
|
||||
SetPriceHandler {
|
||||
set_price: set_price,
|
||||
channel: tx,
|
||||
}).ok().and_then(|_| rx.recv().ok());
|
||||
client.close();
|
||||
});
|
||||
Ok(())
|
||||
|
||||
@@ -49,7 +49,7 @@
|
||||
//! balance: U256::from(1_000_000),
|
||||
//! };
|
||||
//!
|
||||
//! let mut txq = TransactionQueue::new();
|
||||
//! let mut txq = TransactionQueue::default();
|
||||
//! txq.add(st2.clone(), &default_account_details, TransactionOrigin::External).unwrap();
|
||||
//! txq.add(st1.clone(), &default_account_details, TransactionOrigin::External).unwrap();
|
||||
//!
|
||||
@@ -130,11 +130,20 @@ struct TransactionOrder {
|
||||
/// (e.g. Tx(nonce:5), State(nonce:0) -> height: 5)
|
||||
/// High nonce_height = Low priority (processed later)
|
||||
nonce_height: U256,
|
||||
/// Gas specified in the transaction.
|
||||
gas: U256,
|
||||
/// Gas Price of the transaction.
|
||||
/// Low gas price = Low priority (processed later)
|
||||
gas_price: U256,
|
||||
/// Gas usage priority factor. Usage depends on strategy.
|
||||
/// Represents the linear increment in required gas price for heavy transactions.
|
||||
///
|
||||
/// High gas limit + Low gas price = Low priority
|
||||
/// High gas limit + High gas price = High priority
|
||||
gas_factor: U256,
|
||||
/// Gas (limit) of the transaction. Usage depends on strategy.
|
||||
/// Low gas limit = High priority (processed earlier)
|
||||
gas: U256,
|
||||
/// Transaction ordering strategy
|
||||
strategy: PrioritizationStrategy,
|
||||
/// Hash to identify associated transaction
|
||||
hash: H256,
|
||||
/// Origin of the transaction
|
||||
@@ -145,11 +154,15 @@ struct TransactionOrder {
|
||||
|
||||
|
||||
impl TransactionOrder {
|
||||
fn for_transaction(tx: &VerifiedTransaction, base_nonce: U256) -> Self {
|
||||
|
||||
fn for_transaction(tx: &VerifiedTransaction, base_nonce: U256, min_gas_price: U256, strategy: PrioritizationStrategy) -> Self {
|
||||
let factor = (tx.transaction.gas >> 15) * min_gas_price;
|
||||
TransactionOrder {
|
||||
nonce_height: tx.nonce() - base_nonce,
|
||||
gas: tx.transaction.gas.clone(),
|
||||
gas_price: tx.transaction.gas_price,
|
||||
gas: tx.transaction.gas,
|
||||
gas_factor: factor,
|
||||
strategy: strategy,
|
||||
hash: tx.hash(),
|
||||
origin: tx.origin,
|
||||
penalties: 0,
|
||||
@@ -197,11 +210,28 @@ impl Ord for TransactionOrder {
|
||||
return self.origin.cmp(&b.origin);
|
||||
}
|
||||
|
||||
match self.strategy {
|
||||
PrioritizationStrategy::GasAndGasPrice => {
|
||||
if self.gas != b.gas {
|
||||
return self.gas.cmp(&b.gas);
|
||||
}
|
||||
},
|
||||
PrioritizationStrategy::GasFactorAndGasPrice => {
|
||||
// avoiding overflows
|
||||
// (gp1 - g1) > (gp2 - g2) <=>
|
||||
// (gp1 + g2) > (gp2 + g1)
|
||||
let f_a = self.gas_price + b.gas_factor;
|
||||
let f_b = b.gas_price + self.gas_factor;
|
||||
if f_a != f_b {
|
||||
return f_b.cmp(&f_a);
|
||||
}
|
||||
},
|
||||
PrioritizationStrategy::GasPriceOnly => {},
|
||||
}
|
||||
|
||||
// Then compare gas_prices
|
||||
let a_gas = self.gas_price;
|
||||
let b_gas = b.gas_price;
|
||||
if a_gas != b_gas {
|
||||
return b_gas.cmp(&a_gas);
|
||||
if self.gas_price != b.gas_price {
|
||||
return b.gas_price.cmp(&self.gas_price);
|
||||
}
|
||||
|
||||
// Compare hashes
|
||||
@@ -326,14 +356,14 @@ impl TransactionSet {
|
||||
let to_drop : Vec<(Address, U256)> = {
|
||||
self.by_priority
|
||||
.iter()
|
||||
.skip_while(|order| {
|
||||
.filter(|order| {
|
||||
count = count + 1;
|
||||
let r = gas.overflowing_add(order.gas);
|
||||
if r.1 { return false }
|
||||
gas = r.0;
|
||||
// Own and retracted transactions are allowed to go above the gas limit, bot not above the count limit.
|
||||
(gas <= self.gas_limit || order.origin == TransactionOrigin::Local || order.origin == TransactionOrigin::RetractedBlock) &&
|
||||
count <= self.limit
|
||||
// Own and retracted transactions are allowed to go above all limits.
|
||||
order.origin != TransactionOrigin::Local && order.origin != TransactionOrigin::RetractedBlock &&
|
||||
(gas > self.gas_limit || count > self.limit)
|
||||
})
|
||||
.map(|order| by_hash.get(&order.hash)
|
||||
.expect("All transactions in `self.by_priority` and `self.by_address` are kept in sync with `by_hash`."))
|
||||
@@ -345,6 +375,7 @@ impl TransactionSet {
|
||||
.fold(HashMap::new(), |mut removed, (sender, nonce)| {
|
||||
let order = self.drop(&sender, &nonce)
|
||||
.expect("Transaction has just been found in `by_priority`; so it is in `by_address` also.");
|
||||
trace!(target: "txqueue", "Dropped out of limit transaction: {:?}", order.hash);
|
||||
|
||||
by_hash.remove(&order.hash)
|
||||
.expect("hash is in `by_priorty`; all hashes in `by_priority` must be in `by_hash`; qed");
|
||||
@@ -414,8 +445,32 @@ pub struct AccountDetails {
|
||||
/// Transactions with `gas > (gas_limit + gas_limit * Factor(in percents))` are not imported to the queue.
|
||||
const GAS_LIMIT_HYSTERESIS: usize = 10; // (100/GAS_LIMIT_HYSTERESIS) %
|
||||
|
||||
/// Describes the strategy used to prioritize transactions in the queue.
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
pub enum PrioritizationStrategy {
|
||||
/// Use only gas price. Disregards the actual computation cost of the transaction.
|
||||
/// i.e. Higher gas price = Higher priority
|
||||
GasPriceOnly,
|
||||
/// Use gas limit and then gas price.
|
||||
/// i.e. Higher gas limit = Lower priority
|
||||
GasAndGasPrice,
|
||||
/// Calculate and use priority based on gas and gas price.
|
||||
/// PRIORITY = GAS_PRICE - GAS/2^15 * MIN_GAS_PRICE
|
||||
///
|
||||
/// Rationale:
|
||||
/// Heavy transactions are paying linear cost (GAS * GAS_PRICE)
|
||||
/// while the computation might be more expensive.
|
||||
///
|
||||
/// i.e.
|
||||
/// 1M gas tx with `gas_price=30*min` has the same priority
|
||||
/// as 32k gas tx with `gas_price=min`
|
||||
GasFactorAndGasPrice,
|
||||
}
|
||||
|
||||
/// `TransactionQueue` implementation
|
||||
pub struct TransactionQueue {
|
||||
/// Prioritization strategy for this queue
|
||||
strategy: PrioritizationStrategy,
|
||||
/// Gas Price threshold for transactions that can be imported to this queue (defaults to 0)
|
||||
minimal_gas_price: U256,
|
||||
/// The maximum amount of gas any individual transaction may use.
|
||||
@@ -434,18 +489,18 @@ pub struct TransactionQueue {
|
||||
|
||||
impl Default for TransactionQueue {
|
||||
fn default() -> Self {
|
||||
TransactionQueue::new()
|
||||
TransactionQueue::new(PrioritizationStrategy::GasPriceOnly)
|
||||
}
|
||||
}
|
||||
|
||||
impl TransactionQueue {
|
||||
/// Creates new instance of this Queue
|
||||
pub fn new() -> Self {
|
||||
Self::with_limits(1024, !U256::zero(), !U256::zero())
|
||||
pub fn new(strategy: PrioritizationStrategy) -> Self {
|
||||
Self::with_limits(strategy, 1024, !U256::zero(), !U256::zero())
|
||||
}
|
||||
|
||||
/// Create new instance of this Queue with specified limits
|
||||
pub fn with_limits(limit: usize, gas_limit: U256, tx_gas_limit: U256) -> Self {
|
||||
pub fn with_limits(strategy: PrioritizationStrategy, limit: usize, gas_limit: U256, tx_gas_limit: U256) -> Self {
|
||||
let current = TransactionSet {
|
||||
by_priority: BTreeSet::new(),
|
||||
by_address: Table::new(),
|
||||
@@ -463,6 +518,7 @@ impl TransactionQueue {
|
||||
};
|
||||
|
||||
TransactionQueue {
|
||||
strategy: strategy,
|
||||
minimal_gas_price: U256::zero(),
|
||||
tx_gas_limit: tx_gas_limit,
|
||||
gas_limit: !U256::zero(),
|
||||
@@ -644,7 +700,7 @@ impl TransactionQueue {
|
||||
None => vec![],
|
||||
};
|
||||
for k in nonces_from_sender {
|
||||
let order = self.current.drop(&sender, &k).unwrap();
|
||||
let order = self.current.drop(&sender, &k).expect("transaction known to be in self.current; qed");
|
||||
self.current.insert(sender, k, order.penalize());
|
||||
}
|
||||
// Same thing for future
|
||||
@@ -653,7 +709,7 @@ impl TransactionQueue {
|
||||
None => vec![],
|
||||
};
|
||||
for k in nonces_from_sender {
|
||||
let order = self.future.drop(&sender, &k).unwrap();
|
||||
let order = self.future.drop(&sender, &k).expect("transaction known to be in self.future; qed");
|
||||
self.future.insert(sender, k, order.penalize());
|
||||
}
|
||||
}
|
||||
@@ -678,6 +734,8 @@ impl TransactionQueue {
|
||||
let nonce = transaction.nonce();
|
||||
let current_nonce = fetch_account(&sender).nonce;
|
||||
|
||||
trace!(target: "txqueue", "Removing invalid transaction: {:?}", transaction.hash());
|
||||
|
||||
// Remove from future
|
||||
let order = self.future.drop(&sender, &nonce);
|
||||
if order.is_some() {
|
||||
@@ -841,6 +899,7 @@ impl TransactionQueue {
|
||||
return Err(TransactionError::AlreadyImported);
|
||||
}
|
||||
|
||||
let min_gas_price = (self.minimal_gas_price, self.strategy);
|
||||
let address = tx.sender();
|
||||
let nonce = tx.nonce();
|
||||
let hash = tx.hash();
|
||||
@@ -878,7 +937,7 @@ impl TransactionQueue {
|
||||
if nonce > next_nonce {
|
||||
// We have a gap - put to future.
|
||||
// Insert transaction (or replace old one with lower gas price)
|
||||
try!(check_too_cheap(Self::replace_transaction(tx, state_nonce, &mut self.future, &mut self.by_hash)));
|
||||
try!(check_too_cheap(Self::replace_transaction(tx, state_nonce, min_gas_price, &mut self.future, &mut self.by_hash)));
|
||||
// Enforce limit in Future
|
||||
let removed = self.future.enforce_limit(&mut self.by_hash);
|
||||
// Return an error if this transaction was not imported because of limit.
|
||||
@@ -894,7 +953,7 @@ impl TransactionQueue {
|
||||
self.move_matching_future_to_current(address, nonce + U256::one(), state_nonce);
|
||||
|
||||
// Replace transaction if any
|
||||
try!(check_too_cheap(Self::replace_transaction(tx, state_nonce, &mut self.current, &mut self.by_hash)));
|
||||
try!(check_too_cheap(Self::replace_transaction(tx, state_nonce, min_gas_price, &mut self.current, &mut self.by_hash)));
|
||||
// Keep track of highest nonce stored in current
|
||||
let new_max = self.last_nonces.get(&address).map_or(nonce, |n| cmp::max(nonce, *n));
|
||||
self.last_nonces.insert(address, new_max);
|
||||
@@ -931,8 +990,8 @@ impl TransactionQueue {
|
||||
///
|
||||
/// Returns `true` if transaction actually got to the queue (`false` if there was already a transaction with higher
|
||||
/// gas_price)
|
||||
fn replace_transaction(tx: VerifiedTransaction, base_nonce: U256, set: &mut TransactionSet, by_hash: &mut HashMap<H256, VerifiedTransaction>) -> bool {
|
||||
let order = TransactionOrder::for_transaction(&tx, base_nonce);
|
||||
fn replace_transaction(tx: VerifiedTransaction, base_nonce: U256, min_gas_price: (U256, PrioritizationStrategy), set: &mut TransactionSet, by_hash: &mut HashMap<H256, VerifiedTransaction>) -> bool {
|
||||
let order = TransactionOrder::for_transaction(&tx, base_nonce, min_gas_price.0, min_gas_price.1);
|
||||
let hash = tx.hash();
|
||||
let address = tx.sender();
|
||||
let nonce = tx.nonce();
|
||||
@@ -953,12 +1012,14 @@ impl TransactionQueue {
|
||||
let old_fee = old.gas_price;
|
||||
let new_fee = order.gas_price;
|
||||
if old_fee.cmp(&new_fee) == Ordering::Greater {
|
||||
trace!(target: "txqueue", "Didn't insert transaction because gas price was too low: {:?} ({:?} stays in the queue)", order.hash, old.hash);
|
||||
// Put back old transaction since it has greater priority (higher gas_price)
|
||||
set.insert(address, nonce, old);
|
||||
// and remove new one
|
||||
by_hash.remove(&order.hash).expect("The hash has been just inserted and no other line is altering `by_hash`.");
|
||||
false
|
||||
} else {
|
||||
trace!(target: "txqueue", "Replaced transaction: {:?} with transaction with higher gas price: {:?}", old.hash, order.hash);
|
||||
// Make sure we remove old transaction entirely
|
||||
by_hash.remove(&old.hash).expect("The hash is coming from `future` so it has to be in `by_hash`.");
|
||||
true
|
||||
@@ -1010,12 +1071,12 @@ mod test {
|
||||
fn default_gas_val() -> U256 { 100_000.into() }
|
||||
fn default_gas_price() -> U256 { 1.into() }
|
||||
|
||||
fn new_unsigned_tx(nonce: U256, gas_price: U256) -> Transaction {
|
||||
fn new_unsigned_tx(nonce: U256, gas: U256, gas_price: U256) -> Transaction {
|
||||
Transaction {
|
||||
action: Action::Create,
|
||||
value: U256::from(100),
|
||||
data: "3331600055".from_hex().unwrap(),
|
||||
gas: default_gas_val(),
|
||||
gas: gas,
|
||||
gas_price: gas_price,
|
||||
nonce: nonce
|
||||
}
|
||||
@@ -1023,7 +1084,12 @@ mod test {
|
||||
|
||||
fn new_tx(nonce: U256, gas_price: U256) -> SignedTransaction {
|
||||
let keypair = Random.generate().unwrap();
|
||||
new_unsigned_tx(nonce, gas_price).sign(keypair.secret())
|
||||
new_unsigned_tx(nonce, default_gas_val(), gas_price).sign(keypair.secret())
|
||||
}
|
||||
|
||||
fn new_tx_with_gas(gas: U256, gas_price: U256) -> SignedTransaction {
|
||||
let keypair = Random.generate().unwrap();
|
||||
new_unsigned_tx(default_nonce(), gas, gas_price).sign(keypair.secret())
|
||||
}
|
||||
|
||||
fn new_tx_default() -> SignedTransaction {
|
||||
@@ -1038,8 +1104,8 @@ mod test {
|
||||
}
|
||||
|
||||
fn new_tx_pair(nonce: U256, gas_price: U256, nonce_increment: U256, gas_price_increment: U256) -> (SignedTransaction, SignedTransaction) {
|
||||
let tx1 = new_unsigned_tx(nonce, gas_price);
|
||||
let tx2 = new_unsigned_tx(nonce + nonce_increment, gas_price + gas_price_increment);
|
||||
let tx1 = new_unsigned_tx(nonce, default_gas_val(), gas_price);
|
||||
let tx2 = new_unsigned_tx(nonce + nonce_increment, default_gas_val(), gas_price + gas_price_increment);
|
||||
|
||||
let keypair = Random.generate().unwrap();
|
||||
let secret = &keypair.secret();
|
||||
@@ -1049,8 +1115,8 @@ mod test {
|
||||
/// Returns two consecutive transactions, both with increased gas price
|
||||
fn new_tx_pair_with_gas_price_increment(gas_price_increment: U256) -> (SignedTransaction, SignedTransaction) {
|
||||
let gas = default_gas_price() + gas_price_increment;
|
||||
let tx1 = new_unsigned_tx(default_nonce(), gas);
|
||||
let tx2 = new_unsigned_tx(default_nonce() + 1.into(), gas);
|
||||
let tx1 = new_unsigned_tx(default_nonce(), default_gas_val(), gas);
|
||||
let tx2 = new_unsigned_tx(default_nonce() + 1.into(), default_gas_val(), gas);
|
||||
|
||||
let keypair = Random.generate().unwrap();
|
||||
let secret = &keypair.secret();
|
||||
@@ -1077,17 +1143,21 @@ mod test {
|
||||
assert_eq!(TransactionOrigin::External.cmp(&TransactionOrigin::RetractedBlock), Ordering::Greater);
|
||||
}
|
||||
|
||||
fn transaction_order(tx: &VerifiedTransaction, nonce: U256) -> TransactionOrder {
|
||||
TransactionOrder::for_transaction(tx, nonce, 0.into(), PrioritizationStrategy::GasPriceOnly)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_return_correct_nonces_when_dropped_because_of_limit() {
|
||||
// given
|
||||
let mut txq = TransactionQueue::with_limits(2, !U256::zero(), !U256::zero());
|
||||
let mut txq = TransactionQueue::with_limits(PrioritizationStrategy::GasPriceOnly, 2, !U256::zero(), !U256::zero());
|
||||
let (tx1, tx2) = new_tx_pair(123.into(), 1.into(), 1.into(), 0.into());
|
||||
let sender = tx1.sender().unwrap();
|
||||
let nonce = tx1.nonce;
|
||||
txq.add(tx1.clone(), &default_account_details, TransactionOrigin::External).unwrap();
|
||||
txq.add(tx2.clone(), &default_account_details, TransactionOrigin::External).unwrap();
|
||||
assert_eq!(txq.status().pending, 2);
|
||||
assert_eq!(txq.last_nonce(&sender), Some(nonce + U256::one()));
|
||||
assert_eq!(txq.last_nonce(&sender), Some(nonce + 1.into()));
|
||||
|
||||
// when
|
||||
let tx = new_tx(123.into(), 1.into());
|
||||
@@ -1133,9 +1203,9 @@ mod test {
|
||||
x
|
||||
};
|
||||
// Insert both transactions
|
||||
let order1 = TransactionOrder::for_transaction(&tx1, U256::zero());
|
||||
let order1 = transaction_order(&tx1, U256::zero());
|
||||
set.insert(tx1.sender(), tx1.nonce(), order1.clone());
|
||||
let order2 = TransactionOrder::for_transaction(&tx2, U256::zero());
|
||||
let order2 = transaction_order(&tx2, U256::zero());
|
||||
set.insert(tx2.sender(), tx2.nonce(), order2.clone());
|
||||
assert_eq!(set.by_priority.len(), 2);
|
||||
assert_eq!(set.by_address.len(), 2);
|
||||
@@ -1176,7 +1246,7 @@ mod test {
|
||||
x
|
||||
};
|
||||
// Insert both transactions
|
||||
let order1 = TransactionOrder::for_transaction(&tx1, U256::zero());
|
||||
let order1 = transaction_order(&tx1, U256::zero());
|
||||
set.insert(tx1.sender(), tx1.nonce(), order1.clone());
|
||||
assert_eq!(set.by_priority.len(), 1);
|
||||
assert_eq!(set.by_address.len(), 1);
|
||||
@@ -1184,7 +1254,7 @@ mod test {
|
||||
assert_eq!(*set.by_gas_price.iter().next().unwrap().0, 1.into());
|
||||
assert_eq!(set.by_gas_price.iter().next().unwrap().1.len(), 1);
|
||||
// Two different orders (imagine nonce changed in the meantime)
|
||||
let order2 = TransactionOrder::for_transaction(&tx2, U256::one());
|
||||
let order2 = transaction_order(&tx2, U256::one());
|
||||
set.insert(tx2.sender(), tx2.nonce(), order2.clone());
|
||||
assert_eq!(set.by_priority.len(), 1);
|
||||
assert_eq!(set.by_address.len(), 1);
|
||||
@@ -1213,10 +1283,10 @@ mod test {
|
||||
};
|
||||
let tx = new_tx_default();
|
||||
let tx1 = VerifiedTransaction::new(tx.clone(), TransactionOrigin::External).unwrap();
|
||||
let order1 = TransactionOrder::for_transaction(&tx1, U256::zero());
|
||||
let order1 = TransactionOrder::for_transaction(&tx1, 0.into(), 1.into(), PrioritizationStrategy::GasPriceOnly);
|
||||
assert!(set.insert(tx1.sender(), tx1.nonce(), order1).is_none());
|
||||
let tx2 = VerifiedTransaction::new(tx, TransactionOrigin::External).unwrap();
|
||||
let order2 = TransactionOrder::for_transaction(&tx2, U256::zero());
|
||||
let order2 = TransactionOrder::for_transaction(&tx2, 0.into(), 1.into(), PrioritizationStrategy::GasPriceOnly);
|
||||
assert!(set.insert(tx2.sender(), tx2.nonce(), order2).is_some());
|
||||
}
|
||||
|
||||
@@ -1233,7 +1303,7 @@ mod test {
|
||||
assert_eq!(set.gas_price_entry_limit(), 0.into());
|
||||
let tx = new_tx_default();
|
||||
let tx1 = VerifiedTransaction::new(tx.clone(), TransactionOrigin::External).unwrap();
|
||||
let order1 = TransactionOrder::for_transaction(&tx1, U256::zero());
|
||||
let order1 = TransactionOrder::for_transaction(&tx1, 0.into(), 1.into(), PrioritizationStrategy::GasPriceOnly);
|
||||
assert!(set.insert(tx1.sender(), tx1.nonce(), order1.clone()).is_none());
|
||||
assert_eq!(set.gas_price_entry_limit(), 2.into());
|
||||
}
|
||||
@@ -1241,7 +1311,7 @@ mod test {
|
||||
#[test]
|
||||
fn should_handle_same_transaction_imported_twice_with_different_state_nonces() {
|
||||
// given
|
||||
let mut txq = TransactionQueue::new();
|
||||
let mut txq = TransactionQueue::default();
|
||||
let (tx, tx2) = new_similar_tx_pair();
|
||||
let prev_nonce = |a: &Address| AccountDetails{ nonce: default_account_details(a).nonce - U256::one(), balance:
|
||||
!U256::zero() };
|
||||
@@ -1266,7 +1336,7 @@ mod test {
|
||||
#[test]
|
||||
fn should_move_all_transactions_from_future() {
|
||||
// given
|
||||
let mut txq = TransactionQueue::new();
|
||||
let mut txq = TransactionQueue::default();
|
||||
let (tx, tx2) = new_tx_pair_default(1.into(), 1.into());
|
||||
let prev_nonce = |a: &Address| AccountDetails{ nonce: default_account_details(a).nonce - U256::one(), balance:
|
||||
!U256::zero() };
|
||||
@@ -1292,7 +1362,7 @@ mod test {
|
||||
#[test]
|
||||
fn should_import_tx() {
|
||||
// given
|
||||
let mut txq = TransactionQueue::new();
|
||||
let mut txq = TransactionQueue::default();
|
||||
let tx = new_tx_default();
|
||||
|
||||
// when
|
||||
@@ -1304,10 +1374,77 @@ mod test {
|
||||
assert_eq!(stats.pending, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_order_by_gas() {
|
||||
// given
|
||||
let mut txq = TransactionQueue::new(PrioritizationStrategy::GasAndGasPrice);
|
||||
let tx1 = new_tx_with_gas(50000.into(), 40.into());
|
||||
let tx2 = new_tx_with_gas(40000.into(), 30.into());
|
||||
let tx3 = new_tx_with_gas(30000.into(), 10.into());
|
||||
let tx4 = new_tx_with_gas(50000.into(), 20.into());
|
||||
txq.set_minimal_gas_price(15.into());
|
||||
|
||||
// when
|
||||
let res1 = txq.add(tx1, &default_account_details, TransactionOrigin::External);
|
||||
let res2 = txq.add(tx2, &default_account_details, TransactionOrigin::External);
|
||||
let res3 = txq.add(tx3, &default_account_details, TransactionOrigin::External);
|
||||
let res4 = txq.add(tx4, &default_account_details, TransactionOrigin::External);
|
||||
|
||||
// then
|
||||
assert_eq!(res1.unwrap(), TransactionImportResult::Current);
|
||||
assert_eq!(res2.unwrap(), TransactionImportResult::Current);
|
||||
assert_eq!(unwrap_tx_err(res3), TransactionError::InsufficientGasPrice {
|
||||
minimal: U256::from(15),
|
||||
got: U256::from(10),
|
||||
});
|
||||
assert_eq!(res4.unwrap(), TransactionImportResult::Current);
|
||||
let stats = txq.status();
|
||||
assert_eq!(stats.pending, 3);
|
||||
assert_eq!(txq.top_transactions()[0].gas, 40000.into());
|
||||
assert_eq!(txq.top_transactions()[1].gas, 50000.into());
|
||||
assert_eq!(txq.top_transactions()[2].gas, 50000.into());
|
||||
assert_eq!(txq.top_transactions()[1].gas_price, 40.into());
|
||||
assert_eq!(txq.top_transactions()[2].gas_price, 20.into());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_order_by_gas_factor() {
|
||||
// given
|
||||
let mut txq = TransactionQueue::new(PrioritizationStrategy::GasFactorAndGasPrice);
|
||||
|
||||
let tx1 = new_tx_with_gas(150_000.into(), 40.into());
|
||||
let tx2 = new_tx_with_gas(40_000.into(), 16.into());
|
||||
let tx3 = new_tx_with_gas(30_000.into(), 15.into());
|
||||
let tx4 = new_tx_with_gas(150_000.into(), 62.into());
|
||||
txq.set_minimal_gas_price(15.into());
|
||||
|
||||
// when
|
||||
let res1 = txq.add(tx1, &default_account_details, TransactionOrigin::External);
|
||||
let res2 = txq.add(tx2, &default_account_details, TransactionOrigin::External);
|
||||
let res3 = txq.add(tx3, &default_account_details, TransactionOrigin::External);
|
||||
let res4 = txq.add(tx4, &default_account_details, TransactionOrigin::External);
|
||||
|
||||
// then
|
||||
assert_eq!(res1.unwrap(), TransactionImportResult::Current);
|
||||
assert_eq!(res2.unwrap(), TransactionImportResult::Current);
|
||||
assert_eq!(res3.unwrap(), TransactionImportResult::Current);
|
||||
assert_eq!(res4.unwrap(), TransactionImportResult::Current);
|
||||
let stats = txq.status();
|
||||
assert_eq!(stats.pending, 4);
|
||||
assert_eq!(txq.top_transactions()[0].gas, 30_000.into());
|
||||
assert_eq!(txq.top_transactions()[1].gas, 150_000.into());
|
||||
assert_eq!(txq.top_transactions()[2].gas, 40_000.into());
|
||||
assert_eq!(txq.top_transactions()[3].gas, 150_000.into());
|
||||
assert_eq!(txq.top_transactions()[0].gas_price, 15.into());
|
||||
assert_eq!(txq.top_transactions()[1].gas_price, 62.into());
|
||||
assert_eq!(txq.top_transactions()[2].gas_price, 16.into());
|
||||
assert_eq!(txq.top_transactions()[3].gas_price, 40.into());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gas_limit_should_never_overflow() {
|
||||
// given
|
||||
let mut txq = TransactionQueue::new();
|
||||
let mut txq = TransactionQueue::default();
|
||||
txq.set_gas_limit(U256::zero());
|
||||
assert_eq!(txq.gas_limit, U256::zero());
|
||||
|
||||
@@ -1321,7 +1458,7 @@ mod test {
|
||||
#[test]
|
||||
fn should_not_import_transaction_above_gas_limit() {
|
||||
// given
|
||||
let mut txq = TransactionQueue::new();
|
||||
let mut txq = TransactionQueue::default();
|
||||
let tx = new_tx_default();
|
||||
let gas = tx.gas;
|
||||
let limit = gas / U256::from(2);
|
||||
@@ -1344,7 +1481,7 @@ mod test {
|
||||
#[test]
|
||||
fn should_drop_transactions_from_senders_without_balance() {
|
||||
// given
|
||||
let mut txq = TransactionQueue::new();
|
||||
let mut txq = TransactionQueue::default();
|
||||
let tx = new_tx_default();
|
||||
let account = |a: &Address| AccountDetails {
|
||||
nonce: default_account_details(a).nonce,
|
||||
@@ -1367,7 +1504,7 @@ mod test {
|
||||
#[test]
|
||||
fn should_not_import_transaction_below_min_gas_price_threshold_if_external() {
|
||||
// given
|
||||
let mut txq = TransactionQueue::new();
|
||||
let mut txq = TransactionQueue::default();
|
||||
let tx = new_tx_default();
|
||||
txq.set_minimal_gas_price(tx.gas_price + U256::one());
|
||||
|
||||
@@ -1387,7 +1524,7 @@ mod test {
|
||||
#[test]
|
||||
fn should_import_transaction_below_min_gas_price_threshold_if_local() {
|
||||
// given
|
||||
let mut txq = TransactionQueue::new();
|
||||
let mut txq = TransactionQueue::default();
|
||||
let tx = new_tx_default();
|
||||
txq.set_minimal_gas_price(tx.gas_price + U256::one());
|
||||
|
||||
@@ -1406,8 +1543,8 @@ mod test {
|
||||
use rlp::{self, RlpStream, Stream};
|
||||
|
||||
// given
|
||||
let mut txq = TransactionQueue::new();
|
||||
let tx = new_unsigned_tx(123.into(), 1.into());
|
||||
let mut txq = TransactionQueue::default();
|
||||
let tx = new_unsigned_tx(123.into(), 100.into(), 1.into());
|
||||
let stx = {
|
||||
let mut s = RlpStream::new_list(9);
|
||||
s.append(&tx.nonce);
|
||||
@@ -1431,7 +1568,7 @@ mod test {
|
||||
#[test]
|
||||
fn should_import_txs_from_same_sender() {
|
||||
// given
|
||||
let mut txq = TransactionQueue::new();
|
||||
let mut txq = TransactionQueue::default();
|
||||
|
||||
let (tx, tx2) = new_tx_pair_default(1.into(), 0.into());
|
||||
|
||||
@@ -1449,7 +1586,7 @@ mod test {
|
||||
#[test]
|
||||
fn should_prioritize_local_transactions_within_same_nonce_height() {
|
||||
// given
|
||||
let mut txq = TransactionQueue::new();
|
||||
let mut txq = TransactionQueue::default();
|
||||
let tx = new_tx_default();
|
||||
// the second one has same nonce but higher `gas_price`
|
||||
let (_, tx2) = new_similar_tx_pair();
|
||||
@@ -1470,7 +1607,7 @@ mod test {
|
||||
#[test]
|
||||
fn should_prioritize_reimported_transactions_within_same_nonce_height() {
|
||||
// given
|
||||
let mut txq = TransactionQueue::new();
|
||||
let mut txq = TransactionQueue::default();
|
||||
let tx = new_tx_default();
|
||||
// the second one has same nonce but higher `gas_price`
|
||||
let (_, tx2) = new_similar_tx_pair();
|
||||
@@ -1491,7 +1628,7 @@ mod test {
|
||||
#[test]
|
||||
fn should_not_prioritize_local_transactions_with_different_nonce_height() {
|
||||
// given
|
||||
let mut txq = TransactionQueue::new();
|
||||
let mut txq = TransactionQueue::default();
|
||||
let (tx, tx2) = new_tx_pair_default(1.into(), 0.into());
|
||||
|
||||
// when
|
||||
@@ -1509,7 +1646,7 @@ mod test {
|
||||
fn should_penalize_transactions_from_sender_in_future() {
|
||||
// given
|
||||
let prev_nonce = |a: &Address| AccountDetails{ nonce: default_account_details(a).nonce - U256::one(), balance: !U256::zero() };
|
||||
let mut txq = TransactionQueue::new();
|
||||
let mut txq = TransactionQueue::default();
|
||||
// txa, txb - slightly bigger gas price to have consistent ordering
|
||||
let (txa, txb) = new_tx_pair_default(1.into(), 0.into());
|
||||
let (tx1, tx2) = new_tx_pair_with_gas_price_increment(3.into());
|
||||
@@ -1538,7 +1675,7 @@ mod test {
|
||||
#[test]
|
||||
fn should_penalize_transactions_from_sender() {
|
||||
// given
|
||||
let mut txq = TransactionQueue::new();
|
||||
let mut txq = TransactionQueue::default();
|
||||
// txa, txb - slightly bigger gas price to have consistent ordering
|
||||
let (txa, txb) = new_tx_pair_default(1.into(), 0.into());
|
||||
let (tx1, tx2) = new_tx_pair_with_gas_price_increment(3.into());
|
||||
@@ -1571,7 +1708,7 @@ mod test {
|
||||
#[test]
|
||||
fn should_return_pending_hashes() {
|
||||
// given
|
||||
let mut txq = TransactionQueue::new();
|
||||
let mut txq = TransactionQueue::default();
|
||||
|
||||
let (tx, tx2) = new_tx_pair_default(1.into(), 0.into());
|
||||
|
||||
@@ -1589,7 +1726,7 @@ mod test {
|
||||
#[test]
|
||||
fn should_put_transaction_to_futures_if_gap_detected() {
|
||||
// given
|
||||
let mut txq = TransactionQueue::new();
|
||||
let mut txq = TransactionQueue::default();
|
||||
|
||||
let (tx, tx2) = new_tx_pair_default(2.into(), 0.into());
|
||||
|
||||
@@ -1615,7 +1752,7 @@ mod test {
|
||||
!U256::zero() };
|
||||
let next2_nonce = default_nonce() + U256::from(3);
|
||||
|
||||
let mut txq = TransactionQueue::new();
|
||||
let mut txq = TransactionQueue::default();
|
||||
|
||||
let (tx, tx2) = new_tx_pair_default(1.into(), 0.into());
|
||||
txq.add(tx.clone(), &prev_nonce, TransactionOrigin::External).unwrap();
|
||||
@@ -1634,12 +1771,12 @@ mod test {
|
||||
#[test]
|
||||
fn should_move_transactions_if_gap_filled() {
|
||||
// given
|
||||
let mut txq = TransactionQueue::new();
|
||||
let mut txq = TransactionQueue::default();
|
||||
let kp = Random.generate().unwrap();
|
||||
let secret = kp.secret();
|
||||
let tx = new_unsigned_tx(123.into(), 1.into()).sign(secret);
|
||||
let tx1 = new_unsigned_tx(124.into(), 1.into()).sign(secret);
|
||||
let tx2 = new_unsigned_tx(125.into(), 1.into()).sign(secret);
|
||||
let tx = new_unsigned_tx(123.into(), default_gas_val(), 1.into()).sign(secret);
|
||||
let tx1 = new_unsigned_tx(124.into(), default_gas_val(), 1.into()).sign(secret);
|
||||
let tx2 = new_unsigned_tx(125.into(), default_gas_val(), 1.into()).sign(secret);
|
||||
|
||||
txq.add(tx, &default_account_details, TransactionOrigin::External).unwrap();
|
||||
assert_eq!(txq.status().pending, 1);
|
||||
@@ -1661,7 +1798,7 @@ mod test {
|
||||
#[test]
|
||||
fn should_remove_transaction() {
|
||||
// given
|
||||
let mut txq2 = TransactionQueue::new();
|
||||
let mut txq2 = TransactionQueue::default();
|
||||
let (tx, tx2) = new_tx_pair_default(3.into(), 0.into());
|
||||
txq2.add(tx.clone(), &default_account_details, TransactionOrigin::External).unwrap();
|
||||
txq2.add(tx2.clone(), &default_account_details, TransactionOrigin::External).unwrap();
|
||||
@@ -1682,7 +1819,7 @@ mod test {
|
||||
#[test]
|
||||
fn should_move_transactions_to_future_if_gap_introduced() {
|
||||
// given
|
||||
let mut txq = TransactionQueue::new();
|
||||
let mut txq = TransactionQueue::default();
|
||||
let (tx, tx2) = new_tx_pair_default(1.into(), 0.into());
|
||||
let tx3 = new_tx_default();
|
||||
txq.add(tx2.clone(), &default_account_details, TransactionOrigin::External).unwrap();
|
||||
@@ -1703,7 +1840,7 @@ mod test {
|
||||
#[test]
|
||||
fn should_clear_queue() {
|
||||
// given
|
||||
let mut txq = TransactionQueue::new();
|
||||
let mut txq = TransactionQueue::default();
|
||||
let (tx, tx2) = new_tx_pair_default(1.into(), 0.into());
|
||||
|
||||
// add
|
||||
@@ -1723,7 +1860,7 @@ mod test {
|
||||
#[test]
|
||||
fn should_drop_old_transactions_when_hitting_the_limit() {
|
||||
// given
|
||||
let mut txq = TransactionQueue::with_limits(1, !U256::zero(), !U256::zero());
|
||||
let mut txq = TransactionQueue::with_limits(PrioritizationStrategy::GasPriceOnly, 1, !U256::zero(), !U256::zero());
|
||||
let (tx, tx2) = new_tx_pair_default(1.into(), 0.into());
|
||||
let sender = tx.sender().unwrap();
|
||||
let nonce = tx.nonce;
|
||||
@@ -1744,7 +1881,7 @@ mod test {
|
||||
|
||||
#[test]
|
||||
fn should_limit_future_transactions() {
|
||||
let mut txq = TransactionQueue::with_limits(1, !U256::zero(), !U256::zero());
|
||||
let mut txq = TransactionQueue::with_limits(PrioritizationStrategy::GasPriceOnly, 1, !U256::zero(), !U256::zero());
|
||||
txq.current.set_limit(10);
|
||||
let (tx1, tx2) = new_tx_pair_default(4.into(), 1.into());
|
||||
let (tx3, tx4) = new_tx_pair_default(4.into(), 2.into());
|
||||
@@ -1763,23 +1900,28 @@ mod test {
|
||||
|
||||
#[test]
|
||||
fn should_limit_by_gas() {
|
||||
let mut txq = TransactionQueue::with_limits(100, default_gas_val() * U256::from(2), !U256::zero());
|
||||
let mut txq = TransactionQueue::with_limits(PrioritizationStrategy::GasPriceOnly, 100, default_gas_val() * U256::from(2), !U256::zero());
|
||||
let (tx1, tx2) = new_tx_pair_default(U256::from(1), U256::from(1));
|
||||
let (tx3, tx4) = new_tx_pair_default(U256::from(1), U256::from(2));
|
||||
txq.add(tx1.clone(), &default_account_details, TransactionOrigin::External).ok();
|
||||
txq.add(tx2.clone(), &default_account_details, TransactionOrigin::External).ok();
|
||||
txq.add(tx3.clone(), &default_account_details, TransactionOrigin::External).ok();
|
||||
txq.add(tx4.clone(), &default_account_details, TransactionOrigin::External).ok();
|
||||
txq.add(tx1.clone(), &default_account_details, TransactionOrigin::External).unwrap();
|
||||
txq.add(tx2.clone(), &default_account_details, TransactionOrigin::External).unwrap();
|
||||
txq.add(tx3.clone(), &default_account_details, TransactionOrigin::External).unwrap();
|
||||
// limited by gas
|
||||
txq.add(tx4.clone(), &default_account_details, TransactionOrigin::External).unwrap_err();
|
||||
assert_eq!(txq.status().pending, 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_keep_own_transactions_above_gas_limit() {
|
||||
let mut txq = TransactionQueue::with_limits(100, default_gas_val() * U256::from(2), !U256::zero());
|
||||
let mut txq = TransactionQueue::with_limits(PrioritizationStrategy::GasPriceOnly, 100, default_gas_val() * U256::from(2), !U256::zero());
|
||||
let (tx1, tx2) = new_tx_pair_default(U256::from(1), U256::from(1));
|
||||
let (tx3, tx4) = new_tx_pair_default(U256::from(1), U256::from(2));
|
||||
let (tx5, tx6) = new_tx_pair_default(U256::from(1), U256::from(2));
|
||||
txq.add(tx1.clone(), &default_account_details, TransactionOrigin::Local).unwrap();
|
||||
txq.add(tx2.clone(), &default_account_details, TransactionOrigin::Local).unwrap();
|
||||
txq.add(tx5.clone(), &default_account_details, TransactionOrigin::External).unwrap();
|
||||
// Not accepted because of limit
|
||||
txq.add(tx6.clone(), &default_account_details, TransactionOrigin::External).unwrap_err();
|
||||
txq.add(tx3.clone(), &default_account_details, TransactionOrigin::Local).unwrap();
|
||||
txq.add(tx4.clone(), &default_account_details, TransactionOrigin::Local).unwrap();
|
||||
assert_eq!(txq.status().pending, 4);
|
||||
@@ -1787,10 +1929,10 @@ mod test {
|
||||
|
||||
#[test]
|
||||
fn should_drop_transactions_with_old_nonces() {
|
||||
let mut txq = TransactionQueue::new();
|
||||
let mut txq = TransactionQueue::default();
|
||||
let tx = new_tx_default();
|
||||
let last_nonce = tx.nonce + U256::one();
|
||||
let fetch_last_nonce = |_a: &Address| AccountDetails{ nonce: last_nonce, balance: !U256::zero() };
|
||||
let fetch_last_nonce = |_a: &Address| AccountDetails { nonce: last_nonce, balance: !U256::zero() };
|
||||
|
||||
// when
|
||||
let res = txq.add(tx, &fetch_last_nonce, TransactionOrigin::External);
|
||||
@@ -1807,7 +1949,7 @@ mod test {
|
||||
// given
|
||||
let nonce = |a: &Address| AccountDetails { nonce: default_account_details(a).nonce + U256::one(),
|
||||
balance: !U256::zero() };
|
||||
let mut txq = TransactionQueue::new();
|
||||
let mut txq = TransactionQueue::default();
|
||||
let (_tx1, tx2) = new_tx_pair_default(1.into(), 0.into());
|
||||
txq.add(tx2.clone(), &default_account_details, TransactionOrigin::External).unwrap();
|
||||
assert_eq!(txq.status().future, 1);
|
||||
@@ -1826,7 +1968,7 @@ mod test {
|
||||
#[test]
|
||||
fn should_accept_same_transaction_twice_if_removed() {
|
||||
// given
|
||||
let mut txq = TransactionQueue::new();
|
||||
let mut txq = TransactionQueue::default();
|
||||
let (tx1, tx2) = new_tx_pair_default(1.into(), 0.into());
|
||||
txq.add(tx1.clone(), &default_account_details, TransactionOrigin::External).unwrap();
|
||||
txq.add(tx2.clone(), &default_account_details, TransactionOrigin::External).unwrap();
|
||||
@@ -1847,7 +1989,7 @@ mod test {
|
||||
#[test]
|
||||
fn should_not_move_to_future_if_state_nonce_is_higher() {
|
||||
// given
|
||||
let mut txq = TransactionQueue::new();
|
||||
let mut txq = TransactionQueue::default();
|
||||
let (tx, tx2) = new_tx_pair_default(1.into(), 0.into());
|
||||
let tx3 = new_tx_default();
|
||||
txq.add(tx2.clone(), &default_account_details, TransactionOrigin::External).unwrap();
|
||||
@@ -1870,9 +2012,9 @@ mod test {
|
||||
fn should_replace_same_transaction_when_has_higher_fee() {
|
||||
init_log();
|
||||
// given
|
||||
let mut txq = TransactionQueue::new();
|
||||
let mut txq = TransactionQueue::default();
|
||||
let keypair = Random.generate().unwrap();
|
||||
let tx = new_unsigned_tx(123.into(), 1.into()).sign(keypair.secret());
|
||||
let tx = new_unsigned_tx(123.into(), default_gas_val(), 1.into()).sign(keypair.secret());
|
||||
let tx2 = {
|
||||
let mut tx2 = (*tx).clone();
|
||||
tx2.gas_price = U256::from(200);
|
||||
@@ -1893,9 +2035,9 @@ mod test {
|
||||
#[test]
|
||||
fn should_replace_same_transaction_when_importing_to_futures() {
|
||||
// given
|
||||
let mut txq = TransactionQueue::new();
|
||||
let mut txq = TransactionQueue::default();
|
||||
let keypair = Random.generate().unwrap();
|
||||
let tx0 = new_unsigned_tx(123.into(), 1.into()).sign(keypair.secret());
|
||||
let tx0 = new_unsigned_tx(123.into(), default_gas_val(), 1.into()).sign(keypair.secret());
|
||||
let tx1 = {
|
||||
let mut tx1 = (*tx0).clone();
|
||||
tx1.nonce = U256::from(124);
|
||||
@@ -1927,7 +2069,7 @@ mod test {
|
||||
!U256::zero() };
|
||||
let next_nonce = |a: &Address| AccountDetails{ nonce: default_account_details(a).nonce + U256::one(), balance:
|
||||
!U256::zero() };
|
||||
let mut txq = TransactionQueue::new();
|
||||
let mut txq = TransactionQueue::default();
|
||||
let (tx1, tx2) = new_tx_pair_default(1.into(), 0.into());
|
||||
txq.add(tx1.clone(), &previous_nonce, TransactionOrigin::External).unwrap();
|
||||
txq.add(tx2, &previous_nonce, TransactionOrigin::External).unwrap();
|
||||
@@ -1945,7 +2087,7 @@ mod test {
|
||||
#[test]
|
||||
fn should_return_none_when_transaction_from_given_address_does_not_exist() {
|
||||
// given
|
||||
let txq = TransactionQueue::new();
|
||||
let txq = TransactionQueue::default();
|
||||
|
||||
// then
|
||||
assert_eq!(txq.last_nonce(&Address::default()), None);
|
||||
@@ -1954,7 +2096,7 @@ mod test {
|
||||
#[test]
|
||||
fn should_return_correct_nonce_when_transactions_from_given_address_exist() {
|
||||
// given
|
||||
let mut txq = TransactionQueue::new();
|
||||
let mut txq = TransactionQueue::default();
|
||||
let tx = new_tx_default();
|
||||
let from = tx.sender().unwrap();
|
||||
let nonce = tx.nonce;
|
||||
@@ -1970,7 +2112,7 @@ mod test {
|
||||
#[test]
|
||||
fn should_remove_old_transaction_even_if_newer_transaction_was_not_known() {
|
||||
// given
|
||||
let mut txq = TransactionQueue::new();
|
||||
let mut txq = TransactionQueue::default();
|
||||
let (tx1, tx2) = new_tx_pair_default(1.into(), 0.into());
|
||||
let (nonce1, nonce2) = (tx1.nonce, tx2.nonce);
|
||||
let details1 = |_a: &Address| AccountDetails { nonce: nonce1, balance: !U256::zero() };
|
||||
@@ -1988,7 +2130,7 @@ mod test {
|
||||
#[test]
|
||||
fn should_return_valid_last_nonce_after_remove_all() {
|
||||
// given
|
||||
let mut txq = TransactionQueue::new();
|
||||
let mut txq = TransactionQueue::default();
|
||||
let (tx1, tx2) = new_tx_pair_default(4.into(), 0.into());
|
||||
let sender = tx1.sender().unwrap();
|
||||
let (nonce1, nonce2) = (tx1.nonce, tx2.nonce);
|
||||
@@ -2012,7 +2154,7 @@ mod test {
|
||||
#[test]
|
||||
fn should_return_true_if_there_is_local_transaction_pending() {
|
||||
// given
|
||||
let mut txq = TransactionQueue::new();
|
||||
let mut txq = TransactionQueue::default();
|
||||
let (tx1, tx2) = new_tx_pair_default(1.into(), 0.into());
|
||||
assert_eq!(txq.has_local_pending_transactions(), false);
|
||||
|
||||
@@ -2028,7 +2170,7 @@ mod test {
|
||||
#[test]
|
||||
fn should_keep_right_order_in_future() {
|
||||
// given
|
||||
let mut txq = TransactionQueue::with_limits(1, !U256::zero(), !U256::zero());
|
||||
let mut txq = TransactionQueue::with_limits(PrioritizationStrategy::GasPriceOnly, 1, !U256::zero(), !U256::zero());
|
||||
let (tx1, tx2) = new_tx_pair_default(1.into(), 0.into());
|
||||
let prev_nonce = |a: &Address| AccountDetails { nonce: default_account_details(a).nonce - U256::one(), balance:
|
||||
default_account_details(a).balance };
|
||||
@@ -2045,15 +2187,16 @@ mod test {
|
||||
#[test]
|
||||
fn should_return_correct_last_nonce() {
|
||||
// given
|
||||
let mut txq = TransactionQueue::new();
|
||||
let mut txq = TransactionQueue::default();
|
||||
let (tx1, tx2, tx2_2, tx3) = {
|
||||
let keypair = Random.generate().unwrap();
|
||||
let secret = &keypair.secret();
|
||||
let nonce = 123.into();
|
||||
let tx = new_unsigned_tx(nonce, 1.into());
|
||||
let tx2 = new_unsigned_tx(nonce + 1.into(), 1.into());
|
||||
let tx2_2 = new_unsigned_tx(nonce + 1.into(), 5.into());
|
||||
let tx3 = new_unsigned_tx(nonce + 2.into(), 1.into());
|
||||
let gas = default_gas_val();
|
||||
let tx = new_unsigned_tx(nonce, gas, 1.into());
|
||||
let tx2 = new_unsigned_tx(nonce + 1.into(), gas, 1.into());
|
||||
let tx2_2 = new_unsigned_tx(nonce + 1.into(), gas, 5.into());
|
||||
let tx3 = new_unsigned_tx(nonce + 2.into(), gas, 1.into());
|
||||
|
||||
|
||||
(tx.sign(secret), tx2.sign(secret), tx2_2.sign(secret), tx3.sign(secret))
|
||||
|
||||
@@ -89,7 +89,7 @@ impl ClientService {
|
||||
db_config.set_cache(::db::COL_STATE, size);
|
||||
}
|
||||
|
||||
db_config.compaction = config.db_compaction.compaction_profile();
|
||||
db_config.compaction = config.db_compaction.compaction_profile(&client_path);
|
||||
db_config.wal = config.db_wal;
|
||||
|
||||
let pruning = config.pruning;
|
||||
|
||||
@@ -19,12 +19,20 @@
|
||||
use account_db::{AccountDB, AccountDBMut};
|
||||
use snapshot::Error;
|
||||
|
||||
use util::{U256, FixedHash, H256, Bytes, HashDB, SHA3_EMPTY};
|
||||
use util::{U256, FixedHash, H256, Bytes, HashDB, SHA3_EMPTY, SHA3_NULL_RLP};
|
||||
use util::trie::{TrieDB, Trie};
|
||||
use rlp::{Rlp, RlpStream, Stream, UntrustedRlp, View};
|
||||
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
// An empty account -- these are replaced with RLP null data for a space optimization.
|
||||
const ACC_EMPTY: Account = Account {
|
||||
nonce: U256([0, 0, 0, 0]),
|
||||
balance: U256([0, 0, 0, 0]),
|
||||
storage_root: SHA3_NULL_RLP,
|
||||
code_hash: SHA3_EMPTY,
|
||||
};
|
||||
|
||||
// whether an encoded account has code and how it is referred to.
|
||||
#[repr(u8)]
|
||||
enum CodeState {
|
||||
@@ -88,6 +96,10 @@ impl Account {
|
||||
// walk the account's storage trie, returning an RLP item containing the
|
||||
// account properties and the storage.
|
||||
pub fn to_fat_rlp(&self, acct_db: &AccountDB, used_code: &mut HashSet<H256>) -> Result<Bytes, Error> {
|
||||
if self == &ACC_EMPTY {
|
||||
return Ok(::rlp::NULL_RLP.to_vec());
|
||||
}
|
||||
|
||||
let db = try!(TrieDB::new(acct_db, &self.storage_root));
|
||||
|
||||
let mut pairs = Vec::new();
|
||||
@@ -142,6 +154,11 @@ impl Account {
|
||||
) -> Result<(Self, Option<Bytes>), Error> {
|
||||
use util::{TrieDBMut, TrieMut};
|
||||
|
||||
// check for special case of empty account.
|
||||
if rlp.is_empty() {
|
||||
return Ok((ACC_EMPTY, None));
|
||||
}
|
||||
|
||||
let nonce = try!(rlp.val_at(0));
|
||||
let balance = try!(rlp.val_at(1));
|
||||
let code_state: CodeState = {
|
||||
@@ -214,7 +231,7 @@ mod tests {
|
||||
|
||||
use std::collections::{HashSet, HashMap};
|
||||
|
||||
use super::Account;
|
||||
use super::{ACC_EMPTY, Account};
|
||||
|
||||
#[test]
|
||||
fn encoding_basic() {
|
||||
@@ -310,4 +327,14 @@ mod tests {
|
||||
assert_eq!(maybe_code, Some(b"this is definitely code".to_vec()));
|
||||
assert_eq!(acc, account1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn encoding_empty_acc() {
|
||||
let mut db = get_temp_state_db();
|
||||
let mut used_code = HashSet::new();
|
||||
let code_map = HashMap::new();
|
||||
|
||||
assert_eq!(ACC_EMPTY.to_fat_rlp(&AccountDB::new(db.as_hashdb(), &Address::default()), &mut used_code).unwrap(), ::rlp::NULL_RLP.to_vec());
|
||||
assert_eq!(Account::from_fat_rlp(&mut AccountDBMut::new(db.as_hashdb_mut(), &Address::default()), UntrustedRlp::new(&::rlp::NULL_RLP), &code_map).unwrap(), (ACC_EMPTY, None));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ use engines::Engine;
|
||||
use ids::BlockID;
|
||||
use views::BlockView;
|
||||
|
||||
use util::{Bytes, Hashable, HashDB, snappy};
|
||||
use util::{Bytes, Hashable, HashDB, snappy, U256, Uint};
|
||||
use util::memorydb::MemoryDB;
|
||||
use util::Mutex;
|
||||
use util::hash::{FixedHash, H256};
|
||||
@@ -44,6 +44,7 @@ use self::block::AbridgedBlock;
|
||||
use self::io::SnapshotWriter;
|
||||
|
||||
use super::state_db::StateDB;
|
||||
use super::state::Account as StateAccount;
|
||||
|
||||
use crossbeam::{scope, ScopedJoinHandle};
|
||||
use rand::{Rng, OsRng};
|
||||
@@ -409,6 +410,7 @@ impl StateRebuilder {
|
||||
/// Feed an uncompressed state chunk into the rebuilder.
|
||||
pub fn feed(&mut self, chunk: &[u8]) -> Result<(), ::error::Error> {
|
||||
let rlp = UntrustedRlp::new(chunk);
|
||||
let empty_rlp = StateAccount::new_basic(U256::zero(), U256::zero()).rlp();
|
||||
let account_fat_rlps: Vec<_> = rlp.iter().map(|r| r.as_raw()).collect();
|
||||
let mut pairs = Vec::with_capacity(rlp.item_count());
|
||||
|
||||
@@ -476,7 +478,9 @@ impl StateRebuilder {
|
||||
};
|
||||
|
||||
for (hash, thin_rlp) in pairs {
|
||||
bloom.set(&*hash);
|
||||
if &thin_rlp[..] != &empty_rlp[..] {
|
||||
bloom.set(&*hash);
|
||||
}
|
||||
try!(account_trie.insert(&hash, &thin_rlp));
|
||||
}
|
||||
}
|
||||
@@ -564,6 +568,7 @@ const POW_VERIFY_RATE: f32 = 0.02;
|
||||
/// After all chunks have been submitted, we "glue" the chunks together.
|
||||
pub struct BlockRebuilder {
|
||||
chain: BlockChain,
|
||||
db: Arc<Database>,
|
||||
rng: OsRng,
|
||||
disconnected: Vec<(u64, H256)>,
|
||||
best_number: u64,
|
||||
@@ -571,9 +576,10 @@ pub struct BlockRebuilder {
|
||||
|
||||
impl BlockRebuilder {
|
||||
/// Create a new BlockRebuilder.
|
||||
pub fn new(chain: BlockChain, best_number: u64) -> Result<Self, ::error::Error> {
|
||||
pub fn new(chain: BlockChain, db: Arc<Database>, best_number: u64) -> Result<Self, ::error::Error> {
|
||||
Ok(BlockRebuilder {
|
||||
chain: chain,
|
||||
db: db,
|
||||
rng: try!(OsRng::new()),
|
||||
disconnected: Vec::new(),
|
||||
best_number: best_number,
|
||||
@@ -616,15 +622,17 @@ impl BlockRebuilder {
|
||||
}
|
||||
|
||||
let is_best = cur_number == self.best_number;
|
||||
let mut batch = self.db.transaction();
|
||||
|
||||
// special-case the first block in each chunk.
|
||||
if idx == 3 {
|
||||
if self.chain.insert_snapshot_block(&block_bytes, receipts, Some(parent_total_difficulty), is_best) {
|
||||
if self.chain.insert_unordered_block(&mut batch, &block_bytes, receipts, Some(parent_total_difficulty), is_best, false) {
|
||||
self.disconnected.push((cur_number, block.header.hash()));
|
||||
}
|
||||
} else {
|
||||
self.chain.insert_snapshot_block(&block_bytes, receipts, None, is_best);
|
||||
self.chain.insert_unordered_block(&mut batch, &block_bytes, receipts, None, is_best, false);
|
||||
}
|
||||
self.db.write(batch).expect("Error writing to the DB");
|
||||
self.chain.commit();
|
||||
|
||||
parent_hash = BlockView::new(&block_bytes).hash();
|
||||
|
||||
@@ -98,7 +98,7 @@ impl Restoration {
|
||||
.map_err(UtilError::SimpleString)));
|
||||
|
||||
let chain = BlockChain::new(Default::default(), params.genesis, raw_db.clone());
|
||||
let blocks = try!(BlockRebuilder::new(chain, manifest.block_number));
|
||||
let blocks = try!(BlockRebuilder::new(chain, raw_db.clone(), manifest.block_number));
|
||||
|
||||
let root = manifest.state_root.clone();
|
||||
Ok(Restoration {
|
||||
@@ -346,7 +346,7 @@ impl Service {
|
||||
|
||||
self.taking_snapshot.store(false, Ordering::SeqCst);
|
||||
if let Err(e) = res {
|
||||
if client.chain_info().best_block_number >= num + ::client::HISTORY {
|
||||
if client.chain_info().best_block_number >= num + client.pruning_history() {
|
||||
// "Cancelled" is mincing words a bit -- what really happened
|
||||
// is that the state we were snapshotting got pruned out
|
||||
// before we could finish.
|
||||
@@ -415,9 +415,14 @@ impl Service {
|
||||
guard: Guard::new(rest_dir),
|
||||
};
|
||||
|
||||
let state_chunks = params.manifest.state_hashes.len();
|
||||
let block_chunks = params.manifest.block_hashes.len();
|
||||
|
||||
*res = Some(try!(Restoration::new(params)));
|
||||
|
||||
*self.status.lock() = RestorationStatus::Ongoing {
|
||||
state_chunks: state_chunks as u32,
|
||||
block_chunks: block_chunks as u32,
|
||||
state_chunks_done: self.state_chunks.load(Ordering::SeqCst) as u32,
|
||||
block_chunks_done: self.block_chunks.load(Ordering::SeqCst) as u32,
|
||||
};
|
||||
@@ -535,7 +540,7 @@ impl SnapshotService for Service {
|
||||
|
||||
fn status(&self) -> RestorationStatus {
|
||||
let mut cur_status = self.status.lock();
|
||||
if let RestorationStatus::Ongoing { ref mut state_chunks_done, ref mut block_chunks_done } = *cur_status {
|
||||
if let RestorationStatus::Ongoing { ref mut state_chunks_done, ref mut block_chunks_done, .. } = *cur_status {
|
||||
*state_chunks_done = self.state_chunks.load(Ordering::SeqCst) as u32;
|
||||
*block_chunks_done = self.block_chunks.load(Ordering::SeqCst) as u32;
|
||||
}
|
||||
@@ -629,4 +634,4 @@ mod tests {
|
||||
service.restore_state_chunk(Default::default(), vec![]);
|
||||
service.restore_block_chunk(Default::default(), vec![]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ fn chunk_and_restore(amount: u64) {
|
||||
// restore it.
|
||||
let new_db = Arc::new(Database::open(&db_cfg, new_path.as_str()).unwrap());
|
||||
let new_chain = BlockChain::new(Default::default(), &genesis, new_db.clone());
|
||||
let mut rebuilder = BlockRebuilder::new(new_chain, amount).unwrap();
|
||||
let mut rebuilder = BlockRebuilder::new(new_chain, new_db.clone(), amount).unwrap();
|
||||
let reader = PackedReader::new(&snapshot_path).unwrap().unwrap();
|
||||
let engine = ::engines::NullEngine::new(Default::default(), Default::default());
|
||||
for chunk_hash in &reader.manifest().block_hashes {
|
||||
|
||||
@@ -30,7 +30,7 @@ use std::sync::Arc;
|
||||
trait Oracle: Send + Sync {
|
||||
fn to_number(&self, hash: H256) -> Option<u64>;
|
||||
|
||||
fn is_major_syncing(&self) -> bool;
|
||||
fn is_major_importing(&self) -> bool;
|
||||
}
|
||||
|
||||
struct StandardOracle<F> where F: 'static + Send + Sync + Fn() -> bool {
|
||||
@@ -45,10 +45,8 @@ impl<F> Oracle for StandardOracle<F>
|
||||
self.client.block_header(BlockID::Hash(hash)).map(|h| HeaderView::new(&h).number())
|
||||
}
|
||||
|
||||
fn is_major_syncing(&self) -> bool {
|
||||
let queue_info = self.client.queue_info();
|
||||
|
||||
(self.sync_status)() || queue_info.unverified_queue_size + queue_info.verified_queue_size > 3
|
||||
fn is_major_importing(&self) -> bool {
|
||||
(self.sync_status)()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,7 +108,7 @@ impl ChainNotify for Watcher {
|
||||
_: Vec<H256>,
|
||||
_duration: u64)
|
||||
{
|
||||
if self.oracle.is_major_syncing() { return }
|
||||
if self.oracle.is_major_importing() { return }
|
||||
|
||||
trace!(target: "snapshot_watcher", "{} imported", imported.len());
|
||||
|
||||
@@ -145,7 +143,7 @@ mod tests {
|
||||
self.0.get(&hash).cloned()
|
||||
}
|
||||
|
||||
fn is_major_syncing(&self) -> bool { false }
|
||||
fn is_major_importing(&self) -> bool { false }
|
||||
}
|
||||
|
||||
struct TestBroadcast(Option<u64>);
|
||||
@@ -200,4 +198,4 @@ mod tests {
|
||||
fn doesnt_fire_before_history() {
|
||||
harness(vec![10, 11], 10, 5, None);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -151,7 +151,8 @@ impl Spec {
|
||||
if self.state_root_memo.read().is_none() {
|
||||
*self.state_root_memo.write() = Some(self.genesis_state.root());
|
||||
}
|
||||
self.state_root_memo.read().as_ref().unwrap().clone()
|
||||
self.state_root_memo.read().as_ref().cloned()
|
||||
.expect("state root memo ensured to be set at this point; qed")
|
||||
}
|
||||
|
||||
/// Get the known knodes of the network in enode format.
|
||||
|
||||
@@ -288,6 +288,15 @@ impl Account {
|
||||
/// Determine whether there are any un-`commit()`-ed storage-setting operations.
|
||||
pub fn storage_is_clean(&self) -> bool { self.storage_changes.is_empty() }
|
||||
|
||||
/// Check if account has zero nonce, balance, no code and no storage.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.storage_changes.is_empty() &&
|
||||
self.balance.is_zero() &&
|
||||
self.nonce.is_zero() &&
|
||||
self.storage_root == SHA3_NULL_RLP &&
|
||||
self.code_hash == SHA3_EMPTY
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
/// return the storage root associated with this account or None if it has been altered via the overlay.
|
||||
pub fn storage_root(&self) -> Option<&H256> { if self.storage_is_clean() {Some(&self.storage_root)} else {None} }
|
||||
|
||||
@@ -239,15 +239,15 @@ impl State {
|
||||
|
||||
/// Create a recoverable snaphot of this state.
|
||||
pub fn snapshot(&mut self) {
|
||||
self.snapshots.borrow_mut().push(HashMap::new());
|
||||
self.snapshots.get_mut().push(HashMap::new());
|
||||
}
|
||||
|
||||
/// Merge last snapshot with previous.
|
||||
pub fn discard_snapshot(&mut self) {
|
||||
// merge with previous snapshot
|
||||
let last = self.snapshots.borrow_mut().pop();
|
||||
let last = self.snapshots.get_mut().pop();
|
||||
if let Some(mut snapshot) = last {
|
||||
if let Some(ref mut prev) = self.snapshots.borrow_mut().last_mut() {
|
||||
if let Some(ref mut prev) = self.snapshots.get_mut().last_mut() {
|
||||
if prev.is_empty() {
|
||||
**prev = snapshot;
|
||||
} else {
|
||||
@@ -261,11 +261,11 @@ impl State {
|
||||
|
||||
/// Revert to the last snapshot and discard it.
|
||||
pub fn revert_to_snapshot(&mut self) {
|
||||
if let Some(mut snapshot) = self.snapshots.borrow_mut().pop() {
|
||||
if let Some(mut snapshot) = self.snapshots.get_mut().pop() {
|
||||
for (k, v) in snapshot.drain() {
|
||||
match v {
|
||||
Some(v) => {
|
||||
match self.cache.borrow_mut().entry(k) {
|
||||
match self.cache.get_mut().entry(k) {
|
||||
Entry::Occupied(mut e) => {
|
||||
// Merge snapshotted changes back into the main account
|
||||
// storage preserving the cache.
|
||||
@@ -277,7 +277,7 @@ impl State {
|
||||
}
|
||||
},
|
||||
None => {
|
||||
match self.cache.borrow_mut().entry(k) {
|
||||
match self.cache.get_mut().entry(k) {
|
||||
Entry::Occupied(e) => {
|
||||
if e.get().is_dirty() {
|
||||
e.remove();
|
||||
@@ -340,18 +340,20 @@ impl State {
|
||||
|
||||
/// Determine whether an account exists.
|
||||
pub fn exists(&self, a: &Address) -> bool {
|
||||
self.ensure_cached(a, RequireCache::None, |a| a.is_some())
|
||||
// Bloom filter does not contain empty accounts, so it is important here to
|
||||
// check if account exists in the database directly before EIP-158 is in effect.
|
||||
self.ensure_cached(a, RequireCache::None, false, |a| a.is_some())
|
||||
}
|
||||
|
||||
/// Get the balance of account `a`.
|
||||
pub fn balance(&self, a: &Address) -> U256 {
|
||||
self.ensure_cached(a, RequireCache::None,
|
||||
self.ensure_cached(a, RequireCache::None, true,
|
||||
|a| a.as_ref().map_or(U256::zero(), |account| *account.balance()))
|
||||
}
|
||||
|
||||
/// Get the nonce of account `a`.
|
||||
pub fn nonce(&self, a: &Address) -> U256 {
|
||||
self.ensure_cached(a, RequireCache::None,
|
||||
self.ensure_cached(a, RequireCache::None, true,
|
||||
|a| a.as_ref().map_or(self.account_start_nonce, |account| *account.nonce()))
|
||||
}
|
||||
|
||||
@@ -415,18 +417,18 @@ impl State {
|
||||
|
||||
/// Get accounts' code.
|
||||
pub fn code(&self, a: &Address) -> Option<Arc<Bytes>> {
|
||||
self.ensure_cached(a, RequireCache::Code,
|
||||
self.ensure_cached(a, RequireCache::Code, true,
|
||||
|a| a.as_ref().map_or(None, |a| a.code().clone()))
|
||||
}
|
||||
|
||||
pub fn code_hash(&self, a: &Address) -> H256 {
|
||||
self.ensure_cached(a, RequireCache::None,
|
||||
self.ensure_cached(a, RequireCache::None, true,
|
||||
|a| a.as_ref().map_or(SHA3_EMPTY, |a| a.code_hash()))
|
||||
}
|
||||
|
||||
/// Get accounts' code size.
|
||||
pub fn code_size(&self, a: &Address) -> Option<usize> {
|
||||
self.ensure_cached(a, RequireCache::CodeSize,
|
||||
self.ensure_cached(a, RequireCache::CodeSize, true,
|
||||
|a| a.as_ref().and_then(|a| a.code_size()))
|
||||
}
|
||||
|
||||
@@ -505,7 +507,9 @@ impl State {
|
||||
for (address, ref mut a) in accounts.iter_mut().filter(|&(_, ref a)| a.is_dirty()) {
|
||||
match a.account {
|
||||
Some(ref mut account) => {
|
||||
db.note_account_bloom(&address);
|
||||
if !account.is_empty() {
|
||||
db.note_account_bloom(&address);
|
||||
}
|
||||
let addr_hash = account.address_hash(address);
|
||||
let mut account_db = factories.accountdb.create(db.as_hashdb_mut(), addr_hash);
|
||||
account.commit_storage(&factories.trie, account_db.as_hashdb_mut());
|
||||
@@ -516,7 +520,7 @@ impl State {
|
||||
}
|
||||
|
||||
{
|
||||
let mut trie = factories.trie.from_existing(db.as_hashdb_mut(), root).unwrap();
|
||||
let mut trie = try!(factories.trie.from_existing(db.as_hashdb_mut(), root));
|
||||
for (address, ref mut a) in accounts.iter_mut().filter(|&(_, ref a)| a.is_dirty()) {
|
||||
a.state = AccountState::Committed;
|
||||
match a.account {
|
||||
@@ -559,7 +563,6 @@ impl State {
|
||||
pub fn populate_from(&mut self, accounts: PodState) {
|
||||
assert!(self.snapshots.borrow().is_empty());
|
||||
for (add, acc) in accounts.drain().into_iter() {
|
||||
self.db.note_account_bloom(&add);
|
||||
self.cache.borrow_mut().insert(add, AccountEntry::new_dirty(Some(Account::from_pod(acc))));
|
||||
}
|
||||
}
|
||||
@@ -578,14 +581,14 @@ impl State {
|
||||
}
|
||||
|
||||
fn query_pod(&mut self, query: &PodState) {
|
||||
for (address, pod_account) in query.get() {
|
||||
self.ensure_cached(address, RequireCache::Code, |a| {
|
||||
if a.is_some() {
|
||||
for key in pod_account.storage.keys() {
|
||||
self.storage_at(address, key);
|
||||
}
|
||||
}
|
||||
});
|
||||
for (address, pod_account) in query.get().into_iter()
|
||||
.filter(|&(ref a, _)| self.ensure_cached(a, RequireCache::Code, true, |a| a.is_some()))
|
||||
{
|
||||
// needs to be split into two parts for the refcell code here
|
||||
// to work.
|
||||
for key in pod_account.storage.keys() {
|
||||
self.storage_at(address, key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -613,7 +616,7 @@ impl State {
|
||||
/// Check caches for required data
|
||||
/// First searches for account in the local, then the shared cache.
|
||||
/// Populates local cache if nothing found.
|
||||
fn ensure_cached<F, U>(&self, a: &Address, require: RequireCache, f: F) -> U
|
||||
fn ensure_cached<F, U>(&self, a: &Address, require: RequireCache, check_bloom: bool, f: F) -> U
|
||||
where F: Fn(Option<&Account>) -> U {
|
||||
// check local cache first
|
||||
if let Some(ref mut maybe_acc) = self.cache.borrow_mut().get_mut(a) {
|
||||
@@ -636,7 +639,7 @@ impl State {
|
||||
Some(r) => r,
|
||||
None => {
|
||||
// first check bloom if it is not in database for sure
|
||||
if !self.db.check_account_bloom(a) { return f(None); }
|
||||
if check_bloom && !self.db.check_account_bloom(a) { return f(None); }
|
||||
|
||||
// not found in the global cache, get from the DB and insert into local
|
||||
let db = self.factories.trie.readonly(self.db.as_hashdb(), &self.root).expect(SEC_TRIE_DB_UNWRAP_STR);
|
||||
@@ -688,14 +691,15 @@ impl State {
|
||||
}
|
||||
self.note_cache(a);
|
||||
|
||||
match &mut self.cache.borrow_mut().get_mut(a).unwrap().account {
|
||||
&mut Some(ref mut acc) => not_default(acc),
|
||||
slot => *slot = Some(default()),
|
||||
}
|
||||
|
||||
// at this point the account is guaranteed to be in the cache.
|
||||
// at this point the entry is guaranteed to be in the cache.
|
||||
RefMut::map(self.cache.borrow_mut(), |c| {
|
||||
let mut entry = c.get_mut(a).unwrap();
|
||||
let mut entry = c.get_mut(a).expect("entry known to exist in the cache; qed");
|
||||
|
||||
match &mut entry.account {
|
||||
&mut Some(ref mut acc) => not_default(acc),
|
||||
slot => *slot = Some(default()),
|
||||
}
|
||||
|
||||
// set the dirty flag after changing account data.
|
||||
entry.state = AccountState::Dirty;
|
||||
match entry.account {
|
||||
@@ -1667,6 +1671,21 @@ fn remove() {
|
||||
assert_eq!(state.nonce(&a), U256::from(0u64));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_account_exists() {
|
||||
let a = Address::zero();
|
||||
let path = RandomTempPath::new();
|
||||
let db = get_temp_state_db_in(path.as_path());
|
||||
let (root, db) = {
|
||||
let mut state = State::new(db, U256::from(0), Default::default());
|
||||
state.add_balance(&a, &U256::default()); // create an empty account
|
||||
state.commit().unwrap();
|
||||
state.drop()
|
||||
};
|
||||
let state = State::from_existing(db, root, U256::from(0u8), Default::default()).unwrap();
|
||||
assert!(state.exists(&a));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn remove_from_database() {
|
||||
let a = Address::zero();
|
||||
@@ -1797,4 +1816,20 @@ fn create_empty() {
|
||||
assert_eq!(state.root().hex(), "56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_not_panic_on_state_diff_with_storage() {
|
||||
let state = get_temp_state();
|
||||
let mut state = state.reference().clone();
|
||||
|
||||
let a: Address = 0xa.into();
|
||||
state.init_code(&a, b"abcdefg".to_vec());
|
||||
state.add_balance(&a, &256.into());
|
||||
state.set_storage(&a, 0xb.into(), 0xc.into());
|
||||
|
||||
let mut new_state = state.clone();
|
||||
new_state.set_storage(&a, 0xb.into(), 0xd.into());
|
||||
|
||||
new_state.diff_from(state);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -26,17 +26,18 @@ use bloom_journal::{Bloom, BloomJournal};
|
||||
use db::COL_ACCOUNT_BLOOM;
|
||||
use byteorder::{LittleEndian, ByteOrder};
|
||||
|
||||
const STATE_CACHE_ITEMS: usize = 256000;
|
||||
const STATE_CACHE_BLOCKS: usize = 8;
|
||||
|
||||
pub const ACCOUNT_BLOOM_SPACE: usize = 1048576;
|
||||
pub const DEFAULT_ACCOUNT_PRESET: usize = 1000000;
|
||||
|
||||
pub const ACCOUNT_BLOOM_HASHCOUNT_KEY: &'static [u8] = b"account_hash_count";
|
||||
|
||||
const STATE_CACHE_BLOCKS: usize = 12;
|
||||
|
||||
/// Shared canonical state cache.
|
||||
struct AccountCache {
|
||||
/// DB Account cache. `None` indicates that account is known to be missing.
|
||||
// When changing the type of the values here, be sure to update `mem_used` and
|
||||
// `new`.
|
||||
accounts: LruCache<Address, Option<Account>>,
|
||||
/// Information on the modifications in recently committed blocks; specifically which addresses
|
||||
/// changed in which block. Ordered by block number.
|
||||
@@ -92,6 +93,7 @@ pub struct StateDB {
|
||||
local_cache: Vec<CacheQueueItem>,
|
||||
/// Shared account bloom. Does not handle chain reorganizations.
|
||||
account_bloom: Arc<Mutex<Bloom>>,
|
||||
cache_size: usize,
|
||||
/// Hash of the block on top of which this instance was created or
|
||||
/// `None` if cache is disabled
|
||||
parent_hash: Option<H256>,
|
||||
@@ -102,16 +104,41 @@ pub struct StateDB {
|
||||
}
|
||||
|
||||
impl StateDB {
|
||||
|
||||
/// Create a new instance wrapping `JournalDB` and the maximum allowed size
|
||||
/// of the LRU cache in bytes. Actual used memory may (read: will) be higher due to bookkeeping.
|
||||
// TODO: make the cache size actually accurate by moving the account storage cache
|
||||
// into the `AccountCache` structure as its own `LruCache<(Address, H256), H256>`.
|
||||
pub fn new(db: Box<JournalDB>, cache_size: usize) -> StateDB {
|
||||
let bloom = Self::load_bloom(db.backing());
|
||||
let cache_items = cache_size / ::std::mem::size_of::<Option<Account>>();
|
||||
|
||||
StateDB {
|
||||
db: db,
|
||||
account_cache: Arc::new(Mutex::new(AccountCache {
|
||||
accounts: LruCache::new(cache_items),
|
||||
modifications: VecDeque::new(),
|
||||
})),
|
||||
local_cache: Vec::new(),
|
||||
account_bloom: Arc::new(Mutex::new(bloom)),
|
||||
cache_size: cache_size,
|
||||
parent_hash: None,
|
||||
commit_hash: None,
|
||||
commit_number: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Loads accounts bloom from the database
|
||||
/// This bloom is used to handle request for the non-existant account fast
|
||||
pub fn load_bloom(db: &Database) -> Bloom {
|
||||
let hash_count_entry = db.get(COL_ACCOUNT_BLOOM, ACCOUNT_BLOOM_HASHCOUNT_KEY)
|
||||
.expect("Low-level database error");
|
||||
|
||||
if hash_count_entry.is_none() {
|
||||
return Bloom::new(ACCOUNT_BLOOM_SPACE, DEFAULT_ACCOUNT_PRESET);
|
||||
}
|
||||
let hash_count_bytes = hash_count_entry.unwrap();
|
||||
let hash_count_bytes = match hash_count_entry {
|
||||
Some(bytes) => bytes,
|
||||
None => return Bloom::new(ACCOUNT_BLOOM_SPACE, DEFAULT_ACCOUNT_PRESET),
|
||||
};
|
||||
|
||||
assert_eq!(hash_count_bytes.len(), 1);
|
||||
let hash_count = hash_count_bytes[0];
|
||||
|
||||
@@ -129,23 +156,6 @@ impl StateDB {
|
||||
bloom
|
||||
}
|
||||
|
||||
/// Create a new instance wrapping `JournalDB`
|
||||
pub fn new(db: Box<JournalDB>) -> StateDB {
|
||||
let bloom = Self::load_bloom(db.backing());
|
||||
StateDB {
|
||||
db: db,
|
||||
account_cache: Arc::new(Mutex::new(AccountCache {
|
||||
accounts: LruCache::new(STATE_CACHE_ITEMS),
|
||||
modifications: VecDeque::new(),
|
||||
})),
|
||||
local_cache: Vec::new(),
|
||||
account_bloom: Arc::new(Mutex::new(bloom)),
|
||||
parent_hash: None,
|
||||
commit_hash: None,
|
||||
commit_number: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check_account_bloom(&self, address: &Address) -> bool {
|
||||
trace!(target: "account_bloom", "Check account bloom: {:?}", address);
|
||||
let bloom = self.account_bloom.lock();
|
||||
@@ -172,19 +182,24 @@ impl StateDB {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Commit all recent insert operations and canonical historical commits' removals from the
|
||||
/// old era to the backing database, reverting any non-canonical historical commit's inserts.
|
||||
pub fn commit(&mut self, batch: &mut DBTransaction, now: u64, id: &H256, end: Option<(u64, H256)>) -> Result<u32, UtilError> {
|
||||
/// Journal all recent operations under the given era and ID.
|
||||
pub fn journal_under(&mut self, batch: &mut DBTransaction, now: u64, id: &H256) -> Result<u32, UtilError> {
|
||||
{
|
||||
let mut bloom_lock = self.account_bloom.lock();
|
||||
try!(Self::commit_bloom(batch, bloom_lock.drain_journal()));
|
||||
}
|
||||
let records = try!(self.db.commit(batch, now, id, end));
|
||||
let records = try!(self.db.journal_under(batch, now, id));
|
||||
self.commit_hash = Some(id.clone());
|
||||
self.commit_number = Some(now);
|
||||
Ok(records)
|
||||
}
|
||||
|
||||
/// Mark a given candidate from an ancient era as canonical, enacting its removals from the
|
||||
/// backing database and reverting any non-canonical historical commit's insertions.
|
||||
pub fn mark_canonical(&mut self, batch: &mut DBTransaction, end_era: u64, canon_id: &H256) -> Result<u32, UtilError> {
|
||||
self.db.mark_canonical(batch, end_era, canon_id)
|
||||
}
|
||||
|
||||
/// Propagate local cache into the global cache and synchonize
|
||||
/// the global cache with the best block state.
|
||||
/// This function updates the global cache by removing entries
|
||||
@@ -298,6 +313,7 @@ impl StateDB {
|
||||
account_cache: self.account_cache.clone(),
|
||||
local_cache: Vec::new(),
|
||||
account_bloom: self.account_bloom.clone(),
|
||||
cache_size: self.cache_size,
|
||||
parent_hash: None,
|
||||
commit_hash: None,
|
||||
commit_number: None,
|
||||
@@ -311,6 +327,7 @@ impl StateDB {
|
||||
account_cache: self.account_cache.clone(),
|
||||
local_cache: Vec::new(),
|
||||
account_bloom: self.account_bloom.clone(),
|
||||
cache_size: self.cache_size,
|
||||
parent_hash: Some(parent.clone()),
|
||||
commit_hash: None,
|
||||
commit_number: None,
|
||||
@@ -324,7 +341,8 @@ impl StateDB {
|
||||
|
||||
/// Heap size used.
|
||||
pub fn mem_used(&self) -> usize {
|
||||
self.db.mem_used() //TODO: + self.account_cache.lock().heap_size_of_children()
|
||||
// TODO: account for LRU-cache overhead; this is a close approximation.
|
||||
self.db.mem_used() + self.account_cache.lock().accounts.len() * ::std::mem::size_of::<Option<Account>>()
|
||||
}
|
||||
|
||||
/// Returns underlying `JournalDB`.
|
||||
@@ -365,6 +383,11 @@ impl StateDB {
|
||||
cache.accounts.get_mut(a).map(|c| f(c.as_mut()))
|
||||
}
|
||||
|
||||
/// Query how much memory is set aside for the accounts cache (in bytes).
|
||||
pub fn cache_size(&self) -> usize {
|
||||
self.cache_size
|
||||
}
|
||||
|
||||
/// Check if the account can be returned from cache by matching current block parent hash against canonical
|
||||
/// state and filtering out account modified in later blocks.
|
||||
fn is_allowed(addr: &Address, parent_hash: &Option<H256>, modifications: &VecDeque<BlockChanges>) -> bool {
|
||||
@@ -404,77 +427,77 @@ impl StateDB {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use util::{U256, H256, FixedHash, Address, DBTransaction};
|
||||
use tests::helpers::*;
|
||||
use state::Account;
|
||||
use util::log::init_log;
|
||||
use util::{U256, H256, FixedHash, Address, DBTransaction};
|
||||
use tests::helpers::*;
|
||||
use state::Account;
|
||||
use util::log::init_log;
|
||||
|
||||
#[test]
|
||||
fn state_db_smoke() {
|
||||
init_log();
|
||||
#[test]
|
||||
fn state_db_smoke() {
|
||||
init_log();
|
||||
|
||||
let mut state_db_result = get_temp_state_db();
|
||||
let state_db = state_db_result.take();
|
||||
let root_parent = H256::random();
|
||||
let address = Address::random();
|
||||
let h0 = H256::random();
|
||||
let h1a = H256::random();
|
||||
let h1b = H256::random();
|
||||
let h2a = H256::random();
|
||||
let h2b = H256::random();
|
||||
let h3a = H256::random();
|
||||
let h3b = H256::random();
|
||||
let mut batch = DBTransaction::new(state_db.journal_db().backing());
|
||||
let mut state_db_result = get_temp_state_db();
|
||||
let state_db = state_db_result.take();
|
||||
let root_parent = H256::random();
|
||||
let address = Address::random();
|
||||
let h0 = H256::random();
|
||||
let h1a = H256::random();
|
||||
let h1b = H256::random();
|
||||
let h2a = H256::random();
|
||||
let h2b = H256::random();
|
||||
let h3a = H256::random();
|
||||
let h3b = H256::random();
|
||||
let mut batch = DBTransaction::new(state_db.journal_db().backing());
|
||||
|
||||
// blocks [ 3a(c) 2a(c) 2b 1b 1a(c) 0 ]
|
||||
// balance [ 5 5 4 3 2 2 ]
|
||||
let mut s = state_db.boxed_clone_canon(&root_parent);
|
||||
s.add_to_account_cache(address, Some(Account::new_basic(2.into(), 0.into())), false);
|
||||
s.commit(&mut batch, 0, &h0, None).unwrap();
|
||||
s.sync_cache(&[], &[], true);
|
||||
// blocks [ 3a(c) 2a(c) 2b 1b 1a(c) 0 ]
|
||||
// balance [ 5 5 4 3 2 2 ]
|
||||
let mut s = state_db.boxed_clone_canon(&root_parent);
|
||||
s.add_to_account_cache(address, Some(Account::new_basic(2.into(), 0.into())), false);
|
||||
s.journal_under(&mut batch, 0, &h0).unwrap();
|
||||
s.sync_cache(&[], &[], true);
|
||||
|
||||
let mut s = state_db.boxed_clone_canon(&h0);
|
||||
s.commit(&mut batch, 1, &h1a, None).unwrap();
|
||||
s.sync_cache(&[], &[], true);
|
||||
let mut s = state_db.boxed_clone_canon(&h0);
|
||||
s.journal_under(&mut batch, 1, &h1a).unwrap();
|
||||
s.sync_cache(&[], &[], true);
|
||||
|
||||
let mut s = state_db.boxed_clone_canon(&h0);
|
||||
s.add_to_account_cache(address, Some(Account::new_basic(3.into(), 0.into())), true);
|
||||
s.commit(&mut batch, 1, &h1b, None).unwrap();
|
||||
s.sync_cache(&[], &[], false);
|
||||
let mut s = state_db.boxed_clone_canon(&h0);
|
||||
s.add_to_account_cache(address, Some(Account::new_basic(3.into(), 0.into())), true);
|
||||
s.journal_under(&mut batch, 1, &h1b).unwrap();
|
||||
s.sync_cache(&[], &[], false);
|
||||
|
||||
let mut s = state_db.boxed_clone_canon(&h1b);
|
||||
s.add_to_account_cache(address, Some(Account::new_basic(4.into(), 0.into())), true);
|
||||
s.commit(&mut batch, 2, &h2b, None).unwrap();
|
||||
s.sync_cache(&[], &[], false);
|
||||
let mut s = state_db.boxed_clone_canon(&h1b);
|
||||
s.add_to_account_cache(address, Some(Account::new_basic(4.into(), 0.into())), true);
|
||||
s.journal_under(&mut batch, 2, &h2b).unwrap();
|
||||
s.sync_cache(&[], &[], false);
|
||||
|
||||
let mut s = state_db.boxed_clone_canon(&h1a);
|
||||
s.add_to_account_cache(address, Some(Account::new_basic(5.into(), 0.into())), true);
|
||||
s.commit(&mut batch, 2, &h2a, None).unwrap();
|
||||
s.sync_cache(&[], &[], true);
|
||||
let mut s = state_db.boxed_clone_canon(&h1a);
|
||||
s.add_to_account_cache(address, Some(Account::new_basic(5.into(), 0.into())), true);
|
||||
s.journal_under(&mut batch, 2, &h2a).unwrap();
|
||||
s.sync_cache(&[], &[], true);
|
||||
|
||||
let mut s = state_db.boxed_clone_canon(&h2a);
|
||||
s.commit(&mut batch, 3, &h3a, None).unwrap();
|
||||
s.sync_cache(&[], &[], true);
|
||||
let mut s = state_db.boxed_clone_canon(&h2a);
|
||||
s.journal_under(&mut batch, 3, &h3a).unwrap();
|
||||
s.sync_cache(&[], &[], true);
|
||||
|
||||
let s = state_db.boxed_clone_canon(&h3a);
|
||||
assert_eq!(s.get_cached_account(&address).unwrap().unwrap().balance(), &U256::from(5));
|
||||
let s = state_db.boxed_clone_canon(&h3a);
|
||||
assert_eq!(s.get_cached_account(&address).unwrap().unwrap().balance(), &U256::from(5));
|
||||
|
||||
let s = state_db.boxed_clone_canon(&h1a);
|
||||
assert!(s.get_cached_account(&address).is_none());
|
||||
let s = state_db.boxed_clone_canon(&h1a);
|
||||
assert!(s.get_cached_account(&address).is_none());
|
||||
|
||||
let s = state_db.boxed_clone_canon(&h2b);
|
||||
assert!(s.get_cached_account(&address).is_none());
|
||||
let s = state_db.boxed_clone_canon(&h2b);
|
||||
assert!(s.get_cached_account(&address).is_none());
|
||||
|
||||
let s = state_db.boxed_clone_canon(&h1b);
|
||||
assert!(s.get_cached_account(&address).is_none());
|
||||
let s = state_db.boxed_clone_canon(&h1b);
|
||||
assert!(s.get_cached_account(&address).is_none());
|
||||
|
||||
// reorg to 3b
|
||||
// blocks [ 3b(c) 3a 2a 2b(c) 1b 1a 0 ]
|
||||
let mut s = state_db.boxed_clone_canon(&h2b);
|
||||
s.commit(&mut batch, 3, &h3b, None).unwrap();
|
||||
s.sync_cache(&[h1b.clone(), h2b.clone(), h3b.clone()], &[h1a.clone(), h2a.clone(), h3a.clone()], true);
|
||||
let s = state_db.boxed_clone_canon(&h3a);
|
||||
assert!(s.get_cached_account(&address).is_none());
|
||||
}
|
||||
// reorg to 3b
|
||||
// blocks [ 3b(c) 3a 2a 2b(c) 1b 1a 0 ]
|
||||
let mut s = state_db.boxed_clone_canon(&h2b);
|
||||
s.journal_under(&mut batch, 3, &h3b).unwrap();
|
||||
s.sync_cache(&[h1b.clone(), h2b.clone(), h3b.clone()], &[h1a.clone(), h2a.clone(), h3a.clone()], true);
|
||||
let s = state_db.boxed_clone_canon(&h3a);
|
||||
assert!(s.get_cached_account(&address).is_none());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ use common::*;
|
||||
use devtools::*;
|
||||
use miner::Miner;
|
||||
use rlp::{Rlp, View};
|
||||
use spec::Spec;
|
||||
|
||||
#[test]
|
||||
fn imports_from_empty() {
|
||||
@@ -222,7 +223,7 @@ fn can_handle_long_fork() {
|
||||
push_blocks_to_client(client, 49, 1201, 800);
|
||||
push_blocks_to_client(client, 53, 1201, 600);
|
||||
|
||||
for _ in 0..40 {
|
||||
for _ in 0..400 {
|
||||
client.import_verified_blocks();
|
||||
}
|
||||
assert_eq!(2000, client.chain_info().best_block_number);
|
||||
@@ -238,3 +239,27 @@ fn can_mine() {
|
||||
|
||||
assert_eq!(*b.block().header().parent_hash(), BlockView::new(&dummy_blocks[0]).header_view().sha3());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn change_history_size() {
|
||||
let dir = RandomTempPath::new();
|
||||
let test_spec = Spec::new_null();
|
||||
let mut config = ClientConfig::default();
|
||||
let db_config = DatabaseConfig::with_columns(::db::NUM_COLUMNS);
|
||||
config.history = 2;
|
||||
let address = Address::random();
|
||||
{
|
||||
let client = Client::new(ClientConfig::default(), &test_spec, dir.as_path(), Arc::new(Miner::with_spec(&test_spec)), IoChannel::disconnected(), &db_config).unwrap();
|
||||
for _ in 0..20 {
|
||||
let mut b = client.prepare_open_block(Address::default(), (3141562.into(), 31415620.into()), vec![]);
|
||||
b.block_mut().fields_mut().state.add_balance(&address, &5.into());
|
||||
b.block_mut().fields_mut().state.commit().unwrap();
|
||||
let b = b.close_and_lock().seal(&*test_spec.engine, vec![]).unwrap();
|
||||
client.import_sealed_block(b).unwrap(); // account change is in the journal overlay
|
||||
}
|
||||
}
|
||||
let mut config = ClientConfig::default();
|
||||
config.history = 10;
|
||||
let client = Client::new(config, &test_spec, dir.as_path(), Arc::new(Miner::with_spec(&test_spec)), IoChannel::disconnected(), &db_config).unwrap();
|
||||
assert_eq!(client.state().balance(&address), 100.into());
|
||||
}
|
||||
|
||||
@@ -22,7 +22,6 @@ use spec::*;
|
||||
use state_db::StateDB;
|
||||
use block::{OpenBlock, Drain};
|
||||
use blockchain::{BlockChain, Config as BlockChainConfig};
|
||||
use state::State;
|
||||
use evm::Schedule;
|
||||
use engines::Engine;
|
||||
use ethereum;
|
||||
@@ -35,19 +34,20 @@ use db::COL_STATE;
|
||||
pub enum ChainEra {
|
||||
Frontier,
|
||||
Homestead,
|
||||
DaoHardfork,
|
||||
Eip150,
|
||||
TransitionTest,
|
||||
}
|
||||
|
||||
pub struct TestEngine {
|
||||
engine: Arc<Engine>,
|
||||
max_depth: usize
|
||||
max_depth: usize,
|
||||
}
|
||||
|
||||
impl TestEngine {
|
||||
pub fn new(max_depth: usize) -> TestEngine {
|
||||
TestEngine {
|
||||
engine: ethereum::new_frontier_test().engine,
|
||||
max_depth: max_depth
|
||||
max_depth: max_depth,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -347,7 +347,7 @@ pub fn get_temp_state() -> GuardedTempResult<State> {
|
||||
pub fn get_temp_state_db_in(path: &Path) -> StateDB {
|
||||
let db = new_db(path.to_str().expect("Only valid utf8 paths for tests."));
|
||||
let journal_db = journaldb::new(db.clone(), journaldb::Algorithm::EarlyMerge, COL_STATE);
|
||||
StateDB::new(journal_db)
|
||||
StateDB::new(journal_db, 5 * 1024 * 1024)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -129,7 +129,7 @@ impl<T> TraceDB<T> where T: DatabaseExtras {
|
||||
pub fn new(config: Config, tracesdb: Arc<Database>, extras: Arc<T>) -> Self {
|
||||
let mut batch = DBTransaction::new(&tracesdb);
|
||||
batch.put(db::COL_TRACE, b"version", TRACE_DB_VER);
|
||||
tracesdb.write(batch).unwrap();
|
||||
tracesdb.write(batch).expect("failed to update version");
|
||||
|
||||
TraceDB {
|
||||
traces: RwLock::new(HashMap::new()),
|
||||
@@ -275,16 +275,6 @@ impl<T> TraceDatabase for TraceDB<T> where T: DatabaseExtras {
|
||||
.map(Into::into)
|
||||
.collect();
|
||||
|
||||
// insert new block traces into the cache and the database
|
||||
{
|
||||
let mut traces = self.traces.write();
|
||||
// it's important to use overwrite here,
|
||||
// cause this value might be queried by hash later
|
||||
batch.write_with_cache(db::COL_TRACE, &mut *traces, request.block_hash, request.traces, CacheUpdatePolicy::Overwrite);
|
||||
// note_used must be called after locking traces to avoid cache/traces deadlock on garbage collection
|
||||
self.note_used(CacheID::Trace(request.block_hash.clone()));
|
||||
}
|
||||
|
||||
let chain = BloomGroupChain::new(self.bloom_config, self);
|
||||
let trace_blooms = chain.replace(&replaced_range, enacted_blooms);
|
||||
let blooms_to_insert = trace_blooms.into_iter()
|
||||
@@ -299,6 +289,16 @@ impl<T> TraceDatabase for TraceDB<T> where T: DatabaseExtras {
|
||||
self.note_used(CacheID::Bloom(key));
|
||||
}
|
||||
}
|
||||
|
||||
// insert new block traces into the cache and the database
|
||||
{
|
||||
let mut traces = self.traces.write();
|
||||
// it's important to use overwrite here,
|
||||
// cause this value might be queried by hash later
|
||||
batch.write_with_cache(db::COL_TRACE, &mut *traces, request.block_hash, request.traces, CacheUpdatePolicy::Overwrite);
|
||||
// note_used must be called after locking traces to avoid cache/traces deadlock on garbage collection
|
||||
self.note_used(CacheID::Trace(request.block_hash.clone()));
|
||||
}
|
||||
}
|
||||
|
||||
fn trace(&self, block_number: BlockNumber, tx_position: usize, trace_position: Vec<usize>) -> Option<LocalizedTrace> {
|
||||
@@ -504,6 +504,28 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
fn create_noncanon_import_request(block_number: BlockNumber, block_hash: H256) -> ImportRequest {
|
||||
ImportRequest {
|
||||
traces: FlatBlockTraces::from(vec![FlatTransactionTraces::from(vec![FlatTrace {
|
||||
trace_address: Default::default(),
|
||||
subtraces: 0,
|
||||
action: Action::Call(Call {
|
||||
from: 1.into(),
|
||||
to: 2.into(),
|
||||
value: 3.into(),
|
||||
gas: 4.into(),
|
||||
input: vec![],
|
||||
call_type: CallType::Call,
|
||||
}),
|
||||
result: Res::FailedCall(TraceError::OutOfGas),
|
||||
}])]),
|
||||
block_hash: block_hash.clone(),
|
||||
block_number: block_number,
|
||||
enacted: vec![],
|
||||
retracted: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn create_simple_localized_trace(block_number: BlockNumber, block_hash: H256, tx_hash: H256) -> LocalizedTrace {
|
||||
LocalizedTrace {
|
||||
action: Action::Call(Call {
|
||||
@@ -524,6 +546,34 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_import_non_canon_traces() {
|
||||
let temp = RandomTempPath::new();
|
||||
let db = Arc::new(Database::open(&DatabaseConfig::with_columns(::db::NUM_COLUMNS), temp.as_str()).unwrap());
|
||||
let mut config = Config::default();
|
||||
config.enabled = true;
|
||||
let block_0 = H256::from(0xa1);
|
||||
let block_1 = H256::from(0xa2);
|
||||
let tx_0 = H256::from(0xff);
|
||||
let tx_1 = H256::from(0xaf);
|
||||
|
||||
let mut extras = Extras::default();
|
||||
extras.block_hashes.insert(0, block_0.clone());
|
||||
extras.block_hashes.insert(1, block_1.clone());
|
||||
extras.transaction_hashes.insert(0, vec![tx_0.clone()]);
|
||||
extras.transaction_hashes.insert(1, vec![tx_1.clone()]);
|
||||
|
||||
let tracedb = TraceDB::new(config, db.clone(), Arc::new(extras));
|
||||
|
||||
// import block 0
|
||||
let request = create_noncanon_import_request(0, block_0.clone());
|
||||
let mut batch = DBTransaction::new(&db);
|
||||
tracedb.import(&mut batch, request);
|
||||
db.write(batch).unwrap();
|
||||
|
||||
assert!(tracedb.traces(&block_0).is_some(), "Traces should be available even if block is non-canon.");
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn test_import() {
|
||||
|
||||
@@ -31,5 +31,13 @@ pub struct BlockChainInfo {
|
||||
/// Best blockchain block hash.
|
||||
pub best_block_hash: H256,
|
||||
/// Best blockchain block number.
|
||||
pub best_block_number: BlockNumber
|
||||
pub best_block_number: BlockNumber,
|
||||
/// Best ancient block hash.
|
||||
pub ancient_block_hash: Option<H256>,
|
||||
/// Best ancient block number.
|
||||
pub ancient_block_number: Option<BlockNumber>,
|
||||
/// First block on the best sequence.
|
||||
pub first_block_hash: Option<H256>,
|
||||
/// Number of the first block on the best sequence.
|
||||
pub first_block_number: Option<BlockNumber>,
|
||||
}
|
||||
|
||||
@@ -23,6 +23,10 @@ pub enum RestorationStatus {
|
||||
Inactive,
|
||||
/// Ongoing restoration.
|
||||
Ongoing {
|
||||
/// Total number of state chunks.
|
||||
state_chunks: u32,
|
||||
/// Total number of block chunks.
|
||||
block_chunks: u32,
|
||||
/// Number of state chunks completed.
|
||||
state_chunks_done: u32,
|
||||
/// Number of block chunks completed.
|
||||
|
||||
@@ -108,7 +108,7 @@ impl Filter {
|
||||
|
||||
/// Returns true if given trace matches the filter.
|
||||
pub fn matches(&self, trace: &FlatTrace) -> bool {
|
||||
let action = match trace.action {
|
||||
match trace.action {
|
||||
Action::Call(ref call) => {
|
||||
let from_matches = self.from_address.matches(&call.from);
|
||||
let to_matches = self.to_address.matches(&call.to);
|
||||
@@ -116,7 +116,12 @@ impl Filter {
|
||||
}
|
||||
Action::Create(ref create) => {
|
||||
let from_matches = self.from_address.matches(&create.from);
|
||||
let to_matches = self.to_address.matches_all();
|
||||
|
||||
let to_matches = match trace.result {
|
||||
Res::Create(ref create_result) => self.to_address.matches(&create_result.address),
|
||||
_ => false
|
||||
};
|
||||
|
||||
from_matches && to_matches
|
||||
},
|
||||
Action::Suicide(ref suicide) => {
|
||||
@@ -124,11 +129,6 @@ impl Filter {
|
||||
let to_matches = self.to_address.matches(&suicide.refund_address);
|
||||
from_matches && to_matches
|
||||
}
|
||||
};
|
||||
|
||||
action || match trace.result {
|
||||
Res::Create(ref create) => self.to_address.matches(&create.address),
|
||||
_ => false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ use std::cell::*;
|
||||
use rlp::*;
|
||||
use util::sha3::Hashable;
|
||||
use util::{H256, Address, U256, Bytes, HeapSizeOf};
|
||||
use ethkey::{Signature, sign, Secret, Public, recover, public_to_address, Error as EthkeyError};
|
||||
use ethkey::{Signature, Secret, Public, recover, public_to_address, Error as EthkeyError};
|
||||
use error::*;
|
||||
use evm::Schedule;
|
||||
use header::BlockNumber;
|
||||
@@ -143,7 +143,8 @@ impl Transaction {
|
||||
|
||||
/// Signs the transaction as coming from `sender`.
|
||||
pub fn sign(self, secret: &Secret) -> SignedTransaction {
|
||||
let sig = sign(secret, &self.hash()).unwrap();
|
||||
let sig = ::ethkey::sign(secret, &self.hash())
|
||||
.expect("data is valid and context has signing capabilities; qed");
|
||||
self.with_signature(sig)
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
//! Sorts them ready for blockchain insertion.
|
||||
|
||||
use std::thread::{JoinHandle, self};
|
||||
use std::sync::atomic::{AtomicBool, Ordering as AtomicOrdering};
|
||||
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering as AtomicOrdering};
|
||||
use std::sync::{Condvar as SCondvar, Mutex as SMutex};
|
||||
use util::*;
|
||||
use io::*;
|
||||
@@ -83,6 +83,13 @@ pub enum Status {
|
||||
Unknown,
|
||||
}
|
||||
|
||||
// the internal queue sizes.
|
||||
struct Sizes {
|
||||
unverified: AtomicUsize,
|
||||
verifying: AtomicUsize,
|
||||
verified: AtomicUsize,
|
||||
}
|
||||
|
||||
/// A queue of items to be verified. Sits between network or other I/O and the `BlockChain`.
|
||||
/// Keeps them in the same order as inserted, minus invalid items.
|
||||
pub struct VerificationQueue<K: Kind> {
|
||||
@@ -107,7 +114,21 @@ struct QueueSignal {
|
||||
|
||||
impl QueueSignal {
|
||||
#[cfg_attr(feature="dev", allow(bool_comparison))]
|
||||
fn set(&self) {
|
||||
fn set_sync(&self) {
|
||||
// Do not signal when we are about to close
|
||||
if self.deleting.load(AtomicOrdering::Relaxed) {
|
||||
return;
|
||||
}
|
||||
|
||||
if self.signalled.compare_and_swap(false, true, AtomicOrdering::Relaxed) == false {
|
||||
if let Err(e) = self.message_channel.send_sync(ClientIoMessage::BlockVerified) {
|
||||
debug!("Error sending BlockVerified message: {:?}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature="dev", allow(bool_comparison))]
|
||||
fn set_async(&self) {
|
||||
// Do not signal when we are about to close
|
||||
if self.deleting.load(AtomicOrdering::Relaxed) {
|
||||
return;
|
||||
@@ -128,11 +149,12 @@ impl QueueSignal {
|
||||
struct Verification<K: Kind> {
|
||||
// All locks must be captured in the order declared here.
|
||||
unverified: Mutex<VecDeque<K::Unverified>>,
|
||||
verified: Mutex<VecDeque<K::Verified>>,
|
||||
verifying: Mutex<VecDeque<Verifying<K>>>,
|
||||
verified: Mutex<VecDeque<K::Verified>>,
|
||||
bad: Mutex<HashSet<H256>>,
|
||||
more_to_verify: SMutex<()>,
|
||||
empty: SMutex<()>,
|
||||
sizes: Sizes,
|
||||
}
|
||||
|
||||
impl<K: Kind> VerificationQueue<K> {
|
||||
@@ -140,12 +162,16 @@ impl<K: Kind> VerificationQueue<K> {
|
||||
pub fn new(config: Config, engine: Arc<Engine>, message_channel: IoChannel<ClientIoMessage>) -> Self {
|
||||
let verification = Arc::new(Verification {
|
||||
unverified: Mutex::new(VecDeque::new()),
|
||||
verified: Mutex::new(VecDeque::new()),
|
||||
verifying: Mutex::new(VecDeque::new()),
|
||||
verified: Mutex::new(VecDeque::new()),
|
||||
bad: Mutex::new(HashSet::new()),
|
||||
more_to_verify: SMutex::new(()),
|
||||
empty: SMutex::new(()),
|
||||
|
||||
sizes: Sizes {
|
||||
unverified: AtomicUsize::new(0),
|
||||
verifying: AtomicUsize::new(0),
|
||||
verified: AtomicUsize::new(0),
|
||||
}
|
||||
});
|
||||
let more_to_verify = Arc::new(SCondvar::new());
|
||||
let deleting = Arc::new(AtomicBool::new(false));
|
||||
@@ -221,18 +247,21 @@ impl<K: Kind> VerificationQueue<K> {
|
||||
None => continue,
|
||||
};
|
||||
|
||||
verification.sizes.unverified.fetch_sub(item.heap_size_of_children(), AtomicOrdering::SeqCst);
|
||||
verifying.push_back(Verifying { hash: item.hash(), output: None });
|
||||
item
|
||||
};
|
||||
|
||||
let hash = item.hash();
|
||||
match K::verify(item, &*engine) {
|
||||
let is_ready = match K::verify(item, &*engine) {
|
||||
Ok(verified) => {
|
||||
let mut verifying = verification.verifying.lock();
|
||||
let mut idx = None;
|
||||
for (i, e) in verifying.iter_mut().enumerate() {
|
||||
if e.hash == hash {
|
||||
idx = Some(i);
|
||||
|
||||
verification.sizes.verifying.fetch_add(verified.heap_size_of_children(), AtomicOrdering::SeqCst);
|
||||
e.output = Some(verified);
|
||||
break;
|
||||
}
|
||||
@@ -242,8 +271,10 @@ impl<K: Kind> VerificationQueue<K> {
|
||||
// we're next!
|
||||
let mut verified = verification.verified.lock();
|
||||
let mut bad = verification.bad.lock();
|
||||
VerificationQueue::drain_verifying(&mut verifying, &mut verified, &mut bad);
|
||||
ready.set();
|
||||
VerificationQueue::drain_verifying(&mut verifying, &mut verified, &mut bad, &verification.sizes);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
},
|
||||
Err(_) => {
|
||||
@@ -255,24 +286,38 @@ impl<K: Kind> VerificationQueue<K> {
|
||||
verifying.retain(|e| e.hash != hash);
|
||||
|
||||
if verifying.front().map_or(false, |x| x.output.is_some()) {
|
||||
VerificationQueue::drain_verifying(&mut verifying, &mut verified, &mut bad);
|
||||
ready.set();
|
||||
VerificationQueue::drain_verifying(&mut verifying, &mut verified, &mut bad, &verification.sizes);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
};
|
||||
if is_ready {
|
||||
// Import the block immediately
|
||||
ready.set_sync();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn drain_verifying(verifying: &mut VecDeque<Verifying<K>>, verified: &mut VecDeque<K::Verified>, bad: &mut HashSet<H256>) {
|
||||
fn drain_verifying(verifying: &mut VecDeque<Verifying<K>>, verified: &mut VecDeque<K::Verified>, bad: &mut HashSet<H256>, sizes: &Sizes) {
|
||||
let mut removed_size = 0;
|
||||
let mut inserted_size = 0;
|
||||
while let Some(output) = verifying.front_mut().and_then(|x| x.output.take()) {
|
||||
assert!(verifying.pop_front().is_some());
|
||||
let size = output.heap_size_of_children();
|
||||
removed_size += size;
|
||||
|
||||
if bad.contains(&output.parent_hash()) {
|
||||
bad.insert(output.hash());
|
||||
} else {
|
||||
inserted_size += size;
|
||||
verified.push_back(output);
|
||||
}
|
||||
}
|
||||
|
||||
sizes.verifying.fetch_sub(removed_size, AtomicOrdering::SeqCst);
|
||||
sizes.verified.fetch_add(inserted_size, AtomicOrdering::SeqCst);
|
||||
}
|
||||
|
||||
/// Clear the queue and stop verification activity.
|
||||
@@ -283,6 +328,12 @@ impl<K: Kind> VerificationQueue<K> {
|
||||
unverified.clear();
|
||||
verifying.clear();
|
||||
verified.clear();
|
||||
|
||||
let sizes = &self.verification.sizes;
|
||||
sizes.unverified.store(0, AtomicOrdering::Release);
|
||||
sizes.verifying.store(0, AtomicOrdering::Release);
|
||||
sizes.verified.store(0, AtomicOrdering::Release);
|
||||
|
||||
self.processing.write().clear();
|
||||
}
|
||||
|
||||
@@ -326,6 +377,8 @@ impl<K: Kind> VerificationQueue<K> {
|
||||
|
||||
match K::create(input, &*self.engine) {
|
||||
Ok(item) => {
|
||||
self.verification.sizes.unverified.fetch_add(item.heap_size_of_children(), AtomicOrdering::SeqCst);
|
||||
|
||||
self.processing.write().insert(h.clone());
|
||||
self.verification.unverified.lock().push_back(item);
|
||||
self.more_to_verify.notify_all();
|
||||
@@ -355,26 +408,32 @@ impl<K: Kind> VerificationQueue<K> {
|
||||
}
|
||||
|
||||
let mut new_verified = VecDeque::new();
|
||||
let mut removed_size = 0;
|
||||
for output in verified.drain(..) {
|
||||
if bad.contains(&output.parent_hash()) {
|
||||
removed_size += output.heap_size_of_children();
|
||||
bad.insert(output.hash());
|
||||
processing.remove(&output.hash());
|
||||
} else {
|
||||
new_verified.push_back(output);
|
||||
}
|
||||
}
|
||||
|
||||
self.verification.sizes.verified.fetch_sub(removed_size, AtomicOrdering::SeqCst);
|
||||
*verified = new_verified;
|
||||
}
|
||||
|
||||
/// Mark given item as processed
|
||||
pub fn mark_as_good(&self, hashes: &[H256]) {
|
||||
/// Mark given item as processed.
|
||||
/// Returns true if the queue becomes empty.
|
||||
pub fn mark_as_good(&self, hashes: &[H256]) -> bool {
|
||||
if hashes.is_empty() {
|
||||
return;
|
||||
return self.processing.read().is_empty();
|
||||
}
|
||||
let mut processing = self.processing.write();
|
||||
for hash in hashes {
|
||||
processing.remove(hash);
|
||||
}
|
||||
processing.is_empty()
|
||||
}
|
||||
|
||||
/// Removes up to `max` verified items from the queue
|
||||
@@ -383,26 +442,35 @@ impl<K: Kind> VerificationQueue<K> {
|
||||
let count = min(max, verified.len());
|
||||
let result = verified.drain(..count).collect::<Vec<_>>();
|
||||
|
||||
let drained_size = result.iter().map(HeapSizeOf::heap_size_of_children).fold(0, |a, c| a + c);
|
||||
self.verification.sizes.verified.fetch_sub(drained_size, AtomicOrdering::SeqCst);
|
||||
|
||||
self.ready_signal.reset();
|
||||
if !verified.is_empty() {
|
||||
self.ready_signal.set();
|
||||
self.ready_signal.set_async();
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
/// Get queue status.
|
||||
pub fn queue_info(&self) -> QueueInfo {
|
||||
use std::mem::size_of;
|
||||
|
||||
let (unverified_len, unverified_bytes) = {
|
||||
let v = self.verification.unverified.lock();
|
||||
(v.len(), v.heap_size_of_children())
|
||||
let len = self.verification.unverified.lock().len();
|
||||
let size = self.verification.sizes.unverified.load(AtomicOrdering::Acquire);
|
||||
|
||||
(len, size + len * size_of::<K::Unverified>())
|
||||
};
|
||||
let (verifying_len, verifying_bytes) = {
|
||||
let v = self.verification.verifying.lock();
|
||||
(v.len(), v.heap_size_of_children())
|
||||
let len = self.verification.verifying.lock().len();
|
||||
let size = self.verification.sizes.verifying.load(AtomicOrdering::Acquire);
|
||||
(len, size + len * size_of::<Verifying<K>>())
|
||||
};
|
||||
let (verified_len, verified_bytes) = {
|
||||
let v = self.verification.verified.lock();
|
||||
(v.len(), v.heap_size_of_children())
|
||||
let len = self.verification.verified.lock().len();
|
||||
let size = self.verification.sizes.verified.load(AtomicOrdering::Acquire);
|
||||
(len, size + len * size_of::<K::Verified>())
|
||||
};
|
||||
|
||||
QueueInfo {
|
||||
@@ -411,12 +479,9 @@ impl<K: Kind> VerificationQueue<K> {
|
||||
verified_queue_size: verified_len,
|
||||
max_queue_size: self.max_queue_size,
|
||||
max_mem_use: self.max_mem_use,
|
||||
mem_used:
|
||||
unverified_bytes
|
||||
+ verifying_bytes
|
||||
+ verified_bytes
|
||||
// TODO: https://github.com/servo/heapsize/pull/50
|
||||
//+ self.processing.read().heap_size_of_children(),
|
||||
mem_used: unverified_bytes
|
||||
+ verifying_bytes
|
||||
+ verified_bytes
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -108,7 +108,8 @@ pub fn verify_block_family(header: &Header, bytes: &[u8], engine: &Engine, bc: &
|
||||
match bc.block_details(&hash) {
|
||||
Some(details) => {
|
||||
excluded.insert(details.parent.clone());
|
||||
let b = bc.block(&hash).unwrap();
|
||||
let b = bc.block(&hash)
|
||||
.expect("parent already known to be stored; qed");
|
||||
excluded.extend(BlockView::new(&b).uncle_hashes());
|
||||
hash = details.parent;
|
||||
}
|
||||
@@ -296,7 +297,7 @@ mod tests {
|
||||
self.blocks.contains_key(hash)
|
||||
}
|
||||
|
||||
fn first_block(&self) -> H256 {
|
||||
fn first_block(&self) -> Option<H256> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
@@ -313,6 +314,10 @@ mod tests {
|
||||
self.block(hash).map(|b| BlockChain::block_to_body(&b))
|
||||
}
|
||||
|
||||
fn best_ancient_block(&self) -> Option<H256> {
|
||||
None
|
||||
}
|
||||
|
||||
/// Get the familial details concerning a block.
|
||||
fn block_details(&self, hash: &H256) -> Option<BlockDetails> {
|
||||
self.blocks.get(hash).map(|bytes| {
|
||||
|
||||
Reference in New Issue
Block a user