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 ballot_hash: Optional[str] = None
) -> Dict[str, Any]: ) -> 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: Args:
voter_id: Voter identifier 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] CALLED with voter_id={voter_id}, election_id={election_id}")
logger.info(f"[BlockchainClient.submit_vote] healthy_validators count: {len(self.healthy_validators)}") logger.info(f"[BlockchainClient.submit_vote] healthy_validators count: {len(self.healthy_validators)}")
validator = self._get_healthy_validator() if not self.healthy_validators:
if not validator:
logger.error("[BlockchainClient.submit_vote] No healthy validators available!") logger.error("[BlockchainClient.submit_vote] No healthy validators available!")
raise Exception("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 # Generate transaction ID if not provided
if not transaction_id: if not transaction_id:
import uuid import uuid
@ -224,21 +224,23 @@ class BlockchainClient:
"id": transaction_id "id": transaction_id
} }
logger.info(f"[BlockchainClient.submit_vote] Submitting vote to {validator.node_id}: tx_id={transaction_id}") # Submit to ALL healthy validators simultaneously
logger.info(f"[BlockchainClient.submit_vote] RPC URL: {validator.rpc_url}/rpc") logger.info(f"[BlockchainClient.submit_vote] Submitting to {len(self.healthy_validators)} validators")
try: results = {}
if not self._client: if not self._client:
logger.error("[BlockchainClient.submit_vote] AsyncClient not initialized!") logger.error("[BlockchainClient.submit_vote] AsyncClient not initialized!")
raise Exception("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( response = await self._client.post(
f"{validator.rpc_url}/rpc", f"{validator.rpc_url}/rpc",
json=rpc_request, json=rpc_request,
timeout=self.timeout 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() response.raise_for_status()
result = response.json() result = response.json()
@ -246,20 +248,28 @@ class BlockchainClient:
# Check for JSON-RPC errors # Check for JSON-RPC errors
if "error" in result: if "error" in result:
logger.error(f"RPC error from {validator.node_id}: {result['error']}") logger.error(f"RPC error from {validator.node_id}: {result['error']}")
raise Exception(f"RPC error: {result['error']}") results[validator.node_id] = f"RPC error: {result['error']}"
else:
logger.info(f"✓ Vote submitted successfully: {transaction_id}") logger.info(f"✓ Vote accepted by {validator.node_id}: {result.get('result')}")
results[validator.node_id] = result.get("result")
return {
"transaction_id": transaction_id,
"block_hash": result.get("result"),
"validator": validator.node_id,
"status": "pending"
}
except Exception as e: except Exception as e:
logger.error(f"Failed to submit vote to {validator.node_id}: {e}") logger.warning(f"Failed to submit to {validator.node_id}: {e}")
raise 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( async def get_transaction_receipt(
self, self,