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