Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(royalties): make royalties payment to be 15% of the total storage cost #967

Merged
merged 1 commit into from
Nov 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
26 changes: 16 additions & 10 deletions sn_cli/src/subcommands/files/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -240,15 +240,9 @@ async fn upload_files(
}

progress_bar.finish_and_clear();
let elapsed = now.elapsed();
println!(
"Uploaded {unpaid_chunks_to_upload_len} chunks in {}",
format_elapsed_time(elapsed)
);
info!(
"Uploaded {unpaid_chunks_to_upload_len} chunks in {}",
format_elapsed_time(elapsed)
);
let elapsed = format_elapsed_time(now.elapsed());
println!("Uploaded {unpaid_chunks_to_upload_len} chunks in {elapsed}");
info!("Uploaded {unpaid_chunks_to_upload_len} chunks in {elapsed}");
println!("**************************************");
println!("* Payment Details *");
println!("**************************************");
Expand Down Expand Up @@ -483,8 +477,20 @@ async fn verify_and_repay_if_needed_once(
}))
.await
{
Ok(_new_cost) => {
Ok((storage_cost, royalties_fees)) => {
chunk_manager.mark_paid(failed_chunks_batch.iter().map(|(addr, _path)| *addr));
if !storage_cost.is_zero() {
println!(
"Made new payment of {storage_cost} for {} chunks",
failed_chunks_batch.len()
);
}
if !royalties_fees.is_zero() {
println!("Made new payment of {royalties_fees} for royalties fees");
}
if !royalties_fees.is_zero() || !storage_cost.is_zero() {
println!("New wallet balance: {}", wallet.balance());
}
}
Err(error) => {
error!("Failed to repay for record storage: {failed_chunks_batch:?}: {error:?}");
Expand Down
28 changes: 14 additions & 14 deletions sn_node/src/put_validation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,8 @@ use sn_protocol::{
};
use sn_registers::SignedRegister;
use sn_transfers::{
is_genesis_parent_tx, CashNoteRedemption, LocalWallet, Transfer, GENESIS_CASHNOTE,
NETWORK_ROYALTIES_PK,
};
use sn_transfers::{
CashNote, NanoTokens, SignedSpend, UniquePubkey, NETWORK_ROYALTIES_AMOUNT_PER_ADDR,
calculate_royalties_fee, is_genesis_parent_tx, CashNote, CashNoteRedemption, LocalWallet,
NanoTokens, SignedSpend, Transfer, UniquePubkey, GENESIS_CASHNOTE, NETWORK_ROYALTIES_PK,
};
use std::collections::{BTreeSet, HashSet};
use xor_name::XorName;
Expand Down Expand Up @@ -549,17 +546,20 @@ impl Node {
warn!("Failed to get serialized_size for network royalties payment data to publish a notification over gossipsub for record {pretty_key}");
}

// check payment is sufficient both for our store cost and for network royalties
let expected_fee = self
.network
.get_local_storecost()
.await
.map_err(|e| ProtocolError::RecordNotStored(pretty_key.clone(), format!("{e:?}")))?
.checked_add(NETWORK_ROYALTIES_AMOUNT_PER_ADDR)
.ok_or(ProtocolError::RecordNotStored(
// Let's check payment is sufficient both for our store cost and for network royalties
let local_storecost =
self.network.get_local_storecost().await.map_err(|e| {
ProtocolError::RecordNotStored(pretty_key.clone(), format!("{e:?}"))
})?;
// Since the storage payment is made to a single node, we can calculate the royalties fee based on that single payment.
let expected_royalties_fee = calculate_royalties_fee(local_storecost);
let expected_fee = local_storecost.checked_add(expected_royalties_fee).ok_or(
ProtocolError::RecordNotStored(
pretty_key.clone(),
"CashNote value overflow".to_string(),
))?;
),
)?;

// finally, (after we accept any payments to us as they are ours now anyway)
// lets check they actually paid enough
if received_fee < expected_fee {
Expand Down
8 changes: 2 additions & 6 deletions sn_node/tests/nodes_rewards.rs
Original file line number Diff line number Diff line change
Expand Up @@ -219,16 +219,15 @@ async fn nodes_rewards_transfer_notifs_filter() -> Result<()> {
println!("Random chunks stored, paid {storage_cost}/{royalties_fees}");

let count_1 = handle_1.await??;
let expected = royalties_fees.as_nano() as usize;
println!("Number of notifications received by node #1: {count_1}");
let count_2 = handle_2.await??;
println!("Number of notifications received by node #2: {count_2}");
let count_3 = handle_3.await??;
println!("Number of notifications received by node #3: {count_3}");

assert!(
count_1 >= expected,
"expected: {expected:?}, received {count_1:?}... Not enough notifications received"
count_1 >= num_of_chunks,
"expected: {num_of_chunks:?}, received {count_1:?}... Not enough notifications received"
);
assert_eq!(count_2, 0, "Notifications were not expected");
assert_eq!(count_3, 0, "Notifications were not expected");
Expand Down Expand Up @@ -311,9 +310,6 @@ fn spawn_royalties_payment_listener(
println!("Transfer notif received for key {key:?}");
if key == royalties_pk {
count += 1;
// if count == 1 {
// break;
// }
}
}
Ok(_) => { /* ignored */ }
Expand Down
9 changes: 7 additions & 2 deletions sn_transfers/src/genesis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,13 @@ use thiserror::Error;
/// thus creating a total of 1,288,490,189,000,000,000 available units.
pub(super) const GENESIS_CASHNOTE_AMOUNT: u64 = (0.3 * TOTAL_SUPPLY as f64) as u64;

/// Expected amount to be paid as network royalties for each record to be stored.
pub const NETWORK_ROYALTIES_AMOUNT_PER_ADDR: NanoTokens = NanoTokens::from(1);
/// Based on the given store cost, it calculates what's the expected amount to be paid as network royalties.
/// Network royalties fee is expected to be 15% of the payment amount, i.e. 85% of store cost + 15% royalties fees.
pub fn calculate_royalties_fee(store_cost: NanoTokens) -> NanoTokens {
let fees_amount = (store_cost.as_nano() as f64 * 0.15) / 0.85;
// we round down the calculated amount
NanoTokens::from(fees_amount as u64)
}

/// A specialised `Result` type for genesis crate.
pub(super) type GenesisResult<T> = Result<T, Error>;
Expand Down
9 changes: 3 additions & 6 deletions sn_transfers/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,9 @@ pub use transfers::{CashNoteRedemption, OfflineTransfer, Transfer};

/// Utilities exposed
pub use genesis::{
create_faucet_wallet, create_first_cash_note_from_key, is_genesis_parent_tx,
load_genesis_wallet,
};
pub use genesis::{
Error as GenesisError, GENESIS_CASHNOTE, GENESIS_CASHNOTE_SK,
NETWORK_ROYALTIES_AMOUNT_PER_ADDR, NETWORK_ROYALTIES_PK,
calculate_royalties_fee, create_faucet_wallet, create_first_cash_note_from_key,
is_genesis_parent_tx, load_genesis_wallet, Error as GenesisError, GENESIS_CASHNOTE,
GENESIS_CASHNOTE_SK, NETWORK_ROYALTIES_PK,
};
pub use transfers::create_offline_transfer;
pub use wallet::{bls_secret_from_hex, parse_main_pubkey};
Expand Down
40 changes: 23 additions & 17 deletions sn_transfers/src/wallet/local_store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,11 @@ use super::{
};

use crate::{
calculate_royalties_fee,
transfers::{create_offline_transfer, ContentPaymentsIdMap, OfflineTransfer, PaymentDetails},
CashNote, CashNoteRedemption, DerivationIndex, DerivedSecretKey, Hash, MainPubkey,
MainSecretKey, NanoTokens, SignedSpend, Transfer, UniquePubkey, WalletError,
NETWORK_ROYALTIES_AMOUNT_PER_ADDR, NETWORK_ROYALTIES_PK,
NETWORK_ROYALTIES_PK,
};
use xor_name::XorName;

Expand Down Expand Up @@ -308,27 +309,16 @@ impl LocalWallet {
let mut all_payees_only = vec![];
let mut rng = &mut rand::thread_rng();

// we currently pay 1 nano per address as network royalties.
let royalties_pk = *crate::NETWORK_ROYALTIES_PK;

let mut storage_cost = NanoTokens::zero();
let mut royalties_fees = NanoTokens::zero();

for (_content_addr, payees) in all_data_payments.iter_mut() {
// add network royalties payment as payee for each address being payed
payees.push((royalties_pk, NETWORK_ROYALTIES_AMOUNT_PER_ADDR));

let mut cost = NanoTokens::zero();
let mut unique_key_vec = Vec::<(NanoTokens, MainPubkey, [u8; 32])>::new();
for (address, amount) in payees.clone().into_iter() {
if address == *NETWORK_ROYALTIES_PK {
royalties_fees = royalties_fees
.checked_add(amount)
.ok_or(WalletError::TotalPriceTooHigh)?;
} else {
storage_cost = storage_cost
.checked_add(amount)
.ok_or(WalletError::TotalPriceTooHigh)?;
}
cost = cost
.checked_add(amount)
.ok_or(WalletError::TotalPriceTooHigh)?;

unique_key_vec.push((
amount,
Expand All @@ -337,7 +327,23 @@ impl LocalWallet {
));
}

// add network royalties payment as payee as well
let royalties = calculate_royalties_fee(cost);
payees.push((*NETWORK_ROYALTIES_PK, royalties));
unique_key_vec.push((
royalties,
*NETWORK_ROYALTIES_PK,
UniquePubkey::random_derivation_index(&mut rng),
));

all_payees_only.extend(unique_key_vec);

storage_cost = storage_cost
.checked_add(cost)
.ok_or(WalletError::TotalPriceTooHigh)?;
royalties_fees = royalties_fees
.checked_add(royalties)
.ok_or(WalletError::TotalPriceTooHigh)?;
}

let reason_hash = reason_hash.unwrap_or_default();
Expand Down Expand Up @@ -372,7 +378,7 @@ impl LocalWallet {
let value = cash_note.value();

// diffentiate between network royalties and storage payments
if cash_note.main_pubkey == *crate::NETWORK_ROYALTIES_PK {
if cash_note.main_pubkey == *NETWORK_ROYALTIES_PK {
trace!("Created netowrk royalties transaction regarding {content_addr:?} paying {value:?}(origin {token:?}) to payee {payee:?}.");
cash_notes_for_content.push((
Transfer::royalties_transfers_from_cash_note(cash_note.to_owned())?,
Expand Down