-
Notifications
You must be signed in to change notification settings - Fork 41
Expand file tree
/
Copy pathdistinct_reconstruction_thresholds.rs
More file actions
125 lines (117 loc) · 4.75 KB
/
Copy pathdistinct_reconstruction_thresholds.rs
File metadata and controls
125 lines (117 loc) · 4.75 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
use crate::common::{
DISTINCT_RECONSTRUCTION_THRESHOLDS_PORT_SEED, ckd_domain, damgard_etal_domain,
generate_ckd_app_public_key, generate_ecdsa_payload, generate_eddsa_payload, must_get_domain,
must_setup_cluster, wait_metric_on_nodes,
};
use e2e_tests::{CLUSTER_WAIT_TIMEOUT, metrics};
use near_mpc_contract_interface::types::{
DomainConfig, DomainId, DomainPurpose, Protocol, ReconstructionThreshold,
};
use rand::SeedableRng;
/// Each domain signs under its own reconstruction threshold, not the governance
/// threshold. With 6 nodes and 1 killed, Cait-Sith (needs all 6) can no longer sign
/// while Damgard et al. (`2t - 1 = 5`), CKD (`t = 5`) and Frost (`t = 5`) still can.
#[tokio::test]
#[expect(non_snake_case)]
async fn distinct_reconstruction_thresholds__should_use_per_domain_threshold_when_nodes_are_down() {
// Given
let mut rng = rand::rngs::StdRng::seed_from_u64(0);
let (mut cluster, contract_state) =
must_setup_cluster(DISTINCT_RECONSTRUCTION_THRESHOLDS_PORT_SEED, |c| {
c.num_nodes = 6;
c.initial_participant_indices = (0..6).collect();
c.threshold = 6;
c.triples_to_buffer = 2;
c.presignatures_to_buffer = 2;
c.domains = vec![
DomainConfig {
id: DomainId(0),
protocol: Protocol::CaitSith,
reconstruction_threshold: ReconstructionThreshold::new(6),
purpose: DomainPurpose::Sign,
},
damgard_etal_domain(1, 3),
ckd_domain(2, 5),
DomainConfig {
id: DomainId(3),
protocol: Protocol::Frost,
reconstruction_threshold: ReconstructionThreshold::new(5),
purpose: DomainPurpose::Sign,
},
];
})
.await;
let caitsith_domain = must_get_domain(&contract_state, Protocol::CaitSith);
let damgard_domain = must_get_domain(&contract_state, Protocol::DamgardEtAl);
let ckd_domain = must_get_domain(&contract_state, Protocol::ConfidentialKeyDerivation);
let frost_domain = must_get_domain(&contract_state, Protocol::Frost);
// When
cluster.kill_nodes(&[5]).expect("failed to kill node 5");
// Then Damgard et al. (needs 5 signers) still signs.
let outcome = cluster
.send_sign_request(
damgard_domain.id,
generate_ecdsa_payload(&mut rng),
cluster.default_user_account(),
)
.await
.expect("failed to submit Damgard et al. sign request");
assert!(
outcome.is_success(),
"Damgard et al. sign request failed with 5 of 6 nodes alive: {:?}",
outcome.failure_message()
);
// And CKD (its own `t = 5`, not the governance threshold of 6) still derives.
let outcome = cluster
.send_ckd_request(
ckd_domain.id,
generate_ckd_app_public_key(&mut rng),
cluster.default_user_account(),
)
.await
.expect("failed to submit CKD request");
assert!(
outcome.is_success(),
"CKD request failed with 5 of its 5 required signers alive: {:?}",
outcome.failure_message()
);
// And Frost (its own `t = 5`) still signs.
let outcome = cluster
.send_sign_request(
frost_domain.id,
generate_eddsa_payload(&mut rng),
cluster.default_user_account(),
)
.await
.expect("failed to submit Frost sign request");
assert!(
outcome.is_success(),
"Frost sign request failed with 5 of its 5 required signers alive: {:?}",
outcome.failure_message()
);
// And Cait-Sith (needs all 6) is unanswerable. Its request never resolves on
// chain, and the yield auto-timeout outlives the JSON-RPC call, so we race the
// doomed request against the surviving nodes' timeout counter rather than
// awaiting it (see `timeout_metric.rs`).
tokio::select! {
res = wait_metric_on_nodes(
&cluster,
&[0, 1, 2, 3, 4],
metrics::TIMEOUTS_INDEXED,
|v| v >= 1,
CLUSTER_WAIT_TIMEOUT,
) => res.unwrap_or_else(|_| panic!(
"{} did not reach 1 on the surviving nodes — Cait-Sith request was answered \
despite only 5 of its 6 required signers being alive",
metrics::TIMEOUTS_INDEXED
)),
_ = cluster.send_sign_request(
caitsith_domain.id,
generate_ecdsa_payload(&mut rng),
cluster.default_user_account(),
) => panic!(
"Cait-Sith sign request returned before the timeout metric — it should be \
unanswerable with only 5 of 6 required signers alive"
),
}
}