feat: Add transaction broadcasting between PoA validators

Problem: Votes were being submitted to one validator but not shared with
other validators, preventing them from being included in blocks.

Root cause: When a validator received a transaction via eth_sendTransaction,
it added it to its pending_transactions pool but did NOT broadcast it to
peer validators. Only blocks were being broadcast.

This meant:
- validator-1 receives vote → adds to pending_transactions
- validator-2 (responsible for next block) never receives the vote
- validator-2 can't include vote in block because it doesn't know about it
- Result: votes sit in pending queue forever

Solution:
- Add broadcast_transaction() method following same pattern as broadcast_block()
- Broadcast transaction to all known peers via /p2p/new_transaction endpoint
- Call broadcast on receipt of each transaction
- Peer validators receive and add to their pending_transactions pool
- All validators now have same pending transactions
- Any validator can create blocks with all pending transactions

The /p2p/new_transaction endpoint already existed, so validators can now
receive and process transactions from peers.

This fixes the issue where votes were submitted successfully but never
appeared on the blockchain.

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Alexis Bruneteau 2025-11-07 17:00:14 +01:00
parent 8be804f7c6
commit 6cd555a552

View File

@ -411,6 +411,38 @@ class PoAValidator:
except Exception as e:
logger.error(f"Error creating block: {e}")
async def broadcast_transaction(self, transaction: Transaction) -> None:
"""Broadcast transaction to all peers"""
if not self.peer_connections:
logger.debug("No peers to broadcast transaction to")
return
payload = json.dumps({
"type": "new_transaction",
"transaction": {
"voter_id": transaction.voter_id,
"election_id": transaction.election_id,
"encrypted_vote": transaction.encrypted_vote,
"ballot_hash": transaction.ballot_hash,
"proof": transaction.proof,
"timestamp": transaction.timestamp
}
})
async with aiohttp.ClientSession() as session:
for node_id, peer_url in self.peer_connections.items():
try:
async with session.post(
f"{peer_url}/p2p/new_transaction",
data=payload,
headers={"Content-Type": "application/json"},
timeout=aiohttp.ClientTimeout(total=5)
) as resp:
if resp.status == 200:
logger.debug(f"Transaction broadcast to {node_id}")
except Exception as e:
logger.debug(f"Failed to broadcast transaction to {node_id}: {e}")
async def broadcast_block(self, block: Block) -> None:
"""Broadcast block to all peers"""
if not self.peer_connections:
@ -586,6 +618,9 @@ async def handle_json_rpc(request: dict):
transaction = Transaction(**tx_dict)
tx_id = validator.add_transaction(transaction)
# Broadcast transaction to other validators
asyncio.create_task(validator.broadcast_transaction(transaction))
return {
"jsonrpc": "2.0",
"result": tx_id,