Skip to content

Latest commit

 

History

History
659 lines (515 loc) · 46.8 KB

CODEWALK.md

File metadata and controls

659 lines (515 loc) · 46.8 KB

Go-filecoin code overview

This document provides a high level tour of the go-filecoin implementation of the Filecoin protocols in Go.

This document assumes a reasonable level of knowledge about the Filecoin system and protocols, which are not re-explained here. It is complemented by specs (link forthcoming) that describe the key concepts implemented here.

Table of contents

Background

The go-filecoin implementations is the result of combined research and development effort. The protocol spec and architecture evolved from a prototype, and is the result of iterating towards our goals. Go-filecoin is a work in progress. We are still working on clarifying the architecture and propagating good patterns throughout the code. Please bear with us, and we’d love your help.

Filecoin borrows a lot from the IPFS project, including some patterns, tooling, and packages. Some benefits of this include:

  • the projects encode data in the same way (IPLD, CIDs), easing interoperability;
  • the go-filecoin project can build on solid libraries like the IPFS commands.

Other patterns, we've evolving for our needs:

  • go-ipfs relies heavily on shell-based integration testing; we aim to rely heavily on unit testing and Go-based integration tests.
  • The go-ipfs package structure involves a deep hierarchy of dependent implementations; we're moving towards a more Go-idiomatic approach with narrow interfaces defined in consuming packages (see Patterns.
  • The term "block" is heavily overloaded: a blockchain block (types/block.go), but also content-id-addressed blocks in the block service. Blockchain blocks are stored in block service blocks, but are not the same thing.

Architecture overview

                                                                                  | |\/
                                                                                  |_|/\
                    ╔══════════════════════════════════════════╗      ╔══════════════════════════════╗
                    ║                                          ║      ║                              ║
                    ║                                          ║      ║                              ║
                    ║    NETWORK (gossipsub, bitswap, etc.)    ║      ║     COMMANDS / REST API      ║
 Network            ║                                          ║      ║                              ║
                    ║                                          ║      ║                              ║
                    ╚═════════════════════╦════════════════════╝      ╚══════════════════════════════╝
                                          │                                           │
                          ┌───────────────┼───────────────┐    ┌──────────────────────┤
                          │               │               │    │                      │
                          ▼               ▼               ▼    ▼                      │
                  ┌──────────────┬─────────────────┬─────────────┐                    │
         ┌───────▶│ Storage API  │  Retrieval API  │  Block API  │                    │
         │        ├──────────────┼─────────────────┼─────────────┤                    │
         │        │              │                 │             │                    │
         │        │              │                 │             │──┐                 │
         │        │   Storage    │    Retrieval    │    Block    │  │                 │
Internal │        │   Protocol   │    Protocol     │  Protocol   │  │                 │
   API   │        └──────────────┴────────┬────────┴─────────────┘  │                 │
         │                │               │               │         │                 │
         │                ▼               ▼               ▼         │                 ▼
         │          ┌───────────────────────────────────────────┐   │  ┌─────────────┬──────────────┐
         │          │                                           │   │  │             │              │
         │          │                 Core API                  │   │  │  Porcelain  │   Plumbing   │
         └─────────▶│                                           │   └─▶├─────────────┘              │
                    │                                           │      │                            │
                    └───────────────────────────────────────────┘      └────────────────────────────┘
                                          │                                           │
                   ┌─────────────────┬────┴──────────────┬────────────────┬───────────┴─────┐
                   │                 │                   │                │                 │
                   ▼                 ▼                   ▼                ▼                 ▼
 Core       ┌─────────────┐   ┌─────────────┐     ┌─────────────┐  ┌─────────────┐   ┌─────────────┐
            │             │   │             │     │             │  │             │   │             │
            │   Message   │   │ Chain Store │     │  Processor  │  │    Block    │   │   Wallet    │
            │    Pool     │   │             │     │             │  │   Service   │   │             │
            │             │   │             │     │             │  │             │   │             │
            └─────────────┘   └─────────────┘     └─────────────┘  └─────────────┘   └─────────────┘

A tour of the code

History–the Node object

The Node (node/) object is the "server". It contains much of the core protocol implementation and plumbing. As an accident of history it has become something of a god-object, which we are working to resolve. The Node object is difficult to unit test due to its many dependencies and complicated set-up. We are moving away from this pattern, and expect the Node object to be reduced to a glorified constructor over time.

