guts_consensus/simplex/
block.rs

1//! Simplex consensus block type.
2//!
3//! This module provides the block type used by the Simplex BFT consensus engine.
4//! The block implements all required traits for commonware-consensus integration.
5
6use bytes::{Buf, BufMut};
7use commonware_codec::{varint::UInt, EncodeSize, Error as CodecError, Read, ReadExt, Write};
8use commonware_cryptography::{sha256::Digest, Committable, Digestible, Hasher, Sha256};
9
10/// A block in the Simplex consensus chain.
11///
12/// This block type is designed for the Guts decentralized code collaboration
13/// platform and follows the Simplex BFT consensus protocol.
14#[derive(Clone, Debug, PartialEq, Eq)]
15pub struct SimplexBlock {
16    /// The parent block's digest.
17    pub parent: Digest,
18
19    /// The height of the block in the blockchain.
20    pub height: u64,
21
22    /// The timestamp of the block (in milliseconds since the Unix epoch).
23    pub timestamp: u64,
24
25    /// State root after applying transactions in this block.
26    pub state_root: [u8; 32],
27
28    /// Number of transactions in this block.
29    pub tx_count: u32,
30
31    /// Serialized transaction data (for lightweight blocks, actual data synced separately).
32    pub tx_root: [u8; 32],
33
34    /// Pre-computed digest of the block.
35    digest: Digest,
36}
37
38impl SimplexBlock {
39    /// Computes the digest of a block from its components.
40    fn compute_digest(
41        parent: &Digest,
42        height: u64,
43        timestamp: u64,
44        state_root: &[u8; 32],
45        tx_count: u32,
46        tx_root: &[u8; 32],
47    ) -> Digest {
48        let mut hasher = Sha256::new();
49        hasher.update(parent);
50        hasher.update(&height.to_be_bytes());
51        hasher.update(&timestamp.to_be_bytes());
52        hasher.update(state_root);
53        hasher.update(&tx_count.to_be_bytes());
54        hasher.update(tx_root);
55        hasher.finalize()
56    }
57
58    /// Creates a new block.
59    pub fn new(
60        parent: Digest,
61        height: u64,
62        timestamp: u64,
63        state_root: [u8; 32],
64        tx_count: u32,
65        tx_root: [u8; 32],
66    ) -> Self {
67        let digest =
68            Self::compute_digest(&parent, height, timestamp, &state_root, tx_count, &tx_root);
69        Self {
70            parent,
71            height,
72            timestamp,
73            state_root,
74            tx_count,
75            tx_root,
76            digest,
77        }
78    }
79
80    /// Creates a genesis block.
81    pub fn genesis() -> Self {
82        let mut hasher = Sha256::new();
83        hasher.update(b"guts-genesis");
84        let genesis_parent = hasher.finalize();
85
86        Self::new(genesis_parent, 0, 0, [0u8; 32], 0, [0u8; 32])
87    }
88}
89
90impl Write for SimplexBlock {
91    fn write(&self, writer: &mut impl BufMut) {
92        self.parent.write(writer);
93        UInt(self.height).write(writer);
94        UInt(self.timestamp).write(writer);
95        writer.put_slice(&self.state_root);
96        UInt(self.tx_count as u64).write(writer);
97        writer.put_slice(&self.tx_root);
98    }
99}
100
101impl Read for SimplexBlock {
102    type Cfg = ();
103
104    fn read_cfg(reader: &mut impl Buf, _: &Self::Cfg) -> Result<Self, CodecError> {
105        let parent = Digest::read(reader)?;
106        let height = UInt::read(reader)?.into();
107        let timestamp = UInt::read(reader)?.into();
108
109        let mut state_root = [0u8; 32];
110        if reader.remaining() < 32 {
111            return Err(CodecError::EndOfBuffer);
112        }
113        reader.copy_to_slice(&mut state_root);
114
115        let tx_count: u64 = UInt::read(reader)?.into();
116
117        let mut tx_root = [0u8; 32];
118        if reader.remaining() < 32 {
119            return Err(CodecError::EndOfBuffer);
120        }
121        reader.copy_to_slice(&mut tx_root);
122
123        let digest = Self::compute_digest(
124            &parent,
125            height,
126            timestamp,
127            &state_root,
128            tx_count as u32,
129            &tx_root,
130        );
131        Ok(Self {
132            parent,
133            height,
134            timestamp,
135            state_root,
136            tx_count: tx_count as u32,
137            tx_root,
138            digest,
139        })
140    }
141}
142
143impl EncodeSize for SimplexBlock {
144    fn encode_size(&self) -> usize {
145        self.parent.encode_size()
146            + UInt(self.height).encode_size()
147            + UInt(self.timestamp).encode_size()
148            + 32 // state_root
149            + UInt(self.tx_count as u64).encode_size()
150            + 32 // tx_root
151    }
152}
153
154impl Digestible for SimplexBlock {
155    type Digest = Digest;
156
157    fn digest(&self) -> Digest {
158        self.digest
159    }
160}
161
162impl Committable for SimplexBlock {
163    type Commitment = Digest;
164
165    fn commitment(&self) -> Digest {
166        self.digest
167    }
168}
169
170impl commonware_consensus::Block for SimplexBlock {
171    fn parent(&self) -> Digest {
172        self.parent
173    }
174
175    fn height(&self) -> u64 {
176        self.height
177    }
178}
179
180#[cfg(test)]
181mod tests {
182    use super::*;
183    use commonware_codec::Encode;
184
185    /// Create a test parent digest from bytes.
186    fn test_parent() -> Digest {
187        let genesis = SimplexBlock::genesis();
188        genesis.digest()
189    }
190
191    #[test]
192    fn test_genesis_block() {
193        let genesis = SimplexBlock::genesis();
194        assert_eq!(genesis.height, 0);
195        assert_eq!(genesis.timestamp, 0);
196        assert_eq!(genesis.tx_count, 0);
197    }
198
199    #[test]
200    fn test_block_serialization() {
201        let block = SimplexBlock::new(test_parent(), 1, 1234567890, [1u8; 32], 5, [2u8; 32]);
202
203        // Encode
204        let encoded = block.encode();
205
206        // Decode
207        let decoded = SimplexBlock::read(&mut encoded.as_ref()).unwrap();
208
209        assert_eq!(block.height, decoded.height);
210        assert_eq!(block.timestamp, decoded.timestamp);
211        assert_eq!(block.state_root, decoded.state_root);
212        assert_eq!(block.tx_count, decoded.tx_count);
213        assert_eq!(block.digest(), decoded.digest());
214    }
215
216    #[test]
217    fn test_block_digest_consistency() {
218        let parent = test_parent();
219        let block1 = SimplexBlock::new(parent, 1, 1234567890, [1u8; 32], 5, [2u8; 32]);
220
221        let block2 = SimplexBlock::new(parent, 1, 1234567890, [1u8; 32], 5, [2u8; 32]);
222
223        assert_eq!(block1.digest(), block2.digest());
224    }
225
226    #[test]
227    fn test_different_blocks_different_digests() {
228        let parent = test_parent();
229        let block1 = SimplexBlock::new(parent, 1, 1234567890, [1u8; 32], 5, [2u8; 32]);
230
231        let block2 = SimplexBlock::new(
232            parent, 2, // Different height
233            1234567890, [1u8; 32], 5, [2u8; 32],
234        );
235
236        assert_ne!(block1.digest(), block2.digest());
237    }
238}