Skip to main content

dusk_node_data/ledger/
faults.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at http://mozilla.org/MPL/2.0/.
4//
5// Copyright (c) DUSK NETWORK. All rights reserved.
6
7use dusk_bytes::Serializable as DuskSerializeble;
8use dusk_core::signatures::bls::{
9    self as core_bls, Error as BlsSigError,
10    MultisigPublicKey as BlsMultisigPublicKey,
11    MultisigSignature as BlsMultisigSignature,
12};
13use dusk_core::stake::EPOCH;
14use thiserror::Error;
15use tracing::error;
16
17use super::*;
18use crate::bls::PublicKey;
19use crate::hard_fork::bls_version_at;
20use crate::message::payload::{
21    Candidate, Ratification, RatificationResult, Validation, Vote,
22};
23use crate::message::{ConsensusHeader, SignInfo, SignedStepMessage};
24
25#[derive(Debug, Clone)]
26#[cfg_attr(any(feature = "faker", test), derive(fake::Dummy, Eq, PartialEq))]
27pub enum Fault {
28    DoubleCandidate(FaultData<Hash>, FaultData<Hash>),
29    DoubleRatificationVote(FaultData<Vote>, FaultData<Vote>),
30    DoubleValidationVote(FaultData<Vote>, FaultData<Vote>),
31}
32
33impl Fault {
34    pub fn size(&self) -> usize {
35        // prev_block_hash + round + iter
36        const FAULT_CONSENSUS_HEADER_SIZE: usize = 32 + u64::SIZE + u8::SIZE;
37        // signer + signature
38        const FAULT_SIG_INFO_SIZE: usize =
39            BlsMultisigPublicKey::SIZE + BlsMultisigSignature::SIZE;
40
41        const HEADERS: usize = FAULT_CONSENSUS_HEADER_SIZE * 2;
42        const SIG_INFOS: usize = FAULT_SIG_INFO_SIZE * 2;
43        let faults_data_size = match self {
44            Fault::DoubleCandidate(..) => 32 * 2,
45            Fault::DoubleRatificationVote(a, b) => {
46                a.data.size() + b.data.size()
47            }
48            Fault::DoubleValidationVote(a, b) => a.data.size() + b.data.size(),
49        };
50
51        HEADERS + SIG_INFOS + faults_data_size
52    }
53}
54
55#[derive(Debug, Error)]
56pub enum InvalidFault {
57    #[error("Inner faults have same data")]
58    Duplicated,
59    #[error("Fault is expired")]
60    Expired,
61    #[error("Fault is from future")]
62    Future,
63    #[error("Fault is from genesis block")]
64    Genesis,
65    #[error("Previous hash mismatch")]
66    PrevHashMismatch,
67    #[error("Iteration mismatch")]
68    IterationMismatch,
69    #[error("Faults related to emergency iteration")]
70    EmergencyIteration,
71    #[error("Round mismatch")]
72    RoundMismatch,
73    #[error("Inner faults signer mismatch")]
74    SignerMismatch,
75    #[error("Invalid Signature {0}")]
76    InvalidSignature(BlsSigError),
77    #[error("Generic error {0}")]
78    Other(String),
79}
80
81impl From<BlsSigError> for InvalidFault {
82    fn from(value: BlsSigError) -> Self {
83        Self::InvalidSignature(value)
84    }
85}
86
87impl Fault {
88    // TODO: change to HEIGHT|TYPE|PROV_KEY once faults collection is
89    // implemented
90    pub fn id(&self) -> [u8; 32] {
91        self.digest()
92    }
93
94    /// Digest the serialized form
95    pub fn digest(&self) -> [u8; 32] {
96        let mut b = vec![];
97        self.write(&mut b).expect("Write to a vec shall not fail");
98        sha3::Sha3_256::digest(&b[..]).into()
99    }
100
101    pub fn same(&self, other: &Fault) -> bool {
102        let (a, b) = &self.faults_id();
103        other.has_id(a) && other.has_id(b)
104    }
105
106    fn has_id(&self, id: &[u8; 32]) -> bool {
107        let (a, b) = &self.faults_id();
108        a == id || b == id
109    }
110
111    /// Return the IDs of the inner faults.
112    fn faults_id(&self) -> (Hash, Hash) {
113        match self {
114            Fault::DoubleCandidate(a, b) => {
115                let seed = Candidate::SIGN_SEED;
116                let a = sha3::Sha3_256::digest(a.get_signed_data(seed)).into();
117                let b = sha3::Sha3_256::digest(b.get_signed_data(seed)).into();
118                (a, b)
119            }
120            Fault::DoubleRatificationVote(a, b) => {
121                let seed = Ratification::SIGN_SEED;
122                let a = sha3::Sha3_256::digest(a.get_signed_data(seed)).into();
123                let b = sha3::Sha3_256::digest(b.get_signed_data(seed)).into();
124                (a, b)
125            }
126            Fault::DoubleValidationVote(a, b) => {
127                let seed = Validation::SIGN_SEED;
128                let a = sha3::Sha3_256::digest(a.get_signed_data(seed)).into();
129                let b = sha3::Sha3_256::digest(b.get_signed_data(seed)).into();
130                (a, b)
131            }
132        }
133    }
134
135    fn to_culprit(&self) -> PublicKey {
136        match self {
137            Fault::DoubleRatificationVote(a, _)
138            | Fault::DoubleValidationVote(a, _) => a.sig.signer.clone(),
139            Fault::DoubleCandidate(a, _) => a.sig.signer.clone(),
140        }
141    }
142
143    /// Get the ConsensusHeader related to the inner FaultDatas
144    fn consensus_header(&self) -> (&ConsensusHeader, &ConsensusHeader) {
145        match self {
146            Fault::DoubleRatificationVote(a, b)
147            | Fault::DoubleValidationVote(a, b) => (&a.header, &b.header),
148            Fault::DoubleCandidate(a, b) => (&a.header, &b.header),
149        }
150    }
151
152    /// Get the signers related to the inner FaultDatas.
153    fn signers(&self) -> (&PublicKey, &PublicKey) {
154        // `DoubleCandidate` carries `FaultData<Hash>` while the other variants
155        // carry `FaultData<Vote>`, so it cannot be merged into the same `|`
156        // arm.
157        match self {
158            Fault::DoubleRatificationVote(a, b)
159            | Fault::DoubleValidationVote(a, b) => {
160                (&a.sig.signer, &b.sig.signer)
161            }
162            Fault::DoubleCandidate(a, b) => (&a.sig.signer, &b.sig.signer),
163        }
164    }
165
166    /// Check if both faults are related to the same consensus header and
167    /// validate their signatures.
168    ///
169    /// Return the related ConsensusHeader
170    pub fn validate(
171        &self,
172        current_height: u64,
173    ) -> Result<&ConsensusHeader, InvalidFault> {
174        let (h1, h2) = self.consensus_header();
175        // Check that both consensus headers are the same
176        if h1.iteration != h2.iteration {
177            return Err(InvalidFault::IterationMismatch);
178        }
179        if h1.round != h2.round {
180            return Err(InvalidFault::RoundMismatch);
181        }
182        if h1.prev_block_hash != h2.prev_block_hash {
183            return Err(InvalidFault::PrevHashMismatch);
184        }
185
186        // Check that both fault_data are signed by the same signer.
187        let (s1, s2) = self.signers();
188        if s1 != s2 {
189            return Err(InvalidFault::SignerMismatch);
190        }
191
192        // Check that fault refers to different fault_data
193        let (id_a, id_b) = self.faults_id();
194        if id_a == id_b {
195            return Err(InvalidFault::Duplicated);
196        }
197
198        if h1.round == 0 {
199            return Err(InvalidFault::Genesis);
200        }
201
202        // Check that fault is not expired. A fault expires after an epoch
203        if h1.round < current_height.saturating_sub(EPOCH) {
204            return Err(InvalidFault::Expired);
205        }
206
207        // Check that fault is related to something that is already processed
208        if h1.round > current_height {
209            return Err(InvalidFault::Future);
210        }
211
212        // Verify signatures
213        self.verify_sigs()?;
214
215        Ok(h1)
216    }
217
218    /// Check if both faults are signed properly
219    fn verify_sigs(&self) -> Result<(), BlsSigError> {
220        match self {
221            Fault::DoubleCandidate(a, b) => {
222                let seed = Candidate::SIGN_SEED;
223                let msg = a.get_signed_data(seed);
224                Self::verify_signature(a.header.round, &a.sig, &msg)?;
225                let msg = b.get_signed_data(seed);
226                Self::verify_signature(b.header.round, &b.sig, &msg)?;
227                Ok(())
228            }
229            Fault::DoubleRatificationVote(a, b) => {
230                let seed = Ratification::SIGN_SEED;
231                let msg = a.get_signed_data(seed);
232                Self::verify_signature(a.header.round, &a.sig, &msg)?;
233                let msg = b.get_signed_data(seed);
234                Self::verify_signature(b.header.round, &b.sig, &msg)?;
235                Ok(())
236            }
237            Fault::DoubleValidationVote(a, b) => {
238                let seed = Validation::SIGN_SEED;
239                let msg = a.get_signed_data(seed);
240                Self::verify_signature(a.header.round, &a.sig, &msg)?;
241                let msg = b.get_signed_data(seed);
242                Self::verify_signature(b.header.round, &b.sig, &msg)?;
243                Ok(())
244            }
245        }
246    }
247
248    fn verify_signature(
249        block_height: u64,
250        sign_info: &SignInfo,
251        msg: &[u8],
252    ) -> Result<(), BlsSigError> {
253        let bls_version = bls_version_at(block_height);
254        let sig =
255            BlsMultisigSignature::from_bytes(sign_info.signature.inner())?;
256        let apk =
257            core_bls::aggregate(&[*sign_info.signer.inner()], bls_version)?;
258        core_bls::verify_multisig(&apk, &sig, msg, bls_version)
259    }
260}
261
262impl FaultData<Hash> {
263    fn get_signed_data(&self, seed: &[u8]) -> Vec<u8> {
264        let mut signable = self.header.signable();
265        signable.extend_from_slice(seed);
266        signable.extend_from_slice(&self.data);
267        signable
268    }
269}
270impl FaultData<Vote> {
271    fn get_signed_data(&self, seed: &[u8]) -> Vec<u8> {
272        let mut signable = self.header.signable();
273        signable.extend_from_slice(seed);
274        self.data
275            .write(&mut signable)
276            .expect("Writing to vec should succeed");
277        signable
278    }
279}
280
281#[derive(Debug, Clone)]
282#[cfg_attr(any(feature = "faker", test), derive(fake::Dummy, Eq, PartialEq))]
283#[allow(clippy::large_enum_variant)]
284pub struct FaultData<V> {
285    header: ConsensusHeader,
286    sig: SignInfo,
287    data: V,
288}
289
290impl<V: Serializable> Serializable for FaultData<V> {
291    fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
292        self.header.write(w)?;
293        self.sig.write(w)?;
294        self.data.write(w)?;
295        Ok(())
296    }
297
298    fn read<R: Read>(r: &mut R) -> io::Result<Self>
299    where
300        Self: Sized,
301    {
302        let header = ConsensusHeader::read(r)?;
303        let sig = SignInfo::read(r)?;
304        let data = V::read(r)?;
305
306        Ok(Self { header, sig, data })
307    }
308}
309
310impl Serializable for Fault {
311    fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
312        match self {
313            Fault::DoubleCandidate(a, b) => {
314                w.write_all(&[0u8])?;
315                a.write(w)?;
316                b.write(w)?;
317            }
318            Fault::DoubleRatificationVote(a, b) => {
319                w.write_all(&[1u8])?;
320                a.write(w)?;
321                b.write(w)?;
322            }
323            Fault::DoubleValidationVote(a, b) => {
324                w.write_all(&[2u8])?;
325                a.write(w)?;
326                b.write(w)?;
327            }
328        }
329
330        Ok(())
331    }
332
333    fn read<R: Read>(r: &mut R) -> io::Result<Self>
334    where
335        Self: Sized,
336    {
337        let fault = Self::read_u8(r)?;
338        let fault = match fault {
339            0 => {
340                Fault::DoubleCandidate(FaultData::read(r)?, FaultData::read(r)?)
341            }
342            1 => Fault::DoubleRatificationVote(
343                FaultData::read(r)?,
344                FaultData::read(r)?,
345            ),
346            2 => Fault::DoubleValidationVote(
347                FaultData::read(r)?,
348                FaultData::read(r)?,
349            ),
350            p => Err(io::Error::new(
351                io::ErrorKind::InvalidData,
352                format!("Invalid fault: {p}"),
353            ))?,
354        };
355        Ok(fault)
356    }
357}
358
359#[derive(Clone, Debug)]
360pub struct Slash {
361    pub provisioner: PublicKey,
362    pub r#type: SlashType,
363}
364
365#[derive(Clone, Debug)]
366pub enum SlashType {
367    Soft,
368    Hard,
369    HardWithSeverity(u8),
370}
371
372impl Slash {
373    fn from_iteration_info(
374        value: &IterationInfo,
375    ) -> Result<Option<Self>, dusk_bytes::Error> {
376        let (attestation, provisioner) = value;
377        let slash = match attestation.result {
378            RatificationResult::Fail(Vote::NoCandidate) => SlashType::Soft,
379            RatificationResult::Fail(Vote::Invalid(_)) => SlashType::Hard,
380            _ => {
381                return Ok(None);
382            }
383        };
384        let provisioner =
385            (*provisioner.inner()).try_into().inspect_err(|e| {
386                error!(
387                    "Unable to generate provisioners from IterationInfo: {e:?}"
388                );
389            })?;
390        Ok(Some(Self {
391            provisioner,
392            r#type: slash,
393        }))
394    }
395
396    pub fn from_block(block: &Block) -> Result<Vec<Slash>, io::Error> {
397        Self::from_iterations_and_faults(
398            &block.header().failed_iterations,
399            block.faults(),
400        )
401    }
402
403    pub fn from_iterations_and_faults(
404        failed_iterations: &IterationsInfo,
405        faults: &[Fault],
406    ) -> Result<Vec<Slash>, io::Error> {
407        let mut slashing = failed_iterations
408            .att_list
409            .iter()
410            .flatten()
411            .flat_map(Slash::from_iteration_info)
412            .flatten()
413            .collect::<Vec<_>>();
414        slashing.extend(faults.iter().map(Slash::from));
415        Ok(slashing)
416    }
417}
418
419impl From<&Fault> for Slash {
420    fn from(value: &Fault) -> Self {
421        let slash_type = match value {
422            Fault::DoubleCandidate(_, _)
423            | Fault::DoubleRatificationVote(_, _)
424            | Fault::DoubleValidationVote(_, _) => {
425                SlashType::HardWithSeverity(2u8)
426            }
427        };
428        let provisioner = value.to_culprit();
429        Self {
430            provisioner,
431            r#type: slash_type,
432        }
433    }
434}
435
436#[cfg(test)]
437mod tests {
438    use super::*;
439
440    fn header() -> ConsensusHeader {
441        ConsensusHeader {
442            prev_block_hash: [7; 32],
443            round: 10,
444            iteration: 1,
445        }
446    }
447
448    fn fault_data(signer_seed: u64, vote: Vote) -> FaultData<Vote> {
449        FaultData {
450            header: header(),
451            sig: SignInfo {
452                signer: PublicKey::from_sk_seed_u64(signer_seed),
453                signature: Signature::default(),
454            },
455            data: vote,
456        }
457    }
458
459    #[test]
460    fn validate_rejects_mismatched_signers() {
461        let a = fault_data(1, Vote::Valid([1; 32]));
462        let b = fault_data(2, Vote::Valid([2; 32]));
463        let fault = Fault::DoubleValidationVote(a, b);
464
465        let err = fault
466            .validate(10)
467            .expect_err("fault with mismatched signers must be rejected");
468        assert!(matches!(err, InvalidFault::SignerMismatch));
469    }
470
471    #[test]
472    fn validate_does_not_report_signer_mismatch_for_same_signer() {
473        let a = fault_data(1, Vote::Valid([1; 32]));
474        let b = fault_data(1, Vote::Valid([2; 32]));
475        let fault = Fault::DoubleValidationVote(a, b);
476
477        let err = fault.validate(10).expect_err(
478            "fault with same signer still has invalid test signatures",
479        );
480        assert!(matches!(err, InvalidFault::InvalidSignature(_)));
481    }
482}