The api package contains the API of all the core building blocks upon which the protocols are implemented. The implementation of this API is the Node. We are migrating away from this api package to the plumbing package, see below.

The protocol package contains much of the application-level protocol code. The protocols are implemented in terms of the plumbing & porcelain APIs (see below). Currently the hello, retrieval and storage protocols are implemented here. Block mining should move here (from the mining top-level package and Node internals). Chain syncing may move here too.

Core services

At the bottom of the architecture diagram are core services. These are focused implementations of some functionality that don’t achieve much on their own, but are the means to the end. Core services are the bottom level building blocks out of which application functionality can be built.

They are the source of truth in all data.

Core services are mostly found in top-level packages. Most are reasonably well factored and testable in isolation.

Services include (not exhaustive):

  • Message pool: hold messages that haven’t been mined into a block yet.
  • Chain store: stores & indexes blockchain blocks.
  • Chain syncer: syncs chain blocks from the rest of the network.
  • Processor: Defines how transaction messages drive state transitions.
  • Block service: content-addressed key value store that stores IPLD data, including blockchain blocks as well as the state tree (it’s poorly named).
  • Wallet: manages keys.

Plumbing & porcelain

The plumbing & porcelain packages are the API for most non-protocol commands.

Plumbing is the set of public apis required to implement all user-, tool-, and some protocol-level features. Plumbing implementations depend on the core services they need, but not on the Node. Plumbing is intended to be fairly thin, routing requests and data between core components. Plumbing implementations are often tested with real implementations of the core services they use, but can also be tested with fakes and mocks.

Porcelain implementations are convenience compositions of plumbing. They depend only on the plumbing API, and can coordinate a sequence of actions. Porcelain is ephemeral; the lifecycle is the duration of a single porcelain call: something calls into it, it does its thing, and then returns. Porcelain implementations are ideally tested with fakes of the plumbing they use, but can also use full implementations.

Commands

The go-filecoin binary can run in two different modes, either as a long-running daemon exposing a JSON/HTTP RPC API, or as a command-line interface which interprets and routes requests as RPCs to a daemon. In typical usage, you start the daemon with go-filecoin daemon then use the same binary to issue commands like go-filecoin wallet addrs, which are transmitted to the daemon over the HTTP API.

The commands package uses the go-ipfs command library and defines commands as both CLI and JSON entry points.

Commands implement user- and tool-facing functionality. Command implementations should be very, very small. With no logic of their own, they should call just into a single plumbing or porcelain method (never into core APIs directly). The go-ipfs command library introduces some boilerplate which we can reduce with some effort in the future. Right now, some of the command implementations call into the node; this should change.

Tests for commands are generally end-to-end "daemon tests" that exercise CLI. They start some nodes and interact with them through shell commands.

Protocols

Protocols embody "application-level" functionality. They are persistent; they keep running without active user/tool activity. Protocols interact with the network. Protocols depend on plumbing and porcelain for their implementation, as well some "private" core APIs (at present, many still depend on the Node object).

Protocols drive changes in, but do not own, core state. For example, the chain sync protocol drives updates to the chain store (a core service), but the sync protocol does not own the chain data. However, protocols may maintain their own non-core, protocol-specific datastores (e.g. unconfirmed deals).

Application-level protocol implementations include:

  • Storage protocol: the mechanism by which clients make deals with miners, transfer data for storage, and then miners prove storage.
  • Block mining protocol: the protocol for block mining and consensus. Miners who are storing data participate in creating new blocks. Miners win elections in proportion to storage committed. This block mining is spread through a few places in the code. Much in mining package, but also a bunch in the node implementation.
  • Chain protocol: protocol for exchange of mined blocks
Protocol Mining APIs

The storage, retrieval and block packages now house their own APIs. These are the new interfaces for all mining commands, but not miner creation. These Protocol APIs provide a the new interface for the Network layer of go-filecoin. Protocol APIs also consume Plumbing and Porcelain APIs. They are ephemeral, like the Porcelain API. Note also that the MiningOnce command uses BlockMiningAPI to create its own block mining worker, which lasts only for the time it takes to mine and post a new block.

More detail on the individual protocols is coming soon.

Actors

