This repo contains Go implementation of the dBFT 2.0 consensus algorithm and its models written in TLA⁺ language.
- All control flow is done in main
dbftpackage. Most of the code which communicates with external world (event time events) is hidden behind interfaces, callbacks and generic parameters. As a consequence it is highly flexible and extendable. Description of config options can be found inconfig.go. dbftpackage containsPrivateKey/PublicKeyinterfaces which permits usage of one's own cryptography for signing blocks onCommitstage. Refer toidentity.goforPrivateKey/PublicKeydescription. No default implementation is provided.dbftpackage containsHashinterface which permits usage of one's own hash implementation without additional overhead on conversions. Instantiate dBFT with custom hash implementation that matches requirements specified in the corresponding documentation. Refer toidentity.goforHashdescription. No default implementation is provided.dbftpackage containsBlockandTransactionabstractions located at theblock.goandtransaction.gofiles. Every block must be able to be signed and verified as well as implement getters for main fields.Transactionis an entity which can be hashed. Two entities having equal hashes are considered equal. No default implementation is provided.dbftcontains generic interfaces for payloads. No default implementation is provided.dbftcontains genericTimerinterface for time-related operations.timerpackage contains defaultTimerprovider that can safely be used in production code. The interface itself is mostly created for tests dealing with dBFT's time-dependant behaviour.internalcontains an example of custom identity types and payloads implementation used to implement an example of dBFT's usage with 6-node consensus. Refer tointernalsubpackages for type-specific dBFT implementation and tests. Refer tointernal/simulationfor an example of dBFT library usage.formal-modelscontains the set of dBFT's models written in TLA⁺ language and instructions on how to run and check them. Please, refer to the README for more details.
A client of the library must implement its own event loop. The library provides 6 callbacks that change the state of the consensus process:
Start()which initializes internal dBFT structuresReset()which reinitializes the consensus processOnTransaction()which must be called everytime new transaction from the list of proposed transactions appearsOnNewTransaction()which is an extension supplying dynamic block time functionality and must be called everytime new transaction comes to the node's memory pool if dynamic block time extension is enabledOnReceive()which must be called everytime new payload is receivedOnTimer()which must be called everytime timer fires
A minimal example can be found in internal/simulation/main.go.
- dBFT high-level description on NEO website https://docs.neo.org/docs/en-us/basic/consensus/dbft.html
- dBFT research paper https://github.com/NeoResearch/yellowpaper/blob/master/releases/08_dBFT.pdf
- C# NEO node implementation works with the memory pool model, where only transaction hashes
are proposed in the first step of the consensus and
transactions are synchronized in the background.
Some of the callbacks are in config with sole purpose to support this usecase. However it is
very easy to extend
PrepareRequestto also include proposed transactions. - NEO has the ability to change the list nodes which verify the block (they are called Validators). This is done through
GetValidatorscallback which is called at the start of every epoch. In the simple case where validators are constant it can return the same value everytime it is called. ProcessBlockis a callback which is called synchronously every time new block is accepted. It can or can not persist block; it also may keep the blockchain state unchanged. dBFT will NOT be initialized at the next height by itself to collect the next block untilResetis called. In other words, it's the caller's responsibility to initialize dBFT at the next height even after block collection at the current height. It's also the caller's responsibility to update the blockchain state before the next height initialization so that other callbacks includingCurrentHeightandCurrentHashreturn new values.