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.,