Actors are Filecoin’s notion of smart contracts. They are not true smart contracts—with bytecode running on a VM—but instead implemented in Go. It is expected that other implementations will match the behaviour of the Go actors exactly. An ABI describes how inputs and outputs to the VM are encoded. Future work will replace this implementation with a "real" VM.

The Actor struct is the base implementation of actors, with fields common to all of them.

  • Code is a CID identifying the actor code, but since these actors are implemented in Go, is actually some fixed bytes acting as an identifier. This identifier selects the kind of actor implementation when a message is sent to its address.
  • Head is the CID of this actor instance’s state.
  • Nonce is a counter of #messages received from that actor. It is only set for account actors (the actors from which external parties send messages); messages between actors in the VM don’t use the nonce.
  • Balance is FIL balance the actor controls.

Some actors are singletons (e.g. the storage market) but others have multiple instances (e.g. storage miners). A storage miner actor exists for each miner in the Filesystem network. Their structs share the same code CID so they have the same behavior, but have distinct head state CIDs and balance. Each actor instance exists at an address in the state tree. An address is the hash of the actor’s public key.

The account actor doesn’t have any special behavior or state other than a balance. Everyone who wants to send messages (transactions) has an account actor, and it is from this actor’s address that they send messages.

Every storage miner has an instance of a miner actor. The miner actor plays a role in the storage protocol, for example it pledges space and collateral for storage, posts proofs of storage, etc. A miner actor’s state is located in the state tree at its address; the value found there is an Actor structure. The head CID in the actor structure points to that miner’s state instance (encoded).

Other built-in actors include the payment broker, which provides a mechanism for off-chain payments via payment channels, and the storage market, which starts miners and tracks total storage (aka "power"). These are both singletons.

Actors declare a list of exported methods with ABI types. Method implementations typically load the state tree, perform some query or mutation, then return a value or an error.

The state tree

Blockchain state is represented in the state tree, which contains the state of all actors. The state tree is a map of address to (encoded) actor structs. The state tree interface exposes getting and setting actors at addresses, and iterating actors. The underlying data structure is a Hash array-mapped trie. A HAMT is also often used to store actor state, eg when the actor wants to store a large map.

The canonical binary encoding used by Filecoin is CBOR. In Go, structs are CBOR-encoded by reflection. The ABI uses a separate inner encoding, which is manual.

Messages and state transitions

Filecoin state transitions are driven by messages sent to actors; these are our "transactions". A message is a method invocation on an actor. A message has sender and recipient addresses, and optional parameters such as an amount of filecoin to transfer, a method name, and parameters.

Messages from the same actor go on chain in nonce order. Note that the nonce is only really used by account actors (representing external entities such as humans). The nonce guards against replay of messages entering the VM for execution, but is not used for messages between actors during an external message’s execution.

Driving a state transition means invoking an actor method. One invokes a method on an actor by sending it a message. To send a message the message is created, signed, added to your local node’s message pool broadcast on the network to other nodes, which will add it to their message pool too. Some node will then mine a block and possibly include your message. In Filecoin, it is essential to remember that sending the message does not mean it has gone on chain or that its outcome has been reflected in the state tree. Sending means the message is available to be mined into a block. You must wait for the message to be included in a block to see its effect.

Read-only methods, or query messages, are the mechanism by which actor state can be inspected. These messages are executed locally against a read only version of the state tree of the head of the chain. They never leave the node, they are not broadcast. The plumbing API exposes MessageSend and MessageQuery for these two cases.

The processor is the entry point for making and validating state transitions represented by the messages. It is modelled Ethereum’s message processing system. The processor manages the application of messages to the state tree from the prior block/s. It loads the actor from which a message came, check signatures, then loads the actor and state to which a message is addressed and passes the message to the VM for execution.

The vm package has the low level detail of calling actor methods. A VM context defines the world visible from an actor implementation while executing,

Consensus

Filecoin uses a consensus algorithm called expected consensus. Unlike proof-of-work schemes, expected-consensus is a proof-of-stake model, where probability of mining a block in each round (30 seconds) is proportional to amount of storage a miner has committed to the network. Each round, miners are elected through a probabilistic but private mechanism akin to rolling independent, private, but verifiable dice. The expected number of winners in each round is one, but it could be zero or more than one miner. If a miner is elected, they have the right to mine a block in that round.

