Skip to content

Commit

Permalink
feat: add fee per gram stats to ffi library
Browse files Browse the repository at this point in the history
  • Loading branch information
Cifko committed May 18, 2022
1 parent 50993a3 commit 9931dd2
Show file tree
Hide file tree
Showing 11 changed files with 4,297 additions and 264 deletions.
17 changes: 9 additions & 8 deletions base_layer/core/src/mempool/unconfirmed_pool/unconfirmed_pool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -581,15 +581,16 @@ impl UnconfirmedPool {
total_fees += total_tx_fee;
total_weight += weight;
}
if last_count > 0 {
let stat = FeePerGramStat {
order: start as u64,
min_fee_per_gram,
avg_fee_per_gram: total_fees / total_weight,
max_fee_per_gram,
};
stats.push(stat);
if last_count == 0 {
break;
}
let stat = FeePerGramStat {
order: start as u64,
min_fee_per_gram,
avg_fee_per_gram: total_fees / total_weight,
max_fee_per_gram,
};
stats.push(stat);
offset = last_count;
}

Expand Down
160 changes: 160 additions & 0 deletions base_layer/wallet_ffi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,8 @@ pub struct TariContacts(Vec<TariContact>);
pub type TariContact = tari_wallet::contacts_service::storage::database::Contact;
pub type TariCompletedTransaction = tari_wallet::transaction_service::storage::models::CompletedTransaction;
pub type TariTransactionSendStatus = tari_wallet::transaction_service::handle::TransactionSendStatus;
pub type TariFeePerGramStats = tari_wallet::transaction_service::handle::FeePerGramStatsResponse;
pub type TariFeePerGramStat = tari_core::mempool::FeePerGramStat;
pub type TariContactsLivenessData = tari_wallet::contacts_service::handle::ContactsLivenessData;
pub type TariBalance = tari_wallet::output_manager_service::service::Balance;
pub type TariMnemonicLanguage = tari_key_manager::mnemonic::MnemonicLanguage;
Expand Down Expand Up @@ -6430,6 +6432,164 @@ pub unsafe extern "C" fn log_debug_message(msg: *const c_char, error_out: *mut c
}
}

/// ------------------------------------- FeePerGramStats ------------------------------------ ///
#[no_mangle]
pub unsafe extern "C" fn wallet_get_fee_per_gram_stats(
wallet: *mut TariWallet,
count: c_uint,
error_out: *mut c_int,
) -> *mut TariFeePerGramStats {
let mut error = 0;
ptr::swap(error_out, &mut error as *mut c_int);

if wallet.is_null() {
error = LibWalletError::from(InterfaceError::NullError("wallet".to_string())).code;
ptr::swap(error_out, &mut error as *mut c_int);
return ptr::null_mut();
}

match (*wallet).runtime.block_on(
(*wallet)
.wallet
.transaction_service
.get_fee_per_gram_stats_per_block(count as usize),
) {
Ok(estimates) => Box::into_raw(Box::new(estimates)),
Err(e) => {
error!(target: LOG_TARGET, "Error getting the fee estimates: {:?}", e);
error = LibWalletError::from(WalletError::TransactionServiceError(e)).code;
ptr::swap(error_out, &mut error as *mut c_int);
ptr::null_mut()
},
}
}

#[no_mangle]
pub unsafe extern "C" fn fee_per_gram_stats_get_length(
fee_per_gram_stats: *mut TariFeePerGramStats,
error_out: *mut c_int,
) -> c_uint {
let mut error = 0;
ptr::swap(error_out, &mut error as *mut c_int);
let mut len = 0;
if fee_per_gram_stats.is_null() {
error = LibWalletError::from(InterfaceError::NullError("fee_per_gram_stats".to_string())).code;
ptr::swap(error_out, &mut error as *mut c_int);
} else {
len = (*fee_per_gram_stats).stats.len();
}
len as c_uint
}

