alto_types/
block.rs

1use crate::consensus::{Finalization, Notarization, Scheme};
2use bytes::{Buf, BufMut};
3use commonware_codec::{varint::UInt, EncodeSize, Error, Read, ReadExt, Write};
4use commonware_cryptography::{sha256::Digest, Committable, Digestible, Hasher, Sha256};
5use rand::rngs::OsRng;
6
7#[derive(Clone, Debug, PartialEq, Eq)]
8pub struct Block {
9    /// The parent block's digest.
10    pub parent: Digest,
11
12    /// The height of the block in the blockchain.
13    pub height: u64,
14
15    /// The timestamp of the block (in milliseconds since the Unix epoch).
16    pub timestamp: u64,
17
18    /// Pre-computed digest of the block.
19    digest: Digest,
20}
21
22impl Block {
23    fn compute_digest(parent: &Digest, height: u64, timestamp: u64) -> Digest {
24        let mut hasher = Sha256::new();
25        hasher.update(parent);
26        hasher.update(&height.to_be_bytes());
27        hasher.update(&timestamp.to_be_bytes());
28        hasher.finalize()
29    }
30
31    pub fn new(parent: Digest, height: u64, timestamp: u64) -> Self {
32        let digest = Self::compute_digest(&parent, height, timestamp);
33        Self {
34            parent,
35            height,
36            timestamp,
37            digest,
38        }
39    }
40}
41
42impl Write for Block {
43    fn write(&self, writer: &mut impl BufMut) {
44        self.parent.write(writer);
45        UInt(self.height).write(writer);
46        UInt(self.timestamp).write(writer);
47    }
48}
49
50impl Read for Block {
51    type Cfg = ();
52
53    fn read_cfg(reader: &mut impl Buf, _: &Self::Cfg) -> Result<Self, Error> {
54        let parent = Digest::read(reader)?;
55        let height = UInt::read(reader)?.into();
56        let timestamp = UInt::read(reader)?.into();
57
58        // Pre-compute the digest
59        let digest = Self::compute_digest(&parent, height, timestamp);
60        Ok(Self {
61            parent,
62            height,
63            timestamp,
64
65            digest,
66        })
67    }
68}
69
70impl EncodeSize for Block {
71    fn encode_size(&self) -> usize {
72        self.parent.encode_size()
73            + UInt(self.height).encode_size()
74            + UInt(self.timestamp).encode_size()
75    }
76}
77
78impl Digestible for Block {
79    type Digest = Digest;
80
81    fn digest(&self) -> Digest {
82        self.digest
83    }
84}
85
86impl Committable for Block {
87    type Commitment = Digest;
88
89    fn commitment(&self) -> Digest {
90        self.digest
91    }
92}
93
94#[derive(Clone, Debug, PartialEq, Eq)]
95pub struct Notarized {
96    pub proof: Notarization,
97    pub block: Block,
98}
99
100impl Notarized {
101    pub fn new(proof: Notarization, block: Block) -> Self {
102        Self { proof, block }
103    }
104
105    pub fn verify(&self, scheme: &Scheme, namespace: &[u8]) -> bool {
106        self.proof.verify(&mut OsRng, scheme, namespace)
107    }
108}
109
110impl Write for Notarized {
111    fn write(&self, buf: &mut impl BufMut) {
112        self.proof.write(buf);
113        self.block.write(buf);
114    }
115}
116
117impl Read for Notarized {
118    type Cfg = ();
119
120    fn read_cfg(buf: &mut impl Buf, _: &Self::Cfg) -> Result<Self, Error> {
121        let proof = Notarization::read(buf)?;
122        let block = Block::read(buf)?;
123
124        // Ensure the proof is for the block
125        if proof.proposal.payload != block.digest() {
126            return Err(Error::Invalid(
127                "types::Notarized",
128                "Proof payload does not match block digest",
129            ));
130        }
131        Ok(Self { proof, block })
132    }
133}
134
135impl EncodeSize for Notarized {
136    fn encode_size(&self) -> usize {
137        self.proof.encode_size() + self.block.encode_size()
138    }
139}
140
141#[derive(Clone, Debug, PartialEq, Eq)]
142pub struct Finalized {
143    pub proof: Finalization,
144    pub block: Block,
145}
146
147impl Finalized {
148    pub fn new(proof: Finalization, block: Block) -> Self {
149        Self { proof, block }
150    }
151
152    pub fn verify(&self, scheme: &Scheme, namespace: &[u8]) -> bool {
153        self.proof.verify(&mut OsRng, scheme, namespace)
154    }
155}
156
157impl Write for Finalized {
158    fn write(&self, buf: &mut impl BufMut) {
159        self.proof.write(buf);
160        self.block.write(buf);
161    }
162}
163
164impl Read for Finalized {
165    type Cfg = ();
166
167    fn read_cfg(buf: &mut impl Buf, _: &Self::Cfg) -> Result<Self, Error> {
168        let proof = Finalization::read(buf)?;
169        let block = Block::read(buf)?;
170
171        // Ensure the proof is for the block
172        if proof.proposal.payload != block.digest() {
173            return Err(Error::Invalid(
174                "types::Finalized",
175                "Proof payload does not match block digest",
176            ));
177        }
178        Ok(Self { proof, block })
179    }
180}
181
182impl EncodeSize for Finalized {
183    fn encode_size(&self) -> usize {
184        self.proof.encode_size() + self.block.encode_size()
185    }
186}
187
188impl commonware_consensus::Block for Block {
189    fn parent(&self) -> Digest {
190        self.parent
191    }
192
193    fn height(&self) -> u64 {
194        self.height
195    }
196}