Merge branch 'master' into better-timeouts
This commit is contained in:
@@ -66,6 +66,9 @@ pub trait LightChainClient: Send + Sync {
|
||||
/// Clear the queue.
|
||||
fn clear_queue(&self);
|
||||
|
||||
/// Flush the queue.
|
||||
fn flush_queue(&self);
|
||||
|
||||
/// Get queue info.
|
||||
fn queue_info(&self) -> queue::QueueInfo;
|
||||
|
||||
@@ -130,7 +133,7 @@ impl Client {
|
||||
|
||||
BlockChainInfo {
|
||||
total_difficulty: best_block.total_difficulty,
|
||||
pending_total_difficulty: best_block.total_difficulty,
|
||||
pending_total_difficulty: best_block.total_difficulty + self.queue.total_difficulty(),
|
||||
genesis_hash: genesis_hash,
|
||||
best_block_hash: best_block.hash,
|
||||
best_block_number: best_block.number,
|
||||
@@ -151,6 +154,11 @@ impl Client {
|
||||
self.chain.get_header(id)
|
||||
}
|
||||
|
||||
/// Flush the header queue.
|
||||
pub fn flush_queue(&self) {
|
||||
self.queue.flush()
|
||||
}
|
||||
|
||||
/// Get the `i`th CHT root.
|
||||
pub fn cht_root(&self, i: usize) -> Option<H256> {
|
||||
self.chain.cht_root(i)
|
||||
@@ -211,6 +219,10 @@ impl LightChainClient for Client {
|
||||
self.queue.clear()
|
||||
}
|
||||
|
||||
fn flush_queue(&self) {
|
||||
Client::flush_queue(self);
|
||||
}
|
||||
|
||||
fn queue_info(&self) -> queue::QueueInfo {
|
||||
self.queue.queue_info()
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
//! This module provides an interface for configuration of buffer
|
||||
//! flow costs and recharge rates.
|
||||
//!
|
||||
//! Current default costs are picked completely arbitrarily, not based
|
||||
//! Current default costs are picked completely arbitrarily, not based
|
||||
//! on any empirical timings or mathematical models.
|
||||
|
||||
use request;
|
||||
@@ -184,6 +184,23 @@ impl FlowParams {
|
||||
}
|
||||
}
|
||||
|
||||
/// Create effectively infinite flow params.
|
||||
pub fn free() -> Self {
|
||||
let free_cost = Cost(0.into(), 0.into());
|
||||
FlowParams {
|
||||
limit: (!0u64).into(),
|
||||
recharge: 1.into(),
|
||||
costs: CostTable {
|
||||
headers: free_cost.clone(),
|
||||
bodies: free_cost.clone(),
|
||||
receipts: free_cost.clone(),
|
||||
state_proofs: free_cost.clone(),
|
||||
contract_codes: free_cost.clone(),
|
||||
header_proofs: free_cost.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a reference to the buffer limit.
|
||||
pub fn limit(&self) -> &U256 { &self.limit }
|
||||
|
||||
@@ -209,7 +226,7 @@ impl FlowParams {
|
||||
cost.0 + (amount * cost.1)
|
||||
}
|
||||
|
||||
/// Compute the maximum number of costs of a specific kind which can be made
|
||||
/// Compute the maximum number of costs of a specific kind which can be made
|
||||
/// with the given buffer.
|
||||
/// Saturates at `usize::max()`. This is not a problem in practice because
|
||||
/// this amount of requests is already prohibitively large.
|
||||
@@ -317,4 +334,4 @@ mod tests {
|
||||
|
||||
assert_eq!(buffer.estimate, 100.into());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,7 +42,6 @@ use self::error::Punishment;
|
||||
use self::request_set::RequestSet;
|
||||
use self::id_guard::IdGuard;
|
||||
|
||||
mod buffer_flow;
|
||||
mod context;
|
||||
mod error;
|
||||
mod status;
|
||||
@@ -51,6 +50,8 @@ mod request_set;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
pub mod buffer_flow;
|
||||
|
||||
pub use self::error::Error;
|
||||
pub use self::context::{BasicContext, EventContext, IoContext};
|
||||
pub use self::status::{Status, Capabilities, Announcement};
|
||||
@@ -285,7 +286,7 @@ pub struct LightProtocol {
|
||||
peers: RwLock<PeerMap>,
|
||||
capabilities: RwLock<Capabilities>,
|
||||
flow_params: FlowParams, // assumed static and same for every peer.
|
||||
handlers: Vec<Box<Handler>>,
|
||||
handlers: Vec<Arc<Handler>>,
|
||||
req_id: AtomicUsize,
|
||||
}
|
||||
|
||||
@@ -410,11 +411,11 @@ impl LightProtocol {
|
||||
}
|
||||
|
||||
/// Add an event handler.
|
||||
/// Ownership will be transferred to the protocol structure,
|
||||
/// and the handler will be kept alive as long as it is.
|
||||
///
|
||||
/// These are intended to be added when the protocol structure
|
||||
/// is initialized as a means of customizing its behavior.
|
||||
pub fn add_handler(&mut self, handler: Box<Handler>) {
|
||||
/// is initialized as a means of customizing its behavior,
|
||||
/// and dispatching requests immediately upon events.
|
||||
pub fn add_handler(&mut self, handler: Arc<Handler>) {
|
||||
self.handlers.push(handler);
|
||||
}
|
||||
|
||||
@@ -484,8 +485,10 @@ impl LightProtocol {
|
||||
}
|
||||
}
|
||||
|
||||
// handle a packet using the given io context.
|
||||
fn handle_packet(&self, io: &IoContext, peer: &PeerId, packet_id: u8, data: &[u8]) {
|
||||
/// Handle an LES packet using the given io context.
|
||||
/// Packet data is _untrusted_, which means that invalid data won't lead to
|
||||
/// issues.
|
||||
pub fn handle_packet(&self, io: &IoContext, peer: &PeerId, packet_id: u8, data: &[u8]) {
|
||||
let rlp = UntrustedRlp::new(data);
|
||||
|
||||
trace!(target: "les", "Incoming packet {} from peer {}", packet_id, peer);
|
||||
@@ -557,19 +560,8 @@ impl LightProtocol {
|
||||
}
|
||||
}
|
||||
|
||||
fn tick_handlers(&self, io: &IoContext) {
|
||||
for handler in &self.handlers {
|
||||
handler.tick(&TickCtx {
|
||||
io: io,
|
||||
proto: self,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl LightProtocol {
|
||||
// called when a peer connects.
|
||||
fn on_connect(&self, peer: &PeerId, io: &IoContext) {
|
||||
/// called when a peer connects.
|
||||
pub fn on_connect(&self, peer: &PeerId, io: &IoContext) {
|
||||
let proto_version = match io.protocol_version(*peer).ok_or(Error::WrongNetwork) {
|
||||
Ok(pv) => pv,
|
||||
Err(e) => { punish(*peer, io, e); return }
|
||||
@@ -603,8 +595,8 @@ impl LightProtocol {
|
||||
io.send(*peer, packet::STATUS, status_packet);
|
||||
}
|
||||
|
||||
// called when a peer disconnects.
|
||||
fn on_disconnect(&self, peer: PeerId, io: &IoContext) {
|
||||
/// called when a peer disconnects.
|
||||
pub fn on_disconnect(&self, peer: PeerId, io: &IoContext) {
|
||||
trace!(target: "les", "Peer {} disconnecting", peer);
|
||||
|
||||
self.pending_peers.write().remove(&peer);
|
||||
@@ -623,6 +615,27 @@ impl LightProtocol {
|
||||
}
|
||||
}
|
||||
|
||||
/// Execute the given closure with a basic context derived from the I/O context.
|
||||
pub fn with_context<F, T>(&self, io: &IoContext, f: F) -> T
|
||||
where F: FnOnce(&BasicContext) -> T
|
||||
{
|
||||
f(&TickCtx {
|
||||
io: io,
|
||||
proto: self,
|
||||
})
|
||||
}
|
||||
|
||||
fn tick_handlers(&self, io: &IoContext) {
|
||||
for handler in &self.handlers {
|
||||
handler.tick(&TickCtx {
|
||||
io: io,
|
||||
proto: self,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl LightProtocol {
|
||||
// Handle status message from peer.
|
||||
fn status(&self, peer: &PeerId, io: &IoContext, data: UntrustedRlp) -> Result<(), Error> {
|
||||
let pending = match self.pending_peers.write().remove(peer) {
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
//! These don't test of the higher level logic on top of
|
||||
|
||||
use ethcore::blockchain_info::BlockChainInfo;
|
||||
use ethcore::client::{BlockChainClient, EachBlockWith, TestBlockChainClient};
|
||||
use ethcore::client::{EachBlockWith, TestBlockChainClient};
|
||||
use ethcore::ids::BlockId;
|
||||
use ethcore::transaction::PendingTransaction;
|
||||
use ethcore::encoded;
|
||||
@@ -88,7 +88,7 @@ impl Provider for TestProvider {
|
||||
}
|
||||
|
||||
fn reorg_depth(&self, a: &H256, b: &H256) -> Option<u64> {
|
||||
self.0.client.tree_route(a, b).map(|route| route.index as u64)
|
||||
self.0.client.reorg_depth(a, b)
|
||||
}
|
||||
|
||||
fn earliest_state(&self) -> Option<u64> {
|
||||
@@ -305,7 +305,9 @@ fn get_block_bodies() {
|
||||
}
|
||||
|
||||
let request = request::Bodies {
|
||||
block_hashes: (0..10).map(|i| provider.client.block_hash(BlockId::Number(i)).unwrap()).collect(),
|
||||
block_hashes: (0..10).map(|i|
|
||||
provider.client.block_header(BlockId::Number(i)).unwrap().hash()
|
||||
).collect()
|
||||
};
|
||||
|
||||
let req_id = 111;
|
||||
@@ -353,8 +355,9 @@ fn get_block_receipts() {
|
||||
|
||||
// find the first 10 block hashes starting with `f` because receipts are only provided
|
||||
// by the test client in that case.
|
||||
let block_hashes: Vec<_> = (0..1000).map(|i| provider.client.block_hash(BlockId::Number(i)).unwrap())
|
||||
.filter(|hash| format!("{}", hash).starts_with("f")).take(10).collect();
|
||||
let block_hashes: Vec<_> = (0..1000).map(|i|
|
||||
provider.client.block_header(BlockId::Number(i)).unwrap().hash()
|
||||
).filter(|hash| format!("{}", hash).starts_with("f")).take(10).collect();
|
||||
|
||||
let request = request::Receipts {
|
||||
block_hashes: block_hashes.clone(),
|
||||
|
||||
@@ -83,7 +83,7 @@ pub trait Provider: Send + Sync {
|
||||
|
||||
(0u64..req.max as u64)
|
||||
.map(|x: u64| x.saturating_mul(req.skip + 1))
|
||||
.take_while(|x| if req.reverse { x < &start_num } else { best_num - start_num >= *x })
|
||||
.take_while(|x| if req.reverse { x < &start_num } else { best_num.saturating_sub(start_num) >= *x })
|
||||
.map(|x| if req.reverse { start_num - x } else { start_num + x })
|
||||
.map(|x| self.block_header(BlockId::Number(x)))
|
||||
.take_while(|x| x.is_some())
|
||||
|
||||
@@ -1329,6 +1329,7 @@ mod tests {
|
||||
use transaction::{Transaction, Action};
|
||||
use log_entry::{LogEntry, LocalizedLogEntry};
|
||||
use spec::Spec;
|
||||
use ethkey::Secret;
|
||||
|
||||
fn new_db(path: &str) -> Arc<Database> {
|
||||
Arc::new(Database::open(&DatabaseConfig::with_columns(::db::NUM_COLUMNS), path).unwrap())
|
||||
@@ -1467,6 +1468,10 @@ mod tests {
|
||||
// TODO: insert block that already includes one of them as an uncle to check it's not allowed.
|
||||
}
|
||||
|
||||
fn secret() -> Secret {
|
||||
Secret::from_slice(&"".sha3()).unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fork_transaction_addresses() {
|
||||
let mut canon_chain = ChainGenerator::default();
|
||||
@@ -1482,7 +1487,7 @@ mod tests {
|
||||
action: Action::Create,
|
||||
value: 100.into(),
|
||||
data: "601080600c6000396000f3006000355415600957005b60203560003555".from_hex().unwrap(),
|
||||
}.sign(&"".sha3(), None);
|
||||
}.sign(&secret(), None);
|
||||
|
||||
|
||||
let b1a = canon_chain
|
||||
@@ -1546,7 +1551,7 @@ mod tests {
|
||||
action: Action::Create,
|
||||
value: 100.into(),
|
||||
data: "601080600c6000396000f3006000355415600957005b60203560003555".from_hex().unwrap(),
|
||||
}.sign(&"".sha3(), None);
|
||||
}.sign(&secret(), None);
|
||||
|
||||
let t2 = Transaction {
|
||||
nonce: 1.into(),
|
||||
@@ -1555,7 +1560,7 @@ mod tests {
|
||||
action: Action::Create,
|
||||
value: 100.into(),
|
||||
data: "601080600c6000396000f3006000355415600957005b60203560003555".from_hex().unwrap(),
|
||||
}.sign(&"".sha3(), None);
|
||||
}.sign(&secret(), None);
|
||||
|
||||
let t3 = Transaction {
|
||||
nonce: 2.into(),
|
||||
@@ -1564,7 +1569,7 @@ mod tests {
|
||||
action: Action::Create,
|
||||
value: 100.into(),
|
||||
data: "601080600c6000396000f3006000355415600957005b60203560003555".from_hex().unwrap(),
|
||||
}.sign(&"".sha3(), None);
|
||||
}.sign(&secret(), None);
|
||||
|
||||
let b1a = canon_chain
|
||||
.with_transaction(t1.clone())
|
||||
@@ -1870,7 +1875,7 @@ mod tests {
|
||||
action: Action::Create,
|
||||
value: 101.into(),
|
||||
data: "601080600c6000396000f3006000355415600957005b60203560003555".from_hex().unwrap(),
|
||||
}.sign(&"".sha3(), None);
|
||||
}.sign(&secret(), None);
|
||||
let t2 = Transaction {
|
||||
nonce: 0.into(),
|
||||
gas_price: 0.into(),
|
||||
@@ -1878,7 +1883,7 @@ mod tests {
|
||||
action: Action::Create,
|
||||
value: 102.into(),
|
||||
data: "601080600c6000396000f3006000355415600957005b60203560003555".from_hex().unwrap(),
|
||||
}.sign(&"".sha3(), None);
|
||||
}.sign(&secret(), None);
|
||||
let t3 = Transaction {
|
||||
nonce: 0.into(),
|
||||
gas_price: 0.into(),
|
||||
@@ -1886,7 +1891,7 @@ mod tests {
|
||||
action: Action::Create,
|
||||
value: 103.into(),
|
||||
data: "601080600c6000396000f3006000355415600957005b60203560003555".from_hex().unwrap(),
|
||||
}.sign(&"".sha3(), None);
|
||||
}.sign(&secret(), None);
|
||||
let tx_hash1 = t1.hash();
|
||||
let tx_hash2 = t2.hash();
|
||||
let tx_hash3 = t3.hash();
|
||||
|
||||
@@ -848,7 +848,7 @@ impl BlockChainClient for Client {
|
||||
difficulty: header.difficulty(),
|
||||
last_hashes: last_hashes,
|
||||
gas_used: U256::zero(),
|
||||
gas_limit: header.gas_limit(),
|
||||
gas_limit: U256::max_value(),
|
||||
};
|
||||
// that's just a copy of the state.
|
||||
let mut state = self.state_at(block).ok_or(CallError::StatePruned)?;
|
||||
@@ -874,6 +874,7 @@ impl BlockChainClient for Client {
|
||||
}
|
||||
|
||||
fn estimate_gas(&self, t: &SignedTransaction, block: BlockId) -> Result<U256, CallError> {
|
||||
const UPPER_CEILING: u64 = 1_000_000_000_000u64;
|
||||
let header = self.block_header(block).ok_or(CallError::StatePruned)?;
|
||||
let last_hashes = self.build_last_hashes(header.parent_hash());
|
||||
let env_info = EnvInfo {
|
||||
@@ -883,37 +884,38 @@ impl BlockChainClient for Client {
|
||||
difficulty: header.difficulty(),
|
||||
last_hashes: last_hashes,
|
||||
gas_used: U256::zero(),
|
||||
gas_limit: header.gas_limit(),
|
||||
gas_limit: UPPER_CEILING.into(),
|
||||
};
|
||||
// that's just a copy of the state.
|
||||
let mut original_state = self.state_at(block).ok_or(CallError::StatePruned)?;
|
||||
let original_state = self.state_at(block).ok_or(CallError::StatePruned)?;
|
||||
let sender = t.sender().map_err(|e| {
|
||||
let message = format!("Transaction malformed: {:?}", e);
|
||||
ExecutionError::TransactionMalformed(message)
|
||||
})?;
|
||||
let balance = original_state.balance(&sender);
|
||||
let needed_balance = t.value + t.gas * t.gas_price;
|
||||
if balance < needed_balance {
|
||||
// give the sender a sufficient balance
|
||||
original_state.add_balance(&sender, &(needed_balance - balance), CleanupMode::NoEmpty);
|
||||
}
|
||||
let options = TransactOptions { tracing: true, vm_tracing: false, check_nonce: false };
|
||||
let mut tx = t.clone();
|
||||
|
||||
let mut cond = |gas| {
|
||||
let mut state = original_state.clone();
|
||||
tx.gas = gas;
|
||||
|
||||
let mut state = original_state.clone();
|
||||
let needed_balance = tx.value + tx.gas * tx.gas_price;
|
||||
if balance < needed_balance {
|
||||
// give the sender a sufficient balance
|
||||
state.add_balance(&sender, &(needed_balance - balance), CleanupMode::NoEmpty);
|
||||
}
|
||||
|
||||
Executive::new(&mut state, &env_info, &*self.engine, &self.factories.vm)
|
||||
.transact(&tx, options.clone())
|
||||
.map(|r| r.trace[0].result.succeeded())
|
||||
.unwrap_or(false)
|
||||
};
|
||||
|
||||
let mut upper = env_info.gas_limit;
|
||||
let mut upper = header.gas_limit();
|
||||
if !cond(upper) {
|
||||
// impossible at block gas limit - try `UPPER_CEILING` instead.
|
||||
// TODO: consider raising limit by powers of two.
|
||||
const UPPER_CEILING: u64 = 1_000_000_000_000u64;
|
||||
upper = UPPER_CEILING.into();
|
||||
if !cond(upper) {
|
||||
trace!(target: "estimate_gas", "estimate_gas failed with {}", upper);
|
||||
@@ -1669,7 +1671,7 @@ mod tests {
|
||||
use util::Hashable;
|
||||
|
||||
// given
|
||||
let key = KeyPair::from_secret("test".sha3()).unwrap();
|
||||
let key = KeyPair::from_secret_slice(&"test".sha3()).unwrap();
|
||||
let secret = key.secret();
|
||||
|
||||
let block_number = 1;
|
||||
|
||||
@@ -26,6 +26,7 @@ use blockchain::TreeRoute;
|
||||
use client::{
|
||||
BlockChainClient, MiningBlockChainClient, EngineClient, BlockChainInfo, BlockStatus, BlockId,
|
||||
TransactionId, UncleId, TraceId, TraceFilter, LastHashes, CallAnalytics, BlockImportError,
|
||||
ProvingBlockChainClient,
|
||||
};
|
||||
use db::{NUM_COLUMNS, COL_STATE};
|
||||
use header::{Header as BlockHeader, BlockNumber};
|
||||
@@ -134,6 +135,9 @@ impl TestBlockChainClient {
|
||||
|
||||
/// Create test client with custom spec and extra data.
|
||||
pub fn new_with_spec_and_extra(spec: Spec, extra_data: Bytes) -> Self {
|
||||
let genesis_block = spec.genesis_block();
|
||||
let genesis_hash = spec.genesis_header().hash();
|
||||
|
||||
let mut client = TestBlockChainClient {
|
||||
blocks: RwLock::new(HashMap::new()),
|
||||
numbers: RwLock::new(HashMap::new()),
|
||||
@@ -158,8 +162,12 @@ impl TestBlockChainClient {
|
||||
traces: RwLock::new(None),
|
||||
history: RwLock::new(None),
|
||||
};
|
||||
client.add_blocks(1, EachBlockWith::Nothing); // add genesis block
|
||||
client.genesis_hash = client.last_hash.read().clone();
|
||||
|
||||
// insert genesis hash.
|
||||
client.blocks.get_mut().insert(genesis_hash, genesis_block);
|
||||
client.numbers.get_mut().insert(0, genesis_hash);
|
||||
*client.last_hash.get_mut() = genesis_hash;
|
||||
client.genesis_hash = genesis_hash;
|
||||
client
|
||||
}
|
||||
|
||||
@@ -720,6 +728,20 @@ impl BlockChainClient for TestBlockChainClient {
|
||||
fn registry_address(&self, _name: String) -> Option<Address> { None }
|
||||
}
|
||||
|
||||
impl ProvingBlockChainClient for TestBlockChainClient {
|
||||
fn prove_storage(&self, _: H256, _: H256, _: u32, _: BlockId) -> Vec<Bytes> {
|
||||
Vec::new()
|
||||
}
|
||||
|
||||
fn prove_account(&self, _: H256, _: u32, _: BlockId) -> Vec<Bytes> {
|
||||
Vec::new()
|
||||
}
|
||||
|
||||
fn code_by_hash(&self, _: H256, _: BlockId) -> Bytes {
|
||||
Vec::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl EngineClient for TestBlockChainClient {
|
||||
fn update_sealing(&self) {
|
||||
self.miner.update_sealing(self)
|
||||
|
||||
@@ -354,6 +354,7 @@ mod tests {
|
||||
use env_info::EnvInfo;
|
||||
use header::Header;
|
||||
use error::{Error, BlockError};
|
||||
use ethkey::Secret;
|
||||
use rlp::encode;
|
||||
use block::*;
|
||||
use tests::helpers::*;
|
||||
@@ -411,8 +412,8 @@ mod tests {
|
||||
#[test]
|
||||
fn generates_seal_and_does_not_double_propose() {
|
||||
let tap = AccountProvider::transient_provider();
|
||||
let addr1 = tap.insert_account("1".sha3(), "1").unwrap();
|
||||
let addr2 = tap.insert_account("2".sha3(), "2").unwrap();
|
||||
let addr1 = tap.insert_account(Secret::from_slice(&"1".sha3()).unwrap(), "1").unwrap();
|
||||
let addr2 = tap.insert_account(Secret::from_slice(&"2".sha3()).unwrap(), "2").unwrap();
|
||||
|
||||
let spec = Spec::new_test_round();
|
||||
let engine = &*spec.engine;
|
||||
@@ -445,7 +446,7 @@ mod tests {
|
||||
fn proposer_switching() {
|
||||
let mut header: Header = Header::default();
|
||||
let tap = AccountProvider::transient_provider();
|
||||
let addr = tap.insert_account("0".sha3(), "0").unwrap();
|
||||
let addr = tap.insert_account(Secret::from_slice(&"0".sha3()).unwrap(), "0").unwrap();
|
||||
|
||||
header.set_author(addr);
|
||||
|
||||
@@ -464,7 +465,7 @@ mod tests {
|
||||
fn rejects_future_block() {
|
||||
let mut header: Header = Header::default();
|
||||
let tap = AccountProvider::transient_provider();
|
||||
let addr = tap.insert_account("0".sha3(), "0").unwrap();
|
||||
let addr = tap.insert_account(Secret::from_slice(&"0".sha3()).unwrap(), "0").unwrap();
|
||||
|
||||
header.set_author(addr);
|
||||
|
||||
|
||||
@@ -201,6 +201,7 @@ mod tests {
|
||||
use error::{BlockError, Error};
|
||||
use tests::helpers::*;
|
||||
use account_provider::AccountProvider;
|
||||
use ethkey::Secret;
|
||||
use header::Header;
|
||||
use spec::Spec;
|
||||
use engines::Seal;
|
||||
@@ -261,7 +262,7 @@ mod tests {
|
||||
#[test]
|
||||
fn can_generate_seal() {
|
||||
let tap = AccountProvider::transient_provider();
|
||||
let addr = tap.insert_account("".sha3(), "").unwrap();
|
||||
let addr = tap.insert_account(Secret::from_slice(&"".sha3()).unwrap(), "").unwrap();
|
||||
|
||||
let spec = new_test_authority();
|
||||
let engine = &*spec.engine;
|
||||
@@ -281,7 +282,7 @@ mod tests {
|
||||
#[test]
|
||||
fn seals_internally() {
|
||||
let tap = AccountProvider::transient_provider();
|
||||
let authority = tap.insert_account("".sha3(), "").unwrap();
|
||||
let authority = tap.insert_account(Secret::from_slice(&"".sha3()).unwrap(), "").unwrap();
|
||||
|
||||
let engine = new_test_authority().engine;
|
||||
assert!(!engine.is_sealer(&Address::default()).unwrap());
|
||||
|
||||
@@ -23,15 +23,35 @@ use header::Header;
|
||||
use rlp::*;
|
||||
use ethkey::{recover, public_to_address};
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
/// Message transmitted between consensus participants.
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
|
||||
pub struct ConsensusMessage {
|
||||
pub vote_step: VoteStep,
|
||||
pub block_hash: Option<BlockHash>,
|
||||
pub signature: H520,
|
||||
}
|
||||
|
||||
/// Complete step of the consensus process.
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
|
||||
pub struct VoteStep {
|
||||
pub height: Height,
|
||||
pub round: Round,
|
||||
pub step: Step,
|
||||
pub block_hash: Option<BlockHash>,
|
||||
}
|
||||
|
||||
impl VoteStep {
|
||||
pub fn new(height: Height, round: Round, step: Step) -> Self {
|
||||
VoteStep { height: height, round: round, step: step }
|
||||
}
|
||||
|
||||
pub fn is_height(&self, height: Height) -> bool {
|
||||
self.height == height
|
||||
}
|
||||
|
||||
pub fn is_round(&self, height: Height, round: Round) -> bool {
|
||||
self.height == height && self.round == round
|
||||
}
|
||||
}
|
||||
|
||||
fn consensus_round(header: &Header) -> Result<Round, ::rlp::DecoderError> {
|
||||
let round_rlp = header.seal().get(0).expect("seal passed basic verification; seal has 3 fields; qed");
|
||||
@@ -42,53 +62,29 @@ impl ConsensusMessage {
|
||||
pub fn new(signature: H520, height: Height, round: Round, step: Step, block_hash: Option<BlockHash>) -> Self {
|
||||
ConsensusMessage {
|
||||
signature: signature,
|
||||
height: height,
|
||||
round: round,
|
||||
step: step,
|
||||
block_hash: block_hash,
|
||||
vote_step: VoteStep::new(height, round, step),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_proposal(header: &Header) -> Result<Self, ::rlp::DecoderError> {
|
||||
Ok(ConsensusMessage {
|
||||
vote_step: VoteStep::new(header.number() as Height, consensus_round(header)?, Step::Propose),
|
||||
signature: UntrustedRlp::new(header.seal().get(1).expect("seal passed basic verification; seal has 3 fields; qed").as_slice()).as_val()?,
|
||||
height: header.number() as Height,
|
||||
round: consensus_round(header)?,
|
||||
step: Step::Propose,
|
||||
block_hash: Some(header.bare_hash()),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn new_commit(proposal: &ConsensusMessage, signature: H520) -> Self {
|
||||
let mut vote_step = proposal.vote_step.clone();
|
||||
vote_step.step = Step::Precommit;
|
||||
ConsensusMessage {
|
||||
signature: signature,
|
||||
height: proposal.height,
|
||||
round: proposal.round,
|
||||
step: Step::Precommit,
|
||||
vote_step: vote_step,
|
||||
block_hash: proposal.block_hash,
|
||||
signature: signature,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_height(&self, height: Height) -> bool {
|
||||
self.height == height
|
||||
}
|
||||
|
||||
pub fn is_round(&self, height: Height, round: Round) -> bool {
|
||||
self.height == height && self.round == round
|
||||
}
|
||||
|
||||
pub fn is_step(&self, height: Height, round: Round, step: Step) -> bool {
|
||||
self.height == height && self.round == round && self.step == step
|
||||
}
|
||||
|
||||
pub fn is_block_hash(&self, h: Height, r: Round, s: Step, block_hash: Option<BlockHash>) -> bool {
|
||||
self.height == h && self.round == r && self.step == s && self.block_hash == block_hash
|
||||
}
|
||||
|
||||
pub fn is_aligned(&self, m: &ConsensusMessage) -> bool {
|
||||
self.is_block_hash(m.height, m.round, m.step, m.block_hash)
|
||||
}
|
||||
|
||||
pub fn verify(&self) -> Result<Address, Error> {
|
||||
let full_rlp = ::rlp::encode(self);
|
||||
let block_info = Rlp::new(&full_rlp).at(1);
|
||||
@@ -97,16 +93,30 @@ impl ConsensusMessage {
|
||||
}
|
||||
|
||||
pub fn precommit_hash(&self) -> H256 {
|
||||
message_info_rlp(self.height, self.round, Step::Precommit, self.block_hash).sha3()
|
||||
let mut vote_step = self.vote_step.clone();
|
||||
vote_step.step = Step::Precommit;
|
||||
message_info_rlp(&vote_step, self.block_hash).sha3()
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for ConsensusMessage {
|
||||
fn partial_cmp(&self, m: &ConsensusMessage) -> Option<Ordering> {
|
||||
impl PartialOrd for VoteStep {
|
||||
fn partial_cmp(&self, m: &VoteStep) -> Option<Ordering> {
|
||||
Some(self.cmp(m))
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for VoteStep {
|
||||
fn cmp(&self, m: &VoteStep) -> Ordering {
|
||||
if self.height != m.height {
|
||||
self.height.cmp(&m.height)
|
||||
} else if self.round != m.round {
|
||||
self.round.cmp(&m.round)
|
||||
} else {
|
||||
self.step.number().cmp(&m.step.number())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Step {
|
||||
fn number(&self) -> u8 {
|
||||
match *self {
|
||||
@@ -118,20 +128,6 @@ impl Step {
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for ConsensusMessage {
|
||||
fn cmp(&self, m: &ConsensusMessage) -> Ordering {
|
||||
if self.height != m.height {
|
||||
self.height.cmp(&m.height)
|
||||
} else if self.round != m.round {
|
||||
self.round.cmp(&m.round)
|
||||
} else if self.step != m.step {
|
||||
self.step.number().cmp(&m.step.number())
|
||||
} else {
|
||||
self.signature.cmp(&m.signature)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Decodable for Step {
|
||||
fn decode<D>(decoder: &D) -> Result<Self, DecoderError> where D: Decoder {
|
||||
match decoder.as_rlp().as_val()? {
|
||||
@@ -149,42 +145,39 @@ impl Encodable for Step {
|
||||
}
|
||||
}
|
||||
|
||||
/// (signature, height, round, step, block_hash)
|
||||
/// (signature, (height, round, step, block_hash))
|
||||
impl Decodable for ConsensusMessage {
|
||||
fn decode<D>(decoder: &D) -> Result<Self, DecoderError> where D: Decoder {
|
||||
let rlp = decoder.as_rlp();
|
||||
let m = rlp.at(1)?;
|
||||
let block_message: H256 = m.val_at(3)?;
|
||||
Ok(ConsensusMessage {
|
||||
signature: rlp.val_at(0)?,
|
||||
height: m.val_at(0)?,
|
||||
round: m.val_at(1)?,
|
||||
step: m.val_at(2)?,
|
||||
vote_step: VoteStep::new(m.val_at(0)?, m.val_at(1)?, m.val_at(2)?),
|
||||
block_hash: match block_message.is_zero() {
|
||||
true => None,
|
||||
false => Some(block_message),
|
||||
}
|
||||
},
|
||||
signature: rlp.val_at(0)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Encodable for ConsensusMessage {
|
||||
fn rlp_append(&self, s: &mut RlpStream) {
|
||||
let info = message_info_rlp(self.height, self.round, self.step, self.block_hash);
|
||||
let info = message_info_rlp(&self.vote_step, self.block_hash);
|
||||
s.begin_list(2)
|
||||
.append(&self.signature)
|
||||
.append_raw(&info, 1);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn message_info_rlp(height: Height, round: Round, step: Step, block_hash: Option<BlockHash>) -> Bytes {
|
||||
pub fn message_info_rlp(vote_step: &VoteStep, block_hash: Option<BlockHash>) -> Bytes {
|
||||
// TODO: figure out whats wrong with nested list encoding
|
||||
let mut s = RlpStream::new_list(5);
|
||||
s.append(&height).append(&round).append(&step).append(&block_hash.unwrap_or_else(H256::zero));
|
||||
s.append(&vote_step.height).append(&vote_step.round).append(&vote_step.step).append(&block_hash.unwrap_or_else(H256::zero));
|
||||
s.out()
|
||||
}
|
||||
|
||||
|
||||
pub fn message_full_rlp(signature: &H520, vote_info: &Bytes) -> Bytes {
|
||||
let mut s = RlpStream::new_list(2);
|
||||
s.append(signature).append_raw(vote_info, 1);
|
||||
@@ -199,14 +192,17 @@ mod tests {
|
||||
use super::*;
|
||||
use account_provider::AccountProvider;
|
||||
use header::Header;
|
||||
use ethkey::Secret;
|
||||
|
||||
#[test]
|
||||
fn encode_decode() {
|
||||
let message = ConsensusMessage {
|
||||
signature: H520::default(),
|
||||
height: 10,
|
||||
round: 123,
|
||||
step: Step::Precommit,
|
||||
vote_step: VoteStep {
|
||||
height: 10,
|
||||
round: 123,
|
||||
step: Step::Precommit,
|
||||
},
|
||||
block_hash: Some("1".sha3())
|
||||
};
|
||||
let raw_rlp = ::rlp::encode(&message).to_vec();
|
||||
@@ -215,9 +211,11 @@ mod tests {
|
||||
|
||||
let message = ConsensusMessage {
|
||||
signature: H520::default(),
|
||||
height: 1314,
|
||||
round: 0,
|
||||
step: Step::Prevote,
|
||||
vote_step: VoteStep {
|
||||
height: 1314,
|
||||
round: 0,
|
||||
step: Step::Prevote,
|
||||
},
|
||||
block_hash: None
|
||||
};
|
||||
let raw_rlp = ::rlp::encode(&message);
|
||||
@@ -228,10 +226,10 @@ mod tests {
|
||||
#[test]
|
||||
fn generate_and_verify() {
|
||||
let tap = Arc::new(AccountProvider::transient_provider());
|
||||
let addr = tap.insert_account("0".sha3(), "0").unwrap();
|
||||
let addr = tap.insert_account(Secret::from_slice(&"0".sha3()).unwrap(), "0").unwrap();
|
||||
tap.unlock_account_permanently(addr, "0".into()).unwrap();
|
||||
|
||||
let mi = message_info_rlp(123, 2, Step::Precommit, Some(H256::default()));
|
||||
let mi = message_info_rlp(&VoteStep::new(123, 2, Step::Precommit), Some(H256::default()));
|
||||
|
||||
let raw_rlp = message_full_rlp(&tap.sign(addr, None, mi.sha3()).unwrap().into(), &mi);
|
||||
|
||||
@@ -254,9 +252,11 @@ mod tests {
|
||||
message,
|
||||
ConsensusMessage {
|
||||
signature: Default::default(),
|
||||
height: 0,
|
||||
round: 0,
|
||||
step: Step::Propose,
|
||||
vote_step: VoteStep {
|
||||
height: 0,
|
||||
round: 0,
|
||||
step: Step::Propose,
|
||||
},
|
||||
block_hash: Some(header.bare_hash())
|
||||
}
|
||||
);
|
||||
@@ -267,12 +267,10 @@ mod tests {
|
||||
let header = Header::default();
|
||||
let pro = ConsensusMessage {
|
||||
signature: Default::default(),
|
||||
height: 0,
|
||||
round: 0,
|
||||
step: Step::Propose,
|
||||
vote_step: VoteStep::new(0, 0, Step::Propose),
|
||||
block_hash: Some(header.bare_hash())
|
||||
};
|
||||
let pre = message_info_rlp(0, 0, Step::Precommit, Some(header.bare_hash()));
|
||||
let pre = message_info_rlp(&VoteStep::new(0, 0, Step::Precommit), Some(header.bare_hash()));
|
||||
|
||||
assert_eq!(pro.precommit_hash(), pre.sha3());
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ use self::transition::TransitionHandler;
|
||||
use self::params::TendermintParams;
|
||||
use self::vote_collector::VoteCollector;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
|
||||
pub enum Step {
|
||||
Propose,
|
||||
Prevote,
|
||||
@@ -163,13 +163,13 @@ impl Tendermint {
|
||||
let h = self.height.load(AtomicOrdering::SeqCst);
|
||||
let r = self.round.load(AtomicOrdering::SeqCst);
|
||||
let s = self.step.read();
|
||||
let vote_info = message_info_rlp(h, r, *s, block_hash);
|
||||
let vote_info = message_info_rlp(&VoteStep::new(h, r, *s), block_hash);
|
||||
let authority = self.authority.read();
|
||||
match ap.sign(*authority, self.password.read().clone(), vote_info.sha3()).map(Into::into) {
|
||||
Ok(signature) => {
|
||||
let message_rlp = message_full_rlp(&signature, &vote_info);
|
||||
let message = ConsensusMessage::new(signature, h, r, *s, block_hash);
|
||||
self.votes.vote(message.clone(), *authority);
|
||||
self.votes.vote(message.clone(), &*authority);
|
||||
debug!(target: "poa", "Generated {:?} as {}.", message, *authority);
|
||||
self.handle_valid_message(&message);
|
||||
|
||||
@@ -221,7 +221,7 @@ impl Tendermint {
|
||||
},
|
||||
Step::Prevote => {
|
||||
let block_hash = match *self.lock_change.read() {
|
||||
Some(ref m) if !self.should_unlock(m.round) => m.block_hash,
|
||||
Some(ref m) if !self.should_unlock(m.vote_step.round) => m.block_hash,
|
||||
_ => self.proposal.read().clone(),
|
||||
};
|
||||
self.generate_and_broadcast_message(block_hash);
|
||||
@@ -230,8 +230,8 @@ impl Tendermint {
|
||||
trace!(target: "poa", "to_step: Precommit.");
|
||||
let block_hash = match *self.lock_change.read() {
|
||||
Some(ref m) if self.is_round(m) && m.block_hash.is_some() => {
|
||||
trace!(target: "poa", "Setting last lock: {}", m.round);
|
||||
self.last_lock.store(m.round, AtomicOrdering::SeqCst);
|
||||
trace!(target: "poa", "Setting last lock: {}", m.vote_step.round);
|
||||
self.last_lock.store(m.vote_step.round, AtomicOrdering::SeqCst);
|
||||
m.block_hash
|
||||
},
|
||||
_ => None,
|
||||
@@ -246,7 +246,7 @@ impl Tendermint {
|
||||
if let Some(block_hash) = *self.proposal.read() {
|
||||
// Generate seal and remove old votes.
|
||||
if self.is_proposer(&*self.authority.read()).is_ok() {
|
||||
if let Some(seal) = self.votes.seal_signatures(height, round, block_hash) {
|
||||
if let Some(seal) = self.votes.seal_signatures(height, round, &block_hash) {
|
||||
trace!(target: "poa", "Collected seal: {:?}", seal);
|
||||
let seal = vec![
|
||||
::rlp::encode(&round).to_vec(),
|
||||
@@ -290,11 +290,11 @@ impl Tendermint {
|
||||
}
|
||||
|
||||
fn is_height(&self, message: &ConsensusMessage) -> bool {
|
||||
message.is_height(self.height.load(AtomicOrdering::SeqCst))
|
||||
message.vote_step.is_height(self.height.load(AtomicOrdering::SeqCst))
|
||||
}
|
||||
|
||||
fn is_round(&self, message: &ConsensusMessage) -> bool {
|
||||
message.is_round(self.height.load(AtomicOrdering::SeqCst), self.round.load(AtomicOrdering::SeqCst))
|
||||
message.vote_step.is_round(self.height.load(AtomicOrdering::SeqCst), self.round.load(AtomicOrdering::SeqCst))
|
||||
}
|
||||
|
||||
fn increment_round(&self, n: Round) {
|
||||
@@ -302,20 +302,20 @@ impl Tendermint {
|
||||
self.round.fetch_add(n, AtomicOrdering::SeqCst);
|
||||
}
|
||||
|
||||
fn should_unlock(&self, lock_change_round: Round) -> bool {
|
||||
fn should_unlock(&self, lock_change_round: Round) -> bool {
|
||||
self.last_lock.load(AtomicOrdering::SeqCst) < lock_change_round
|
||||
&& lock_change_round < self.round.load(AtomicOrdering::SeqCst)
|
||||
}
|
||||
|
||||
|
||||
fn has_enough_any_votes(&self) -> bool {
|
||||
let step_votes = self.votes.count_step_votes(self.height.load(AtomicOrdering::SeqCst), self.round.load(AtomicOrdering::SeqCst), *self.step.read());
|
||||
let step_votes = self.votes.count_step_votes(&VoteStep::new(self.height.load(AtomicOrdering::SeqCst), self.round.load(AtomicOrdering::SeqCst), *self.step.read()));
|
||||
self.is_above_threshold(step_votes)
|
||||
}
|
||||
|
||||
fn has_enough_future_step_votes(&self, message: &ConsensusMessage) -> bool {
|
||||
if message.round > self.round.load(AtomicOrdering::SeqCst) {
|
||||
let step_votes = self.votes.count_step_votes(message.height, message.round, message.step);
|
||||
fn has_enough_future_step_votes(&self, vote_step: &VoteStep) -> bool {
|
||||
if vote_step.round > self.round.load(AtomicOrdering::SeqCst) {
|
||||
let step_votes = self.votes.count_step_votes(vote_step);
|
||||
self.is_above_threshold(step_votes)
|
||||
} else {
|
||||
false
|
||||
@@ -328,12 +328,13 @@ impl Tendermint {
|
||||
}
|
||||
|
||||
fn handle_valid_message(&self, message: &ConsensusMessage) {
|
||||
let ref vote_step = message.vote_step;
|
||||
let is_newer_than_lock = match *self.lock_change.read() {
|
||||
Some(ref lock) => message > lock,
|
||||
Some(ref lock) => vote_step > &lock.vote_step,
|
||||
None => true,
|
||||
};
|
||||
let lock_change = is_newer_than_lock
|
||||
&& message.step == Step::Prevote
|
||||
&& vote_step.step == Step::Prevote
|
||||
&& message.block_hash.is_some()
|
||||
&& self.has_enough_aligned_votes(message);
|
||||
if lock_change {
|
||||
@@ -351,15 +352,15 @@ impl Tendermint {
|
||||
Some(Step::Commit)
|
||||
}
|
||||
},
|
||||
Step::Precommit if self.has_enough_future_step_votes(message) => {
|
||||
self.increment_round(message.round - self.round.load(AtomicOrdering::SeqCst));
|
||||
Step::Precommit if self.has_enough_future_step_votes(&vote_step) => {
|
||||
self.increment_round(vote_step.round - self.round.load(AtomicOrdering::SeqCst));
|
||||
Some(Step::Precommit)
|
||||
},
|
||||
// Avoid counting twice.
|
||||
Step::Prevote if lock_change => Some(Step::Precommit),
|
||||
Step::Prevote if self.has_enough_aligned_votes(message) => Some(Step::Precommit),
|
||||
Step::Prevote if self.has_enough_future_step_votes(message) => {
|
||||
self.increment_round(message.round - self.round.load(AtomicOrdering::SeqCst));
|
||||
Step::Prevote if self.has_enough_future_step_votes(&vote_step) => {
|
||||
self.increment_round(vote_step.round - self.round.load(AtomicOrdering::SeqCst));
|
||||
Some(Step::Prevote)
|
||||
},
|
||||
_ => None,
|
||||
@@ -390,8 +391,8 @@ impl Engine for Tendermint {
|
||||
let message = ConsensusMessage::new_proposal(header).expect("Invalid header.");
|
||||
map![
|
||||
"signature".into() => message.signature.to_string(),
|
||||
"height".into() => message.height.to_string(),
|
||||
"round".into() => message.round.to_string(),
|
||||
"height".into() => message.vote_step.height.to_string(),
|
||||
"round".into() => message.vote_step.round.to_string(),
|
||||
"block_hash".into() => message.block_hash.as_ref().map(ToString::to_string).unwrap_or("".into())
|
||||
]
|
||||
}
|
||||
@@ -431,11 +432,11 @@ impl Engine for Tendermint {
|
||||
let height = header.number() as Height;
|
||||
let round = self.round.load(AtomicOrdering::SeqCst);
|
||||
let bh = Some(header.bare_hash());
|
||||
let vote_info = message_info_rlp(height, round, Step::Propose, bh.clone());
|
||||
let vote_info = message_info_rlp(&VoteStep::new(height, round, Step::Propose), bh.clone());
|
||||
if let Ok(signature) = ap.sign(*author, self.password.read().clone(), vote_info.sha3()).map(H520::from) {
|
||||
// Insert Propose vote.
|
||||
debug!(target: "poa", "Submitting proposal {} at height {} round {}.", header.bare_hash(), height, round);
|
||||
self.votes.vote(ConsensusMessage::new(signature, height, round, Step::Propose, bh), *author);
|
||||
self.votes.vote(ConsensusMessage::new(signature, height, round, Step::Propose, bh), author);
|
||||
// Remember proposal for later seal submission.
|
||||
*self.proposal.write() = bh;
|
||||
Seal::Proposal(vec![
|
||||
@@ -461,9 +462,11 @@ impl Engine for Tendermint {
|
||||
if !self.is_authority(&sender) {
|
||||
Err(EngineError::NotAuthorized(sender))?;
|
||||
}
|
||||
self.broadcast_message(rlp.as_raw().to_vec());
|
||||
if self.votes.vote(message.clone(), &sender).is_some() {
|
||||
Err(EngineError::DoubleVote(sender))?
|
||||
}
|
||||
trace!(target: "poa", "Handling a valid {:?} from {}.", message, sender);
|
||||
self.votes.vote(message.clone(), sender);
|
||||
self.broadcast_message(rlp.as_raw().to_vec());
|
||||
self.handle_valid_message(&message);
|
||||
}
|
||||
Ok(())
|
||||
@@ -502,7 +505,7 @@ impl Engine for Tendermint {
|
||||
}
|
||||
|
||||
fn verify_block_unordered(&self, header: &Header, _block: Option<&[u8]>) -> Result<(), Error> {
|
||||
let proposal = ConsensusMessage::new_proposal(header)?;
|
||||
let proposal = ConsensusMessage::new_proposal(header)?;
|
||||
let proposer = proposal.verify()?;
|
||||
if !self.is_authority(&proposer) {
|
||||
Err(EngineError::NotAuthorized(proposer))?
|
||||
@@ -541,7 +544,7 @@ impl Engine for Tendermint {
|
||||
found: signatures_len
|
||||
}))?;
|
||||
}
|
||||
self.is_round_proposer(proposal.height, proposal.round, &proposer)?;
|
||||
self.is_round_proposer(proposal.vote_step.height, proposal.vote_step.round, &proposer)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -607,16 +610,16 @@ impl Engine for Tendermint {
|
||||
let proposal = ConsensusMessage::new_proposal(header).expect("block went through full verification; this Engine verifies new_proposal creation; qed");
|
||||
if signatures_len != 1 {
|
||||
// New Commit received, skip to next height.
|
||||
trace!(target: "poa", "Received a commit for height {}, round {}.", proposal.height, proposal.round);
|
||||
self.to_next_height(proposal.height);
|
||||
trace!(target: "poa", "Received a commit: {:?}.", proposal.vote_step);
|
||||
self.to_next_height(proposal.vote_step.height);
|
||||
return false;
|
||||
}
|
||||
let proposer = proposal.verify().expect("block went through full verification; this Engine tries verify; qed");
|
||||
debug!(target: "poa", "Received a new proposal for height {}, round {} from {}.", proposal.height, proposal.round, proposer);
|
||||
debug!(target: "poa", "Received a new proposal {:?} from {}.", proposal.vote_step, proposer);
|
||||
if self.is_round(&proposal) {
|
||||
*self.proposal.write() = proposal.block_hash.clone();
|
||||
}
|
||||
self.votes.vote(proposal, proposer);
|
||||
self.votes.vote(proposal, &proposer);
|
||||
true
|
||||
}
|
||||
|
||||
@@ -671,6 +674,7 @@ mod tests {
|
||||
use error::{Error, BlockError};
|
||||
use header::Header;
|
||||
use env_info::EnvInfo;
|
||||
use ethkey::Secret;
|
||||
use client::chain_notify::ChainNotify;
|
||||
use miner::MinerService;
|
||||
use tests::helpers::*;
|
||||
@@ -703,7 +707,7 @@ mod tests {
|
||||
}
|
||||
|
||||
fn vote<F>(engine: &Engine, signer: F, height: usize, round: usize, step: Step, block_hash: Option<H256>) -> Bytes where F: FnOnce(H256) -> Result<H520, ::account_provider::Error> {
|
||||
let mi = message_info_rlp(height, round, step, block_hash);
|
||||
let mi = message_info_rlp(&VoteStep::new(height, round, step), block_hash);
|
||||
let m = message_full_rlp(&signer(mi.sha3()).unwrap().into(), &mi);
|
||||
engine.handle_message(&m).unwrap();
|
||||
m
|
||||
@@ -711,7 +715,7 @@ mod tests {
|
||||
|
||||
fn proposal_seal(tap: &Arc<AccountProvider>, header: &Header, round: Round) -> Vec<Bytes> {
|
||||
let author = header.author();
|
||||
let vote_info = message_info_rlp(header.number() as Height, round, Step::Propose, Some(header.bare_hash()));
|
||||
let vote_info = message_info_rlp(&VoteStep::new(header.number() as Height, round, Step::Propose), Some(header.bare_hash()));
|
||||
let signature = tap.sign(*author, None, vote_info.sha3()).unwrap();
|
||||
vec![
|
||||
::rlp::encode(&round).to_vec(),
|
||||
@@ -721,7 +725,7 @@ mod tests {
|
||||
}
|
||||
|
||||
fn insert_and_unlock(tap: &Arc<AccountProvider>, acc: &str) -> Address {
|
||||
let addr = tap.insert_account(acc.sha3(), acc).unwrap();
|
||||
let addr = tap.insert_account(Secret::from_slice(&acc.sha3()).unwrap(), acc).unwrap();
|
||||
tap.unlock_account_permanently(addr, acc.into()).unwrap();
|
||||
addr
|
||||
}
|
||||
@@ -825,7 +829,7 @@ mod tests {
|
||||
header.set_author(proposer);
|
||||
let mut seal = proposal_seal(&tap, &header, 0);
|
||||
|
||||
let vote_info = message_info_rlp(0, 0, Step::Precommit, Some(header.bare_hash()));
|
||||
let vote_info = message_info_rlp(&VoteStep::new(0, 0, Step::Precommit), Some(header.bare_hash()));
|
||||
let signature1 = tap.sign(proposer, None, vote_info.sha3()).unwrap();
|
||||
|
||||
seal[2] = ::rlp::encode(&vec![H520::from(signature1.clone())]).to_vec();
|
||||
@@ -886,7 +890,7 @@ mod tests {
|
||||
fn relays_messages() {
|
||||
let (spec, tap) = setup();
|
||||
let engine = spec.engine.clone();
|
||||
|
||||
|
||||
let v0 = insert_and_register(&tap, engine.as_ref(), "0");
|
||||
let v1 = insert_and_register(&tap, engine.as_ref(), "1");
|
||||
|
||||
|
||||
@@ -17,13 +17,50 @@
|
||||
//! Collects votes on hashes at each height and round.
|
||||
|
||||
use util::*;
|
||||
use super::message::ConsensusMessage;
|
||||
use super::{Height, Round, Step};
|
||||
use super::message::*;
|
||||
use super::{Height, Round, Step, BlockHash};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct VoteCollector {
|
||||
/// Storing all Proposals, Prevotes and Precommits.
|
||||
votes: RwLock<BTreeMap<ConsensusMessage, Address>>,
|
||||
votes: RwLock<BTreeMap<VoteStep, StepCollector>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
struct StepCollector {
|
||||
voted: HashSet<Address>,
|
||||
pub block_votes: HashMap<Option<BlockHash>, HashMap<H520, Address>>,
|
||||
messages: HashSet<ConsensusMessage>,
|
||||
}
|
||||
|
||||
impl StepCollector {
|
||||
/// Returns Some(&Address) when validator is double voting.
|
||||
fn insert<'a>(&mut self, message: ConsensusMessage, address: &'a Address) -> Option<&'a Address> {
|
||||
// Do nothing when message was seen.
|
||||
if self.messages.insert(message.clone()) {
|
||||
if self.voted.insert(address.clone()) {
|
||||
self
|
||||
.block_votes
|
||||
.entry(message.block_hash)
|
||||
.or_insert_with(HashMap::new)
|
||||
.insert(message.signature, address.clone());
|
||||
} else {
|
||||
// Bad validator sent a different message.
|
||||
return Some(address);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Count all votes for the given block hash at this step.
|
||||
fn count_block(&self, block_hash: &Option<BlockHash>) -> usize {
|
||||
self.block_votes.get(block_hash).map_or(0, HashMap::len)
|
||||
}
|
||||
|
||||
/// Count all votes collected for the given step.
|
||||
fn count(&self) -> usize {
|
||||
self.block_votes.values().map(HashMap::len).sum()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -42,109 +79,105 @@ impl PartialEq for SealSignatures {
|
||||
impl Eq for SealSignatures {}
|
||||
|
||||
impl VoteCollector {
|
||||
pub fn new() -> VoteCollector {
|
||||
pub fn new() -> Self {
|
||||
let mut collector = BTreeMap::new();
|
||||
// Insert dummy message to fulfill invariant: "only messages newer than the oldest are inserted".
|
||||
collector.insert(ConsensusMessage {
|
||||
signature: H520::default(),
|
||||
height: 0,
|
||||
round: 0,
|
||||
step: Step::Propose,
|
||||
block_hash: None
|
||||
},
|
||||
Address::default());
|
||||
// Insert dummy entry to fulfill invariant: "only messages newer than the oldest are inserted".
|
||||
collector.insert(VoteStep::new(0, 0, Step::Propose), Default::default());
|
||||
VoteCollector { votes: RwLock::new(collector) }
|
||||
}
|
||||
|
||||
/// Insert vote if it is newer than the oldest one.
|
||||
pub fn vote(&self, message: ConsensusMessage, voter: Address) -> Option<Address> {
|
||||
self.votes.write().insert(message, voter)
|
||||
pub fn vote<'a>(&self, message: ConsensusMessage, voter: &'a Address) -> Option<&'a Address> {
|
||||
self
|
||||
.votes
|
||||
.write()
|
||||
.entry(message.vote_step.clone())
|
||||
.or_insert_with(Default::default)
|
||||
.insert(message, voter)
|
||||
}
|
||||
|
||||
/// Checks if the message should be ignored.
|
||||
pub fn is_old_or_known(&self, message: &ConsensusMessage) -> bool {
|
||||
self.votes.read().get(message).map_or(false, |a| {
|
||||
trace!(target: "poa", "Known message from {}: {:?}.", a, message);
|
||||
true
|
||||
}) || {
|
||||
self
|
||||
.votes
|
||||
.read()
|
||||
.get(&message.vote_step)
|
||||
.map_or(false, |c| {
|
||||
let is_known = c.messages.contains(message);
|
||||
if is_known { trace!(target: "poa", "Known message: {:?}.", message); }
|
||||
is_known
|
||||
})
|
||||
|| {
|
||||
let guard = self.votes.read();
|
||||
let is_old = guard.keys().next().map_or(true, |oldest| message <= oldest);
|
||||
let is_old = guard.keys().next().map_or(true, |oldest| message.vote_step <= *oldest);
|
||||
if is_old { trace!(target: "poa", "Old message {:?}.", message); }
|
||||
is_old
|
||||
}
|
||||
}
|
||||
|
||||
/// Throws out messages older than message, leaves message as marker for the oldest.
|
||||
pub fn throw_out_old(&self, message: &ConsensusMessage) {
|
||||
pub fn throw_out_old(&self, vote_step: &VoteStep) {
|
||||
let mut guard = self.votes.write();
|
||||
let new_collector = guard.split_off(message);
|
||||
let new_collector = guard.split_off(vote_step);
|
||||
*guard = new_collector;
|
||||
}
|
||||
|
||||
pub fn seal_signatures(&self, height: Height, round: Round, block_hash: H256) -> Option<SealSignatures> {
|
||||
let bh = Some(block_hash);
|
||||
let (proposal, votes) = {
|
||||
/// Collects the signatures used to seal a block.
|
||||
pub fn seal_signatures(&self, height: Height, round: Round, block_hash: &H256) -> Option<SealSignatures> {
|
||||
let ref bh = Some(*block_hash);
|
||||
let precommit_step = VoteStep::new(height, round, Step::Precommit);
|
||||
let maybe_seal = {
|
||||
let guard = self.votes.read();
|
||||
let mut current_signatures = guard.keys().skip_while(|m| !m.is_block_hash(height, round, Step::Propose, bh));
|
||||
let proposal = current_signatures.next().cloned();
|
||||
let votes = current_signatures
|
||||
.skip_while(|m| !m.is_block_hash(height, round, Step::Precommit, bh))
|
||||
.filter(|m| m.is_block_hash(height, round, Step::Precommit, bh))
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
(proposal, votes)
|
||||
guard
|
||||
.get(&VoteStep::new(height, round, Step::Propose))
|
||||
.and_then(|c| c.block_votes.get(bh))
|
||||
.and_then(|proposals| proposals.keys().next())
|
||||
.map(|proposal| SealSignatures {
|
||||
proposal: proposal.clone(),
|
||||
votes: guard
|
||||
.get(&precommit_step)
|
||||
.and_then(|c| c.block_votes.get(bh))
|
||||
.map(|precommits| precommits.keys().cloned().collect())
|
||||
.unwrap_or_else(Vec::new),
|
||||
})
|
||||
.and_then(|seal| if seal.votes.is_empty() { None } else { Some(seal) })
|
||||
};
|
||||
if votes.is_empty() {
|
||||
return None;
|
||||
if maybe_seal.is_some() {
|
||||
// Remove messages that are no longer relevant.
|
||||
self.throw_out_old(&precommit_step);
|
||||
}
|
||||
// Remove messages that are no longer relevant.
|
||||
votes.last().map(|m| self.throw_out_old(m));
|
||||
let mut votes_vec: Vec<_> = votes.into_iter().map(|m| m.signature).collect();
|
||||
votes_vec.sort();
|
||||
proposal.map(|p| SealSignatures {
|
||||
proposal: p.signature,
|
||||
votes: votes_vec,
|
||||
})
|
||||
maybe_seal
|
||||
}
|
||||
|
||||
/// Count votes which agree with the given message.
|
||||
pub fn count_aligned_votes(&self, message: &ConsensusMessage) -> usize {
|
||||
let guard = self.votes.read();
|
||||
guard.keys()
|
||||
.skip_while(|m| !m.is_aligned(message))
|
||||
// sorted by signature so might not be continuous
|
||||
.filter(|m| m.is_aligned(message))
|
||||
.count()
|
||||
self
|
||||
.votes
|
||||
.read()
|
||||
.get(&message.vote_step)
|
||||
.map_or(0, |m| m.count_block(&message.block_hash))
|
||||
}
|
||||
|
||||
pub fn count_step_votes(&self, height: Height, round: Round, step: Step) -> usize {
|
||||
let guard = self.votes.read();
|
||||
let current = guard.iter().skip_while(|&(m, _)| !m.is_step(height, round, step));
|
||||
let mut origins = HashSet::new();
|
||||
let mut n = 0;
|
||||
for (message, origin) in current {
|
||||
if message.is_step(height, round, step) {
|
||||
if origins.insert(origin) {
|
||||
n += 1;
|
||||
} else {
|
||||
warn!("count_step_votes: Authority {} has cast multiple step votes, this indicates malicious behaviour.", origin)
|
||||
}
|
||||
}
|
||||
}
|
||||
n
|
||||
/// Count all votes collected for a given step.
|
||||
pub fn count_step_votes(&self, vote_step: &VoteStep) -> usize {
|
||||
self.votes.read().get(vote_step).map_or(0, StepCollector::count)
|
||||
}
|
||||
|
||||
/// Get all messages older than the height.
|
||||
pub fn get_up_to(&self, height: Height) -> Vec<Bytes> {
|
||||
let guard = self.votes.read();
|
||||
guard
|
||||
.keys()
|
||||
.filter(|m| m.step.is_pre())
|
||||
.take_while(|m| m.height <= height)
|
||||
.map(|m| ::rlp::encode(m).to_vec())
|
||||
.collect()
|
||||
.iter()
|
||||
.filter(|&(s, _)| s.step.is_pre())
|
||||
.take_while(|&(s, _)| s.height <= height)
|
||||
.map(|(_, c)| c.messages.iter().map(|m| ::rlp::encode(m).to_vec()).collect::<Vec<_>>())
|
||||
.fold(Vec::new(), |mut acc, mut messages| { acc.append(&mut messages); acc })
|
||||
}
|
||||
|
||||
/// Retrieve address from which the message was sent from cache.
|
||||
pub fn get(&self, message: &ConsensusMessage) -> Option<Address> {
|
||||
let guard = self.votes.read();
|
||||
guard.get(message).cloned()
|
||||
guard.get(&message.vote_step).and_then(|c| c.block_votes.get(&message.block_hash)).and_then(|origins| origins.get(&message.signature).cloned())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,15 +185,15 @@ impl VoteCollector {
|
||||
mod tests {
|
||||
use util::*;
|
||||
use super::*;
|
||||
use super::super::{Height, Round, BlockHash, Step};
|
||||
use super::super::message::ConsensusMessage;
|
||||
use super::super::{BlockHash, Step};
|
||||
use super::super::message::*;
|
||||
|
||||
fn random_vote(collector: &VoteCollector, signature: H520, h: Height, r: Round, step: Step, block_hash: Option<BlockHash>) -> Option<H160> {
|
||||
full_vote(collector, signature, h, r, step, block_hash, H160::random())
|
||||
fn random_vote(collector: &VoteCollector, signature: H520, vote_step: VoteStep, block_hash: Option<BlockHash>) -> bool {
|
||||
full_vote(collector, signature, vote_step, block_hash, &H160::random()).is_none()
|
||||
}
|
||||
|
||||
fn full_vote(collector: &VoteCollector, signature: H520, h: Height, r: Round, step: Step, block_hash: Option<BlockHash>, address: Address) -> Option<H160> {
|
||||
collector.vote(ConsensusMessage { signature: signature, height: h, round: r, step: step, block_hash: block_hash }, address)
|
||||
fn full_vote<'a>(collector: &VoteCollector, signature: H520, vote_step: VoteStep, block_hash: Option<BlockHash>, address: &'a Address) -> Option<&'a Address> {
|
||||
collector.vote(ConsensusMessage { signature: signature, vote_step: vote_step, block_hash: block_hash }, address)
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -173,68 +206,71 @@ mod tests {
|
||||
for _ in 0..5 {
|
||||
signatures.push(H520::random());
|
||||
}
|
||||
let propose_step = VoteStep::new(h, r, Step::Propose);
|
||||
let prevote_step = VoteStep::new(h, r, Step::Prevote);
|
||||
let precommit_step = VoteStep::new(h, r, Step::Precommit);
|
||||
// Wrong height proposal.
|
||||
random_vote(&collector, signatures[4].clone(), h - 1, r, Step::Propose, bh.clone());
|
||||
random_vote(&collector, signatures[4].clone(), VoteStep::new(h - 1, r, Step::Propose), bh.clone());
|
||||
// Good proposal
|
||||
random_vote(&collector, signatures[0].clone(), h, r, Step::Propose, bh.clone());
|
||||
random_vote(&collector, signatures[0].clone(), propose_step.clone(), bh.clone());
|
||||
// Wrong block proposal.
|
||||
random_vote(&collector, signatures[0].clone(), h, r, Step::Propose, Some("0".sha3()));
|
||||
random_vote(&collector, signatures[0].clone(), propose_step.clone(), Some("0".sha3()));
|
||||
// Wrong block precommit.
|
||||
random_vote(&collector, signatures[3].clone(), h, r, Step::Precommit, Some("0".sha3()));
|
||||
random_vote(&collector, signatures[3].clone(), precommit_step.clone(), Some("0".sha3()));
|
||||
// Wrong round proposal.
|
||||
random_vote(&collector, signatures[0].clone(), h, r - 1, Step::Propose, bh.clone());
|
||||
random_vote(&collector, signatures[0].clone(), VoteStep::new(h, r - 1, Step::Propose), bh.clone());
|
||||
// Prevote.
|
||||
random_vote(&collector, signatures[0].clone(), h, r, Step::Prevote, bh.clone());
|
||||
random_vote(&collector, signatures[0].clone(), prevote_step.clone(), bh.clone());
|
||||
// Relevant precommit.
|
||||
random_vote(&collector, signatures[2].clone(), h, r, Step::Precommit, bh.clone());
|
||||
random_vote(&collector, signatures[2].clone(), precommit_step.clone(), bh.clone());
|
||||
// Replcated vote.
|
||||
random_vote(&collector, signatures[2].clone(), h, r, Step::Precommit, bh.clone());
|
||||
random_vote(&collector, signatures[2].clone(), precommit_step.clone(), bh.clone());
|
||||
// Wrong round precommit.
|
||||
random_vote(&collector, signatures[4].clone(), h, r + 1, Step::Precommit, bh.clone());
|
||||
random_vote(&collector, signatures[4].clone(), VoteStep::new(h, r + 1, Step::Precommit), bh.clone());
|
||||
// Wrong height precommit.
|
||||
random_vote(&collector, signatures[3].clone(), h + 1, r, Step::Precommit, bh.clone());
|
||||
random_vote(&collector, signatures[3].clone(), VoteStep::new(h + 1, r, Step::Precommit), bh.clone());
|
||||
// Relevant precommit.
|
||||
random_vote(&collector, signatures[1].clone(), h, r, Step::Precommit, bh.clone());
|
||||
random_vote(&collector, signatures[1].clone(), precommit_step.clone(), bh.clone());
|
||||
// Wrong round precommit, same signature.
|
||||
random_vote(&collector, signatures[1].clone(), h, r + 1, Step::Precommit, bh.clone());
|
||||
random_vote(&collector, signatures[1].clone(), VoteStep::new(h, r + 1, Step::Precommit), bh.clone());
|
||||
// Wrong round precommit.
|
||||
random_vote(&collector, signatures[4].clone(), h, r - 1, Step::Precommit, bh.clone());
|
||||
random_vote(&collector, signatures[4].clone(), VoteStep::new(h, r - 1, Step::Precommit), bh.clone());
|
||||
let seal = SealSignatures {
|
||||
proposal: signatures[0],
|
||||
votes: signatures[1..3].to_vec()
|
||||
};
|
||||
assert_eq!(seal, collector.seal_signatures(h, r, bh.unwrap()).unwrap());
|
||||
assert_eq!(seal, collector.seal_signatures(h, r, &bh.unwrap()).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn count_votes() {
|
||||
let collector = VoteCollector::new();
|
||||
let prevote_step = VoteStep::new(3, 2, Step::Prevote);
|
||||
let precommit_step = VoteStep::new(3, 2, Step::Precommit);
|
||||
// good prevote
|
||||
random_vote(&collector, H520::random(), 3, 2, Step::Prevote, Some("0".sha3()));
|
||||
random_vote(&collector, H520::random(), 3, 1, Step::Prevote, Some("0".sha3()));
|
||||
random_vote(&collector, H520::random(), prevote_step.clone(), Some("0".sha3()));
|
||||
random_vote(&collector, H520::random(), VoteStep::new(3, 1, Step::Prevote), Some("0".sha3()));
|
||||
// good precommit
|
||||
random_vote(&collector, H520::random(), 3, 2, Step::Precommit, Some("0".sha3()));
|
||||
random_vote(&collector, H520::random(), 3, 3, Step::Precommit, Some("0".sha3()));
|
||||
random_vote(&collector, H520::random(), precommit_step.clone(), Some("0".sha3()));
|
||||
random_vote(&collector, H520::random(), VoteStep::new(3, 3, Step::Precommit), Some("0".sha3()));
|
||||
// good prevote
|
||||
random_vote(&collector, H520::random(), 3, 2, Step::Prevote, Some("1".sha3()));
|
||||
random_vote(&collector, H520::random(), prevote_step.clone(), Some("1".sha3()));
|
||||
// good prevote
|
||||
let same_sig = H520::random();
|
||||
random_vote(&collector, same_sig.clone(), 3, 2, Step::Prevote, Some("1".sha3()));
|
||||
random_vote(&collector, same_sig, 3, 2, Step::Prevote, Some("1".sha3()));
|
||||
random_vote(&collector, same_sig.clone(), prevote_step.clone(), Some("1".sha3()));
|
||||
random_vote(&collector, same_sig, prevote_step.clone(), Some("1".sha3()));
|
||||
// good precommit
|
||||
random_vote(&collector, H520::random(), 3, 2, Step::Precommit, Some("1".sha3()));
|
||||
random_vote(&collector, H520::random(), precommit_step.clone(), Some("1".sha3()));
|
||||
// good prevote
|
||||
random_vote(&collector, H520::random(), 3, 2, Step::Prevote, Some("0".sha3()));
|
||||
random_vote(&collector, H520::random(), 2, 2, Step::Precommit, Some("2".sha3()));
|
||||
random_vote(&collector, H520::random(), prevote_step.clone(), Some("0".sha3()));
|
||||
random_vote(&collector, H520::random(), VoteStep::new(2, 2, Step::Precommit), Some("2".sha3()));
|
||||
|
||||
assert_eq!(collector.count_step_votes(3, 2, Step::Prevote), 4);
|
||||
assert_eq!(collector.count_step_votes(3, 2, Step::Precommit), 2);
|
||||
assert_eq!(collector.count_step_votes(&prevote_step), 4);
|
||||
assert_eq!(collector.count_step_votes(&precommit_step), 2);
|
||||
|
||||
let message = ConsensusMessage {
|
||||
signature: H520::default(),
|
||||
height: 3,
|
||||
round: 2,
|
||||
step: Step::Prevote,
|
||||
vote_step: prevote_step,
|
||||
block_hash: Some("1".sha3())
|
||||
};
|
||||
assert_eq!(collector.count_aligned_votes(&message), 2);
|
||||
@@ -243,30 +279,29 @@ mod tests {
|
||||
#[test]
|
||||
fn remove_old() {
|
||||
let collector = VoteCollector::new();
|
||||
random_vote(&collector, H520::random(), 3, 2, Step::Prevote, Some("0".sha3()));
|
||||
random_vote(&collector, H520::random(), 3, 1, Step::Prevote, Some("0".sha3()));
|
||||
random_vote(&collector, H520::random(), 3, 3, Step::Precommit, Some("0".sha3()));
|
||||
random_vote(&collector, H520::random(), 3, 2, Step::Prevote, Some("1".sha3()));
|
||||
random_vote(&collector, H520::random(), 3, 2, Step::Prevote, Some("1".sha3()));
|
||||
random_vote(&collector, H520::random(), 3, 2, Step::Prevote, Some("0".sha3()));
|
||||
random_vote(&collector, H520::random(), 2, 2, Step::Precommit, Some("2".sha3()));
|
||||
|
||||
let message = ConsensusMessage {
|
||||
signature: H520::default(),
|
||||
height: 3,
|
||||
round: 2,
|
||||
step: Step::Precommit,
|
||||
block_hash: Some("1".sha3())
|
||||
let vote = |height, round, step, hash| {
|
||||
random_vote(&collector, H520::random(), VoteStep::new(height, round, step), hash);
|
||||
};
|
||||
collector.throw_out_old(&message);
|
||||
vote(3, 2, Step::Prevote, Some("0".sha3()));
|
||||
vote(3, 1, Step::Prevote, Some("0".sha3()));
|
||||
vote(3, 3, Step::Precommit, Some("0".sha3()));
|
||||
vote(3, 2, Step::Prevote, Some("1".sha3()));
|
||||
vote(3, 2, Step::Prevote, Some("1".sha3()));
|
||||
vote(3, 2, Step::Prevote, Some("0".sha3()));
|
||||
vote(2, 2, Step::Precommit, Some("2".sha3()));
|
||||
|
||||
collector.throw_out_old(&VoteStep::new(3, 2, Step::Precommit));
|
||||
assert_eq!(collector.votes.read().len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn malicious_authority() {
|
||||
let collector = VoteCollector::new();
|
||||
full_vote(&collector, H520::random(), 3, 2, Step::Prevote, Some("0".sha3()), Address::default());
|
||||
full_vote(&collector, H520::random(), 3, 2, Step::Prevote, Some("1".sha3()), Address::default());
|
||||
assert_eq!(collector.count_step_votes(3, 2, Step::Prevote), 1);
|
||||
let vote_step = VoteStep::new(3, 2, Step::Prevote);
|
||||
// Vote is inserted fine.
|
||||
assert!(full_vote(&collector, H520::random(), vote_step.clone(), Some("0".sha3()), &Address::default()).is_none());
|
||||
// Returns the double voting address.
|
||||
full_vote(&collector, H520::random(), vote_step.clone(), Some("1".sha3()), &Address::default()).unwrap();
|
||||
assert_eq!(collector.count_step_votes(&vote_step), 1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,17 +118,17 @@ mod provider {
|
||||
}
|
||||
}
|
||||
fn as_string<T: fmt::Debug>(e: T) -> String { format!("{:?}", e) }
|
||||
|
||||
|
||||
/// Auto-generated from: `{"constant":true,"inputs":[],"name":"getValidators","outputs":[{"name":"","type":"address[]"}],"payable":false,"type":"function"}`
|
||||
#[allow(dead_code)]
|
||||
pub fn get_validators(&self) -> Result<Vec<util::Address>, String> {
|
||||
pub fn get_validators(&self) -> Result<Vec<util::Address>, String> {
|
||||
let call = self.contract.function("getValidators".into()).map_err(Self::as_string)?;
|
||||
let data = call.encode_call(
|
||||
vec![]
|
||||
).map_err(Self::as_string)?;
|
||||
let output = call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?;
|
||||
let mut result = output.into_iter().rev().collect::<Vec<_>>();
|
||||
Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = r.to_array().and_then(|v| v.into_iter().map(|a| a.to_address()).collect::<Option<Vec<[u8; 20]>>>()).ok_or("Invalid type returned")?; r.into_iter().map(|a| util::Address::from(a)).collect::<Vec<_>>() }))
|
||||
Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = r.to_array().and_then(|v| v.into_iter().map(|a| a.to_address()).collect::<Option<Vec<[u8; 20]>>>()).ok_or("Invalid type returned")?; r.into_iter().map(|a| util::Address::from(a)).collect::<Vec<_>>() }))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -140,6 +140,7 @@ mod tests {
|
||||
use account_provider::AccountProvider;
|
||||
use transaction::{Transaction, Action};
|
||||
use client::{BlockChainClient, EngineClient};
|
||||
use ethkey::Secret;
|
||||
use miner::MinerService;
|
||||
use tests::helpers::generate_dummy_client_with_spec_and_data;
|
||||
use super::super::ValidatorSet;
|
||||
@@ -158,8 +159,9 @@ mod tests {
|
||||
#[test]
|
||||
fn changes_validators() {
|
||||
let tap = Arc::new(AccountProvider::transient_provider());
|
||||
let v0 = tap.insert_account("1".sha3(), "").unwrap();
|
||||
let v1 = tap.insert_account("0".sha3(), "").unwrap();
|
||||
let s0 = Secret::from_slice(&"1".sha3()).unwrap();
|
||||
let v0 = tap.insert_account(s0.clone(), "").unwrap();
|
||||
let v1 = tap.insert_account(Secret::from_slice(&"0".sha3()).unwrap(), "").unwrap();
|
||||
let spec_factory = || {
|
||||
let spec = Spec::new_validator_contract();
|
||||
spec.engine.register_account_provider(tap.clone());
|
||||
@@ -178,7 +180,7 @@ mod tests {
|
||||
action: Action::Call(validator_contract),
|
||||
value: 0.into(),
|
||||
data: "f94e18670000000000000000000000000000000000000000000000000000000000000001".from_hex().unwrap(),
|
||||
}.sign(&"1".sha3(), None);
|
||||
}.sign(&s0, None);
|
||||
client.miner().import_own_transaction(client.as_ref(), tx.into()).unwrap();
|
||||
client.update_sealing();
|
||||
assert_eq!(client.chain_info().best_block_number, 1);
|
||||
@@ -190,7 +192,7 @@ mod tests {
|
||||
action: Action::Call(validator_contract),
|
||||
value: 0.into(),
|
||||
data: "4d238c8e00000000000000000000000082a978b3f5962a5b0957d9ee9eef472ee55b42f1".from_hex().unwrap(),
|
||||
}.sign(&"1".sha3(), None);
|
||||
}.sign(&s0, None);
|
||||
client.miner().import_own_transaction(client.as_ref(), tx.into()).unwrap();
|
||||
client.update_sealing();
|
||||
// The transaction is not yet included so still unable to seal.
|
||||
@@ -209,7 +211,7 @@ mod tests {
|
||||
action: Action::Call(Address::default()),
|
||||
value: 0.into(),
|
||||
data: Vec::new(),
|
||||
}.sign(&"1".sha3(), None);
|
||||
}.sign(&s0, None);
|
||||
client.miner().import_own_transaction(client.as_ref(), tx.into()).unwrap();
|
||||
client.update_sealing();
|
||||
// Able to seal again.
|
||||
|
||||
@@ -844,6 +844,7 @@ mod tests {
|
||||
use std::str::FromStr;
|
||||
use rustc_serialize::hex::FromHex;
|
||||
use super::*;
|
||||
use ethkey::Secret;
|
||||
use util::{U256, H256, FixedHash, Address, Hashable};
|
||||
use tests::helpers::*;
|
||||
use devtools::*;
|
||||
@@ -854,6 +855,10 @@ mod tests {
|
||||
use trace::{FlatTrace, TraceError, trace};
|
||||
use types::executed::CallType;
|
||||
|
||||
fn secret() -> Secret {
|
||||
Secret::from_slice(&"".sha3()).unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_apply_create_transaction() {
|
||||
init_log();
|
||||
@@ -872,7 +877,7 @@ mod tests {
|
||||
action: Action::Create,
|
||||
value: 100.into(),
|
||||
data: FromHex::from_hex("601080600c6000396000f3006000355415600957005b60203560003555").unwrap(),
|
||||
}.sign(&"".sha3(), None);
|
||||
}.sign(&secret(), None);
|
||||
|
||||
state.add_balance(t.sender().as_ref().unwrap(), &(100.into()), CleanupMode::NoEmpty);
|
||||
let result = state.apply(&info, &engine, &t, true).unwrap();
|
||||
@@ -932,7 +937,7 @@ mod tests {
|
||||
action: Action::Create,
|
||||
value: 100.into(),
|
||||
data: FromHex::from_hex("5b600056").unwrap(),
|
||||
}.sign(&"".sha3(), None);
|
||||
}.sign(&secret(), None);
|
||||
|
||||
state.add_balance(t.sender().as_ref().unwrap(), &(100.into()), CleanupMode::NoEmpty);
|
||||
let result = state.apply(&info, &engine, &t, true).unwrap();
|
||||
@@ -969,7 +974,7 @@ mod tests {
|
||||
action: Action::Call(0xa.into()),
|
||||
value: 100.into(),
|
||||
data: vec![],
|
||||
}.sign(&"".sha3(), None);
|
||||
}.sign(&secret(), None);
|
||||
|
||||
state.init_code(&0xa.into(), FromHex::from_hex("6000").unwrap());
|
||||
state.add_balance(t.sender().as_ref().unwrap(), &(100.into()), CleanupMode::NoEmpty);
|
||||
@@ -1012,7 +1017,7 @@ mod tests {
|
||||
action: Action::Call(0xa.into()),
|
||||
value: 100.into(),
|
||||
data: vec![],
|
||||
}.sign(&"".sha3(), None);
|
||||
}.sign(&secret(), None);
|
||||
|
||||
state.add_balance(t.sender().as_ref().unwrap(), &(100.into()), CleanupMode::NoEmpty);
|
||||
let result = state.apply(&info, &engine, &t, true).unwrap();
|
||||
@@ -1054,7 +1059,7 @@ mod tests {
|
||||
action: Action::Call(0x1.into()),
|
||||
value: 0.into(),
|
||||
data: vec![],
|
||||
}.sign(&"".sha3(), None);
|
||||
}.sign(&secret(), None);
|
||||
|
||||
let result = state.apply(&info, engine, &t, true).unwrap();
|
||||
|
||||
@@ -1096,7 +1101,7 @@ mod tests {
|
||||
action: Action::Call(0xa.into()),
|
||||
value: 0.into(),
|
||||
data: vec![],
|
||||
}.sign(&"".sha3(), None);
|
||||
}.sign(&secret(), None);
|
||||
|
||||
state.init_code(&0xa.into(), FromHex::from_hex("600060006000600060006001610be0f1").unwrap());
|
||||
let result = state.apply(&info, engine, &t, true).unwrap();
|
||||
@@ -1139,7 +1144,7 @@ mod tests {
|
||||
action: Action::Call(0xa.into()),
|
||||
value: 0.into(),
|
||||
data: vec![],
|
||||
}.sign(&"".sha3(), None);
|
||||
}.sign(&secret(), None);
|
||||
|
||||
state.init_code(&0xa.into(), FromHex::from_hex("60006000600060006000600b611000f2").unwrap());
|
||||
state.init_code(&0xb.into(), FromHex::from_hex("6000").unwrap());
|
||||
@@ -1201,7 +1206,7 @@ mod tests {
|
||||
action: Action::Call(0xa.into()),
|
||||
value: 0.into(),
|
||||
data: vec![],
|
||||
}.sign(&"".sha3(), None);
|
||||
}.sign(&secret(), None);
|
||||
|
||||
state.init_code(&0xa.into(), FromHex::from_hex("6000600060006000600b618000f4").unwrap());
|
||||
state.init_code(&0xb.into(), FromHex::from_hex("6000").unwrap());
|
||||
@@ -1260,7 +1265,7 @@ mod tests {
|
||||
action: Action::Call(0xa.into()),
|
||||
value: 100.into(),
|
||||
data: vec![],
|
||||
}.sign(&"".sha3(), None);
|
||||
}.sign(&secret(), None);
|
||||
|
||||
state.init_code(&0xa.into(), FromHex::from_hex("5b600056").unwrap());
|
||||
state.add_balance(t.sender().as_ref().unwrap(), &(100.into()), CleanupMode::NoEmpty);
|
||||
@@ -1300,7 +1305,7 @@ mod tests {
|
||||
action: Action::Call(0xa.into()),
|
||||
value: 100.into(),
|
||||
data: vec![],
|
||||
}.sign(&"".sha3(), None);
|
||||
}.sign(&secret(), None);
|
||||
|
||||
state.init_code(&0xa.into(), FromHex::from_hex("60006000600060006000600b602b5a03f1").unwrap());
|
||||
state.init_code(&0xb.into(), FromHex::from_hex("6000").unwrap());
|
||||
@@ -1360,7 +1365,7 @@ mod tests {
|
||||
action: Action::Call(0xa.into()),
|
||||
value: 100.into(),
|
||||
data: vec![],
|
||||
}.sign(&"".sha3(), None);
|
||||
}.sign(&secret(), None);
|
||||
|
||||
state.init_code(&0xa.into(), FromHex::from_hex("60006000600060006045600b6000f1").unwrap());
|
||||
state.add_balance(t.sender().as_ref().unwrap(), &(100.into()), CleanupMode::NoEmpty);
|
||||
@@ -1415,7 +1420,7 @@ mod tests {
|
||||
action: Action::Call(0xa.into()),
|
||||
value: 100.into(),
|
||||
data: vec![],
|
||||
}.sign(&"".sha3(), None);
|
||||
}.sign(&secret(), None);
|
||||
|
||||
state.init_code(&0xa.into(), FromHex::from_hex("600060006000600060ff600b6000f1").unwrap()); // not enough funds.
|
||||
state.add_balance(t.sender().as_ref().unwrap(), &(100.into()), CleanupMode::NoEmpty);
|
||||
@@ -1458,7 +1463,7 @@ mod tests {
|
||||
action: Action::Call(0xa.into()),
|
||||
value: 100.into(),
|
||||
data: vec![],//600480600b6000396000f35b600056
|
||||
}.sign(&"".sha3(), None);
|
||||
}.sign(&secret(), None);
|
||||
|
||||
state.init_code(&0xa.into(), FromHex::from_hex("60006000600060006000600b602b5a03f1").unwrap());
|
||||
state.init_code(&0xb.into(), FromHex::from_hex("5b600056").unwrap());
|
||||
@@ -1514,7 +1519,7 @@ mod tests {
|
||||
action: Action::Call(0xa.into()),
|
||||
value: 100.into(),
|
||||
data: vec![],
|
||||
}.sign(&"".sha3(), None);
|
||||
}.sign(&secret(), None);
|
||||
|
||||
state.init_code(&0xa.into(), FromHex::from_hex("60006000600060006000600b602b5a03f1").unwrap());
|
||||
state.init_code(&0xb.into(), FromHex::from_hex("60006000600060006000600c602b5a03f1").unwrap());
|
||||
@@ -1589,7 +1594,7 @@ mod tests {
|
||||
action: Action::Call(0xa.into()),
|
||||
value: 100.into(),
|
||||
data: vec![],//600480600b6000396000f35b600056
|
||||
}.sign(&"".sha3(), None);
|
||||
}.sign(&secret(), None);
|
||||
|
||||
state.init_code(&0xa.into(), FromHex::from_hex("60006000600060006000600b602b5a03f1").unwrap());
|
||||
state.init_code(&0xb.into(), FromHex::from_hex("60006000600060006000600c602b5a03f1505b601256").unwrap());
|
||||
@@ -1662,7 +1667,7 @@ mod tests {
|
||||
action: Action::Call(0xa.into()),
|
||||
value: 100.into(),
|
||||
data: vec![],
|
||||
}.sign(&"".sha3(), None);
|
||||
}.sign(&secret(), None);
|
||||
|
||||
state.init_code(&0xa.into(), FromHex::from_hex("73000000000000000000000000000000000000000bff").unwrap());
|
||||
state.add_balance(&0xa.into(), &50.into(), CleanupMode::NoEmpty);
|
||||
|
||||
@@ -28,7 +28,7 @@ use rlp::View;
|
||||
use spec::Spec;
|
||||
use views::BlockView;
|
||||
use util::stats::Histogram;
|
||||
use ethkey::KeyPair;
|
||||
use ethkey::{KeyPair, Secret};
|
||||
use transaction::{PendingTransaction, Transaction, Action};
|
||||
use miner::MinerService;
|
||||
|
||||
@@ -290,7 +290,7 @@ fn change_history_size() {
|
||||
|
||||
#[test]
|
||||
fn does_not_propagate_delayed_transactions() {
|
||||
let key = KeyPair::from_secret("test".sha3()).unwrap();
|
||||
let key = KeyPair::from_secret(Secret::from_slice(&"test".sha3()).unwrap()).unwrap();
|
||||
let secret = key.secret();
|
||||
let tx0 = PendingTransaction::new(Transaction {
|
||||
nonce: 0.into(),
|
||||
|
||||
@@ -163,7 +163,7 @@ pub fn generate_dummy_client_with_spec_and_data<F>(get_test_spec: F, block_numbe
|
||||
let mut last_hashes = vec![];
|
||||
let mut last_header = genesis_header.clone();
|
||||
|
||||
let kp = KeyPair::from_secret("".sha3()).unwrap();
|
||||
let kp = KeyPair::from_secret_slice(&"".sha3()).unwrap();
|
||||
let author = kp.address();
|
||||
|
||||
let mut n = 0;
|
||||
|
||||
@@ -102,6 +102,7 @@ impl HeapSizeOf for Transaction {
|
||||
impl From<ethjson::state::Transaction> for SignedTransaction {
|
||||
fn from(t: ethjson::state::Transaction) -> Self {
|
||||
let to: Option<ethjson::hash::Address> = t.to.into();
|
||||
let secret = Secret::from_slice(&t.secret.0).expect("Valid secret expected.");
|
||||
Transaction {
|
||||
nonce: t.nonce.into(),
|
||||
gas_price: t.gas_price.into(),
|
||||
@@ -112,7 +113,7 @@ impl From<ethjson::state::Transaction> for SignedTransaction {
|
||||
},
|
||||
value: t.value.into(),
|
||||
data: t.data.into(),
|
||||
}.sign(&t.secret.into(), None)
|
||||
}.sign(&secret, None)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user