Skip to main content

Overview

The Fenines consensus layer implements a Hybrid Proof-of-Authority (PoA) mechanism named Fenine, which combines deterministic block production with smart contract-governed validator selection. This architecture decouples consensus validation from validator governance, enabling dynamic validator sets without protocol-level modifications.
Consensus Type: Proof-of-Authority (PoA)
Signature Algorithm: ECDSA (secp256k1)
Block Time: 3 seconds (Tblock=3sT_{\text{block}} = 3s)
Epoch Length: 200 blocks (Eperiod=200E_{\text{period}} = 200)

Consensus Engine Architecture

Core Components

The Fenine consensus engine consists of four primary modules:

Mathematical Foundation

1. In-Turn Block Production

Validators produce blocks in a deterministic round-robin schedule defined by: Signer(Bn)=V[nmodV]\text{Signer}(B_n) = \mathcal{V}\left[n \bmod |\mathcal{V}|\right] where:
  • BnB_n = Block at height nn
  • V\mathcal{V} = Ordered validator set from FenineSystem contract
  • V|\mathcal{V}| = Cardinality of active validators (1V1011 \leq |\mathcal{V}| \leq 101)

2. Difficulty Assignment

Block difficulty encodes the in-turn status: Difficulty(Bn)={2if Signer(Bn)=Expected(Bn)1otherwise (out-of-turn)\text{Difficulty}(B_n) = \begin{cases} 2 & \text{if } \text{Signer}(B_n) = \text{Expected}(B_n) \\ 1 & \text{otherwise (out-of-turn)} \end{cases}
Difficulty serves as a tie-breaker during chain reorganizations, not a computational puzzle. Fenine uses signature verification, not hash-based mining.

3. Signature Verification

Each block header contains an ECDSA signature in the extraData field: ExtraData=Vanity32Signature65\text{ExtraData} = \text{Vanity}_{32} \| \text{Signature}_{65} Signature verification ensures: Verify(σ,Hblock,PubKeysigner)={trueif σ=Sign(Hblock,PrivKeysigner)falseotherwise\text{Verify}(\sigma, H_{\text{block}}, \text{PubKey}_{\text{signer}}) = \begin{cases} \text{true} & \text{if } \sigma = \text{Sign}(H_{\text{block}}, \text{PrivKey}_{\text{signer}}) \\ \text{false} & \text{otherwise} \end{cases} where:
  • σ\sigma = 65-byte ECDSA signature (R, S, V)
  • HblockH_{\text{block}} = Keccak256 hash of block header (excluding signature)

Block Production Protocol

Timing Model

Block production follows strict timing constraints to prevent network partitioning: Tseal=Tparent+Delay(position)T_{\text{seal}} = T_{\text{parent}} + \text{Delay}(\text{position}) where: Delay(position)={Tblockif in-turnTblock+Tblock2if out-of-turn\text{Delay}(\text{position}) = \begin{cases} T_{\text{block}} & \text{if in-turn} \\ T_{\text{block}} + \frac{T_{\text{block}}}{2} & \text{if out-of-turn} \end{cases}

Seal Algorithm

1

Header Preparation

header.Difficulty = DIFF_INTURN  // or DIFF_NOTURN
header.Coinbase = validatorAddress
header.Extra = vanity[32] + placeholder[65]
2

Hash Computation

Compute block hash without signature:Hunsigned=Keccak256(RLP(header[:97]))H_{\text{unsigned}} = \text{Keccak256}(\text{RLP}(\text{header}[:97]))
3

Signature Generation

Sign the hash using validator’s private key:
signature = Sign(hash, privateKey)
// signature = [R(32) || S(32) || V(1)]
4

Signature Embedding

Replace placeholder with actual signature:
header.Extra[32:97] = signature

Anti-Split-Brain Protection

To prevent simultaneous node startup from creating chain splits, Fenine implements a random delay: ΔTstartupU(0,Tblock)\Delta T_{\text{startup}} \sim \mathcal{U}(0, T_{\text{block}}) This ensures statistical convergence to a single canonical chain within: Tconvergence=O(logn)T_{\text{convergence}} = \mathcal{O}(\log n) blocks, where n=Vn = |\mathcal{V}|.

Snapshot System

Snapshot Data Structure

Snapshots cache validator sets to avoid repeated contract reads:
type Snapshot struct {
    Number  uint64                      // Block height
    Hash    common.Hash                 // Block hash
    Signers map[Address]struct{}        // Authorized validators
    Recents map[uint64]Address          // Recent signers (spam protection)
}

Snapshot Evolution

Snapshots evolve through the chain via the apply() function: Sn+1=apply(Sn,Bn+1)\mathcal{S}_{n+1} = \text{apply}(\mathcal{S}_n, B_{n+1}) where: apply(S,B)={loadFromContract(B)if BmodEperiod=0SSigner(B)otherwise\text{apply}(\mathcal{S}, B) = \begin{cases} \text{loadFromContract}(B) & \text{if } B \bmod E_{\text{period}} = 0 \\ \mathcal{S} \cup \\{\text{Signer}(B)\\} & \text{otherwise} \end{cases}

Snapshot Persistence

Snapshots are persisted to disk at checkpoint intervals to enable fast sync: Checkpoint(n)    nmod1024=0\text{Checkpoint}(n) \iff n \bmod 1024 = 0 Database key schema:
Key:   "fenine-" + BlockHash
Value: JSON(Snapshot)

LRU Caching Strategy

In-memory caching follows a dual-LRU architecture:
CacheCapacityPurpose
recents128 snapshotsRecent validator sets
signatures4096 entriesBlock signer addresses
Cache hit rate analysis: Phit=1(VCcapacity)kP_{\text{hit}} = 1 - \left(\frac{|\mathcal{V}|}{C_{\text{capacity}}}\right)^k For V=101|\mathcal{V}| = 101 and C=128C = 128, hit rate exceeds 99% for k>2k > 2 lookbacks.

System Transaction Mechanism

Epoch Boundary Protocol

At every epoch block (nmod200=0n \bmod 200 = 0), three system transactions execute atomically:
1

updateValidatorCandidates()

Purpose: Refresh active validator set from contract stateFunction Selector: 0x3c1cc290Effect: Synchronizes Vconsensus\mathcal{V}_{\text{consensus}} with Vcontract\mathcal{V}_{\text{contract}}Vn+1FenineSystem.activeValidatorSet\mathcal{V}_{n+1} \leftarrow \text{FenineSystem.activeValidatorSet}
2

distributeBlockReward(uint256)

Purpose: Distribute epoch rewards to validatorsFunction Selector: 0x9a2e5597Reward Calculation:Ωepoch=Rblock×Eperiod=1×200=200 FEN\Omega_{\text{epoch}} = R_{\text{block}} \times E_{\text{period}} = 1 \times 200 = 200 \text{ FEN}Distribution:distribute(Ω)={splitAmongValidators(Ω,V)if V>0sendToTreasury(Ω)if V=0\text{distribute}(\Omega) = \begin{cases} \text{splitAmongValidators}(\Omega, \mathcal{V}) & \text{if } |\mathcal{V}| > 0 \\ \text{sendToTreasury}(\Omega) & \text{if } |\mathcal{V}| = 0 \end{cases}
3

syncRewardState()

Purpose: Activate pending reward changesFunction Selector: 0x4d73a62aState Transition:RactiveRpending at epoch EactivationR_{\text{active}} \leftarrow R_{\text{pending}} \text{ at epoch } E_{\text{activation}}

System Transaction Properties

System transactions have special characteristics:
PropertyValueRationale
Gas Fee CapbaseFeeMinimum EIP-1559 requirement
Gas Tip Cap0No priority fee
Senderblock.coinbaseValidator EOA
Gas CheckExemptedBalance may be zero
NonceAuto-incrementSequential ordering
Validator EOAs should maintain zero balance to prevent accidental user transactions. System TXs are exempt from balance checks.

Header Verification Rules

Comprehensive Verification

The verifyHeader() function enforces 12 consensus rules:
if header.UncleHash != uncleHash {
    return ErrInvalidUncleHash
}
Huncle=?Keccak256(RLP([]))H_{\text{uncle}} \stackrel{?}{=} \text{Keccak256}(\text{RLP}([]))
signer := ecrecover(header, signatures)
if signer != header.Coinbase {
    return ErrInvalidCoinbase
}
Coinbase=?EcRecover(σ,Hblock)\text{Coinbase} \stackrel{?}{=} \text{EcRecover}(\sigma, H_{\text{block}})
if header.Time <= parent.Time {
    return ErrInvalidTimestamp
}
Tn>Tn1T_n > T_{n-1}
if header.Time < parent.Time + period {
    return ErrInvalidTimestamp
}
TnTn1+TblockT_n \geq T_{n-1} + T_{\text{block}}
if header.Nonce != nonceAuthVote {
    return ErrInvalidNonce
}
Nonce must be 0x0000000000000000 (no voting in Fenine).
if len(header.Extra) < extraVanity+extraSeal {
    return ErrInvalidExtraData
}
ExtraData32+65=97 bytes|\text{ExtraData}| \geq 32 + 65 = 97 \text{ bytes}
if header.MixDigest != (common.Hash{}) {
    return ErrInvalidMixDigest
}
Must be zero (no PoW mining).
expected := CalcDifficulty(chain, header.Time, parent)
if header.Difficulty.Cmp(expected) != 0 {
    return ErrWrongDifficulty
}
Dn={2if in-turn1if out-of-turnD_n = \begin{cases} 2 & \text{if in-turn} \\ 1 & \text{if out-of-turn} \end{cases}
if err := ecrecover(header, signatures); err != nil {
    return ErrInvalidSignature
}
ECDSA signature must be valid.
snap := getSnapshot(parent)
if _, ok := snap.Signers[signer]; !ok {
    return ErrUnauthorizedSigner
}
Signer(Bn)Vn\text{Signer}(B_n) \in \mathcal{V}_n
for seen, recent := range snap.Recents {
    if recent == signer {
        limit := (len(snap.Signers)/2 + 1)
        if number-seen < uint64(limit) {
            return ErrRecentlySigned
        }
    }
}
Prevents spam by enforcing minimum gap:BcurrentBlast_signedV2+1B_{\text{current}} - B_{\text{last\_signed}} \geq \left\lfloor \frac{|\mathcal{V}|}{2} \right\rfloor + 1
diff := int64(parent.GasLimit) - int64(header.GasLimit)
if diff < 0 {
    diff *= -1
}
if uint64(diff) >= parent.GasLimit/1024 {
    return ErrInvalidGasLimit
}
Gas limit may only change by ±1/1024 per block:GnGn1<Gn11024\left| G_{n} - G_{n-1} \right| < \frac{G_{n-1}}{1024}

Block Difficulty Calculation

CalcDifficulty Algorithm

func (r *Fenine) CalcDifficulty(chain ChainHeaderReader, 
                                 time uint64, 
                                 parent *types.Header) *big.Int {
    snap := r.snapshot(chain, parent.Number.Uint64(), parent.Hash(), nil)
    return calcDifficulty(snap, r.signer)
}

In-Turn Status Determination

InTurn(n,s)    V[nmodV]=s\text{InTurn}(n, s) \iff \mathcal{V}\left[n \bmod |\mathcal{V}|\right] = s where:
  • nn = Block number
  • ss = Signer address
  • V\mathcal{V} = Sorted validator set (lexicographic order)

Lexicographic Ordering

Validators are sorted by address to ensure determinism:
func (s *Snapshot) inturn(number uint64, signer common.Address) bool {
    signers := s.signers()
    offset := number % uint64(len(signers))
    return signers[offset] == signer
}

func (s *Snapshot) signers() []common.Address {
    signers := make([]common.Address, 0, len(s.Signers))
    for signer := range s.Signers {
        signers = append(signers, signer)
    }
    sort.Slice(signers, func(i, j int) bool {
        return bytes.Compare(signers[i][:], signers[j][:]) < 0
    })
    return signers
}

Finality Semantics

Probabilistic Finality

Fenine provides probabilistic finality based on confirmation depth: Pfinality(k)=1(12)kP_{\text{finality}}(k) = 1 - \left(\frac{1}{2}\right)^k where kk is the number of confirmations.
ConfirmationsFinality ProbabilityTime (3s blocks)
150%3s
275%6s
387.5%9s
698.4%18s
1299.98%36s
Recommended Confirmations:
  • Small transactions: 3 blocks (9s)
  • Medium value: 6 blocks (18s)
  • High value: 12 blocks (36s)

Economic Finality

For transactions exceeding VtxV_{\text{tx}}, required confirmations: ksafe=log2(VtxVstake)k_{\text{safe}} = \left\lceil \log_2\left(\frac{V_{\text{tx}}}{V_{\text{stake}}}\right) \right\rceil where VstakeV_{\text{stake}} is the minimum validator stake (10,000 FEN).

Chain Reorganization Handling

Fork Choice Rule

During competing chains, selection follows: Chaincanonical=argmaxCleftBCD(B),  Cright\text{Chain}_{\text{canonical}} = \arg\max_{\mathcal{C}} \\left\\{ \sum_{B \in \mathcal{C}} D(B), \; |\mathcal{C}| \\right\\} where:
  1. Prefer chain with higher cumulative difficulty
  2. Tie-break by chain length
  3. Tie-break by lexicographically smallest block hash

Reorg Protection

Maximum reorganization depth is bounded by: Reorgmax=min(128,Eperiod)\text{Reorg}_{\text{max}} = \min(128, E_{\text{period}}) Beyond 128 blocks, snapshots are checkpointed and considered immutable.

Performance Metrics

Block Production Latency

End-to-end block production time decomposition: Ttotal=Texec+Tseal+TpropagateT_{\text{total}} = T_{\text{exec}} + T_{\text{seal}} + T_{\text{propagate}} Typical values:
ComponentTimePercentage
Transaction Execution50-500ms1.7-16.7%
Block Sealing1-5ms0.03-0.17%
Network Propagation100-300ms3.3-10%
Consensus Overhead~2-2.5s66-83%

Throughput Characteristics

Theoretical limits: TPStransfer=30,000,00021,000×3476 TPSTPSERC20=30,000,00065,000×3154 TPSTPSswap=30,000,000150,000×367 TPS\begin{align*} \text{TPS}_{\text{transfer}} &= \frac{30,000,000}{21,000 \times 3} \approx 476 \text{ TPS} \\ \text{TPS}_{\text{ERC20}} &= \frac{30,000,000}{65,000 \times 3} \approx 154 \text{ TPS} \\ \text{TPS}_{\text{swap}} &= \frac{30,000,000}{150,000 \times 3} \approx 67 \text{ TPS} \end{align*}

Consensus Safety Guarantees

Byzantine Fault Tolerance

Safety threshold: f<n2f < \frac{n}{2} where:
  • ff = Number of Byzantine validators
  • nn = Total active validators

Liveness Guarantee

Chain progresses if: Vhonest1|\mathcal{V}_{\text{honest}}| \geq 1 Even a single honest validator ensures liveness (though centralization risk).

Censorship Resistance

Transaction inclusion probability after kk blocks: Pinclusion(k)=1(fn)kP_{\text{inclusion}}(k) = 1 - \left(\frac{f}{n}\right)^k For f/n=0.3f/n = 0.3 (30% adversarial):
  • k=1k=1: 70% chance
  • k=3k=3: 97.3% chance
  • k=5k=5: 99.76% chance

System Contracts

FenineSystem contract specification

FPoS Economics

Validator selection and rewards

Security Model

Attack vectors and mitigations

Network Parameters

Configurable consensus constants