diff --git a/Cargo.lock b/Cargo.lock index d4381b9472..a04770f24d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3225,12 +3225,14 @@ dependencies = [ "libc", "rand", "tari_common", + "tari_common_types", "tari_comms", "tari_core", "tari_crypto", "tari_features", "tari_utilities", "thiserror", + "tokio", ] [[package]] diff --git a/base_layer/core/src/blocks/new_block_template.rs b/base_layer/core/src/blocks/new_block_template.rs index 1c05b433e0..370587117e 100644 --- a/base_layer/core/src/blocks/new_block_template.rs +++ b/base_layer/core/src/blocks/new_block_template.rs @@ -22,6 +22,7 @@ use std::fmt::{Display, Formatter}; +use borsh::{BorshDeserialize, BorshSerialize}; use serde::{Deserialize, Serialize}; use crate::{ @@ -36,7 +37,7 @@ use crate::{ /// The new block template is used constructing a new partial block, allowing a miner to added the coinbase utxo and as /// a final step the Base node to add the MMR roots to the header. -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] pub struct NewBlockTemplate { /// The NewBlockHeaderTemplate is used for the construction of a new mineable block. It contains all the metadata /// for the block that the Base Node is able to complete on behalf of a Miner. diff --git a/base_layer/core/src/blocks/new_blockheader_template.rs b/base_layer/core/src/blocks/new_blockheader_template.rs index b26b6aa7d3..5cdf55e172 100644 --- a/base_layer/core/src/blocks/new_blockheader_template.rs +++ b/base_layer/core/src/blocks/new_blockheader_template.rs @@ -22,15 +22,15 @@ use std::fmt::{Display, Error, Formatter}; +use borsh::{BorshDeserialize, BorshSerialize}; use serde::{Deserialize, Serialize}; use tari_common_types::types::{BlockHash, PrivateKey}; use tari_utilities::hex::Hex; use crate::{blocks::block_header::BlockHeader, proof_of_work::ProofOfWork}; - /// The NewBlockHeaderTemplate is used for the construction of a new mineable block. It contains all the metadata for /// the block that the Base Node is able to complete on behalf of a Miner. -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, BorshSerialize, BorshDeserialize)] pub struct NewBlockHeaderTemplate { /// Version of the block pub version: u16, diff --git a/base_layer/core/src/proof_of_work/difficulty.rs b/base_layer/core/src/proof_of_work/difficulty.rs index 13e05d34a1..dc9ec85baf 100644 --- a/base_layer/core/src/proof_of_work/difficulty.rs +++ b/base_layer/core/src/proof_of_work/difficulty.rs @@ -22,6 +22,7 @@ use std::fmt; +use borsh::{BorshDeserialize, BorshSerialize}; use num_format::{Locale, ToFormattedString}; use primitive_types::U256; use serde::{Deserialize, Serialize}; @@ -34,7 +35,9 @@ use crate::proof_of_work::{error::DifficultyError, DifficultyAdjustmentError}; pub const MIN_DIFFICULTY: u64 = 1; /// The difficulty is defined as the maximum target divided by the block hash. -#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Eq, Ord, Deserialize, Serialize)] +#[derive( + Debug, Clone, Copy, PartialEq, PartialOrd, Eq, Ord, Deserialize, Serialize, BorshSerialize, BorshDeserialize, +)] pub struct Difficulty(u64); impl Difficulty { diff --git a/base_layer/tari_mining_helper_ffi/Cargo.toml b/base_layer/tari_mining_helper_ffi/Cargo.toml index adc94c7bec..f2c8b1b525 100644 --- a/base_layer/tari_mining_helper_ffi/Cargo.toml +++ b/base_layer/tari_mining_helper_ffi/Cargo.toml @@ -11,11 +11,13 @@ tari_comms = { path = "../../comms/core" } tari_crypto = { version = "0.20" } tari_common = { path = "../../common" } tari_core = { path = "../core", default-features = false, features = ["transactions", "base_node_proto", "base_node"] } +tari_common_types = { path = "../../base_layer/common_types", version = "1.0.0-pre.9" } tari_utilities = { version = "0.7" } libc = "0.2.65" thiserror = "1.0.26" borsh = "1.2" hex = "0.4.2" +tokio = { version = "1.36", features = ["rt"] } [dev-dependencies] tari_core = { path = "../core", features = ["transactions", "base_node"] } @@ -25,4 +27,4 @@ rand = "0.8" tari_features = { path = "../../common/tari_features", version = "1.0.0-pre.9" } [lib] -crate-type = ["staticlib","cdylib"] +crate-type = ["cdylib"] diff --git a/base_layer/tari_mining_helper_ffi/src/error.rs b/base_layer/tari_mining_helper_ffi/src/error.rs index d5a8b475a0..e9fea91c8d 100644 --- a/base_layer/tari_mining_helper_ffi/src/error.rs +++ b/base_layer/tari_mining_helper_ffi/src/error.rs @@ -36,6 +36,14 @@ pub enum InterfaceError { AllocationError, #[error("An error because the supplied position was out of range")] PositionInvalidError, + #[error("An error has occurred when trying to create the tokio runtime: `{0}`")] + TokioError(String), + #[error("An error has occurred when trying to create the a coinbase: `{0}`")] + CoinbaseBuildError(String), + #[error("An invalid address was passed in: `{0}`")] + InvalidAddress(String), + #[error("An invalid network was passed in: `{0}`")] + InvalidNetwork(String), } /// This struct is meant to hold an error for use by Miningcore. The error has an integer code and string @@ -73,6 +81,22 @@ impl From for MiningHelperError { code: 6, message: format!("{:?}", v), }, + InterfaceError::TokioError(_) => Self { + code: 7, + message: format!("{:?}", v), + }, + InterfaceError::CoinbaseBuildError(_) => Self { + code: 8, + message: format!("{:?}", v), + }, + InterfaceError::InvalidAddress(_) => Self { + code: 9, + message: format!("{:?}", v), + }, + InterfaceError::InvalidNetwork(_) => Self { + code: 10, + message: format!("{:?}", v), + }, } } } diff --git a/base_layer/tari_mining_helper_ffi/src/lib.rs b/base_layer/tari_mining_helper_ffi/src/lib.rs index 41440352fe..dd569a2b9d 100644 --- a/base_layer/tari_mining_helper_ffi/src/lib.rs +++ b/base_layer/tari_mining_helper_ffi/src/lib.rs @@ -30,12 +30,24 @@ mod error; use core::ptr; -use std::{convert::TryFrom, ffi::CString, slice}; +use std::{convert::TryFrom, ffi::CString, slice, str::FromStr}; use borsh::{BorshDeserialize, BorshSerialize}; use libc::{c_char, c_int, c_uchar, c_uint, c_ulonglong}; -use tari_core::{blocks::BlockHeader, proof_of_work::sha3x_difficulty}; +use tari_common::{configuration::Network, network_check::set_network_if_choice_valid}; +use tari_common_types::tari_address::TariAddress; +use tari_core::{ + blocks::{BlockHeader, NewBlockTemplate}, + consensus::ConsensusManager, + proof_of_work::sha3x_difficulty, + transactions::{ + generate_coinbase, + key_manager::create_memory_db_key_manager, + transaction_components::RangeProofType, + }, +}; use tari_crypto::tari_utilities::hex::Hex; +use tokio::runtime::Runtime; use crate::error::{InterfaceError, MiningHelperError}; @@ -233,6 +245,155 @@ pub unsafe extern "C" fn inject_nonce(header: *mut ByteVector, nonce: c_ulonglon (*header).0 = buffer; } +/// Injects a coinbase into a blocktemplate +/// +/// ## Arguments +/// `block_template_bytes` - The block template as bytes, serialized with borsh.io +/// `value` - The value of the coinbase +/// `stealth_payment` - Boolean value, is this a stealh payment or normal one-sided +/// `revealed_value_proof` - Boolean value, should this use the reveal value proof, or BP+ +/// `wallet_payment_address` - The address to pay the coinbase to +/// `coinbase_extra` - The value of the coinbase extra field +/// `network` - The value of the network +/// +/// ## Returns +/// `block_template_bytes` - The updated block template +/// `error_out` - Error code returned, 0 means no error +/// +/// # Safety +/// None +#[allow(clippy::too_many_lines)] +#[no_mangle] +pub unsafe extern "C" fn inject_coinbase( + block_template_bytes: *mut ByteVector, + coibase_value: c_ulonglong, + stealth_payment: bool, + revealed_value_proof: bool, + wallet_payment_address: *const c_char, + coinbase_extra: *const c_char, + network: c_uint, + error_out: *mut c_int, +) { + let mut error = 0; + ptr::swap(error_out, &mut error as *mut c_int); + if block_template_bytes.is_null() { + error = MiningHelperError::from(InterfaceError::NullError("block template".to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + return; + } + if wallet_payment_address.is_null() { + error = MiningHelperError::from(InterfaceError::NullError("wallet_payment_address".to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + return; + } + let native_string_address = CString::from_raw(wallet_payment_address as *mut i8) + .to_str() + .unwrap() + .to_owned(); + let wallet_address = match TariAddress::from_str(&native_string_address) { + Ok(v) => v, + Err(e) => { + error = MiningHelperError::from(InterfaceError::InvalidAddress(e.to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + return; + }, + }; + if coinbase_extra.is_null() { + error = MiningHelperError::from(InterfaceError::NullError("coinbase_extra".to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + return; + } + let network_u8 = match u8::try_from(network) { + Ok(v) => v, + Err(e) => { + error = MiningHelperError::from(InterfaceError::InvalidNetwork(e.to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + return; + }, + }; + let network = match Network::try_from(network_u8) { + Ok(v) => v, + Err(e) => { + error = MiningHelperError::from(InterfaceError::InvalidNetwork(e.to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + return; + }, + }; + // Set the static network variable according to the user chosen network (for use with + // `get_current_or_user_setting_or_default()`) - + if let Err(e) = set_network_if_choice_valid(network) { + error = MiningHelperError::from(InterfaceError::InvalidNetwork(e.to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + return; + }; + let coinbase_extra_string = CString::from_raw(coinbase_extra as *mut i8) + .to_str() + .unwrap() + .to_owned(); + let mut bytes = (*block_template_bytes).0.as_slice(); + let mut block_template: NewBlockTemplate = match BorshDeserialize::deserialize(&mut bytes) { + Ok(v) => v, + Err(e) => { + error = MiningHelperError::from(InterfaceError::Conversion(e.to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + return; + }, + }; + let key_manager = create_memory_db_key_manager(); + + let consensus_manager = match ConsensusManager::builder(network).build() { + Ok(v) => v, + Err(e) => { + error = MiningHelperError::from(InterfaceError::NullError(e.to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + return; + }, + }; + let runtime = match Runtime::new() { + Ok(r) => r, + Err(e) => { + error = MiningHelperError::from(InterfaceError::TokioError(e.to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + return; + }, + }; + let range_proof_type = if revealed_value_proof { + RangeProofType::RevealedValue + } else { + RangeProofType::BulletProofPlus + }; + let height = block_template.header.height; + let (coinbase_output, coinbase_kernel) = match runtime.block_on(async { + // we dont count the fee or the reward here, we assume the caller has calculated the amount to be the exact + // value for the coinbase(s) they want. + generate_coinbase( + 0.into(), + coibase_value.into(), + height, + coinbase_extra_string.as_bytes(), + &key_manager, + &wallet_address, + stealth_payment, + consensus_manager.consensus_constants(height), + range_proof_type, + ) + .await + }) { + Ok(v) => v, + Err(e) => { + error = MiningHelperError::from(InterfaceError::CoinbaseBuildError(e.to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + return; + }, + }; + block_template.body.add_output(coinbase_output); + block_template.body.add_kernel(coinbase_kernel); + block_template.body.sort(); + let mut buffer = Vec::new(); + BorshSerialize::serialize(&block_template, &mut buffer).unwrap(); + (*block_template_bytes).0 = buffer; +} + /// Returns the difficulty of a share /// /// ## Arguments @@ -245,9 +406,36 @@ pub unsafe extern "C" fn inject_nonce(header: *mut ByteVector, nonce: c_ulonglon /// # Safety /// None #[no_mangle] -pub unsafe extern "C" fn share_difficulty(header: *mut ByteVector, error_out: *mut c_int) -> c_ulonglong { +pub unsafe extern "C" fn share_difficulty( + header: *mut ByteVector, + network: c_uint, + error_out: *mut c_int, +) -> c_ulonglong { let mut error = 0; ptr::swap(error_out, &mut error as *mut c_int); + let network_u8 = match u8::try_from(network) { + Ok(v) => v, + Err(e) => { + error = MiningHelperError::from(InterfaceError::InvalidNetwork(e.to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + return 1; + }, + }; + let network = match Network::try_from(network_u8) { + Ok(v) => v, + Err(e) => { + error = MiningHelperError::from(InterfaceError::InvalidNetwork(e.to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + return 1; + }, + }; + // Set the static network variable according to the user chosen network (for use with + // `get_current_or_user_setting_or_default()`) - + if let Err(e) = set_network_if_choice_valid(network) { + error = MiningHelperError::from(InterfaceError::InvalidNetwork(e.to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + return 1; + }; if header.is_null() { error = MiningHelperError::from(InterfaceError::NullError("header".to_string())).code; ptr::swap(error_out, &mut error as *mut c_int); @@ -297,12 +485,36 @@ pub unsafe extern "C" fn share_difficulty(header: *mut ByteVector, error_out: *m pub unsafe extern "C" fn share_validate( header: *mut ByteVector, hash: *const c_char, + network: c_uint, share_difficulty: c_ulonglong, template_difficulty: c_ulonglong, error_out: *mut c_int, ) -> c_int { let mut error = 0; ptr::swap(error_out, &mut error as *mut c_int); + let network_u8 = match u8::try_from(network) { + Ok(v) => v, + Err(e) => { + error = MiningHelperError::from(InterfaceError::InvalidNetwork(e.to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + return 1; + }, + }; + let network = match Network::try_from(network_u8) { + Ok(v) => v, + Err(e) => { + error = MiningHelperError::from(InterfaceError::InvalidNetwork(e.to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + return 1; + }, + }; + // Set the static network variable according to the user chosen network (for use with + // `get_current_or_user_setting_or_default()`) - + if let Err(e) = set_network_if_choice_valid(network) { + error = MiningHelperError::from(InterfaceError::InvalidNetwork(e.to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + return 1; + }; if header.is_null() { error = MiningHelperError::from(InterfaceError::NullError("header".to_string())).code; ptr::swap(error_out, &mut error as *mut c_int); @@ -355,6 +567,7 @@ mod tests { use tari_core::{ blocks::{genesis_block::get_genesis_block, Block}, proof_of_work::Difficulty, + transactions::tari_amount::MicroMinotari, }; use super::*; @@ -387,16 +600,33 @@ mod tests { #[test] fn detect_change_in_consensus_encoding() { #[cfg(tari_target_network_mainnet)] - let (nonce, difficulty) = match Network::get_current_or_user_setting_or_default() { - Network::MainNet => (3145418102407526886, Difficulty::from_u64(1505).unwrap()), - Network::StageNet => (135043993867732261, Difficulty::from_u64(1059).unwrap()), + let (nonce, difficulty, network) = match Network::get_current_or_user_setting_or_default() { + Network::MainNet => ( + 3145418102407526886, + Difficulty::from_u64(1505).unwrap(), + Network::MainNet, + ), + Network::StageNet => ( + 135043993867732261, + Difficulty::from_u64(1059).unwrap(), + Network::StageNet, + ), _ => panic!("Invalid network for mainnet target"), }; #[cfg(tari_target_network_nextnet)] - let (nonce, difficulty) = (5154919981564263219, Difficulty::from_u64(2950).unwrap()); + let (nonce, difficulty, network) = ( + 5154919981564263219, + Difficulty::from_u64(2950).unwrap(), + Network::NextNet, + ); #[cfg(not(any(tari_target_network_mainnet, tari_target_network_nextnet)))] - let (nonce, difficulty) = (8520885611996410570, Difficulty::from_u64(3143).unwrap()); + let (nonce, difficulty, network) = ( + 8520885611996410570, + Difficulty::from_u64(3143).unwrap(), + Network::Esmeralda, + ); unsafe { + set_network_if_choice_valid(network).unwrap(); let mut error = -1; let error_ptr = &mut error as *mut c_int; let block = create_test_block(); @@ -406,12 +636,11 @@ mod tests { let byte_vec = byte_vector_create(header_bytes.as_ptr(), len, error_ptr); inject_nonce(byte_vec, nonce, error_ptr); assert_eq!(error, 0); - let result = share_difficulty(byte_vec, error_ptr); + let result = share_difficulty(byte_vec, u32::from(network.as_byte()), error_ptr); if result != difficulty.as_u64() { // Use this to generate new NONCE and DIFFICULTY // Use ONLY if you know encoding has changed let (difficulty, nonce) = generate_nonce_with_min_difficulty(min_difficulty()).unwrap(); - let network = Network::get_current_or_user_setting_or_default(); eprintln!("network = {network:?}"); eprintln!("nonce = {:?}", nonce); eprintln!("difficulty = {:?}", difficulty); @@ -427,6 +656,7 @@ mod tests { #[test] fn check_difficulty() { unsafe { + let network = Network::get_current_or_user_setting_or_default(); let (difficulty, nonce) = generate_nonce_with_min_difficulty(min_difficulty()).unwrap(); let mut error = -1; let error_ptr = &mut error as *mut c_int; @@ -436,7 +666,7 @@ mod tests { let byte_vec = byte_vector_create(header_bytes.as_ptr(), len, error_ptr); inject_nonce(byte_vec, nonce, error_ptr); assert_eq!(error, 0); - let result = share_difficulty(byte_vec, error_ptr); + let result = share_difficulty(byte_vec, u32::from(network.as_byte()), error_ptr); assert_eq!(result, difficulty.as_u64()); byte_vector_destroy(byte_vec); } @@ -463,6 +693,7 @@ mod tests { #[test] fn check_share() { unsafe { + let network = Network::get_current_or_user_setting_or_default(); let (difficulty, nonce) = generate_nonce_with_min_difficulty(min_difficulty()).unwrap(); let mut error = -1; let error_ptr = &mut error as *mut c_int; @@ -481,6 +712,7 @@ mod tests { let result = share_validate( byte_vec, hash_hex_broken_ptr, + u32::from(network.as_byte()), share_difficulty, template_difficulty, error_ptr, @@ -495,20 +727,41 @@ mod tests { share_difficulty = difficulty.as_u64() + 1000; template_difficulty = difficulty.as_u64() + 2000; // let calculate for invalid share and target diff - let result = share_validate(byte_vec, hash_hex_ptr, share_difficulty, template_difficulty, error_ptr); + let result = share_validate( + byte_vec, + hash_hex_ptr, + u32::from(network.as_byte()), + share_difficulty, + template_difficulty, + error_ptr, + ); assert_eq!(result, 4); assert_eq!(error, 4); // let calculate for valid share and invalid target diff share_difficulty = difficulty.as_u64(); let hash_hex = CString::new(hash.clone()).unwrap(); let hash_hex_ptr: *const c_char = CString::into_raw(hash_hex) as *const c_char; - let result = share_validate(byte_vec, hash_hex_ptr, share_difficulty, template_difficulty, error_ptr); + let result = share_validate( + byte_vec, + hash_hex_ptr, + u32::from(network.as_byte()), + share_difficulty, + template_difficulty, + error_ptr, + ); assert_eq!(result, 1); // let calculate for valid target diff template_difficulty = difficulty.as_u64(); let hash_hex = CString::new(hash).unwrap(); let hash_hex_ptr: *const c_char = CString::into_raw(hash_hex) as *const c_char; - let result = share_validate(byte_vec, hash_hex_ptr, share_difficulty, template_difficulty, error_ptr); + let result = share_validate( + byte_vec, + hash_hex_ptr, + u32::from(network.as_byte()), + share_difficulty, + template_difficulty, + error_ptr, + ); assert_eq!(result, 0); byte_vector_destroy(byte_vec); } @@ -539,4 +792,49 @@ mod tests { assert_ne!(error, 0); } } + + #[test] + fn check_inject_coinbase() { + unsafe { + let network = Network::get_current_or_user_setting_or_default(); + let mut error = -1; + let error_ptr = &mut error as *mut c_int; + let header = BlockHeader::new(0); + let block = + NewBlockTemplate::from_block(header.into_builder().build(), Difficulty::min(), 0.into()).unwrap(); + + let block_bytes = borsh::to_vec(&block).unwrap(); + #[allow(clippy::cast_possible_truncation)] + let len = block_bytes.len() as u32; + let byte_vec = byte_vector_create(block_bytes.as_ptr(), len, error_ptr); + + let address = TariAddress::default(); + let add_string = CString::new(address.to_string()).unwrap(); + let add_ptr: *const c_char = CString::into_raw(add_string) as *const c_char; + + let extra_string = CString::new("a").unwrap(); + let extra_ptr: *const c_char = CString::into_raw(extra_string) as *const c_char; + + inject_coinbase( + byte_vec, + 100, + false, + true, + add_ptr, + extra_ptr, + u32::from(network.as_byte()), + error_ptr, + ); + + assert_eq!(error, 0); + + let block_temp: NewBlockTemplate = BorshDeserialize::deserialize(&mut (*byte_vec).0.as_slice()).unwrap(); + + assert_eq!(block_temp.body.kernels().len(), 1); + assert_eq!(block_temp.body.outputs().len(), 1); + assert!(block_temp.body.outputs()[0].features.is_coinbase()); + assert_eq!(block_temp.body.outputs()[0].features.coinbase_extra, vec![97]); + assert_eq!(block_temp.body.outputs()[0].minimum_value_promise, MicroMinotari(100)); + } + } }