Introduction
-
State
reliable
-
Theory Audit
n/a
-
Edit this section
-
section-intro
-
State
reliable -
Theory Audit
n/a - Edit this section
-
section-intro
Filecoin is a distributed storage network based on a blockchain mechanism. Filecoin miners can elect to provide storage capacity for the network, and thereby earn units of the Filecoin cryptocurrency (FIL) by periodically producing cryptographic proofs that certify that they are providing the capacity specified. In addition, Filecoin enables parties to exchange FIL currency through transactions recorded in a shared ledger on the Filecoin blockchain. Rather than using Nakamoto-style proof of work to maintain consensus on the chain, however, Filecoin uses proof of storage itself: a miner’s power in the consensus protocol is proportional to the amount of storage it provides.
The Filecoin blockchain not only maintains the ledger for FIL transactions and accounts, but also implements the Filecoin VM, a replicated state machine which executes a variety of cryptographic contracts and market mechanisms among participants on the network. These contracts include storage deals, in which clients pay FIL currency to miners in exchange for storing the specific file data that the clients request. Via the distributed implementation of the Filecoin VM, storage deals and other contract mechanisms recorded on the chain continue to be processed over time, without requiring further interaction from the original parties (such as the clients who requested the data storage).
Spec Status
-
State
reliable
-
Theory Audit
n/a
-
Edit this section
-
section-intro.spec-status
-
State
reliable -
Theory Audit
n/a - Edit this section
-
section-intro.spec-status
Each section of the spec must be stable and audited before it is considered done. The state of each section is tracked below.
- The State column indicates the stability as defined in the legend.
- The Theory Audit column shows the date of the last theory audit with a link to the report.
Spec Status Legend
-
State
reliable
-
Theory Audit
n/a
-
Edit this section
-
section-intro.spec-status-legend
-
State
reliable -
Theory Audit
n/a - Edit this section
-
section-intro.spec-status-legend
| Spec state | Label |
|---|---|
| Unlikely to change in the foreseeable future. | Stable |
| All content is correct. Important details are covered. | Reliable |
| All content is correct. Details are being worked on. | Draft/WIP |
| Do not follow. Important things have changed. | Incorrect |
| No work has been done yet. | Missing |
Spec Status Overview
-
State
reliable
-
Theory Audit
n/a
-
Edit this section
-
section-intro.spec-status-overview
-
State
reliable -
Theory Audit
n/a - Edit this section
-
section-intro.spec-status-overview
| Section | State | Theory Audit |
|---|---|---|
| 1 Introduction | Reliable | |
| 1.2 Architecture Diagrams | Reliable | |
| 1.3 Key Concepts | Reliable | |
| 1.4 Filecoin VM | Reliable | |
| 1.5 System Decomposition | Reliable | |
| 1.5.1 What are Systems? How do they work? | Reliable | |
| 1.5.2 Implementing Systems | Reliable | |
| 2 Systems | Draft/WIP | |
| 2.1 Filecoin Nodes | Reliable | |
| 2.1.1 Node Types | Stable | |
| 2.1.2 Node Repository | Stable | |
| 2.1.2.1 Key Store | Reliable | |
| 2.1.2.2 IPLD Store | Stable | Draft/WIP |
| 2.1.3 Network Interface | Stable | |
| 2.1.4 Clock | Reliable | |
| 2.2 Files & Data | Reliable | |
| 2.2.1 File | Reliable | |
| 2.2.1.1 FileStore - Local Storage for Files | Reliable | |
| 2.2.2 The Filecoin Piece | Stable | |
| 2.2.3 Data Transfer in Filecoin | Stable | |
| 2.2.4 Data Formats and Serialization | Reliable | |
| 2.3 Virtual Machine | Reliable | |
| 2.3.1 VM Actor Interface | Reliable | Draft/WIP |
| 2.3.2 State Tree | Reliable | Draft/WIP |
| 2.3.3 VM Message - Actor Method Invocation | Reliable | Draft/WIP |
| 2.3.4 VM Runtime Environment (Inside the VM) | Reliable | |
| 2.3.5 Gas Fees | Reliable | Report Coming Soon |
| 2.3.6 System Actors | Reliable | Reports |
| 2.3.7 VM Interpreter - Message Invocation (Outside VM) | Draft/WIP | Draft/WIP |
| 2.4 Blockchain | Reliable | Draft/WIP |
| 2.4.1 Blocks | Reliable | |
| 2.4.1.1 Block | Reliable | |
| 2.4.1.2 Tipset | Reliable | |
| 2.4.1.3 Chain Manager | Reliable | |
| 2.4.1.4 Block Producer | Reliable | Draft/WIP |
| 2.4.2 Message Pool | Stable | Draft/WIP |
| 2.4.2.1 Message Propagation | Stable | |
| 2.4.2.2 Message Storage | Stable | |
| 2.4.3 ChainSync | Stable | |
| 2.4.4 Storage Power Consensus | Reliable | Draft/WIP |
| 2.4.4.6 Storage Power Actor | Reliable | Draft/WIP |
| 2.5 Token | Reliable | |
| 2.5.1 Minting Model | Reliable | |
| 2.5.2 Block Reward Minting | Reliable | |
| 2.5.3 Token Allocation | Reliable | |
| 2.5.4 Payment Channels | Stable | Draft/WIP |
| 2.5.5 Multisig Wallet & Actor | Reliable | Reports |
| 2.6 Storage Mining | Reliable | Draft/WIP |
| 2.6.1 Sector | Stable | |
| 2.6.1.1 Sector Lifecycle | Stable | |
| 2.6.1.2 Sector Quality | Stable | |
| 2.6.1.3 Sector Sealing | Stable | Draft/WIP |
| 2.6.1.4 Sector Faults | Stable | Draft/WIP |
| 2.6.1.5 Sector Recovery | Reliable | Draft/WIP |
| 2.6.1.6 Adding Storage | Stable | Draft/WIP |
| 2.6.1.7 Upgrading Sectors | Stable | Draft/WIP |
| 2.6.2 Storage Miner | Reliable | Draft/WIP |
| 2.6.2.4 Storage Mining Cycle | Reliable | Draft/WIP |
| 2.6.2.5 Storage Miner Actor | Draft/WIP | Reports |
| 2.6.3 Miner Collaterals | Reliable | |
| 2.6.4 Storage Proving | Draft/WIP | Draft/WIP |
| 2.6.4.2 Sector Poster | Draft/WIP | Draft/WIP |
| 2.6.4.3 Sector Sealer | Draft/WIP | Draft/WIP |
| 2.7 Markets | Stable | |
| 2.7.1 Storage Market in Filecoin | Stable | Draft/WIP |
| 2.7.2 Storage Market On-Chain Components | Reliable | Draft/WIP |
| 2.7.2.3 Storage Market Actor | Reliable | Reports |
| 2.7.2.4 Storage Deal Flow | Reliable | Draft/WIP |
| 2.7.2.5 Storage Deal States | Reliable | |
| 2.7.2.6 Faults | Reliable | Draft/WIP |
| 2.7.3 Retrieval Market in Filecoin | Stable | |
| 2.7.3.5 Retrieval Peer Resolver | Stable | |
| 2.7.3.6 Retrieval Protocols | Stable | |
| 2.7.3.7 Retrieval Client | Stable | |
| 2.7.3.8 Retrieval Provider (Miner) | Stable | |
| 2.7.3.9 Retrieval Deal Status | Stable | |
| 3 Libraries | Reliable | |
| 3.1 DRAND | Stable | Reports |
| 3.2 IPFS | Stable | Draft/WIP |
| 3.3 Multiformats | Stable | |
| 3.4 IPLD | Stable | |
| 3.5 Libp2p | Stable | Draft/WIP |
| 4 Algorithms | Draft/WIP | |
| 4.1 Expected Consensus | Reliable | Draft/WIP |
| 4.2 Proof-of-Storage | Reliable | Draft/WIP |
| 4.2.2 Proof-of-Replication (PoRep) | Reliable | Draft/WIP |
| 4.2.3 Proof-of-Spacetime (PoSt) | Reliable | Draft/WIP |
| 4.3 Stacked DRG Proof of Replication | Stable | Report Coming Soon |
| 4.3.16 SDR Notation, Constants, and Types | Stable | Report Coming Soon |
| 4.4 BlockSync | Stable | |
| 4.5 GossipSub | Stable | Reports |
| 4.6 Cryptographic Primitives | Draft/WIP | |
| 4.6.1 Signatures | Draft/WIP | Report Coming Soon |
| 4.6.2 Verifiable Random Function | Incorrect | |
| 4.6.3 Randomness | Reliable | Draft/WIP |
| 4.6.4 Poseidon | Incorrect | Missing |
| 4.7 Verified Clients | Draft/WIP | Draft/WIP |
| 4.8 Filecoin CryptoEconomics | Reliable | Draft/WIP |
| 5 Glossary | Reliable | |
| 6 Appendix | Draft/WIP | |
| 6.1 Filecoin Address | Reliable | |
| 6.2 Data Structures | Reliable | |
| 6.3 Filecoin Parameters | Draft/WIP | |
| 6.4 Audit Reports | Reliable | |
| 7 Filecoin Implementations | Reliable | |
| 7.1 Lotus | Reliable | |
| 7.2 Venus | Reliable | |
| 7.3 Forest | Reliable | |
| 7.4 Fuhon (cpp-filecoin) | Reliable | |
| 8 Releases |
Spec Stabilization Progress
-
State
reliable
-
Theory Audit
n/a
-
Edit this section
-
section-intro.spec-stabilization-progress
-
State
reliable -
Theory Audit
n/a - Edit this section
-
section-intro.spec-stabilization-progress
This progress bar shows what percentage of the spec sections are considered stable.
Implementations Status
-
State
reliable
-
Theory Audit
n/a
-
Edit this section
-
section-intro.implementations-status
-
State
reliable -
Theory Audit
n/a - Edit this section
-
section-intro.implementations-status
Known implementations of the filecoin spec are tracked below, with their current CI build status, their test coverage as reported by codecov.io, and a link to their last security audit report where one exists.
| Repo | Language | CI | Test Coverage | Security Audit |
|---|---|---|---|---|
| lotus | go | Failed | 40% | Reports |
| go-fil-markets | go | Passed | 58% | Reports |
| specs-actors | go | Unknown | 69% | Reports |
| rust | Unknown | Unknown | Reports | |
| venus | go | Passed | 23% | Missing |
| forest | rust | Passed | 56% | Missing |
| cpp-filecoin | c++ | Passed | 45% | Missing |
Architecture Diagrams
-
State
reliable
-
Theory Audit
n/a
-
Edit this section
-
section-intro.arch
-
State
reliable -
Theory Audit
n/a - Edit this section
-
section-intro.arch
Actor State Diagram
Key Concepts
-
State
reliable
-
Theory Audit
n/a
-
Edit this section
-
section-intro.concepts
-
State
reliable -
Theory Audit
n/a - Edit this section
-
section-intro.concepts
For clarity, we refer the following types of entities to describe implementations of the Filecoin protocol:
-
Data structures are collections of semantically-tagged data members (e.g., structs, interfaces, or enums).
-
Functions are computational procedures that do not depend on external state (i.e., mathematical functions, or programming language functions that do not refer to global variables).
-
Components are sets of functionality that are intended to be represented as single software units in the implementation structure. Depending on the choice of language and the particular component, this might correspond to a single software module, a thread or process running some main loop, a disk-backed database, or a variety of other design choices. For example, the ChainSync is a component: it could be implemented as a process or thread running a single specified main loop, which waits for network messages and responds accordingly by recording and/or forwarding block data.
-
APIs are the interfaces for delivering messages to components. A client’s view of a given sub-protocol, such as a request to a miner node’s Storage Provider component to store files in the storage market, may require the execution of a series of API requests.
-
Nodes are complete software and hardware systems that interact with the protocol. A node might be constantly running several of the above components, participating in several subsystems, and exposing APIs locally and/or over the network, depending on the node configuration. The term full node refers to a system that runs all of the above components and supports all of the APIs detailed in the spec.
-
Subsystems are conceptual divisions of the entire Filecoin protocol, either in terms of complete protocols (such as the Storage Market or Retrieval Market), or in terms of functionality (such as the VM - Virtual Machine). They do not necessarily correspond to any particular node or software component.
-
Actors are virtual entities embodied in the state of the Filecoin VM. Protocol actors are analogous to participants in smart contracts; an actor carries a FIL currency balance and can interact with other actors via the operations of the VM, but does not necessarily correspond to any particular node or software component.
Filecoin VM
-
State
reliable
-
Theory Audit
n/a
-
Edit this section
-
section-intro.filecoin_vm
-
State
reliable -
Theory Audit
n/a - Edit this section
-
section-intro.filecoin_vm
The majority of Filecoin’s user facing functionality (payments, storage market, power table, etc) is managed through the Filecoin Virtual Machine (Filecoin VM). The network generates a series of blocks, and agrees which ‘chain’ of blocks is the correct one. Each block contains a series of state transitions called messages, and a checkpoint of the current global state after the application of those messages.
The global state here consists of a set of actors, each with their own private state.
An actor is the Filecoin equivalent of Ethereum’s smart contracts, it is essentially an ‘object’ in the filecoin network with state and a set of methods that can be used to interact with it. Every actor has a Filecoin balance attributed to it, a state pointer, a code CID which tells the system what type of actor it is, and a nonce which tracks the number of messages sent by this actor.
There are two routes to calling a method on an actor. First, to call a method as an external participant of the system (aka, a normal user with Filecoin) you must send a signed message to the network, and pay a fee to the miner that includes your message. The signature on the message must match the key associated with an account with sufficient Filecoin to pay for the message’s execution. The fee here is equivalent to transaction fees in Bitcoin and Ethereum, where it is proportional to the work that is done to process the message (Bitcoin prices messages per byte, Ethereum uses the concept of ‘gas’. We also use ‘gas’).
Second, an actor may call a method on another actor during the invocation of one of its methods. However, the only time this may happen is as a result of some actor being invoked by an external users message (note: an actor called by a user may call another actor that then calls another actor, as many layers deep as the execution can afford to run for).
For full implementation details, see the VM Subsystem.
System Decomposition
-
State
reliable
-
Theory Audit
n/a
-
Edit this section
-
section-intro.systems
-
State
reliable -
Theory Audit
n/a - Edit this section
-
section-intro.systems
What are Systems? How do they work?
-
State
reliable
-
Theory Audit
n/a
-
Edit this section
-
section-intro.systems.why_systems
-
State
reliable -
Theory Audit
n/a - Edit this section
-
section-intro.systems.why_systems
Filecoin decouples and modularizes functionality into loosely-joined systems.
Each system adds significant functionality, usually to achieve a set of important and tightly related goals.
For example, the Blockchain System provides structures like Block, Tipset, and Chain, and provides functionality like Block Sync, Block Propagation, Block Validation, Chain Selection, and Chain Access. This is separated from the Files, Pieces, Piece Preparation, and Data Transfer. Both of these systems are separated from the Markets, which provide Orders, Deals, Market Visibility, and Deal Settlement.
Why is System decoupling useful?
-
State
reliable
-
Theory Audit
n/a
-
Edit this section
-
section-intro.systems.why_systems.why-is-system-decoupling-useful
-
State
reliable -
Theory Audit
n/a - Edit this section
-
section-intro.systems.why_systems.why-is-system-decoupling-useful
This decoupling is useful for:
- Implementation Boundaries: it is possible to build implementations of Filecoin that only implement a subset of systems. This is especially useful for Implementation Diversity: we want many implementations of security critical systems (eg Blockchain), but do not need many implementations of Systems that can be decoupled.
- Runtime Decoupling: system decoupling makes it easier to build and run Filecoin Nodes that isolate Systems into separate programs, and even separate physical computers.
- Security Isolation: some systems require higher operational security than others. System decoupling allows implementations to meet their security and functionality needs. A good example of this is separating Blockchain processing from Data Transfer.
- Scalability: systems, and various use cases, may drive different performance requirements for different operators. System decoupling makes it easier for operators to scale their deployments along system boundaries.
Filecoin Nodes don’t need all the systems
-
State
reliable
-
Theory Audit
n/a
-
Edit this section
-
section-intro.systems.why_systems.filecoin-nodes-dont-need-all-the-systems
-
State
reliable -
Theory Audit
n/a - Edit this section
-
section-intro.systems.why_systems.filecoin-nodes-dont-need-all-the-systems
Filecoin Nodes vary significantly and do not need all the systems. Most systems are only needed for a subset of use cases.
For example, the Blockchain System is required for synchronizing the chain, participating in secure consensus, storage mining, and chain validation. Many Filecoin Nodes do not need the chain and can perform their work by just fetching content from the latest StateTree, from a node they trust.
Note: Filecoin does not use the “full node” or “light client” terminology, in wide use in Bitcoin and other blockchain networks. In filecoin, these terms are not well defined. It is best to define nodes in terms of their capabilities, and therefore, in terms of the Systems they run. For example:
- Chain Verifier Node: Runs the Blockchain system. Can sync and validate the chain. Cannot mine or produce blocks.
- Client Node: Runs the Blockchain, Market, and Data Transfer systems. Can sync and validate the chain. Cannot mine or produce blocks.
- Retrieval Miner Node: Runs the Market and Data Transfer systems. Does not need the chain. Can make Retrieval Deals (Retrieval Provider side). Can send Clients data, and get paid for it.
- Storage Miner Node: Runs the Blockchain, Storage Market, Storage Mining systems. Can sync and validate the chain. Can make Storage Deals (Storage Provider side). Can seal stored data into sectors. Can acquire storage consensus power. Can mine and produce blocks.
Separating Systems
-
State
reliable
-
Theory Audit
n/a
-
Edit this section
-
section-intro.systems.why_systems.separating-systems
-
State
reliable -
Theory Audit
n/a - Edit this section
-
section-intro.systems.why_systems.separating-systems
How do we determine what functionality belongs in one system vs another?
Drawing boundaries between systems is the art of separating tightly related functionality from unrelated parts. In a sense, we seek to keep tightly integrated components in the same system, and away from other unrelated components. This is sometimes straightforward, the boundaries naturally spring from the data structures or functionality. For example, it is straightforward to observe that Clients and Miners negotiating a deal with each other is very unrelated to VM Execution.
Sometimes this is harder, and it requires detangling, adding, or removing abstractions. For
example, the StoragePowerActor and the StorageMarketActor were a single Actor previously. This caused
a large coupling of functionality across StorageDeal making, the StorageMarket, markets in general, with
Storage Mining, Sector Sealing, PoSt Generation, and more. Detangling these two sets of related functionality
required breaking apart the one actor into two.
Decomposing within a System
-
State
reliable
-
Theory Audit
n/a
-
Edit this section
-
section-intro.systems.why_systems.decomposing-within-a-system
-
State
reliable -
Theory Audit
n/a - Edit this section
-
section-intro.systems.why_systems.decomposing-within-a-system
Systems themselves decompose into smaller subunits. These are sometimes called “subsystems” to avoid confusion with the much larger, first-class Systems. Subsystems themselves may break down further. The naming here is not strictly enforced, as these subdivisions are more related to protocol and implementation engineering concerns than to user capabilities.
Implementing Systems
-
State
reliable
-
Theory Audit
n/a
-
Edit this section
-
section-intro.systems.impl_systems
-
State
reliable -
Theory Audit
n/a - Edit this section
-
section-intro.systems.impl_systems
System Requirements
-
State
reliable
-
Theory Audit
n/a
-
Edit this section
-
section-intro.systems.impl_systems.system-requirements
-
State
reliable -
Theory Audit
n/a - Edit this section
-
section-intro.systems.impl_systems.system-requirements
In order to make it easier to decouple functionality into systems, the Filecoin Protocol assumes a set of functionality available to all systems. This functionality can be achieved by implementations in a variety of ways, and should take the guidance here as a recommendation (SHOULD).
All Systems, as defined in this document, require the following:
- Repository:
- Local
IpldStore. Some amount of persistent local storage for data structures (small structured objects). Systems expect to be initialized with an IpldStore in which to store data structures they expect to persist across crashes. - User Configuration Values. A small amount of user-editable configuration values. These should be easy for end-users to access, view, and edit.
- Local, Secure
KeyStore. A facility to use to generate and use cryptographic keys, which MUST remain secret to the Filecoin Node. Systems SHOULD NOT access the keys directly, and should do so over an abstraction (ie theKeyStore) which provides the ability to Encrypt, Decrypt, Sign, SigVerify, and more.
- Local
- Local
FileStore. Some amount of persistent local storage for files (large byte arrays). Systems expect to be initialized with a FileStore in which to store large files. Some systems (like Markets) may need to store and delete large volumes of smaller files (1MB - 10GB). Other systems (like Storage Mining) may need to store and delete large volumes of large files (1GB - 1TB). - Network. Most systems need access to the network, to be able to connect to their counterparts in other Filecoin Nodes.
Systems expect to be initialized with a
libp2p.Nodeon which they can mount their own protocols. - Clock. Some systems need access to current network time, some with low tolerance for drift. Systems expect to be initialized with a Clock from which to tell network time. Some systems (like Blockchain) require very little clock drift, and require secure time.
For this purpose, we use the FilecoinNode data structure, which is passed into all systems at initialization.
System Limitations
-
State
reliable
-
Theory Audit
n/a
-
Edit this section
-
section-intro.systems.impl_systems.system-limitations
-
State
reliable -
Theory Audit
n/a - Edit this section
-
section-intro.systems.impl_systems.system-limitations
Further, Systems MUST abide by the following limitations:
- Random crashes. A Filecoin Node may crash at any moment. Systems must be secure and consistent through crashes. This is primarily achieved by limiting the use of persistent state, persisting such state through Ipld data structures, and through the use of initialization routines that check state, and perhaps correct errors.
- Isolation. Systems must communicate over well-defined, isolated interfaces. They must not build their critical functionality over a shared memory space. (Note: for performance, shared memory abstractions can be used to power IpldStore, FileStore, and libp2p, but the systems themselves should not require it.) This is not just an operational concern; it also significantly simplifies the protocol and makes it easier to understand, analyze, debug, and change.
- No direct access to host OS Filesystem or Disk. Systems cannot access disks directly – they do so over the FileStore and IpldStore abstractions. This is to provide a high degree of portability and flexibility for end-users, especially storage miners and clients of large amounts of data, which need to be able to easily replace how their Filecoin Nodes access local storage.
- No direct access to host OS Network stack or TCP/IP. Systems cannot access the network directly – they do so over the libp2p library. There must not be any other kind of network access. This provides a high degree of portability across platforms and network protocols, enabling Filecoin Nodes (and all their critical systems) to run in a wide variety of settings, using all kinds of protocols (eg Bluetooth, LANs, etc).
Systems
-
State
wip
-
Theory Audit
n/a
-
Edit this section
-
section-systems
-
State
wip -
Theory Audit
n/a - Edit this section
-
section-systems
In this section we are detailing all the system components one by one in increasing level of complexity and/or interdependence to other system components. The interaction of the components between each other is only briefly discussed where appropriate, but the overall workflow is given in the Introduction section. In particular, in this section we discuss:
- Filecoin Nodes: the different types of nodes that participate in the Filecoin Network, as well as important parts and processes that these nodes run, such as the key store and IPLD store, as well as the network interface to libp2p.
- Files & Data: the data units of Filecoin, such as the Sectors and the Pieces.
- Virtual Machine: the subcomponents of the Filecoin VM, such as the actors, i.e., the smart contracts that run on the Filecoin Blockchain, and the State Tree.
- Blockchain: the main building blocks of the Filecoin blockchain, such as the structure of messages and blocks, the message pool, as well as how nodes synchronise the blockchain when they first join the network.
- Token: the components needed for a wallet.
- Storage Mining: the details of storage mining, storage power consensus, and how storage miners prove storage (without going into details of proofs, which are discussed later).
- Markets: the storage and retrieval markets, which are primarily processes that take place off-chain, but are very important for the smooth operation of the decentralised storage market.
Filecoin Nodes
-
State
reliable
-
Theory Audit
n/a
-
Edit this section
-
section-systems.filecoin_nodes
-
State
reliable -
Theory Audit
n/a - Edit this section
-
section-systems.filecoin_nodes
This section starts by discussing the concept of Filecoin Nodes. Although different node types in the Lotus implementation of Filecoin are less strictly defined than in other blockchain networks, there are different properties and features that different types of nodes should implement. In short, nodes are defined based on the set of services they provide.
In this section we also discuss issues related to storage of system files in Filecoin nodes. Note that by storage in this section we do not refer to the storage that a node commits for mining in the network, but rather the local storage repositories that it needs to have available for keys and IPLD data among other things.
In this section we are also discussing the network interface and how nodes find and connect with each other, how they interact and propagate messages using libp2p, as well as how to set the node’s clock.
Node Types
-
State
stable
-
Theory Audit
n/a
-
Edit this section
-
section-systems.filecoin_nodes.node_types
-
State
stable -
Theory Audit
n/a - Edit this section
-
section-systems.filecoin_nodes.node_types
Nodes in the Filecoin network are primarily identified in terms of the services they provide. The type of node, therefore, depends on which services a node provides. A basic set of services in the Filecoin network include:
- chain verification
- storage market client
- storage market provider
- retrieval market client
- retrieval market provider
- storage mining
Any node participating in the Filecoin network should provide the chain verification service as a minimum. Depending on which extra services a node provides on top of chain verification, it gets the corresponding functionality and Node Type “label”.
Nodes can be realized with a repository (directory) in the host in a one-to-one relationship - that is, one repo belongs to a single node. That said, one host can implement multiple Filecoin nodes by having the corresponding repositories.
A Filecoin implementation can support the following subsystems, or types of nodes:
- Chain Verifier Node: this is the minimum functionality that a node needs to have in order to participate in the Filecoin network. This type of node cannot play an active role in the network, unless it implements Client Node functionality, described below. A Chain Verifier Node must synchronise the chain (ChainSync) when it first joins the network to reach current consensus. From then on, the node must constantly be fetching any addition to the chain (i.e., receive the latest blocks) and validate them to reach consensus state.
- Client Node: this type of node builds on top of the Chain Verifier Node and must be implemented by any application that is building on the Filecoin network. This can be thought of as the main infrastructure node (at least as far as interaction with the blockchain is concerned) of applications such as exchanges or decentralised storage applications building on Filecoin. The node should implement the storage market and retrieval market client services. The client node should interact with the Storage and Retrieval Markets and be able to do Data Transfers through the Data Transfer Module.
- Retrieval Miner Node: this node type is extending the Chain Verifier Node to add retrieval miner functionality, that is, participate in the retrieval market. As such, this node type needs to implement the retrieval market provider service and be able to do Data Transfers through the Data Transfer Module.
- Storage Miner Node: this type of node must implement all of the required functionality for validating, creating and adding blocks to extend the blockchain. It should implement the chain verification, storage mining and storage market provider services and be able to do Data Transfers through the Data Transfer Module.
Node Interface
-
State
stable
-
Theory Audit
n/a
-
Edit this section
-
section-systems.filecoin_nodes.node_types.node-interface
-
State
stable -
Theory Audit
n/a - Edit this section
-
section-systems.filecoin_nodes.node_types.node-interface
The Lotus implementation of the Node Interface can be found here.
Chain Verifier Node
-
State
stable
-
Theory Audit
n/a
-
Edit this section
-
section-systems.filecoin_nodes.node_types.chain-verifier-node
-
State
stable -
Theory Audit
n/a - Edit this section
-
section-systems.filecoin_nodes.node_types.chain-verifier-node
type ChainVerifierNode interface {
FilecoinNode
systems.Blockchain
}
The Lotus implementation of the Chain Verifier Node can be found here.
Client Node
-
State
stable
-
Theory Audit
n/a
-
Edit this section
-
section-systems.filecoin_nodes.node_types.client-node
-
State
stable -
Theory Audit
n/a - Edit this section
-
section-systems.filecoin_nodes.node_types.client-node
type ClientNode struct {
FilecoinNode
systems.Blockchain
markets.StorageMarketClient
markets.RetrievalMarketClient
markets.DataTransfers
}
The Lotus implementation of the Client Node can be found here.
Storage Miner Node
-
State
stable
-
Theory Audit
n/a
-
Edit this section
-
section-systems.filecoin_nodes.node_types.storage-miner-node
-
State
stable -
Theory Audit
n/a - Edit this section
-
section-systems.filecoin_nodes.node_types.storage-miner-node
type StorageMinerNode interface {
FilecoinNode
systems.Blockchain
systems.Mining
markets.StorageMarketProvider
markets.DataTransfers
}
The Lotus implementation of the Storage Miner Node can be found here.
Retrieval Miner Node
-
State
stable
-
Theory Audit
n/a
-
Edit this section
-
section-systems.filecoin_nodes.node_types.retrieval-miner-node
-
State
stable -
Theory Audit
n/a - Edit this section
-
section-systems.filecoin_nodes.node_types.retrieval-miner-node
type RetrievalMinerNode interface {
FilecoinNode
blockchain.Blockchain
markets.RetrievalMarketProvider
markets.DataTransfers
}
Relayer Node
-
State
stable
-
Theory Audit
n/a
-
Edit this section
-
section-systems.filecoin_nodes.node_types.relayer-node
-
State
stable -
Theory Audit
n/a - Edit this section
-
section-systems.filecoin_nodes.node_types.relayer-node
type RelayerNode interface {
FilecoinNode
blockchain.MessagePool
}
Node Configuration
-
State
stable
-
Theory Audit
n/a
-
Edit this section
-
section-systems.filecoin_nodes.node_types.node-configuration
-
State
stable -
Theory Audit
n/a - Edit this section
-
section-systems.filecoin_nodes.node_types.node-configuration
The Lotus implementation of Filecoin Node configuration values can be found here.
Node Repository
-
State
stable
-
Theory Audit
n/a
-
Edit this section
-
section-systems.filecoin_nodes.repository
-
State
stable -
Theory Audit
n/a - Edit this section
-
section-systems.filecoin_nodes.repository
The Filecoin node repository is simply local storage for system and chain data. It is an abstraction of the data which any functional Filecoin node needs to store locally in order to run correctly.
The repository is accessible to the node’s systems and subsystems and can be compartmentalized from the node’s FileStore.
The repository stores the node’s keys, the IPLD data structures of stateful objects as well as the node configuration settings.
The Lotus implementation of the FileStore Repository can be found here.
Key Store
-
State
reliable
-
Theory Audit
n/a
-
Edit this section
-
section-systems.filecoin_nodes.repository.key_store
-
State
reliable -
Theory Audit
n/a - Edit this section
-
section-systems.filecoin_nodes.repository.key_store
The Key Store is a fundamental abstraction in any full Filecoin node used to store the keypairs associated with a given miner’s address (see actual definition further down) and distinct workers (should the miner choose to run multiple workers).
Node security depends in large part on keeping these keys secure. To that end we strongly recommend: 1) keeping keys separate from all subsystems, 2) using a separate key store to sign requests as required by other subsystems, and 3) keeping those keys that are not used as part of mining in cold storage.
Filecoin storage miners rely on three main components:
- The storage miner actor address is uniquely assigned to a given storage miner actor upon calling
registerMiner()in the Storage Power Consensus Subsystem. In effect, the storage miner does not have an address itself, but is rather identified by the address of the actor it is tied to. This is a unique identifier for a given storage miner to which its power and other keys will be associated. Theactor valuespecifies the address of an already created miner actor. - The owner keypair is provided by the miner ahead of registration and its public key associated with the miner address. The owner keypair can be used to administer a miner and withdraw funds.
- The worker keypair is the public key associated with the storage miner actor address. It can be chosen and changed by the miner. The worker keypair is used to sign blocks and may also be used to sign other messages. It must be a BLS keypair given its use as part of the Verifiable Random Function.
Multiple storage miner actors can share one owner public key or likewise a worker public key.
The process for changing the worker keypairs on-chain (i.e. the worker Key associated with a storage miner actor) is specified in Storage Miner Actor. Note that this is a two-step process. First, a miner stages a change by sending a message to the chain. Then, the miner confirms the key change after the randomness lookback time. Finally, the miner will begin signing blocks with the new key after an additional randomness lookback time. This delay exists to prevent adaptive key selection attacks.
Key security is of utmost importance in Filecoin, as is also the case with keys in every blockchain. Failure to securely store and use keys or exposure of private keys to adversaries can result in the adversary having access to the miner’s funds.
IPLD Store
-
State
stable
-
Theory Audit
wip
-
Edit this section
-
section-systems.filecoin_nodes.repository.ipldstore
-
State
stable -
Theory Audit
wip - Edit this section
-
section-systems.filecoin_nodes.repository.ipldstore
InterPlanetary Linked Data (IPLD) is a set of libraries which allow for the interoperability of content-addressed data structures across different distributed systems and protocols. It provides a fundamental ‘common language’ to primitive cryptographic hashing, enabling data structures to be verifiably referenced and retrieved between two independent protocols. For example, a user can reference an IPFS directory in an Ethereum transaction or smart contract.
The IPLD Store of a Filecoin Node is local storage for hash-linked data.
IPLD is fundamentally comprised of three layers:
- the Block Layer, which focuses on block formats and addressing, how blocks can advertise or self-describe their codec
- the Data Model Layer, which defines a set of required types that need to be included in any implementation - discussed in more detail below.
- the Schema Layer, which allows for extension of the Data Model to interact with more complex structures without the need for custom translation abstractions.
Further details about IPLD can be found in its specification.
The Data Model
-
State
stable
-
Theory Audit
wip
-
Edit this section
-
section-systems.filecoin_nodes.repository.ipldstore.the-data-model
-
State
stable -
Theory Audit
wip - Edit this section
-
section-systems.filecoin_nodes.repository.ipldstore.the-data-model
At its core, IPLD defines a Data Model for representing data. The Data Model is designed for practical implementation across a wide variety of programming languages, while maintaining usability for content-addressed data and a broad range of generalized tools that interact with that data.
The Data Model includes a range of standard primitive types (or “kinds”), such as booleans, integers, strings, nulls and byte arrays, as well as two recursive types: lists and maps. Because IPLD is designed for content-addressed data, it also includes a “link” primitive in its Data Model. In practice, links use the CID specification. IPLD data is organized into “blocks”, where a block is represented by the raw, encoded data and its content-address, or CID. Every content-addressable chunk of data can be represented as a block, and together, blocks can form a coherent graph, or Merkle DAG.
Applications interact with IPLD via the Data Model, and IPLD handles marshalling and unmarshalling via a suite of codecs. IPLD codecs may support the complete Data Model or part of the Data Model. Two codecs that support the complete Data Model are DAG-CBOR and DAG-JSON. These codecs are respectively based on the CBOR and JSON serialization formats but include formalizations that allow them to encapsulate the IPLD Data Model (including its link type) and additional rules that create a strict mapping between any set of data and it’s respective content address (or hash digest). These rules include the mandating of particular ordering of keys when encoding maps, or the sizing of integer types when stored.
IPLD in Filecoin
-
State
stable
-
Theory Audit
wip
-
Edit this section
-
section-systems.filecoin_nodes.repository.ipldstore.ipld-in-filecoin
-
State
stable -
Theory Audit
wip - Edit this section
-
section-systems.filecoin_nodes.repository.ipldstore.ipld-in-filecoin
IPLD is used in two ways in the Filecoin network:
- All system datastructures are stored using DAG-CBOR (an IPLD codec). DAG-CBOR is a more strict subset of CBOR with a predefined tagging scheme, designed for storage, retrieval and traversal of hash-linked data DAGs. As compared to CBOR, DAG-CBOR can guarantee determinism.
- Files and data stored on the Filecoin network are also stored using various IPLD codecs (not necessarily DAG-CBOR).
IPLD provides a consistent and coherent abstraction above data that allows Filecoin to build and interact with complex, multi-block data structures, such as HAMT and AMT. Filecoin uses the DAG-CBOR codec for the serialization and deserialization of its data structures and interacts with that data using the IPLD Data Model, upon which various tools are built. IPLD Selectors can also be used to address specific nodes within a linked data structure.
IpldStores
-
State
stable
-
Theory Audit
wip
-
Edit this section
-
section-systems.filecoin_nodes.repository.ipldstore.ipldstores
-
State
stable -
Theory Audit
wip - Edit this section
-
section-systems.filecoin_nodes.repository.ipldstore.ipldstores
The Filecoin network relies primarily on two distinct IPLD GraphStores:
- One
ChainStorewhich stores the blockchain, including block headers, associated messages, etc. - One
StateStorewhich stores the payload state from a given blockchain, or thestateTreeresulting from all block messages in a given chain being applied to the genesis state by the Filecoin VM.
The ChainStore is downloaded by a node from their peers during the bootstrapping phase of
Chain Sync and is stored by the node thereafter. It is updated on every new block reception, or if the node syncs to a new best chain.
The StateStore is computed through the execution of all block messages in a given ChainStore and is stored by the node thereafter. It is updated with every new incoming block’s processing by the
VM Interpreter, and referenced accordingly by new blocks produced atop it in the
block header’s ParentState field.
Network Interface
-
State
stable
-
Theory Audit
n/a
-
Edit this section
-
section-systems.filecoin_nodes.network
-
State
stable -
Theory Audit
n/a - Edit this section
-
section-systems.filecoin_nodes.network
Filecoin nodes use several protocols of the libp2p networking stack for peer discovery, peer routing and block and message propagation. Libp2p is a modular networking stack for peer-to-peer networks. It includes several protocols and mechanisms to enable efficient, secure and resilient peer-to-peer communication. Libp2p nodes open connections with one another and mount different protocols or streams over the same connection. In the initial handshake, nodes exchange the protocols that each of them supports and all Filecoin related protocols will be mounted under /fil/... protocol identifiers.
The complete specification of libp2p can be found at https://github.com/libp2p/specs. Here is the list of libp2p protocols used by Filecoin.
-
Graphsync: Graphsync is a protocol to synchronize graphs across peers. It is used to reference, address, request and transfer blockchain and user data between Filecoin nodes. The draft specification of GraphSync provides more details on the concepts, the interfaces and the network messages used by GraphSync. There are no Filecoin-specific modifications to the protocol id.
-
Gossipsub: Block headers and messages are propagating through the Filecoin network using a gossip-based pubsub protocol acronymed GossipSub. As is traditionally the case with pubsub protocols, nodes subscribe to topics and receive messages published on those topics. When nodes receive messages from a topic they are subscribed to, they run a validation process and i) pass the message to the application, ii) forward the message further to nodes they know of being subscribed to the same topic. Furthermore, v1.1 version of GossipSub, which is the one used in Filecoin is enhanced with security mechanisms that make the protocol resilient against security attacks. The GossipSub Specification provides all the protocol details pertaining to its design and implementation, as well as specific settings for the protocols parameters. There have been no Filecoin-specific modifications to the protocol id. However the topic identifiers MUST be of the form
fil/blocks/<network-name>andfil/msgs/<network-name> -
Kademlia DHT: The Kademlia DHT is a distributed hash table with a logarithmic bound on the maximum number of lookups for a particular node. In the Filecoin network, the Kademlia DHT is used primarily for peer discovery and peer routing. In particular, when a node wants to store data in the Filecoin network, they get a list of miners and their node information. This node information includes (among other things) the PeerID of the miner. In order to connect to the miner and exchange data, the node that wants to store data in the network has to find the Multiaddress of the miner, which they do by querying the DHT. The libp2p Kad DHT Specification provides implementation details of the DHT structure. For the Filecoin network, the protocol id must be of the form
fil/<network-name>/kad/1.0.0. -
Bootstrap List: This is a list of nodes that a new node attempts to connect to upon joining the network. The list of bootstrap nodes and their addresses are defined by the users (i.e., applications).
-
Peer Exchange: This protocol is the realisation of the peer discovery process discussed above at Kademlia DHT. It enables peers to find information and addresses of other peers in the network by interfacing with the DHT and create and issue queries for the peers they want to connect to.
Clock
-
State
reliable
-
Theory Audit
n/a
-
Edit this section
-
section-systems.filecoin_nodes.clock
-
State
reliable -
Theory Audit
n/a - Edit this section
-
section-systems.filecoin_nodes.clock
Filecoin assumes weak clock synchrony amongst participants in the system. That is, the system relies on participants having access to a globally synchronized clock (tolerating some bounded offset).
Filecoin relies on this system clock in order to secure consensus. Specifically, the clock is necessary to support validation rules that prevent block producers from mining blocks with a future timestamp and running leader elections more frequently than the protocol allows.
Clock uses
-
State
reliable
-
Theory Audit
n/a
-
Edit this section
-
section-systems.filecoin_nodes.clock.clock-uses
-
State
reliable -
Theory Audit
n/a - Edit this section
-
section-systems.filecoin_nodes.clock.clock-uses
The Filecoin system clock is used:
- by syncing nodes to validate that incoming blocks were mined in the appropriate epoch given their timestamp (see Block Validation). This is possible because the system clock maps all times to a unique epoch number totally determined by the start time in the genesis block.
- by syncing nodes to drop blocks coming from a future epoch
- by mining nodes to maintain protocol liveness by allowing participants to try leader election in the next round if no one has produced a block in the current round (see Storage Power Consensus).
In order to allow miners to do the above, the system clock must:
- Have low enough offset relative to other nodes so that blocks are not mined in epochs considered future epochs from the perspective of other nodes (those blocks should not be validated until the proper epoch/time as per validation rules).
- Set epoch number on node initialization equal to
epoch = Floor[(current_time - genesis_time) / epoch_time]
It is expected that other subsystems will register to a NewRound() event from the clock subsystem.
Clock Requirements
-
State
reliable
-
Theory Audit
n/a
-
Edit this section
-
section-systems.filecoin_nodes.clock.clock-requirements
-
State
reliable -
Theory Audit
n/a - Edit this section
-
section-systems.filecoin_nodes.clock.clock-requirements
Clocks used as part of the Filecoin protocol should be kept in sync, with offset less than 1 second so as to enable appropriate validation.
Computer-grade crystals can be expected to deviate by 1ppm (i.e. 1 microsecond every second, or 0.6 seconds per week). Therefore, in order to respect the requirement above:
- Nodes SHOULD run an NTP daemon (e.g. timesyncd, ntpd, chronyd) to keep their clocks synchronized to one or more reliable external references.
- Larger mining operations MAY consider using local NTP/PTP servers with GPS references and/or frequency-stable external clocks for improved timekeeping.
Mining operations have a strong incentive to prevent their clock skewing ahead more than one epoch to keep their block submissions from being rejected. Likewise they have an incentive to prevent their clocks skewing behind more than one epoch to avoid partitioning themselves off from the synchronized nodes in the network.
Files & Data
-
State
reliable
-
Theory Audit
n/a
-
Edit this section
-
section-systems.filecoin_files
-
State
reliable -
Theory Audit
n/a - Edit this section
-
section-systems.filecoin_files
Filecoin’s primary aim is to store client’s Files and Data.
This section details data structures and tooling related to working with files,
chunking, encoding, graph representations, Pieces, storage abstractions, and more.
File
-
State
reliable
-
Theory Audit
n/a
-
Edit this section
-
section-systems.filecoin_files.file
-
State
reliable -
Theory Audit
n/a - Edit this section
-
section-systems.filecoin_files.file
// Path is an opaque locator for a file (e.g. in a unix-style filesystem).
type Path string
// File is a variable length data container.
// The File interface is modeled after a unix-style file, but abstracts the
// underlying storage system.
type File interface {
Path() Path
Size() int
Close() error
// Read reads from File into buf, starting at offset, and for size bytes.
Read(offset int, size int, buf Bytes) struct {size int, e error}
// Write writes from buf into File, starting at offset, and for size bytes.
Write(offset int, size int, buf Bytes) struct {size int, e error}
}
FileStore - Local Storage for Files
-
State
reliable
-
Theory Audit
n/a
-
Edit this section
-
section-systems.filecoin_files.file.filestore
-
State
reliable -
Theory Audit
n/a - Edit this section
-
section-systems.filecoin_files.file.filestore
The FileStore is an abstraction used to refer to any underlying system or device
that Filecoin will store its data to. It is based on Unix filesystem semantics, and
includes the notion of Paths. This abstraction is here in order to make sure Filecoin
implementations make it easy for end-users to replace the underlying storage system with
whatever suits their needs. The simplest version of FileStore is just the host operating
system’s file system.
// FileStore is an object that can store and retrieve files by path.
type FileStore struct {
Open(p Path) union {f File, e error}
Create(p Path) union {f File, e error}
Store(p Path, f File) error
Delete(p Path) error
// maybe add:
// Copy(SrcPath, DstPath)
}
Varying user needs
-
State
reliable
-
Theory Audit
n/a
-
Edit this section
-
section-systems.filecoin_files.file.filestore.varying-user-needs
-
State
reliable -
Theory Audit
n/a - Edit this section
-
section-systems.filecoin_files.file.filestore.varying-user-needs
Filecoin user needs vary significantly, and many users – especially miners – will implement
complex storage architectures underneath and around Filecoin. The FileStore abstraction is here
to make it easy for these varying needs to be easy to satisfy. All file and sector local data
storage in the Filecoin Protocol is defined in terms of this FileStore interface, which makes
it easy for implementations to make swappable, and for end-users to swap out with their system
of choice.
Implementation examples
-
State
reliable
-
Theory Audit
n/a
-
Edit this section
-
section-systems.filecoin_files.file.filestore.implementation-examples
-
State
reliable -
Theory Audit
n/a - Edit this section
-
section-systems.filecoin_files.file.filestore.implementation-examples
The FileStore interface may be implemented by many kinds of backing data storage systems. For example:
- The host Operating System file system
- Any Unix/Posix file system
- RAID-backed file systems
- Networked of distributed file systems (NFS, HDFS, etc)
- IPFS
- Databases
- NAS systems
- Raw serial or block devices
- Raw hard drives (hdd sectors, etc)
Implementations SHOULD implement support for the host OS file system. Implementations MAY implement support for other storage systems.
The Filecoin Piece
-
State
stable
-
Theory Audit
n/a
-
Edit this section
-
section-systems.filecoin_files.piece
-
State
stable -
Theory Audit
n/a - Edit this section
-
section-systems.filecoin_files.piece
The Filecoin Piece is the main unit of negotiation for data that users store on the Filecoin network. The Filecoin Piece is not a unit of storage, it is not of a specific size, but is upper-bounded by the size of the Sector. A Filecoin Piece can be of any size, but if a Piece is larger than the size of a Sector that the miner supports it has to be split into more Pieces so that each Piece fits into a Sector.
A Piece is an object that represents a whole or part of a File,
and is used by Storage Clients and Storage Miners in Deals. Storage Clients hire Storage Miners to store Pieces.
The Piece data structure is designed for proving storage of arbitrary IPLD graphs and client data. This diagram shows the detailed composition of a Piece and its proving tree, including both full and bandwidth-optimized Piece data structures.
Data Representation
-
State
stable
-
Theory Audit
n/a
-
Edit this section
-
section-systems.filecoin_files.piece.data-representation
-
State
stable -
Theory Audit
n/a - Edit this section
-
section-systems.filecoin_files.piece.data-representation
It is important to highlight that data submitted to the Filecoin network go through several transformations before they come to the format at which the StorageProvider stores it.
Below is the process followed from the point a user starts preparing a file to store in Filecoin to the point that the provider produces all the identifiers of Pieces stored in a Sector.
The first three steps take place on the client side.
-
When a client wants to store a file in the Filecoin network, they start by producing the IPLD DAG of the file. The hash that represents the root node of the DAG is an IPFS-style CID, called Payload CID.
-
In order to make a Filecoin Piece, the IPLD DAG is serialised into a “Content-Addressable aRchive” (.car) file, which is in raw bytes format. A CAR file is an opaque blob of data that packs together and transfers IPLD nodes. The Payload CID is common between the CAR’ed and un-CAR’ed constructions. This helps later during data retrieval, when data is transferred between the storage client and the storage provider as we discuss later.
-
The resulting .car file is padded with extra zero bits in order for the file to make a binary Merkle tree. To achieve a clean binary Merkle Tree the .car file size has to be in some power of two (^2) size. A padding process, called
Fr32 padding, which adds two (2) zero bits to every 254 out of every 256 bits is applied to the input file. At the next step, the padding process takes the output of theFr32 paddingprocess and finds the size above it that makes for a power of two size. This gap between the result of theFr32 paddingand the next power of two size is padded with zeros.
In order to justify the reasoning behind these steps, it is important to understand the overall negotiation process between the StorageClient and a StorageProvider. The piece CID or CommP is what is included in the deal that the client negotiates and agrees with the storage provider. When the deal is agreed, the client sends the file to the provider (using GraphSync). The provider has to construct the CAR file out of the file received and derive the Piece CID on their side. In order to avoid the client sending a different file to the one agreed, the Piece CID that the provider generates has to be the same as the one included in the deal negotiated earlier.
The following steps take place on the StorageProvider side (apart from step 4 that can also take place at the client side).
-
Once the
StorageProviderreceives the file from the client, they calculate the Merkle root out of the hashes of the Piece (padded .car file). The resulting root of the clean binary Merkle tree is the Piece CID. This is also referred to as CommP or Piece Commitment and as mentioned earlier, has to be the same with the one included in the deal. -
The Piece is included in a Sector together with data from other deals. The
StorageProviderthen calculates Merkle root for all the Pieces inside the Sector. The root of this tree is CommD (aka Commitment of Data orUnsealedSectorCID). -
The
StorageProvideris then sealing the sector and the root of the resulting Merkle root is the CommRLast. -
Proof of Replication (PoRep), SDR in particular, generates another Merkle root hash called CommC, as an attestation that replication of the data whose commitment is CommD has been performed correctly.
-
Finally, CommR (or Commitment of Replication) is the hash of CommC || CommRLast.
IMPORTANT NOTES:
Fr32is a 32-bit representation of a field element (which, in our case, is the arithmetic field of BLS12-381). To be well-formed, a value of typeFr32must actually fit within that field, but this is not enforced by the type system. It is an invariant which must be perserved by correct usage. In the case of so-calledFr32 padding, two zero bits are inserted ‘after’ a number requiring at most 254 bits to represent. This guarantees that the result will beFr32, regardless of the value of the initial 254 bits. This is a ‘conservative’ technique, since for some initial values, only one bit of zero-padding would actually be required.- Steps 2 and 3 above are specific to the Lotus implementation. The same outcome can be achieved in different ways, e.g., without using
Fr32bit-padding. However, any implementation has to make sure that the initial IPLD DAG is serialised and padded so that it gives a clean binary tree, and therefore, calculating the Merkle root out of the resulting blob of data gives the same Piece CID. As long as this is the case, implementations can deviate from the first three steps above. - Finally, it is important to add a note related to the Payload CID (discussed in the first two steps above) and the data retrieval process. The retrieval deal is negotiated on the basis of the Payload CID. When the retrieval deal is agreed, the retrieval miner starts sending the unsealed and “un-CAR’ed” file to the client. The transfer starts from the root node of the IPLD Merkle Tree and in this way the client can validate the Payload CID from the beginning of the transfer and verify that the file they are receiving is the file they negotiated in the deal and not random bits.
PieceStore
-
State
stable
-
Theory Audit
n/a
-
Edit this section
-
section-systems.filecoin_files.piece.piecestore
-
State
stable -
Theory Audit
n/a - Edit this section
-
section-systems.filecoin_files.piece.piecestore
The PieceStore module allows for storage and retrieval of Pieces from some local storage. The piecestore’s main goal is to help the
storage and
retrieval market modules to find where sealed data lives inside of sectors. The storage market writes the data, and retrieval market reads it in order to send out to retrieval clients.
The implementation of the PieceStore module can be found here.
Data Transfer in Filecoin
-
State
stable
-
Theory Audit
n/a
-
Edit this section
-
section-systems.filecoin_files.data_transfer
-
State
stable -
Theory Audit
n/a - Edit this section
-
section-systems.filecoin_files.data_transfer
The Data Transfer Protocol is a protocol for transferring all or part of a Piece across the network when a deal is made. The overall goal for the data transfer module is for it to be an abstraction of the underlying transport medium over which data is transferred between different parties in the Filecoin network. Currently, the underlying medium or protocol used to actually do the data transfer is GraphSync. As such, the Data Transfer Protocol can be thought of as a negotiation protocol.
The Data Transfer Protocol is used both for Storage and for Retrieval Deals. In both cases, the data transfer request is initiated by the client. The primary reason for this is that clients will more often than not be behind NATs and therefore, it is more convenient to start any data transfer from their side. In the case of Storage Deals the data transfer request is initiated as a push request to send data to the storage provider. In the case of Retrieval Deals the data transfer request is initiated as a pull request to retrieve data by the storage provider.
The request to initiate a data transfer includes a voucher or token (none to be confused with the Payment Channel voucher) that points to a specific deal that the two parties have agreed to before. This is so that the storage provider can identify and link the request to a deal it has agreed to and not disregard the request. As described below the case might be slightly different for retrieval deals, where both a deal proposal and a data transfer request can be sent at once.
Modules
-
State
stable
-
Theory Audit
n/a
-
Edit this section
-
section-systems.filecoin_files.data_transfer.modules
-
State
stable -
Theory Audit
n/a - Edit this section
-
section-systems.filecoin_files.data_transfer.modules
This diagram shows how Data Transfer and its modules fit into the picture with the Storage and Retrieval Markets. In particular, note how the Data Transfer Request Validators from the markets are plugged into the Data Transfer module, but their code belongs in the Markets system.
Terminology
-
State
stable
-
Theory Audit
n/a
-
Edit this section
-
section-systems.filecoin_files.data_transfer.terminology
-
State
stable -
Theory Audit
n/a - Edit this section
-
section-systems.filecoin_files.data_transfer.terminology
- Push Request: A request to send data to the other party - normally initiated by the client and primarily in case of a Storage Deal.
- Pull Request: A request to have the other party send data - normally initiated by the client and primarily in case of a Retrieval Deal.
- Requestor: The party that initiates the data transfer request (whether Push or Pull) - normally the client, at least as currently implemented in Filecoin, to overcome NAT-traversal problems.
- Responder: The party that receives the data transfer request - normally the storage provider.
- Data Transfer Voucher or Token: A wrapper around storage- or retrieval-related data that can identify and validate the transfer request to the other party.
- Request Validator: The data transfer module only initiates a transfer when the responder can validate that the request is tied directly to either an existing storage or retrieval deal. Validation is not performed by the data transfer module itself. Instead, a request validator inspects the data transfer voucher to determine whether to respond to the request or disregard the request.
- Transporter: Once a request is negotiated and validated, the actual transfer is managed by a transporter on both sides. The transporter is part of the data transfer module but is isolated from the negotiation process. It has access to an underlying verifiable transport protocol and uses it to send data and track progress.
- Subscriber: An external component that monitors progress of a data transfer by subscribing to data transfer events, such as progress or completion.
- GraphSync: The default underlying transport protocol used by the Transporter. The full graphsync specification can be found here
Request Phases
-
State
stable
-
Theory Audit
n/a
-
Edit this section
-
section-systems.filecoin_files.data_transfer.request-phases
-
State
stable -
Theory Audit
n/a - Edit this section
-
section-systems.filecoin_files.data_transfer.request-phases
There are two basic phases to any data transfer:
- Negotiation: the requestor and responder agree to the transfer by validating it with the data transfer voucher.
- Transfer: once the negotiation phase is complete, the data is actually transferred. The default protocol used to do the transfer is Graphsync.
Note that the Negotiation and Transfer stages can occur in separate round trips,
or potentially the same round trip, where the requesting party implicitly agrees by sending the request, and the responding party can agree and immediately send or receive data. Whether the process is taking place in a single or multiple round-trips depends in part on whether the request is a push request (storage deal) or a pull request (retrieval deal), and on whether the data transfer negotiation process is able to piggy back on the underlying transport mechanism.
In the case of GraphSync as transport mechanism, data transfer requests can piggy back as an extension to the GraphSync protocol using
GraphSync’s built-in extensibility. So, only a single round trip is required for Pull Requests. However, because Graphsync is a request/response protocol with no direct support for push type requests, in the Push case, negotiation happens in a seperate request over data transfer’s own libp2p protocol /fil/datatransfer/1.0.0. Other future transport mechanisms might handle both Push and Pull, either, or neither as a single round trip.
Upon receiving a data transfer request, the data transfer module does the decoding the voucher and delivers it to the request validators. In storage deals, the request validator checks if the deal included is one that the recipient has agreed to before. For retrieval deals the request includes the proposal for the retrieval deal itself. As long as request validator accepts the deal proposal, everything is done at once as a single round-trip.
It is worth noting that in the case of retrieval the provider can accept the deal and the data transfer request, but then pause the retrieval itself in order to carry out the unsealing process. The storage provider has to unseal all of the requested data before initiating the actual data transfer. Furthermore, the storage provider has the option of pausing the retrieval flow before starting the unsealing process in order to ask for an unsealing payment request. Storage providers have the option to request for this payment in order to cover unsealing computation costs and avoid falling victims of misbehaving clients.
Example Flows
-
State
stable
-
Theory Audit
n/a
-
Edit this section
-
section-systems.filecoin_files.data_transfer.example-flows
-
State
stable -
Theory Audit
n/a - Edit this section
-
section-systems.filecoin_files.data_transfer.example-flows
Push Flow
-
State
stable
-
Theory Audit
n/a
-
Edit this section
-
section-systems.filecoin_files.data_transfer.push-flow
-
State
stable -
Theory Audit
n/a - Edit this section
-
section-systems.filecoin_files.data_transfer.push-flow
- A requestor initiates a Push transfer when it wants to send data to another party.
- The requestors’ data transfer module will send a push request to the responder along with the data transfer voucher.
- The responder’s data transfer module validates the data transfer request via the Validator provided as a dependency by the responder.
- The responder’s data transfer module initiates the transfer by making a GraphSync request.
- The requestor receives the GraphSync request, verifies that it recognises the data transfer and begins sending data.
- The responder receives data and can produce an indication of progress.
- The responder completes receiving data, and notifies any listeners.
The push flow is ideal for storage deals, where the client initiates the data transfer straightaway once the provider indicates their intent to accept and publish the client’s deal proposal.
Pull Flow - Single Round Trip
-
State
stable
-
Theory Audit
n/a
-
Edit this section
-
section-systems.filecoin_files.data_transfer.pull-flow---single-round-trip
-
State
stable -
Theory Audit
n/a - Edit this section
-
section-systems.filecoin_files.data_transfer.pull-flow---single-round-trip
- A requestor initiates a Pull transfer when it wants to receive data from another party.
- The requestor’s data transfer module initiates the transfer by making a pull request embedded in the GraphSync request to the responder. The request includes the data transfer voucher.
- The responder receives the GraphSync request, and forwards the data transfer request to the data transfer module.
- The responder’s data transfer module validates the data transfer request via a PullValidator provided as a dependency by the responder.
- The responder accepts the GraphSync request and sends the accepted response along with the data transfer level acceptance response.
- The requestor receives data and can produce an indication of progress. This timing of this step comes later in time, after the storage provider has finished unsealing the data.
- The requestor completes receiving data, and notifies any listeners.
Protocol
-
State
stable
-
Theory Audit
n/a
-
Edit this section
-
section-systems.filecoin_files.data_transfer.protocol
-
State
stable -
Theory Audit
n/a - Edit this section
-
section-systems.filecoin_files.data_transfer.protocol
A data transfer CAN be negotiated over the network via the Data Transfer Protocol, a libp2p protocol type.
Using the Data Transfer Protocol as an independent libp2p communication mechanism is not a hard requirement – as long as both parties have an implementation of the Data Transfer Subsystem that can talk to the other, any transport mechanism (including offline mechanisms) is acceptable.
Data Structures
-
State
stable
-
Theory Audit
n/a
-
Edit this section
-
section-systems.filecoin_files.data_transfer.data-structures
-
State
stable -
Theory Audit
n/a - Edit this section
-
section-systems.filecoin_files.data_transfer.data-structures
package datatransfer
import (
"fmt"
"time"
"github.com/ipfs/go-cid"
"github.com/ipld/go-ipld-prime"
"github.com/ipld/go-ipld-prime/datamodel"
"github.com/libp2p/go-libp2p/core/peer"
cbg "github.com/whyrusleeping/cbor-gen"
)
//go:generate cbor-gen-for ChannelID ChannelStages ChannelStage Log
// TypeIdentifier is a unique string identifier for a type of encodable object in a
// registry
type TypeIdentifier string
// EmptyTypeIdentifier means there is no voucher present
const EmptyTypeIdentifier = TypeIdentifier("")
// TypedVoucher is a voucher or voucher result in IPLD form and an associated
// type identifier for that voucher or voucher result
type TypedVoucher struct {
Voucher datamodel.Node
Type TypeIdentifier
}
// Equals is a utility to compare that two TypedVouchers are the same - both type
// and the voucher's IPLD content
func (tv1 TypedVoucher) Equals(tv2 TypedVoucher) bool {
return tv1.Type == tv2.Type && ipld.DeepEqual(tv1.Voucher, tv2.Voucher)
}
// TransferID is an identifier for a data transfer, shared between
// request/responder and unique to the requester
type TransferID uint64
// ChannelID is a unique identifier for a channel, distinct by both the other
// party's peer ID + the transfer ID
type ChannelID struct {
Initiator peer.ID
Responder peer.ID
ID TransferID
}
func (c ChannelID) String() string {
return fmt.Sprintf("%s-%s-%d", c.Initiator, c.Responder, c.ID)
}
// OtherParty returns the peer on the other side of the request, depending
// on whether this peer is the initiator or responder
func (c ChannelID) OtherParty(thisPeer peer.ID) peer.ID {
if thisPeer == c.Initiator {
return c.Responder
}
return c.Initiator
}
// Channel represents all the parameters for a single data transfer
type Channel interface {
// TransferID returns the transfer id for this channel
TransferID() TransferID
// BaseCID returns the CID that is at the root of this data transfer
BaseCID() cid.Cid
// Selector returns the IPLD selector for this data transfer (represented as
// an IPLD node)
Selector() datamodel.Node
// Voucher returns the initial voucher for this data transfer
Voucher() TypedVoucher
// Sender returns the peer id for the node that is sending data
Sender() peer.ID
// Recipient returns the peer id for the node that is receiving data
Recipient() peer.ID
// TotalSize returns the total size for the data being transferred
TotalSize() uint64
// IsPull returns whether this is a pull request
IsPull() bool
// ChannelID returns the ChannelID for this request
ChannelID() ChannelID
// OtherPeer returns the counter party peer for this channel
OtherPeer() peer.ID
}
// ChannelState is channel parameters plus it's current state
type ChannelState interface {
Channel
// SelfPeer returns the peer this channel belongs to
SelfPeer() peer.ID
// Status is the current status of this channel
Status() Status
// Sent returns the number of bytes sent
Sent() uint64
// Received returns the number of bytes received
Received() uint64
// Message offers additional information about the current status
Message() string
// Vouchers returns all vouchers sent on this channel
Vouchers() []TypedVoucher
// VoucherResults are results of vouchers sent on the channel
VoucherResults() []TypedVoucher
// LastVoucher returns the last voucher sent on the channel
LastVoucher() TypedVoucher
// LastVoucherResult returns the last voucher result sent on the channel
LastVoucherResult() TypedVoucher
// ReceivedCidsTotal returns the number of (non-unique) cids received so far
// on the channel - note that a block can exist in more than one place in the DAG
ReceivedCidsTotal() int64
// QueuedCidsTotal returns the number of (non-unique) cids queued so far
// on the channel - note that a block can exist in more than one place in the DAG
QueuedCidsTotal() int64
// SentCidsTotal returns the number of (non-unique) cids sent so far
// on the channel - note that a block can exist in more than one place in the DAG
SentCidsTotal() int64
// Queued returns the number of bytes read from the node and queued for sending
Queued() uint64
// DataLimit is the maximum data that can be transferred on this channel before
// revalidation. 0 indicates no limit.
DataLimit() uint64
// RequiresFinalization indicates at the end of the transfer, the channel should
// be left open for a final settlement
RequiresFinalization() bool
// InitiatorPaused indicates whether the initiator of this channel is in a paused state
InitiatorPaused() bool
// ResponderPaused indicates whether the responder of this channel is in a paused state
ResponderPaused() bool
// BothPaused indicates both sides of the transfer have paused the transfer
BothPaused() bool
// SelfPaused indicates whether the local peer for this channel is in a paused state
SelfPaused() bool
// Stages returns the timeline of events this data transfer has gone through,
// for observability purposes.
//
// It is unsafe for the caller to modify the return value, and changes
// may not be persisted. It should be treated as immutable.
Stages() *ChannelStages
}
// ChannelStages captures a timeline of the progress of a data transfer channel,
// grouped by stages.
//
// EXPERIMENTAL; subject to change.
type ChannelStages struct {
// Stages contains an entry for every stage the channel has gone through.
// Each stage then contains logs.
Stages []*ChannelStage
}
// ChannelStage traces the execution of a data transfer channel stage.
//
// EXPERIMENTAL; subject to change.
type ChannelStage struct {
// Human-readable fields.
// TODO: these _will_ need to be converted to canonical representations, so
// they are machine readable.
Name string
Description string
// Timestamps.
// TODO: may be worth adding an exit timestamp. It _could_ be inferred from
// the start of the next stage, or from the timestamp of the last log line
// if this is a terminal stage. But that's non-determistic and it relies on
// assumptions.
CreatedTime cbg.CborTime
UpdatedTime cbg.CborTime
// Logs contains a detailed timeline of events that occurred inside
// this stage.
Logs []*Log
}
// Log represents a point-in-time event that occurred inside a channel stage.
//
// EXPERIMENTAL; subject to change.
type Log struct {
// Log is a human readable message.
//
// TODO: this _may_ need to be converted to a canonical data model so it
// is machine-readable.
Log string
UpdatedTime cbg.CborTime
}
// AddLog adds a log to the specified stage, creating the stage if
// it doesn't exist yet.
//
// EXPERIMENTAL; subject to change.
func (cs *ChannelStages) AddLog(stage, msg string) {
if cs == nil {
return
}
now := curTime()
st := cs.GetStage(stage)
if st == nil {
st = &ChannelStage{
CreatedTime: now,
}
cs.Stages = append(cs.Stages, st)
}
st.Name = stage
st.UpdatedTime = now
if msg != "" && (len(st.Logs) == 0 || st.Logs[len(st.Logs)-1].Log != msg) {
// only add the log if it's not a duplicate.
st.Logs = append(st.Logs, &Log{msg, now})
}
}
// GetStage returns the ChannelStage object for a named stage, or nil if not found.
//
// TODO: the input should be a strongly-typed enum instead of a free-form string.
// TODO: drop Get from GetStage to make this code more idiomatic. Return a
//
// second ok boolean to make it even more idiomatic.
//
// EXPERIMENTAL; subject to change.
func (cs *ChannelStages) GetStage(stage string) *ChannelStage {
if cs == nil {
return nil
}
for _, s := range cs.Stages {
if s.Name == stage {
return s
}
}
return nil
}
func curTime() cbg.CborTime {
now := time.Now()
return cbg.CborTime(time.Unix(0, now.UnixNano()).UTC())
}
package datatransfer
import "github.com/filecoin-project/go-statemachine/fsm"
// Status is the status of transfer for a given channel
type Status uint64
const (
// Requested means a data transfer was requested by has not yet been approved
Requested Status = iota
// Ongoing means the data transfer is in progress
Ongoing
// TransferFinished indicates the initiator is done sending/receiving
// data but is awaiting confirmation from the responder
TransferFinished
// ResponderCompleted indicates the initiator received a message from the
// responder that it's completed
ResponderCompleted
// Finalizing means the responder is awaiting a final message from the initator to
// consider the transfer done
Finalizing
// Completing just means we have some final cleanup for a completed request
Completing
// Completed means the data transfer is completed successfully
Completed
// Failing just means we have some final cleanup for a failed request
Failing
// Failed means the data transfer failed
Failed
// Cancelling just means we have some final cleanup for a cancelled request
Cancelling
// Cancelled means the data transfer ended prematurely
Cancelled
// DEPRECATED: Use InitiatorPaused() method on ChannelState
InitiatorPaused
// DEPRECATED: Use ResponderPaused() method on ChannelState
ResponderPaused
// DEPRECATED: Use BothPaused() method on ChannelState
BothPaused
// ResponderFinalizing is a unique state where the responder is awaiting a final voucher
ResponderFinalizing
// ResponderFinalizingTransferFinished is a unique state where the responder is awaiting a final voucher
// and we have received all data
ResponderFinalizingTransferFinished
// ChannelNotFoundError means the searched for data transfer does not exist
ChannelNotFoundError
// Queued indicates a data transfer request has been accepted, but is not actively transfering yet
Queued
// AwaitingAcceptance indicates a transfer request is actively being processed by the transport
// even if the remote has not yet responded that it's accepted the transfer. Such a state can
// occur, for example, in a requestor-initiated transfer that starts processing prior to receiving
// acceptance from the server.
AwaitingAcceptance
)
type statusList []Status
func (sl statusList) Contains(s Status) bool {
for _, ts := range sl {
if ts == s {
return true
}
}
return false
}
func (sl statusList) AsFSMStates() []fsm.StateKey {
sk := make([]fsm.StateKey, 0, len(sl))
for _, s := range sl {
sk = append(sk, s)
}
return sk
}
var NotAcceptedStates = statusList{
Requested,
AwaitingAcceptance,
Cancelled,
Cancelling,
Failed,
Failing,
ChannelNotFoundError}
func (s Status) IsAccepted() bool {
return !NotAcceptedStates.Contains(s)
}
func (s Status) String() string {
return Statuses[s]
}
var FinalizationStatuses = statusList{Finalizing, Completed, Completing}
func (s Status) InFinalization() bool {
return FinalizationStatuses.Contains(s)
}
var TransferCompleteStates = statusList{
TransferFinished,
ResponderFinalizingTransferFinished,
Finalizing,
Completed,
Completing,
Failing,
Failed,
Cancelling,
Cancelled,
ChannelNotFoundError,
}
func (s Status) TransferComplete() bool {
return TransferCompleteStates.Contains(s)
}
var TransferringStates = statusList{
Ongoing,
ResponderCompleted,
ResponderFinalizing,
AwaitingAcceptance,
}
func (s Status) Transferring() bool {
return TransferringStates.Contains(s)
}
// Statuses are human readable names for data transfer states
var Statuses = map[Status]string{
// Requested means a data transfer was requested by has not yet been approved
Requested: "Requested",
Ongoing: "Ongoing",
TransferFinished: "TransferFinished",
ResponderCompleted: "ResponderCompleted",
Finalizing: "Finalizing",
Completing: "Completing",
Completed: "Completed",
Failing: "Failing",
Failed: "Failed",
Cancelling: "Cancelling",
Cancelled: "Cancelled",
InitiatorPaused: "InitiatorPaused",
ResponderPaused: "ResponderPaused",
BothPaused: "BothPaused",
ResponderFinalizing: "ResponderFinalizing",
ResponderFinalizingTransferFinished: "ResponderFinalizingTransferFinished",
ChannelNotFoundError: "ChannelNotFoundError",
Queued: "Queued",
AwaitingAcceptance: "AwaitingAcceptance",
}
type Manager interface {
// Start initializes data transfer processing
Start(ctx context.Context) error
// OnReady registers a listener for when the data transfer comes on line
OnReady(ReadyFunc)
// Stop terminates all data transfers and ends processing
Stop(ctx context.Context) error
// RegisterVoucherType registers a validator for the given voucher type
// will error if voucher type does not implement voucher
// or if there is a voucher type registered with an identical identifier
RegisterVoucherType(voucherType TypeIdentifier, validator RequestValidator) error
// RegisterTransportConfigurer registers the given transport configurer to be run on requests with the given voucher
// type
RegisterTransportConfigurer(voucherType TypeIdentifier, configurer TransportConfigurer) error
// open a data transfer that will send data to the recipient peer and
// transfer parts of the piece that match the selector
OpenPushDataChannel(ctx context.Context, to peer.ID, voucher TypedVoucher, baseCid cid.Cid, selector datamodel.Node, options ...TransferOption) (ChannelID, error)
// open a data transfer that will request data from the sending peer and
// transfer parts of the piece that match the selector
OpenPullDataChannel(ctx context.Context, to peer.ID, voucher TypedVoucher, baseCid cid.Cid, selector datamodel.Node, options ...TransferOption) (ChannelID, error)
// send an intermediate voucher as needed when the receiver sends a request for revalidation
SendVoucher(ctx context.Context, chid ChannelID, voucher TypedVoucher) error
// send information from the responder to update the initiator on the state of their voucher
SendVoucherResult(ctx context.Context, chid ChannelID, voucherResult TypedVoucher) error
// Update the validation status for a given channel, to change data limits, finalization, accepted status, and pause state
// and send new voucher results as
UpdateValidationStatus(ctx context.Context, chid ChannelID, validationResult ValidationResult) error
// close an open channel (effectively a cancel)
CloseDataTransferChannel(ctx context.Context, chid ChannelID) error
// pause a data transfer channel (only allowed if transport supports it)
PauseDataTransferChannel(ctx context.Context, chid ChannelID) error
// resume a data transfer channel (only allowed if transport supports it)
ResumeDataTransferChannel(ctx context.Context, chid ChannelID) error
// get status of a transfer
TransferChannelStatus(ctx context.Context, x ChannelID) Status
// get channel state
ChannelState(ctx context.Context, chid ChannelID) (ChannelState, error)
// get notified when certain types of events happen
SubscribeToEvents(subscriber Subscriber) Unsubscribe
// get all in progress transfers
InProgressChannels(ctx context.Context) (map[ChannelID]ChannelState, error)
// RestartDataTransferChannel restarts an existing data transfer channel
RestartDataTransferChannel(ctx context.Context, chid ChannelID) error
}
Data Formats and Serialization
-
State
reliable
-
Theory Audit
n/a
-
Edit this section
-
section-systems.filecoin_files.serialization
-
State
reliable -
Theory Audit
n/a - Edit this section
-
section-systems.filecoin_files.serialization
Filecoin seeks to make use of as few data formats as needed, with well-specced serialization rules to better protocol security through simplicity and enable interoperability amongst implementations of the Filecoin protocol.
Read more on design considerations here for CBOR-usage and here for int types in Filecoin.
Data Formats
-
State
reliable
-
Theory Audit
n/a
-
Edit this section
-
section-systems.filecoin_files.serialization.data-formats
-
State
reliable -
Theory Audit
n/a - Edit this section
-
section-systems.filecoin_files.serialization.data-formats
Filecoin in-memory data types are mostly straightforward. Implementations should support two integer types: Int (meaning native 64-bit integer), and BigInt (meaning arbitrary length) and avoid dealing with floating-point numbers to minimize interoperability issues across programming languages and implementations.
You can also read more on data formats as part of randomness generation in the Filecoin protocol.
Serialization
-
State
reliable
-
Theory Audit
n/a
-
Edit this section
-
section-systems.filecoin_files.serialization.serialization
-
State
reliable -
Theory Audit
n/a - Edit this section
-
section-systems.filecoin_files.serialization.serialization
Data Serialization in Filecoin ensures a consistent format for serializing in-memory data for transfer
in-flight and in-storage. Serialization is critical to protocol security and interoperability across
implementations of the Filecoin protocol, enabling consistent state updates across Filecoin nodes.
All data structures in Filecoin are CBOR-tuple encoded. That is, any data structures used in the Filecoin system (structs in this spec) should be serialized as CBOR-arrays with items corresponding to the data structure fields in their order of declaration.
You can find the encoding structure for major data types in CBOR here.
For illustration, an in-memory map would be represented as a CBOR-array of the keys and values listed in some pre-determined order. A near-term update to the serialization format will involve tagging fields appropriately to ensure appropriate serialization/deserialization as the protocol evolves.
Virtual Machine
-
State
reliable
-
Theory Audit
n/a
-
Edit this section
-
section-systems.filecoin_vm
-
State
reliable -
Theory Audit
n/a - Edit this section
-
section-systems.filecoin_vm
An Actor in the Filecoin Blockchain is the equivalent of the smart contract in the Ethereum Virtual Machine.
The Filecoin Virtual Machine (VM) is the system component that is in charge of execution of all actors code. Execution of actors on the Filecoin VM (i.e., on-chain executions) incur a gas cost.
Any operation applied (i.e., executed) on the Filecoin VM produces an output in the form of a State Tree (discussed below). The latest State Tree is the current source of truth in the Filecoin Blockchain. The State Tree is identified by a CID, which is stored in the IPLD store.
VM Actor Interface
-
State
reliable
-
Theory Audit
wip
-
Edit this section
-
section-systems.filecoin_vm.actor
-
State
reliable -
Theory Audit
wip - Edit this section
-
section-systems.filecoin_vm.actor
As mentioned above, Actors are the Filecoin equivalent of smart contracts in the Ethereum Virtual Machine. As such, Actors are very central components of the system. Any change to the current state of the Filecoin blockchain has to be triggered through an actor method invocation.
This sub-section describes the interface between Actors and the Filecoin Virtual Machine. This means that most of what is described below does not strictly belong to the VM. Instead it is logic that sits on the interface between the VM and Actors logic.
There are eleven (11) types of builtin Actors in total, not all of which interact with the VM. Some Actors do not invoke changes to the StateTree of the blockchain and therefore, do not need to have an interface to the VM. We discuss the details of all System Actors later on in the System Actors subsection.
The actor address is a stable address generated by hashing the sender’s public key and a creation nonce. It should be stable across chain re-organizations. The actor ID address on the other hand, is an auto-incrementing address that is compact but can change in case of chain re-organizations. That being said, after being created, actors should use an actor address.
package builtin
import (
addr "github.com/filecoin-project/go-address"
)
// Addresses for singleton system actors.
var (
// Distinguished AccountActor that is the source of system implicit messages.
SystemActorAddr = mustMakeAddress(0)
InitActorAddr = mustMakeAddress(1)
RewardActorAddr = mustMakeAddress(2)
CronActorAddr = mustMakeAddress(3)
StoragePowerActorAddr = mustMakeAddress(4)
StorageMarketActorAddr = mustMakeAddress(5)
VerifiedRegistryActorAddr = mustMakeAddress(6)
// Distinguished AccountActor that is the destination of all burnt funds.
BurntFundsActorAddr = mustMakeAddress(99)
)
const FirstNonSingletonActorId = 100
func mustMakeAddress(id uint64) addr.Address {
address, err := addr.NewIDAddress(id)
if err != nil {
panic(err)
}
return address
}
The ActorState structure is composed of the actor’s balance, in terms of tokens held by this actor, as well as a group of state methods used to query, inspect and interact with chain state.
State Tree
-
State
reliable
-
Theory Audit
wip
-
Edit this section
-
section-systems.filecoin_vm.state_tree
-
State
reliable -
Theory Audit
wip - Edit this section
-
section-systems.filecoin_vm.state_tree
The State Tree is the output of the execution of any operation applied on the Filecoin Blockchain. The on-chain (i.e., VM) state data structure is a map (in the form of a Hash Array Mapped Trie - HAMT) that binds addresses to actor states. The current State Tree function is called by the VM upon every actor method invocation.
type StateTree struct {
root adt.Map
version types.StateTreeVersion
info cid.Cid
Store cbor.IpldStore
lookupIDFun func(address.Address) (address.Address, error)
snaps *stateSnaps
}
VM Message - Actor Method Invocation
-
State
reliable
-
Theory Audit
wip
-
Edit this section
-
section-systems.filecoin_vm.message
-
State
reliable -
Theory Audit
wip - Edit this section
-
section-systems.filecoin_vm.message
A message is the unit of communication between two actors, and thus the primitive cause of changes in state. A message combines:
- a token amount to be transferred from the sender to the receiver, and
- a method with parameters to be invoked on the receiver (optional/where applicable).
Actor code may send additional messages to other actors while processing a received message. Messages are processed synchronously, that is, an actor waits for a sent message to complete before resuming control.
The processing of a message consumes units of computation and storage, both of which are denominated in gas. A message’s gas limit provides an upper bound on the computation required to process it. The sender of a message pays for the gas units consumed by a message’s execution (including all nested messages) at a gas price they determine. A block producer chooses which messages to include in a block and is rewarded according to each message’s gas price and consumption, forming a market.
Message syntax validation
-
State
reliable
-
Theory Audit
wip
-
Edit this section
-
section-systems.filecoin_vm.message.message-syntax-validation
-
State
reliable -
Theory Audit
wip - Edit this section
-
section-systems.filecoin_vm.message.message-syntax-validation
A syntactically invalid message must not be transmitted, retained in a message pool, or included in a block. If an invalid message is received, it should be dropped and not propagated further.
When transmitted individually (before inclusion in a block), a message is packaged as
SignedMessage, regardless of signature scheme used. A valid signed message has a total serialized size no greater than message.MessageMaxSize.
type SignedMessage struct {
Message Message
Signature crypto.Signature
}
A syntactically valid UnsignedMessage:
- has a well-formed, non-empty
Toaddress, - has a well-formed, non-empty
Fromaddress, - has
Valueno less than zero and no greater than the total token supply (2e9 * 1e18), and - has non-negative
GasPrice, - has
GasLimitthat is at least equal to the gas consumption associated with the message’s serialized bytes, - has
GasLimitthat is no greater than the block gas limit network parameter.
type Message struct {
// Version of this message (has to be non-negative)
Version uint64
// Address of the receiving actor.
To address.Address
// Address of the sending actor.
From address.Address
CallSeqNum uint64
// Value to transfer from sender's to receiver's balance.
Value BigInt
// GasPrice is a Gas-to-FIL cost
GasPrice BigInt
// Maximum Gas to be spent on the processing of this message
GasLimit int64
// Optional method to invoke on receiver, zero for a plain value transfer.
Method abi.MethodNum
//Serialized parameters to the method.
Params []byte
}
There should be several functions able to extract information from the Message struct, such as the sender and recipient addresses, the value to be transferred, the required funds to execute the message and the CID of the message.
Given that Messages should eventually be included in a Block and added to the blockchain, the validity of the message should be checked with regard to the sender and the receiver of the message, the value (which should be non-negative and always smaller than the circulating supply), the gas price (which again should be non-negative) and the BlockGasLimit which should not be greater than the block’s gas limit.
Message semantic validation
-
State
reliable
-
Theory Audit
wip
-
Edit this section
-
section-systems.filecoin_vm.message.message-semantic-validation
-
State
reliable -
Theory Audit
wip - Edit this section
-
section-systems.filecoin_vm.message.message-semantic-validation
Semantic validation refers to validation requiring information outside of the message itself.
A semantically valid SignedMessage must carry a signature that verifies the payload as having
been signed with the public key of the account actor identified by the From address.
Note that when the From address is an ID-address, the public key must be
looked up in the state of the sending account actor in the parent state identified by the block.
Note: the sending actor must exist in the parent state identified by the block that includes the message. This means that it is not valid for a single block to include a message that creates a new account actor and a message from that same actor. The first message from that actor must wait until a subsequent epoch. Message pools may exclude messages from an actor that is not yet present in the chain state.
There is no further semantic validation of a message that can cause a block including the message
to be invalid. Every syntactically valid and correctly signed message can be included in a block and
will produce a receipt from execution. The MessageReceipt sturct includes the following:
type MessageReceipt struct {
ExitCode exitcode.ExitCode
Return []byte
GasUsed int64
}
However, a message may fail to execute to completion, in which case it will not trigger the desired state change.
The reason for this “no message semantic validation” policy is that the state that a message will be applied to cannot be known before the message is executed as part of a tipset. A block producer does not know whether another block will precede it in the tipset, thus altering the state to which the block’s messages will apply from the declared parent state.
package types
import (
"bytes"
"encoding/json"
"fmt"
block "github.com/ipfs/go-block-format"
"github.com/ipfs/go-cid"
"golang.org/x/xerrors"
"github.com/filecoin-project/go-address"
"github.com/filecoin-project/go-state-types/abi"
"github.com/filecoin-project/go-state-types/big"
"github.com/filecoin-project/go-state-types/network"
"github.com/filecoin-project/lotus/build/buildconstants"
)
const MessageVersion = 0
type ChainMsg interface {
Cid() cid.Cid
VMMessage() *Message
ToStorageBlock() (block.Block, error)
// FIXME: This is the *message* length, this name is misleading.
ChainLength() int
}
type Message struct {
Version uint64
To address.Address
From address.Address
Nonce uint64
Value abi.TokenAmount
GasLimit int64
GasFeeCap abi.TokenAmount
GasPremium abi.TokenAmount
Method abi.MethodNum
Params []byte
}
func (m *Message) Caller() address.Address {
return m.From
}
func (m *Message) Receiver() address.Address {
return m.To
}
func (m *Message) ValueReceived() abi.TokenAmount {
return m.Value
}
func DecodeMessage(b []byte) (*Message, error) {
var msg Message
if err := msg.UnmarshalCBOR(bytes.NewReader(b)); err != nil {
return nil, err
}
if msg.Version != MessageVersion {
return nil, fmt.Errorf("decoded message had incorrect version (%d)", msg.Version)
}
return &msg, nil
}
func (m *Message) Serialize() ([]byte, error) {
buf := new(bytes.Buffer)
if err := m.MarshalCBOR(buf); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
func (m *Message) ChainLength() int {
ser, err := m.Serialize()
if err != nil {
panic(err)
}
return len(ser)
}
func (m *Message) ToStorageBlock() (block.Block, error) {
data, err := m.Serialize()
if err != nil {
return nil, err
}
c, err := abi.CidBuilder.Sum(data)
if err != nil {
return nil, err
}
return block.NewBlockWithCid(data, c)
}
func (m *Message) Cid() cid.Cid {
b, err := m.ToStorageBlock()
if err != nil {
panic(fmt.Sprintf("failed to marshal message: %s", err)) // I think this is maybe sketchy, what happens if we try to serialize a message with an undefined address in it?
}
return b.Cid()
}
type mCid struct {
*RawMessage
CID cid.Cid
}
type RawMessage Message
func (m *Message) MarshalJSON() ([]byte, error) {
return json.Marshal(&mCid{
RawMessage: (*RawMessage)(m),
CID: m.Cid(),
})
}
func (m *Message) RequiredFunds() BigInt {
return BigMul(m.GasFeeCap, NewInt(uint64(m.GasLimit)))
}
func (m *Message) VMMessage() *Message {
return m
}
func (m *Message) Equals(o *Message) bool {
return m.Cid() == o.Cid()
}
func (m *Message) EqualCall(o *Message) bool {
m1 := *m
m2 := *o
m1.GasLimit, m2.GasLimit = 0, 0
m1.GasFeeCap, m2.GasFeeCap = big.Zero(), big.Zero()
m1.GasPremium, m2.GasPremium = big.Zero(), big.Zero()
return (&m1).Equals(&m2)
}
func (m *Message) ValidForBlockInclusion(minGas int64, version network.Version) error {
if m.Version != 0 {
return xerrors.New("'Version' unsupported")
}
if m.To == address.Undef {
return xerrors.New("'To' address cannot be empty")
}
if m.To == buildconstants.ZeroAddress && version >= network.Version7 {
return xerrors.New("invalid 'To' address")
}
if !abi.AddressValidForNetworkVersion(m.To, version) {
return xerrors.New("'To' address protocol unsupported for network version")
}
if m.From == address.Undef {
return xerrors.New("'From' address cannot be empty")
}
if !abi.AddressValidForNetworkVersion(m.From, version) {
return xerrors.New("'From' address protocol unsupported for network version")
}
if m.Value.Int == nil {
return xerrors.New("'Value' cannot be nil")
}
if m.Value.LessThan(big.Zero()) {
return xerrors.New("'Value' field cannot be negative")
}
if m.Value.GreaterThan(TotalFilecoinInt) {
return xerrors.New("'Value' field cannot be greater than total filecoin supply")
}
if m.GasFeeCap.Int == nil {
return xerrors.New("'GasFeeCap' cannot be nil")
}
if m.GasFeeCap.LessThan(big.Zero()) {
return xerrors.New("'GasFeeCap' field cannot be negative")
}
if m.GasPremium.Int == nil {
return xerrors.New("'GasPremium' cannot be nil")
}
if m.GasPremium.LessThan(big.Zero()) {
return xerrors.New("'GasPremium' field cannot be negative")
}
if m.GasPremium.GreaterThan(m.GasFeeCap) {
return xerrors.New("'GasFeeCap' less than 'GasPremium'")
}
if m.GasLimit > buildconstants.BlockGasLimit {
return xerrors.Errorf("'GasLimit' field cannot be greater than a block's gas limit (%d > %d)", m.GasLimit, buildconstants.BlockGasLimit)
}
if m.GasLimit <= 0 {
return xerrors.Errorf("'GasLimit' field %d must be positive", m.GasLimit)
}
// since prices might vary with time, this is technically semantic validation
if m.GasLimit < minGas {
return xerrors.Errorf("'GasLimit' field cannot be less than the cost of storing a message on chain %d < %d", m.GasLimit, minGas)
}
return nil
}
// EffectiveGasPremium returns the effective gas premium claimable by the miner
// given the supplied base fee. This method is not used anywhere except the Eth API.
//
// Filecoin clamps the gas premium at GasFeeCap - BaseFee, if lower than the
// specified premium. Returns 0 if GasFeeCap is less than BaseFee.
func (m *Message) EffectiveGasPremium(baseFee abi.TokenAmount) abi.TokenAmount {
available := big.Sub(m.GasFeeCap, baseFee)
// It's possible that storage providers may include messages with gasFeeCap less than the baseFee
// In such cases, their reward should be viewed as zero
if available.LessThan(big.NewInt(0)) {
available = big.NewInt(0)
}
if big.Cmp(m.GasPremium, available) <= 0 {
return m.GasPremium
}
return available
}
const TestGasLimit = 100e6
VM Runtime Environment (Inside the VM)
-
State
reliable
-
Theory Audit
n/a
-
Edit this section
-
section-systems.filecoin_vm.runtime
-
State
reliable -
Theory Audit
n/a - Edit this section
-
section-systems.filecoin_vm.runtime
Receipts
-
State
reliable
-
Theory Audit
n/a
-
Edit this section
-
section-systems.filecoin_vm.runtime.receipts
-
State
reliable -
Theory Audit
n/a - Edit this section
-
section-systems.filecoin_vm.runtime.receipts
A MessageReceipt contains the result of a top-level message execution. Every syntactically valid and correctly signed message can be included in a block and will produce a receipt from execution.
A syntactically valid receipt has:
- a non-negative
ExitCode, - a non empty
Returnvalue only if the exit code is zero, and - a non-negative
GasUsed.
type MessageReceipt struct {
ExitCode exitcode.ExitCode
Return []byte
GasUsed int64
}
vm/runtime Actors Interface
-
State
reliable
-
Theory Audit
n/a
-
Edit this section
-
section-systems.filecoin_vm.runtime.vmruntime-actors-interface
-
State
reliable -
Theory Audit
n/a - Edit this section
-
section-systems.filecoin_vm.runtime.vmruntime-actors-interface
The Actors Interface implementation can be found here
vm/runtime VM Implementation
-
State
reliable
-
Theory Audit
n/a
-
Edit this section
-
section-systems.filecoin_vm.runtime.vmruntime-vm-implementation
-
State
reliable -
Theory Audit
n/a - Edit this section
-
section-systems.filecoin_vm.runtime.vmruntime-vm-implementation
The Lotus implementation of the Filecoin Virtual Machine runtime can be found here
Exit Codes
-
State
reliable
-
Theory Audit
n/a
-
Edit this section
-
section-systems.filecoin_vm.runtime.exit-codes
-
State
reliable -
Theory Audit
n/a - Edit this section
-
section-systems.filecoin_vm.runtime.exit-codes
There are some common runtime exit codes that are shared by different actors. Their definition can be found here.
Gas Fees
-
State
reliable
-
Theory Audit
coming
-
Edit this section
-
section-systems.filecoin_vm.gas_fee
-
State
reliable -
Theory Audit
coming - Edit this section
-
section-systems.filecoin_vm.gas_fee
Summary
-
State
reliable
-
Theory Audit
coming
-
Edit this section
-
section-systems.filecoin_vm.gas_fee.summary
-
State
reliable -
Theory Audit
coming - Edit this section
-
section-systems.filecoin_vm.gas_fee.summary
As is traditionally the case with many blockchains, Gas is a unit of measure of how much storage and/or compute resource an on-chain message operation consumes in order to be executed. At a high level, it works as follows: the message sender specifies the maximum amount they are willing to pay in order for their message to be executed and included in a block. This is specified both in terms of total number of units of gas (GasLimit), which is generally expected to be higher than the actual GasUsed and in terms of the price (or fee) per unit of gas (GasFeeCap).
Traditionally, GasUsed * GasFeeCap goes to the block producing miner as a reward. The result of this product is treated as the priority fee for message inclusion, that is, messages are ordered in decreasing sequence and those with the highest GasUsed * GasFeeCap are prioritised, given that they return more profit to the miner.
However, it has been observed that this tactic (of paying GasUsed * GasFee) is problematic for block producing miners for a few reasons. Firstly, a block producing miner may include a very expensive message (in terms of chain resources required) for free in which case the chain itself needs to bear the cost. Secondly, message senders can set arbitrarily high prices but for low-cost messages (again, in terms of chain resources), leading to a DoS vulnerability.
In order to overcome this situation, the Filecoin blockchain defines a BaseFee, which is burnt for every message. The rationale is that given that Gas is a measure of on-chain resource consumption, it makes sense for it to be burned, as compared to be rewarded to miners. This way, fee manipulation from miners is avoided. The BaseFee is dynamic, adjusted automatically according to network congestion. This fact, makes the network resilient against spam attacks. Given that the network load increases during SPAM attacks, maintaining full blocks of SPAM messages for an extended period of time is impossible for an attacker due to the increasing BaseFee.
Finally, GasPremium is the priority fee included by senders to incentivize miners to pick the most profitable messages. In other words, if a message sender wants its message to be included more quickly, they can set a higher GasPremium.
Parameters
-
State
reliable
-
Theory Audit
coming
-
Edit this section
-
section-systems.filecoin_vm.gas_fee.parameters
-
State
reliable -
Theory Audit
coming - Edit this section
-
section-systems.filecoin_vm.gas_fee.parameters
GasUsedis a measure of the amount of resources (or units of gas) consumed, in order to execute a message. Each unit of gas is measured in attoFIL and therefore,GasUsedis a number that represents the units of energy consumed.GasUsedis independent of whether a message was executed correctly or failed.BaseFeeis the set price per unit of gas (measured in attoFIL/gas unit) to be burned (sent to an unrecoverable address) for every message execution. The value of theBaseFeeis dynamic and adjusts according to current network congestion parameters. For example, when the network exceeds 5B gas limit usage, theBaseFeeincreases and the opposite happens when gas limit usage falls below 5B. TheBaseFeeapplied to each block should be included in the block itself. It should be possible to get the value of the currentBaseFeefrom the head of the chain. TheBaseFeeapplies per unit ofGasUsedand therefore, the total amount of gas burned for a message isBaseFee * GasUsed. Note that theBaseFeeis incurred for every message, but its value is the same for all messages in the same block.GasLimitis measured in units of gas and set by the message sender. It imposes a hard limit on the amount of gas (i.e., number of units of gas) that a message’s execution should be allowed to consume on chain. A message consumes gas for every fundamental operation it triggers, and a message that runs out of gas fails. When a message fails, every modification to the state that happened as a result of this message’s execution is reverted back to its previous state. Independently of whether a message execution was successful or not, the miner will receive a reward for the resources they consumed to execute the message (seeGasPremiumbelow).GasFeeCapis the maximum price that the message sender is willing to pay per unit of gas (measured in attoFIL/gas unit). Together with theGasLimit, theGasFeeCapis setting the maximum amount of FIL that a sender will pay for a message: a sender is guaranteed that a message will never cost them more thanGasLimit * GasFeeCapattoFIL (not including any Premium that the message includes for its recipient).GasPremiumis the price per unit of gas (measured in attoFIL/gas) that the message sender is willing to pay (on top of theBaseFee) to “tip” the miner that will include this message in a block. A message typically earns its minerGasLimit * GasPremiumattoFIL, where effectivelyGasPremium = GasFeeCap - BaseFee. Note thatGasPremiumis applied onGasLimit, as opposed toGasUsed, in order to make message selection for miners more straightforward.
func ComputeGasOverestimationBurn(gasUsed, gasLimit int64) (int64, int64) {
if gasUsed == 0 {
return 0, gasLimit
}
// over = gasLimit/gasUsed - 1 - 0.1
// over = min(over, 1)
// gasToBurn = (gasLimit - gasUsed) * over
// so to factor out division from `over`
// over*gasUsed = min(gasLimit - (11*gasUsed)/10, gasUsed)
// gasToBurn = ((gasLimit - gasUsed)*over*gasUsed) / gasUsed
over := gasLimit - (gasOveruseNum*gasUsed)/gasOveruseDenom
if over < 0 {
return gasLimit - gasUsed, 0
}
// if we want sharper scaling it goes here:
// over *= 2
if over > gasUsed {
over = gasUsed
}
// needs bigint, as it overflows in pathological case gasLimit > 2^32 gasUsed = gasLimit / 2
gasToBurn := big.NewInt(gasLimit - gasUsed)
gasToBurn = big.Mul(gasToBurn, big.NewInt(over))
gasToBurn = big.Div(gasToBurn, big.NewInt(gasUsed))
return gasLimit - gasUsed - gasToBurn.Int64(), gasToBurn.Int64()
}func ComputeNextBaseFee(baseFee types.BigInt, gasLimitUsed int64, noOfBlocks int, epoch abi.ChainEpoch) types.BigInt {
// deta := gasLimitUsed/noOfBlocks - buildconstants.BlockGasTarget
// change := baseFee * deta / BlockGasTarget
// nextBaseFee = baseFee + change
// nextBaseFee = max(nextBaseFee, buildconstants.MinimumBaseFee)
var delta int64
if epoch > buildconstants.UpgradeSmokeHeight {
delta = gasLimitUsed / int64(noOfBlocks)
delta -= buildconstants.BlockGasTarget
} else {
delta = buildconstants.PackingEfficiencyDenom * gasLimitUsed / (int64(noOfBlocks) * buildconstants.PackingEfficiencyNum)
delta -= buildconstants.BlockGasTarget
}
// cap change at 12.5% (BaseFeeMaxChangeDenom) by capping delta
if delta > buildconstants.BlockGasTarget {
delta = buildconstants.BlockGasTarget
}
if delta < -buildconstants.BlockGasTarget {
delta = -buildconstants.BlockGasTarget
}
change := big.Mul(baseFee, big.NewInt(delta))
change = big.Div(change, big.NewInt(buildconstants.BlockGasTarget))
change = big.Div(change, big.NewInt(buildconstants.BaseFeeMaxChangeDenom))
nextBaseFee := big.Add(baseFee, change)
if big.Cmp(nextBaseFee, big.NewInt(buildconstants.MinimumBaseFee)) < 0 {
nextBaseFee = big.NewInt(buildconstants.MinimumBaseFee)
}
return nextBaseFee
}
Notes & Implications
-
State
reliable
-
Theory Audit
coming
-
Edit this section
-
section-systems.filecoin_vm.gas_fee.notes--implications
-
State
reliable -
Theory Audit
coming - Edit this section
-
section-systems.filecoin_vm.gas_fee.notes--implications
-
The
GasFeeCapshould always be higher than the network’sBaseFee. If a message’sGasFeeCapis lower than theBaseFee, then the remainder comes from the miner (as a penalty). This penalty is applied to the miner because they have selected a message that pays less than the networkBaseFee(i.e., does not cover the network costs). However, a miner might want to choose a message whoseGasFeeCapis smaller than theBaseFeeif the same sender has another message in the message pool whoseGasFeeCapis much bigger than theBaseFee. Recall, that a miner should pick all the messages of a sender from the message pool, if more than one exists. The justification is that the increased fee of the second message will pay off the loss from the first. -
If
BaseFee + GasPremium>GasFeeCap, then the miner might not earn the entireGasLimit * GasPremiumas their reward. -
A message is hard-constrained to spending no more than
GasFeeCap * GasLimit. From this amount, the networkBaseFeeis paid (burnt) first. After that, up toGasLimit * GasPremiumwill be given to the miner as a reward. -
A message that runs out of gas fails with an “out of gas” exit code.
GasUsed * BaseFeewill still be burned (in this caseGasUsed = GasLimit), and the miner will still be rewardedGasLimit * GasPremium. This assumes thatGasFeeCap > BaseFee + GasPremium. -
A low value for the
GasFeeCapwill likely cause the message to be stuck in the message pool, as it will not be attractive-enough in terms of profit for any miner to pick it and include it in a block. When this happens, there is a procedure to update theGasFeeCapso that the message becomes more attractive to miners. The sender can push a new message into the message pool (which, by default, will propagate to other miners’ message pool) where: i) the identifier of the old and new messages is the same (e.g., sameNonce) and ii) theGasPremiumis updated and increased by at least 25% of the previous value.
System Actors
-
State
reliable
-
Theory Audit
done
-
Edit this section
-
section-systems.filecoin_vm.sysactors
-
State
reliable -
Theory Audit
done - Edit this section
-
section-systems.filecoin_vm.sysactors
There are eleven (11) builtin System Actors in total, but not all of them interact with the VM. Each actor is identified by a Code ID (or CID).
There are four (4) system actors required for VM processing:
- the InitActor, which initializes new actors and records the network name, and
- the CronActor, a scheduler actor that runs critical functions at every epoch.
There are another two actors that interact with the VM:
- the AccountActor responsible for user accounts (non-singleton), and
- the RewardActor for block reward and token vesting (singleton).
The remaining seven (7) builtin System Actors that do not interact directly with the VM are the following:
StorageMarketActor: responsible for managing storage and retrieval deals [ Market Actor Repo]StorageMinerActor: actor responsible to deal with storage mining operations and collect proofs [ Storage Miner Actor Repo]MultisigActor(or Multi-Signature Wallet Actor): responsible for dealing with operations involving the Filecoin wallet [ Multisig Actor Repo]PaymentChannelActor: responsible for setting up and settling funds related to payment channels [ Paych Actor Repo]StoragePowerActor: responsible for keeping track of the storage power allocated at each storage miner [ Storage Power Actor]VerifiedRegistryActor: responsible for managing verified clients [ Verifreg Actor Repo]SystemActor: general system actor [ System Actor Repo]
CronActor
-
State
reliable
-
Theory Audit
done
-
Edit this section
-
section-systems.filecoin_vm.sysactors.cronactor
-
State
reliable -
Theory Audit
done - Edit this section
-
section-systems.filecoin_vm.sysactors.cronactor
Built in to the genesis state, the CronActor’s dispatch table invokes the StoragePowerActor and StorageMarketActor for them to maintain internal state and process deferred events. It could in principle invoke other actors after a network upgrade.
package cron
import (
"github.com/filecoin-project/go-state-types/abi"
"github.com/filecoin-project/go-state-types/cbor"
rtt "github.com/filecoin-project/go-state-types/rt"
cron0 "github.com/filecoin-project/specs-actors/actors/builtin/cron"
"github.com/ipfs/go-cid"
"github.com/filecoin-project/specs-actors/v8/actors/builtin"
"github.com/filecoin-project/specs-actors/v8/actors/runtime"
)
// The cron actor is a built-in singleton that sends messages to other registered actors at the end of each epoch.
type Actor struct{}
func (a Actor) Exports() []interface{} {
return []interface{}{
builtin.MethodConstructor: a.Constructor,
2: a.EpochTick,
}
}
func (a Actor) Code() cid.Cid {
return builtin.CronActorCodeID
}
func (a Actor) IsSingleton() bool {
return true
}
func (a Actor) State() cbor.Er {
return new(State)
}
var _ runtime.VMActor = Actor{}
//type ConstructorParams struct {
// Entries []Entry
//}
type ConstructorParams = cron0.ConstructorParams
type EntryParam = cron0.Entry
func (a Actor) Constructor(rt runtime.Runtime, params *ConstructorParams) *abi.EmptyValue {
rt.ValidateImmediateCallerIs(builtin.SystemActorAddr)
entries := make([]Entry, len(params.Entries))
for i, e := range params.Entries {
entries[i] = Entry(e) // Identical
}
rt.StateCreate(ConstructState(entries))
return nil
}
// Invoked by the system after all other messages in the epoch have been processed.
func (a Actor) EpochTick(rt runtime.Runtime, _ *abi.EmptyValue) *abi.EmptyValue {
rt.ValidateImmediateCallerIs(builtin.SystemActorAddr)
var st State
rt.StateReadonly(&st)
for _, entry := range st.Entries {
code := rt.Send(entry.Receiver, entry.MethodNum, nil, abi.NewTokenAmount(0), &builtin.Discard{})
// Any error and return value are ignored.
if code.IsError() {
rt.Log(rtt.ERROR, "cron failed to send entry to %s, send error code %d", entry.Receiver, code)
}
}
return nil
}
InitActor
-
State
reliable
-
Theory Audit
done
-
Edit this section
-
section-systems.filecoin_vm.sysactors.initactor
-
State
reliable -
Theory Audit
done - Edit this section
-
section-systems.filecoin_vm.sysactors.initactor
The InitActor has the power to create new actors, e.g., those that enter the system. It maintains a table resolving a public key and temporary actor addresses to their canonical ID-addresses. Invalid CIDs should not get committed to the state tree.
Note that the canonical ID address does not persist in case of chain re-organization. The actor address or public key survives chain re-organization.
package init
import (
addr "github.com/filecoin-project/go-address"
"github.com/filecoin-project/go-state-types/abi"
"github.com/filecoin-project/go-state-types/cbor"
"github.com/filecoin-project/go-state-types/exitcode"
init0 "github.com/filecoin-project/specs-actors/actors/builtin/init"
cid "github.com/ipfs/go-cid"
"github.com/filecoin-project/specs-actors/v8/actors/builtin"
"github.com/filecoin-project/specs-actors/v8/actors/runtime"
"github.com/filecoin-project/specs-actors/v8/actors/util/adt"
)
// The init actor uniquely has the power to create new actors.
// It maintains a table resolving pubkey and temporary actor addresses to the canonical ID-addresses.
type Actor struct{}
func (a Actor) Exports() []interface{} {
return []interface{}{
builtin.MethodConstructor: a.Constructor,
2: a.Exec,
}
}
func (a Actor) Code() cid.Cid {
return builtin.InitActorCodeID
}
func (a Actor) IsSingleton() bool {
return true
}
func (a Actor) State() cbor.Er { return new(State) }
var _ runtime.VMActor = Actor{}
//type ConstructorParams struct {
// NetworkName string
//}
type ConstructorParams = init0.ConstructorParams
func (a Actor) Constructor(rt runtime.Runtime, params *ConstructorParams) *abi.EmptyValue {
rt.ValidateImmediateCallerIs(builtin.SystemActorAddr)
st, err := ConstructState(adt.AsStore(rt), params.NetworkName)
builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to construct state")
rt.StateCreate(st)
return nil
}
//type ExecParams struct {
// CodeCID cid.Cid `checked:"true"` // invalid CIDs won't get committed to the state tree
// ConstructorParams []byte
//}
type ExecParams = init0.ExecParams
//type ExecReturn struct {
// IDAddress addr.Address // The canonical ID-based address for the actor.
// RobustAddress addr.Address // A more expensive but re-org-safe address for the newly created actor.
//}
type ExecReturn = init0.ExecReturn
func (a Actor) Exec(rt runtime.Runtime, params *ExecParams) *ExecReturn {
rt.ValidateImmediateCallerAcceptAny()
callerCodeCID, ok := rt.GetActorCodeCID(rt.Caller())
builtin.RequireState(rt, ok, "no code for caller at %s", rt.Caller())
if !canExec(callerCodeCID, params.CodeCID) {
rt.Abortf(exitcode.ErrForbidden, "caller type %v cannot exec actor type %v", callerCodeCID, params.CodeCID)
}
// Compute a re-org-stable address.
// This address exists for use by messages coming from outside the system, in order to
// stably address the newly created actor even if a chain re-org causes it to end up with
// a different ID.
uniqueAddress := rt.NewActorAddress()
// Allocate an ID for this actor.
// Store mapping of pubkey or actor address to actor ID
var st State
var idAddr addr.Address
rt.StateTransaction(&st, func() {
var err error
idAddr, err = st.MapAddressToNewID(adt.AsStore(rt), uniqueAddress)
builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to allocate ID address")
})
// Create an empty actor.
rt.CreateActor(params.CodeCID, idAddr)
// Invoke constructor.
code := rt.Send(idAddr, builtin.MethodConstructor, builtin.CBORBytes(params.ConstructorParams), rt.ValueReceived(), &builtin.Discard{})
builtin.RequireSuccess(rt, code, "constructor failed")
return &ExecReturn{IDAddress: idAddr, RobustAddress: uniqueAddress}
}
func canExec(callerCodeID cid.Cid, execCodeID cid.Cid) bool {
switch execCodeID {
case builtin.StorageMinerActorCodeID:
if callerCodeID == builtin.StoragePowerActorCodeID {
return true
}
return false
case builtin.PaymentChannelActorCodeID, builtin.MultisigActorCodeID:
return true
default:
return false
}
}
RewardActor
-
State
reliable
-
Theory Audit
done
-
Edit this section
-
section-systems.filecoin_vm.sysactors.rewardactor
-
State
reliable -
Theory Audit
done - Edit this section
-
section-systems.filecoin_vm.sysactors.rewardactor
The RewardActor is where unminted Filecoin tokens are kept. The actor distributes rewards directly to miner actors, where they are locked for vesting. The reward value used for the current epoch is updated at the end of an epoch through a cron tick.
package reward
import (
"github.com/filecoin-project/go-state-types/abi"
"github.com/filecoin-project/go-state-types/big"
"github.com/filecoin-project/go-state-types/cbor"
"github.com/filecoin-project/go-state-types/exitcode"
rtt "github.com/filecoin-project/go-state-types/rt"
reward0 "github.com/filecoin-project/specs-actors/actors/builtin/reward"
reward6 "github.com/filecoin-project/specs-actors/v6/actors/builtin/reward"
"github.com/ipfs/go-cid"
"github.com/filecoin-project/specs-actors/v8/actors/builtin"
"github.com/filecoin-project/specs-actors/v8/actors/runtime"
)
// PenaltyMultiplier is the factor miner penaltys are scaled up by
const PenaltyMultiplier = 3
type Actor struct{}
func (a Actor) Exports() []interface{} {
return []interface{}{
builtin.MethodConstructor: a.Constructor,
2: a.AwardBlockReward,
3: a.ThisEpochReward,
4: a.UpdateNetworkKPI,
}
}
func (a Actor) Code() cid.Cid {
return builtin.RewardActorCodeID
}
func (a Actor) IsSingleton() bool {
return true
}
func (a Actor) State() cbor.Er {
return new(State)
}
var _ runtime.VMActor = Actor{}
func (a Actor) Constructor(rt runtime.Runtime, currRealizedPower *abi.StoragePower) *abi.EmptyValue {
rt.ValidateImmediateCallerIs(builtin.SystemActorAddr)
if currRealizedPower == nil {
rt.Abortf(exitcode.ErrIllegalArgument, "argument should not be nil")
return nil // linter does not understand abort exiting
}
st := ConstructState(*currRealizedPower)
rt.StateCreate(st)
return nil
}
//type AwardBlockRewardParams struct {
// Miner address.Address
// Penalty abi.TokenAmount // penalty for including bad messages in a block, >= 0
// GasReward abi.TokenAmount // gas reward from all gas fees in a block, >= 0
// WinCount int64 // number of reward units won, > 0
//}
type AwardBlockRewardParams = reward0.AwardBlockRewardParams
// Awards a reward to a block producer.
// This method is called only by the system actor, implicitly, as the last message in the evaluation of a block.
// The system actor thus computes the parameters and attached value.
//
// The reward includes two components:
// - the epoch block reward, computed and paid from the reward actor's balance,
// - the block gas reward, expected to be transferred to the reward actor with this invocation.
//
// The reward is reduced before the residual is credited to the block producer, by:
// - a penalty amount, provided as a parameter, which is burnt,
func (a Actor) AwardBlockReward(rt runtime.Runtime, params *AwardBlockRewardParams) *abi.EmptyValue {
rt.ValidateImmediateCallerIs(builtin.SystemActorAddr)
priorBalance := rt.CurrentBalance()
if params.Penalty.LessThan(big.Zero()) {
rt.Abortf(exitcode.ErrIllegalArgument, "negative penalty %v", params.Penalty)
}
if params.GasReward.LessThan(big.Zero()) {
rt.Abortf(exitcode.ErrIllegalArgument, "negative gas reward %v", params.GasReward)
}
if priorBalance.LessThan(params.GasReward) {
rt.Abortf(exitcode.ErrIllegalState, "actor current balance %v insufficient to pay gas reward %v",
priorBalance, params.GasReward)
}
if params.WinCount <= 0 {
rt.Abortf(exitcode.ErrIllegalArgument, "invalid win count %d", params.WinCount)
}
minerAddr, ok := rt.ResolveAddress(params.Miner)
if !ok {
rt.Abortf(exitcode.ErrNotFound, "failed to resolve given owner address")
}
// The miner penalty is scaled up by a factor of PenaltyMultiplier
penalty := big.Mul(big.NewInt(PenaltyMultiplier), params.Penalty)
totalReward := big.Zero()
var st State
rt.StateTransaction(&st, func() {
blockReward := big.Mul(st.ThisEpochReward, big.NewInt(params.WinCount))
blockReward = big.Div(blockReward, big.NewInt(builtin.ExpectedLeadersPerEpoch))
totalReward = big.Add(blockReward, params.GasReward)
currBalance := rt.CurrentBalance()
if totalReward.GreaterThan(currBalance) {
rt.Log(rtt.WARN, "reward actor balance %d below totalReward expected %d, paying out rest of balance", currBalance, totalReward)
totalReward = currBalance
blockReward = big.Sub(totalReward, params.GasReward)
// Since we have already asserted the balance is greater than gas reward blockReward is >= 0
builtin.RequireState(rt, blockReward.GreaterThanEqual(big.Zero()), "programming error, block reward %v below zero", blockReward)
}
st.TotalStoragePowerReward = big.Add(st.TotalStoragePowerReward, blockReward)
})
builtin.RequireState(rt, totalReward.LessThanEqual(priorBalance), "reward %v exceeds balance %v", totalReward, priorBalance)
// if this fails, we can assume the miner is responsible and avoid failing here.
rewardParams := builtin.ApplyRewardParams{
Reward: totalReward,
Penalty: penalty,
}
code := rt.Send(minerAddr, builtin.MethodsMiner.ApplyRewards, &rewardParams, totalReward, &builtin.Discard{})
if !code.IsSuccess() {
rt.Log(rtt.ERROR, "failed to send ApplyRewards call to the miner actor with funds: %v, code: %v", totalReward, code)
code := rt.Send(builtin.BurntFundsActorAddr, builtin.MethodSend, nil, totalReward, &builtin.Discard{})
if !code.IsSuccess() {
rt.Log(rtt.ERROR, "failed to send unsent reward to the burnt funds actor, code: %v", code)
}
}
return nil
}
// Changed since v0:
// - removed ThisEpochReward (unsmoothed)
//type ThisEpochRewardReturn struct {
// ThisEpochRewardSmoothed smoothing.FilterEstimate
// ThisEpochBaselinePower abi.StoragePower
//}
type ThisEpochRewardReturn = reward6.ThisEpochRewardReturn
// The award value used for the current epoch, updated at the end of an epoch
// through cron tick. In the case previous epochs were null blocks this
// is the reward value as calculated at the last non-null epoch.
func (a Actor) ThisEpochReward(rt runtime.Runtime, _ *abi.EmptyValue) *ThisEpochRewardReturn {
rt.ValidateImmediateCallerAcceptAny()
var st State
rt.StateReadonly(&st)
return &ThisEpochRewardReturn{
ThisEpochRewardSmoothed: st.ThisEpochRewardSmoothed,
ThisEpochBaselinePower: st.ThisEpochBaselinePower,
}
}
// Called at the end of each epoch by the power actor (in turn by its cron hook).
// This is only invoked for non-empty tipsets, but catches up any number of null
// epochs to compute the next epoch reward.
func (a Actor) UpdateNetworkKPI(rt runtime.Runtime, currRealizedPower *abi.StoragePower) *abi.EmptyValue {
rt.ValidateImmediateCallerIs(builtin.StoragePowerActorAddr)
if currRealizedPower == nil {
rt.Abortf(exitcode.ErrIllegalArgument, "argument should not be nil")
}
var st State
rt.StateTransaction(&st, func() {
prev := st.Epoch
// if there were null runs catch up the computation until
// st.Epoch == rt.CurrEpoch()
for st.Epoch < rt.CurrEpoch() {
// Update to next epoch to process null rounds
st.updateToNextEpoch(*currRealizedPower)
}
st.updateToNextEpochWithReward(*currRealizedPower)
// only update smoothed estimates after updating reward and epoch
st.updateSmoothedEstimates(st.Epoch - prev)
})
return nil
}
AccountActor
-
State
reliable
-
Theory Audit
done
-
Edit this section
-
section-systems.filecoin_vm.sysactors.accountactor
-
State
reliable -
Theory Audit
done - Edit this section
-
section-systems.filecoin_vm.sysactors.accountactor
The AccountActor is responsible for user accounts. Account actors are not created by the InitActor, but their constructor is called by the system. Account actors are created by sending a message to a public-key style address. The address must be BLS or SECP, or otherwise there should be an exit error. The account actor is updating the state tree with the new actor address.
package account
import (
addr "github.com/filecoin-project/go-address"
"github.com/filecoin-project/go-state-types/abi"
"github.com/filecoin-project/go-state-types/cbor"
"github.com/filecoin-project/go-state-types/exitcode"
"github.com/ipfs/go-cid"
"github.com/filecoin-project/specs-actors/v8/actors/builtin"
"github.com/filecoin-project/specs-actors/v8/actors/runtime"
)
type Actor struct{}
func (a Actor) Exports() []interface{} {
return []interface{}{
1: a.Constructor,
2: a.PubkeyAddress,
}
}
func (a Actor) Code() cid.Cid {
return builtin.AccountActorCodeID
}
func (a Actor) State() cbor.Er {
return new(State)
}
var _ runtime.VMActor = Actor{}
type State struct {
Address addr.Address
}
func (a Actor) Constructor(rt runtime.Runtime, address *addr.Address) *abi.EmptyValue {
// Account actors are created implicitly by sending a message to a pubkey-style address.
// This constructor is not invoked by the InitActor, but by the system.
rt.ValidateImmediateCallerIs(builtin.SystemActorAddr)
switch address.Protocol() {
case addr.SECP256K1:
case addr.BLS:
break // ok
default:
rt.Abortf(exitcode.ErrIllegalArgument, "address must use BLS or SECP protocol, got %v", address.Protocol())
}
st := State{Address: *address}
rt.StateCreate(&st)
return nil
}
// Fetches the pubkey-type address from this actor.
func (a Actor) PubkeyAddress(rt runtime.Runtime, _ *abi.EmptyValue) *addr.Address {
rt.ValidateImmediateCallerAcceptAny()
var st State
rt.StateReadonly(&st)
return &st.Address
}
VM Interpreter - Message Invocation (Outside VM)
-
State
wip
-
Theory Audit
wip
-
Edit this section
-
section-systems.filecoin_vm.interpreter
-
State
wip -
Theory Audit
wip - Edit this section
-
section-systems.filecoin_vm.interpreter
The VM interpreter orchestrates the execution of messages from a tipset on that tipset’s parent state, producing a new state and a sequence of message receipts. The CIDs of this new state and of the receipt collection are included in blocks from the subsequent epoch, which must agree about those CIDs in order to form a new tipset.
Every state change is driven by the execution of a message. The messages from all the blocks in a tipset must be executed in order to produce a next state. All messages from the first block are executed before those of second and subsequent blocks in the tipset. For each block, BLS-aggregated messages are executed first, then SECP signed messages.
Implicit messages
-
State
wip
-
Theory Audit
wip
-
Edit this section
-
section-systems.filecoin_vm.interpreter.implicit-messages
-
State
wip -
Theory Audit
wip - Edit this section
-
section-systems.filecoin_vm.interpreter.implicit-messages
In addition to the messages explicitly included in each block, a few state changes at each epoch are made by implicit messages. Implicit messages are not transmitted between nodes, but constructed by the interpreter at evaluation time.
For each block in a tipset, an implicit message:
- invokes the block producer’s miner actor to process the (already-validated) election PoSt submission, as the first message in the block;
- invokes the reward actor to pay the block reward to the miner’s owner account, as the final message in the block;
For each tipset, an implicit message:
- invokes the cron actor to process automated checks and payments, as the final message in the tipset.
All implicit messages are constructed with a From address being the distinguished system account actor.
They specify a gas price of zero, but must be included in the computation.
They must succeed (have an exit code of zero) in order for the new state to be computed.
Receipts for implicit messages are not included in the receipt list; only explicit messages have an
explicit receipt.
Gas payments
-
State
wip
-
Theory Audit
wip
-
Edit this section
-
section-systems.filecoin_vm.interpreter.gas-payments
-
State
wip -
Theory Audit
wip - Edit this section
-
section-systems.filecoin_vm.interpreter.gas-payments
In most cases, the sender of a message pays the miner which produced the block including that message a gas fee for its execution.
The gas payments for each message execution are paid to the miner owner account immediately after that message is executed. There are no encumbrances to either the block reward or gas fees earned: both may be spent immediately.
Duplicate messages
-
State
wip
-
Theory Audit
wip
-
Edit this section
-
section-systems.filecoin_vm.interpreter.duplicate-messages
-
State
wip -
Theory Audit
wip - Edit this section
-
section-systems.filecoin_vm.interpreter.duplicate-messages
Since different miners produce blocks in the same epoch, multiple blocks in a single tipset may include the same message (identified by the same CID). When this happens, the message is processed only the first time it is encountered in the tipset’s canonical order. Subsequent instances of the message are ignored and do not result in any state mutation, produce a receipt, or pay gas to the block producer.
The sequence of executions for a tipset is thus summarised:
- pay reward for first block
- process election post for first block
- messages for first block (BLS before SECP)
- pay reward for second block
- process election post for second block
- messages for second block (BLS before SECP, skipping any already encountered)
[... subsequent blocks ...]- cron tick
Message validity and failure
-
State
wip
-
Theory Audit
wip
-
Edit this section
-
section-systems.filecoin_vm.interpreter.message-validity-and-failure
-
State
wip -
Theory Audit
wip - Edit this section
-
section-systems.filecoin_vm.interpreter.message-validity-and-failure
Every message in a valid block can be processed and produce a receipt (note that block validity implies all messages are syntactically valid – see Message Syntax – and correctly signed). However, execution may or may not succeed, depending on the state to which the message is applied. If the execution of a message fails, the corresponding receipt will carry a non-zero exit code.
If a message fails due to a reason that can reasonably be attributed to the miner including a message that could never have succeeded in the parent state, or because the sender lacks funds to cover the maximum message cost, then the miner pays a penalty by burning the gas fee (rather than the sender paying fees to the block miner).
The only state changes resulting from a message failure are either:
- incrementing of the sending actor’s
CallSeqNum, and payment of gas fees from the sender to the owner of the miner of the block including the message; or - a penalty equivalent to the gas fee for the failed message, burnt by the miner (sender’s
CallSeqNumunchanged).
A message execution will fail if, in the immediately preceding state:
- the
Fromactor does not exist in the state (miner penalized), - the
Fromactor is not an account actor (miner penalized), - the
CallSeqNumof the message does not match theCallSeqNumof theFromactor (miner penalized), - the
Fromactor does not have sufficient balance to cover the sum of the messageValueplus the maximum gas cost,GasLimit * GasPrice(miner penalized), - the
Toactor does not exist in state and theToaddress is not a pubkey-style address, - the
Toactor exists (or is implicitly created as an account) but does not have a method corresponding to the non-zeroMethodNum, - deserialized
Paramsis not an array of length matching the arity of theToactor’sMethodNummethod, - deserialized
Paramsare not valid for the types specified by theToactor’sMethodNummethod, - the invoked method consumes more gas than the
GasLimitallows, - the invoked method exits with a non-zero code (via
Runtime.Abort()), or - any inner message sent by the receiver fails for any of the above reasons.
Note that if the To actor does not exist in state and the address is a valid H(pubkey) address,
it will be created as an account actor.
Blockchain
-
State
reliable
-
Theory Audit
wip
-
Edit this section
-
section-systems.filecoin_blockchain
-
State
reliable -
Theory Audit
wip - Edit this section
-
section-systems.filecoin_blockchain
The Filecoin Blockchain is a distributed virtual machine that achieves consensus, processes messages, accounts for storage, and maintains security in the Filecoin Protocol. It is the main interface linking various actors in the Filecoin system.
The Filecoin blockchain system includes:
- A Message Pool subsystem that nodes use to track and propagate messages that miners have declared they want to include in the blockchain.
- A Virtual Machine subsystem used to interpret and execute messages in order to update system state.
- A State Tree subsystem which manages the creation and maintenance of state trees (the system state) deterministically generated by the vm from a given subchain.
- A Chain Synchronisation (ChainSync) susbystem that tracks and propagates validated message blocks, maintaining sets of candidate chains on which the miner may mine and running syntactic validation on incoming blocks.
- A Storage Power Consensus subsystem which tracks storage state (i.e., Storage Subystem) for a given chain and helps the blockchain system choose subchains to extend and blocks to include in them.
The blockchain system also includes:
- A Chain Manager, which maintains a given chain’s state, providing facilities to other blockchain subsystems which will query state about the latest chain in order to run, and ensuring incoming blocks are semantically validated before inclusion into the chain.
- A Block Producer which is called in the event of a successful leader election in order to produce a new block that will extend the current heaviest chain before forwarding it to the syncer for propagation.
At a high-level, the Filecoin blockchain grows through successive rounds of leader election in which a number of miners are elected to generate a block, whose inclusion in the chain will earn them block rewards. Filecoin’s blockchain runs on storage power. That is, its consensus algorithm by which miners agree on which subchain to mine is predicated on the amount of storage backing that subchain. At a high-level, the Storage Power Consensus subsystem maintains a Power Table that tracks the amount of storage that storage miner actors have contributed to the network through Sector commitments and Proofs of Spacetime.
Blocks
-
State
reliable
-
Theory Audit
n/a
-
Edit this section
-
section-systems.filecoin_blockchain.struct
-
State
reliable -
Theory Audit
n/a - Edit this section
-
section-systems.filecoin_blockchain.struct
The Block is the main unit of the Filecoin blockchain, as is also the case with most other blockchains. Block messages are directly linked with Tipsets, which are groups of Block messages as detailed later on in this section. In the following we discuss the main structure of a Block message and the process of validating Block messages in the Filecoin blockchain.
Block
-
State
reliable
-
Theory Audit
n/a
-
Edit this section
-
section-systems.filecoin_blockchain.struct.block
-
State
reliable -
Theory Audit
n/a - Edit this section
-
section-systems.filecoin_blockchain.struct.block
The Block is the main unit of the Filecoin blockchain.
The Block structure in the Filecoin blockchain is composed of: i) the Block Header, ii) the list of messages inside the block, and iii) the signed messages. This is represented inside the FullBlock abstraction. The messages indicate the required set of changes to apply in order to arrive at a deterministic state of the chain.
The Lotus implementation of the block has the following struct:
type FullBlock struct {
Header *BlockHeader
BlsMessages []*Message
SecpkMessages []*SignedMessage
}Note
A block is functionally the same as a block header in the Filecoin protocol. While a block header contains Merkle links to the full system state, messages, and message receipts, a block can be thought of as the full set of this information (not just the Merkle roots, but rather the full data of the state tree, message tree, receipts tree, etc.). Because a full block is large in size, the Filecoin blockchain consists of block headers rather than full blocks. We often use the termsblockandblock headerinterchangeably.
A BlockHeader is a canonical representation of a block. BlockHeaders are propagated between miner nodes. From the BlockHeader message, a miner has all the required information to apply the associated FullBlock’s state and update the chain. In order to be able to do this, the minimum set of information items that need to be included in the BlockHeader are shown below and include among others: the miner’s address, the Ticket, the
Proof of SpaceTime, the CID of the parents where this block evolved from in the IPLD DAG, as well as the messages’ own CIDs.
The Lotus implementation of the block header has the following structs:
type BlockHeader struct {
Miner address.Address // 0 unique per block/miner
Ticket *Ticket // 1 unique per block/miner: should be a valid VRF
ElectionProof *ElectionProof // 2 unique per block/miner: should be a valid VRF
BeaconEntries []BeaconEntry // 3 identical for all blocks in same tipset
WinPoStProof []proof.PoStProof // 4 unique per block/miner
Parents []cid.Cid // 5 identical for all blocks in same tipset
ParentWeight BigInt // 6 identical for all blocks in same tipset
Height abi.ChainEpoch // 7 identical for all blocks in same tipset
ParentStateRoot cid.Cid // 8 identical for all blocks in same tipset
ParentMessageReceipts cid.Cid // 9 identical for all blocks in same tipset
Messages cid.Cid // 10 unique per block
BLSAggregate *crypto.Signature // 11 unique per block: aggrregate of BLS messages from above
Timestamp uint64 // 12 identical for all blocks in same tipset / hard-tied to the value of Height above
BlockSig *crypto.Signature // 13 unique per block/miner: miner signature
ForkSignaling uint64 // 14 currently unused/undefined
ParentBaseFee abi.TokenAmount // 15 identical for all blocks in same tipset: the base fee after executing parent tipset
validated bool // internal, true if the signature has been validated
}type Ticket struct {
VRFProof []byte
}type ElectionProof struct {
WinCount int64
VRFProof []byte
}type BeaconEntry struct {
Round uint64
Data []byte
}The BlockHeader structure has to refer to the TicketWinner of the current round which ensures the correct winner is passed to
ChainSync.
func IsTicketWinner(vrfTicket []byte, mypow BigInt, totpow BigInt) bool
The Message structure has to include the source (From) and destination (To) addresses, a Nonce and the GasPrice.
The Lotus implementation of the message has the following structure:
type Message struct {
Version uint64
To address.Address
From address.Address
Nonce uint64
Value abi.TokenAmount
GasLimit int64
GasFeeCap abi.TokenAmount
GasPremium abi.TokenAmount
Method abi.MethodNum
Params []byte
}The message is also validated before it is passed to the chain synchronization logic:
func (m *Message) ValidForBlockInclusion(minGas int64, version network.Version) error {
if m.Version != 0 {
return xerrors.New("'Version' unsupported")
}
if m.To == address.Undef {
return xerrors.New("'To' address cannot be empty")
}
if m.To == buildconstants.ZeroAddress && version >= network.Version7 {
return xerrors.New("invalid 'To' address")
}
if !abi.AddressValidForNetworkVersion(m.To, version) {
return xerrors.New("'To' address protocol unsupported for network version")
}
if m.From == address.Undef {
return xerrors.New("'From' address cannot be empty")
}
if !abi.AddressValidForNetworkVersion(m.From, version) {
return xerrors.New("'From' address protocol unsupported for network version")
}
if m.Value.Int == nil {
return xerrors.New("'Value' cannot be nil")
}
if m.Value.LessThan(big.Zero()) {
return xerrors.New("'Value' field cannot be negative")
}
if m.Value.GreaterThan(TotalFilecoinInt) {
return xerrors.New("'Value' field cannot be greater than total filecoin supply")
}
if m.GasFeeCap.Int == nil {
return xerrors.New("'GasFeeCap' cannot be nil")
}
if m.GasFeeCap.LessThan(big.Zero()) {
return xerrors.New("'GasFeeCap' field cannot be negative")
}
if m.GasPremium.Int == nil {
return xerrors.New("'GasPremium' cannot be nil")
}
if m.GasPremium.LessThan(big.Zero()) {
return xerrors.New("'GasPremium' field cannot be negative")
}
if m.GasPremium.GreaterThan(m.GasFeeCap) {
return xerrors.New("'GasFeeCap' less than 'GasPremium'")
}
if m.GasLimit > buildconstants.BlockGasLimit {
return xerrors.Errorf("'GasLimit' field cannot be greater than a block's gas limit (%d > %d)", m.GasLimit, buildconstants.BlockGasLimit)
}
if m.GasLimit <= 0 {
return xerrors.Errorf("'GasLimit' field %d must be positive", m.GasLimit)
}
// since prices might vary with time, this is technically semantic validation
if m.GasLimit < minGas {
return xerrors.Errorf("'GasLimit' field cannot be less than the cost of storing a message on chain %d < %d", m.GasLimit, minGas)
}
return nil
}
Block syntax validation
-
State
reliable
-
Theory Audit
n/a
-
Edit this section
-
section-systems.filecoin_blockchain.struct.block.block-syntax-validation
-
State
reliable -
Theory Audit
n/a - Edit this section
-
section-systems.filecoin_blockchain.struct.block.block-syntax-validation
Syntax validation refers to validation that should be performed on a block and its messages without reference to outside information such as the parent state tree. This type of validation is sometimes called static validation.
An invalid block must not be transmitted or referenced as a parent.
A syntactically valid block header must decode into fields matching the definitions below, must be a valid CBOR PubSub BlockMsg message and must have:
- between 1 and
5*ec.ExpectedLeadersParentsCIDs ifEpochis greater than zero (else emptyParents), - a non-negative
ParentWeight, - less than or equal to
BlockMessageLimitnumber of messages, - aggregate message CIDs, encapsulated in the
MsgMetastructure, serialized to theMessagesCID in the block header, - a
Mineraddress which is an ID-address. The MinerAddressin the block header should be present and correspond to a public-key address in the current chain state. - Block signature (
BlockSig) that belongs to the public-key address retrieved for the Miner - a non-negative
Epoch, - a positive
Timestamp, - a
Ticketwith non-emptyVRFResult, ElectionPoStOutputcontaining:- a
Candidatesarray with between 1 andEC.ExpectedLeadersvalues (inclusive), - a non-empty
PoStRandomnessfield, - a non-empty
Prooffield,
- a
- a non-empty
ForkSignalfield.
A syntactically valid full block must have:
- all referenced messages syntactically valid,
- all referenced parent receipts syntactically valid,
- the sum of the serialized sizes of the block header and included messages is no greater than
block.BlockMaxSize, - the sum of the gas limit of all explicit messages is no greater than
block.BlockGasLimit.
Note that validation of the block signature requires access to the miner worker address and public key from the parent tipset state, so signature validation forms part of semantic validation. Similarly, message signature validation requires lookup of the public key associated with each message’s From account actor in the block’s parent state.
Block semantic validation
-
State
reliable
-
Theory Audit
n/a
-
Edit this section
-
section-systems.filecoin_blockchain.struct.block.block-semantic-validation
-
State
reliable -
Theory Audit
n/a - Edit this section
-
section-systems.filecoin_blockchain.struct.block.block-semantic-validation
Semantic validation refers to validation that requires reference to information outside the block header and messages themselves. Semantic validation relates to the parent tipset and state on which the block is built.
In order to proceed to semantic validation the FullBlock must be assembled from the received block header retrieving its Filecoin messages. Block message CIDs can be retrieved from the network and be decoded into valid CBOR Message/SignedMessage.
In the Lotus implementation the semantic validation of a block is carried out by the Syncer module:
func (syncer *Syncer) ValidateBlock(ctx context.Context, b *types.FullBlock, useCache bool) (err error) {
defer func() {
// b.Cid() could panic for empty blocks that are used in tests.
if rerr := recover(); rerr != nil {
err = xerrors.Errorf("validate block panic: %w", rerr)
return
}
}()
if useCache {
isValidated, err := syncer.store.IsBlockValidated(ctx, b.Cid())
if err != nil {
return xerrors.Errorf("check block validation cache %s: %w", b.Cid(), err)
}
if isValidated {
return nil
}
}
validationStart := build.Clock.Now()
defer func() {
stats.Record(ctx, metrics.BlockValidationDurationMilliseconds.M(metrics.SinceInMilliseconds(validationStart)))
log.Infow("block validation", "took", time.Since(validationStart), "height", b.Header.Height, "age", time.Since(time.Unix(int64(b.Header.Timestamp), 0)))
}()
ctx, span := trace.StartSpan(ctx, "validateBlock")
defer span.End()
if err := syncer.consensus.ValidateBlock(ctx, b); err != nil {
return err
}
if useCache {
if err := syncer.store.MarkBlockAsValidated(ctx, b.Cid()); err != nil {
return xerrors.Errorf("caching block validation %s: %w", b.Cid(), err)
}
}
return nil
}Messages are retrieved through the Syncer. There are the following two steps followed by the Syncer:
1- Assemble a FullTipSet populated with the single block received earlier. The Block’s ParentWeight is greater than the one from the (first block of the) heaviest tipset.
2- Retrieve all tipsets from the received Block down to our chain. Validation is expanded to every block inside these tipsets. The validation should ensure that: - Beacon entires are ordered by their round number. - The Tipset Parents CIDs match the fetched parent tipset through BlockSync.
A semantically valid block must meet all of the following requirements.
Parents-Related
Parentslisted in lexicographic order of their header’sTicket.ParentStateRootCID of the block matches the state CID computed from the parent Tipset.ParentStatematches the state tree produced by executing the parent tipset’s messages (as defined by the VM interpreter) against that tipset’s parent state.ParentMessageReceiptsidentifying the receipt list produced by parent tipset execution, with one receipt for each unique message from the parent tipset. In other words, the Block’sParentMessageReceiptsCID matches the receipts CID computed from the parent tipset.ParentWeightmatches the weight of the chain up to and including the parent tipset.
Time-Related
Epochis greater than that of itsParents, and- not in the future according to the node’s local clock reading of the current epoch,
- blocks with future epochs should not be rejected, but should not be evaluated (validated or included in a tipset) until the appropriate epoch
- not farther in the past than the soft finality as defined by SPC
Finality,
- this rule only applies when receiving new gossip blocks (i.e. from the current chain head), not when syncing to the chain for the first time.
- not in the future according to the node’s local clock reading of the current epoch,
- The
Timestampincluded is in seconds that:- must not be bigger than current time plus
ΑllowableClockDriftSecs - must not be smaller than previous block’s
TimestampplusBlockDelay(including null blocks) - is of the precise value implied by the genesis block’s timestamp, the network’s Βlock time and the Βlock’s
Epoch.
- must not be bigger than current time plus
Miner-Related
- The
Mineris active in the storage power table in the parent tipset state. The Miner’s address is registered in theClaimsHAMT of the Power Actor - The
TipSetStateshould be included for each tipset being validated.- Every Block in the tipset should belong to different a miner.
- The Actor associated with the message’s
Fromaddress exists, is an account actor and its Nonce matches the message Nonce. - Valid proofs that the Miner proved access to sealed versions of the sectors it was challenged for are included. In order to achieve that:
- draw randomness for current epoch with
WinningPoStdomain separation tag. - get list of sectors challanged in this epoch for this miner, based on the randomness drawn.
- draw randomness for current epoch with
- Miner is not slashed in
StoragePowerActor.
Beacon- & Ticket-Related
- Valid
BeaconEntriesshould be included:- Check that every one of the
BeaconEntriesis a signature of a message:previousSignature || roundsigned using DRAND’s public key. - All entries between
MaxBeaconRoundForEpochdown toprevEntry(from previous tipset) should be included.
- Check that every one of the
- A
Ticketderived from the minimum ticket from the parent tipset’s block headers,Ticket.VRFResultvalidly signed by theMineractor’s worker account public key,
ElectionProof Ticketis computed correctly by checking BLS signature using miner’s key. TheElectionProofticket should be a winning ticket.
Message- & Signature-Related
secp256k1messages are correctly signed by their sending actor’s (From) worker account key,- A
BLSAggregatesignature is included that signs the array of CIDs of all the BLS messages referenced by the block with their sending actor’s key. - A valid
Signatureover the block header’s fields from the block’sMineractor’s worker account public key is included. - For each message in
ValidForBlockInclusion()the following hold:- Message fields
Version,To,From,Value,GasPrice, andGasLimitare correctly defined. - Message
GasLimitis under the message minimum gas cost (derived from chain height and message length).
- Message fields
- For each message in
ApplyMessage(that is before a message is executed), the following hold:- Basic gas and value checks in
checkMessage():- The Message
GasLimitis bigger than zero. - The Message
GasPriceandValueare set.
- The Message
- The Message’s storage gas cost is under the message’s
GasLimit. - The Message’s
Noncematches the nonce in the Actor retrieved from the message’sFromaddress. - The Message’s maximum gas cost (derived from its
GasLimit,GasPrice, andValue) is under the balance of the Actor retrieved from message’sFromaddress. - The Message’s transfer
Valueis under the balance of the Actor retrieved from message’sFromaddress.
- Basic gas and value checks in
There is no semantic validation of the messages included in a block beyond validation of their signatures. If all messages included in a block are syntactically valid then they may be executed and produce a receipt.
A chain sync system may perform syntactic and semantic validation in stages in order to minimize unnecessary resource expenditure.
If all of the above tests are successful, the block is marked as validated. Ultimately, an invalid block must not be propagated further or validated as a parent node.
Tipset
-
State
reliable
-
Theory Audit
n/a
-
Edit this section
-
section-systems.filecoin_blockchain.struct.tipset
-
State
reliable -
Theory Audit
n/a - Edit this section
-
section-systems.filecoin_blockchain.struct.tipset
Expected Consensus probabilistically elects multiple leaders in each epoch meaning a Filecoin chain may contain zero or multiple blocks at each epoch (one per elected miner). Blocks from the same epoch are assembled into tipsets. The VM Interpreter modifies the Filecoin state tree by executing all messages in a tipset (after de-duplication of identical messages included in more than one block).
Each block references a parent tipset and validates that tipset’s state, while proposing messages to be included for the current epoch. The state to which a new block’s messages apply cannot be known until that block is incorporated into a tipset. It is thus not meaningful to execute the messages from a single block in isolation: a new state tree is only known once all messages in that block’s tipset are executed.
A valid tipset contains a non-empty collection of blocks that have distinct miners and all specify identical:
EpochParentsParentWeightStateRootReceiptsRoot
The blocks in a tipset are canonically ordered by the lexicographic ordering of the bytes in each block’s ticket, breaking ties with the bytes of the CID of the block itself.
Due to network propagation delay, it is possible for a miner in epoch N+1 to omit valid blocks mined at epoch N from their parent tipset. This does not make the newly generated block invalid, it does however reduce its weight and chances of being part of the canonical chain in the protocol as defined by EC’s Chain Selection function.
Block producers are expected to coordinate how they select messages for inclusion in blocks in order to avoid duplicates and thus maximize their expected earnings from message fees (see Message Pool).
The main Tipset structure in the Lotus implementation includes the following:
type TipSet struct {
cids []cid.Cid
blks []*BlockHeader
height abi.ChainEpoch
}Semantic validation of a Tipset includes the following checks.
func NewTipSet(blks []*BlockHeader) (*TipSet, error) {
if len(blks) == 0 {
return nil, xerrors.Errorf("NewTipSet called with zero length array of blocks")
}
sort.Slice(blks, tipsetSortFunc(blks))
var ts TipSet
ts.cids = []cid.Cid{blks[0].Cid()}
ts.blks = blks
for _, b := range blks[1:] {
if b.Height != blks[0].Height {
return nil, fmt.Errorf("cannot create tipset with mismatching heights")
}
if len(blks[0].Parents) != len(b.Parents) {
return nil, fmt.Errorf("cannot create tipset with mismatching number of parents")
}
for i, cid := range b.Parents {
if cid != blks[0].Parents[i] {
return nil, fmt.Errorf("cannot create tipset with mismatching parents")
}
}
ts.cids = append(ts.cids, b.Cid())
}
ts.height = blks[0].Height
return &ts, nil
}
Chain Manager
-
State
reliable
-
Theory Audit
n/a
-
Edit this section
-
section-systems.filecoin_blockchain.struct.chain_manager
-
State
reliable -
Theory Audit
n/a - Edit this section
-
section-systems.filecoin_blockchain.struct.chain_manager
The Chain Manager is a central component in the blockchain system. It tracks and updates competing subchains received by a given node in order to select the appropriate blockchain head: the latest block of the heaviest subchain it is aware of in the system.
In so doing, the chain manager is the central subsystem that handles bookkeeping for numerous other systems in a Filecoin node and exposes convenience methods for use by those systems, enabling systems to sample randomness from the chain for instance, or to see which block has been finalized most recently.
Chain Extension
-
State
reliab
-
State
reliab