dusk-rusk 1.6.0

Rusk is the Dusk Network node implementation
Documentation
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Copyright (c) DUSK NETWORK. All rights reserved.

pub mod driverstore;
mod events;
mod rusk;
mod vm;

use std::collections::BTreeMap;
use std::path::PathBuf;
use std::sync::Arc;

use dusk_core::{Dusk, dusk};

use self::rusk::plonk_version_at;
use dusk_core::abi::ContractId;
use dusk_vm::VM;
use dusk_vm::host_queries;
use node::LongLivedService;
use node::database::rocksdb::{self, Backend};
use node::network::Kadcast;
use node_data::hard_fork::{HardFork, hard_fork_at};
use parking_lot::RwLock;
use tokio::sync::broadcast;
pub use vm::*;

use crate::http::{DriverExecutor, RuesEvent};
pub use driverstore::DriverStore;
pub(crate) use events::ChainEventStreamer;
#[cfg(feature = "archive")]
use node::archive::Archive;

#[derive(Debug, Clone, Copy)]
pub struct RuskTip {
    pub current: [u8; 32],
    pub base: [u8; 32],
}

#[derive(Clone)]
pub struct Rusk {
    pub tip: Arc<RwLock<RuskTip>>,
    pub vm: Arc<VM>,
    dir: PathBuf,
    pub(crate) chain_id: u8,
    pub(crate) vm_config: RuskVmConfig,
    pub(crate) min_gas_limit: u64,
    pub(crate) feeder_gas_limit: u64,
    pub(crate) event_sender: broadcast::Sender<RuesEvent>,
    #[cfg(feature = "archive")]
    pub archive: Archive,
    pub(crate) driver_store: Arc<RwLock<DriverStore>>,
    pub(crate) instance_cache:
        Arc<RwLock<BTreeMap<ContractId, DriverExecutor>>>,
}

pub(crate) type Services =
    dyn LongLivedService<Kadcast<255>, rocksdb::Backend, Rusk>;

#[derive(Clone)]
pub struct RuskNode {
    inner: node::Node<Kadcast<255>, Backend, Rusk>,
    #[cfg(feature = "archive")]
    archive: Archive,
}

pub(crate) fn set_vm_host_context(
    vm_config: &RuskVmConfig,
    block_height: u64,
) -> (host_queries::PlonkVersionGuard, host_queries::HardForkGuard) {
    let hard_fork = hard_fork_at(block_height);
    let plonk = host_queries::set_plonk_version(plonk_version_at(
        vm_config,
        block_height,
        hard_fork,
    ));
    let hard_fork = match hard_fork {
        HardFork::PreFork => host_queries::HardFork::PreFork,
        HardFork::Aegis => host_queries::HardFork::Aegis,
    };
    let hard_fork = host_queries::set_hard_fork(hard_fork);

    (plonk, hard_fork)
}

impl RuskNode {
    pub fn new(
        inner: node::Node<Kadcast<255>, Backend, Rusk>,
        #[cfg(feature = "archive")] archive: Archive,
    ) -> Self {
        Self {
            inner,
            #[cfg(feature = "archive")]
            archive,
        }
    }

    #[cfg(feature = "archive")]
    pub fn with_archive(mut self, archive: Archive) -> Self {
        self.archive = archive;
        self
    }
}

impl RuskNode {
    pub fn db(&self) -> Arc<tokio::sync::RwLock<Backend>> {
        self.inner.database() as Arc<tokio::sync::RwLock<Backend>>
    }

    #[cfg(feature = "archive")]
    pub fn archive(&self) -> Archive {
        self.archive.clone()
    }

    pub fn network(&self) -> Arc<tokio::sync::RwLock<Kadcast<255>>> {
        self.inner.network() as Arc<tokio::sync::RwLock<Kadcast<255>>>
    }

    pub fn inner(&self) -> &node::Node<Kadcast<255>, Backend, Rusk> {
        &self.inner
    }
}

