Skip to content

Commit ee81905

Browse files
authored
Merge pull request #3288 from eqlabs/vbar/consensus-fail_on_proposal_committed
test: require 4 nodes to test fail_on_proposal_committed
2 parents c2f3d41 + 6108a90 commit ee81905

File tree

3 files changed

+167
-4
lines changed

3 files changed

+167
-4
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ jobs:
7171
- name: Compile unit tests with all features enabled
7272
run: cargo nextest run --cargo-profile ci-dev --all-targets --all-features --workspace --locked --no-run --timings
7373
- name: Run unit tests with all features enabled excluding consensus integration tests
74-
run: timeout 10m cargo nextest run --cargo-profile ci-dev --no-fail-fast --all-targets --all-features --workspace --locked -E 'not test(/^test::consensus_3_nodes/)' --retries 2
74+
run: timeout 10m cargo nextest run --cargo-profile ci-dev --no-fail-fast --all-targets --all-features --workspace --locked -E 'not test(/^test::consensus_[34]_nodes/)' --retries 2
7575
- name: Store timings with all features enabled
7676
uses: actions/upload-artifact@v4
7777
with:

crates/pathfinder/tests/consensus.rs

Lines changed: 164 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,6 @@ mod test {
7373
#[case::fail_on_prevote_rx(Some(InjectFailureConfig { height: 4, trigger: InjectFailureTrigger::PrevoteRx }))]
7474
#[case::fail_on_precommit_rx(Some(InjectFailureConfig { height: 4, trigger: InjectFailureTrigger::PrecommitRx }))]
7575
#[case::fail_on_proposal_decided(Some(InjectFailureConfig { height: 4, trigger: InjectFailureTrigger::ProposalDecided }))]
76-
#[case::fail_on_proposal_committed(Some(InjectFailureConfig { height: 4, trigger: InjectFailureTrigger::ProposalCommitted }))]
7776
#[tokio::test]
7877
async fn consensus_3_nodes_with_failures(#[case] inject_failure: Option<InjectFailureConfig>) {
7978
const NUM_NODES: usize = 3;
@@ -233,6 +232,170 @@ mod test {
233232
);
234233
}
235234

235+
#[rstest]
236+
// Cannot be tested with just 3 nodes b/c Bob might break the
237+
// Gossip communication property when restarting - see
238+
// https://github.com/eqlabs/pathfinder/issues/3286
239+
#[case::fail_on_proposal_committed(InjectFailureConfig { height: 4, trigger: InjectFailureTrigger::ProposalCommitted })]
240+
#[tokio::test]
241+
async fn consensus_4_nodes_with_failures(#[case] inject_failure: InjectFailureConfig) {
242+
const NUM_NODES: usize = 4;
243+
const READY_TIMEOUT: Duration = Duration::from_secs(20);
244+
const TEST_TIMEOUT: Duration = Duration::from_secs(120);
245+
const POLL_READY: Duration = Duration::from_millis(500);
246+
const POLL_HEIGHT: Duration = Duration::from_secs(1);
247+
248+
let disallow_reverted_txns = true;
249+
250+
let (configs, boot_height, stopwatch) =
251+
utils::setup(NUM_NODES, disallow_reverted_txns).unwrap();
252+
253+
let target_height: u64 = boot_height + 5;
254+
255+
let alice_cfg = configs.first().unwrap();
256+
let mut fgw = FeederGateway::spawn(alice_cfg).unwrap();
257+
fgw.wait_for_ready(POLL_READY, READY_TIMEOUT).await.unwrap();
258+
259+
let mut configs = configs.into_iter().map(|cfg| {
260+
cfg.with_local_feeder_gateway(fgw.port())
261+
.with_sync_enabled()
262+
});
263+
264+
let alice = PathfinderInstance::spawn(configs.next().unwrap()).unwrap();
265+
alice
266+
.wait_for_ready(POLL_READY, READY_TIMEOUT)
267+
.await
268+
.unwrap();
269+
270+
let boot_port = alice.consensus_p2p_port();
271+
let mut configs = configs.map(|cfg| cfg.with_boot_port(boot_port));
272+
273+
let bob_cfg = configs
274+
.next()
275+
.unwrap()
276+
.with_inject_failure(Some(inject_failure));
277+
278+
let bob = PathfinderInstance::spawn(bob_cfg.clone()).unwrap();
279+
let charlie = PathfinderInstance::spawn(configs.next().unwrap()).unwrap();
280+
let dan = PathfinderInstance::spawn(configs.next().unwrap()).unwrap();
281+
282+
let (bob_rdy, charlie_rdy, dan_rdy) = tokio::join!(
283+
bob.wait_for_ready(POLL_READY, READY_TIMEOUT),
284+
charlie.wait_for_ready(POLL_READY, READY_TIMEOUT),
285+
dan.wait_for_ready(POLL_READY, READY_TIMEOUT),
286+
);
287+
bob_rdy.unwrap();
288+
charlie_rdy.unwrap();
289+
dan_rdy.unwrap();
290+
291+
utils::log_elapsed(stopwatch);
292+
293+
let (hnr_tx, hnr_rx) = mpsc::channel(target_height as usize * 3);
294+
let hnr_rx = tokio_stream::wrappers::ReceiverStream::new(hnr_rx);
295+
let (err_tx, err_rx) = mpsc::channel(6);
296+
297+
let alice_decided = wait_for_height(
298+
&alice,
299+
target_height,
300+
POLL_HEIGHT,
301+
Some(hnr_tx),
302+
err_tx.clone(),
303+
);
304+
let bob_decided = wait_for_height(&bob, target_height, POLL_HEIGHT, None, err_tx.clone());
305+
let charlie_decided =
306+
wait_for_height(&charlie, target_height, POLL_HEIGHT, None, err_tx.clone());
307+
let dan_decided = wait_for_height(&dan, target_height, POLL_HEIGHT, None, err_tx.clone());
308+
let alice_committed = wait_for_block_exists(
309+
&alice,
310+
target_height,
311+
POLL_HEIGHT,
312+
disallow_reverted_txns,
313+
err_tx.clone(),
314+
);
315+
let bob_committed = wait_for_block_exists(
316+
&bob,
317+
target_height,
318+
POLL_HEIGHT,
319+
disallow_reverted_txns,
320+
err_tx.clone(),
321+
);
322+
let charlie_committed = wait_for_block_exists(
323+
&charlie,
324+
target_height,
325+
POLL_HEIGHT,
326+
disallow_reverted_txns,
327+
err_tx.clone(),
328+
);
329+
let dan_committed = wait_for_block_exists(
330+
&charlie,
331+
target_height,
332+
POLL_HEIGHT,
333+
disallow_reverted_txns,
334+
err_tx.clone(),
335+
);
336+
337+
let maybe_bob = respawn_on_fail(true, bob, bob_cfg, POLL_READY, READY_TIMEOUT);
338+
339+
// Wait for: the test to pass, timeout, user interruption, or bail out early if
340+
// the RPC client encounters an error
341+
utils::join_all(
342+
vec![
343+
alice_decided,
344+
bob_decided,
345+
charlie_decided,
346+
dan_decided,
347+
alice_committed,
348+
bob_committed,
349+
charlie_committed,
350+
dan_committed,
351+
],
352+
TEST_TIMEOUT,
353+
err_rx,
354+
)
355+
.await
356+
.unwrap();
357+
358+
let decided_hnrs = hnr_rx.collect::<Vec<_>>().await;
359+
if let Some(x) = decided_hnrs.iter().find(|hnr| hnr.round() > 0) {
360+
println!("Network failed to recover in round 0 at (h:r): {x}");
361+
}
362+
363+
let alice_artifacts = get_cached_artifacts_info(&alice, target_height)
364+
.await
365+
.unwrap();
366+
assert!(
367+
alice_artifacts.is_empty(),
368+
"Alice should not have leftover cached consensus data: {alice_artifacts:#?}"
369+
);
370+
371+
if let Some(bob) = maybe_bob.instance() {
372+
let bob_artifacts = get_cached_artifacts_info(&bob, target_height)
373+
.await
374+
.unwrap();
375+
assert!(
376+
bob_artifacts.is_empty(),
377+
"Bob should not have leftover cached consensus data after respawn: \
378+
{bob_artifacts:#?}"
379+
);
380+
}
381+
382+
let charlie_artifacts = get_cached_artifacts_info(&charlie, target_height)
383+
.await
384+
.unwrap();
385+
assert!(
386+
charlie_artifacts.is_empty(),
387+
"Charlie should not have leftover cached consensus data: {charlie_artifacts:#?}"
388+
);
389+
390+
let dan_artifacts = get_cached_artifacts_info(&dan, target_height)
391+
.await
392+
.unwrap();
393+
assert!(
394+
dan_artifacts.is_empty(),
395+
"Dan should not have leftover cached consensus data: {dan_artifacts:#?}"
396+
);
397+
}
398+
236399
#[tokio::test]
237400
async fn consensus_3_nodes_fourth_node_joins_late_can_catch_up() {
238401
const NUM_NODES: usize = 4;

justfile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@ default:
33

44
test $RUST_BACKTRACE="1" *args="": build-pathfinder-release
55
cargo nextest run --no-fail-fast --all-targets --features p2p --workspace --locked \
6-
-E 'not (test(/^p2p_network::sync::sync_handlers::tests::prop/) | test(/^consensus::inner::p2p_task::handler_proptest/) | test(/^test::consensus_3_nodes/))' \
6+
-E 'not (test(/^p2p_network::sync::sync_handlers::tests::prop/) | test(/^consensus::inner::p2p_task::handler_proptest/) | test(/^test::consensus_[34]_nodes/))' \
77
{{args}}
88

99
test-all-features $RUST_BACKTRACE="1" *args="": build-pathfinder-release
1010
cargo nextest run --no-fail-fast --all-targets --all-features --workspace --locked \
11-
-E 'not (test(/^p2p_network::sync::sync_handlers::tests::prop/) | test(/^consensus::inner::p2p_task::handler_proptest/) | test(/^test::consensus_3_nodes/))' \
11+
-E 'not (test(/^p2p_network::sync::sync_handlers::tests::prop/) | test(/^consensus::inner::p2p_task::handler_proptest/) | test(/^test::consensus_[34]_nodes/))' \
1212
{{args}}
1313

1414
test-consensus $RUST_BACKTRACE="1" *args="": build-pathfinder build-feeder-gateway

0 commit comments

Comments
 (0)