Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
18985a6
feat(platform)!: introduce protocol version 15 with drive table v10 f…
DCG-Claude Sep 12, 2026
4069200
feat(drive)!: require fee history for storage refunds and credit thei…
DCG-Claude Sep 12, 2026
e3e4df7
test(drive): pin fee history handling of calculate_fee v0 and v1
DCG-Claude Sep 12, 2026
23c5ee4
test(drive): cover the recorded-owner refund credit primitive
DCG-Claude Sep 12, 2026
5e9991a
test(drive): close group actions through the production funnel with f…
DCG-Claude Sep 12, 2026
19b4370
test(drive): pin the protocol 12 schema strip as the recorded refund …
DCG-Claude Sep 12, 2026
852c78d
docs(book): describe fee history and refund ownership from protocol v…
DCG-Claude Sep 12, 2026
8f8d0ff
test(drive): pass the fee history where tests replace epoch-flagged d…
DCG-Claude Sep 12, 2026
b66ef7b
test(drive): name the per-owner refund fixtures for clippy
DCG-Claude Sep 12, 2026
977e6d1
fix(drive): report refund credits that repay identity debt for the pr…
DCG-Claude Sep 12, 2026
787dc39
docs(book): say lifecycle refund settlement is not yet wired at proto…
DCG-Claude Sep 12, 2026
9754db8
docs(book): attribute storage refunds to the recorded owner in the fe…
DCG-Claude Sep 12, 2026
ca6cd53
refactor(drive): read each refund owner once when crediting its balance
DCG-Claude Sep 12, 2026
7e0b768
test(drive): unban, unsuspend and replace suspensions through the pro…
DCG-Claude Sep 22, 2026
4763aa0
fix(drive): price ephemeral TTL bytes in calculate_fee v1 as v0 does
DCG-Claude Sep 22, 2026
8552817
test(drive): close the structure fixture's group action with fee hist…
DCG-Claude Sep 22, 2026
efa6e5a
fix(drive)!: price a batch before committing the transaction drive owns
DCG-Claude Sep 23, 2026
1f2a124
test(drive): assert a rejected refund pricing leaves state untouched …
DCG-Claude Sep 23, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 44 additions & 2 deletions book/src/fees/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,9 @@ in `FeeStorageVersion`:
| `storage_seek_cost` | 2,000 | Cost of a single disk seek |

