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:
- Every town independently creates blocks every 10 minutes
- Each town broadcasts its block to peers
- Problem: No consensus, no merge, each town has different blocks!
- 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
- Proof of Uptime (PoU): Towns earn validation rights by staying online
- Rotating Leader: One validator per round, selected by uptime score
- Byzantine Fault Tolerance: Tolerates up to 33% malicious nodes
- Economic Incentives: Aligned with human behavior (game theory)
- 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
- Block Rewards: 10 APX3 per validated block (only if >66% approve)
- Reputation Score: Higher chance to be selected as future leader
- Transaction Fees: Collect fees from all transactions in block
- Network Trust: Better uptime = more influence
Malicious Behavior Costs
- Stake at Risk: Towns must register with DNS (provable cost)
- Reputation Loss: Bad blocks reduce uptime score
- Block Rejection: Lose 10 minutes of potential earnings
- Network Exclusion: Consistent bad behavior = ignored by peers
- 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:
- Maximize uptime → Higher leader selection chance
- Validate honestly → Build reputation
- Vote correctly → Block gets approved, earn fees
- Participate consistently → Compound reputation
For Malicious Town:
- Create bad block → 0 reward (rejected)
- Vote dishonestly → Block fails anyway (minority)
- Stay offline → 0 uptime, never selected
- 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.