guts_consensus/
block.rs

1//! Consensus block structure.
2//!
3//! Blocks contain ordered transactions and are the unit of consensus.
4
5use crate::transaction::{
6    SerializablePublicKey, SerializableSignature, Transaction, TransactionId,
7};
8use serde::{Deserialize, Serialize};
9use sha2::{Digest, Sha256};
10
11/// A unique block identifier (SHA-256 hash of the block header).
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
13pub struct BlockId([u8; 32]);
14
15impl BlockId {
16    /// The genesis block ID (all zeros).
17    pub const GENESIS_PARENT: Self = Self([0u8; 32]);
18
19    /// Creates a block ID from raw bytes.
20    pub fn from_bytes(bytes: [u8; 32]) -> Self {
21        Self(bytes)
22    }
23
24    /// Returns the raw bytes.
25    pub fn as_bytes(&self) -> &[u8; 32] {
26        &self.0
27    }
28
29    /// Returns the hex representation.
30    pub fn to_hex(&self) -> String {
31        hex::encode(self.0)
32    }
33
34    /// Creates a block ID from a hex string.
35    pub fn from_hex(hex_str: &str) -> Result<Self, hex::FromHexError> {
36        let mut bytes = [0u8; 32];
37        hex::decode_to_slice(hex_str, &mut bytes)?;
38        Ok(Self(bytes))
39    }
40}
41
42impl Default for BlockId {
43    fn default() -> Self {
44        Self::GENESIS_PARENT
45    }
46}
47
48impl std::fmt::Display for BlockId {
49    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
50        write!(f, "{}", self.to_hex())
51    }
52}
53
54/// A block header containing metadata.
55#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
56pub struct BlockHeader {
57    /// Block height (0 = genesis).
58    pub height: u64,
59
60    /// Parent block hash.
61    pub parent: BlockId,
62
63    /// Block producer (validator public key).
64    pub producer: SerializablePublicKey,
65
66    /// Timestamp (unix milliseconds).
67    pub timestamp: u64,
68
69    /// Merkle root of transactions.
70    pub tx_root: [u8; 32],
71
72    /// State root after applying all transactions.
73    pub state_root: [u8; 32],
74
75    /// Number of transactions in this block.
76    pub tx_count: u32,
77}
78
79impl BlockHeader {
80    /// Computes the block ID from the header.
81    pub fn id(&self) -> BlockId {
82        let bytes = serde_json::to_vec(self).expect("header serialization should not fail");
83        let mut hasher = Sha256::new();
84        hasher.update(&bytes);
85        let result = hasher.finalize();
86        let mut id = [0u8; 32];
87        id.copy_from_slice(&result);
88        BlockId(id)
89    }
90}
91
92/// A full block containing header and transactions.
93#[derive(Debug, Clone, Serialize, Deserialize)]
94pub struct Block {
95    /// Block header.
96    pub header: BlockHeader,
97
98    /// Ordered transactions in this block.
99    pub transactions: Vec<Transaction>,
100}
101
102impl Block {
103    /// Creates a new block.
104    pub fn new(
105        height: u64,
106        parent: BlockId,
107        producer: SerializablePublicKey,
108        timestamp: u64,
109        transactions: Vec<Transaction>,
110        state_root: [u8; 32],
111    ) -> Self {
112        let tx_root = Self::compute_tx_root(&transactions);
113        let tx_count = transactions.len() as u32;
114
115        let header = BlockHeader {
116            height,
117            parent,
118            producer,
119            timestamp,
120            tx_root,
121            state_root,
122            tx_count,
123        };
124
125        Self {
126            header,
127            transactions,
128        }
129    }
130
131    /// Creates the genesis block.
132    pub fn genesis(producer: SerializablePublicKey) -> Self {
133        Self::new(0, BlockId::GENESIS_PARENT, producer, 0, vec![], [0u8; 32])
134    }
135
136    /// Returns the block ID.
137    pub fn id(&self) -> BlockId {
138        self.header.id()
139    }
140
141    /// Returns the block height.
142    pub fn height(&self) -> u64 {
143        self.header.height
144    }
145
146    /// Returns the parent block ID.
147    pub fn parent(&self) -> BlockId {
148        self.header.parent
149    }
150
151    /// Returns the timestamp.
152    pub fn timestamp(&self) -> u64 {
153        self.header.timestamp
154    }
155
156    /// Returns the number of transactions.
157    pub fn tx_count(&self) -> usize {
158        self.transactions.len()
159    }
160
161    /// Computes the Merkle root of transactions.
162    fn compute_tx_root(transactions: &[Transaction]) -> [u8; 32] {
163        if transactions.is_empty() {
164            return [0u8; 32];
165        }
166
167        // Simple Merkle tree: hash pairs of transaction IDs
168        let mut hashes: Vec<[u8; 32]> = transactions.iter().map(|tx| *tx.id().as_bytes()).collect();
169
170        while hashes.len() > 1 {
171            let mut next_level = Vec::with_capacity(hashes.len().div_ceil(2));
172
173            for chunk in hashes.chunks(2) {
174                let mut hasher = Sha256::new();
175                hasher.update(chunk[0]);
176                if chunk.len() > 1 {
177                    hasher.update(chunk[1]);
178                } else {
179                    hasher.update(chunk[0]); // Duplicate last hash if odd
180                }
181                let result = hasher.finalize();
182                let mut hash = [0u8; 32];
183                hash.copy_from_slice(&result);
184                next_level.push(hash);
185            }
186
187            hashes = next_level;
188        }
189
190        hashes[0]
191    }
192
193    /// Verifies the transaction root matches.
194    pub fn verify_tx_root(&self) -> bool {
195        let computed = Self::compute_tx_root(&self.transactions);
196        computed == self.header.tx_root
197    }
198
199    /// Returns an iterator over transaction IDs.
200    pub fn transaction_ids(&self) -> impl Iterator<Item = TransactionId> + '_ {
201        self.transactions.iter().map(|tx| tx.id())
202    }
203}
204
205/// Block with consensus metadata (votes, signatures).
206#[derive(Debug, Clone, Serialize, Deserialize)]
207pub struct FinalizedBlock {
208    /// The block.
209    pub block: Block,
210
211    /// View number when finalized.
212    pub view: u64,
213
214    /// Validator signatures (notarization).
215    pub signatures: Vec<(SerializablePublicKey, SerializableSignature)>,
216}
217
218impl FinalizedBlock {
219    /// Creates a new finalized block.
220    pub fn new(
221        block: Block,
222        view: u64,
223        signatures: Vec<(SerializablePublicKey, SerializableSignature)>,
224    ) -> Self {
225        Self {
226            block,
227            view,
228            signatures,
229        }
230    }
231
232    /// Returns the block ID.
233    pub fn id(&self) -> BlockId {
234        self.block.id()
235    }
236
237    /// Returns the block height.
238    pub fn height(&self) -> u64 {
239        self.block.height()
240    }
241
242    /// Returns the number of signatures.
243    pub fn signature_count(&self) -> usize {
244        self.signatures.len()
245    }
246}
247
248#[cfg(test)]
249mod tests {
250    use super::*;
251    use commonware_cryptography::{ed25519, PrivateKeyExt, Signer};
252
253    fn test_keypair() -> (SerializablePublicKey, SerializableSignature) {
254        let key = ed25519::PrivateKey::from_seed(42);
255        let sig = key.sign(Some(b"_GUTS"), b"test");
256        (
257            SerializablePublicKey::from_pubkey(&key.public_key()),
258            SerializableSignature::from_signature(&sig),
259        )
260    }
261
262    #[test]
263    fn test_block_id_roundtrip() {
264        let bytes = [0xab; 32];
265        let id = BlockId::from_bytes(bytes);
266        assert_eq!(id.as_bytes(), &bytes);
267
268        let hex = id.to_hex();
269        let parsed = BlockId::from_hex(&hex).unwrap();
270        assert_eq!(id, parsed);
271    }
272
273    #[test]
274    fn test_genesis_block() {
275        let (producer, _) = test_keypair();
276        let genesis = Block::genesis(producer);
277
278        assert_eq!(genesis.height(), 0);
279        assert_eq!(genesis.parent(), BlockId::GENESIS_PARENT);
280        assert_eq!(genesis.tx_count(), 0);
281        assert!(genesis.verify_tx_root());
282    }
283
284    #[test]
285    fn test_block_with_transactions() {
286        let (producer, signature) = test_keypair();
287
288        let tx = Transaction::CreateRepository {
289            owner: "alice".into(),
290            name: "test".into(),
291            description: "A test".into(),
292            default_branch: "main".into(),
293            visibility: "public".into(),
294            creator: producer.clone(),
295            signature: signature.clone(),
296        };
297
298        let block = Block::new(
299            1,
300            BlockId::GENESIS_PARENT,
301            producer,
302            12345,
303            vec![tx],
304            [0u8; 32],
305        );
306
307        assert_eq!(block.height(), 1);
308        assert_eq!(block.tx_count(), 1);
309        assert!(block.verify_tx_root());
310    }
311
312    #[test]
313    fn test_block_id_unique() {
314        let (producer, _) = test_keypair();
315
316        let block1 = Block::new(
317            1,
318            BlockId::GENESIS_PARENT,
319            producer.clone(),
320            12345,
321            vec![],
322            [0u8; 32],
323        );
324
325        let block2 = Block::new(
326            2, // Different height
327            BlockId::GENESIS_PARENT,
328            producer,
329            12345,
330            vec![],
331            [0u8; 32],
332        );
333
334        assert_ne!(block1.id(), block2.id());
335    }
336
337    #[test]
338    fn test_finalized_block() {
339        let (producer, sig) = test_keypair();
340        let block = Block::genesis(producer.clone());
341        let signatures = vec![(producer, sig)];
342
343        let finalized = FinalizedBlock::new(block, 1, signatures);
344
345        assert_eq!(finalized.height(), 0);
346        assert_eq!(finalized.signature_count(), 1);
347        assert_eq!(finalized.view, 1);
348    }
349}