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 `|` arm.
156        match self {
157            Fault::DoubleRatificationVote(a, b)
158            | Fault::DoubleValidationVote(a, b) => {
159                (&a.sig.signer, &b.sig.signer)
160            }
161            Fault::DoubleCandidate(a, b) => (&a.sig.signer, &b.sig.signer),
162        }
163    }
164
165    /// Check if both faults are related to the same consensus header and
166    /// validate their signatures.
167    ///
168    /// Return the related ConsensusHeader
169    pub fn validate(
170        &self,
171        current_height: u64,
172    ) -> Result<&ConsensusHeader, InvalidFault> {
173        let (h1, h2) = self.consensus_header();
174        // Check that both consensus headers are the same
175        if h1.iteration != h2.iteration {
176            return Err(InvalidFault::IterationMismatch);
177        }
178        if h1.round != h2.round {
179            return Err(InvalidFault::RoundMismatch);
180        }
181        if h1.prev_block_hash != h2.prev_block_hash {
182            return Err(InvalidFault::PrevHashMismatch);
183        }
184
185        // Check that both fault_data are signed by the same signer.
186        let (s1, s2) = self.signers();
187        if s1 != s2 {
188            return Err(InvalidFault::SignerMismatch);
189        }
190
191        // Check that fault refers to different fault_data
192        let (id_a, id_b) = self.faults_id();
193        if id_a == id_b {
194            return Err(InvalidFault::Duplicated);
195        }
196
197        if h1.round == 0 {
198            return Err(InvalidFault::Genesis);
199        }
200
201        // Check that fault is not expired. A fault expires after an epoch
202        if h1.round < current_height.saturating_sub(EPOCH) {
203            return Err(InvalidFault::Expired);
204        }
205
206        // Check that fault is related to something that is already processed
207        if h1.round > current_height {
208            return Err(InvalidFault::Future);
209        }
210
211        // Verify signatures
212        self.verify_sigs()?;
213
214        Ok(h1)
215    }
216
217    /// Check if both faults are signed properly
218    fn verify_sigs(&self) -> Result<(), BlsSigError> {
219        match self {
220            Fault::DoubleCandidate(a, b) => {
221                let seed = Candidate::SIGN_SEED;
222                let msg = a.get_signed_data(seed);
223                Self::verify_signature(a.header.round, &a.sig, &msg)?;
224                let msg = b.get_signed_data(seed);
225                Self::verify_signature(b.header.round, &b.sig, &msg)?;
226                Ok(())
227            }
228            Fault::DoubleRatificationVote(a, b) => {
229                let seed = Ratification::SIGN_SEED;
230                let msg = a.get_signed_data(seed);
231                Self::verify_signature(a.header.round, &a.sig, &msg)?;
232                let msg = b.get_signed_data(seed);
233                Self::verify_signature(b.header.round, &b.sig, &msg)?;
234                Ok(())
235            }
236            Fault::DoubleValidationVote(a, b) => {
237                let seed = Validation::SIGN_SEED;
238                let msg = a.get_signed_data(seed);
239                Self::verify_signature(a.header.round, &a.sig, &msg)?;
240                let msg = b.get_signed_data(seed);
241                Self::verify_signature(b.header.round, &b.sig, &msg)?;
242                Ok(())
243            }
244        }
245    }
246
247    fn verify_signature(
248        block_height: u64,
249        sign_info: &SignInfo,
250        msg: &[u8],
251    ) -> Result<(), BlsSigError> {
252        let bls_version = bls_version_at(block_height);
253        let sig =
254            BlsMultisigSignature::from_bytes(sign_info.signature.inner())?;
255        let apk =
256            core_bls::aggregate(&[*sign_info.signer.inner()], bls_version)?;
257        core_bls::verify_multisig(&apk, &sig, msg, bls_version)
258    }
259}
260
261impl FaultData<Hash> {
262    fn get_signed_data(&self, seed: &[u8]) -> Vec<u8> {
263        let mut signable = self.header.signable();
264        signable.extend_from_slice(seed);
265        signable.extend_from_slice(&self.data);
266        signable
267    }
268}
269impl FaultData<Vote> {
270    fn get_signed_data(&self, seed: &[u8]) -> Vec<u8> {
271        let mut signable = self.header.signable();
272        signable.extend_from_slice(seed);
273        self.data
274            .write(&mut signable)
275            .expect("Writing to vec should succeed");
276        signable
277    }
278}
279
280#[derive(Debug, Clone)]
281#[cfg_attr(any(feature = "faker", test), derive(fake::Dummy, Eq, PartialEq))]
282#[allow(clippy::large_enum_variant)]
283pub struct FaultData<V> {
284    header: ConsensusHeader,
285    sig: SignInfo,
286    data: V,
287}
288
289impl<V: Serializable> Serializable for FaultData<V> {
290    fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
291        self.header.write(w)?;
292        self.sig.write(w)?;
293        self.data.write(w)?;
294        Ok(())
295    }
296
297    fn read<R: Read>(r: &mut R) -> io::Result<Self>
298    where
299        Self: Sized,
300    {
301        let header = ConsensusHeader::read(r)?;
302        let sig = SignInfo::read(r)?;
303        let data = V::read(r)?;
304
305        Ok(Self { header, sig, data })
306    }
307}
308
309impl Serializable for Fault {
310    fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
311        match self {
312            Fault::DoubleCandidate(a, b) => {
313                w.write_all(&[0u8])?;
314                a.write(w)?;
315                b.write(w)?;
316            }
317            Fault::DoubleRatificationVote(a, b) => {
318                w.write_all(&[1u8])?;
319                a.write(w)?;
320                b.write(w)?;
321            }
322            Fault::DoubleValidationVote(a, b) => {
323                w.write_all(&[2u8])?;
324                a.write(w)?;
325                b.write(w)?;
326            }
327        }
328
329        Ok(())
330    }
331
332    fn read<R: Read>(r: &mut R) -> io::Result<Self>
333    where
334        Self: Sized,
335    {
336        let fault = Self::read_u8(r)?;
337        let fault = match fault {
338            0 => {
339                Fault::DoubleCandidate(FaultData::read(r)?, FaultData::read(r)?)
340            }
341            1 => Fault::DoubleRatificationVote(
342                FaultData::read(r)?,
343                FaultData::read(r)?,
344            ),
345            2 => Fault::DoubleValidationVote(
346                FaultData::read(r)?,
347                FaultData::read(r)?,
348            ),
349            p => Err(io::Error::new(
350                io::ErrorKind::InvalidData,
351                format!("Invalid fault: {p}"),
352            ))?,
353        };
354        Ok(fault)
355    }
356}
357
358#[derive(Clone, Debug)]
359pub struct Slash {
360    pub provisioner: PublicKey,
361    pub r#type: SlashType,
362}
363
364#[derive(Clone, Debug)]
365pub enum SlashType {
366    Soft,
367    Hard,
368    HardWithSeverity(u8),
369}
370
371impl Slash {
372    fn from_iteration_info(
373        value: &IterationInfo,
374    ) -> Result<Option<Self>, dusk_bytes::Error> {
375        let (attestation, provisioner) = value;
376        let slash = match attestation.result {
377            RatificationResult::Fail(Vote::NoCandidate) => SlashType::Soft,
378            RatificationResult::Fail(Vote::Invalid(_)) => SlashType::Hard,
379            _ => {
380                return Ok(None);
381            }
382        };
383        let provisioner =
384            (*provisioner.inner()).try_into().inspect_err(|e| {
385                error!(
386                    "Unable to generate provisioners from IterationInfo: {e:?}"
387                );
388            })?;
389        Ok(Some(Self {
390            provisioner,
391            r#type: slash,
392        }))
393    }
394
395    pub fn from_block(block: &Block) -> Result<Vec<Slash>, io::Error> {
396        Self::from_iterations_and_faults(
397            &block.header().failed_iterations,
398            block.faults(),
399        )
400    }
401
402    pub fn from_iterations_and_faults(
403        failed_iterations: &IterationsInfo,
404        faults: &[Fault],
405    ) -> Result<Vec<Slash>, io::Error> {
406        let mut slashing = failed_iterations
407            .att_list
408            .iter()
409            .flatten()
410            .flat_map(Slash::from_iteration_info)
411            .flatten()
412            .collect::<Vec<_>>();
413        slashing.extend(faults.iter().map(Slash::from));
414        Ok(slashing)
415    }
416}
417
418impl From<&Fault> for Slash {
419    fn from(value: &Fault) -> Self {
420        let slash_type = match value {
421            Fault::DoubleCandidate(_, _)
422            | Fault::DoubleRatificationVote(_, _)
423            | Fault::DoubleValidationVote(_, _) => {
424                SlashType::HardWithSeverity(2u8)
425            }
426        };
427        let provisioner = value.to_culprit();
428        Self {
429            provisioner,
430            r#type: slash_type,
431        }
432    }
433}
434
435#[cfg(test)]
436mod tests {
437    use super::*;
438
439    fn header() -> ConsensusHeader {
440        ConsensusHeader {
441            prev_block_hash: [7; 32],
442            round: 10,
443            iteration: 1,
444        }
445    }
446
447    fn fault_data(signer_seed: u64, vote: Vote) -> FaultData<Vote> {
448        FaultData {
449            header: header(),
450            sig: SignInfo {
451                signer: PublicKey::from_sk_seed_u64(signer_seed),
452                signature: Signature::default(),
453            },
454            data: vote,
455        }
456    }
457
458    #[test]
459    fn validate_rejects_mismatched_signers() {
460        let a = fault_data(1, Vote::Valid([1; 32]));
461        let b = fault_data(2, Vote::Valid([2; 32]));
462        let fault = Fault::DoubleValidationVote(a, b);
463
464        let err = fault
465            .validate(10)
466            .expect_err("fault with mismatched signers must be rejected");
467        assert!(matches!(err, InvalidFault::SignerMismatch));
468    }
469
470    #[test]
471    fn validate_does_not_report_signer_mismatch_for_same_signer() {
472        let a = fault_data(1, Vote::Valid([1; 32]));
473        let b = fault_data(1, Vote::Valid([2; 32]));
474        let fault = Fault::DoubleValidationVote(a, b);
475
476        let err = fault.validate(10).expect_err(
477            "fault with same signer still has invalid test signatures",
478        );
479        assert!(matches!(err, InvalidFault::InvalidSignature(_)));
480    }
481}