Files
apxtri/documents/08-CONSENSUS_DESIGN.md
T
2026-05-18 12:13:27 +02:00

19 KiB
Executable File

Blockchain Consensus Design - Proof of Uptime with Game Theory

Current State Analysis

How It Works Now (Line 199-201 in Blockchain.js)

static start() {
  log("Blockchain process started. New block will be proposed every 10 minutes.");
  setInterval(this.createBlock.bind(this), 10 * 60 * 1000);  // Every 10 minutes
}

Current Flow:

  1. Every town independently creates blocks every 10 minutes
  2. Each town broadcasts its block to peers
  3. Problem: No consensus, no merge, each town has different blocks!
  4. Result: Network diverges, no single truth

Issues:

  • ❌ Multiple conflicting blocks every 10 minutes
  • ❌ No leader election
  • ❌ No block validation voting
  • ❌ No chain merge mechanism
  • ❌ Network splits into N separate chains

Proposed Solution: Proof of Uptime + Rotating Leader + Byzantine Fault Tolerance

Core Principles

  1. Proof of Uptime (PoU): Towns earn validation rights by staying online
  2. Rotating Leader: One validator per round, selected by uptime score
  3. Byzantine Fault Tolerance: Tolerates up to 33% malicious nodes
  4. Economic Incentives: Aligned with human behavior (game theory)
  5. Sybil Resistance: High cost to create fake towns

System Architecture

1. Uptime Tracking

Each town tracks its uptime score based on:

  • Availability: % time online and reachable
  • Block participation: % of rounds where town voted
  • Data quality: % of valid transactions submitted
// Uptime Score Calculation
uptimeScore = {
  availability: 0.0 - 1.0,      // 0% to 100% uptime in last 7 days
  participation: 0.0 - 1.0,     // % of consensus rounds participated
  reputation: 0.0 - 1.0,        // % of valid blocks/votes submitted
  
  totalScore: (availability * 0.5) + (participation * 0.3) + (reputation * 0.2)
}

Data Structure:

{
  "townId": "farm",
  "uptimeMetrics": {
    "lastSeen": 1705750800000,
    "totalUptime": 604800000,        // 7 days in ms
    "availabilityScore": 0.98,
    "participationCount": 985,
    "totalRounds": 1000,
    "participationScore": 0.985,
    "validBlocksSubmitted": 50,
    "totalBlocksSubmitted": 52,
    "reputationScore": 0.96,
    "totalScore": 0.973
  }
}

2. Leader Selection (Weighted Random)

Algorithm: Weighted Random Selection

// Select leader based on uptime scores
function selectLeader(towns, roundNumber) {
  // Filter active towns (seen in last 15 minutes)
  const activeTowns = towns.filter(t => 
    Date.now() - t.uptimeMetrics.lastSeen < 15 * 60 * 1000
  );
  
  // Calculate total weight
  const totalWeight = activeTowns.reduce((sum, t) => 
    sum + t.uptimeMetrics.totalScore, 0
  );
  
  // Use round number as seed for deterministic selection
  // All towns calculate the same leader!
  const seed = roundNumber;
  const randomValue = seededRandom(seed) * totalWeight;
  
  let cumulativeWeight = 0;
  for (const town of activeTowns) {
    cumulativeWeight += town.uptimeMetrics.totalScore;
    if (randomValue <= cumulativeWeight) {
      return town.townId;
    }
  }
}

// Deterministic random (same seed = same result on all nodes)
function seededRandom(seed) {
  const x = Math.sin(seed) * 10000;
  return x - Math.floor(x);
}

Why This Works:

  • ✅ Deterministic: All towns calculate same leader
  • ✅ Fair: Higher uptime = higher chance to lead
  • ✅ Sybil resistant: New towns have 0 uptime, can't dominate
  • ✅ No communication: Pure math, no coordination needed

3. Consensus Round (10-Minute Cycle)

Phase 1: Transaction Collection (0:00 - 8:00)

  • All towns collect transactions locally
  • Transactions added to pending pool

Phase 2: Block Proposal (8:00 - 8:30)

  • Leader (selected by uptime) creates block proposal
  • Leader aggregates its own transactions
  • Leader broadcasts proposal to all peers

