hotshot_testing/block_builder/
mod.rs1use std::collections::HashMap;
8
9use async_broadcast::Receiver;
10use async_trait::async_trait;
11use futures::Stream;
12use hotshot::{traits::BlockPayload, types::Event};
13use hotshot_builder_api::{
14 v0_1::{
15 self,
16 block_info::{AvailableBlockData, AvailableBlockInfo},
17 builder::{Error, Options},
18 },
19 v0_2::block_info::AvailableBlockHeaderInputV1,
20};
21use hotshot_types::{
22 constants::LEGACY_BUILDER_MODULE,
23 traits::{
24 block_contents::EncodeBytes, node_implementation::NodeType,
25 signature_key::BuilderSignatureKey,
26 },
27};
28use tide_disco::{method::ReadState, App, Url};
29use tokio::spawn;
30use vbs::version::StaticVersionType;
31
32use crate::test_builder::BuilderChange;
33
34pub mod random;
35pub use random::RandomBuilderImplementation;
36
37pub mod simple;
38pub use simple::SimpleBuilderImplementation;
39
40#[async_trait]
41pub trait TestBuilderImplementation<TYPES: NodeType>
42where
43 <TYPES as NodeType>::InstanceState: Default,
44{
45 type Config: Default;
46
47 async fn start(
48 num_storage_nodes: usize,
49 url: Url,
50 options: Self::Config,
51 changes: HashMap<u64, BuilderChange>,
52 ) -> Box<dyn BuilderTask<TYPES>>;
53}
54
55pub trait BuilderTask<TYPES: NodeType>: Send + Sync {
56 fn start(
57 self: Box<Self>,
58 stream: Box<dyn Stream<Item = Event<TYPES>> + std::marker::Unpin + Send + 'static>,
59 );
60}
61
62#[derive(Debug, Clone)]
64struct BlockEntry<TYPES: NodeType> {
65 metadata: AvailableBlockInfo<TYPES>,
66 payload: Option<AvailableBlockData<TYPES>>,
67 header_input: Option<AvailableBlockHeaderInputV1<TYPES>>,
68}
69
70pub fn run_builder_source<TYPES, Source>(
75 url: Url,
76 mut change_receiver: Receiver<BuilderChange>,
77 source: Source,
78) where
79 TYPES: NodeType,
80 <TYPES as NodeType>::InstanceState: Default,
81 Source: Clone + Send + Sync + tide_disco::method::ReadState + 'static,
82 <Source as ReadState>::State: Sync + Send + v0_1::data_source::BuilderDataSource<TYPES>,
83{
84 spawn(async move {
85 let start_builder = |url: Url, source: Source| -> _ {
86 let builder_api_0_1 = hotshot_builder_api::v0_1::builder::define_api::<Source, TYPES>(
87 &Options::default(),
88 )
89 .expect("Failed to construct the builder API");
90
91 let mut app: App<Source, Error> = App::with_state(source);
92 app.register_module(LEGACY_BUILDER_MODULE, builder_api_0_1)
93 .expect("Failed to register the builder API 0.1");
94 spawn(app.serve(url, hotshot_builder_api::v0_1::Version::instance()))
95 };
96
97 let mut handle = Some(start_builder(url.clone(), source.clone()));
98
99 while let Ok(event) = change_receiver.recv().await {
100 match event {
101 BuilderChange::Up if handle.is_none() => {
102 handle = Some(start_builder(url.clone(), source.clone()));
103 },
104 BuilderChange::Down => {
105 if let Some(handle) = handle.take() {
106 handle.abort();
107 }
108 },
109 _ => {},
110 }
111 }
112 });
113}
114
115pub fn run_builder_source_0_1<TYPES, Source>(
120 url: Url,
121 mut change_receiver: Receiver<BuilderChange>,
122 source: Source,
123) where
124 TYPES: NodeType,
125 <TYPES as NodeType>::InstanceState: Default,
126 Source: Clone + Send + Sync + tide_disco::method::ReadState + 'static,
127 <Source as ReadState>::State: Sync + Send + v0_1::data_source::BuilderDataSource<TYPES>,
128{
129 spawn(async move {
130 let start_builder = |url: Url, source: Source| -> _ {
131 let builder_api = hotshot_builder_api::v0_1::builder::define_api::<Source, TYPES>(
132 &Options::default(),
133 )
134 .expect("Failed to construct the builder API");
135 let mut app: App<Source, Error> = App::with_state(source);
136 app.register_module(LEGACY_BUILDER_MODULE, builder_api)
137 .expect("Failed to register the builder API");
138 spawn(app.serve(url, hotshot_builder_api::v0_1::Version::instance()))
139 };
140
141 let mut handle = Some(start_builder(url.clone(), source.clone()));
142
143 while let Ok(event) = change_receiver.recv().await {
144 match event {
145 BuilderChange::Up if handle.is_none() => {
146 handle = Some(start_builder(url.clone(), source.clone()));
147 },
148 BuilderChange::Down => {
149 if let Some(handle) = handle.take() {
150 handle.abort();
151 }
152 },
153 _ => {},
154 }
155 }
156 });
157}
158
159async fn build_block<TYPES: NodeType>(
161 transactions: Vec<TYPES::Transaction>,
162 pub_key: TYPES::BuilderSignatureKey,
163 priv_key: <TYPES::BuilderSignatureKey as BuilderSignatureKey>::BuilderPrivateKey,
164) -> BlockEntry<TYPES>
165where
166 <TYPES as NodeType>::InstanceState: Default,
167{
168 let (block_payload, metadata) = TYPES::BlockPayload::from_transactions(
169 transactions,
170 &Default::default(),
171 &Default::default(),
172 )
173 .await
174 .expect("failed to build block payload from transactions");
175
176 let commitment = block_payload.builder_commitment(&metadata);
177
178 let block_size = block_payload.encode().len() as u64;
180
181 let signature_over_block_info =
182 TYPES::BuilderSignatureKey::sign_block_info(&priv_key, block_size, 123, &commitment)
183 .expect("Failed to sign block info");
184
185 let signature_over_builder_commitment =
186 TYPES::BuilderSignatureKey::sign_builder_message(&priv_key, commitment.as_ref())
187 .expect("Failed to sign commitment");
188
189 let signature_over_fee_info =
190 TYPES::BuilderSignatureKey::sign_fee(&priv_key, 123_u64, &metadata)
191 .expect("Failed to sign fee info");
192
193 let block = AvailableBlockData {
194 block_payload,
195 metadata,
196 sender: pub_key.clone(),
197 signature: signature_over_builder_commitment,
198 };
199 let metadata = AvailableBlockInfo {
200 sender: pub_key.clone(),
201 signature: signature_over_block_info,
202 block_hash: commitment,
203 block_size,
204 offered_fee: 123,
205 _phantom: std::marker::PhantomData,
206 };
207 let header_input = AvailableBlockHeaderInputV1 {
208 fee_signature: signature_over_fee_info,
209 sender: pub_key,
210 };
211
212 BlockEntry {
213 metadata,
214 payload: Some(block),
215 header_input: Some(header_input),
216 }
217}