Given the probabilistic nature of mining new blocks, more than one block may be mined in any given round. Hence, a new block might have more than one parent block. The parents form a set, which we call a tipset. All the blocks in a tipset are at the same height and share the same parents. Tipsets contain one or more blocks. A null block count indicates the absence of any blocks mined in a previous round. Subsequent blocks are built upon all of the tipset; there is a canonical ordering of the messages in a tipset defining a new consensus state, not directly referenced from any of the tipset’s blocks.

Storage protocol

The storage protocol is mechanism by which clients make deals directly with storage miners to store their data, implemented in protocol/storage.

A storage miner (protocol/storage/miner.go) advertises storage with an on-chain ask, which specifies an asking price and storage capacity at that price. Clients discover asks by iterating miner actors’ on-chain state. A client wishing to take an ask creates a deal proposal. A proposal references a specific unit of data, termed a piece, which has a CID (hash of the bytes). A piece must fit inside a single sector (see below) as defined by network parameters.

A storage client (protocol/storage/client.go) connects directly to a miner to propose a deal, using a libp2p peer id embedded in the on-chain storage miner actor data. An off-chain lookup service maps peer ids to concrete addresses, in the form of multiaddr, using a libp2p distributed hash table (DHT). A client also creates a payment channel so the miner can be paid over time for storing the piece. The miner responds with acceptance or otherwise.

When proposing a deal, a client loads the piece data into its IPFS block service. This advertises the availability of the data to the network, making it available to miners. A miner accepting a deal pulls the data from the client (or any other host) using the IPFS block service bitswap protocol.

A miner packs pieces from many clients into a sector, which is then sealed with a proof of replication (aka commitment). Sealing is a computationally intensive process that can take tens of minutes. A client polls the miner directly for deal status to monitor the piece being received, staged in a sector, the sector sealed, and the proof posted to the blockchain. Once the sector commitment is on chain, the client can observe it directly. A miner periodically computes proofs for all sealed sectors and posts on chain. There is no on-chain mapping of pieces to sectors; a client must keep track of its own pieces.

Note that the mechanisms for communication of deals and state are not specified in the protocol, except the format of messages and the eventual on-chain commitment. Other mechanisms may be used.

The storage client commands interface to a go-filecoin daemon in the same way as other node commands. Right now, a client must be running a full node, but that’s not in-principle necessary. Future reorganisation will allow the extraction of a thin client binary.

Data preparation is entirely the responsibility of the client, including breaking it up into appropriate chunks (<= sector size), compressing, encrypting, adding error-correcting codes, and replicating widely enough to achieve redundancy goals. In the future, we will build a client library which handles many of these tasks for a user. We also plan support for "repair miners", to whom responsibility can be delegated for monitoring and repairing faults.

Retrieval

Retrieval mining is not necessarily linked to storage mining, although in practise we expect all storage miners to also run retrieval miners. Retrieval miners serve requests to fetch content, and are not much more than a big cache and some logic to find missing pieces.

The retrieval protocol and implementation are not yet fully developed. At present (early 2019), retrieval is not charged for, and always causes unsealing of a sector.

Entry point

There’s no centrally dispatched event loop. The node starts up all the components, connects them as needed, and waits. Protocols (goroutines) communicate through custom channels. This architecture needs more thought, but we are considering moving more inter-module communication to use iterators (c.f. those in Java). An event bus might also be a good pattern for some cases, though.

Sector builder & proofs

A storage mining node commits to storage by cryptographically proving that it has stored a sector, a process known as sealing. Proof of replication, or PoRep, is an operation which generates a unique copy (sealed sector) of a sector's original data, a SNARK proof, and a set of commitments identifying the sealed sector and linking it to the corresponding unsealed sector. The commitSector message, posted to the blockchain, includes these commitments (CommR, CommRStar, CommD) and the SNARK proof.

A storage miner continually computes proofs over their sealed sectors and periodically posts a summary of their proofs on chain. When a miner commits their first sector to the network (with commitSector message included in some block), a "proving period" begins. A proving period is a window of time (a fixed number of blocks) in which the miner must generate a "proof of space-time", or PoSt, in order to demonstrate to the network that they have not lost the sector which they have committed. If the miner does not get a submitPoSt message included in a block during a proving period, it may be penalised ("slashed").

