Skip to content
  •  
  •  
  •  
16 changes: 9 additions & 7 deletions book/src/contributing/coding-conventions.md
Original file line number Diff line number Diff line change
Expand Up @@ -174,11 +174,14 @@ already owns a new schedule, amend that schedule instead of creating another
generation nobody references.

`fee_version_number` is a separate concept from the schedule generation: it
keys persisted fee history and storage-refund behaviour, and it is stored in
platform state. Inspect its consumers before changing it. `FEE_VERSION1` and
`FEE_VERSION2` both carry `fee_version_number: 1` because storage rates did not
change between them, only a fee group did. See the
[Fee System Overview](../fees/overview.md).
keys the fee history saved in platform state. Inspect its consumers before
changing it. `FEE_VERSION1` and `FEE_VERSION2` both carry
`fee_version_number: 1` because storage rates did not change between them, only
a fee group did.

A schedule can change the storage rate without breaking refunds: from protocol
version 15 a refund is priced at the rate of the protocol version that ran the
epoch that stored the bytes. See the [Fee System Overview](../fees/overview.md).

### Consensus code reads the version from state

Expand Down Expand Up @@ -225,8 +228,7 @@ The observed order in `drive` and `drive-abci` is: `&self`, domain inputs,
`block_info`, mode flags (`apply`, `stateful`), the estimation map,
`transaction`, the operations accumulator, `platform_version`. A new parameter
goes up with the context items, never after `platform_version`. The same holds
for fields on context structs such as parse contexts. The known exception is
`previous_fee_versions`, which trails it on fee-returning methods.
for fields on context structs such as parse contexts.

Why: readers of a few hundred versioned signatures expect the closing
parameter to be the version. Appending after it breaks that scan.
Expand Down
2 changes: 1 addition & 1 deletion book/src/data-model/key-limits.md
Original file line number Diff line number Diff line change
Expand Up @@ -358,7 +358,7 @@ sequenceDiagram

```rust
let ExecutionEvent::Paid { signing_key_limits: Some(signing_key_limits), .. } = event else {
return self.validate_fees_of_event_v0(event, block_info, transaction, platform_version, previous_fee_versions);
return self.validate_fees_of_event_v0(event, block_info, transaction, platform_version);
};
```

