1#![allow(clippy::panic)]
8use std::{collections::BTreeMap, fmt::Debug, hash::Hash, marker::PhantomData, sync::Arc};
9
10use async_broadcast::{Receiver, Sender};
11use async_lock::RwLock;
12use bitvec::bitvec;
13use committable::Committable;
14use hotshot::{
15 HotShotInitializer, SystemContext,
16 traits::{BlockPayload, NodeImplementation, TestableNodeImplementation},
17 types::{SignatureKey, SystemContextHandle},
18};
19use hotshot_example_types::{
20 block_types::TestTransaction,
21 node_types::TestTypes,
22 state_types::{TestInstanceState, TestValidatedState},
23 storage_types::TestStorage,
24};
25use hotshot_task_impls::events::HotShotEvent;
26use hotshot_types::{
27 ValidatorConfig,
28 consensus::ConsensusMetricsValue,
29 data::{
30 EpochNumber, Leaf2, VidCommitment, VidDisperse, VidDisperseAndDuration, VidDisperseShare,
31 ViewNumber, vid_commitment,
32 },
33 epoch_membership::{EpochMembership, EpochMembershipCoordinator},
34 message::{Proposal, UpgradeLock},
35 simple_certificate::DaCertificate2,
36 simple_vote::{DaData2, DaVote2, SimpleVote, VersionedVoteData},
37 stake_table::StakeTableEntries,
38 storage_metrics::StorageMetricsValue,
39 traits::{EncodeBytes, election::Membership, node_implementation::NodeType},
40 utils::{View, ViewInner, option_epoch_from_block_number},
41 vote::{Certificate, HasViewNumber, Vote},
42};
43use serde::Serialize;
44use vbs::version::Version;
45
46use crate::{test_builder::TestDescription, test_launcher::TestLauncher};
47
48pub type TestNodeKeyMap = BTreeMap<
49 <TestTypes as NodeType>::SignatureKey,
50 <<TestTypes as NodeType>::SignatureKey as SignatureKey>::PrivateKey,
51>;
52
53pub async fn build_system_handle<
57 TYPES: NodeType<InstanceState = TestInstanceState>,
58 I: NodeImplementation<TYPES, Storage = TestStorage<TYPES>> + TestableNodeImplementation<TYPES>,
59>(
60 node_id: u64,
61) -> (
62 SystemContextHandle<TYPES, I>,
63 Sender<Arc<HotShotEvent<TYPES>>>,
64 Receiver<Arc<HotShotEvent<TYPES>>>,
65 Arc<TestNodeKeyMap>,
66)
67where
68 <TYPES as NodeType>::Membership: Membership<TYPES, Storage = TestStorage<TYPES>>,
69{
70 let builder: TestDescription<TYPES, I> = TestDescription::default_multiple_rounds();
71
72 let launcher = builder.gen_launcher().map_hotshot_config(|hotshot_config| {
73 hotshot_config.epoch_height = 0;
74 });
75 build_system_handle_from_launcher(node_id, &launcher).await
76}
77
78pub async fn build_system_handle_from_launcher<
82 TYPES: NodeType<InstanceState = TestInstanceState>,
83 I: NodeImplementation<TYPES, Storage = TestStorage<TYPES>> + TestableNodeImplementation<TYPES>,
84>(
85 node_id: u64,
86 launcher: &TestLauncher<TYPES, I>,
87) -> (
88 SystemContextHandle<TYPES, I>,
89 Sender<Arc<HotShotEvent<TYPES>>>,
90 Receiver<Arc<HotShotEvent<TYPES>>>,
91 Arc<TestNodeKeyMap>,
92)
93where
94 <TYPES as NodeType>::Membership: Membership<TYPES, Storage = TestStorage<TYPES>>,
95{
96 let network = (launcher.resource_generators.channel_generator)(node_id).await;
97 let storage = (launcher.resource_generators.storage)(node_id);
98 let hotshot_config = (launcher.resource_generators.hotshot_config)(node_id);
99
100 let initializer = HotShotInitializer::<TYPES>::from_genesis(
101 TestInstanceState::new(
102 launcher
103 .metadata
104 .async_delay_config
105 .get(&node_id)
106 .cloned()
107 .unwrap_or_default(),
108 ),
109 launcher.metadata.test_config.epoch_height,
110 launcher.metadata.test_config.epoch_start_block,
111 vec![],
112 launcher.metadata.upgrade,
113 )
114 .await
115 .unwrap();
116
117 let is_da = node_id < hotshot_config.da_staked_committee_size as u64;
119
120 let validator_config: ValidatorConfig<TYPES> = ValidatorConfig::generated_from_seed_indexed(
122 [0u8; 32],
123 node_id,
124 launcher.metadata.node_stakes.get(node_id),
125 is_da,
126 );
127 let private_key = validator_config.private_key.clone();
128 let public_key = validator_config.public_key.clone();
129 let state_private_key = validator_config.state_private_key.clone();
130
131 let memberships = Arc::new(RwLock::new(TYPES::Membership::new::<I>(
132 hotshot_config.known_nodes_with_stake.clone(),
133 hotshot_config.known_da_nodes.clone(),
134 storage.clone(),
135 network.clone(),
136 public_key.clone(),
137 launcher.metadata.test_config.epoch_height,
138 )));
139
140 let coordinator =
141 EpochMembershipCoordinator::new(memberships, hotshot_config.epoch_height, &storage);
142 let node_key_map = launcher.metadata.build_node_key_map();
143
144 let (c, s, r) = SystemContext::init(
145 public_key,
146 private_key,
147 state_private_key,
148 node_id,
149 hotshot_config,
150 launcher.metadata.upgrade,
151 coordinator,
152 network,
153 initializer,
154 ConsensusMetricsValue::default(),
155 storage,
156 StorageMetricsValue::default(),
157 )
158 .await
159 .expect("Could not init hotshot");
160
161 (c, s, r, node_key_map)
162}
163
164pub async fn build_cert<
168 TYPES: NodeType,
169 DATAType: Committable + Clone + Eq + Hash + Serialize + Debug + 'static,
170 VOTE: Vote<TYPES, Commitment = DATAType>,
171 CERT: Certificate<TYPES, VOTE::Commitment, Voteable = VOTE::Commitment>,
172>(
173 data: DATAType,
174 epoch_membership: &EpochMembership<TYPES>,
175 view: ViewNumber,
176 public_key: &TYPES::SignatureKey,
177 private_key: &<TYPES::SignatureKey as SignatureKey>::PrivateKey,
178 upgrade_lock: &UpgradeLock<TYPES>,
179) -> CERT {
180 let real_qc_sig = build_assembled_sig::<TYPES, VOTE, CERT, DATAType>(
181 &data,
182 epoch_membership,
183 view,
184 upgrade_lock,
185 )
186 .await;
187
188 let vote = SimpleVote::<TYPES, DATAType>::create_signed_vote(
189 data,
190 view,
191 public_key,
192 private_key,
193 upgrade_lock,
194 )
195 .await
196 .expect("Failed to sign data!");
197
198 let vote_commitment =
199 VersionedVoteData::new(vote.date().clone(), vote.view_number(), upgrade_lock)
200 .await
201 .expect("Failed to create VersionedVoteData!")
202 .commit();
203
204 CERT::create_signed_certificate(
205 vote_commitment,
206 vote.date().clone(),
207 real_qc_sig,
208 vote.view_number(),
209 )
210}
211
212pub fn vid_share<TYPES: NodeType>(
213 shares: &[Proposal<TYPES, VidDisperseShare<TYPES>>],
214 pub_key: TYPES::SignatureKey,
215) -> Proposal<TYPES, VidDisperseShare<TYPES>> {
216 shares
217 .iter()
218 .filter(|s| *s.data.recipient_key() == pub_key)
219 .cloned()
220 .collect::<Vec<_>>()
221 .first()
222 .expect("No VID for key")
223 .clone()
224}
225
226pub async fn build_assembled_sig<
230 TYPES: NodeType,
231 VOTE: Vote<TYPES>,
232 CERT: Certificate<TYPES, VOTE::Commitment, Voteable = VOTE::Commitment>,
233 DATAType: Committable + Clone + Eq + Hash + Serialize + Debug + 'static,
234>(
235 data: &DATAType,
236 epoch_membership: &EpochMembership<TYPES>,
237 view: ViewNumber,
238 upgrade_lock: &UpgradeLock<TYPES>,
239) -> <TYPES::SignatureKey as SignatureKey>::QcType {
240 let stake_table = CERT::stake_table(epoch_membership).await;
241 let stake_table_entries = StakeTableEntries::<TYPES>::from(stake_table.clone()).0;
242 let real_qc_pp: <TYPES::SignatureKey as SignatureKey>::QcParams<'_> =
243 <TYPES::SignatureKey as SignatureKey>::public_parameter(
244 &stake_table_entries,
245 CERT::threshold(epoch_membership).await,
246 );
247
248 let total_nodes = stake_table.len();
249 let signers = bitvec![1; total_nodes];
250 let mut sig_lists = Vec::new();
251
252 for node_id in 0..total_nodes {
254 let (private_key_i, public_key_i) = key_pair_for_id::<TYPES>(node_id.try_into().unwrap());
255 let vote: SimpleVote<TYPES, DATAType> = SimpleVote::<TYPES, DATAType>::create_signed_vote(
256 data.clone(),
257 view,
258 &public_key_i,
259 &private_key_i,
260 upgrade_lock,
261 )
262 .await
263 .expect("Failed to sign data!");
264 let original_signature: <TYPES::SignatureKey as SignatureKey>::PureAssembledSignatureType =
265 vote.signature();
266 sig_lists.push(original_signature);
267 }
268
269 <TYPES::SignatureKey as SignatureKey>::assemble(
270 &real_qc_pp,
271 signers.as_bitslice(),
272 &sig_lists[..],
273 )
274}
275
276#[must_use]
278pub fn key_pair_for_id<TYPES: NodeType>(
279 node_id: u64,
280) -> (
281 <TYPES::SignatureKey as SignatureKey>::PrivateKey,
282 TYPES::SignatureKey,
283) {
284 let private_key = TYPES::SignatureKey::generated_from_seed_indexed([0u8; 32], node_id).1;
285 let public_key = <TYPES as NodeType>::SignatureKey::from_private(&private_key);
286 (private_key, public_key)
287}
288
289pub async fn da_payload_commitment<TYPES: NodeType>(
290 membership: &EpochMembership<TYPES>,
291 transactions: Vec<TestTransaction>,
292 metadata: &<TYPES::BlockPayload as BlockPayload<TYPES>>::Metadata,
293 version: Version,
294) -> VidCommitment {
295 let encoded_transactions = TestTransaction::encode(&transactions);
296
297 vid_commitment(
298 &encoded_transactions,
299 &metadata.encode(),
300 membership.total_nodes().await,
301 version,
302 )
303}
304
305pub async fn build_payload_commitment<TYPES: NodeType>(
306 membership: &EpochMembership<TYPES>,
307 view: ViewNumber,
308 version: Version,
309) -> VidCommitment {
310 let encoded_transactions = Vec::new();
313 let num_storage_nodes = membership.committee_members(view).await.len();
314 vid_commitment(&encoded_transactions, &[], num_storage_nodes, version)
315}
316
317pub async fn build_vid_proposal<TYPES: NodeType>(
318 membership: &EpochMembership<TYPES>,
319 view_number: ViewNumber,
320 epoch_number: Option<EpochNumber>,
321 payload: &TYPES::BlockPayload,
322 metadata: &<TYPES::BlockPayload as BlockPayload<TYPES>>::Metadata,
323 private_key: &<TYPES::SignatureKey as SignatureKey>::PrivateKey,
324 upgrade_lock: &UpgradeLock<TYPES>,
325) -> (
326 Proposal<TYPES, VidDisperse<TYPES>>,
327 Vec<Proposal<TYPES, VidDisperseShare<TYPES>>>,
328) {
329 let VidDisperseAndDuration {
330 disperse: vid_disperse,
331 duration: _,
332 } = VidDisperse::calculate_vid_disperse(
333 payload,
334 &membership.coordinator,
335 view_number,
336 epoch_number,
337 epoch_number,
338 metadata,
339 upgrade_lock,
340 )
341 .await
342 .unwrap();
343
344 let signature =
345 TYPES::SignatureKey::sign(private_key, vid_disperse.payload_commitment().as_ref())
346 .expect("Failed to sign VID commitment");
347 let vid_disperse_proposal = Proposal {
348 data: vid_disperse.clone(),
349 signature,
350 _pd: PhantomData,
351 };
352
353 (
354 vid_disperse_proposal,
355 vid_disperse
356 .to_shares()
357 .into_iter()
358 .map(|share| {
359 share
360 .to_proposal(private_key)
361 .expect("Failed to sign payload commitment")
362 })
363 .collect(),
364 )
365}
366
367#[allow(clippy::too_many_arguments)]
368pub async fn build_da_certificate<TYPES: NodeType>(
369 membership: &EpochMembership<TYPES>,
370 view_number: ViewNumber,
371 epoch_number: Option<EpochNumber>,
372 transactions: Vec<TestTransaction>,
373 metadata: &<TYPES::BlockPayload as BlockPayload<TYPES>>::Metadata,
374 public_key: &TYPES::SignatureKey,
375 private_key: &<TYPES::SignatureKey as SignatureKey>::PrivateKey,
376 upgrade_lock: &UpgradeLock<TYPES>,
377) -> anyhow::Result<DaCertificate2<TYPES>> {
378 let encoded_transactions = TestTransaction::encode(&transactions);
379
380 let da_payload_commitment = vid_commitment(
381 &encoded_transactions,
382 &metadata.encode(),
383 membership.total_nodes().await,
384 upgrade_lock.version_infallible(view_number),
385 );
386
387 let next_epoch_da_payload_commitment =
388 if upgrade_lock.epochs_enabled(view_number) && membership.epoch().is_some() {
389 Some(vid_commitment(
390 &encoded_transactions,
391 &metadata.encode(),
392 membership
393 .next_epoch_stake_table()
394 .await?
395 .total_nodes()
396 .await,
397 upgrade_lock.version_infallible(view_number),
398 ))
399 } else {
400 None
401 };
402
403 let da_data = DaData2 {
404 payload_commit: da_payload_commitment,
405 next_epoch_payload_commit: next_epoch_da_payload_commitment,
406 epoch: epoch_number,
407 };
408
409 anyhow::Ok(
410 build_cert::<TYPES, DaData2, DaVote2<TYPES>, DaCertificate2<TYPES>>(
411 da_data,
412 membership,
413 view_number,
414 public_key,
415 private_key,
416 upgrade_lock,
417 )
418 .await,
419 )
420}
421
422pub fn permute_input_with_index_order<T>(inputs: Vec<T>, order: Vec<usize>) -> Vec<T>
429where
430 T: Clone,
431{
432 let mut ordered_inputs = Vec::with_capacity(inputs.len());
433 for &index in &order {
434 ordered_inputs.push(inputs[index].clone());
435 }
436 ordered_inputs
437}
438
439pub async fn build_fake_view_with_leaf(
441 leaf: Leaf2<TestTypes>,
442 upgrade_lock: &UpgradeLock<TestTypes>,
443 epoch_height: u64,
444) -> View<TestTypes> {
445 build_fake_view_with_leaf_and_state(
446 leaf,
447 TestValidatedState::default(),
448 upgrade_lock,
449 epoch_height,
450 )
451 .await
452}
453
454pub async fn build_fake_view_with_leaf_and_state(
456 leaf: Leaf2<TestTypes>,
457 state: TestValidatedState,
458 _upgrade_lock: &UpgradeLock<TestTypes>,
459 epoch_height: u64,
460) -> View<TestTypes> {
461 let epoch = option_epoch_from_block_number(leaf.with_epoch, leaf.height(), epoch_height);
462 View {
463 view_inner: ViewInner::Leaf {
464 leaf: leaf.commit(),
465 state: state.into(),
466 delta: None,
467 epoch,
468 },
469 }
470}