Skip to content

File Contracts

A file contract is a smart contract enacting a storage agreement between a renter and a host. It consists of a bidirectional payment channel in which the renter and host each lock up some quantity of siacoins. The channel state also includes the Merkle root of the data that the host agrees to store on the renter's behalf. Each time the renter uploads more data to the host, both parties sign a contract revision that transfers some of the locked siacoins to the host, and simultaneously updates the Merkle root.

When the contract period concludes, the host can submit a Merkle proof demonstrating that they are storing a randomly-selected portion of the renter's data. The contract then "resolves valid," releasing its locked value by creating siacoin outputs controlled by the renter and host. If the host fails to submit a valid proof in time, the contract "resolves missed," and the host receives a different output. (Typically, the host agrees to put up collateral proportional to the data it stores, and this collateral is only returned if the contract resolves valid.)

Contract formation, revision, and resolution are negotiated via the Renter-Host Protocol. Nothing in the consensus rules themselves prevents renters and hosts from negotiating contracts through other means, but in practice the RHP is more or less universal.

Formation

Contracts are formed via Transactions, funded by siacoin inputs. When the transaction is confirmed in a block, the contract is added to the Element Accumulator.

The full state of a contract is:

  • Capacity: The quantity of storage for which the host has been paid. This is not enforceable by consensus, but it is important information for determining how many siacoins the renter needs to transfer to the host.
  • Filesize: The number of bytes stored by the host. Consensus uses this as an upper bound when calculating the leaf index for the storage proof.
  • FileMerkleRoot: The Merkle root of the data stored by the host. Logically, the data is represented as a Binary Numeral Tree, enabling efficient updates to its Merkle root.
  • ProofHeight: The height at which storage proofs may be submitted and no further revisions are accepted. The length of time from the contract's creation to this height is known as the "contract period." Most hosts will refuse to accept revision requests a few blocks prior to this height, as there is a risk that the transaction will not be mined in time.
  • ExpirationHeight: The height at which storage proofs are no longer accepted. The contract has now de facto resolved missed; however, no outputs are created until the contract is explicitly resolved by a transaction. Note that anyone can submit such a transaction; no signatures are required.
  • RenterOutput: The siacoin output that will be created for the renter when the contract resolves.
  • HostOutput: The siacoin output that will be created for the host if the contract resolves valid.
  • MissedHostValue: The value of HostOutput if the contract resolves missed.
  • TotalCollateral: The value of siacoins put up by the host as collateral. Similar to Capacity, this field is useful for bookkeeping, but is not relevant to consensus.
  • RenterPublicKey: The host's public key, used to validate signatures.
  • HostPublicKey: The renter's public key, used to validate signatures.
  • RevisionNumber: The sequence number of the contract, which must monotonically increase.
  • RenterSignature: A signature from the renter covering all the above fields.
  • HostSignature: A signature from the host covering all the above fields.

A 4% tax is levied on the total contract value (i.e. the sum of the RenterOutput and HostOutput), which is distributed as a dividend to siafund holders.

Revision

Revisions take place off-chain; they only update the consensus view of the contract when they are broadcast in a Transaction. To be accepted, a revision must have a higher revision number than the current contract state. This ensures that contracts cannot be reverted to an earlier state (which would typically benefit the renter, allowing them to claw back any funds transferred to the host).

Once the contract's ProofHeight has been reached, no further revisions are accepted.

Resolution

There are three possible ways in which a contract can resolve.

  1. Renewal: Requires a signature from the renter and host. The channel is bilaterally closed in some agreed-upon final state and a new channel is opened in its place. The renter and host may "rollover" value into the new contract, deducting it from the old contract outputs. As the name implies, renewal is typically used to extend the lifetime of a contract without changing the data being stored. However, it can also be used to add or remove value without having to wait for the contract to expire.
  2. Storage Proof: Requires a Merkle proof for a 64-byte "leaf" of the stored data, verified against the current FileMerkleRoot. The leaf is chosen by interpreting the ID of the block at the ProofHeight as an integer; thus, a proof of the presence of that ID in the chain must also be included. The channel is unilaterally closed in its current state, creating RenterOutput and HostOutput.
  3. Expiration: Requires the height to exceed ExpirationHeight. The channel is unilaterally closed in its current state, except that the host receives, as punishment for failing to complete a storage proof, MissedHostValue instead of the full value of HostOutput. The excess siacoins are destroyed. (If they were not, the recipient would be incentivized to prevent the host from completing a storage proof.)

In v1

Generally speaking, v1 contracts were more flexible. They supported an arbitrary number of outputs; by convention, the first output was the renter's, the second was the host's, and the third was sent to the void address (to burn the host's collateral). Instead of public keys, they specified a set of unlock conditions, so contracts could be timelocked or require multisigs other than one renter and one host.

Contracts expired "automatically:" every node tracked the set of unresolved contracts, and created their missed outputs when their proof window ended. This obscured the true size of Sia's State.

Renewals in v1 were not atomic: the final revision to the old contract was signed in a separate transaction from the first revision of the new contract. Even without malicious behavior, this could lead to a renewal being split across blocks, or the new contract failing to be mined.

The original whitepaper described storage proofs happening regularly, instead of once at the end of the contract. While this instictively feels safer, the expected value is the same. Renters that want more assurances that their data is still stored can simply request a random portion manually; if the host fails to provide it, the renter can refuse to upload any additional data. Many renter-host interactions are, in this way, safeguarded by the host's incentive to continue "doing business with" the renter.