Developing a Custom Consensus Mechanism
Most projects that come with a request to "develop your own consensus" don't actually need a custom consensus. They need an application-specific chain with modified parameters: different block time, different transaction inclusion rules, custom mempool. This is solved via Cosmos SDK or OP Stack without writing a new consensus protocol. True custom consensus is 1–2 years of work by a team of experienced distributed systems engineers and formal verification of correctness. This is important to understand before starting technical discussion.
When Custom Consensus is Actually Needed
Legitimate cases: specialized network with non-standard performance requirements (> 100k TPS, deterministic finality < 100ms), consortium networks with custom validator rules, research/academic projects, experimental mechanisms (VDF-based randomness, threshold signatures as consensus).
In most cases it's simpler to take an existing algorithm and adapt it. Let's examine the architectural choice space.
Taxonomy of Consensus Protocols
Classical BFT: pBFT and Derivatives
pBFT (Practical Byzantine Fault Tolerance, Castro & Liskov, 1999) — first practical BFT algorithm. O(n²) messages, works with < n/3 Byzantine nodes. In pure form doesn't scale past ~20 nodes due to communication overhead.
Modern derivatives:
- HotStuff (used in LibraBFT/DiemBFT/Jolteon): linear communication complexity O(n), leader scheme with pipelining. Facebook/Meta used for Diem.
- Tendermint/CometBFT: round-based, deterministic finality per block, used in Cosmos SDK. Two phases: prevote and precommit. Requires > 2/3 voting power for finality.
- PBFT with threshold signatures: replace n² communication with aggregation via BLS threshold signatures — each validator signs with BLS key, aggregator collects threshold signatures into one.
Nakamoto Consensus and Derivatives
Nakamoto PoW — probabilistic finality, fork choice rule (longest chain / most work). Never final, but practically irreversible after sufficient confirmations. Simplicity is the main advantage.
GHOST protocol (Greedy Heaviest-Observed Subtree): fork choice considers uncle blocks, not just main chain. Used in Ethereum (Gasper — combination of GHOST + Casper FFG).
Proof-of-Stake variants: in PoS "computational work" is replaced with stake. Validator selection via VRF (Verifiable Random Function) — Algorand, Cardano Ouroboros.
DAG-Based Consensus
Hashgraph (Hedera): events organized in DAG, virtual voting without messages. Deterministic algorithm from DAG structure computes consensus timestamp and order.
Narwhal/Bullshark (Sui): Narwhal — DAG-based mempool with certified availability (each block certified by 2f+1 signatures). Bullshark — interprets DAG for ordering. Separation of data dissemination and ordering.
Mysticeti (new Sui consensus): removes leader from critical path, reduces latency.
Implementation: Example HotStuff-Inspired Protocol
Let's examine key components when implementing BFT consensus in Go:
Data Structures
type Block struct {
Height uint64
ParentHash [32]byte
Txns []Transaction
QC *QuorumCertificate // proof of previous block
Timestamp int64
ProposerID NodeID
}
type QuorumCertificate struct {
BlockHash [32]byte
Height uint64
Signatures []BLSSignature // threshold aggregated signature
Signers []NodeID
}
type Vote struct {
BlockHash [32]byte
Height uint64
Round uint32
VoterID NodeID
Signature BLSSignature
}
Three Phases of HotStuff
HotStuff organizes consensus in three phases (prepare, pre-commit, commit) with pipelining — while block k passes commit, block k+1 passes pre-commit, block k+2 is in prepare:
type HotStuffNode struct {
id NodeID
height uint64
lockedQC *QuorumCertificate // locked at pre-commit phase
preparedQC *QuorumCertificate // prepared at prepare phase
privateKey bls.PrivateKey
validators ValidatorSet
}
func (n *HotStuffNode) onReceiveProposal(block *Block) {
// Safety rule: accept only if block.QC >= n.lockedQC
if block.QC.Height < n.lockedQC.Height {
return // reject
}
// Liveness rule: accept if block.QC >= n.preparedQC
// or block extends locked block
if !n.safeNode(block) {
return
}
vote := n.createVote(block)
n.sendToLeader(vote)
}
func (n *HotStuffNode) safeNode(block *Block) bool {
// Extends locked branch OR QC higher than lockedQC
return block.QC.Height > n.lockedQC.Height ||
n.extendsLockedBlock(block)
}
BLS Threshold Signatures
Signature aggregation via BLS12-381 curve — standard for modern BFT protocols. Threshold scheme (t of n): each validator signs with own key, aggregator collects t signatures and creates one aggregated signature, verifiable by one public key:
import "github.com/herumi/bls-eth-go-binary/bls"
func aggregateSignatures(sigs []bls.Sign) bls.Sign {
var agg bls.Sign
agg.Add(&sigs[0])
for i := 1; i < len(sigs); i++ {
agg.Add(&sigs[i])
}
return agg
}
func verifyQC(qc *QuorumCertificate, validators ValidatorSet) bool {
pubkeys := make([]bls.PublicKey, len(qc.Signers))
for i, id := range qc.Signers {
pubkeys[i] = validators.GetPublicKey(id)
}
aggPubkey := bls.AggregatePubkeys(pubkeys)
return qc.Signatures[0].VerifyHash(&aggPubkey, qc.BlockHash[:])
}
View Change: Handling Leader Failures
Liveness under Byzantine leader is the hardest aspect. In HotStuff, view change occurs on timeout:
func (n *HotStuffNode) onTimeout(view uint32) {
// Broadcast timeout message with current lockedQC
timeout := TimeoutMsg{
View: view,
LockedQC: n.lockedQC,
SenderID: n.id,
Sig: n.sign(view, n.lockedQC),
}
n.broadcast(timeout)
}
func (n *HotStuffNode) onReceiveTimeouts(timeouts []TimeoutMsg) {
if len(timeouts) < n.validators.QuorumSize() {
return
}
// New leader — node with highest view in round-robin or VRF
newLeader := n.electLeader(timeouts[0].View + 1)
if newLeader == n.id {
// Select highest QC from timeout messages
highQC := n.highestQC(timeouts)
n.proposeBlock(highQC)
}
}
Formal Verification
For production consensus protocol — formal verification of safety and liveness properties is mandatory. Tools:
TLA+ — formal specification language. Specify safety invariant: "two honest nodes cannot commit different blocks at the same height". TLC model checker verifies against all reachable states for n ≤ 5–7 nodes.
Ivy — language for distributed protocol verification. Hedera team used for Hashgraph. Coq/Lean — for proof assistant approach.
Without formal verification, custom consensus should not be used in production with real assets. Blockchain history is full of consensus bugs discovered years later (Ethereum Byzantium fork bugs, recent Cosmos SDK consensus vulnerabilities).
Network Layer: P2P Transport
Consensus messages require low-latency delivery. Protobuf serialization is mandatory (JSON is too slow for consensus-critical messages). Transport:
libp2p — de facto standard in Web3. GossipSub for broadcast, Direct streams for unicast. Used in Ethereum, Filecoin, Polkadot.
QUIC/gRPC — for more controlled P2P topologies (enterprise blockchain).
Stack and Timeline
Implementation language: Go (Tendermint, Ethereum CL) or Rust (Solana, NEAR, Polkadot substrate) — both have mature BLS libraries and p2p stacks.
Realistic stages:
- Specification and formal model in TLA+: 4–6 weeks
- Basic happy path implementation: 8–12 weeks
- View change and Byzantine fault handling: 8–12 weeks
- Testing (chaos testing, Byzantine fault injection): 8–12 weeks
- Formal verification: 4–8 weeks
- Security audit: 6–8 weeks
Total: 10–18 months to production-ready consensus. Adapting existing (CometBFT/HotStuff reference impl) with custom parameters — 3–6 months.