Storage and proofs are administered by the sector builder. Most of the sector builder is implemented in Rust and invoked via a FFI. This code includes functionality to:

  • write (and "preprocess") user piece-bytes to disk,
  • schedule seal (proof-of-replication) jobs,
  • schedule proof-of-spacetime generation jobs,
  • schedule unseal (retrieval) jobs,
  • verify proof-of-spacetime and proof-of-replication-verification,
  • map between replica commitment and sealed sector-bytes,
  • map between piece key (CID of user piece) and sealed sector-bytes.

The Go SectorBuilder interface corresponds closely to the rust SectorBuilder struct. Rust code is invoked directly (in-process) via Cgo. The sector builder’s lifecycle (in Rust) is controlled by go-filecoin. Cgo functions like C.CBytes are used to move bytes across Cgo from Go to Rust; Go allocates in the Rust heap through Cgo and then provides Rust with pointers from which it can reconstitute arrays, structs, and so forth.

Sectors and sealed sectors (aka replicas) are just flat files on disk. Sealing a sector is a destructive operation on the sector. The process of sealing yields metadata such as the proof/commitment, which is stored is a separate metadata store, not within the replica file.

We intend the sector builder interface to represent a service, abstracting away both policy (e.g. sector packing, scheduling of PoSt calculation) and implementation details. In the future, we would like to able to interface to it via IPC/RPC as well as FFI.

The sector builder and proofs code is written in Rust partly to ease use of the Bellman zk-SNARK library. The PoRep and PoSt code is under active development. PoRep is integrated with go-filecoin, while the PoST implementation and integration is still in progress (March 2019).

Building and distribution.

The Rust code responsible for sectors and proofs is in the rust-fil-proofs repo. This repo is included in go-filecoin as a Git submodule. The submodule refers to a specific repository SHA hash. The install-rust-proofs.sh script, invoked by the deps build step of go-filecoin, builds the Rust proofs code and copies binary assets to locations hardcoded in Go interface code.

As an alternative to compiling Rust code locally, the continuous integration server publishes an archive of precompiled binary objects with every successful build of the rust-fil-proofs/master branch. These releases are identified by the Git submodule SHA. This archive is pushed to the GitHub releases service. When the FILECOIN_USE_PRECOMPILED_RUST_PROOFS environment variable is set, install-rust-proofs.sh attempts to fetch these assets from GitHub releases and installs them in the same hardcoded locations required by the Go build.