#[no_mangle]
pub unsafe extern "C" fn fee_per_gram_stats_get_at(
fee_per_gram_stats: *mut TariFeePerGramStats,
position: c_uint,
error_out: *mut c_int,
) -> *mut TariFeePerGramStat {
let mut error = 0;
ptr::swap(error_out, &mut error as *mut c_int);
if fee_per_gram_stats.is_null() {
error = LibWalletError::from(InterfaceError::NullError("fee_per_gram_stats".to_string())).code;
ptr::swap(error_out, &mut error as *mut c_int);
return ptr::null_mut();
}
let len = fee_per_gram_stats_get_length(fee_per_gram_stats, error_out) as c_int - 1;
if len < 0 || position > len as c_uint {
error = LibWalletError::from(InterfaceError::PositionInvalidError).code;
ptr::swap(error_out, &mut error as *mut c_int);
return ptr::null_mut();
}
Box::into_raw(Box::new((*fee_per_gram_stats).stats[position as usize].clone()))
}

#[no_mangle]
pub unsafe extern "C" fn fee_per_gram_stats_destroy(fee_per_gram_stats: *mut TariFeePerGramStats) {
if !fee_per_gram_stats.is_null() {
Box::from_raw(fee_per_gram_stats);
}
}

/// ------------------------------------------------------------------------------------------ ///
/// ------------------------------------- FeePerGramStat ------------------------------------- ///
#[no_mangle]
pub unsafe extern "C" fn fee_per_gram_stat_get_order(
fee_per_gram_stat: *mut TariFeePerGramStat,
error_out: *mut c_int,
) -> c_ulonglong {
let mut error = 0;
ptr::swap(error_out, &mut error as *mut c_int);
let mut order = 0;
if fee_per_gram_stat.is_null() {
error = LibWalletError::from(InterfaceError::NullError("fee_per_gram_stat".to_string())).code;
ptr::swap(error_out, &mut error as *mut c_int);
} else {
order = (*fee_per_gram_stat).order;
}
order
}

#[no_mangle]
pub unsafe extern "C" fn fee_per_gram_stat_get_min_fee_per_gram(
fee_per_gram_stat: *mut TariFeePerGramStat,
error_out: *mut c_int,
) -> c_ulonglong {
let mut error = 0;
ptr::swap(error_out, &mut error as *mut c_int);
let mut fee_per_gram = 0;
if fee_per_gram_stat.is_null() {
error = LibWalletError::from(InterfaceError::NullError("fee_per_gram_stat".to_string())).code;
ptr::swap(error_out, &mut error as *mut c_int);
} else {
fee_per_gram = (*fee_per_gram_stat).min_fee_per_gram.as_u64();
}
fee_per_gram
}

#[no_mangle]
pub unsafe extern "C" fn fee_per_gram_stat_get_avg_fee_per_gram(
fee_per_gram_stat: *mut TariFeePerGramStat,
error_out: *mut c_int,
) -> c_ulonglong {
let mut error = 0;
ptr::swap(error_out, &mut error as *mut c_int);
let mut fee_per_gram = 0;
if fee_per_gram_stat.is_null() {
error = LibWalletError::from(InterfaceError::NullError("fee_per_gram_stat".to_string())).code;
ptr::swap(error_out, &mut error as *mut c_int);
} else {
fee_per_gram = (*fee_per_gram_stat).avg_fee_per_gram.as_u64();
}
fee_per_gram
}

#[no_mangle]
pub unsafe extern "C" fn fee_per_gram_stat_get_max_fee_per_gram(
fee_per_gram_stat: *mut TariFeePerGramStat,
error_out: *mut c_int,
) -> c_ulonglong {
let mut error = 0;
ptr::swap(error_out, &mut error as *mut c_int);
let mut fee_per_gram = 0;
if fee_per_gram_stat.is_null() {
error = LibWalletError::from(InterfaceError::NullError("fee_per_gram_stat".to_string())).code;
ptr::swap(error_out, &mut error as *mut c_int);
} else {
fee_per_gram = (*fee_per_gram_stat).max_fee_per_gram.as_u64();
}
fee_per_gram
}

