PotDrive
Open Drive

Concepts

TransactionStorage

The Portaldot V3 pallet that holds PotDrive's data: how it indexes bytes, how long it keeps them, and how block producers prove they still have them.

The calls

CallWhat it does
store(data)Charges a fee and asks the node to index data under blake2_256(data). Max 8 MiB.
renew(block, index)Re-indexes data stored at (block, index) for another retention period. Same fee as storing.
checkProof(proof)Inherent included by the block author: proof of a random chunk of the data stored a retention period ago.

On Portaldot V3 the pallet is the fee-based variant: there is no authorize_account step, so any funded account can store. We verified this against the live runtime (specVersion 3002): the call list is exactly store, renew and checkProof, and Authorizations is empty.

How data is indexed

store does not write your bytes into runtime state. It records a TransactionInfo (content hash, size, chunk root) under the current block, and emits an index operation telling the node's database to keep the extrinsic's data addressable by its blake2b-256 hash. The node later serves it through bitswap_v1_get(cid), which accepts any CIDv1 whose multihash is that digest.

Why blocks cannot share a batch

The database keeps one index entry per extrinsic and stores the last size bytes of the encoded extrinsic (sc-client-db, apply_index_ops):

substrate/client/db/src/lib.rs (abridged)
IndexOperation::Insert { extrinsic, hash, size } => {
    index_map.insert(extrinsic, (hash, size));   // keyed by extrinsic index
}
// …
let offset = encoded.len() - size;
transaction.store(columns::TRANSACTION, hash, encoded[offset..].to_vec());

Two store calls inside one utility.batchAll would overwrite each other's index entry, and the indexed bytes would not be the data. So every block is its own extrinsic. PotDrive keeps that to one wallet prompt with an upload key.

Retention

Data is kept for RetentionPeriod = 100,800 blocks, about 7 days at 6-second blocks. When the period ends, the pallet drops the TransactionInfo and validators stop having to hold the data. renew moves it into the renewal block, starting a new period. Anyone can renew any data. PotDrive's keeper does it automatically for prepaid files.

You can read what is held at any moment from chain state. Each key of TransactionStorage.Transactions is a block number, and its value lists that block's stored items. Explore renders exactly this.

Storage proofs

In block N, the author must include checkProof for the data stored at block N − RetentionPeriod. The runtime picks a random 256-byte chunk using on-chain randomness, and the author has to supply that chunk with a trie proof against the stored chunk root. A block with a missing or invalid proof is invalid. Validators that drop data cannot keep producing blocks.

Fees

store pays the pallet's own byte and entry fees (ByteFee = 10, EntryFee = 1000 planck, negligible) plus the normal transaction fee. On V3 the length component dominates. Measured with paymentInfo on 17 October 2026:

PayloadFee
1 KiB0.1135 tPOT
256 KiB26.23 tPOT
1 MiB104.87 tPOT
4 MiB419.44 tPOT

Fees are linear in size, so PotDrive estimates any upload from two paymentInfo calls. They are also the reason deduplication matters.

Edit this page on GitHub