The build or tarball contains:

  • libfilecoin_proofs.a (a static library)
  • libfilecoin_proofs.h (corresponding C-header file)
  • libfilecoin_proofs.pc (a pkg-config manifest, used to specify linker dependencies)
  • paramcache (populates Groth parameter-cache
  • paramfetch (fetches Groth parameters from IPFS gateway into cache)
  • parameters.json (contains the most recently-published Groth parameters)

Groth parameters

The proving algorithms rely on a large binary parameter file known as the Groth parameters. This file is stored in a cache directory, typically /tmp/filecoin-proof-parameters. When proofs code changes, the params may need to change.

The paramcache program populates the Groth parameter directory by generating the parameters, a slow process (10s of minutes). If the cache directory is already populated, generation is skipped. The parampublish program (not part of the precompiled proofs archive) publishes params from the cache directory to a local IPFS node. The CIDs of the parameter files thus published must be pinned (made continuously available) e.g. by a Protocol Labs pinning service. The paramfetch program fetches params to local cache directory from IPFS gateway. The install-rust-proofs.sh script fetches or generates these Groth parameters as necessary when building deps.

Groth parameters in /tmp/filecoin-proof-parameters are accessed at go-filecoin runtime. The parameters are identified by the parameters.json file from fil-rust-proofs, which includes a checksum.

Proof mode configuration

For ease of development, go-filecoin can be configured to use a test proofs mode, which will cause storage miners to use sectors into which only 1016 bytes of user data can be written. This lowers the computational burden of sealing and generating PoSts.

The genesis.car in fixtures/test/ is configured to use test proofs mode.

Networking

Filecoin relies on libp2p for its networking needs.

libp2p is a modular networking stack for the peer-to-peer era. It offers building blocks to tackle requirements such as peer discovery, transport switching, multiplexing, content routing, NAT traversal, pubsub, circuit relay, etc., most of which Filecoin uses. Developers can compose these blocks easily to build the networking layer behind their P2P system.

A detailed overview of how Filecoin uses libp2p can be found in the Networking doc.

Filesystem storage

The repo, aka fsrepo, is a directory stored on disk containing all necessary information to run a go-filecoin daemon, typically at $HOME/.filecoin. The repo does not include client data stored by storage miners, which is held instead in the sector base. The repo does include a JSON config file with preferences on how the daemon should operate, several key value datastores holding data important to the internal services, and the keystore which holds private key data for encryption.

JSON Config

The JSON config file is stored at $HOME/.filecoin/config.json, and can be easily edited using the go-filecoin config command. Users can also edit the file directly at their own peril.

Datastores

The key-value datastores in the repo include persisted data from a variety of systems within Filecoin. Most of them hold CBOR encoded data keyed on CID, however this varies. The key value stores include the badger, chain, deals, and wallet directories under $HOME/.filecoin.

The purpose of these directories is:

  • Badger is a general purpose datastore currently only holding the genesis key, but in the future, almost all our datastores should be merged into this one.
  • Chain is where the local copy of the blockchain is stored.
  • Deals is where the miner and client store persisted information on open deals for data storage, essentially who is storing what data, for what fee and which sectors have been sealed.
  • Wallet is where the user’s Filecoin wallet information is stored.

Keystore

The keystore contains the binary encoded peer key for interacting securely over the network. This data lives in a file at $HOME/.filecoin/keystore/self.

Testing

The go-filecoin codebase has a few different testing mechanisms: unit tests, in-process integration tests, "daemon" integration tests, and a couple of high level functional tests.

Many parts of code have good unit tests. We’d like all parts to have unit tests, but in some places it hasn’t been possible where prototype code omitted testability features. Functionality on the Node object is a prime example, which we are moving away from.

Historically there has been a prevalence of integration-type testing. Relying only on integration tests can make it hard to verify small changes to internals. We’re driving towards both wide unit test coverage, with integration tests to verifying end-to-end behaviour.

There are two patterns for unit tests. In plumbing and low level components, many tests use real dependencies (or at least in-memory versions of them). For higher level components like porcelain or protocols, where dependencies are more complex to set up, we often use fake implementations of just those parts of the plumbing that are required. It is a goal to have both unit tests (with fakes or real deps), as well as higher level integration-style tests.

Code generally uses simple manual dependency injection. A component that takes a large number of deps at construction can have them factored into a struct. A module should often (re-)declare a narrow subset of the interfaces it depends on (see Consumer-defined interfaces)), in canonical Go style.

Some node integration tests start one or more full nodes in-process. This is useful for fine-grained control over the node being tested. Setup for these tests is a bit difficult and we aim to make it much easier to instantiate and test full nodes in-process.

