fix: Submit votes to ALL validators instead of single validator

Problem: Votes were only being submitted to one validator selected via
round-robin, then expected inter-validator broadcasting to propagate the
transaction. But inter-validator transaction broadcasting wasn't working
reliably.

Solution: Submit each vote to ALL healthy validators simultaneously.
This ensures every validator receives the transaction directly, making it
available for block creation regardless of inter-validator communication.

Benefits:
- No dependency on P2P transaction broadcasting
- All validators have same pending transaction pool
- Any validator can create blocks with all pending transactions
- More robust and simpler than trying to maintain P2P mesh

Implementation:
- Modified submit_vote() to loop through all healthy_validators
- Submit same JSON-RPC request to each validator
- Log results from each submission
- Require at least one successful submission

This is simpler and more reliable than the previous architecture.

🤖 Generated with Claude Code

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

View File

@ -160,7 +160,10 @@ class BlockchainClient:
ballot_hash: Optional[str] = None
) -> Dict[str, Any]:
"""
Submit a vote to the PoA blockchain network.
Submit a vote to ALL PoA validators simultaneously.
This ensures every validator receives the transaction directly,
guaranteeing it will be included in the next block.
Args:
voter_id: Voter identifier
@ -178,13 +181,10 @@ class BlockchainClient:
logger.info(f"[BlockchainClient.submit_vote] CALLED with voter_id={voter_id}, election_id={election_id}")
logger.info(f"[BlockchainClient.submit_vote] healthy_validators count: {len(self.healthy_validators)}")
validator = self._get_healthy_validator()
if not validator:
if not self.healthy_validators:
logger.error("[BlockchainClient.submit_vote] No healthy validators available!")
raise Exception("No healthy validators available")
logger.info(f"[BlockchainClient.submit_vote] Selected validator: {validator.node_id}")
# Generate transaction ID if not provided
if not transaction_id:
import uuid
@ -224,21 +224,23 @@ class BlockchainClient:
"id": transaction_id
}
logger.info(f"[BlockchainClient.submit_vote] Submitting vote to {validator.node_id}: tx_id={transaction_id}")
logger.info(f"[BlockchainClient.submit_vote] RPC URL: {validator.rpc_url}/rpc")
# Submit to ALL healthy validators simultaneously
logger.info(f"[BlockchainClient.submit_vote] Submitting to {len(self.healthy_validators)} validators")
try:
results = {}
if not self._client:
logger.error("[BlockchainClient.submit_vote] AsyncClient not initialized!")
raise Exception("AsyncClient not initialized")
logger.info(f"[BlockchainClient.submit_vote] Sending POST request to {validator.rpc_url}/rpc")
for validator in self.healthy_validators:
try:
logger.info(f"[BlockchainClient.submit_vote] Submitting to {validator.node_id} ({validator.rpc_url}/rpc)")
response = await self._client.post(
f"{validator.rpc_url}/rpc",
json=rpc_request,
timeout=self.timeout
)
logger.info(f"[BlockchainClient.submit_vote] Response received: status={response.status_code}")
logger.info(f"[BlockchainClient.submit_vote] Response from {validator.node_id}: status={response.status_code}")
response.raise_for_status()
result = response.json()
@ -246,20 +248,28 @@ class BlockchainClient:
# Check for JSON-RPC errors
if "error" in result:
logger.error(f"RPC error from {validator.node_id}: {result['error']}")
raise Exception(f"RPC error: {result['error']}")
logger.info(f"✓ Vote submitted successfully: {transaction_id}")
return {
"transaction_id": transaction_id,
"block_hash": result.get("result"),
"validator": validator.node_id,
"status": "pending"
}
results[validator.node_id] = f"RPC error: {result['error']}"
else:
logger.info(f"✓ Vote accepted by {validator.node_id}: {result.get('result')}")
results[validator.node_id] = result.get("result")
except Exception as e:
logger.error(f"Failed to submit vote to {validator.node_id}: {e}")
raise
logger.warning(f"Failed to submit to {validator.node_id}: {e}")
results[validator.node_id] = str(e)
# Check if at least one validator accepted the vote
successful = [v for v in results.values() if not str(v).startswith(("RPC error", "Failed"))]
if successful:
logger.info(f"✓ Vote submitted successfully to {len(successful)} validators: {transaction_id}")
return {
"transaction_id": transaction_id,
"block_hash": successful[0] if successful else None,
"validator": self.healthy_validators[0].node_id,
"status": "pending"
}
else:
logger.error(f"Failed to submit vote to any validator")
raise Exception(f"All validator submissions failed: {results}")
async def get_transaction_receipt(
self,