sn_transfers 0.9.8

Safe Network Transfer Logic
Documentation
// Copyright 2023 MaidSafe.net limited.
//
// This SAFE Network Software is licensed to you under The General Public License (GPL), version 3.
// Unless required by applicable law or agreed to in writing, the SAFE Network Software distributed
// under the GPL Licence is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. Please review the Licences for the specific language governing
// permissions and limitations relating to use of the SAFE Network Software.

use super::wallet::LocalWallet;

#[cfg(test)]
use sn_dbc::{
    rng, DbcIdSource, Hash, InputHistory, RevealedAmount, RevealedInput, TransactionBuilder,
};

use sn_dbc::{Dbc, DbcTransaction, Error as DbcError, MainKey};

use lazy_static::lazy_static;
use std::{fmt::Debug, path::PathBuf};
use thiserror::Error;

/// Number of tokens in the Genesis DBC.
/// At the inception of the Network 30 % of total supply - i.e. 1,288,490,189 - whole tokens will be created.
/// Each whole token can be subdivided 10^9 times,
/// thus creating a total of 1,288,490,189,000,000,000 available units.
#[cfg(test)]
pub(super) const GENESIS_DBC_AMOUNT: u64 = (0.3 * TOTAL_SUPPLY as f64) as u64;

/// A specialised `Result` type for dbc_genesis crate.
#[cfg(test)]
pub(super) type GenesisResult<T> = Result<T, Error>;

/// Total supply of tokens that will eventually exist in the network: 4,294,967,295 * 10^9 = 4,294,967,295,000,000,000.
#[cfg(test)]
const TOTAL_SUPPLY: u64 = u32::MAX as u64 * u64::pow(10, 9);

/// The secret key for the genesis DBC.
///
/// This key is public for auditing purposes. Hard coding its value means all nodes will be able to
/// validate it.
const GENESIS_DBC_SK: &str = "5f15ae2ea589007e1474e049bbc32904d583265f12ce1f8153f955076a9af49b";
const GENESIS_DBC_HEX: &str = "000000000000000049dff9aaca1dc4720c0b0d3311e95ca7cabf564d494c160573b2f3142d46fa25e9e34ca90982e4f35ad18fa8fc9126044deb21fb1ad88b77541e9c63b83fa932369f1a93f8139482b52f65dbb80c5c1c696cf30875bf3894022dd61ccdc1d88da83a50b3ce1f8fa29ba65c72b767f7b878055ee4ac31e78ca8188bb1f406557eb427816d5cdd023a0000000000000028aa8317bec2db715282ed641758a43cd899d6b144ea57670afaaa8b06058fadfd5d12ad5aef0c2da90fc8c4cab83796aaf7f940ce7b2c90b35fbc774c71724b4b507b6e6bba497f0e66cfb875e48f94df10669deae88277e3cb2e7e145fa38d10cc40a6a963885dcc465ef3ae33613aa31946b971990a8255ee9b7cef615d245df811781ddc2e69e1e05e1a39f6a2b0afeb6dcf823100d908ced0eef6dec65fd5838e55a169e5b721d5ea338a9c774a8b00000000000000201fd7137d18bc3d68dd74bf8b883288f9f69134af0f9a7678e89650f6eb0d00bcf1ee85af6b3aa34dc3abdf394e46eba02a4123be9e0b9cffe1f4cc94a849a72e866fdbee4f54ed6e390b7878bc32c9162a53cc700cc76f511553beb3e8a5548c18a465ddf34b54fa622d570c68df841d26d321c55313342ea9aeadffb01bd170061b9f3ef1247dc95939065a21bb13292ada858065e5f0136b2587206a1ea1e70ae9518be6ce63feffc2dc73a9a3dcca2c20e458708396b77610e843afbf370658a9265717eec815ec959c7f56d0a1af4ddbf7ae443576ef97165fdf3261f2c056d27fee3a97d486d276656fe3f7f7318089ca569fba710ac82e33ffcaeae4486605d1f70218a3c5b8f7807332e0855d7464f0014826d0e99760074844ddf19a0d7710553e8017dd706e272317ba8cda3eb5c07ec47d7a83914d8e643d02f648556a961eef4d03001b58b5fde1caca410ed780a6b0949d4edaace4f2ec336ec42f54c599e544b15d90eee64edbbc4099e1007b8a3be073eaba00a90bf614d63a4ec5817d88f1f96c0e1b0b557b180652b48a369a8734d35a8e88ad95d3abd6766de60217b7be22fd92b72aaec06c2a25a254ff6ef5673e96964d5221667f55f42e87900980a43ec4d757dd78a4c7af24309798b4a7ffa5ab66292082c58abf144d6319ceb1cf15d7110945e660734f4c9c14714a1356226fcde8c8c418546bca29696b815c3ef6ec1a41f52ecfa5df9ae36cda15ebc44b59381b01ff88ebe4b63f22ce019c4b7d7dcabe79a334aa8b17acce32a10a91202176d90d69184ea2d802a28417765a91ac60a05b68c8f37f3eb73695f9cd0c9d3add3e224e607c7ea60eb15a0f3b7b4576a27b7e166723e0daaf07f798977827bec92180dbf2a4f9f502f614797d1d817b9749dbb5bd5088aba720a3ce3fe985bcaab0834c859b2b02505d9876f1179619ec39edc8f48f6dadc66886e872b655a955ffebc6341ca394077aa28a6872b57bf154afa910ded2f1c2593765229ea01a60588ab939bf8adc14aac406df62a31a49f0356485340733422c1027ee253c52287267b361d040445a82e827d3d1082edac263c7034ca25481b162031b6bf6b4a1acdb84ed2108fe00000000000002a0e1efaa9f098cf6af927d2a03b6b6735e23725f60a6412123f4b7c08c42e8d8067a2e00fd8c3b3af67d2929e1f7c739a300000000000000010000000000000000e1efaa9f098cf6af927d2a03b6b6735e23725f60a6412123f4b7c08c42e8d8067a2e00fd8c3b3af67d2929e1f7c739a3";