Daemon tests are end-to-end integration tests that exercise the command interface of the go-filecoin binary. These execute separate go-filecoin processes and drive them via the command line. These tests are mostly under the commands package, and use TestDaemon. Because the test and the daemon being tested are in separate processes, getting access to the daemon process’s output streams or attaching a debugger is tricky; see comments in [createNewProcess][(https://github.com/filecoin-project/go-filecoin/blob/726e6705860ddfc8ca4e55bc3610ad2230a95c0c/testhelpers/commands.go#L849)

In daemon tests it is important to remember that messages do not have any effect on chain state until they are mined into a block. Preparing an actor in order to receive messages and mutate state requires some delicate network set-up, mining messages into a block to create the actor before it can receive messages. See MineOnce in mining/scheduler which synchronously performs a round of block mining and then stops, pushing the test state forward.

The functional-tests directory contains some Go and Bash scripts which perform complicated multi-node tests on our continuous build. These are not daemon tests, but run separately.

Some packages have a testing.go file with helpers for setting up tests involving that package’s types. The types/testing.go file has some more generally useful constructors. There is also a top-level testhelpers package with higher level helpers, often used by daemon tests.

We’re in process of creating the Filecoin Automation and Systems Toolkit (FAST) library. The goal of this is to unify duplicated code paths which bootstrap and drive go-filecoin daemons for daemon tests, functional tests, and network deployment verification, providing a common library for filecoin automation in Go.

Tests are typically run with go run ./build/*.go test. It passes flags to go test under the hood, so you can provide -run <regex> to run a subset of tests. By default it will run unit and integration tests, but skip more expensive functional and sectorbuilder tests. For a complete description of testing flags see Test Categorization. Vanilla go test also works, after build scripts have built and installed appropriate dependencies.

Test Categorization

Unit Tests (-unit)

By default unit tests are enabled when issuing the go test command. To disable pass -unit=false. A unit test exercises one or a few code modules exhaustively. Unit tests do not involve complex integrations or non-trivial communication, disk access, or artificial delays. A unit test should complete in well under a second, frequently <10 milliseconds. Unit tests should have no side effects and be executable in parallel

Integration Tests (-integration)

By default integration tests are enabled when issuing the go test command. To disable pass -integration=false. An integration test exercises integrated functionality and/or multiple nodes and may include multiple processes, inter-node communication, delays for consensus, nontrivial disk access and other expensive operations. Integration tests may involve multiple processes (daemon tests) or in-process integrations. An individual integration test should complete in seconds.

Functional Tests (-functional)

By default functional tests are disabled when issuing the go test command. To enable pass -functional. A functional test is an extensive multi-node orchestration or resource-intensive test that may take minutes to run.

Sector Builder Tests (-sectorbuilder)

By default sectorbuilder tests are disabled when issuing the go test command. To enable pass -sectorbuilder. A sectorbuilder test is a resource-intensive test that may take minutes to run.

Dependencies

Dependencies in go-filecoin are managed as go modules, go's new dependency system.

If you've cloned go-filecoin into your GOPATH, you may need to set the GO111MODULES environment variable to on. The build system automatically sets this but your favorite editor or IDE may not work without it.

Patterns

The project makes heavy use of or is moving towards a few key design patterns, explained here.

Plumbing and porcelain

The plumbing and porcelain pattern is borrowed from Git. Plumbing and porcelain form the API to the internal core services, and will replace the api package.

Plumbing is the small set of public building blocks of queries and operations that protocols, clients and humans want to use with a Filecoin node. These are things like MessageSend, GetHead, GetBlockTime, etc. By fundamental, we mean that it doesn't make sense to expose anything lower level. The bar for adding new plumbing is high. It is very important, for testing and sanity, that plumbing methods be implemented in terms of their narrowest actual dependencies on core services, and that they not depend on Node or another god object.

The plumbing API is defined by its implementation in plumbing/api.go. Consumers of plumbing (re-)define the subset of plumbing on which they depend, which is an idiomatic Go pattern (see below). Implementations of plumbing live in their own concisely named packages under plumbing.

Porcelain are calls on top of the plumbing API. A porcelain call is a useful composition of plumbing calls and is implemented in terms of calls to plumbing. An example of a porcelain call is CreateMiner == MessageSend + MessageWait + ConfigUpdate. The bar is low for creation of porcelain calls. Porcelain calls should define the subset of the plumbing interface on which they depend for ease of testing.

Porcelain lives in a single porcelain package. Porcelain calls are free functions that take the plumbing interface as an argument. The call defines the subset of the plumbing interface that it needs, which can be easily faked in testing.

We are in the process of refactoring all protocols to depend only on porcelain, plumbing and other core APIs, instead of on the Node.

Consumer-defined interfaces

Go interfaces generally belong in the package that uses values of the interface type, not the package that implements those values. This embraces Postel's law, reducing direct dependencies between packages and making them easier to test. It isolates small changes to small parts of the code.

Note that this is quite different to the more common pattern in object-oriented languages, where interfaces are defined near their implementations. Our implementation of plumbing and porcelain embraces this pattern, and we are adopting it more broadly.

This idiom is unfortunately hidden away in a wiki page about code review. See also Dave Cheney on SOLID Go Design.

Observability

go-filecoin uses Opencensus-go for stats collection and distributed tracing instrumentation. Stats are exported for consumption via Prometheus and traces are exported for consumption via Jaeger.

Metrics

go-filecoin can be configured to collect and export metrics to Prometheus via the MetricsConfig. The details of this can be found inside the config/ package. To view metrics from your filecoin node using the default configuration options set the prometheusEnabled value to true, start the filecoin daemon, then visit localhost:9400/metrics in your web-browser.

Tracing

go-filecoin can be configured to collect and export traces to Jaeger via the TraceConfig. The details of this can be found inside the config/ package. To collect traces from your filecoin node using the default configuration options set the jaegerTracingEnabled value to true, start the filecoin daemon, then follow the Jaeger Getting started guide.