Phase 3: Validation Vote (8:30 - 9:30)

  • All towns verify block proposal:
    • Check transactions are valid
    • Check signatures are correct
    • Check no double-spends
  • Each town votes: APPROVE or REJECT
  • Vote includes their signature

Phase 4: Finalization (9:30 - 10:00)

  • Leader counts votes
  • If >66% APPROVE: Block is finalized
  • Leader broadcasts final block with votes
  • All towns apply transactions and update blockchain
Timeline (10 minutes):
|-------- 8 min --------|--30s--|--60s--|--30s--|
[Tx Collection]         [Proposal][Vote][Final]
                         Leader   All    Leader
                         creates  vote   counts

Implementation

Phase 1: Uptime Heartbeat System

class UptimeTracker {
  /**
   * Each town broadcasts heartbeat every 2 minutes
   */
  static async sendHeartbeat() {
    const heartbeat = {
      townId: process.env.TOWN,
      timestamp: Date.now(),
      blockHeight: blockchain.length,
      pendingTxCount: pendingTransactions.length,
      signature: await this._sign({ townId: process.env.TOWN, timestamp: Date.now() })
    };
    
    await Blockchain._broadcast('/heartbeat', heartbeat);
  }
  
  /**
   * Receive and store peer heartbeats
   */
  static async receiveHeartbeat(heartbeat) {
    if (!await this._verifyHeartbeat(heartbeat)) return;
    
    const townFile = path.join(DATA_PATH, 'apxtri/blockchain/uptime', `${heartbeat.townId}.json`);
    
    let uptimeData = await fs.readJson(townFile).catch(() => ({
      townId: heartbeat.townId,
      firstSeen: Date.now(),
      heartbeats: []
    }));
    
    uptimeData.heartbeats.push({
      timestamp: heartbeat.timestamp,
      blockHeight: heartbeat.blockHeight
    });
    
    // Keep last 5000 heartbeats (~7 days at 2-minute intervals)
    if (uptimeData.heartbeats.length > 5000) {
      uptimeData.heartbeats = uptimeData.heartbeats.slice(-5000);
    }
    
    // Calculate metrics
    uptimeData.metrics = this._calculateUptimeMetrics(uptimeData.heartbeats);
    
    await fs.outputJson(townFile, uptimeData);
  }
  
  /**
   * Calculate uptime metrics from heartbeat history
   */
  static _calculateUptimeMetrics(heartbeats) {
    const now = Date.now();
    const sevenDays = 7 * 24 * 60 * 60 * 1000;
    const recentHeartbeats = heartbeats.filter(h => now - h.timestamp < sevenDays);
    
    // Expected: 1 heartbeat every 2 minutes = 5040 in 7 days
    const expectedHeartbeats = sevenDays / (2 * 60 * 1000);
    const availabilityScore = Math.min(recentHeartbeats.length / expectedHeartbeats, 1.0);
    
    return {
      lastSeen: heartbeats[heartbeats.length - 1]?.timestamp || 0,
      totalHeartbeats: recentHeartbeats.length,
      availabilityScore,
      totalScore: availabilityScore  // Can add participation and reputation later
    };
  }
  
  static start() {
    // Send heartbeat every 2 minutes
    setInterval(this.sendHeartbeat.bind(this), 2 * 60 * 1000);
    
    // Clean old heartbeats daily
    setInterval(this._cleanOldHeartbeats.bind(this), 24 * 60 * 60 * 1000);
  }
}

Phase 2: Leader-Based Consensus

class Consensus {
  /**
   * Main consensus round (runs every 10 minutes)
   */
  static async runConsensusRound() {
    const roundNumber = Math.floor(Date.now() / (10 * 60 * 1000));
    const currentTown = process.env.TOWN;
    
    log(`[Consensus] Starting round #${roundNumber}`);
    
    // 1. Determine leader for this round
    const allTowns = await this._getAllTownsWithUptime();
    const leader = this._selectLeader(allTowns, roundNumber);
    
    log(`[Consensus] Leader for round #${roundNumber}: ${leader}`);
    
    if (leader === currentTown) {
      // I am the leader
      await this._leaderProposeBlock(roundNumber);
    } else {
      // I am a validator
      await this._validatorWaitForProposal(roundNumber, leader);
    }
  }
  