Storage fees are **refundable**: when data is deleted, a portion of the original
storage fee is returned to the identity that paid it (see [Refunds](#refunds)
below).
storage fee becomes a refund for the owner recorded in the stored bytes' storage
flags, which is not always the identity that paid the fee (see
[Refunds](#refunds) below).

### Processing Fees

Expand Down Expand Up @@ -400,6 +401,38 @@ out to proposers.
There is a **dust limit**: refunds below 32 bytes worth of storage credits are
discarded to prevent micro-refund spam.

### Fee history and refund ownership (protocol version 15 onward)

A refund is priced with the fee history of the block that removes the bytes:
the `previous_fee_versions` map platform state carries, which the epoch change
hook extends whenever the fee version number changes. `Drive::calculate_fee`
v1 (`DRIVE_VERSION_V10`) consults that history for every owner-attributed
storage removal, on every fee version number, and returns an internal error
when a caller passes none. Earlier generations priced fee version number 1
against an empty history, so a caller that forgot the history silently
refunded at the first generation's storage rates; from protocol version 15
that omission halts instead of mispricing. Every shipped schedule shares fee
version number 1 and the same storage rates, so the credits themselves are
unchanged for every shipped input.

Refunds follow the recorded owner in the element's storage flags.
`Drive::credit_storage_refunds_to_owners_operations` credits each owner that
has a balance element without consulting any key or permission, so a frozen
but existing owner still receives its bookkeeping refund. Two shares of a
refund never reach a balance and are reported for the caller instead: the
part that clears an owner's negative credit (identity debt, which lives
outside the credit sum trees) and the part whose owner has no balance element
(the native stand-in for a wiped owner). The caller moves both into the
current epoch's processing pool with a single pool write and records every
refund against its storage epoch in the pending epoch refunds, so the credit
conservation check stays balanced. This primitive is the settlement step
block lifecycle paths that remove owner-attributed bytes are meant to use in
the block that removes them; at protocol version 15 the vote poll end cleanup
does not yet price or settle its refunds, and wiring it up is a separate
change. The protocol 12 schema migration, which shrank stored contracts
without refunding the stripped bytes, ran once at that activation and is the
recorded historical exception; it replays exactly as executed.

## Epoch-Based Fee Distribution

Fees do not go directly to the block proposer. Instead, they accumulate in
Expand Down Expand Up @@ -473,6 +506,15 @@ Fee versions are stored in the `FEE_VERSIONS` array and looked up by number. The
`uses_version_fee_multiplier_permille` field allows a global scaling factor
(permille = divide by 1000; a value of 1000 means no change).

`fee_version_number` keys the persisted fee history that refunds are priced
against. A schedule that changes storage rates needs a new number, because the
refund code resolves the schedule for an epoch through the history and (in
generations before protocol version 15) shortcut number 1 to the first
generation's rates. `FEE_VERSION1` and `FEE_VERSION2` share number 1 because
only a non-storage group changed between them; a test in `rs-drive`'s fee
operation module pins that every shipped schedule keeps the first generation's
storage rates.

## Key Source Files

| File | Contents |
Expand Down
70 changes: 35 additions & 35 deletions packages/rs-drive/grovedb-structure.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"schema_version": 1,
"latest_protocol_version": 14,
"latest_protocol_version": 15,
"element_kinds": [
{
"name": "Item",
Expand Down Expand Up @@ -5050,7 +5050,7 @@
},
"layer_shapes": {
"contract_groups": {
"origin": "genesis@14",
"origin": "genesis@15",
"tree": {
"hex": "00",
"right": {
Expand All @@ -5059,7 +5059,7 @@
}
},
"contract_groups.groups.group": {
"origin": "fixture contract_groups_and_bound_keys@14",
"origin": "fixture contract_groups_and_bound_keys@15",
"tree": {
"hex": "02",
"left": {
Expand All @@ -5074,7 +5074,7 @@
}
},
"contract_groups.members.contract": {
"origin": "fixture contract_groups_and_bound_keys@14",
"origin": "fixture contract_groups_and_bound_keys@15",
"tree": {
"hex": "01",
"left": {
Expand All @@ -5086,7 +5086,7 @@
}
},
"contracts.contract": {
"origin": "fixture contracts_with_documents@14",
"origin": "fixture contracts_with_documents@15",
"tree": {
"hex": "01",
"left": {
Expand All @@ -5098,7 +5098,7 @@
}
},
"contracts.contract.other": {
"origin": "fixture moderated_contract@14",
"origin": "fixture moderated_contract@15",
"tree": {
"hex": "80",
"left": {
Expand All @@ -5116,7 +5116,7 @@
}
},
"group_actions.contract.group": {
"origin": "fixture tokens_and_group_actions@14",
"origin": "fixture tokens_and_group_actions@15",
"tree": {
"hex": "4d",
"left": {
Expand All @@ -5128,7 +5128,7 @@
}
},
"group_actions.contract.group.active.action": {
"origin": "fixture tokens_and_group_actions@14",
"origin": "fixture tokens_and_group_actions@15",
"tree": {
"hex": "53",
"left": {
Expand All @@ -5137,7 +5137,7 @@
}
},
"group_actions.contract.group.closed.action": {
"origin": "fixture tokens_and_group_actions@14",
"origin": "fixture tokens_and_group_actions@15",
"tree": {
"hex": "53",
"left": {
Expand All @@ -5146,7 +5146,7 @@
}
},
"identities.identity": {
"origin": "fixture contract_groups_and_bound_keys@14",
"origin": "fixture contract_groups_and_bound_keys@15",
"tree": {
"hex": "80",
"left": {
Expand All @@ -5171,7 +5171,7 @@
"states": [
{
"state": "created",
"origin": "fixture identities@14",
"origin": "fixture identities@15",
"tree": {
"hex": "80",
"left": {
Expand All @@ -5190,7 +5190,7 @@
},
{
"state": "used_with_a_contract",
"origin": "fixture identities@14",
"origin": "fixture identities@15",
"tree": {
"hex": "80",
"left": {
Expand All @@ -5212,7 +5212,7 @@
},
{
"state": "budgeted_key_and_contract",
"origin": "fixture contract_groups_and_bound_keys@14",
"origin": "fixture contract_groups_and_bound_keys@15",
"tree": {
"hex": "80",
"left": {
Expand All @@ -5238,7 +5238,7 @@
]
},
"identities.identity.contract_info.bound": {
"origin": "fixture contract_groups_and_bound_keys@14",
"origin": "fixture contract_groups_and_bound_keys@15",
"tree": {
"hex": "01",
"left": {
Expand All @@ -5247,7 +5247,7 @@
}
},
"identities.identity.key_references": {
"origin": "fixture identities@14",
"origin": "fixture identities@15",
"tree": {
"hex": "03",
"left": {
Expand All @@ -5259,7 +5259,7 @@
}
},
"identities.identity.key_references.authentication": {
"origin": "fixture identities@14",
"origin": "fixture identities@15",
"tree": {
"hex": "02",
"left": {
Expand All @@ -5274,7 +5274,7 @@
}
},
"misc": {
"origin": "genesis@14",
"origin": "genesis@15",
"tree": {
"hex": "54",
"left": {
Expand All @@ -5283,7 +5283,7 @@
}
},
"pools.epoch": {
"origin": "fixture current_epoch@14",
"origin": "fixture current_epoch@15",
"tree": {
"hex": "6d",
"left": {
Expand Down Expand Up @@ -5311,14 +5311,14 @@
"states": [
{
"state": "future",
"origin": "fixture current_epoch@14",
"origin": "fixture current_epoch@15",
"tree": {
"hex": "73"
}
},
{
"state": "running",
"origin": "fixture current_epoch@14",
"origin": "fixture current_epoch@15",
"tree": {
"hex": "6d",
"left": {
Expand Down Expand Up @@ -5346,7 +5346,7 @@
},
{
"state": "paid",
"origin": "fixture paid_epoch@14",
"origin": "fixture paid_epoch@15",
"tree": {
"hex": "74",
"left": {
Expand All @@ -5369,7 +5369,7 @@
]
},
"prefunded_balances": {
"origin": "genesis@14",
"origin": "genesis@15",
"tree": {
"hex": "80",
"left": {
Expand All @@ -5381,7 +5381,7 @@
}
},
"root": {
"origin": "genesis@14",
"origin": "genesis@15",
"tree": {
"hex": "40",
"left": {
Expand Down Expand Up @@ -5438,7 +5438,7 @@
}
},
"saved_block_transactions": {
"origin": "genesis@14",
"origin": "genesis@15",
"tree": {
"hex": "65",
"left": {
Expand All @@ -5450,7 +5450,7 @@
}
},
"shielded_balances.main_pool": {
"origin": "genesis@14",
"origin": "genesis@15",
"tree": {
"hex": "80",
"left": {
Expand All @@ -5468,7 +5468,7 @@
}
},
"tokens": {
"origin": "genesis@14",
"origin": "genesis@15",
"tree": {
"hex": "80",
"left": {
Expand All @@ -5489,7 +5489,7 @@
}
},
"tokens.distributions": {
"origin": "genesis@14",
"origin": "genesis@15",
"tree": {
"hex": "80",
"left": {
Expand All @@ -5504,7 +5504,7 @@
}
},
"tokens.distributions.perpetual.token": {
"origin": "fixture token_distributions_unclaimed@14",
"origin": "fixture token_distributions_unclaimed@15",
"tree": {
"hex": "c0",
"left": {
Expand All @@ -5513,7 +5513,7 @@
}
},
"tokens.distributions.timed": {
"origin": "genesis@14",
"origin": "genesis@15",
"tree": {
"hex": "80",
"left": {
Expand All @@ -5525,7 +5525,7 @@
}
},
"versions": {
"origin": "genesis@14",
"origin": "genesis@15",
"tree": {
"hex": "01",
"left": {
Expand All @@ -5534,7 +5534,7 @@
}
},
"votes": {
"origin": "genesis@14",
"origin": "genesis@15",
"tree": {
"hex": "64",
"left": {
Expand All @@ -5546,7 +5546,7 @@
}
},
"votes.contested_resource": {
"origin": "genesis@14",
"origin": "genesis@15",
"tree": {
"hex": "70",
"left": {
Expand All @@ -5555,7 +5555,7 @@
}
},
"votes.contested_resource.active_polls.contract.document_type": {
"origin": "fixture contested_documents@14",
"origin": "fixture contested_documents@15",
"tree": {
"hex": "01",
"left": {
Expand All @@ -5564,7 +5564,7 @@
}
},
"votes.contested_resource.active_polls.contract.document_type.indexes.value.contender": {
"origin": "fixture contested_documents@14",
"origin": "fixture contested_documents@15",
"tree": {
"hex": "01",
"left": {
Expand All @@ -5573,7 +5573,7 @@
}
},
"withdrawals": {
"origin": "genesis@14",
"origin": "genesis@15",
"tree": {
"hex": "03",
"left": {
Expand Down
Loading
Loading