# Technical Design: Proof-of-Authority Blockchain Architecture ## Architecture Overview ### Network Topology ``` Private Docker Network (172.25.0.0/16) │ ├─ bootnode:8546 (Peer Discovery) │ └─ POST /register_peer - Register validator │ └─ GET /discover - List active peers │ ├─ validator-1:30303 (PoA Validator + JSON-RPC) │ ├─ p2p_listen_addr: 30303 │ ├─ rpc_listen_addr: 8001 │ └─ Authorized signer: validator_1_private_key │ ├─ validator-2:30304 (PoA Validator + JSON-RPC) │ ├─ p2p_listen_addr: 30304 │ ├─ rpc_listen_addr: 8002 │ └─ Authorized signer: validator_2_private_key │ ├─ validator-3:30305 (PoA Validator + JSON-RPC) │ ├─ p2p_listen_addr: 30305 │ ├─ rpc_listen_addr: 8003 │ └─ Authorized signer: validator_3_private_key │ ├─ api-server:8000 (FastAPI) │ └─ Submits votes to validators via JSON-RPC │ ├─ mariadb:3306 (Database) │ └─ Stores voter data and election metadata │ └─ frontend:3000 (Next.js) └─ User interface ``` --- ## Component Details ### 1. Bootnode Service **Purpose**: Enable peer discovery and network bootstrap **Interface**: ```python # HTTP Interface GET /health Returns: {"status": "healthy"} POST /register_peer Request: { "node_id": "validator-1", "ip": "validator-1", "p2p_port": 30303, "rpc_port": 8001, "public_key": "0x..." } Returns: {"registered": true, "peers": [...]} GET /discover Query params: node_id=validator-1 Returns: { "peers": [ {"node_id": "validator-2", "ip": "validator-2", "p2p_port": 30304, ...}, {"node_id": "validator-3", "ip": "validator-3", "p2p_port": 30305, ...} ] } GET /peers Returns: {"peers": [...], "count": 3} ``` **Implementation** (`bootnode/bootnode.py`): - FastAPI service - In-memory peer registry (dict) - Register endpoint (POST /register_peer) - Discover endpoint (GET /discover) - Health check endpoint - Periodic cleanup of stale peers **Docker Service**: ```yaml bootnode: build: context: . dockerfile: docker/Dockerfile.bootnode container_name: evoting_bootnode ports: - "8546:8546" networks: - evoting_network environment: BOOTNODE_PORT: 8546 healthcheck: test: ["CMD", "curl", "-f", "http://localhost:8546/health"] ``` --- ### 2. Validator Node Service **Purpose**: Run PoA consensus and maintain blockchain state **Key Data Structures**: ```python # Genesis Block (hardcoded in each validator) genesis_block = { "index": 0, "prev_hash": "0x" + "0" * 64, "timestamp": 1699360000, "transactions": [], "block_hash": "0x...", "validator": "genesis", "signature": "genesis", "authorized_validators": [ "0x1234567890abcdef...", # validator-1 public key "0x0987654321fedcba...", # validator-2 public key "0xabcdefabcdefabcd...", # validator-3 public key ] } # Block Structure block = { "index": 1, "prev_hash": "0x...", # Hash of previous block "timestamp": 1699360010, "transactions": [ { "voter_id": "anon-tx-abc123", "election_id": 1, "encrypted_vote": "0x7f3a...", "ballot_hash": "0x9f2b...", "proof": "0xabcd...", "timestamp": 1699360001 } ], "block_hash": "0xdeadbeef...", "validator": "0x1234567...", # Address of validator who created block "signature": "0x5678..." # Signature of block_hash } # Pending Transaction Pool pending_transactions = [ # Votes waiting to be included in a block ] ``` **Consensus Algorithm** (PoA - Proof of Authority): ``` Round-robin block creation: 1. Validator #1 creates block 1, signs it 2. Validator #2 creates block 2, signs it 3. Validator #3 creates block 3, signs it 4. Validator #1 creates block 4, signs it ... (repeat) Block validation by other validators: 1. Receive block from peer 2. Verify block_hash is correct 3. Verify signature is from authorized validator 4. Verify block extends previous block (prev_hash matches) 5. Verify all transactions in block are valid 6. If valid, add to chain and broadcast to other peers 7. If invalid, reject and optionally report to watchdog ``` **JSON-RPC Interface** (for API Server communication): ```python # Standard Ethereum-like JSON-RPC methods # Send vote (transaction) POST /rpc { "jsonrpc": "2.0", "method": "eth_sendTransaction", "params": [{ "to": null, "data": "0x..." + base64(encrypted_vote_json) }], "id": 1 } Response: {"jsonrpc": "2.0", "result": "0x...", "id": 1} # result = transaction hash # Get transaction receipt POST /rpc { "jsonrpc": "2.0", "method": "eth_getTransactionReceipt", "params": ["0x..."], "id": 2 } Response: {"jsonrpc": "2.0", "result": {...}, "id": 2} # Get block number POST /rpc { "jsonrpc": "2.0", "method": "eth_blockNumber", "params": [], "id": 3 } Response: {"jsonrpc": "2.0", "result": "0x64", "id": 3} # Block 100 # Get block by number POST /rpc { "jsonrpc": "2.0", "method": "eth_getBlockByNumber", "params": ["0x64", true], "id": 4 } Response: {"jsonrpc": "2.0", "result": {...block...}, "id": 4} ``` **P2P Network Protocol** (Simple custom protocol): ```python # Message Types { "type": "peer_hello", "node_id": "validator-1", "genesis_hash": "0x...", "chain_length": 42, "best_hash": "0x..." } { "type": "sync_request", "from_index": 0, "to_index": 42 } { "type": "sync_response", "blocks": [{...}, {...}, ...] } { "type": "new_block", "block": {...} } { "type": "new_transaction", "transaction": {...} } ``` **Implementation** (`validator/validator.py`): ```python class PoAValidator: def __init__(self, node_id, private_key, bootnode_url): self.node_id = node_id self.private_key = private_key self.chain = [genesis_block] self.pending_transactions = [] self.peer_connections = {} self.bootnode_url = bootnode_url async def startup(self): # 1. Register with bootnode await self.register_with_bootnode() # 2. Discover other peers await self.discover_peers() # 3. Connect to peers await self.connect_to_peers() # 4. Sync blockchain await self.sync_blockchain() # 5. Start block creation task asyncio.create_task(self.block_creation_loop()) # 6. Start P2P listen asyncio.create_task(self.p2p_listen()) async def block_creation_loop(self): while True: if self.should_create_block(): block = self.create_block() self.add_block_to_chain(block) await self.broadcast_block(block) await asyncio.sleep(5) # Create block every 5 seconds def should_create_block(self): # Determine if this validator's turn to create block # Based on round-robin: block_index % num_validators next_index = len(self.chain) validator_index = get_validator_index(self.node_id) num_validators = 3 return next_index % num_validators == validator_index def create_block(self): # Take up to N pending transactions txs = self.pending_transactions[:32] self.pending_transactions = self.pending_transactions[32:] block = { "index": len(self.chain), "prev_hash": self.chain[-1]["block_hash"], "timestamp": time.time(), "transactions": txs, "validator": self.public_key, "block_hash": None, # Calculated below "signature": None # Calculated below } # Calculate block hash block_hash = sha256(json.dumps(block, sort_keys=True)) block["block_hash"] = block_hash # Sign block hash signature = sign(block_hash, self.private_key) block["signature"] = signature return block def validate_block(self, block): # Verify block is valid checks = [ self.verify_block_hash(block), self.verify_signature(block), self.verify_prev_hash(block), self.verify_transactions(block) ] return all(checks) async def handle_block(self, block): if self.validate_block(block): self.add_block_to_chain(block) await self.broadcast_block(block) # Gossip to peers else: logger.error(f"Invalid block: {block['index']}") async def handle_transaction(self, transaction): self.pending_transactions.append(transaction) await self.broadcast_transaction(transaction) ``` **Docker Service**: ```yaml validator-1: build: context: . dockerfile: docker/Dockerfile.validator container_name: evoting_validator_1 ports: - "8001:8001" # JSON-RPC port - "30303:30303" # P2P port environment: NODE_ID: validator-1 PRIVATE_KEY: ${VALIDATOR_1_PRIVATE_KEY} BOOTNODE_URL: http://bootnode:8546 RPC_PORT: 8001 P2P_PORT: 30303 networks: - evoting_network depends_on: - bootnode healthcheck: test: ["CMD", "curl", "-f", "http://localhost:8001/rpc"] ``` --- ### 3. API Server Integration **Changes to existing FastAPI backend**: ```python # In backend/main.py or new backend/blockchain_client.py class BlockchainClient: def __init__(self, validator_urls: List[str]): self.validator_urls = validator_urls self.current_validator_index = 0 async def submit_vote(self, encrypted_vote: bytes, ballot_hash: str) -> str: """ Submit vote to blockchain via validator JSON-RPC Returns: transaction hash """ # Format vote as transaction transaction = { "voter_id": f"anon-tx-{uuid.uuid4().hex[:12]}", "election_id": election_id, "encrypted_vote": encrypted_vote.hex(), "ballot_hash": ballot_hash, "proof": proof, "timestamp": int(time.time()) } # Send via JSON-RPC tx_hash = await self.send_transaction(transaction) return tx_hash async def send_transaction(self, transaction: dict) -> str: """Send transaction to blockchain""" payload = { "jsonrpc": "2.0", "method": "eth_sendTransaction", "params": [{ "data": "0x" + json.dumps(transaction).encode().hex() }], "id": 1 } response = await self._rpc_call(payload) return response["result"] async def get_transaction_receipt(self, tx_hash: str) -> dict: """Get confirmation that vote was included in block""" payload = { "jsonrpc": "2.0", "method": "eth_getTransactionReceipt", "params": [tx_hash], "id": 2 } response = await self._rpc_call(payload) return response["result"] async def _rpc_call(self, payload: dict) -> dict: """Make JSON-RPC call with failover to other validators""" for i in range(len(self.validator_urls)): validator_url = self.validator_urls[self.current_validator_index] self.current_validator_index = (self.current_validator_index + 1) % len(self.validator_urls) try: async with aiohttp.ClientSession() as session: async with session.post(f"{validator_url}/rpc", json=payload) as resp: if resp.status == 200: return await resp.json() except Exception as e: logger.error(f"Failed to reach {validator_url}: {e}") continue raise Exception("All validators unreachable") # Initialize blockchain client blockchain_client = BlockchainClient([ "http://validator-1:8001", "http://validator-2:8002", "http://validator-3:8003" ]) # In routes/votes.py @router.post("/api/votes/submit") async def submit_vote(vote_bulletin: VoteBulletin, current_voter: Voter = Depends(get_current_voter)): """ Submit encrypted vote to blockchain """ # ... existing validation ... # Submit to blockchain tx_hash = await blockchain_client.submit_vote( encrypted_vote=encrypted_vote_bytes, ballot_hash=ballot_hash ) # Optional: wait for confirmation # receipt = await blockchain_client.get_transaction_receipt(tx_hash) # Mark voter as voted services.VoterService.mark_as_voted(db, current_voter.id) return { "id": vote.id, "transaction_id": tx_hash, "ballot_hash": ballot_hash, "timestamp": vote.timestamp } ``` --- ## Database Schema Updates **New Tables** (optional - for vote indexing): ```sql -- Store vote transaction hashes for quick lookup CREATE TABLE vote_transactions ( id INT PRIMARY KEY AUTO_INCREMENT, voter_id INT NOT NULL, election_id INT NOT NULL, tx_hash VARCHAR(66) NOT NULL UNIQUE, -- Transaction hash on blockchain block_number INT, -- Block containing this vote ballot_hash VARCHAR(64) NOT NULL, submitted_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (voter_id) REFERENCES voter(id), FOREIGN KEY (election_id) REFERENCES election(id) ); -- Store validator list for audit trail CREATE TABLE validator_nodes ( id INT PRIMARY KEY AUTO_INCREMENT, node_id VARCHAR(64) NOT NULL UNIQUE, public_key VARCHAR(256) NOT NULL, endpoint VARCHAR(256) NOT NULL, active BOOLEAN DEFAULT TRUE, registered_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, last_heartbeat TIMESTAMP ); ``` --- ## Security Considerations ### Validator Key Management ``` # Private keys generated once during setup # Stored in environment variables validator-1: VALIDATOR_1_PRIVATE_KEY=0x... validator-2: VALIDATOR_2_PRIVATE_KEY=0x... validator-3: VALIDATOR_3_PRIVATE_KEY=0x... # In production: # - Store in HashiCorp Vault # - Use AWS KMS for key management # - Implement HSM (Hardware Security Module) support ``` ### Byzantine Fault Tolerance ``` With 3 validators: - Can tolerate 1 validator failure (f = 1) - Need 2/3 majority for consensus (2 validators) Formula: Can tolerate (n - 1) / 3 Byzantine nodes - 3 validators: tolerate 0 (actually 1 if majority rule) - 5 validators: tolerate 1 - 7 validators: tolerate 2 For true BFT, would need different consensus (PBFT) ``` ### Vote Confidentiality ``` Vote submission path: 1. Voter -> Frontend (HTTPS) 2. Frontend encrypts vote with ElGamal public key 3. Frontend submits encrypted vote to API Server (HTTPS) 4. API Server -> Validator (encrypted over HTTPS) 5. Validator broadcasts encrypted vote via P2P 6. Encrypted vote stored on blockchain 7. Only authorized party can decrypt with private key ``` --- ## Docker Compose Updates ```yaml version: '3.8' services: # Bootnode for peer discovery bootnode: build: context: . dockerfile: docker/Dockerfile.bootnode container_name: evoting_bootnode ports: - "8546:8546" networks: - evoting_network healthcheck: test: ["CMD", "curl", "-f", "http://localhost:8546/health"] depends_on: [] # Validators (PoA blockchain nodes) validator-1: build: context: . dockerfile: docker/Dockerfile.validator container_name: evoting_validator_1 ports: - "8001:8001" - "30303:30303" depends_on: - bootnode environment: NODE_ID: validator-1 PRIVATE_KEY: ${VALIDATOR_1_PRIVATE_KEY:-0x...} BOOTNODE_URL: http://bootnode:8546 networks: - evoting_network validator-2: # ... similar to validator-1 ports: - "8002:8002" - "30304:30304" environment: NODE_ID: validator-2 PRIVATE_KEY: ${VALIDATOR_2_PRIVATE_KEY:-0x...} validator-3: # ... similar to validator-1 ports: - "8003:8003" - "30305:30305" environment: NODE_ID: validator-3 PRIVATE_KEY: ${VALIDATOR_3_PRIVATE_KEY:-0x...} # API Server (existing backend with blockchain integration) api-server: build: context: . dockerfile: docker/Dockerfile.backend container_name: evoting_api ports: - "8000:8000" depends_on: - mariadb - validator-1 - validator-2 - validator-3 environment: DB_HOST: mariadb VALIDATOR_URLS: http://validator-1:8001,http://validator-2:8002,http://validator-3:8003 networks: - evoting_network mariadb: image: mariadb:latest # ... existing config ... frontend: # ... existing config ... ``` --- ## Testing Strategy ### Unit Tests ```python # tests/test_validator.py def test_block_creation(): validator = PoAValidator("test-validator", private_key) block = validator.create_block() assert block["index"] == 1 assert block["validator"] == validator.public_key assert verify_signature(block["signature"], block["block_hash"]) def test_block_validation(): validator = PoAValidator("test-validator", private_key) valid_block = validator.create_block() assert validator.validate_block(valid_block) == True # Tamper with block valid_block["transactions"][0]["encrypted_vote"] = "0x00" assert validator.validate_block(valid_block) == False def test_consensus(): # Three validators create blocks in sequence validators = [ PoAValidator("validator-1", key1), PoAValidator("validator-2", key2), PoAValidator("validator-3", key3) ] # Simulate block creation round for i, validator in enumerate(validators): if validator.should_create_block(): block = validator.create_block() for other in validators: if other != validator: assert other.validate_block(block) == True ``` ### Integration Tests ```python # tests/integration/test_voting_workflow.py async def test_complete_voting_workflow(): # 1. Start Docker containers # 2. Register voter # 3. Login # 4. Submit vote # 5. Verify vote on blockchain # 6. Check blockchain integrity response = await register_voter(email="test@example.com") assert response["success"] == True token = await login(email="test@example.com", password="password") assert token is not None tx_hash = await submit_vote(token, election_id=1, candidate_id=1) assert tx_hash is not None receipt = await get_transaction_receipt(tx_hash) assert receipt["blockNumber"] > 0 block = await get_block(receipt["blockNumber"]) assert len(block["transactions"]) > 0 ``` ### End-to-End Tests ```bash #!/bin/bash # tests/e2e.sh # Start system docker compose up -d --build # Wait for services to be ready sleep 30 # Test bootnode curl http://localhost:8546/health assert_success # Test validators for i in {1..3}; do curl http://localhost:$((8000 + i))/rpc assert_success done # Test voting workflow # ... (Python test script) # Check blockchain integrity curl http://localhost:8000/api/blockchain/verify assert_success # Cleanup docker compose down ``` --- ## Deployment Notes ### Initial Setup ```bash # 1. Generate validator keys python scripts/generate_keys.py -> Creates VALIDATOR_1_PRIVATE_KEY, VALIDATOR_2_PRIVATE_KEY, etc. # 2. Update .env file echo "VALIDATOR_1_PRIVATE_KEY=0x..." >> .env echo "VALIDATOR_2_PRIVATE_KEY=0x..." >> .env echo "VALIDATOR_3_PRIVATE_KEY=0x..." >> .env # 3. Start system docker compose up -d --build # 4. Verify all services are healthy docker compose ps docker compose logs bootnode | grep "healthy" ``` ### Monitoring ``` # Monitor blockchain growth curl http://localhost:8000/api/blockchain/stats -> {"total_blocks": 42, "total_votes": 39, "chain_valid": true} # Monitor validator health curl http://localhost:8001/health curl http://localhost:8002/health curl http://localhost:8003/health # Check peer connections curl http://localhost:8546/peers -> {"peers": [{"node_id": "validator-1", ...}, ...]} ``` --- ## Performance Characteristics ### Throughput - **Block creation**: 1 block per 5 seconds (configurable) - **Transactions per block**: ~32 (configurable) - **Overall throughput**: ~6.4 votes per second - **Can increase** by: - Reducing block time - Increasing transactions per block - Adding more validators (sharding) ### Latency - **Vote submission to blockchain**: ~5-10 seconds - 1-2 seconds: submit to validator - 3-5 seconds: wait for block creation - 1-2 seconds: broadcast to peers - **Confirmation**: 1-2 additional blocks (10-20 seconds) ### Storage - **Per vote**: ~1 KB (encrypted vote + metadata) - **Per block**: ~32 KB (32 votes + signatures) - **Annual storage**: ~1-5 GB for 100K votes --- ## Rollback Plan If critical issues discovered: 1. **Stop new submissions**: - API Server rejects new votes temporarily 2. **Identify issue**: - Analyze blockchain logs - Check validator consensus - Verify data integrity 3. **Rollback options**: - **Option A** (No data loss): Reset validators to last valid block - **Option B** (Start fresh): Wipe blockchain, restart from genesis 4. **Restart**: - `docker compose down` - Fix code/configuration - `docker compose up -d --build` - Resume voting