Expand Down
5 changes: 1 addition & 4 deletions book/src/drive/batch-operations.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,6 @@ impl Drive {
block_info: &BlockInfo,
transaction: TransactionArg,
platform_version: &PlatformVersion,
previous_fee_versions: Option<&CachedEpochIndexFeeVersions>,
) -> Result<FeeResult, Error> {
if operations.is_empty() {
return Ok(FeeResult::default());
Expand Down Expand Up @@ -147,13 +146,11 @@ impl Drive {
}

// Calculate total fee from accumulated costs
Drive::calculate_fee(
self.calculate_fee(
None,
Some(cost_operations),
&block_info.epoch,
self.config.epochs_per_era,
platform_version,
previous_fee_versions,
)
}
}
Expand Down
9 changes: 7 additions & 2 deletions book/src/drive/cost-tracking.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,6 @@ pub fn consume_to_fees_v0(
epoch: &Epoch,
epochs_per_era: u16,
fee_version: &FeeVersion,
previous_fee_versions: Option<&CachedEpochIndexFeeVersions>,
) -> Result<Vec<FeeResult>, Error> {
drive_operations.into_iter().map(|operation| match operation {
PreCalculatedFeeResult(f) => Ok(f),
Expand Down Expand Up @@ -180,7 +179,7 @@ pub fn consume_to_fees_v0(
removal_per_epoch_by_identifier,
epoch.index,
epochs_per_era,
previous_fee_versions,
|_| Ok(fee_version.storage.storage_disk_usage_credit_per_byte),
)?, system_amount)
}
};
Expand All @@ -203,6 +202,12 @@ Each operation produces a `FeeResult` with four components:
- **`fee_refunds`**: Credits returned because previously-stored data was removed.
- **`removed_bytes_from_system`**: Bytes removed that were stored by the system (not any particular identity), so no refund is issued.

Generation 0 prices every refund at the current block's storage rate. From protocol version 15,
`consume_to_fees_v1` hands generation 0 each sectioned removal as a basic removal of as many
bytes, so everything else is priced the same, and then prices the refunds itself at the storage
rate of the protocol version that ran each storing epoch. `Drive::calculate_fee` reads that
version from the epoch's tree, so it is a method on `Drive`.

## Ephemeral Cost Calculation

The `ephemeral_cost` method on `OperationCost` computes the processing fee from the raw operation metrics:
Expand Down
3 changes: 1 addition & 2 deletions book/src/drive/finalize-tasks.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,6 @@ pub(crate) fn apply_drive_operations_v0(
block_info: &BlockInfo,
transaction: TransactionArg,
platform_version: &PlatformVersion,
previous_fee_versions: Option<&CachedEpochIndexFeeVersions>,
) -> Result<FeeResult, Error> {
// ...

Expand Down Expand Up @@ -150,7 +149,7 @@ pub(crate) fn apply_drive_operations_v0(
}

// Step 5: Calculate fees
Drive::calculate_fee(/* ... */)
self.calculate_fee(/* ... */)
}
```

Expand Down
8 changes: 8 additions & 0 deletions book/src/fees/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -482,6 +482,14 @@ have elapsed since the data was stored — the longer the data has been stored,
smaller the refund, because more of the distributed fees have already been paid
out to proposers.

From protocol version 15 a refund is priced at the storage rate the bytes were
paid at. The storage flags say which epoch stored them, the first block of every
epoch records the epoch's protocol version in its epoch tree, and that version's
fee schedule has the rate, so `Drive::calculate_fee` reads it from there. Bytes
stored in the current epoch use the current block's rate, which is the same
schedule. Earlier protocol versions price every refund at the current block's
rate. Every schedule so far has the same storage rate, so the two agree.

There is a **dust limit**: refunds below 32 bytes worth of storage credits are
discarded to prevent micro-refund spam.

Expand Down
10 changes: 3 additions & 7 deletions book/src/state-transitions/drive-operations.md
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,6 @@ pub(crate) fn apply_drive_operations_v0(
block_info: &BlockInfo,
transaction: TransactionArg,
platform_version: &PlatformVersion,
previous_fee_versions: Option<&CachedEpochIndexFeeVersions>,
) -> Result<FeeResult, Error> {
if operations.is_empty() {
return Ok(FeeResult::default());
Expand Down Expand Up @@ -239,9 +238,8 @@ pub(crate) fn apply_drive_operations_v0(
task.execute(self, platform_version);
}

Drive::calculate_fee(
None, Some(cost_operations), &block_info.epoch,
self.config.epochs_per_era, platform_version, previous_fee_versions,
self.calculate_fee(
None, Some(cost_operations), &block_info.epoch, platform_version,
)
}
```
Expand Down Expand Up @@ -303,13 +301,11 @@ At the end of `apply_drive_operations`, fees are calculated from the accumulated
cost operations:

```rust
Drive::calculate_fee(
self.calculate_fee(
None,
Some(cost_operations),
&block_info.epoch,
self.config.epochs_per_era,
platform_version,
previous_fee_versions,
)
```

Expand Down
5 changes: 2 additions & 3 deletions book/src/versioning/versioned-dispatch.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,6 @@ impl Drive {
apply: bool,
transaction: TransactionArg,
platform_version: &PlatformVersion,
previous_fee_versions: Option<&CachedEpochIndexFeeVersions>,
) -> Result<FeeResult, Error> {
match platform_version
.drive
Expand All @@ -91,11 +90,11 @@ impl Drive {
{
0 => self.update_contract_v0(
contract, block_info, apply,
transaction, platform_version, previous_fee_versions,
transaction, platform_version,
),
1 => self.update_contract_v1(
contract, block_info, apply,
transaction, platform_version, previous_fee_versions,
transaction, platform_version,
),
version => Err(Error::Drive(DriveError::UnknownVersionMismatch {
method: "update_contract".to_string(),
Expand Down
144 changes: 1 addition & 143 deletions packages/rs-dpp/src/fee/default_costs/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@
//! Fee costs for Known Platform operations
//!

use crate::block::epoch::{Epoch, EpochIndex};
use crate::fee::Credits;
use crate::block::epoch::EpochIndex;
use platform_version::version::fee::{
FeeVersion, FeeVersionFieldsBeforeVersion4, FeeVersionNumber,
};
Expand All @@ -21,144 +20,3 @@ pub type EpochIndexFeeVersionsForStorage = BTreeMap<EpochIndex, FeeVersionNumber
// This FeeProcessingVersionFieldsBeforeVersion4 is how things were before version 1.4 was released
pub type CachedEpochIndexFeeVersionsFieldsBeforeVersion4 =
BTreeMap<EpochIndex, FeeVersionFieldsBeforeVersion4>;

/// A Known Cost Item is an item that changes costs depending on the Epoch
#[derive(Eq, PartialEq, Copy, Clone, Hash)]
pub enum KnownCostItem {
/// The storage cost used when writing bytes
StorageDiskUsageCreditPerByte,
/// The processing cost used when writing bytes
StorageProcessingCreditPerByte,
/// The processing cost used when loading bytes from storage
StorageLoadCreditPerByte,
/// The processing cost used when loading bytes not from storage
NonStorageLoadCreditPerByte,
/// The cost used when performing a disk seek
StorageSeekCost,
// The following are set costs of routine operations
/// The cost for fetching an identity balance
FetchIdentityBalanceProcessingCost,
/// The cost for fetching an identity key
FetchSingleIdentityKeyProcessingCost,
/// The cost for a Single SHA256 operation, with a specific size
SingleSHA256(usize),
/// The cost for a Blake3 operation, with a specific size
Blake3(usize),
/// The cost for a EcdsaSecp256k1 signature verification
VerifySignatureEcdsaSecp256k1,
/// The cost for a BLS12_381 signature verification
VerifySignatureBLS12_381,
/// The cost for a EcdsaHash160 signature verification
VerifySignatureEcdsaHash160,
/// The cost for a Bip13ScriptHash signature verification
VerifySignatureBip13ScriptHash,
/// The cost for a Eddsa25519Hash160 signature verification
VerifySignatureEddsa25519Hash160,
}

impl KnownCostItem {
#[inline]
pub fn lookup_cost(&self, fee_version: &FeeVersion) -> Credits {
match self {
KnownCostItem::StorageDiskUsageCreditPerByte => {
fee_version.storage.storage_disk_usage_credit_per_byte
}
KnownCostItem::StorageProcessingCreditPerByte => {
fee_version.storage.storage_processing_credit_per_byte
}
KnownCostItem::StorageLoadCreditPerByte => {
fee_version.storage.storage_load_credit_per_byte
}
KnownCostItem::NonStorageLoadCreditPerByte => {
fee_version.storage.non_storage_load_credit_per_byte
}
KnownCostItem::StorageSeekCost => fee_version.storage.storage_seek_cost,
KnownCostItem::FetchIdentityBalanceProcessingCost => {
fee_version
.processing
.fetch_identity_balance_processing_cost
}
KnownCostItem::FetchSingleIdentityKeyProcessingCost => {
fee_version
.processing
.fetch_single_identity_key_processing_cost
}
KnownCostItem::Blake3(size) => {
fee_version.hashing.blake3_base
+ fee_version.hashing.blake3_per_block * *size as u64
}
KnownCostItem::SingleSHA256(size) => {
fee_version.hashing.single_sha256_base
+ fee_version.hashing.sha256_per_block * *size as u64
}
KnownCostItem::VerifySignatureEcdsaSecp256k1 => {
fee_version.signature.verify_signature_ecdsa_secp256k1
}
KnownCostItem::VerifySignatureBLS12_381 => {
fee_version.signature.verify_signature_bls12_381
}
KnownCostItem::VerifySignatureEcdsaHash160 => {
fee_version.signature.verify_signature_ecdsa_hash160
}
KnownCostItem::VerifySignatureBip13ScriptHash => {
fee_version.signature.verify_signature_bip13_script_hash
}
KnownCostItem::VerifySignatureEddsa25519Hash160 => {
fee_version.signature.verify_signature_eddsa25519_hash160
}
}
}

pub fn lookup_cost_on_epoch<T: EpochCosts>(
&self,
epoch: &T,
cached_fee_version: &CachedEpochIndexFeeVersions,
) -> Credits {
let version = epoch.active_fee_version(cached_fee_version);
self.lookup_cost(version)
}
}

/// Costs for Epochs
pub trait EpochCosts {
/// Get the closest epoch in the past that has a cost table
/// This is where the base costs last changed
fn active_fee_version(
&self,
cached_fee_version: &CachedEpochIndexFeeVersions,
) -> &'static FeeVersion;
/// Get the cost for the known cost item
fn cost_for_known_cost_item(
&self,
cached_fee_version: &CachedEpochIndexFeeVersions,
cost_item: KnownCostItem,
) -> Credits;
}

impl EpochCosts for Epoch {
/// Get the active fee version for an epoch
fn active_fee_version(
&self,
cached_fee_version: &CachedEpochIndexFeeVersions,
) -> &'static FeeVersion {
// If the exact EpochIndex is matching to a FeeVersion update
if let Some(fee_version) = cached_fee_version.get(&self.index) {
return fee_version;
}
// else return the FeeVersion at lower adjacent EpochIndex (if available, else the FeeVersion of first PlatformVersion)
cached_fee_version
.range(..=self.index)
.next_back()
.map(|(_, fee_version)| *fee_version)
.unwrap_or_else(|| FeeVersion::first())
}

/// Get the cost for the known cost item
fn cost_for_known_cost_item(
&self,
cached_fee_version: &CachedEpochIndexFeeVersions,
cost_item: KnownCostItem,
) -> Credits {
cost_item.lookup_cost_on_epoch(self, cached_fee_version)
}
}
Loading
Loading