#[no_mangle]
pub unsafe extern "C" fn fee_per_gram_stat_destroy(fee_per_gram_stat: *mut TariFeePerGramStat) {
if !fee_per_gram_stat.is_null() {
Box::from_raw(fee_per_gram_stat);
}
}

/// ------------------------------------------------------------------------------------------ ///
#[cfg(test)]
mod test {
use std::{
Expand Down
4 changes: 4 additions & 0 deletions base_layer/wallet_ffi/wallet.h
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ struct EmojiSet;

struct TariTransactionKernel;

struct TariFeePerGramStatsResponse;

/// -------------------------------- Transport Types ----------------------------------------------- ///

// Creates a memory transport type
Expand Down Expand Up @@ -868,6 +870,8 @@ bool wallet_start_recovery(struct TariWallet *wallet, struct TariPublicKey *base
/// None
bool wallet_set_one_sided_payment_message(struct TariWallet *wallet, const char *message, int *error_out);

struct TariFeePerGramStatsResponse *wallet_get_fee_estimates_per_block(struct TariWallet *wallet, unsigned int count, int *error_out);

// Frees memory for a TariWallet
void wallet_destroy(struct TariWallet *wallet);

Expand Down
27 changes: 23 additions & 4 deletions integration_tests/features/WalletFFI.feature
Original file line number Diff line number Diff line change
Expand Up @@ -88,14 +88,14 @@ Feature: Wallet FFI
@critical
Scenario: As a client I want to receive contact liveness events
Given I have a seed node SEED
# Contact liveness is based on P2P messaging; ensure connectivity by forcing 'DirectOnly'
# Contact liveness is based on P2P messaging; ensure connectivity by forcing 'DirectOnly'
And I have non-default wallet WALLET1 connected to all seed nodes using DirectOnly
And I have non-default wallet WALLET2 connected to all seed nodes using DirectOnly
And I have a ffi wallet FFI_WALLET connected to seed node SEED
# Start the contact liveness pings by adding contacts to the FFI wallet
# Start the contact liveness pings by adding contacts to the FFI wallet
And I add contact with alias ALIAS1 and pubkey WALLET1 to ffi wallet FFI_WALLET
And I add contact with alias ALIAS2 and pubkey WALLET2 to ffi wallet FFI_WALLET
# Do some mining and send transactions to force P2P discovery
# Do some mining and send transactions to force P2P discovery
And I have mining node MINER1 connected to base node SEED and wallet WALLET1
And I have mining node MINER2 connected to base node SEED and wallet WALLET2
And mining node MINER1 mines 1 blocks
Expand Down Expand Up @@ -137,7 +137,7 @@ Feature: Wallet FFI
Then I want to view the transaction kernels for completed transactions in ffi wallet FFI_WALLET
And I stop ffi wallet FFI_WALLET

#BROKEN: Sending via SAF works when running this test alone, but not when run with all other tests. Also works manually on dibbler
#BROKEN: Sending via SAF works when running this test alone, but not when run with all other tests. Also works manually on dibbler
@critical @broken
Scenario: As a client I want to receive Tari via my Public Key sent while I am offline when I come back online
Given I have a seed node SEED
Expand Down Expand Up @@ -209,6 +209,25 @@ Feature: Wallet FFI
Then ffi wallet FFI_RECEIVER detects AT_LEAST 1 ffi transactions to be TRANSACTION_STATUS_FAUX_CONFIRMED
And I stop ffi wallet FFI_RECEIVER

Scenario: As a client I want to get fee per gram stats
Given I have a base node BASE
And I have wallet WALLET_A connected to base node BASE
And I have wallet WALLET_B connected to base node BASE
And I have mining node MINER connected to base node BASE and wallet WALLET_A
And mining node MINER mines 7 blocks
And I have wallet WALLET_B connected to base node BASE
Then I wait for wallet WALLET_A to have at least 10000000 uT
And I have a ffi wallet FFI_WALLET connected to base node BASE
And The fee per gram stats for FFI_WALLET are 1, 1, 1
And I send 1000000 uT from wallet WALLET_A to wallet WALLET_B at fee 20
And The fee per gram stats for FFI_WALLET are 20, 20, 20
And I send 1000000 uT from wallet WALLET_A to wallet WALLET_B at fee 40
And The fee per gram stats for FFI_WALLET are 20, 30, 40
And I send 1000000 uT from wallet WALLET_A to wallet WALLET_B at fee 60
And The fee per gram stats for FFI_WALLET are 20, 40, 60
And mining node MINER mines 1 blocks
And The fee per gram stats for FFI_WALLET are 1, 1, 1

# Scenario: As a client I want to get my balance
# It's a subtest of "As a client I want to retrieve a list of transactions I have made and received"

Expand Down
14 changes: 14 additions & 0 deletions integration_tests/features/support/ffi_steps.js
Original file line number Diff line number Diff line change
Expand Up @@ -732,3 +732,17 @@ Then(
);
}
);

Then(
"The fee per gram stats for {word} are {int}, {int}, {int}",
{ timeout: 125 * 1000 },
async function (walletName, min_fee, avg_fee, max_fee) {
const wallet = this.getWallet(walletName);
const feePerGramStats = await wallet.getFeePerGramStats(5);
expect(feePerGramStats.getLength()).to.be.greaterThanOrEqual(1);
const feePerGramStat = feePerGramStats.getAt(0);
expect(feePerGramStat.getMinFeePerGram()).to.be.equal(min_fee);
expect(feePerGramStat.getAvgFeePerGram()).to.be.equal(avg_fee);
expect(feePerGramStat.getMaxFeePerGram()).to.be.equal(max_fee);
}
);
46 changes: 46 additions & 0 deletions integration_tests/helpers/ffi/feePerGramStat.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// Copyright 2022 The Tari Project
// SPDX-License-Identifier: BSD-3-Clause

const InterfaceFFI = require("./ffiInterface");

class FeePerGramStat {
ptr;

pointerAssign(ptr) {
if (this.ptr) {
this.destroy();
this.ptr = ptr;
} else {
this.ptr = ptr;
}
}

getPtr() {
return this.ptr;
}

getOrder() {
return InterfaceFFI.feePerGramStatGetOrder(this.ptr);
}

getMinFeePerGram() {
return InterfaceFFI.feePerGramStatGetMinFeePerGram(this.ptr);
}

getAvgFeePerGram() {
return InterfaceFFI.feePerGramStatGetAvgFeePerGram(this.ptr);
}

getMaxFeePerGram() {
return InterfaceFFI.feePerGramStatGetMaxFeePerGram(this.ptr);
}

destroy() {
if (this.ptr) {
InterfaceFFI.feePerGramStatDestroy(this.ptr);
this.ptr = undefined; //prevent double free segfault
}
}
}

module.exports = FeePerGramStat;
32 changes: 32 additions & 0 deletions integration_tests/helpers/ffi/feePerGramStats.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// Copyright 2022 The Tari Project
// SPDX-License-Identifier: BSD-3-Clause

const FeePerGramStat = require("./FeePerGramStat");
const InterfaceFFI = require("./ffiInterface");

class FeePerGramStats {
ptr;

constructor(ptr) {
this.ptr = ptr;
}

getLength() {
return InterfaceFFI.feePerGramStatsGetLength(this.ptr);
}

getAt(position) {
let result = new FeePerGramStat();
result.pointerAssign(InterfaceFFI.feePerGramStatsGetAt(this.ptr, position));
return result;
}

destroy() {
if (this.ptr) {
InterfaceFFI.feePerGramStatsDestroy(this.ptr);
this.ptr = undefined; //prevent double free segfault
}
}
}

module.exports = FeePerGramStats;
Loading

0 comments on commit 9931dd2

Please sign in to comment.