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