1use std::fmt::Display;
5
6use borsh::{BorshDeserialize, BorshSerialize};
7use serde::{Deserialize, Serialize};
8use tari_common_types::{
9 epoch::VnEpoch,
10 types::{CompressedPublicKey, FixedHash, PrivateKey, UncompressedPublicKey},
11};
12use tari_crypto::signatures::CompressedSchnorrSignature;
13use tari_hashing::{layer2, ValidatorNodeHashDomain};
14use tari_jellyfish::{LeafKey, SparseMerkleProofExt, TreeHash};
15use tari_utilities::ByteArray;
16
17use super::error::SidechainProofValidationError;
18use crate::{
19 command::{Command, ToCommand},
20 serde::hex_or_bytes,
21 shard_group::ShardGroup,
22 validations::check_proof_elements,
23};
24
25pub type ValidatorBlockSignature =
26 CompressedSchnorrSignature<UncompressedPublicKey, PrivateKey, ValidatorNodeHashDomain>;
27pub type CheckVnFunc<'a> = dyn Fn(&CompressedPublicKey) -> Result<bool, SidechainProofValidationError> + 'a;
28
29#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize, BorshSerialize, BorshDeserialize)]
30pub enum CommandCommitProof<C> {
31 V1(CommandCommitProofV1<C>),
32}
33
34impl<C: ToCommand> CommandCommitProof<C> {
35 pub fn new(command: C, commit_proof: SidechainBlockCommitProof, inclusion_proof: SparseMerkleProofExt) -> Self {
36 Self::V1(CommandCommitProofV1 {
37 command,
38 commit_proof,
39 inclusion_proof,
40 })
41 }
42
43 pub fn command(&self) -> &C {
44 match self {
45 CommandCommitProof::V1(v1) => &v1.command,
46 }
47 }
48
49 pub fn header(&self) -> &SidechainBlockHeader {
50 match self {
51 CommandCommitProof::V1(v1) => &v1.commit_proof.header,
52 }
53 }
54
55 pub fn epoch(&self) -> VnEpoch {
56 match self {
57 CommandCommitProof::V1(v1) => VnEpoch(v1.commit_proof.header().epoch),
58 }
59 }
60
61 pub fn shard_group(&self) -> ShardGroup {
62 match self {
63 CommandCommitProof::V1(v1) => v1.commit_proof.header().shard_group,
64 }
65 }
66
67 pub fn validate_committed(
68 &self,
69 quorum_threshold: usize,
70 check_vn: &CheckVnFunc<'_>,
71 ) -> Result<(), SidechainProofValidationError> {
72 #[allow(clippy::single_match)]
73 match self {
74 CommandCommitProof::V1(v1) => v1.validate_committed(quorum_threshold, check_vn),
75 }
76 }
77}
78
79#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize, BorshSerialize, BorshDeserialize)]
80pub struct CommandCommitProofV1<C> {
81 pub command: C,
82 pub commit_proof: SidechainBlockCommitProof,
83 pub inclusion_proof: SparseMerkleProofExt,
84}
85
86impl<C: ToCommand> CommandCommitProofV1<C> {
87 pub fn command(&self) -> &C {
88 &self.command
89 }
90
91 pub fn commit_proof(&self) -> &SidechainBlockCommitProof {
92 &self.commit_proof
93 }
94
95 pub fn inclusion_proof(&self) -> &SparseMerkleProofExt {
96 &self.inclusion_proof
97 }
98
99 fn validate_inclusion_proof(&self, command: &Command) -> Result<(), SidechainProofValidationError> {
100 let command_hash = TreeHash::new(command.hash().into_array());
101 let key = LeafKey::new(command_hash);
103 let root_hash = TreeHash::new(self.commit_proof.header.command_merkle_root.into_array());
104 self.inclusion_proof.verify_inclusion(&root_hash, &key, &command_hash)?;
105 Ok(())
106 }
107
108 pub fn validate_committed(
109 &self,
110 quorum_threshold: usize,
111 check_vn: &CheckVnFunc<'_>,
112 ) -> Result<(), SidechainProofValidationError> {
113 let command = self.command.to_command();
114 self.validate_inclusion_proof(&command)?;
115 self.commit_proof.validate_committed(quorum_threshold, check_vn)
116 }
117}
118
119#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize, BorshSerialize, BorshDeserialize)]
120pub struct SidechainBlockCommitProof {
121 pub header: SidechainBlockHeader,
122 pub proof_elements: Vec<CommitProofElement>,
123}
124
125impl SidechainBlockCommitProof {
126 pub fn validate_committed(
127 &self,
128 quorum_threshold: usize,
129 check_vn: &CheckVnFunc<'_>,
130 ) -> Result<(), SidechainProofValidationError> {
131 check_proof_elements(
132 &self.header,
133 &self.proof_elements,
134 check_vn,
135 QuorumDecision::Accept,
136 quorum_threshold,
137 )?;
138
139 Ok(())
140 }
141
142 pub fn proof_elements(&self) -> &[CommitProofElement] {
143 &self.proof_elements
144 }
145
146 pub fn header(&self) -> &SidechainBlockHeader {
147 &self.header
148 }
149
150 pub fn last_qc(&self) -> Option<&QuorumCertificate> {
151 self.proof_elements
152 .iter()
153 .filter_map(|elem| match elem {
154 CommitProofElement::QuorumCertificate(qc) => Some(qc),
155 _ => None,
156 })
157 .next_back()
158 }
159}
160
161#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize, BorshSerialize, BorshDeserialize)]
162pub enum CommitProofElement {
163 QuorumCertificate(QuorumCertificate),
164 ChainLinks(Vec<ChainLink>),
165}
166
167#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize, BorshSerialize, BorshDeserialize)]
168pub struct ChainLink {
169 #[serde(with = "hex_or_bytes")]
170 pub header_hash: FixedHash,
171 #[serde(with = "hex_or_bytes")]
172 pub parent_id: FixedHash,
173}
174
175impl ChainLink {
176 pub fn calc_block_id(&self) -> FixedHash {
177 layer2::block_hasher()
178 .chain(&self.parent_id)
179 .chain(&self.header_hash)
180 .finalize()
181 .into()
182 }
183}
184
185#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize, BorshSerialize, BorshDeserialize)]
186pub struct SidechainBlockHeader {
187 pub network: u8,
188 #[serde(with = "hex_or_bytes")]
189 pub parent_id: FixedHash,
190 #[serde(with = "hex_or_bytes")]
191 pub justify_id: FixedHash,
192 pub height: u64,
193 pub epoch: u64,
194 pub shard_group: ShardGroup,
195 pub proposed_by: CompressedPublicKey,
196 #[serde(with = "hex_or_bytes")]
197 pub state_merkle_root: FixedHash,
198 #[serde(with = "hex_or_bytes")]
199 pub command_merkle_root: FixedHash,
200 pub signature: ValidatorBlockSignature,
202 pub accumulated_data: ShardGroupAccumulatedData,
203 #[serde(with = "hex_or_bytes")]
204 pub metadata_hash: FixedHash,
205}
206
207impl SidechainBlockHeader {
208 pub fn calculate_hash(&self) -> FixedHash {
209 let fields = BlockHeaderHashFields::V1(BlockHeaderHashFieldsV1 {
210 network: self.network,
211 justify_id: &self.justify_id,
212 height: self.height,
213 epoch: self.epoch,
214 shard_group: self.shard_group,
215 proposed_by: self.proposed_by.as_bytes(),
216 state_merkle_root: &self.state_merkle_root,
217 command_merkle_root: &self.command_merkle_root,
218 accumulated_data: &self.accumulated_data,
219 metadata_hash: &self.metadata_hash,
220 });
221
222 layer2::block_hasher().chain(&fields).finalize().into()
223 }
224
225 pub fn calculate_block_id(&self) -> FixedHash {
226 let header_hash = self.calculate_hash();
227 layer2::block_hasher()
228 .chain(&self.parent_id)
229 .chain(&header_hash)
230 .finalize()
231 .into()
232 }
233
234 pub fn signature(&self) -> &ValidatorBlockSignature {
235 &self.signature
236 }
237
238 pub fn accumulated_data(&self) -> &ShardGroupAccumulatedData {
239 &self.accumulated_data
240 }
241}
242
243#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Deserialize, Serialize, BorshSerialize, BorshDeserialize)]
244pub struct ShardGroupAccumulatedData {
245 pub total_exhaust_burn: u128,
246}
247
248#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize, BorshSerialize, BorshDeserialize)]
249pub struct QuorumCertificate {
250 #[serde(with = "hex_or_bytes")]
251 pub header_hash: FixedHash,
252 #[serde(with = "hex_or_bytes")]
253 pub parent_id: FixedHash,
254 pub signatures: Vec<ValidatorQcSignature>,
255 pub decision: QuorumDecision,
256}
257
258impl QuorumCertificate {
259 pub fn calculate_justified_block(&self) -> FixedHash {
260 layer2::block_hasher()
261 .chain(&self.parent_id)
262 .chain(&self.header_hash)
263 .finalize()
264 .into()
265 }
266}
267
268#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, BorshSerialize, BorshDeserialize)]
269pub enum QuorumDecision {
270 Accept,
271 Reject,
272}
273
274impl QuorumDecision {
275 pub fn is_accept(&self) -> bool {
276 matches!(self, QuorumDecision::Accept)
277 }
278
279 pub fn is_reject(&self) -> bool {
280 matches!(self, QuorumDecision::Reject)
281 }
282}
283
284impl QuorumDecision {
285 pub fn as_u8(&self) -> u8 {
286 match self {
287 QuorumDecision::Accept => 0,
288 QuorumDecision::Reject => 1,
289 }
290 }
291
292 pub fn from_u8(v: u8) -> Option<Self> {
293 match v {
294 0 => Some(QuorumDecision::Accept),
295 1 => Some(QuorumDecision::Reject),
296 _ => None,
297 }
298 }
299}
300
301impl TryFrom<u8> for QuorumDecision {
302 type Error = InvalidQuorumDecisionByteError;
303
304 fn try_from(value: u8) -> Result<Self, Self::Error> {
305 Self::from_u8(value).ok_or(InvalidQuorumDecisionByteError(value))
306 }
307}
308
309impl Display for QuorumDecision {
310 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
311 match self {
312 QuorumDecision::Accept => write!(f, "Accept"),
313 QuorumDecision::Reject => write!(f, "Reject"),
314 }
315 }
316}
317
318#[derive(Debug, thiserror::Error)]
319#[error("Invalid quorum decision byte: {0}")]
320pub struct InvalidQuorumDecisionByteError(u8);
321
322#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize, BorshSerialize, BorshDeserialize)]
323pub struct ValidatorQcSignature {
324 pub public_key: CompressedPublicKey,
325 pub signature: ValidatorBlockSignature,
326}
327
328impl ValidatorQcSignature {
329 #[must_use]
330 pub fn verify(&self, block_id: &FixedHash, decision: QuorumDecision) -> bool {
331 let Ok(public_key) = self.public_key.to_public_key() else {
332 return false;
333 };
334
335 let Ok(signature) = self.signature.to_schnorr_signature() else {
336 return false;
337 };
338
339 let fields = ProposalCertificateSignatureFields { block_id, decision };
340
341 let message = layer2::proposal_vote_signature_hasher().chain(&fields).finalize();
342 signature.verify(&public_key, message)
343 }
344
345 pub fn public_key(&self) -> &CompressedPublicKey {
346 &self.public_key
347 }
348
349 pub fn signature(&self) -> &ValidatorBlockSignature {
350 &self.signature
351 }
352}
353
354#[derive(Debug, BorshSerialize)]
355pub struct ProposalCertificateSignatureFields<'a> {
356 pub block_id: &'a FixedHash,
357 pub decision: QuorumDecision,
358}
359
360#[derive(Debug, BorshSerialize)]
361pub enum BlockHeaderHashFields<'a> {
362 V1(BlockHeaderHashFieldsV1<'a>),
363}
364
365#[derive(Debug, BorshSerialize)]
366pub struct BlockHeaderHashFieldsV1<'a> {
367 pub network: u8,
368 pub justify_id: &'a FixedHash,
369 pub height: u64,
370 pub epoch: u64,
371 pub shard_group: ShardGroup,
372 pub accumulated_data: &'a ShardGroupAccumulatedData,
373 pub proposed_by: &'a [u8],
375 pub state_merkle_root: &'a FixedHash,
376 pub command_merkle_root: &'a FixedHash,
377 pub metadata_hash: &'a FixedHash,
378}