  /**
   * Leader creates and proposes block
   */
  static async _leaderProposeBlock(roundNumber) {
    log(`[Consensus] I am the leader. Creating block proposal...`);
    
    if (pendingTransactions.length === 0) {
      log(`[Consensus] No transactions. Skipping round.`);
      return;
    }
    
    // Collect transactions from all towns (optional: request from peers)
    const block = {
      index: blockchain.length,
      roundNumber,
      timestamp: Date.now(),
      transactions: [...pendingTransactions],
      previousHash: blockchain.length > 0 ? blockchain[blockchain.length - 1].hash : "0".repeat(64),
      proposer: process.env.TOWN,
      votes: []
    };
    
    block.hash = Blockchain._calculateHash(block);
    block.signature = await Blockchain._sign(block);
    
    // Broadcast proposal
    log(`[Consensus] Broadcasting block proposal #${block.index}`);
    await Blockchain._broadcast('/propose', block);
    
    // Wait 60 seconds for votes
    await new Promise(resolve => setTimeout(resolve, 60 * 1000));
    
    // Count votes
    const votes = await this._collectVotes(block.hash);
    const totalVotes = votes.length;
    const approveVotes = votes.filter(v => v.vote === 'approve').length;
    const approvalRate = approveVotes / totalVotes;
    
    log(`[Consensus] Votes: ${approveVotes}/${totalVotes} (${(approvalRate * 100).toFixed(1)}%)`);
    
    // Require 66% approval (Byzantine fault tolerance)
    if (approvalRate >= 0.66) {
      block.votes = votes;
      blockchain.push(block);
      pendingTransactions = [];
      
      // Generate APX3 reward for leader
      const APX3 = require('./APX3');
      await APX3.generateBlockReward(block, process.env.TOWN_MAYOR || process.env.TOWN);
      
      log(`[Consensus] ✅ Block #${block.index} FINALIZED with ${approveVotes} votes`);
      
      // Broadcast final block
      await Blockchain._broadcast('/finalize', block);
    } else {
      log(`[Consensus] ❌ Block REJECTED. Not enough votes (${approvalRate * 100}% < 66%)`);
    }
  }
  
  /**
   * Validator receives proposal and votes
   */
  static async _validatorWaitForProposal(roundNumber, leader) {
    log(`[Consensus] Waiting for proposal from leader ${leader}...`);
    
    // Wait up to 90 seconds for proposal
    const startTime = Date.now();
    while (Date.now() - startTime < 90 * 1000) {
      const proposal = await this._checkForProposal(roundNumber);
      
      if (proposal) {
        log(`[Consensus] Received proposal. Validating...`);
        const isValid = await this._validateProposal(proposal);
        
        const vote = {
          roundNumber,
          blockHash: proposal.hash,
          voter: process.env.TOWN,
          vote: isValid ? 'approve' : 'reject',
          timestamp: Date.now(),
          signature: await Blockchain._sign({
            roundNumber,
            blockHash: proposal.hash,
            vote: isValid ? 'approve' : 'reject'
          })
        };
        
        log(`[Consensus] Voting: ${vote.vote.toUpperCase()}`);
        
        // Send vote to leader
        const leaderTown = await this._getTownByID(leader);
        const voteUrl = `http://${leaderTown.dns[0]}/apxtri/blockchain/vote`;
        await axios.post(voteUrl, vote).catch(err => 
          log(`Failed to send vote to leader: ${err.message}`)
        );
        
        // Wait for finalization
        await this._waitForFinalization(roundNumber);
        return;
      }
      
      await new Promise(resolve => setTimeout(resolve, 1000));
    }
    
    log(`[Consensus] Timeout waiting for proposal from ${leader}`);
  }
  
  /**
   * Validate block proposal
   */
  static async _validateProposal(proposal) {
    try {
      // 1. Verify signature
      if (!await Blockchain._verifySignature(proposal)) {
        log(`[Consensus] Invalid signature`);
        return false;
      }
      
      // 2. Check transactions are valid
      for (const tx of proposal.transactions) {
        // Check transaction format
        if (!tx.id || !tx.type || !tx.model) {
          log(`[Consensus] Invalid transaction format`);
          return false;
        }
        
        // For APX3 transactions, verify balance
        if (tx.model === 'apx3') {
          const APX3 = require('./APX3');
          const balance = await APX3.getBalance(tx.from);
          if (balance < tx.amount) {
            log(`[Consensus] Insufficient balance for APX3 tx`);
            return false;
          }
        }
      }
      
      // 3. Check no duplicate transactions
      const txIds = new Set();
      for (const tx of proposal.transactions) {
        if (txIds.has(tx.id)) {
          log(`[Consensus] Duplicate transaction detected`);
          return false;
        }
        txIds.add(tx.id);
      }
      
      return true;
    } catch (error) {
      log(`[Consensus] Validation error: ${error.message}`);
      return false;
    }
  }
  
  /**
   * Select leader using weighted random based on uptime
   */
  static _selectLeader(towns, roundNumber) {
    const activeTowns = towns.filter(t => 
      Date.now() - t.uptimeMetrics.lastSeen < 15 * 60 * 1000 &&
      t.uptimeMetrics.totalScore > 0.5  // Minimum 50% uptime to lead
    );
    
    if (activeTowns.length === 0) {
      return process.env.TOWN;  // Fallback
    }
    
    const totalWeight = activeTowns.reduce((sum, t) => 
      sum + t.uptimeMetrics.totalScore, 0
    );
    
    // Deterministic selection
    const seed = roundNumber + 12345;  // Add constant to avoid predictability
    const x = Math.sin(seed) * 10000;
    const randomValue = (x - Math.floor(x)) * totalWeight;
    
    let cumulativeWeight = 0;
    for (const town of activeTowns) {
      cumulativeWeight += town.uptimeMetrics.totalScore;
      if (randomValue <= cumulativeWeight) {
        return town.townId;
      }
    }
    
    return activeTowns[0].townId;  // Fallback
  }
  
  static start() {
    // Start consensus rounds every 10 minutes
    // Offset by 30 seconds to allow transaction collection
    setTimeout(() => {
      this.runConsensusRound();
      setInterval(this.runConsensusRound.bind(this), 10 * 60 * 1000);
    }, 30 * 1000);
  }
}

Game Theory Analysis

Incentive Structure

Honest Behavior Rewards

  1. Block Rewards: 10 APX3 per validated block (only if >66% approve)
  2. Reputation Score: Higher chance to be selected as future leader
  3. Transaction Fees: Collect fees from all transactions in block
  4. Network Trust: Better uptime = more influence

Malicious Behavior Costs

  1. Stake at Risk: Towns must register with DNS (provable cost)
  2. Reputation Loss: Bad blocks reduce uptime score
  3. Block Rejection: Lose 10 minutes of potential earnings
  4. Network Exclusion: Consistent bad behavior = ignored by peers
  5. Economic Loss: Wasted electricity, server costs with no reward

Attack Scenarios & Defenses

Attack 1: Double-Spend Attack

Scenario: Malicious leader includes double-spend transaction

Defense:

  • ✅ Validators check transaction validity
  • ✅ >66% reject block
  • ✅ Leader gets 0 reward
  • ✅ Leader's reputation drops

Game Theory: Cost (lost reward + reputation) > Benefit (failed double-spend)

Attack 2: Sybil Attack (Fake Towns)

Scenario: Attacker creates 100 fake towns to dominate voting

Defense:

  • ✅ Each town needs registered DNS (cost: $10-50/domain)
  • ✅ New towns have 0 uptime (0% chance to lead)
  • ✅ Takes 7 days to build uptime score
  • ✅ Heartbeat verification with signatures

Game Theory: Cost ($1000+ for 100 domains + 7 days) > Benefit (might not work)

Attack 3: Censorship (Leader Ignores Transactions)

Scenario: Malicious leader excludes specific users' transactions

Defense:

  • ✅ Validators can see missing transactions (compare with their pool)
  • ✅ Can reject block if suspicious
  • ✅ Next round, different leader includes them
  • ✅ Max delay: 10 minutes

Game Theory: Cost (block rejection + reputation) > Benefit (temporary delay)

Attack 4: DDoS on Leader

Scenario: Attacker floods leader to prevent block creation

Defense:

  • ✅ If no proposal in 90 seconds, round fails gracefully
  • ✅ Next round, different leader (rotating)
  • ✅ Leader changes every 10 minutes
  • ✅ DDoS protection at Caddy level

Game Theory: Cost (DDoS service) > Benefit (delay 1 round)

Attack 5: 34% Collusion (Byzantine Attack)

Scenario: 34% of towns collude to approve bad blocks

Defense:

  • ✅ Requires >66% approval (Byzantine fault tolerance)
  • ✅ Need to compromise 67% of network
  • ✅ High uptime towns harder to compromise (established reputation)
  • ✅ Economic cost very high

Game Theory:

  • To control 67% of voting power: ~67 high-uptime towns
  • Cost: $670 domains + 7 days + server costs
  • Benefit: Can manipulate blockchain
  • Defense: Social layer - community notices suspicious pattern

Optimal Strategy (Nash Equilibrium)

For Honest Town:

  1. Maximize uptime → Higher leader selection chance
  2. Validate honestly → Build reputation
  3. Vote correctly → Block gets approved, earn fees
  4. Participate consistently → Compound reputation

For Malicious Town:

  1. Create bad block → 0 reward (rejected)
  2. Vote dishonestly → Block fails anyway (minority)
  3. Stay offline → 0 uptime, never selected
  4. Conclusion: Malicious behavior is not profitable!

Nash Equilibrium: All towns have incentive to behave honestly.

Comparison with Alternatives

Mechanism Pros Cons Suitability
Proof of Work (PoW) Battle-tested, secure Wasteful energy, slow ❌ Overkill for apxtri
Proof of Stake (PoS) Energy efficient Rich get richer, complex ⚠️ Requires staking mechanism
Proof of Authority (PoA) Fast, efficient Centralized, trust-based ❌ Against decentralization
Proof of Uptime (PoU) Meritocratic, efficient New mechanism ✅ Perfect for apxtri!

Migration Path

Phase 1: Heartbeat System (Week 1)

  • Implement uptime tracking
  • All towns start broadcasting heartbeats
  • Build 7 days of uptime data

Phase 2: Leader Selection (Week 2)

  • Implement deterministic leader selection
  • Test leader rotation logic
  • Verify all towns calculate same leader

Phase 3: Consensus Voting (Week 3)

  • Implement proposal/vote/finalize flow
  • Test with small network (3-5 towns)
  • Verify Byzantine fault tolerance

Phase 4: Full Deployment (Week 4)

  • Deploy to production network
  • Monitor for 2 weeks
  • Tune parameters if needed

Configuration

Environment Variables (.env)

# Consensus Settings
CONSENSUS_ENABLED=true
BLOCK_TIME=600000                    # 10 minutes
HEARTBEAT_INTERVAL=120000            # 2 minutes
MIN_UPTIME_TO_LEAD=0.5               # 50% minimum
VOTE_THRESHOLD=0.66                  # 66% approval needed
PROPOSAL_TIMEOUT=90000               # 90 seconds
VOTE_COLLECTION_TIME=60000           # 60 seconds

Summary

Proof of Uptime + Rotating Leader is ideal for apxtri because:

✅ Merit-Based: Towns that stay online get rewarded ✅ Sybil Resistant: New towns can't instantly dominate ✅ Energy Efficient: No wasteful mining ✅ Fast: 10-minute blocks with 90-second finalization ✅ Byzantine Tolerant: Works with 33% malicious nodes ✅ Economically Sound: Honest behavior is most profitable ✅ Decentralized: No single point of failure ✅ Human-Aligned: Rewards reliability and good citizenship

Game Theory Conclusion: The system creates a positive-sum game where cooperation benefits everyone more than defection. The optimal strategy is to be an honest, reliable validator.