use std::collections::HashSet;
use crypto::{Hash, CryptoHash, PublicKey};
use blockchain::{Schema, Transaction};
use messages::{BlockRequest, BlockResponse, ConsensusMessage, Message, Precommit, Prevote,
PrevotesRequest, Propose, ProposeRequest, RawTransaction, TransactionsRequest};
use helpers::{Height, Round, ValidatorId};
use storage::Patch;
use node::{NodeHandler, RequestData};
use events::InternalRequest;
impl NodeHandler {
#[cfg_attr(feature = "flame_profile", flame)]
pub fn handle_consensus(&mut self, msg: ConsensusMessage) {
if !self.is_enabled {
info!(
"Ignoring a consensus message {:?} because the node is disabled",
msg
);
return;
}
if msg.height() < self.state.height() || msg.height() > self.state.height().next() {
warn!(
"Received consensus message from other height: msg.height={}, self.height={}",
msg.height(),
self.state.height()
);
return;
}
if msg.height() == self.state.height().next() || msg.round() > self.state.round() {
trace!(
"Received consensus message from future round: msg.height={}, msg.round={}, \
self.height={}, self.round={}",
msg.height(),
msg.round(),
self.state.height(),
self.state.round()
);
let validator = msg.validator();
let round = msg.round();
self.state.add_queued(msg);
trace!("Trying to reach actual round.");
if let Some(r) = self.state.get_actual_round(validator, round) {
trace!("Scheduling jump to round.");
let height = self.state.height();
self.execute_later(InternalRequest::JumpToRound(height, r));
}
return;
}
let key = match self.state.consensus_public_key_of(msg.validator()) {
Some(public_key) => {
if !msg.verify(&public_key) {
error!(
"Received consensus message with incorrect signature, msg={:?}",
msg
);
return;
}
public_key
}
None => {
error!("Received message from incorrect validator, msg={:?}", msg);
return;
}
};
trace!("Handle message={:?}", msg);
match msg {
ConsensusMessage::Propose(msg) => self.handle_propose(key, &msg),
ConsensusMessage::Prevote(msg) => self.handle_prevote(key, &msg),
ConsensusMessage::Precommit(msg) => self.handle_precommit(key, &msg),
}
}
pub fn handle_propose(&mut self, from: PublicKey, msg: &Propose) {
debug_assert_eq!(
Some(from),
self.state.consensus_public_key_of(msg.validator())
);
if msg.prev_hash() != self.state.last_hash() {
error!("Received propose with wrong last_block_hash msg={:?}", msg);
return;
}
if msg.validator() != self.state.leader(msg.round()) {
error!(
"Wrong propose leader detected: actual={}, expected={}",
msg.validator(),
self.state.leader(msg.round())
);
return;
}
let snapshot = self.blockchain.snapshot();
for hash in msg.transactions() {
if Schema::new(&snapshot).transactions().contains(hash) {
error!(
"Received propose with already committed transaction, msg={:?}",
msg
);
return;
}
}
if self.state.propose(&msg.hash()).is_some() {
return;
}
trace!("Handle propose");
let (hash, has_unknown_txs) = match self.state.add_propose(msg) {
Some(state) => (state.hash(), state.has_unknown_txs()),
None => return,
};
let known_nodes = self.remove_request(&RequestData::Propose(hash));
if has_unknown_txs {
trace!("REQUEST TRANSACTIONS");
self.request(RequestData::Transactions(hash), from);
for node in known_nodes {
self.request(RequestData::Transactions(hash), node);
}
} else {
self.has_full_propose(hash, msg.round());
}
}
#[cfg_attr(feature = "flame_profile", flame)]
pub fn handle_block(&mut self, msg: &BlockResponse) {
if msg.to() != self.state.consensus_public_key() {
error!(
"Received block that intended for another peer, to={}, from={}",
msg.to().to_hex(),
msg.from().to_hex()
);
return;
}
if !self.state.whitelist().allow(msg.from()) {
error!(
"Received request message from peer = {} which not in whitelist.",
msg.from().to_hex()
);
return;
}
if !msg.verify_signature(msg.from()) {
error!("Received block with incorrect signature, msg={:?}", msg);
return;
}
trace!("Handle block");
let block = msg.block();
let block_hash = block.hash();
if self.state.height() != block.height() {
return;
}
if block.prev_hash() != &self.last_block_hash() {
error!(
"Received block prev_hash is distinct from the one in db, \
block={:?}, block.prev_hash={:?}, db.last_block_hash={:?}",
msg,
*block.prev_hash(),
self.last_block_hash()
);
return;
}
if let Err(err) = self.verify_precommits(&msg.precommits(), &block_hash, block.height()) {
error!("{}, block={:?}", err, msg);
return;
}
if self.state.block(&block_hash).is_none() {
let snapshot = self.blockchain.snapshot();
let schema = Schema::new(&snapshot);
let mut tx_hashes = Vec::new();
for raw in msg.transactions() {
if let Some(tx) = self.blockchain.tx_from_raw(raw) {
let hash = tx.hash();
if schema.transactions().contains(&hash) {
error!(
"Received block with already committed transaction, block={:?}",
msg
);
return;
}
profiler_span!("tx.verify()", {
if !tx.verify() {
error!("Incorrect transaction in block detected, block={:?}", msg);
return;
}
});
self.state.add_transaction(hash, tx, true);
tx_hashes.push(hash);
} else {
error!("Unknown transaction in block detected, block={:?}", msg);
return;
}
}
let (block_hash, patch) =
self.create_block(block.proposer_id(), block.height(), tx_hashes.as_slice());
if block_hash != block.hash() {
panic!(
"Block_hash incorrect in the received block={:?}. Either a node's \
implementation is incorrect or validators majority works incorrectly",
msg
);
}
self.state.add_block(
block_hash,
patch,
tx_hashes,
block.proposer_id(),
);
}
self.commit(block_hash, msg.precommits().iter(), None);
self.request_next_block();
}
pub fn has_full_propose(&mut self, hash: Hash, propose_round: Round) {
if self.state.locked_round() == Round::zero() {
if self.state.is_validator() && !self.state.have_prevote(propose_round) {
self.broadcast_prevote(propose_round, &hash);
} else {
}
}
let start_round = ::std::cmp::max(self.state.locked_round().next(), propose_round);
for round in start_round.iter_to(self.state.round().next()) {
if self.state.has_majority_prevotes(round, hash) {
self.has_majority_prevotes(round, &hash);
}
}
for (round, block_hash) in self.state.unknown_propose_with_precommits(&hash) {
let our_block_hash = self.execute(&hash);
if our_block_hash != block_hash {
panic!(
"Full propose: wrong state hash. Either a node's implementation is \
incorrect or validators majority works incorrectly"
);
}
let precommits = self.state.precommits(round, our_block_hash).to_vec();
self.commit(our_block_hash, precommits.iter(), Some(propose_round));
}
}
pub fn handle_prevote(&mut self, from: PublicKey, msg: &Prevote) {
trace!("Handle prevote");
debug_assert_eq!(
Some(from),
self.state.consensus_public_key_of(msg.validator())
);
let has_consensus = self.state.add_prevote(msg);
let has_propose_with_txs = self.request_propose_or_txs(msg.propose_hash(), from);
if msg.locked_round() > self.state.locked_round() {
self.request(
RequestData::Prevotes(msg.locked_round(), *msg.propose_hash()),
from,
);
}
if has_consensus && has_propose_with_txs {
self.has_majority_prevotes(msg.round(), msg.propose_hash());
}
}
pub fn has_majority_prevotes(&mut self, prevote_round: Round, propose_hash: &Hash) {
self.remove_request(&RequestData::Prevotes(prevote_round, *propose_hash));
if self.state.locked_round() < prevote_round && self.state.propose(propose_hash).is_some() {
self.lock(prevote_round, *propose_hash);
}
}
pub fn has_majority_precommits(
&mut self,
round: Round,
propose_hash: &Hash,
block_hash: &Hash,
) {
if self.state.propose(propose_hash).is_none() {
self.state.add_unknown_propose_with_precommits(
round,
*propose_hash,
*block_hash,
);
return;
}
let proposer = {
let propose_state = self.state.propose(propose_hash).unwrap();
if propose_state.has_unknown_txs() {
Some(
self.state
.consensus_public_key_of(propose_state.message().validator())
.unwrap(),
)
} else {
None
}
};
if let Some(proposer) = proposer {
self.request(RequestData::Transactions(*propose_hash), proposer);
return;
}
let our_block_hash = self.execute(propose_hash);
assert_eq!(
&our_block_hash,
block_hash,
"Our block_hash different from precommits one."
);
let precommits = self.state.precommits(round, our_block_hash).to_vec();
self.commit(our_block_hash, precommits.iter(), Some(round));
}
pub fn lock(&mut self, prevote_round: Round, propose_hash: Hash) {
trace!("MAKE LOCK {:?} {:?}", prevote_round, propose_hash);
for round in prevote_round.iter_to(self.state.round().next()) {
if self.state.is_validator() && !self.state.have_prevote(round) {
self.broadcast_prevote(round, &propose_hash);
}
if self.state.has_majority_prevotes(round, propose_hash) {
self.check_propose_saved(round, &propose_hash);
let raw_messages = self.state
.prevotes(prevote_round, propose_hash)
.iter()
.map(|msg| msg.raw().clone())
.collect::<Vec<_>>();
self.blockchain.save_messages(round, raw_messages);
self.state.lock(round, propose_hash);
if self.state.is_validator() && !self.state.have_incompatible_prevotes() {
let block_hash = self.execute(&propose_hash);
self.broadcast_precommit(round, &propose_hash, &block_hash);
if self.state.has_majority_precommits(round, block_hash) {
self.has_majority_precommits(round, &propose_hash, &block_hash);
return;
}
}
self.remove_request(&RequestData::Prevotes(round, propose_hash));
}
}
}
pub fn handle_precommit(&mut self, from: PublicKey, msg: &Precommit) {
trace!("Handle precommit");
debug_assert_eq!(
Some(from),
self.state.consensus_public_key_of(msg.validator())
);
let has_consensus = self.state.add_precommit(msg);
if self.state.propose(msg.propose_hash()).is_none() {
self.request(RequestData::Propose(*msg.propose_hash()), from);
}
if msg.round() > self.state.locked_round() {
self.request(
RequestData::Prevotes(msg.round(), *msg.propose_hash()),
from,
);
}
if has_consensus {
self.has_majority_precommits(msg.round(), msg.propose_hash(), msg.block_hash());
}
}
pub fn commit<'a, I: Iterator<Item = &'a Precommit>>(
&mut self,
block_hash: Hash,
precommits: I,
round: Option<Round>,
) {
trace!("COMMIT {:?}", block_hash);
let (committed_txs, proposer) = {
let block_state = self.state.block(&block_hash).unwrap().clone();
self.blockchain
.commit(block_state.patch(), block_hash, precommits)
.unwrap();
self.state.update_config(
Schema::new(&self.blockchain.snapshot()).actual_configuration(),
);
let block_hash = self.blockchain.last_hash();
self.state.new_height(
&block_hash,
self.system_state.current_time(),
);
(block_state.txs().len(), block_state.proposer_id())
};
let mempool_size = self.state
.transactions()
.read()
.expect("Expected read lock")
.len();
metric!("node.mempool", mempool_size);
let height = self.state.height();
info!(
"COMMIT ====== height={}, proposer={}, round={}, committed={}, pool={}, hash={}",
height,
proposer,
round
.map(|x| format!("{}", x))
.unwrap_or_else(|| "?".into()),
committed_txs,
mempool_size,
block_hash.to_hex(),
);
self.broadcast_status();
self.add_status_timeout();
self.state.adjust_timeout(&*self.blockchain.snapshot());
self.add_round_timeout();
if self.state.is_leader() {
self.add_propose_timeout();
}
for msg in self.state.queued() {
self.handle_consensus(msg);
}
}
#[cfg_attr(feature = "flame_profile", flame)]
pub fn handle_tx(&mut self, msg: RawTransaction) {
let hash = msg.hash();
let tx = {
let service_id = msg.service_id();
if let Some(tx) = self.blockchain.tx_from_raw(msg) {
tx
} else {
error!(
"Received transaction with unknown service_id={}",
service_id
);
return;
}
};
profiler_span!("Make sure that it is new transaction", {
if self.state
.transactions()
.read()
.expect("Expected read lock")
.contains_key(&hash)
{
return;
}
let snapshot = self.blockchain.snapshot();
if Schema::new(&snapshot).transactions().contains(&hash) {
return;
}
});
profiler_span!("tx.verify()", {
if !tx.verify() {
return;
}
});
let full_proposes = self.state.add_transaction(hash, tx, false);
for (hash, round) in full_proposes {
self.remove_request(&RequestData::Transactions(hash));
self.has_full_propose(hash, round);
}
}
pub fn handle_incoming_tx(&mut self, msg: Box<Transaction>) {
trace!("Handle incoming transaction");
let hash = msg.hash();
if self.state
.transactions()
.read()
.expect("Expected read lock")
.contains_key(&hash)
{
return;
}
let snapshot = self.blockchain.snapshot();
if Schema::new(&snapshot).transactions().contains(&hash) {
return;
}
trace!("Broadcast transactions: {:?}", msg.raw());
self.broadcast(msg.raw());
let full_proposes = self.state.add_transaction(hash, msg, false);
for (hash, round) in full_proposes {
self.remove_request(&RequestData::Transactions(hash));
self.has_full_propose(hash, round);
}
}
pub fn handle_new_round(&mut self, height: Height, round: Round) {
trace!("Handle new round");
if height != self.state.height() {
return;
}
if round <= self.state.round() {
return;
}
info!("Jump to a new round = {}", round);
self.state.jump_round(round);
self.process_new_round();
}
fn process_new_round(&mut self) {
if self.state.is_validator() {
if let Some(hash) = self.state.locked_propose() {
let round = self.state.round();
let has_majority_prevotes = self.broadcast_prevote(round, &hash);
if has_majority_prevotes {
self.has_majority_prevotes(round, &hash);
}
} else if self.state.is_leader() {
self.add_propose_timeout();
}
}
for msg in self.state.queued() {
self.handle_consensus(msg);
}
}
pub fn handle_round_timeout(&mut self, height: Height, round: Round) {
if height != self.state.height() {
return;
}
if round != self.state.round() {
return;
}
warn!("ROUND TIMEOUT height={}, round={}", height, round);
self.state.new_round();
self.add_round_timeout();
self.process_new_round();
}
pub fn handle_propose_timeout(&mut self, height: Height, round: Round) {
if height != self.state.height() {
return;
}
if round != self.state.round() {
return;
}
if self.state.locked_propose().is_some() {
return;
}
if let Some(validator_id) = self.state.validator_id() {
if self.state.have_prevote(round) {
return;
}
let pool_len = self.state
.transactions()
.read()
.expect("Expected read lock")
.len();
info!("LEADER: pool = {}", pool_len);
let round = self.state.round();
let max_count = ::std::cmp::min(self.txs_block_limit() as usize, pool_len);
let txs: Vec<Hash> = self.state
.transactions()
.read()
.expect("Expected read lock")
.keys()
.take(max_count)
.cloned()
.collect();
let propose = Propose::new(
validator_id,
self.state.height(),
round,
self.state.last_hash(),
&txs,
self.state.consensus_secret_key(),
);
self.blockchain.save_message(round, propose.raw());
trace!("Broadcast propose: {:?}", propose);
self.broadcast(propose.raw());
let hash = self.state.add_self_propose(propose);
let has_majority_prevotes = self.broadcast_prevote(round, &hash);
if has_majority_prevotes {
self.has_majority_prevotes(round, &hash);
}
}
}
pub fn handle_request_timeout(&mut self, data: &RequestData, peer: Option<PublicKey>) {
trace!("HANDLE REQUEST TIMEOUT");
if let Some(peer) = self.state.retry(data, peer) {
self.add_request_timeout(data.clone(), Some(peer));
let message = match *data {
RequestData::Propose(ref propose_hash) => {
ProposeRequest::new(
self.state.consensus_public_key(),
&peer,
self.state.height(),
propose_hash,
self.state.consensus_secret_key(),
).raw()
.clone()
}
RequestData::Transactions(ref propose_hash) => {
let txs: Vec<_> = self.state
.propose(propose_hash)
.unwrap()
.unknown_txs()
.iter()
.cloned()
.collect();
TransactionsRequest::new(
self.state.consensus_public_key(),
&peer,
&txs,
self.state.consensus_secret_key(),
).raw()
.clone()
}
RequestData::Prevotes(round, ref propose_hash) => {
PrevotesRequest::new(
self.state.consensus_public_key(),
&peer,
self.state.height(),
round,
propose_hash,
self.state.known_prevotes(round, propose_hash),
self.state.consensus_secret_key(),
).raw()
.clone()
}
RequestData::Block(height) => {
BlockRequest::new(
self.state.consensus_public_key(),
&peer,
height,
self.state.consensus_secret_key(),
).raw()
.clone()
}
};
trace!("Send request {:?} to peer {:?}", data, peer);
self.send_to_peer(peer, &message);
}
}
pub fn create_block(
&mut self,
proposer_id: ValidatorId,
height: Height,
tx_hashes: &[Hash],
) -> (Hash, Patch) {
self.blockchain.create_patch(
proposer_id,
height,
tx_hashes,
&self.state.transactions().read().expect(
"Expected read lock",
),
)
}
#[cfg_attr(feature = "flame_profile", flame)]
pub fn execute(&mut self, propose_hash: &Hash) -> Hash {
if let Some(hash) = self.state.propose_mut(propose_hash).unwrap().block_hash() {
return hash;
}
let propose = self.state.propose(propose_hash).unwrap().message().clone();
let tx_hashes = propose.transactions().to_vec();
let (block_hash, patch) =
self.create_block(propose.validator(), propose.height(), tx_hashes.as_slice());
self.state.add_block(
block_hash,
patch,
tx_hashes,
propose.validator(),
);
self.state
.propose_mut(propose_hash)
.unwrap()
.set_block_hash(block_hash);
block_hash
}
pub fn request_propose_or_txs(&mut self, propose_hash: &Hash, key: PublicKey) -> bool {
let requested_data = match self.state.propose(propose_hash) {
Some(state) => {
if state.has_unknown_txs() {
Some(RequestData::Transactions(*propose_hash))
} else {
None
}
}
None => {
Some(RequestData::Propose(*propose_hash))
}
};
if let Some(data) = requested_data.clone() {
self.request(data, key);
false
} else {
true
}
}
pub fn request_next_block(&mut self) {
let heights: Vec<_> = self.state
.nodes_with_bigger_height()
.into_iter()
.cloned()
.collect();
if !heights.is_empty() {
for peer in heights {
if self.state.peers().contains_key(&peer) {
let height = self.state.height();
self.request(RequestData::Block(height), peer);
break;
}
}
}
}
pub fn remove_request(&mut self, data: &RequestData) -> HashSet<PublicKey> {
self.state.remove_request(data)
}
pub fn broadcast_prevote(&mut self, round: Round, propose_hash: &Hash) -> bool {
let validator_id = self.state.validator_id().expect(
"called broadcast_prevote in Auditor node.",
);
let locked_round = self.state.locked_round();
let prevote = Prevote::new(
validator_id,
self.state.height(),
round,
propose_hash,
locked_round,
self.state.consensus_secret_key(),
);
let has_majority_prevotes = self.state.add_prevote(&prevote);
self.check_propose_saved(round, propose_hash);
self.blockchain.save_message(round, prevote.raw());
trace!("Broadcast prevote: {:?}", prevote);
self.broadcast(prevote.raw());
has_majority_prevotes
}
pub fn broadcast_precommit(&mut self, round: Round, propose_hash: &Hash, block_hash: &Hash) {
let validator_id = self.state.validator_id().expect(
"called broadcast_precommit in Auditor node.",
);
let precommit = Precommit::new(
validator_id,
self.state.height(),
round,
propose_hash,
block_hash,
self.system_state.current_time(),
self.state.consensus_secret_key(),
);
self.state.add_precommit(&precommit);
self.blockchain.save_message(round, precommit.raw());
trace!("Broadcast precommit: {:?}", precommit);
self.broadcast(precommit.raw());
}
fn verify_precommits(
&self,
precommits: &[Precommit],
block_hash: &Hash,
block_height: Height,
) -> Result<(), String> {
if precommits.len() < self.state.majority_count() {
return Err("Received block without consensus".to_string());
} else if precommits.len() > self.state.validators().len() {
return Err("Wrong precommits count in block".to_string());
}
let mut validators = HashSet::with_capacity(precommits.len());
let round = precommits[0].round();
for precommit in precommits {
if !validators.insert(precommit.validator()) {
return Err("Several precommits from one validator in block".to_string());
}
self.verify_precommit(
block_hash,
block_height,
round,
precommit,
)?;
}
Ok(())
}
fn verify_precommit(
&self,
block_hash: &Hash,
block_height: Height,
precommit_round: Round,
precommit: &Precommit,
) -> Result<(), String> {
if let Some(pub_key) = self.state.consensus_public_key_of(precommit.validator()) {
if !precommit.verify_signature(&pub_key) {
let e = format!("Received wrong signed precommit, precommit={:?}", precommit);
return Err(e);
}
if precommit.block_hash() != block_hash {
let e = format!(
"Received precommit with wrong block_hash, precommit={:?}",
precommit
);
return Err(e);
}
if precommit.height() != block_height {
let e = format!(
"Received precommit with wrong height, precommit={:?}",
precommit
);
return Err(e);
}
if precommit.round() != precommit_round {
let e = format!(
"Received precommits with the different rounds, precommit={:?}",
precommit
);
return Err(e);
}
} else {
let e = format!(
"Received precommit with wrong validator, precommit={:?}",
precommit
);
return Err(e);
}
Ok(())
}
fn check_propose_saved(&mut self, round: Round, propose_hash: &Hash) {
if let Some(propose_state) = self.state.propose_mut(propose_hash) {
if !propose_state.is_saved() {
self.blockchain.save_message(
round,
propose_state.message().raw(),
);
propose_state.set_saved(true);
}
}
}
}