hotshot_example_types/
block_types.rs

1// Copyright (c) 2021-2024 Espresso Systems (espressosys.com)
2// This file is part of the HotShot repository.
3
4// You should have received a copy of the MIT License
5// along with the HotShot repository. If not, see <https://mit-license.org/>.
6
7use std::{
8    fmt::{Debug, Display},
9    mem::size_of,
10    sync::Arc,
11};
12
13use alloy::primitives::FixedBytes;
14use async_trait::async_trait;
15use committable::{Commitment, Committable, RawCommitmentBuilder};
16use derive_more::Display;
17use hotshot_types::{
18    data::{BlockError, Leaf2, VidCommitment, ViewNumber, vid_commitment},
19    light_client::LightClientState,
20    traits::{
21        BlockPayload, ValidatedState,
22        block_contents::{
23            BlockHeader, BuilderFee, EncodeBytes, GENESIS_VID_NUM_STORAGE_NODES, TestableBlock,
24            Transaction,
25        },
26        node_implementation::NodeType,
27    },
28    utils::BuilderCommitment,
29};
30use rand::{Rng, thread_rng};
31use serde::{Deserialize, Serialize};
32use sha3::{Digest, Keccak256};
33use thiserror::Error;
34use time::OffsetDateTime;
35use vbs::version::Version;
36
37use crate::{
38    node_types::TestTypes,
39    state_types::{TestInstanceState, TestValidatedState},
40    testable_delay::{DelayConfig, SupportedTraitTypesForAsyncDelay, TestableDelay},
41};
42
43/// The transaction in a [`TestBlockPayload`].
44#[derive(Default, PartialEq, Eq, Hash, Serialize, Deserialize, Clone, Debug)]
45#[serde(try_from = "Vec<u8>")]
46pub struct TestTransaction(Vec<u8>);
47
48#[derive(Debug, Error)]
49pub enum TransactionError {
50    #[error("Transaction too long")]
51    TransactionTooLong,
52}
53
54impl TryFrom<Vec<u8>> for TestTransaction {
55    type Error = TransactionError;
56
57    fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
58        Self::try_new(value).ok_or(TransactionError::TransactionTooLong)
59    }
60}
61
62impl TestTransaction {
63    /// Construct a new transaction
64    ///
65    /// # Panics
66    /// If `bytes.len()` > `u32::MAX`
67    pub fn new(bytes: Vec<u8>) -> Self {
68        Self::try_new(bytes).expect("Vector too long")
69    }
70
71    /// Construct a new transaction.
72    /// Returns `None` if `bytes.len()` > `u32::MAX`
73    /// for cross-platform compatibility
74    pub fn try_new(bytes: Vec<u8>) -> Option<Self> {
75        if u32::try_from(bytes.len()).is_err() {
76            None
77        } else {
78            Some(Self(bytes))
79        }
80    }
81
82    /// Get reference to raw bytes of transaction
83    pub fn bytes(&self) -> &Vec<u8> {
84        &self.0
85    }
86
87    /// Convert transaction to raw vector of bytes
88    pub fn into_bytes(self) -> Vec<u8> {
89        self.0
90    }
91
92    /// Encode a list of transactions into bytes.
93    ///
94    /// # Errors
95    /// If the transaction length conversion fails.
96    pub fn encode(transactions: &[Self]) -> Vec<u8> {
97        let mut encoded = Vec::new();
98
99        for txn in transactions {
100            // The transaction length is converted from `usize` to `u32` to ensure consistent
101            // number of bytes on different platforms.
102            let txn_size = u32::try_from(txn.0.len())
103                .expect("Invalid transaction length")
104                .to_le_bytes();
105
106            // Concatenate the bytes of the transaction size and the transaction itself.
107            encoded.extend(txn_size);
108            encoded.extend(&txn.0);
109        }
110
111        encoded
112    }
113}
114
115impl Committable for TestTransaction {
116    fn commit(&self) -> Commitment<Self> {
117        let builder = committable::RawCommitmentBuilder::new("Txn Comm");
118        let mut hasher = Keccak256::new();
119        hasher.update(&self.0);
120        let generic_array = hasher.finalize();
121        builder.generic_byte_array(&generic_array).finalize()
122    }
123
124    fn tag() -> String {
125        "TEST_TXN".to_string()
126    }
127}
128
129impl Transaction for TestTransaction {
130    fn minimum_block_size(&self) -> u64 {
131        // the estimation on transaction size is the length of the transaction
132        self.0.len() as u64
133    }
134}
135
136/// A [`BlockPayload`] that contains a list of `TestTransaction`.
137#[derive(PartialEq, Eq, Hash, Serialize, Deserialize, Clone, Debug)]
138pub struct TestBlockPayload {
139    /// List of transactions.
140    pub transactions: Vec<TestTransaction>,
141}
142
143impl TestBlockPayload {
144    /// Create a genesis block payload with bytes `vec![0]`, to be used for
145    /// consensus task initiation.
146    /// # Panics
147    /// If the `VidScheme` construction fails.
148    #[must_use]
149    pub fn genesis() -> Self {
150        TestBlockPayload {
151            transactions: vec![],
152        }
153    }
154}
155
156impl Display for TestBlockPayload {
157    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
158        write!(f, "BlockPayload #txns={}", self.transactions.len())
159    }
160}
161
162impl<TYPES: NodeType> TestableBlock<TYPES> for TestBlockPayload {
163    fn genesis() -> Self {
164        Self::genesis()
165    }
166
167    fn txn_count(&self) -> u64 {
168        self.transactions.len() as u64
169    }
170}
171
172#[derive(
173    Debug, Display, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash,
174)]
175#[display("{{num_transactions:{num_transactions}}}")]
176pub struct TestMetadata {
177    pub num_transactions: u64,
178}
179
180impl EncodeBytes for TestMetadata {
181    fn encode(&self) -> Arc<[u8]> {
182        Arc::new([])
183    }
184}
185
186impl EncodeBytes for TestBlockPayload {
187    fn encode(&self) -> Arc<[u8]> {
188        TestTransaction::encode(&self.transactions).into()
189    }
190}
191
192#[async_trait]
193impl<TYPES: NodeType> BlockPayload<TYPES> for TestBlockPayload {
194    type Error = BlockError;
195    type Instance = TestInstanceState;
196    type Transaction = TestTransaction;
197    type Metadata = TestMetadata;
198    type ValidatedState = TestValidatedState;
199
200    async fn from_transactions(
201        transactions: impl IntoIterator<Item = Self::Transaction> + Send,
202        _validated_state: &Self::ValidatedState,
203        _instance_state: &Self::Instance,
204    ) -> Result<(Self, Self::Metadata), Self::Error> {
205        let txns_vec: Vec<TestTransaction> = transactions.into_iter().collect();
206        let metadata = TestMetadata {
207            num_transactions: txns_vec.len() as u64,
208        };
209        Ok((
210            Self {
211                transactions: txns_vec,
212            },
213            metadata,
214        ))
215    }
216
217    fn from_bytes(encoded_transactions: &[u8], _metadata: &Self::Metadata) -> Self {
218        let mut transactions = Vec::new();
219        let mut current_index = 0;
220        while current_index < encoded_transactions.len() {
221            // Decode the transaction length.
222            let txn_start_index = current_index + size_of::<u32>();
223            let mut txn_len_bytes = [0; size_of::<u32>()];
224            txn_len_bytes.copy_from_slice(&encoded_transactions[current_index..txn_start_index]);
225            let txn_len: usize = u32::from_le_bytes(txn_len_bytes) as usize;
226
227            // Get the transaction.
228            let next_index = txn_start_index + txn_len;
229            transactions.push(TestTransaction(
230                encoded_transactions[txn_start_index..next_index].to_vec(),
231            ));
232            current_index = next_index;
233        }
234
235        Self { transactions }
236    }
237
238    fn empty() -> (Self, Self::Metadata) {
239        (
240            Self::genesis(),
241            TestMetadata {
242                num_transactions: 0,
243            },
244        )
245    }
246
247    fn builder_commitment(&self, _metadata: &Self::Metadata) -> BuilderCommitment {
248        let mut digest = sha2::Sha256::new();
249        for txn in &self.transactions {
250            digest.update(&txn.0);
251        }
252        BuilderCommitment::from_raw_digest(digest.finalize())
253    }
254
255    fn transactions<'a>(
256        &'a self,
257        _metadata: &'a Self::Metadata,
258    ) -> impl 'a + Iterator<Item = Self::Transaction> {
259        self.transactions.iter().cloned()
260    }
261
262    fn txn_bytes(&self) -> usize {
263        self.transactions.iter().map(|tx| tx.0.len()).sum()
264    }
265}
266
267/// A [`BlockHeader`] that commits to [`TestBlockPayload`].
268#[derive(PartialEq, Eq, Hash, Clone, Debug, Deserialize, Serialize)]
269pub struct TestBlockHeader {
270    /// Block number.
271    pub block_number: u64,
272    /// VID commitment to the payload.
273    pub payload_commitment: VidCommitment,
274    /// Fast commitment for builder verification
275    pub builder_commitment: BuilderCommitment,
276    /// block metadata
277    pub metadata: TestMetadata,
278    /// Timestamp when this header was created.
279    pub timestamp: u64,
280    /// Timestamp when this header was created.
281    pub timestamp_millis: u64,
282    /// random
283    pub random: u64,
284    /// version
285    pub version: Version,
286}
287
288impl TestBlockHeader {
289    pub fn new<TYPES: NodeType<BlockHeader = Self>>(
290        parent_leaf: &Leaf2<TYPES>,
291        payload_commitment: VidCommitment,
292        builder_commitment: BuilderCommitment,
293        metadata: TestMetadata,
294        version: Version,
295    ) -> Self {
296        let parent = parent_leaf.block_header();
297
298        let time = OffsetDateTime::now_utc();
299
300        let mut timestamp = time.unix_timestamp() as u64;
301        let mut timestamp_millis = (time.unix_timestamp_nanos() / 1_000_000) as u64;
302
303        if timestamp < parent.timestamp {
304            // Prevent decreasing timestamps.
305            timestamp = parent.timestamp;
306        }
307
308        if timestamp_millis < parent.timestamp_millis {
309            // Prevent decreasing timestamps.
310            timestamp_millis = parent.timestamp_millis;
311        }
312
313        let random = thread_rng().gen_range(0..=u64::MAX);
314
315        Self {
316            block_number: parent.block_number + 1,
317            payload_commitment,
318            builder_commitment,
319            metadata,
320            timestamp,
321            timestamp_millis,
322            random,
323            version,
324        }
325    }
326}
327
328impl<
329    TYPES: NodeType<
330            BlockHeader = Self,
331            BlockPayload = TestBlockPayload,
332            InstanceState = TestInstanceState,
333        >,
334> BlockHeader<TYPES> for TestBlockHeader
335{
336    type Error = std::convert::Infallible;
337
338    async fn new(
339        _parent_state: &TYPES::ValidatedState,
340        instance_state: &<TYPES::ValidatedState as ValidatedState<TYPES>>::Instance,
341        parent_leaf: &Leaf2<TYPES>,
342        payload_commitment: VidCommitment,
343        builder_commitment: BuilderCommitment,
344        metadata: <TYPES::BlockPayload as BlockPayload<TYPES>>::Metadata,
345        _builder_fee: BuilderFee<TYPES>,
346        version: Version,
347        _view_number: u64,
348    ) -> Result<Self, Self::Error> {
349        Self::run_delay_settings_from_config(&instance_state.delay_config).await;
350        Ok(Self::new(
351            parent_leaf,
352            payload_commitment,
353            builder_commitment,
354            metadata,
355            version,
356        ))
357    }
358
359    fn genesis(
360        _instance_state: &<TYPES::ValidatedState as ValidatedState<TYPES>>::Instance,
361        payload: TYPES::BlockPayload,
362        metadata: &<TYPES::BlockPayload as BlockPayload<TYPES>>::Metadata,
363        version: Version,
364    ) -> Self {
365        let builder_commitment =
366            <TestBlockPayload as BlockPayload<TYPES>>::builder_commitment(&payload, metadata);
367
368        let payload_bytes = payload.encode();
369        let payload_commitment = vid_commitment(
370            &payload_bytes,
371            &metadata.encode(),
372            GENESIS_VID_NUM_STORAGE_NODES,
373            version,
374        );
375
376        Self {
377            block_number: 0,
378            payload_commitment,
379            builder_commitment,
380            metadata: *metadata,
381            timestamp: 0,
382            timestamp_millis: 0,
383            random: 0,
384            version,
385        }
386    }
387
388    fn block_number(&self) -> u64 {
389        self.block_number
390    }
391
392    fn payload_commitment(&self) -> VidCommitment {
393        self.payload_commitment
394    }
395
396    fn metadata(&self) -> &<TYPES::BlockPayload as BlockPayload<TYPES>>::Metadata {
397        &self.metadata
398    }
399
400    fn builder_commitment(&self) -> BuilderCommitment {
401        self.builder_commitment.clone()
402    }
403
404    fn version(&self) -> Version {
405        self.version
406    }
407
408    fn get_light_client_state(&self, view: ViewNumber) -> anyhow::Result<LightClientState> {
409        LightClientState::new(
410            view.u64(),
411            self.block_number,
412            self.payload_commitment.as_ref(),
413        )
414    }
415
416    fn auth_root(&self) -> anyhow::Result<FixedBytes<32>> {
417        Ok(FixedBytes::from([0u8; 32]))
418    }
419
420    fn timestamp(&self) -> u64 {
421        self.timestamp
422    }
423
424    fn timestamp_millis(&self) -> u64 {
425        self.timestamp_millis
426    }
427}
428
429impl Committable for TestBlockHeader {
430    fn commit(&self) -> Commitment<Self> {
431        RawCommitmentBuilder::new("Header Comm")
432            .u64_field(
433                "block number",
434                <TestBlockHeader as BlockHeader<TestTypes>>::block_number(self),
435            )
436            .constant_str("payload commitment")
437            .fixed_size_bytes(
438                <TestBlockHeader as BlockHeader<TestTypes>>::payload_commitment(self).as_ref(),
439            )
440            .finalize()
441    }
442
443    fn tag() -> String {
444        "TEST_HEADER".to_string()
445    }
446}
447
448#[async_trait]
449impl TestableDelay for TestBlockHeader {
450    async fn run_delay_settings_from_config(delay_config: &DelayConfig) {
451        if let Some(settings) =
452            delay_config.get_setting(&SupportedTraitTypesForAsyncDelay::BlockHeader)
453        {
454            Self::handle_async_delay(settings).await;
455        }
456    }
457}