Skip to content

filestorage: couple storage nodes to staking identity + reserve collateral#452

Open
adamkrellenstein wants to merge 1 commit into
feat/storage-economicsfrom
feat/storage-staking-coupling
Open

filestorage: couple storage nodes to staking identity + reserve collateral#452
adamkrellenstein wants to merge 1 commit into
feat/storage-economicsfrom
feat/storage-staking-coupling

Conversation

@adamkrellenstein

@adamkrellenstein adamkrellenstein commented Jun 2, 2026

Copy link
Copy Markdown
Contributor

Stacked on #441 (storage economics). Makes the storage-slash mechanism real: until now λ_slash, k_f, and get_failed_challenges() existed but a challenge's prover_id was a free-form node_id with no authenticated link to a slashable stake — so nothing could actually be slashed. This adds the contract-side coupling.

What it does

  • Authenticated identity binding. join_agreement binds each membership to the joining signer's staking identity (x-only pubkey / Holder), stored in AgreementNodes.owners. A failed challenge's prover_id (= node_id) now resolves to a stakeable identity. leave_agreement verifies the leaver owns the membership.
  • Per-identity collateral reservation. node_reservations tracks Σ k_f over an identity's memberships — incremented on join, released on leave — so a node's storage commitments are accounted against one bond.
  • Views get_node_owner / get_node_reservation expose both for the slasher.
  • Lite test for the owner binding + reservation accumulate/release.

Design decisions

  1. Unified collateral pool. Storage collateral is consensus stake — storage providers are validators, and a node's stake both backs its storage obligations and counts as its consensus voting power. There is no separate storage bond.

  2. Solvency check + slash are reactor-side, not in the contract. The join-time solvency check (staking stake ≥ reservation) and the per-block slash (get_failed_challenges → owner → staking::slash(λ_slash·k_f)) are deferred to the reactor (core-context), where the slash firing already lives. Why: both require filestorage to read staking state / call staking, and there is no native-contract→native-contract view-call precedent in the codebase — every existing cross-contract call targets a proc/core function (token::transfer, nft→filestorage::create_agreement); file_registry is a host builtin, not a contract. Rather than pioneer an unvalidated cross-contract view path inside a user-facing join, this contract owns the per-identity accounting, and the reactor owns the cross-contract enforcement (it can read both staking stake and these reservations each block). If/when native→native view calls are validated, the solvency check can move into join directly.

  3. node_id stays a label, bound to the Holder (rather than node_id == pubkey). This keeps one operator able to run multiple labeled nodes under one stake (each reserving its own k_f), and avoids breaking the existing multi-node-from-one-signer test/usage pattern, while still authenticating the binding.

Scope / follow-ups


Note

Medium Risk
Join/leave semantics and economic accounting change (identity binding and collateral reservations); actual stake checks and slashing remain reactor-side, but mistakes here would break slash resolution or over-commit storage without on-chain solvency in join.

Overview
Adds the contract-side link between storage node_id labels and slashable staking identities, so a failed PoR challenge’s prover_id can resolve to an owner before reactor-side staking::slash.

On join_agreement, each membership is bound to the joining signer’s x-only pubkey (AgreementNodes.owners), and that agreement’s per-node k_f is added to node_reservations (Σ k_f per identity). leave_agreement now requires the same identity that joined, and releases that membership’s k_f from the reservation (alongside existing leave-fee / replication rules).

New views get_node_owner and get_node_reservation (WIT + impl) expose the mapping and committed collateral for Phase 2 slashing/solvency wiring in the reactor—not enforced inside this contract yet.

A regtest filestorage_node_staking_coupling test checks owner binding, reservation growth across two nodes, and release on leave.

Reviewed by Cursor Bugbot for commit 3e8892d. Bugbot is set up for automated code reviews on this repo. Configure here.


Merge order: #452 and #456 both modify core/indexer/tests/contracts/file_storage_tests/native_filestorage_contract.rs. Merge this impl PR (#452) before the test PR #456; #456 rebases on top afterward.

…teral

The storage-slash mechanism (λ_slash, k_f, get_failed_challenges) was inert: a
challenge's prover_id was a free-form node_id with no authenticated link to a
slashable stake. Add the contract-side coupling:

- join_agreement binds each membership to the joining signer's staking identity
  (x-only pubkey / Holder), stored in AgreementNodes.owners. A failed challenge's
  prover_id now resolves to a stakeable identity. leave verifies the leaver owns
  the membership.
- Per-identity collateral reservation: node_reservations tracks Σ k_f over an
  identity's memberships, incremented on join, released on leave, so a node's
  storage commitments are accounted against one bond.
- Views get_node_owner / get_node_reservation expose both for the slasher.

Design decisions:
- Unified collateral pool: storage providers are validators; their stake backs
  storage obligations and counts as consensus voting power. No separate storage
  bond.
- The solvency check (staking stake ≥ reservation) and the per-block slash
  (get_failed_challenges → owner → staking::slash) are deferred to the reactor
  (core-context), where slash firing already lives, because both require reading
  staking state / calling staking from filestorage and there is NO
  native-contract→native-contract view-call precedent in the codebase. The
  contract owns the per-identity accounting; the reactor owns cross-contract
  enforcement.

Adds a lite test for the owner binding + reservation accumulate/release.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 3e8892d. Configure here.

.node_reservations()
.set(&owner_str, reserved.add(k_f)?);
nodes_state.owners().set(&node_id, owner_str);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Owner Overwrite Causes Wrong Identity Slashed

Medium Severity

leave_agreement never clears owners[node_id], and join_agreement unconditionally overwrites it with the new joiner's identity at line 328. The active-membership guard (line 296) only blocks re-join if the node is currently active, so after a departure any other identity can claim the same node_id label, silently replacing the owners entry. Any pending failed challenges from the previous owner's tenure then resolve through get_node_owner to the new identity, causing the Phase 2 slasher to slash the wrong staker for failures it had no part in.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 3e8892d. Configure here.

@adamkrellenstein adamkrellenstein added enhancement New capability or improvement area: storage Filestorage / PoR area: economics Token / staking / storage economics labels Jun 3, 2026
@adamkrellenstein

Copy link
Copy Markdown
Contributor Author

Heads-up for whenever this lands: it's built on the filestorage contract surface from before #483 (in-contract registry on append-only kontor-crypto 0.3.0). Once #483 merges, this branch needs a rebase onto the updated filestorage contract (the file-registry / verify-proof WIT and the slot-bearing agreement record changed).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: economics Token / staking / storage economics area: storage Filestorage / PoR enhancement New capability or improvement

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant