Oasis Core is designed around the principle of modularity. The consensus layer
is an interface that provides a number of important services to other parts of
Oasis Core. This allows, in theory, for the consensus backend to be changed. The
different backends live in go/consensus
, with the general interfaces in
go/consensus/api
. The general rule is that anything outside of a specific
consensus backend package should be consensus backend agnostic.
For more details about the actual API that the consensus backends must provide see the consensus backend API documentation.
Currently the only supported consensus backend is Tendermint, a BFT consensus protocol. For this reason some API surfaces may not be fully consensus backend agnostic.
Each consensus backend needs to provide the following services:
- Epoch Time, an epoch-based time keeping service.
- Random Beacon, a source of randomness for other services.
- Staking, operations required to operate a PoS blockchain.
- Registry, an entity/node/runtime public key and metadata registry service.
- Committee Scheduler service.
- Governance service.
- Root Hash, runtime commitment processing and minimal runtime state keeping service.
- Key Manager policy state keeping service.
Each of the above services provides methods to query its current state. In order to mutate the current state, each operation needs to be wrapped into a consensus transaction and submitted to the consensus layer for processing.
Oasis Core defines an interface for each kind of service (in
go/<service>/api
), with all concrete service implementations living together
with the consensus backend implementation. The service API defines the
transaction format for mutating state together with any query methods (both are
consensus backend agnostic).
The Tendermint consensus backend lives in go/consensus/tendermint
.
For more information about Tendermint itself see the Tendermint Core developer documentation. This section assumes familiarity with the Tendermint Core concepts and APIs. When used as an Oasis Core consensus backend, Tendermint Core is used as a library and thus lives in the same process.
The Tendermint consensus backend is split into two major parts:
-
The first part is the ABCI application that represents the core logic that is replicated by Tendermint Core among the network nodes using the Tendermint BFT protocol for consensus.
-
The second part is the query and transaction submission glue that makes it easy to interact with the ABCI application, presenting everything via the Oasis Core Consensus interface.
Tendermint Core consumes consensus layer logic via the ABCI protocol, which assumes a single application. Since we have multiple services that need to be provided by the consensus layer we use an ABCI application multiplexer which performs some common functions and dispatches transactions to the appropriate service-specific handler.
The multiplexer lives in go/consensus/tendermint/abci/mux.go
with the
multiplexed applications, generally corresponding to services required by the
consensus layer interface living in go/consensus/tendermint/apps/<app>
.
All application state for the Tendermint consensus backend is stored using our Merklized Key-Value Store.
Service implementations for the Tendermint consensus backend live in
go/consensus/tendermint/<service>
. They provide the glue between the
services running as part of the ABCI application multiplexer and the Oasis Core
service APIs. The interfaces generally provide a read-only view of the consensus
layer state at a given height. Internally, these perform queries against the
ABCI application state.
Queries do not use the ABCI query functionality as that would incur needless
overhead for our use case (with Tendermint Core running in the same process).
Instead, each multiplexed service provides its own QueryFactory
which can be
used to query state at a specific block height.
An example of a QueryFactory
and the corresponding Query
interfaces for the
staking service are as follows:
// QueryFactory is the staking query factory interface.
type QueryFactory interface {
QueryAt(ctx context.Context, height int64) (Query, error)
}
// Query is the staking query interface.
type Query interface {
TotalSupply(ctx context.Context) (*quantity.Quantity, error)
CommonPool(ctx context.Context) (*quantity.Quantity, error)
LastBlockFees(ctx context.Context) (*quantity.Quantity, error)
// ... further query methods omitted ...
}
Implementations of this interface generally directly access the underlying ABCI
state storage to answer queries. Tendermint implementations of Oasis Core
consensus services generally follow the following pattern (example from the
staking service API for querying TotalSupply
):
func (s *staking) TotalSupply(ctx context.Context, height int64) (*quantity.Quantity, error) {
q, err := s.querier.QueryAt(ctx, height)
if err != nil {
return nil, err
}
return q.TotalSupply(ctx)
}
Each serialized signed Oasis Core transaction directly corresponds to a Tendermint transaction. Submission is performed by pushing the serialized transaction bytes into the mempool where it first undergoes basic checks and is then gossiped to the Tendermint P2P network.
Handling of basic checks and transaction execution is performed by the ABCI application multiplexer mentioned above.