use bit_vec::BitVec;
use exonum::{
blockchain::{Epoch, ProposerId},
crypto::Hash,
helpers::{Height, Round, ValidatorId},
merkledb::ObjectHash,
messages::Verified,
};
use std::{collections::HashSet, convert::TryFrom, time::Duration};
use crate::{
messages::{PrevotesRequest, TransactionsRequest},
sandbox::{
sandbox_tests_helper::*, timestamping_sandbox, timestamping_sandbox_builder, Sandbox,
},
state::{PREVOTES_REQUEST_TIMEOUT, PROPOSE_REQUEST_TIMEOUT, TRANSACTIONS_REQUEST_TIMEOUT},
};
#[test]
fn positive_get_propose_send_prevote() {
let sandbox = timestamping_sandbox();
let propose = ProposeBuilder::new(&sandbox).build();
sandbox.recv(&propose);
sandbox.assert_lock(NOT_LOCKED, None);
sandbox.broadcast(&sandbox.create_prevote(
ValidatorId(0),
Height(1),
Round(1),
propose.object_hash(),
NOT_LOCKED,
sandbox.secret_key(ValidatorId(0)),
));
}
#[test]
fn request_propose_when_get_prevote() {
let sandbox = timestamping_sandbox();
sandbox.recv(&sandbox.create_prevote(
ValidatorId(2),
Height(1),
Round(1),
Hash::zero(),
NOT_LOCKED,
sandbox.secret_key(ValidatorId(2)),
));
sandbox.add_time(Duration::from_millis(sandbox.current_round_timeout() - 1));
sandbox.send(
sandbox.public_key(ValidatorId(2)),
&Sandbox::create_propose_request(
sandbox.public_key(ValidatorId(0)),
sandbox.public_key(ValidatorId(2)),
Height(1),
Hash::zero(),
sandbox.secret_key(ValidatorId(0)),
),
);
sandbox.add_time(Duration::from_millis(0));
}
#[test]
fn request_prevotes_when_get_prevote_message() {
let sandbox = timestamping_sandbox();
sandbox.recv(&sandbox.create_prevote(
ValidatorId(2),
Height(1),
Round(1),
Hash::zero(),
Round(1),
sandbox.secret_key(ValidatorId(2)),
));
sandbox.add_time(Duration::from_millis(sandbox.current_round_timeout() - 1));
sandbox.send(
sandbox.public_key(ValidatorId(2)),
&Sandbox::create_propose_request(
sandbox.public_key(ValidatorId(0)),
sandbox.public_key(ValidatorId(2)),
Height(1),
Hash::zero(),
sandbox.secret_key(ValidatorId(0)),
),
);
let mut validators = BitVec::from_elem(sandbox.validators().len(), false);
validators.set(ValidatorId(2).into(), true);
sandbox.send(
sandbox.public_key(ValidatorId(2)),
&Sandbox::create_prevote_request(
sandbox.public_key(ValidatorId(0)),
sandbox.public_key(ValidatorId(2)),
Height(1),
Round(1),
Hash::zero(),
validators,
sandbox.secret_key(ValidatorId(0)),
),
);
sandbox.add_time(Duration::from_millis(0));
}
#[test]
fn lock_to_propose_when_get_2_3_prevote_positive() {
let sandbox = timestamping_sandbox();
let propose = ProposeBuilder::new(&sandbox).build();
let block = BlockBuilder::new(&sandbox).build();
sandbox.recv(&propose);
sandbox.broadcast(&sandbox.create_prevote(
ValidatorId(0),
Height(1),
Round(1),
propose.object_hash(),
NOT_LOCKED,
sandbox.secret_key(ValidatorId(0)),
));
sandbox.recv(&sandbox.create_prevote(
ValidatorId(1),
Height(1),
Round(1),
propose.object_hash(),
NOT_LOCKED,
sandbox.secret_key(ValidatorId(1)),
));
sandbox.assert_lock(NOT_LOCKED, None);
sandbox.recv(&sandbox.create_prevote(
ValidatorId(2),
Height(1),
Round(1),
propose.object_hash(),
NOT_LOCKED,
sandbox.secret_key(ValidatorId(2)),
));
sandbox.assert_lock(Round(1), Some(propose.object_hash()));
sandbox.broadcast(&sandbox.create_precommit(
ValidatorId(0),
Height(1),
Round(1),
propose.object_hash(),
block.object_hash(),
sandbox.time().into(),
sandbox.secret_key(ValidatorId(0)),
));
sandbox.assert_lock(Round(1), Some(propose.object_hash()));
sandbox.add_time(Duration::from_millis(0));
{
sandbox.add_time(Duration::from_millis(sandbox.current_round_timeout()));
sandbox.broadcast(&sandbox.create_prevote(
ValidatorId(0),
Height(1),
Round(2),
propose.object_hash(),
Round(1),
sandbox.secret_key(ValidatorId(0)),
));
sandbox.add_time(Duration::from_millis(sandbox.current_round_timeout()));
sandbox.broadcast(&sandbox.create_prevote(
ValidatorId(0),
Height(1),
Round(3),
propose.object_hash(),
Round(1),
sandbox.secret_key(ValidatorId(0)),
));
}
sandbox.add_time(Duration::from_millis(0));
}
#[test]
fn lock_to_past_round_broadcast_prevote() {
let sandbox = timestamping_sandbox();
sandbox.add_time(Duration::from_millis(PROPOSE_TIMEOUT));
let propose = ProposeBuilder::new(&sandbox).build();
let block = BlockBuilder::new(&sandbox).build();
sandbox.recv(&propose);
sandbox.broadcast(&make_prevote_from_propose(&sandbox, &propose));
sandbox.add_time(Duration::from_millis(
sandbox.current_round_timeout() - PROPOSE_TIMEOUT,
));
sandbox.assert_state(Height(1), Round(2));
sandbox.recv(&sandbox.create_prevote(
ValidatorId(1),
Height(1),
Round(1),
propose.object_hash(),
NOT_LOCKED,
sandbox.secret_key(ValidatorId(1)),
));
sandbox.assert_lock(NOT_LOCKED, None);
sandbox.recv(&sandbox.create_prevote(
ValidatorId(2),
Height(1),
Round(1),
propose.object_hash(),
NOT_LOCKED,
sandbox.secret_key(ValidatorId(2)),
));
sandbox.assert_lock(Round(1), Some(propose.object_hash()));
sandbox.broadcast(&sandbox.create_precommit(
ValidatorId(0),
Height(1),
Round(1),
propose.object_hash(),
block.object_hash(),
sandbox.time().into(),
sandbox.secret_key(ValidatorId(0)),
));
sandbox.assert_lock(Round(1), Some(propose.object_hash()));
sandbox.broadcast(&sandbox.create_prevote(
ValidatorId(0),
Height(1),
Round(2),
propose.object_hash(),
Round(1),
sandbox.secret_key(ValidatorId(0)),
));
sandbox.add_time(Duration::from_millis(0));
{
sandbox.add_time(Duration::from_millis(sandbox.current_round_timeout()));
sandbox.broadcast(&sandbox.create_prevote(
ValidatorId(0),
Height(1),
Round(3),
propose.object_hash(),
Round(1),
sandbox.secret_key(ValidatorId(0)),
));
sandbox.add_time(Duration::from_millis(sandbox.current_round_timeout()));
sandbox.broadcast(&sandbox.create_prevote(
ValidatorId(0),
Height(1),
Round(4),
propose.object_hash(),
Round(1),
sandbox.secret_key(ValidatorId(0)),
));
}
sandbox.add_time(Duration::from_millis(0));
}
#[test]
fn handle_precommit_remove_request_prevotes() {
let sandbox = timestamping_sandbox();
let propose = ProposeBuilder::new(&sandbox).build();
let block = BlockBuilder::new(&sandbox).build();
sandbox.recv(&propose);
sandbox.broadcast(&sandbox.create_prevote(
ValidatorId(0),
Height(1),
Round(1),
propose.object_hash(),
NOT_LOCKED,
sandbox.secret_key(ValidatorId(0)),
));
sandbox.recv(&sandbox.create_prevote(
ValidatorId(1),
Height(1),
Round(1),
propose.object_hash(),
NOT_LOCKED,
sandbox.secret_key(ValidatorId(1)),
));
sandbox.assert_lock(NOT_LOCKED, None);
{
sandbox.recv(&sandbox.create_prevote(
ValidatorId(2),
Height(1),
Round(1),
propose.object_hash(),
NOT_LOCKED,
sandbox.secret_key(ValidatorId(2)),
));
sandbox.assert_lock(Round(1), Some(propose.object_hash()));
sandbox.broadcast(&sandbox.create_precommit(
ValidatorId(0),
Height(1),
Round(1),
propose.object_hash(),
block.object_hash(),
sandbox.time().into(),
sandbox.secret_key(ValidatorId(0)),
));
sandbox.assert_lock(Round(1), Some(propose.object_hash()));
sandbox.add_time(Duration::from_millis(0));
}
sandbox.recv(&sandbox.create_precommit(
ValidatorId(1),
Height(1),
Round(1),
propose.object_hash(),
block.object_hash(),
sandbox.time().into(),
sandbox.secret_key(ValidatorId(1)),
));
sandbox.add_time(Duration::from_millis(PREVOTES_REQUEST_TIMEOUT));
}
#[test]
fn lock_to_propose_and_send_prevote() {
let sandbox = timestamping_sandbox();
let empty_propose = ProposeBuilder::new(&sandbox).build();
sandbox.recv(&empty_propose);
sandbox.broadcast(&make_prevote_from_propose(&sandbox, &empty_propose));
let tx = gen_timestamping_tx();
sandbox.recv(&tx);
let propose = ProposeBuilder::new(&sandbox)
.with_tx_hashes(&[tx.object_hash()])
.build();
let block = sandbox.create_block(&[tx]);
sandbox.recv(&propose);
sandbox.add_time(Duration::from_millis(sandbox.current_round_timeout()));
sandbox.recv(&sandbox.create_prevote(
ValidatorId(1),
Height(1),
Round(2),
propose.object_hash(),
NOT_LOCKED,
sandbox.secret_key(ValidatorId(1)),
));
sandbox.assert_lock(NOT_LOCKED, None);
sandbox.recv(&sandbox.create_prevote(
ValidatorId(2),
Height(1),
Round(2),
propose.object_hash(),
NOT_LOCKED,
sandbox.secret_key(ValidatorId(2)),
));
sandbox.assert_lock(NOT_LOCKED, None);
sandbox.recv(&sandbox.create_prevote(
ValidatorId(3),
Height(1),
Round(2),
propose.object_hash(),
NOT_LOCKED,
sandbox.secret_key(ValidatorId(3)),
));
sandbox.assert_lock(Round(2), Some(propose.object_hash()));
sandbox.broadcast(&sandbox.create_prevote(
ValidatorId(0),
Height(1),
Round(2),
propose.object_hash(),
NOT_LOCKED,
sandbox.secret_key(ValidatorId(0)),
));
sandbox.broadcast(&sandbox.create_precommit(
ValidatorId(0),
Height(1),
Round(2),
propose.object_hash(),
block.object_hash(),
sandbox.time().into(),
sandbox.secret_key(ValidatorId(0)),
));
sandbox.assert_lock(Round(2), Some(propose.object_hash()));
sandbox.add_time(Duration::from_millis(0));
}
#[test]
fn lock_remove_request_prevotes() {
let sandbox = timestamping_sandbox();
sandbox.add_time(Duration::from_millis(sandbox.current_round_timeout()));
let propose = ProposeBuilder::new(&sandbox).build();
let block = BlockBuilder::new(&sandbox).build();
sandbox.recv(&propose);
sandbox.broadcast(&make_prevote_from_propose(&sandbox, &propose));
sandbox.recv(&sandbox.create_prevote(
ValidatorId(2),
Height(1),
Round(1),
propose.object_hash(),
Round(1),
sandbox.secret_key(ValidatorId(2)),
));
sandbox.recv(&sandbox.create_prevote(
ValidatorId(3),
Height(1),
Round(1),
propose.object_hash(),
Round(1),
sandbox.secret_key(ValidatorId(3)),
));
{
sandbox.recv(&sandbox.create_prevote(
ValidatorId(1),
Height(1),
Round(1),
propose.object_hash(),
Round(1),
sandbox.secret_key(ValidatorId(1)),
));
sandbox.broadcast(&sandbox.create_prevote(
ValidatorId(0),
Height(1),
Round(1),
propose.object_hash(),
NOT_LOCKED,
sandbox.secret_key(ValidatorId(0)),
));
sandbox.broadcast(&sandbox.create_precommit(
ValidatorId(0),
Height(1),
Round(1),
propose.object_hash(),
block.object_hash(),
sandbox.time().into(),
sandbox.secret_key(ValidatorId(0)),
));
}
sandbox.add_time(Duration::from_millis(PREVOTES_REQUEST_TIMEOUT));
}
#[test]
#[should_panic(expected = "handle_majority_precommits: wrong block hash.")]
fn handle_precommit_different_block_hash() {
let sandbox = timestamping_sandbox();
let tx = gen_timestamping_tx();
let propose = ProposeBuilder::new(&sandbox)
.with_tx_hashes(&[tx.object_hash()]) .build();
let block = BlockBuilder::new(&sandbox).build();
let precommit_1 = sandbox.create_precommit(
ValidatorId(1),
Height(1),
Round(1),
propose.object_hash(),
block.object_hash(),
sandbox.time().into(),
sandbox.secret_key(ValidatorId(1)),
);
let precommit_2 = sandbox.create_precommit(
ValidatorId(2),
Height(1),
Round(1),
propose.object_hash(),
block.object_hash(),
sandbox.time().into(),
sandbox.secret_key(ValidatorId(2)),
);
let precommit_3 = sandbox.create_precommit(
ValidatorId(3),
Height(1),
Round(1),
propose.object_hash(),
block.object_hash(),
sandbox.time().into(),
sandbox.secret_key(ValidatorId(3)),
);
sandbox.recv(&precommit_1);
sandbox.add_time(Duration::from_millis(PROPOSE_REQUEST_TIMEOUT));
sandbox.send(
sandbox.public_key(ValidatorId(1)),
&make_request_propose_from_precommit(&sandbox, precommit_1.as_ref()),
);
sandbox.send(
sandbox.public_key(ValidatorId(1)),
&make_request_prevote_from_precommit(&sandbox, precommit_1.as_ref()),
);
sandbox.recv(&propose);
sandbox.recv(&tx);
sandbox.broadcast(&make_prevote_from_propose(&sandbox, &propose));
sandbox.recv(&precommit_2);
sandbox.recv(&precommit_3);
}
fn test_handle_precommit_incorrect_tx(known_before_propose: bool) {
let sandbox = timestamping_sandbox();
let incorrect_tx = gen_incorrect_tx();
let propose = ProposeBuilder::new(&sandbox)
.with_tx_hashes(&[incorrect_tx.object_hash()])
.build();
let block = sandbox.create_block(&[incorrect_tx.clone()]);
let precommit_1 = sandbox.create_precommit(
ValidatorId(1),
Height(1),
Round(1),
propose.object_hash(),
block.object_hash(),
sandbox.time().into(),
sandbox.secret_key(ValidatorId(1)),
);
let precommit_2 = sandbox.create_precommit(
ValidatorId(2),
Height(1),
Round(1),
propose.object_hash(),
block.object_hash(),
sandbox.time().into(),
sandbox.secret_key(ValidatorId(2)),
);
let precommit_3 = sandbox.create_precommit(
ValidatorId(3),
Height(1),
Round(1),
propose.object_hash(),
block.object_hash(),
sandbox.time().into(),
sandbox.secret_key(ValidatorId(3)),
);
sandbox.recv(&precommit_1);
sandbox.add_time(Duration::from_millis(PROPOSE_REQUEST_TIMEOUT));
sandbox.send(
sandbox.public_key(ValidatorId(1)),
&make_request_propose_from_precommit(&sandbox, precommit_1.as_ref()),
);
sandbox.send(
sandbox.public_key(ValidatorId(1)),
&make_request_prevote_from_precommit(&sandbox, precommit_1.as_ref()),
);
if known_before_propose {
sandbox.recv(&incorrect_tx);
sandbox.assert_tx_cache_len(0);
sandbox.recv(&propose);
} else {
sandbox.recv(&propose);
sandbox.recv(&incorrect_tx);
}
sandbox.recv(&precommit_2);
sandbox.recv(&precommit_3);
}
#[test]
#[should_panic(expected = "handle_majority_precommits: propose contains")]
fn handle_precommit_incorrect_tx() {
test_handle_precommit_incorrect_tx(false);
}
#[test]
#[should_panic(expected = "handle_majority_precommits: propose contains")]
fn handle_precommit_incorrect_tx_received_before_propose() {
test_handle_precommit_incorrect_tx(true);
}
#[test]
fn not_sending_precommit_for_proposal_with_incorrect_tx() {
let sandbox = timestamping_sandbox();
let tx = gen_timestamping_tx();
let propose = ProposeBuilder::new(&sandbox)
.with_tx_hashes(&[tx.object_hash()])
.build();
let block = sandbox.create_block(&[tx]);
let precommit_1 = sandbox.create_precommit(
ValidatorId(1),
Height(1),
Round(1),
propose.object_hash(),
block.object_hash(),
sandbox.time().into(),
sandbox.secret_key(ValidatorId(1)),
);
sandbox.recv(&precommit_1);
sandbox.add_time(Duration::from_millis(PROPOSE_REQUEST_TIMEOUT));
sandbox.send(
sandbox.public_key(ValidatorId(1)),
&make_request_propose_from_precommit(&sandbox, precommit_1.as_ref()),
);
sandbox.send(
sandbox.public_key(ValidatorId(1)),
&make_request_prevote_from_precommit(&sandbox, precommit_1.as_ref()),
);
let incorrect_tx = gen_incorrect_tx();
let incorrect_propose = ProposeBuilder::new(&sandbox)
.with_tx_hashes(&[incorrect_tx.object_hash()])
.build();
sandbox.recv(&incorrect_propose);
sandbox.recv(&incorrect_tx);
sandbox.recv(&sandbox.create_prevote(
ValidatorId(1),
Height(1),
Round(1),
propose.object_hash(),
NOT_LOCKED,
sandbox.secret_key(ValidatorId(1)),
));
sandbox.assert_lock(NOT_LOCKED, None);
sandbox.recv(&sandbox.create_prevote(
ValidatorId(2),
Height(1),
Round(1),
propose.object_hash(),
NOT_LOCKED,
sandbox.secret_key(ValidatorId(2)),
));
sandbox.assert_lock(NOT_LOCKED, None); sandbox.recv(&sandbox.create_prevote(
ValidatorId(3),
Height(1),
Round(1),
propose.object_hash(),
NOT_LOCKED,
sandbox.secret_key(ValidatorId(3)),
));
sandbox.add_time(Duration::from_millis(0));
}
#[test]
fn invalid_tx_does_not_invalidate_unrelated_proposes() {
let sandbox = timestamping_sandbox();
let invalid_tx = gen_incorrect_tx();
let propose = ProposeBuilder::new(&sandbox).with_tx_hashes(&[]).build();
sandbox.recv(&propose);
let our_prevote = sandbox.create_prevote(
ValidatorId(0),
Height(1),
Round(1),
propose.object_hash(),
NOT_LOCKED,
sandbox.secret_key(ValidatorId(0)),
);
sandbox.broadcast(&our_prevote);
sandbox.recv(&invalid_tx);
{
let inner = sandbox.inner.borrow();
let propose_state = inner.handler.state.propose(&propose.object_hash()).unwrap();
assert!(!propose_state.has_invalid_txs());
}
let block = sandbox.create_block(&[]);
let precommits = (1..4).map(|i| {
let validator_id = ValidatorId(i);
sandbox.create_precommit(
validator_id,
Height(1),
Round(1),
propose.object_hash(),
block.object_hash(),
sandbox.time().into(),
sandbox.secret_key(validator_id),
)
});
for precommit in precommits {
sandbox.recv(&precommit);
}
sandbox.assert_state(Height(2), Round(1));
sandbox.check_broadcast_status(Height(2), block.object_hash());
}
#[test]
fn handle_precommit_positive_scenario_commit() {
let sandbox = timestamping_sandbox();
let tx = gen_timestamping_tx();
let propose = ProposeBuilder::new(&sandbox)
.with_tx_hashes(&[tx.object_hash()]) .build();
let block = sandbox.create_block(&[tx.clone()]);
let precommit_1 = sandbox.create_precommit(
ValidatorId(1),
Height(1),
Round(1),
propose.object_hash(),
block.object_hash(),
sandbox.time().into(),
sandbox.secret_key(ValidatorId(1)),
);
let precommit_2 = sandbox.create_precommit(
ValidatorId(2),
Height(1),
Round(1),
propose.object_hash(),
block.object_hash(),
sandbox.time().into(),
sandbox.secret_key(ValidatorId(2)),
);
let precommit_3 = sandbox.create_precommit(
ValidatorId(3),
Height(1),
Round(1),
propose.object_hash(),
block.object_hash(),
sandbox.time().into(),
sandbox.secret_key(ValidatorId(3)),
);
sandbox.recv(&precommit_1);
sandbox.add_time(Duration::from_millis(PROPOSE_REQUEST_TIMEOUT));
sandbox.send(
sandbox.public_key(ValidatorId(1)),
&make_request_propose_from_precommit(&sandbox, precommit_1.as_ref()),
);
sandbox.send(
sandbox.public_key(ValidatorId(1)),
&make_request_prevote_from_precommit(&sandbox, precommit_1.as_ref()),
);
sandbox.recv(&precommit_2);
sandbox.add_time(Duration::from_millis(PROPOSE_REQUEST_TIMEOUT));
sandbox.send(
sandbox.public_key(ValidatorId(2)),
&make_request_propose_from_precommit(&sandbox, precommit_2.as_ref()),
);
sandbox.send(
sandbox.public_key(ValidatorId(2)),
&make_request_prevote_from_precommit(&sandbox, precommit_2.as_ref()),
);
sandbox.recv(&propose);
sandbox.recv(&tx);
sandbox.broadcast(&make_prevote_from_propose(&sandbox, &propose));
sandbox.assert_state(Height(1), Round(1));
sandbox.recv(&precommit_3);
sandbox.assert_state(Height(2), Round(1));
sandbox.check_broadcast_status(Height(2), block.object_hash());
sandbox.add_time(Duration::from_millis(0));
}
fn test_transaction_after_propose_and_precommits(precommits_before_propose: usize) {
let sandbox = timestamping_sandbox();
let tx = gen_timestamping_tx();
let propose = ProposeBuilder::new(&sandbox)
.with_validator(ValidatorId(2))
.with_tx_hashes(&[tx.object_hash()])
.with_height(Height(1))
.with_round(Round(1))
.with_prev_hash(&sandbox.last_hash())
.build();
let block = sandbox.create_block(&[tx.clone()]);
let mut precommits = (1..4).map(|i| {
let validator_id = ValidatorId(i);
sandbox.create_precommit(
validator_id,
Height(1),
Round(1),
propose.object_hash(),
block.object_hash(),
sandbox.time().into(),
sandbox.secret_key(validator_id),
)
});
for precommit in precommits.by_ref().take(precommits_before_propose) {
sandbox.recv(&precommit);
}
sandbox.recv(&propose);
for precommit in precommits {
sandbox.recv(&precommit);
}
sandbox.recv(&tx);
sandbox.assert_state(Height(2), Round(1));
let our_prevote = sandbox.create_prevote(
ValidatorId(0),
Height(1),
Round(1),
propose.object_hash(),
Round::zero(),
sandbox.secret_key(ValidatorId(0)),
);
sandbox.broadcast(&our_prevote);
sandbox.check_broadcast_status(Height(2), block.object_hash());
}
#[test]
fn transaction_after_propose_and_precommits() {
test_transaction_after_propose_and_precommits(0);
}
#[test]
fn transaction_after_1_precommit_and_propose() {
test_transaction_after_propose_and_precommits(1);
}
#[test]
fn transaction_after_2_precommits_and_propose() {
test_transaction_after_propose_and_precommits(2);
}
#[test]
fn transaction_after_all_precommits_and_propose() {
test_transaction_after_propose_and_precommits(3);
}
#[test]
fn lock_not_send_prevotes_after_commit() {
let sandbox = timestamping_sandbox();
let propose = ProposeBuilder::new(&sandbox).build();
let block = BlockBuilder::new(&sandbox).build();
let precommit_1 = sandbox.create_precommit(
ValidatorId(1),
Height(1),
Round(1),
propose.object_hash(),
block.object_hash(),
sandbox.time().into(),
sandbox.secret_key(ValidatorId(1)),
);
let precommit_2 = sandbox.create_precommit(
ValidatorId(2),
Height(1),
Round(1),
propose.object_hash(),
block.object_hash(),
sandbox.time().into(),
sandbox.secret_key(ValidatorId(2)),
);
{
sandbox.recv(&precommit_1);
sandbox.add_time(Duration::from_millis(PROPOSE_REQUEST_TIMEOUT));
sandbox.send(
sandbox.public_key(ValidatorId(1)),
&make_request_propose_from_precommit(&sandbox, precommit_1.as_ref()),
);
sandbox.send(
sandbox.public_key(ValidatorId(1)),
&make_request_prevote_from_precommit(&sandbox, precommit_1.as_ref()),
);
}
{
sandbox.recv(&precommit_2);
sandbox.add_time(Duration::from_millis(PROPOSE_REQUEST_TIMEOUT));
sandbox.send(
sandbox.public_key(ValidatorId(2)),
&make_request_propose_from_precommit(&sandbox, precommit_2.as_ref()),
);
sandbox.send(
sandbox.public_key(ValidatorId(2)),
&make_request_prevote_from_precommit(&sandbox, precommit_2.as_ref()),
);
}
{
sandbox.recv(&propose);
sandbox.broadcast(&make_prevote_from_propose(&sandbox, &propose));
sandbox.recv(&sandbox.create_prevote(
ValidatorId(2),
Height(1),
Round(1),
propose.object_hash(),
NOT_LOCKED,
sandbox.secret_key(ValidatorId(2)),
));
sandbox.assert_lock(NOT_LOCKED, None);
sandbox.recv(&sandbox.create_prevote(
ValidatorId(3),
Height(1),
Round(1),
propose.object_hash(),
NOT_LOCKED,
sandbox.secret_key(ValidatorId(3)),
));
sandbox.broadcast(&sandbox.create_precommit(
ValidatorId(0),
Height(1),
Round(1),
propose.object_hash(),
block.object_hash(),
sandbox.time().into(),
sandbox.secret_key(ValidatorId(0)),
));
sandbox.check_broadcast_status(Height(2), block.object_hash());
}
sandbox.add_time(Duration::from_millis(sandbox.current_round_timeout()));
{
}
}
#[test]
fn do_not_commit_if_propose_is_unknown() {
let sandbox = timestamping_sandbox();
let tx = gen_timestamping_tx();
let propose = ProposeBuilder::new(&sandbox)
.with_tx_hashes(&[tx.object_hash()]) .build();
let block = BlockBuilder::new(&sandbox)
.with_tx_hash(&tx.object_hash())
.build();
let precommit_1 = sandbox.create_precommit(
ValidatorId(1),
Height(1),
Round(1),
propose.object_hash(),
block.object_hash(),
sandbox.time().into(),
sandbox.secret_key(ValidatorId(1)),
);
let precommit_2 = sandbox.create_precommit(
ValidatorId(2),
Height(1),
Round(1),
propose.object_hash(),
block.object_hash(),
sandbox.time().into(),
sandbox.secret_key(ValidatorId(2)),
);
let precommit_3 = sandbox.create_precommit(
ValidatorId(3),
Height(1),
Round(1),
propose.object_hash(),
block.object_hash(),
sandbox.time().into(),
sandbox.secret_key(ValidatorId(3)),
);
sandbox.recv(&precommit_1);
sandbox.add_time(Duration::from_millis(PROPOSE_REQUEST_TIMEOUT));
sandbox.send(
sandbox.public_key(ValidatorId(1)),
&make_request_propose_from_precommit(&sandbox, precommit_1.as_ref()),
);
sandbox.send(
sandbox.public_key(ValidatorId(1)),
&make_request_prevote_from_precommit(&sandbox, precommit_1.as_ref()),
);
sandbox.recv(&precommit_2);
sandbox.add_time(Duration::from_millis(PROPOSE_REQUEST_TIMEOUT));
sandbox.send(
sandbox.public_key(ValidatorId(2)),
&make_request_propose_from_precommit(&sandbox, precommit_2.as_ref()),
);
sandbox.send(
sandbox.public_key(ValidatorId(2)),
&make_request_prevote_from_precommit(&sandbox, precommit_2.as_ref()),
);
sandbox.assert_state(Height(1), Round(1));
sandbox.recv(&precommit_3);
sandbox.assert_state(Height(1), Round(1));
sandbox.add_time(Duration::from_millis(0));
}
#[test]
fn do_not_commit_if_tx_is_unknown() {
let sandbox = timestamping_sandbox();
let tx = gen_timestamping_tx();
let propose = ProposeBuilder::new(&sandbox)
.with_tx_hashes(&[tx.object_hash()]) .build();
let block = BlockBuilder::new(&sandbox)
.with_tx_hash(&tx.object_hash())
.build();
let precommit_1 = sandbox.create_precommit(
ValidatorId(1),
Height(1),
Round(1),
propose.object_hash(),
block.object_hash(),
sandbox.time().into(),
sandbox.secret_key(ValidatorId(1)),
);
let precommit_2 = sandbox.create_precommit(
ValidatorId(2),
Height(1),
Round(1),
propose.object_hash(),
block.object_hash(),
sandbox.time().into(),
sandbox.secret_key(ValidatorId(2)),
);
let precommit_3 = sandbox.create_precommit(
ValidatorId(3),
Height(1),
Round(1),
propose.object_hash(),
block.object_hash(),
sandbox.time().into(),
sandbox.secret_key(ValidatorId(3)),
);
sandbox.recv(&precommit_1);
sandbox.add_time(Duration::from_millis(PROPOSE_REQUEST_TIMEOUT));
sandbox.send(
sandbox.public_key(ValidatorId(1)),
&make_request_propose_from_precommit(&sandbox, precommit_1.as_ref()),
);
sandbox.send(
sandbox.public_key(ValidatorId(1)),
&make_request_prevote_from_precommit(&sandbox, precommit_1.as_ref()),
);
sandbox.recv(&precommit_2);
sandbox.add_time(Duration::from_millis(PROPOSE_REQUEST_TIMEOUT));
sandbox.send(
sandbox.public_key(ValidatorId(2)),
&make_request_propose_from_precommit(&sandbox, precommit_2.as_ref()),
);
sandbox.send(
sandbox.public_key(ValidatorId(2)),
&make_request_prevote_from_precommit(&sandbox, precommit_2.as_ref()),
);
sandbox.recv(&propose);
sandbox.assert_state(Height(1), Round(1));
sandbox.recv(&precommit_3);
sandbox.assert_state(Height(1), Round(1));
sandbox.add_time(Duration::from_millis(0));
}
#[test]
fn commit_using_unknown_propose_with_precommits() {
let sandbox = timestamping_sandbox();
let tx = gen_timestamping_tx();
let propose = ProposeBuilder::new(&sandbox)
.with_tx_hashes(&[tx.object_hash()]) .build();
let block = sandbox.create_block(&[tx.clone()]);
let precommit_1 = sandbox.create_precommit(
ValidatorId(1),
Height(1),
Round(1),
propose.object_hash(),
block.object_hash(),
sandbox.time().into(),
sandbox.secret_key(ValidatorId(1)),
);
let precommit_2 = sandbox.create_precommit(
ValidatorId(2),
Height(1),
Round(1),
propose.object_hash(),
block.object_hash(),
sandbox.time().into(),
sandbox.secret_key(ValidatorId(2)),
);
let precommit_3 = sandbox.create_precommit(
ValidatorId(3),
Height(1),
Round(1),
propose.object_hash(),
block.object_hash(),
sandbox.time().into(),
sandbox.secret_key(ValidatorId(3)),
);
sandbox.recv(&precommit_1);
sandbox.add_time(Duration::from_millis(PROPOSE_REQUEST_TIMEOUT));
sandbox.send(
sandbox.public_key(ValidatorId(1)),
&make_request_propose_from_precommit(&sandbox, precommit_1.as_ref()),
);
sandbox.send(
sandbox.public_key(ValidatorId(1)),
&make_request_prevote_from_precommit(&sandbox, precommit_1.as_ref()),
);
sandbox.recv(&precommit_2);
sandbox.add_time(Duration::from_millis(PROPOSE_REQUEST_TIMEOUT));
sandbox.send(
sandbox.public_key(ValidatorId(2)),
&make_request_propose_from_precommit(&sandbox, precommit_2.as_ref()),
);
sandbox.send(
sandbox.public_key(ValidatorId(2)),
&make_request_prevote_from_precommit(&sandbox, precommit_2.as_ref()),
);
sandbox.recv(&precommit_3);
sandbox.add_time(Duration::from_millis(PROPOSE_REQUEST_TIMEOUT));
sandbox.send(
sandbox.public_key(ValidatorId(3)),
&make_request_propose_from_precommit(&sandbox, precommit_3.as_ref()),
);
sandbox.send(
sandbox.public_key(ValidatorId(3)),
&make_request_prevote_from_precommit(&sandbox, precommit_3.as_ref()),
);
sandbox.assert_state(Height(1), Round(1));
sandbox.recv(&tx);
sandbox.recv(&propose);
sandbox.broadcast(&sandbox.create_prevote(
ValidatorId(0),
Height(1),
Round(1),
propose.object_hash(),
NOT_LOCKED,
sandbox.secret_key(ValidatorId(0)),
));
sandbox.check_broadcast_status(Height(2), block.object_hash());
sandbox.add_time(Duration::from_millis(0));
sandbox.assert_state(Height(2), Round(1));
}
#[test]
#[should_panic(expected = "handle_full_propose: wrong block hash")]
fn handle_full_propose_wrong_state_hash() {
let sandbox = timestamping_sandbox();
let tx = gen_timestamping_tx();
let propose = ProposeBuilder::new(&sandbox)
.with_tx_hashes(&[tx.object_hash()]) .build();
let block = BlockBuilder::new(&sandbox)
.with_tx_hash(&tx.object_hash())
.with_state_hash(&Hash::zero())
.build();
let precommit_1 = sandbox.create_precommit(
ValidatorId(1),
Height(1),
Round(1),
propose.object_hash(),
block.object_hash(),
sandbox.time().into(),
sandbox.secret_key(ValidatorId(1)),
);
let precommit_2 = sandbox.create_precommit(
ValidatorId(2),
Height(1),
Round(1),
propose.object_hash(),
block.object_hash(),
sandbox.time().into(),
sandbox.secret_key(ValidatorId(2)),
);
let precommit_3 = sandbox.create_precommit(
ValidatorId(3),
Height(1),
Round(1),
propose.object_hash(),
block.object_hash(),
sandbox.time().into(),
sandbox.secret_key(ValidatorId(3)),
);
sandbox.recv(&precommit_1);
sandbox.add_time(Duration::from_millis(PROPOSE_REQUEST_TIMEOUT));
sandbox.send(
sandbox.public_key(ValidatorId(1)),
&make_request_propose_from_precommit(&sandbox, precommit_1.as_ref()),
);
sandbox.send(
sandbox.public_key(ValidatorId(1)),
&make_request_prevote_from_precommit(&sandbox, precommit_1.as_ref()),
);
sandbox.recv(&precommit_2);
sandbox.add_time(Duration::from_millis(PROPOSE_REQUEST_TIMEOUT));
sandbox.send(
sandbox.public_key(ValidatorId(2)),
&make_request_propose_from_precommit(&sandbox, precommit_2.as_ref()),
);
sandbox.send(
sandbox.public_key(ValidatorId(2)),
&make_request_prevote_from_precommit(&sandbox, precommit_2.as_ref()),
);
sandbox.recv(&precommit_3);
sandbox.add_time(Duration::from_millis(PROPOSE_REQUEST_TIMEOUT));
sandbox.send(
sandbox.public_key(ValidatorId(3)),
&make_request_propose_from_precommit(&sandbox, precommit_3.as_ref()),
);
sandbox.send(
sandbox.public_key(ValidatorId(3)),
&make_request_prevote_from_precommit(&sandbox, precommit_3.as_ref()),
);
sandbox.assert_state(Height(1), Round(1));
sandbox.recv(&tx);
sandbox.recv(&propose);
sandbox.broadcast(&sandbox.create_prevote(
ValidatorId(0),
Height(1),
Round(1),
propose.object_hash(),
NOT_LOCKED,
sandbox.secret_key(ValidatorId(0)),
));
sandbox.add_time(Duration::from_millis(0));
sandbox.assert_state(Height(2), Round(1));
}
#[test]
fn do_not_send_precommit_if_has_incompatible_prevotes() {
let sandbox = timestamping_sandbox();
let sandbox_state = SandboxState::new();
let propose = ProposeBuilder::new(&sandbox).build();
sandbox.recv(&propose);
sandbox.broadcast(&sandbox.create_prevote(
ValidatorId(0),
Height(1),
Round(1),
propose.object_hash(),
NOT_LOCKED,
sandbox.secret_key(ValidatorId(0)),
));
sandbox.recv(&sandbox.create_prevote(
ValidatorId(1),
Height(1),
Round(1),
propose.object_hash(),
NOT_LOCKED,
sandbox.secret_key(ValidatorId(1)),
));
sandbox.assert_lock(NOT_LOCKED, None); add_round_with_transactions(&sandbox, &sandbox_state, &[]);
let future_propose = ProposeBuilder::new(&sandbox)
.with_validator(ValidatorId(3))
.with_round(Round(2))
.build();
sandbox.recv(&future_propose);
sandbox.broadcast(&sandbox.create_prevote(
ValidatorId(0),
Height(1),
Round(2),
future_propose.object_hash(),
NOT_LOCKED,
sandbox.secret_key(ValidatorId(0)),
));
sandbox.recv(&sandbox.create_prevote(
ValidatorId(3),
Height(1),
Round(1),
propose.object_hash(),
NOT_LOCKED,
sandbox.secret_key(ValidatorId(3)),
));
sandbox.assert_lock(Round(1), Some(propose.object_hash()));
sandbox.assert_lock(Round(1), Some(propose.object_hash()));
sandbox.add_time(Duration::from_millis(0));
}
#[test]
fn handle_precommit_positive_scenario_commit_with_queued_precommit() {
let sandbox = timestamping_sandbox();
let sandbox_state = SandboxState::new();
let tx = gen_timestamping_tx();
let mut first_block = sandbox.create_block(&[tx.clone()]);
first_block.add_header::<ProposerId>(ValidatorId(0));
let height_one_propose = ProposeBuilder::new(&sandbox)
.with_validator(ValidatorId(3))
.with_height(Height(2))
.with_prev_hash(&first_block.object_hash())
.build();
let mut second_block = BlockBuilder::new(&sandbox)
.with_proposer_id(ValidatorId(3))
.with_state_hash(&first_block.state_hash)
.build();
second_block.height = Height(2);
second_block.prev_hash = first_block.object_hash();
second_block.additional_headers.insert::<Epoch>(Height(2));
let precommit_1 = sandbox.create_precommit(
ValidatorId(1),
Height(2),
Round(1),
height_one_propose.object_hash(),
second_block.object_hash(),
sandbox.time().into(),
sandbox.secret_key(ValidatorId(1)),
);
let precommit_2 = sandbox.create_precommit(
ValidatorId(2),
Height(2),
Round(1),
height_one_propose.object_hash(),
second_block.object_hash(),
sandbox.time().into(),
sandbox.secret_key(ValidatorId(2)),
);
let precommit_3 = sandbox.create_precommit(
ValidatorId(3),
Height(2),
Round(1),
height_one_propose.object_hash(),
second_block.object_hash(),
sandbox.time().into(),
sandbox.secret_key(ValidatorId(3)),
);
sandbox.recv(&precommit_1);
sandbox.assert_state(Height(1), Round(1));
add_one_height_with_transactions(&sandbox, &sandbox_state, &[tx]);
sandbox.assert_state(Height(2), Round(1));
assert_eq!(first_block.object_hash(), sandbox.last_hash());
sandbox.add_time(Duration::from_millis(PROPOSE_REQUEST_TIMEOUT));
sandbox.send(
sandbox.public_key(ValidatorId(1)),
&make_request_propose_from_precommit(&sandbox, precommit_1.as_ref()),
);
sandbox.send(
sandbox.public_key(ValidatorId(1)),
&make_request_prevote_from_precommit(&sandbox, precommit_1.as_ref()),
);
sandbox.recv(&precommit_2);
sandbox.add_time(Duration::from_millis(PROPOSE_REQUEST_TIMEOUT));
sandbox.send(
sandbox.public_key(ValidatorId(2)),
&make_request_propose_from_precommit(&sandbox, precommit_2.as_ref()),
);
sandbox.send(
sandbox.public_key(ValidatorId(2)),
&make_request_prevote_from_precommit(&sandbox, precommit_2.as_ref()),
);
sandbox.recv(&height_one_propose);
sandbox.broadcast(&sandbox.create_prevote(
ValidatorId(0),
Height(2),
Round(1),
height_one_propose.object_hash(),
NOT_LOCKED,
sandbox.secret_key(ValidatorId(0)),
));
sandbox.assert_state(Height(2), Round(1));
sandbox.recv(&precommit_3);
sandbox.assert_state(Height(3), Round(1));
sandbox.check_broadcast_status(Height(3), second_block.object_hash());
sandbox.add_time(Duration::from_millis(0));
assert_eq!(second_block, sandbox.last_block());
}
#[test]
fn commit_as_leader_send_propose_round_timeout() {
let sandbox = timestamping_sandbox();
let sandbox_state = SandboxState::new();
let tx = gen_timestamping_tx();
add_one_height(&sandbox, &sandbox_state);
{
sandbox.assert_state(Height(2), Round(1));
{
assert_eq!(*sandbox_state.time_since_round_start.borrow(), 0);
}
}
let current_round = sandbox.current_round();
let current_height = sandbox.current_epoch();
let propose = ProposeBuilder::new(&sandbox)
.with_tx_hashes(&[tx.object_hash()]) .build();
let block = sandbox.create_block(&[tx.clone()]);
let precommit_1 = sandbox.create_precommit(
ValidatorId(1),
current_height,
current_round,
propose.object_hash(),
block.object_hash(),
sandbox.time().into(),
sandbox.secret_key(ValidatorId(1)),
);
let precommit_2 = sandbox.create_precommit(
ValidatorId(2),
current_height,
current_round,
propose.object_hash(),
block.object_hash(),
sandbox.time().into(),
sandbox.secret_key(ValidatorId(2)),
);
let precommit_3 = sandbox.create_precommit(
ValidatorId(3),
current_height,
current_round,
propose.object_hash(),
block.object_hash(),
sandbox.time().into(),
sandbox.secret_key(ValidatorId(3)),
);
sandbox.recv(&precommit_1);
sandbox.add_time(Duration::from_millis(PROPOSE_REQUEST_TIMEOUT));
sandbox.send(
sandbox.public_key(ValidatorId(1)),
&make_request_propose_from_precommit(&sandbox, precommit_1.as_ref()),
);
sandbox.send(
sandbox.public_key(ValidatorId(1)),
&make_request_prevote_from_precommit(&sandbox, precommit_1.as_ref()),
);
sandbox.recv(&precommit_2);
sandbox.add_time(Duration::from_millis(PROPOSE_REQUEST_TIMEOUT));
sandbox.send(
sandbox.public_key(ValidatorId(2)),
&make_request_propose_from_precommit(&sandbox, precommit_2.as_ref()),
);
sandbox.send(
sandbox.public_key(ValidatorId(2)),
&make_request_prevote_from_precommit(&sandbox, precommit_2.as_ref()),
);
{
sandbox.recv(&propose);
sandbox.recv(&tx);
sandbox.broadcast(&make_prevote_from_propose(&sandbox, &propose));
}
assert_eq!(vec![tx.object_hash()], sandbox.transactions_hashes());
sandbox.assert_state(current_height, current_round);
sandbox.recv(&precommit_3);
let new_height = current_height.next();
sandbox.assert_state(new_height, Round(1));
sandbox.check_broadcast_status(new_height, block.object_hash());
let propose = ProposeBuilder::new(&sandbox).build();
sandbox.add_time(Duration::from_millis(PROPOSE_TIMEOUT));
sandbox.broadcast(&propose);
sandbox.broadcast(&make_prevote_from_propose(&sandbox, &propose));
sandbox.add_time(Duration::from_millis(
sandbox.current_round_timeout() - PROPOSE_TIMEOUT,
));
sandbox.assert_state(sandbox.current_epoch(), Round(2));
}
#[test]
fn handle_tx_handle_full_propose() {
let sandbox = timestamping_sandbox();
let tx = gen_timestamping_tx();
let propose = ProposeBuilder::new(&sandbox)
.with_tx_hashes(&[tx.object_hash()]) .build();
sandbox.recv(&propose);
sandbox.add_time(Duration::from_millis(TRANSACTIONS_REQUEST_TIMEOUT));
sandbox.send(
sandbox.public_key(ValidatorId(2)),
&Sandbox::create_transactions_request(
sandbox.public_key(ValidatorId(0)),
sandbox.public_key(ValidatorId(2)),
vec![tx.object_hash()],
sandbox.secret_key(ValidatorId(0)),
),
);
sandbox.recv(&tx);
sandbox.broadcast(&make_prevote_from_propose(&sandbox, &propose));
sandbox.add_time(Duration::from_millis(0));
}
#[test]
fn broadcast_prevote_with_tx_positive() {
let sandbox = timestamping_sandbox();
let sandbox_state = SandboxState::new();
add_one_height(&sandbox, &sandbox_state);
sandbox.assert_state(Height(2), Round(1));
let tx = gen_timestamping_tx();
sandbox.recv(&tx);
let propose = ProposeBuilder::new(&sandbox)
.with_tx_hashes(&[tx.object_hash()]) .build();
sandbox.recv(&propose);
sandbox.assert_lock(NOT_LOCKED, None);
sandbox.broadcast(&sandbox.create_prevote(
ValidatorId(0),
Height(2),
Round(1),
propose.object_hash(),
NOT_LOCKED,
sandbox.secret_key(ValidatorId(0)),
));
}
#[test]
fn handle_tx_ignore_existing_tx_in_blockchain() {
let sandbox = timestamping_sandbox();
let sandbox_state = SandboxState::new();
let tx = gen_timestamping_tx();
add_one_height_with_transactions(&sandbox, &sandbox_state, &[tx.clone()]);
sandbox.assert_state(Height(2), Round(1));
sandbox.add_time(Duration::from_millis(sandbox.current_round_timeout()));
assert!(sandbox.is_leader());
sandbox.recv(&tx);
sandbox.add_time(Duration::from_millis(PROPOSE_TIMEOUT));
let propose = ProposeBuilder::new(&sandbox)
.with_tx_hashes(&[])
.build();
sandbox.broadcast(&propose);
sandbox.broadcast(&make_prevote_from_propose(&sandbox, &propose));
sandbox.add_time(Duration::from_millis(0));
}
#[test]
fn handle_tx_ignore_invalid_tx() {
let sandbox = timestamping_sandbox();
let sandbox_state = SandboxState::new();
add_one_height(&sandbox, &sandbox_state);
sandbox.assert_state(Height(2), Round(1));
sandbox.add_time(Duration::from_millis(sandbox.current_round_timeout()));
assert!(sandbox.is_leader());
let incorrect_tx = gen_incorrect_tx();
let correct_tx = gen_timestamping_tx();
sandbox.recv(&incorrect_tx);
sandbox.assert_tx_cache_len(0);
sandbox.recv(&correct_tx);
sandbox.assert_tx_cache_len(1);
sandbox.add_time(Duration::from_millis(PROPOSE_TIMEOUT));
let propose = ProposeBuilder::new(&sandbox)
.with_tx_hashes(&[correct_tx.object_hash()])
.build();
sandbox.broadcast(&propose);
sandbox.broadcast(&make_prevote_from_propose(&sandbox, &propose));
sandbox.add_time(Duration::from_millis(0));
}
#[test]
fn handle_precommit_remove_propose_request() {
let sandbox = timestamping_sandbox_builder().build();
let tx = gen_timestamping_tx();
let propose = ProposeBuilder::new(&sandbox)
.with_tx_hashes(&[tx.object_hash()])
.build();
let block = BlockBuilder::new(&sandbox)
.with_tx_hash(&tx.object_hash())
.build();
let precommit = sandbox.create_precommit(
propose.payload().validator,
Height(1),
Round(1),
propose.object_hash(),
block.object_hash(),
sandbox.time().into(),
sandbox.secret_key(propose.payload().validator),
);
sandbox.recv(&precommit);
sandbox.recv(&propose);
sandbox.add_time(Duration::from_millis(TRANSACTIONS_REQUEST_TIMEOUT));
sandbox.send(
sandbox.public_key(propose.payload().validator),
&Sandbox::create_transactions_request(
sandbox.public_key(ValidatorId(0)),
sandbox.public_key(propose.payload().validator),
vec![tx.object_hash()],
sandbox.secret_key(ValidatorId(0)),
),
);
let prevoters = BitVec::from_elem(sandbox.validators().len(), false);
sandbox.send(
sandbox.public_key(propose.payload().validator),
&Sandbox::create_prevote_request(
sandbox.public_key(ValidatorId(0)),
sandbox.public_key(propose.payload().validator),
Height(1),
Round(1),
propose.object_hash(),
prevoters,
sandbox.secret_key(ValidatorId(0)),
),
);
}
#[test]
fn handle_receive_multiple_proposals_same_round() {
let sandbox = timestamping_sandbox_builder().build();
let tx_1 = gen_timestamping_tx();
let tx_2 = gen_timestamping_tx();
let propose_1 = ProposeBuilder::new(&sandbox)
.with_tx_hashes(&[tx_1.object_hash()])
.build();
let propose_2 = ProposeBuilder::new(&sandbox)
.with_tx_hashes(&[tx_1.object_hash(), tx_2.object_hash()])
.build();
let block_1 = BlockBuilder::new(&sandbox)
.with_txs_hashes(&[tx_1.object_hash()])
.build();
let block_2 = BlockBuilder::new(&sandbox)
.with_txs_hashes(&[tx_1.object_hash(), tx_2.object_hash()])
.build();
sandbox.recv(&tx_2);
let (propose_hash, block_hash, txs_in_pool) =
if propose_1.object_hash() < propose_2.object_hash() {
(propose_1.object_hash(), block_1.object_hash(), 1)
} else {
(propose_2.object_hash(), block_2.object_hash(), 0)
};
for i in 1..sandbox.validators().len() as u16 {
sandbox.recv(&sandbox.create_prevote(
ValidatorId(i),
Height(1),
Round(1),
propose_hash,
NOT_LOCKED,
sandbox.secret_key(ValidatorId(i)),
));
}
for i in 1..sandbox.validators().len() as u16 {
sandbox.recv(&sandbox.create_precommit(
ValidatorId(i),
Height(1),
Round(1),
propose_hash,
block_hash,
sandbox.time().into(),
sandbox.secret_key(ValidatorId(i)),
))
}
sandbox.recv(&propose_1);
sandbox.recv(&propose_2);
sandbox.recv(&tx_1);
sandbox.broadcast(&sandbox.create_prevote(
ValidatorId(0),
Height(1),
Round(1),
propose_hash,
NOT_LOCKED,
sandbox.secret_key(ValidatorId(0)),
));
sandbox.broadcast(&sandbox.create_precommit(
ValidatorId(0),
Height(1),
Round(1),
propose_hash,
block_hash,
sandbox.time().into(),
sandbox.secret_key(ValidatorId(0)),
));
sandbox.broadcast(&Sandbox::create_status(
sandbox.public_key(ValidatorId(0)),
Height(2),
block_hash,
txs_in_pool,
sandbox.secret_key(ValidatorId(0)),
));
}
#[test]
fn handle_precommit_remove_propose_request_ask_prevoters() {
let sandbox = timestamping_sandbox_builder().build();
let tx = gen_timestamping_tx();
let propose = ProposeBuilder::new(&sandbox)
.with_tx_hashes(&[tx.object_hash()])
.build();
let block = BlockBuilder::new(&sandbox)
.with_tx_hash(&tx.object_hash())
.build();
let precommit = sandbox.create_precommit(
propose.payload().validator,
Height(1),
Round(1),
propose.object_hash(),
block.object_hash(),
sandbox.time().into(),
sandbox.secret_key(propose.payload().validator),
);
sandbox.recv(&precommit);
let mut prevoters = BitVec::from_elem(sandbox.validators().len(), false);
for i in 1..sandbox.validators().len() as u16 {
sandbox.recv(&sandbox.create_prevote(
ValidatorId(i),
Height(1),
Round(1),
propose.object_hash(),
Round(0),
sandbox.secret_key(ValidatorId(i)),
));
prevoters.set(i as usize, true);
}
sandbox.recv(&propose);
let mut validators = (1..sandbox.validators().len() as u16)
.map(|x| sandbox.public_key(ValidatorId(x)))
.collect::<HashSet<_>>();
for i in 1..sandbox.validators().len() {
sandbox.add_time(Duration::from_millis(TRANSACTIONS_REQUEST_TIMEOUT));
sandbox.process_events();
let (_, msg) = sandbox.pop_sent_message().unwrap();
let msg = Verified::<TransactionsRequest>::try_from(msg)
.expect("Incorrect message. TransactionsRequest was expected.");
assert!(
validators.remove(&msg.payload().to),
"Unexpected validator's PublicKey"
);
if i == 1 {
sandbox.send(
sandbox.public_key(propose.payload().validator),
&Sandbox::create_prevote_request(
sandbox.public_key(ValidatorId(0)),
sandbox.public_key(propose.payload().validator),
Height(1),
Round(1),
propose.object_hash(),
prevoters.clone(),
sandbox.secret_key(ValidatorId(0)),
),
);
}
}
assert!(
validators.is_empty(),
"Should send TransactionsRequest to all validators"
);
}
#[test]
fn handle_precommit_remove_propose_request_ask_precommitters() {
let sandbox = timestamping_sandbox();
let tx = gen_timestamping_tx();
let propose = ProposeBuilder::new(&sandbox)
.with_tx_hashes(&[tx.object_hash()])
.build();
let block = BlockBuilder::new(&sandbox)
.with_tx_hash(&tx.object_hash())
.build();
for i in 1..sandbox.validators().len() as u16 {
sandbox.recv(&sandbox.create_precommit(
ValidatorId(i),
Height(1),
Round(1),
propose.object_hash(),
block.object_hash(),
sandbox.time().into(),
sandbox.secret_key(ValidatorId(i)),
))
}
sandbox.recv(&propose);
let mut validators = (1..sandbox.validators().len() as u16)
.map(|x| sandbox.public_key(ValidatorId(x)))
.collect::<HashSet<_>>();
for _ in 1..sandbox.validators().len() {
sandbox.add_time(Duration::from_millis(TRANSACTIONS_REQUEST_TIMEOUT));
sandbox.process_events();
let (_, msg) = sandbox.pop_sent_message().unwrap();
let msg = Verified::<TransactionsRequest>::try_from(msg)
.expect("Incorrect message. TransactionsRequest was expected.");
assert!(
validators.remove(&msg.payload().to),
"Unexpected validator's PublicKey"
);
let (_, msg) = sandbox.pop_sent_message().unwrap();
Verified::<PrevotesRequest>::try_from(msg)
.expect("Incorrect message. PrevotesRequest was expected.");
}
assert!(
validators.is_empty(),
"Should send TransactionsRequest to all validators"
);
}