Skip to main content

Fault Tolerance and Finality

BFT Guarantees

Savitri consensus uses a BFT (Byzantine Fault Tolerant) voting protocol with a 2f+1 quorum (67% threshold).

Fault Tolerance

Total Nodes (n)Max Byzantine (f)Quorum Required
302
514
725
1037
15411

Formula: f = floor((n-1) / 3), quorum = 2f + 1

Safety

A block is only finalized when a Block Acceptance Certificate (BAC) is produced with signatures from 2f+1 validators. This guarantees:

  • No conflicting blocks can be finalized at the same height
  • No rollbacks once a BAC is issued (deterministic finality, not probabilistic)
  • Byzantine nodes cannot forge certificates without quorum participation

Liveness

The network can produce blocks as long as n - f honest nodes are online:

  • Proposer rotation: After 50 blocks, proposer steps down to prevent single-point failure
  • Election watchdog: Re-triggers election if no block progress for 60 seconds
  • Progressive quorum relaxation: After 3+ consecutive election failures, minimum candidates drops to 2

DAG Structure

BlockHeaders support multi-parent DAG structure for concurrent block production:

pub struct BlockHeader {
pub parent_hash: Vec<u8>, // primary parent (backward compatible)
pub parent_hashes: Vec<Vec<u8>>, // additional parents for DAG
// ...
}

Conflict Detection

The ConflictDetector identifies:

  • Double-spend conflicts: Same UTXO referenced in multiple branches
  • State conflicts: Conflicting state transitions
  • Fork detection: Multiple blocks at the same height from different proposers

Conflict Resolution

When conflicts are detected:

  1. Choose branch with higher cumulative PoU score
  2. Revert conflicting transactions
  3. Re-apply non-conflicting transactions from the losing branch

Group-Aware Consensus

Group Formation

Masternodes organize lightnodes into groups:

  1. Sort lightnodes by PoU score
  2. Assign to groups of group_size (3 dev, 7 prod)
  3. Publish group assignments via gossipsub
  4. Deterministic group IDs based on epoch (not wall-clock time)

Intra-Group Election

Within each group:

  1. Members share PoU scores
  2. Highest PoU score becomes proposer (peer_id tiebreaker)
  3. Minimum (total+1)/2 candidates required
  4. After 3+ failures: relax to minimum 2 candidates

Block Acceptance

  1. Group proposer produces a block
  2. Group members verify and sign
  3. BAC produced with 2f+1 signatures
  4. Masternodes verify BAC and finalize

Recovery Mechanisms

Election Recovery

MechanismTriggerAction
Watchdog timerNo block for 60sRe-trigger PoU + election
Progressive relaxation3+ failuresLower quorum to 2
Proposer rotation50 blocksStep down, re-elect
Immediate re-electionGroup re-initSpawn PoU + election task

Peer Recovery

MechanismTriggerAction
Gossipsub keepalive60s intervalPoU score publish
Mesh recovery watchdogNo mesh for 60sRe-dial group peers
SlowPeer disconnectQueue saturatedDisconnect and reconnect
Nonce resetLocal > storage + 200Reset to storage nonce

Group Recovery

When a group is re-initialized:

  1. PoU scores retained for continuing members
  2. New members start with default scores
  3. intra_group_started flag reset
  4. Immediate PoU + election spawned (don't wait for periodic timers)

Slashing (Production)

OffensePenaltyCooldown
Double vote50% stakeN/A
Downtime10% stakeGradual
Invalid proposal25% stakeN/A

Slashing is disabled on devnet, enabled on mainnet (config/production.toml).

Consensus Configuration

ParameterDevelopmentProduction
Timeout15s10s
Max rounds510
Quorum threshold0.670.67
BFT optimizationsDisabledEnabled
SlashingDisabledEnabled
Block time2s5s
Group size37