/// Main error type for the crate.
#[derive(Error, Debug, Clone)]
pub enum Error {
    /// Error occurred when creating the Genesis DBC.
    #[error("Genesis DBC error:: {0}")]
    GenesisDbcError(String),
    /// The dbc error reason that parsing failed.
    #[error("Failed to parse reason: {0}")]
    FailedToParseReason(#[from] Box<DbcError>),
}

lazy_static! {
    /// Load the genesis DBC.
    /// The genesis DBC is the first DBC in the network. It is created without
    /// a source transaction, as there was nothing before it.
    pub static ref GENESIS_DBC: Dbc = match sn_dbc::Dbc::from_hex(GENESIS_DBC_HEX) {
        Ok(dbc) => dbc,
        Err(err) => panic!("Failed to read genesis DBC: {err:?}"),
    };
}

/// Return if provided DbcTransaction is genesis parent tx.
pub fn is_genesis_parent_tx(parent_tx: &DbcTransaction) -> bool {
    parent_tx == &GENESIS_DBC.src_tx
}

pub async fn load_genesis_wallet() -> LocalWallet {
    info!("Loading genesis...");
    let mut genesis_wallet = create_genesis_wallet().await;

    info!("Depositing genesis DBC: {:#?}", GENESIS_DBC.id());
    genesis_wallet.deposit(vec![GENESIS_DBC.clone()]);
    genesis_wallet
        .store()
        .await
        .expect("Genesis wallet shall be stored successfully.");

    let genesis_balance = genesis_wallet.balance();
    info!("Genesis wallet balance: {genesis_balance}");

    genesis_wallet
}

async fn create_genesis_wallet() -> LocalWallet {
    let root_dir = get_genesis_dir().await;
    let wallet_dir = root_dir.join("wallet");
    tokio::fs::create_dir_all(&wallet_dir)
        .await
        .expect("Genesis wallet path to be successfully created.");

    let secret_key = bls::SecretKey::from_hex(GENESIS_DBC_SK)
        .expect("Genesis key hex shall be successfully parsed.");
    let main_key = MainKey::new(secret_key);
    let main_key_path = wallet_dir.join("main_key");
    tokio::fs::write(main_key_path, hex::encode(main_key.to_bytes()))
        .await
        .expect("Genesis key hex shall be successfully stored.");

    LocalWallet::load_from(&root_dir)
        .await
        .expect("Faucet wallet shall be created successfully.")
}

/// Create a first DBC given any key (i.e. not specifically the hard coded genesis key).
/// The derivation index and blinding factor are hard coded to ensure deterministic creation.
/// This is useful in tests.
#[cfg(test)]
pub(crate) fn create_first_dbc_from_key(first_dbc_key: &MainKey) -> GenesisResult<Dbc> {
    let dbc_id_src = DbcIdSource {
        public_address: first_dbc_key.public_address(),
        derivation_index: [0u8; 32],
    };
    let derived_key = first_dbc_key.derive_key(&dbc_id_src.derivation_index);
    let revealed_amount = RevealedAmount {
        value: GENESIS_DBC_AMOUNT,
        blinding_factor: sn_dbc::BlindingFactor::from_bits([0u8; 32]),
    };

    // Use the same key as the input and output of Genesis Tx.
    // The src tx is empty as this is the first DBC.
    let genesis_input = InputHistory {
        input: RevealedInput::new(derived_key, revealed_amount),
        input_src_tx: DbcTransaction {
            inputs: vec![],
            outputs: vec![],
        },
    };

    let reason = Hash::hash(b"GENESIS");

    let dbc_builder = TransactionBuilder::default()
        .add_input(genesis_input)
        .add_output(sn_dbc::Token::from_nano(GENESIS_DBC_AMOUNT), dbc_id_src)
        .build(reason, rng::thread_rng())
        .map_err(|err| {
            Error::GenesisDbcError(format!(
                "Failed to build the DBC transaction for genesis DBC: {err}",
            ))
        })?;

    // build the output DBCs
    let output_dbcs = dbc_builder.build_without_verifying().map_err(|err| {
        Error::GenesisDbcError(format!(
            "DBC builder failed to create output genesis DBC: {err}",
        ))
    })?;

    // just one output DBC is expected which is the genesis DBC
    let (genesis_dbc, _) = output_dbcs.into_iter().next().ok_or_else(|| {
        Error::GenesisDbcError(
            "DBC builder (unexpectedly) contains an empty set of outputs.".to_string(),
        )
    })?;

    Ok(genesis_dbc)
}

/// Split a dbc into multiple. ONLY FOR TEST.
#[cfg(test)]
#[allow(clippy::result_large_err, unused)]
pub(super) fn split(
    dbc: &Dbc,
    main_key: &MainKey,
    number: usize,
) -> GenesisResult<Vec<(Dbc, RevealedAmount)>> {
    let rng = &mut rng::thread_rng();

    let derived_key = dbc
        .derived_key(main_key)
        .map_err(|e| Error::FailedToParseReason(Box::new(e)))?;
    let revealed_amount = dbc
        .revealed_amount(&derived_key)
        .map_err(|e| Error::FailedToParseReason(Box::new(e)))?;
    let input = InputHistory {
        input: RevealedInput::new(derived_key, revealed_amount),
        input_src_tx: dbc.src_tx.clone(),
    };

    let recipients: Vec<_> = (0..number)
        .map(|_| {
            let dbc_id_src = main_key.random_dbc_id_src(rng);
            let amount = revealed_amount.value() / number as u64;
            (sn_dbc::Token::from_nano(amount), dbc_id_src)
        })
        .collect();

    let dbc_builder = TransactionBuilder::default()
        .add_input(input)
        .add_outputs(recipients)
        .build(Hash::default(), rng::thread_rng())
        .map_err(|err| {
            Error::GenesisDbcError(format!(
                "Failed to build the DBC transaction for genesis DBC: {err}",
            ))
        })?;

    // build the output DBCs
    let output_dbcs = dbc_builder.build_without_verifying().map_err(|err| {
        Error::GenesisDbcError(format!(
            "DBC builder failed to create output genesis DBC: {err}",
        ))
    })?;

    Ok(output_dbcs)
}

pub async fn create_faucet_wallet() -> LocalWallet {
    let root_dir = get_faucet_dir().await;
    LocalWallet::load_from(&root_dir)
        .await
        .expect("Faucet wallet shall be created successfully.")
}

// We need deterministic and fix path for the genesis wallet.
// Otherwise the test instances will not be able to find the same genesis instance.
async fn get_genesis_dir() -> PathBuf {
    let mut home_dirs = dirs_next::home_dir().expect("A homedir to exist.");
    home_dirs.push(".safe");
    home_dirs.push("test_genesis");
    tokio::fs::create_dir_all(home_dirs.as_path())
        .await
        .expect("Genesis test path to be successfully created.");
    home_dirs
}

// We need deterministic and fix path for the faucet wallet.
// Otherwise the test instances will not be able to find the same faucet instance.
async fn get_faucet_dir() -> PathBuf {
    let mut home_dirs = dirs_next::home_dir().expect("A homedir to exist.");
    home_dirs.push(".safe");
    home_dirs.push("test_faucet");
    tokio::fs::create_dir_all(home_dirs.as_path())
        .await
        .expect("Faucet test path to be successfully created.");
    home_dirs
}