This is a proposed interface for a _Consensus_ module to be used in a simplified version of the Lotus Filecoin node software. ## Consensus Interface ```go=+ package consensus // Instance represents a single instance of a consensus protocol. type Instance interface { // Start starts the execution of this instance of consensus. // The function returns a channel on which the caller will receive a // Result value once the execution of the instance finishes. The Result // contains either the decision value or an error. Start() (<-chan Result) } // Factory creates consensus instances. type Factory interface { // NewInstance returns a consensus instance. // The returned consensus instance is not started yet. The execution // starts only after the Start() method is called on the instance. NewInstance( // instanceID is a unique ID identifying this instance of consensus. // For example, if used for blockchain consensus, // the instanceID might correspond to the block height // for which a block is being agreed upon. instanceID []byte, // membership describes all nodes that participate in the created // consensus instance. Each NodeDescriptor's ID field // must be the same as the key under which it is stored here. // At least one node in the membership must have a non-zero weight. membership map[NodeID]*NodeDescriptor, // propose is a function that returns the local node's // consensus proposal as a byte slice. // The consensus implementation may call this function any time. // If propose returns a non-nil error, the proposal may be undefined // and the consensus implementation may stop with a non-nil error. propose func() ([]byte, error), // validate is a function for validating a proposal // (potentially received from another node). // It returns nil if the given proposal is valid, // a non-nil error otherwise. validate func([]byte) error, // params are arbitrary additional parameters // to the created consensus instance represented as key-value pairs. // They may be instance- and implementation-specific, // e.g., timeouts, quorum sizes, etc... params map[string]interface{}, ) Consensus } ``` ## Data Types ```go=+ type NodeID []byte // NodeDescriptor describes a node's identity. type NodeDescriptor struct { // ID is a unique identifier of the node throughout the whole system. ID NodeID // Addr is the network address of the node. Addr string // Key is the public key of the node. Key []byte // Weight represents a relative (compared to other nodes) // "voting" power of the node, such as in proof-of-stake. Weight uint64 } // Result encapsulates the output of a consensus instance. It either // contains a decision value or an error. type Result struct { // Decision holds the decided value. // If the consensus instance terminates successfully, // Decision is guaranteed to be the same across all nodes. Decision []byte // Error is set to a non-nil value if the local execution of the // consensus instance encounters an error. This does not imply that the // other nodes have not decided. It just means that the local instance // did not terminate correctly. If this value is not nil, Decision is // undefined and must not be used. Error error } ``` ## Example Implementation (Granite) ```go=+ // First, we define our factory of Granite consensus instances. type GraniteFactory struct { // Potentially other Granite-specific fields. // The following are just examples. // The ID of the local node. myID NodeID // Implementation of cryptographic primitives used by Granite. crypto Crypto // Implementatino of a broadcast communication primitive // that Granite nodes use to communicate. broadcast Broadcast } // NewGraniteFactory creates a new instance of the Granite consensus factory. // The given crypto and braodcast modules will be used // by all created Granite consensus instances. func NewGraniteFactory() *GraniteFactory(crypto Crypto, broadcast Broadcast) { return &GraniteFactory{crypto, broadcast} } // We now define the Granite consensus instance. // It contains all the information a single instance // of Granite consensus needs to have. // The ones listed here are just examples. type GraniteConsensus struct { instanceID []byte membership map[NodeID]*NodeDescriptor myID NodeID propose func() ([]byte, error) validate func([]byte) error timeout time.Duration crypto Crypto broadcast Broadcast } func (gc *GraniteConsensus) Start() <-chan Result { // The implementation of Granite consensus, e.g., proposal, err := gc.propose() if err != nil { //... } signedProposal := gc.Crypto.Sign(proposal) gc.broadcast.Broadcast(signedProposal) // ...implementation continues... } // Finally, this method combines the parameters of the GraniteFactory // with the instance-specific ones (received as arguments) // and returns a new instance of Granite consensus. func (gf *GraniteFactory) NewInstance( instanceID []byte, membership map[NodeID]*NodeDescriptor, propose func() ([]byte, error), validate func([]byte) error, params map[string]interface{}, ) *GraniteConsensus { return &GraniteConsensus{ instanceID, membership, gf.myID, propose, validate, params["timeout"], gf.crypto, gf.broadcast, } } ``` ## Example Usage ```go=+ factory := NewGraniteFactory(crypto, broadcast) instance := factory.NewInstance( nextHeight, membership, myID, params propose, validate ) resultPromise := instance.Start() // ... potentially more code ... // When the decision is needed, wait for the consensus to terminate. result := <-resultPromise if result.Error != nil { // Deal with error. } else { // Process result.Decision. } ``` ## Notes - The interface still does not cover proofs of consensus, i.e.,