dusk_node/chain/
header_validation.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 std::cmp;
8use std::collections::BTreeMap;
9use std::sync::Arc;
10
11use dusk_bytes::Serializable;
12use dusk_consensus::config::{
13    is_emergency_block, is_emergency_iter, CONSENSUS_MAX_ITER,
14    MINIMUM_BLOCK_TIME, MIN_EMERGENCY_BLOCK_TIME, RELAX_ITERATION_THRESHOLD,
15};
16use dusk_consensus::errors::{
17    AttestationError, FailedIterationError, HeaderError,
18};
19use dusk_consensus::operations::Voter;
20use dusk_consensus::quorum::verifiers;
21use dusk_consensus::quorum::verifiers::QuorumResult;
22use dusk_consensus::user::committee::CommitteeSet;
23use dusk_consensus::user::provisioners::{ContextProvisioners, Provisioners};
24use dusk_core::signatures::bls::{
25    MultisigPublicKey, MultisigSignature, PublicKey as BlsPublicKey,
26};
27use dusk_core::stake::EPOCH;
28use hex;
29use node_data::bls::PublicKeyBytes;
30use node_data::ledger::{Fault, InvalidFault, Seed, Signature};
31use node_data::message::payload::{RatificationResult, Vote};
32use node_data::message::{ConsensusHeader, BLOCK_HEADER_VERSION};
33use node_data::{get_current_timestamp, ledger, StepName};
34use thiserror::Error;
35use tokio::sync::RwLock;
36use tracing::{debug, error};
37
38use crate::database;
39use crate::database::Ledger;
40
41const MARGIN_TIMESTAMP: u64 = 3;
42
43// TODO: Use thiserror instead of anyhow
44
45#[derive(Debug, Error)]
46enum HeaderVerificationErr {}
47
48/// An implementation of the all validation checks of a candidate block header
49/// according to current context
50pub(crate) struct Validator<'a, DB: database::DB> {
51    pub(crate) db: Arc<RwLock<DB>>,
52    prev_header: &'a ledger::Header,
53    provisioners: &'a ContextProvisioners,
54}
55
56impl<'a, DB: database::DB> Validator<'a, DB> {
57    pub fn new(
58        db: Arc<RwLock<DB>>,
59        prev_header: &'a ledger::Header,
60        provisioners: &'a ContextProvisioners,
61    ) -> Self {
62        Self {
63            db,
64            prev_header,
65            provisioners,
66        }
67    }
68
69    /// Executes check points to make sure a candidate header is fully valid
70    ///
71    /// * `disable_winner_att_check` - disables the check of the winning
72    /// attestation
73    ///
74    /// Returns a tuple containing:
75    ///   - the number of Previous Non-Attested Iterations (PNI)
76    ///   - previous block voters
77    ///   - current block voters (if not `disable_winner_att_check`)
78    pub async fn execute_checks(
79        &self,
80        header: &ledger::Header,
81        expected_generator: &PublicKeyBytes,
82        check_attestation: bool,
83    ) -> Result<(u8, Vec<Voter>, Vec<Voter>), HeaderError> {
84        let generator =
85            self.verify_block_generator(header, expected_generator)?;
86        self.verify_basic_fields(header, &generator).await?;
87
88        let prev_block_voters = self.verify_prev_block_cert(header).await?;
89
90        let mut block_voters = vec![];
91        if check_attestation {
92            (_, _, block_voters) = verify_att(
93                &header.att,
94                header.to_consensus_header(),
95                self.prev_header.seed,
96                self.provisioners.current(),
97                Some(RatificationResult::Success(Vote::Valid(header.hash))),
98            )
99            .await?;
100        }
101
102        let pni = self.verify_failed_iterations(header).await?;
103        Ok((pni, prev_block_voters, block_voters))
104    }
105
106    fn verify_block_generator(
107        &self,
108        header: &'a ledger::Header,
109        expected_generator: &PublicKeyBytes,
110    ) -> Result<MultisigPublicKey, HeaderError> {
111        if expected_generator != &header.generator_bls_pubkey {
112            return Err(HeaderError::InvalidBlockSignature(
113                "Signed by a different generator:".into(),
114            ));
115        }
116
117        // Get generator MultisigPublicKey
118        let generator = header.generator_bls_pubkey.inner();
119        let generator = BlsPublicKey::from_bytes(generator).map_err(|err| {
120            HeaderError::InvalidBlockSignature(format!(
121                "invalid pk bytes: {err:?}"
122            ))
123        })?;
124        let generator =
125            MultisigPublicKey::aggregate(&[generator]).map_err(|err| {
126                HeaderError::InvalidBlockSignature(format!(
127                    "failed aggregating single key: {err:?}"
128                ))
129            })?;
130
131        // Verify block signature
132        let block_sig = MultisigSignature::from_bytes(header.signature.inner())
133            .map_err(|err| {
134                HeaderError::InvalidBlockSignature(format!(
135                    "invalid block signature bytes: {err:?}"
136                ))
137            })?;
138        generator.verify(&block_sig, &header.hash).map_err(|err| {
139            HeaderError::InvalidBlockSignature(format!(
140                "invalid block signature: {err:?}"
141            ))
142        })?;
143
144        Ok(generator)
145    }
146
147    /// Verifies any non-attestation field
148    async fn verify_basic_fields(
149        &self,
150        candidate_block: &'a ledger::Header,
151        generator: &MultisigPublicKey,
152    ) -> Result<(), HeaderError> {
153        if candidate_block.version != BLOCK_HEADER_VERSION {
154            return Err(HeaderError::UnsupportedVersion);
155        }
156
157        if candidate_block.hash == [0u8; 32] {
158            return Err(HeaderError::EmptyHash);
159        }
160
161        if candidate_block.height != self.prev_header.height + 1 {
162            return Err(HeaderError::MismatchHeight(
163                candidate_block.height,
164                self.prev_header.height,
165            ));
166        }
167
168        // Ensure rule of minimum block time is addressed
169        if candidate_block.timestamp
170            < self.prev_header.timestamp + *MINIMUM_BLOCK_TIME
171        {
172            return Err(HeaderError::BlockTimeLess);
173        }
174
175        // The Emergency Block can only be produced after all iterations in a
176        // round have failed. To ensure Dusk (or anyone in possess of the Dusk
177        // private key) is not able to shortcircuit a round with an arbitrary
178        // block, nodes should only accept an Emergency Block if its timestamp
179        // is higher than the maximum time needed to run all round iterations.
180        // This guarantees the network has enough time to actually produce a
181        // block, if possible.
182        if is_emergency_block(candidate_block.iteration)
183            && candidate_block.timestamp
184                < self.prev_header.timestamp
185                    + MIN_EMERGENCY_BLOCK_TIME.as_secs()
186        {
187            return Err(HeaderError::BlockTimeLess);
188        }
189
190        let local_time = get_current_timestamp();
191
192        if candidate_block.timestamp > local_time + MARGIN_TIMESTAMP {
193            return Err(HeaderError::BlockTimeHigher(
194                candidate_block.timestamp,
195            ));
196        }
197
198        if candidate_block.prev_block_hash != self.prev_header.hash {
199            return Err(HeaderError::PrevBlockHash);
200        }
201
202        // Ensure block is not already in the ledger
203        let block_exists = self
204            .db
205            .read()
206            .await
207            .view(|db| db.block_exists(&candidate_block.hash))
208            .map_err(|e| {
209                HeaderError::Storage(
210                    "error checking Ledger::get_block_exists",
211                    e,
212                )
213            })?;
214
215        if block_exists {
216            return Err(HeaderError::BlockExists);
217        }
218
219        // Verify seed field
220        self.verify_seed_field(candidate_block.seed.inner(), generator)?;
221
222        Ok(())
223    }
224
225    fn verify_seed_field(
226        &self,
227        seed: &[u8; 48],
228        pk: &MultisigPublicKey,
229    ) -> Result<(), HeaderError> {
230        let signature = MultisigSignature::from_bytes(seed).map_err(|err| {
231            HeaderError::InvalidSeed(format!(
232                "invalid seed signature bytes: {err:?}"
233            ))
234        })?;
235
236        pk.verify(&signature, self.prev_header.seed.inner())
237            .map_err(|err| {
238                HeaderError::InvalidSeed(format!("invalid seed: {err:?}"))
239            })?;
240
241        Ok(())
242    }
243
244    async fn verify_prev_block_cert(
245        &self,
246        candidate_block: &'a ledger::Header,
247    ) -> Result<Vec<Voter>, HeaderError> {
248        if self.prev_header.height == 0
249            || is_emergency_block(self.prev_header.iteration)
250        {
251            return Ok(vec![]);
252        }
253
254        let prev_block_hash = candidate_block.prev_block_hash;
255
256        let prev_block_seed = self
257            .db
258            .read()
259            .await
260            .view(|v| v.block_header(&self.prev_header.prev_block_hash))
261            .map_err(|e| {
262                HeaderError::Storage(
263                    "error checking Ledger::fetch_block_header",
264                    e,
265                )
266            })?
267            .ok_or(HeaderError::Generic("Header not found"))
268            .map(|h| h.seed)?;
269
270        let (_, _, voters) = verify_att(
271            &candidate_block.prev_block_cert,
272            self.prev_header.to_consensus_header(),
273            prev_block_seed,
274            self.provisioners.prev(),
275            Some(RatificationResult::Success(Vote::Valid(prev_block_hash))),
276        )
277        .await?;
278
279        Ok(voters)
280    }
281
282    /// Verify the Failed Iterations field in a block.
283    ///
284    /// Return the number of attested failed iterations. We refer to this number
285    /// as Previous Non-Attested Iterations, or PNI
286    async fn verify_failed_iterations(
287        &self,
288        candidate_block: &'a ledger::Header,
289    ) -> Result<u8, FailedIterationError> {
290        let mut failed_atts = 0u8;
291
292        let att_list = &candidate_block.failed_iterations.att_list;
293
294        if att_list.len() > RELAX_ITERATION_THRESHOLD as usize {
295            return Err(FailedIterationError::TooMany(att_list.len()));
296        }
297
298        for (iter, att) in att_list.iter().enumerate() {
299            if let Some((att, pk)) = att {
300                debug!(event = "verify fail attestation", iter);
301
302                let expected_pk = self.provisioners.current().get_generator(
303                    iter as u8,
304                    self.prev_header.seed,
305                    candidate_block.height,
306                );
307
308                if pk != &expected_pk {
309                    return Err(FailedIterationError::InvalidGenerator(
310                        expected_pk,
311                    ));
312                }
313
314                let mut consensus_header =
315                    candidate_block.to_consensus_header();
316                consensus_header.iteration = iter as u8;
317
318                verify_att(
319                    att,
320                    consensus_header,
321                    self.prev_header.seed,
322                    self.provisioners.current(),
323                    Some(RatificationResult::Fail(Vote::default())),
324                )
325                .await?;
326
327                failed_atts += 1;
328            }
329        }
330
331        // In case of Emergency Block, which iteration number is u8::MAX, we
332        // count failed iterations up to CONSENSUS_MAX_ITER
333        let last_iter = cmp::min(candidate_block.iteration, CONSENSUS_MAX_ITER);
334
335        Ok(last_iter - failed_atts)
336    }
337
338    /// Extracts voters list of a block.
339    ///
340    /// Returns a list of voters with their credits for both ratification and
341    /// validation step
342    pub async fn get_voters(
343        blk: &'a ledger::Header,
344        provisioners: &Provisioners,
345        prev_block_seed: Seed,
346    ) -> Vec<Voter> {
347        let att = &blk.att;
348        let consensus_header = blk.to_consensus_header();
349
350        let committee = RwLock::new(CommitteeSet::new(provisioners));
351
352        let validation_voters = verifiers::get_step_voters(
353            &consensus_header,
354            &att.validation,
355            &committee,
356            prev_block_seed,
357            StepName::Validation,
358        )
359        .await;
360
361        let ratification_voters = verifiers::get_step_voters(
362            &consensus_header,
363            &att.ratification,
364            &committee,
365            prev_block_seed,
366            StepName::Ratification,
367        )
368        .await;
369
370        merge_voters(validation_voters, ratification_voters)
371    }
372
373    /// Verify faults inside a block.
374    pub async fn verify_faults(
375        &self,
376        current_height: u64,
377        faults: &[Fault],
378    ) -> Result<(), InvalidFault> {
379        verify_faults(self.db.clone(), current_height, faults).await
380    }
381}
382
383pub async fn verify_faults<DB: database::DB>(
384    db: Arc<RwLock<DB>>,
385    current_height: u64,
386    faults: &[Fault],
387) -> Result<(), InvalidFault> {
388    for f in faults {
389        let fault_header = f.validate(current_height)?;
390        if is_emergency_iter(fault_header.iteration) {
391            return Err(InvalidFault::EmergencyIteration);
392        }
393        db.read()
394            .await
395            .view(|db| {
396                let prev_header = db
397                    .block_header(&fault_header.prev_block_hash)?
398                    .ok_or(anyhow::anyhow!("Slashing a non accepted header"))?;
399                // No overflow here, since the header has been already validated
400                // not to be 0
401                if prev_header.height != fault_header.round - 1 {
402                    anyhow::bail!("Invalid height for fault");
403                }
404
405                // FIX_ME: Instead of fetching all store faults, check the fault
406                // id directly This needs the fault id to be
407                // changed into "HEIGHT|TYPE|PROV_KEY"
408                let start_height = fault_header.round.saturating_sub(EPOCH);
409                let stored_faults = db.faults_by_block(start_height)?;
410                if stored_faults.iter().any(|other| f.same(other)) {
411                    anyhow::bail!("Double fault detected");
412                }
413
414                Ok(())
415            })
416            .map_err(|e| InvalidFault::Other(format!("{e:?}")))?;
417    }
418    Ok(())
419}
420
421pub async fn verify_att(
422    att: &ledger::Attestation,
423    consensus_header: ConsensusHeader,
424    curr_seed: Signature,
425    curr_eligible_provisioners: &Provisioners,
426    expected_result: Option<RatificationResult>,
427) -> Result<(QuorumResult, QuorumResult, Vec<Voter>), AttestationError> {
428    // Check expected result
429    if let Some(expected) = expected_result {
430        match (att.result, expected) {
431            // Both are Success and the inner Valid(Hash) values match
432            (
433                RatificationResult::Success(Vote::Valid(r_hash)),
434                RatificationResult::Success(Vote::Valid(e_hash)),
435            ) => {
436                if r_hash != e_hash {
437                    error!("Invalid Attestation. Expected: Valid({:?}), got: Valid({:?})", hex::encode(e_hash), hex::encode(r_hash));
438                    return Err(AttestationError::InvalidHash(e_hash, r_hash));
439                }
440            }
441            // Both are Fail
442            (RatificationResult::Fail(_), RatificationResult::Fail(_)) => {}
443            // All other mismatches
444            _ => {
445                error!(
446                    "Invalid Attestation. Expected: {:?}, got: {:?}",
447                    expected, att.result
448                );
449                return Err(AttestationError::InvalidResult(
450                    att.result, expected,
451                ));
452            }
453        }
454    }
455
456    let committee = RwLock::new(CommitteeSet::new(curr_eligible_provisioners));
457    let vote = att.result.vote();
458
459    // Verify validation
460    let (val_result, validation_voters) = verifiers::verify_step_votes(
461        &consensus_header,
462        vote,
463        &att.validation,
464        &committee,
465        curr_seed,
466        StepName::Validation,
467    )
468    .await
469    .map_err(|s| AttestationError::InvalidVotes(StepName::Validation, s))?;
470
471    // Verify ratification
472    let (rat_result, ratification_voters) = verifiers::verify_step_votes(
473        &consensus_header,
474        vote,
475        &att.ratification,
476        &committee,
477        curr_seed,
478        StepName::Ratification,
479    )
480    .await
481    .map_err(|s| AttestationError::InvalidVotes(StepName::Ratification, s))?;
482
483    let voters = merge_voters(validation_voters, ratification_voters);
484    Ok((val_result, rat_result, voters))
485}
486
487/// Merges two Vec<Voter>, summing up the usize values if the PublicKey is
488/// repeated
489fn merge_voters(v1: Vec<Voter>, v2: Vec<Voter>) -> Vec<Voter> {
490    let mut voter_map = BTreeMap::new();
491
492    for (pk, count) in v1.into_iter().chain(v2.into_iter()) {
493        let counter = voter_map.entry(pk).or_default();
494        *counter += count;
495    }
496
497    voter_map.into_iter().collect()
498}