/// Calculates reward amounts based on the emission and fees amount.
///
/// 10% of the reward value goes to the Dusk address (rounded down).
/// 70% of the reward value is considered fixed reward for Block Generator.
/// 10% of the reward value is considered extra reward for Block Generator.
/// 10% of the reward value goes to the all validators/voters of previous block
/// (rounded down).
const fn get_block_rewards(
    block_height: u64,
    dusk_spent: u64,
) -> (Dusk, Dusk, Dusk, Dusk) {
    let reward_value = emission_amount(block_height) + dusk_spent;
    let one_tenth_reward = reward_value / 10;

    let dusk_value = one_tenth_reward;
    let voters_value = one_tenth_reward;
    let generator_extra_value = one_tenth_reward;

    let generator_fixed_value =
        reward_value - dusk_value - voters_value - generator_extra_value;

    (
        dusk_value,
        generator_fixed_value,
        generator_extra_value,
        voters_value,
    )
}

/// The emission schedule works as follows:
///   - the emission follows a Bitcoin-like halving function
///   - a total 500.000.000 Dusk will be emitted over 36 years divided in 9
///     periods of 4 years each
///
/// Considering the target block rate of 10 seconds, we assume a production of
/// 8640 blocks per day, which corresponds to 12_614_400 blocks per period.
///
/// Returns the block emission for a certain height, following the halving
/// function
pub const fn emission_amount(block_height: u64) -> Dusk {
    match block_height {
        0 => 0,                                     // Genesis
        1..=12_614_400 => dusk(19.8574),            // Period 1
        12_614_401..=25_228_800 => dusk(9.9287),    // Period 2
        25_228_801..=37_843_200 => dusk(4.96435),   // Period 3
        37_843_201..=50_457_600 => dusk(2.48218),   // Period 4
        50_457_601..=63_072_000 => dusk(1.24109),   // Period 5
        63_072_001..=75_686_400 => dusk(0.62054),   // Period 6
        75_686_401..=88_300_800 => dusk(0.31027),   // Period 7
        88_300_801..=100_915_200 => dusk(0.15514),  // Period 8
        100_915_201..=113_529_596 => dusk(0.07757), // Period 9
        113_529_597 => dusk(0.05428),               // Last mint
        _ => 0,
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    // Target block production per day, assuming a block rate of 10 seconds
    const BLOCKS_PER_DAY: u64 = 8640;
    // Target block production per 4-year period
    const BLOCKS_PER_PERIOD: u64 = BLOCKS_PER_DAY * 365 * 4;

    const EXPECTED_PERIOD_EMISSIONS: [u64; 10] = [
        dusk(250_489_186.56), // Period 1
        dusk(125_244_593.28), // Period 2
        dusk(62_622_296.64),  // Period 3
        dusk(31_311_211.392), // Period 4
        dusk(15_655_605.696), // Period 5
        dusk(7_827_739.776),  // Period 6
        dusk(3_913_869.888),  // Period 7
        dusk(1_956_998.016),  // Period 8
        dusk(978_498.752),    // Period 9
        dusk(0.0),            // After Period 9
    ];

    #[test]
    fn test_period_emissions() {
        // Check each period emission corresponds to the expected value
        for (period, &expected) in EXPECTED_PERIOD_EMISSIONS.iter().enumerate()
        {
            let start_block = (period as u64 * BLOCKS_PER_PERIOD) + 1;
            let end_block = start_block + BLOCKS_PER_PERIOD;
            let mut period_emission = 0;
            for height in start_block..end_block {
                period_emission += emission_amount(height);
            }
            assert_eq!(
                period_emission,
                expected,
                "Emission for period {} did not match: expected {}, got {}",
                period + 1,
                expected,
                period_emission
            );
        }
    }

    #[test]
    fn test_total_emission() {
        let mut total_emission = 0u64;
        // Loop through each block emission and calculate the total emission
        for h in 0..=BLOCKS_PER_PERIOD * 10 {
            total_emission += emission_amount(h)
        }
        // Expected total emission based on the schedule
        let expected_total = dusk(500_000_000.0);

        // Ensure the calculated total matches the expected total
        assert_eq!(
            total_emission, expected_total,
            "Total emission did not match the expected value"
        );
    }
}