From 38369a7f8801cf92d1ea7b963fca1ac9f1fd9e5f Mon Sep 17 00:00:00 2001 From: Alexis Bruneteau Date: Fri, 7 Nov 2025 17:07:34 +0100 Subject: [PATCH] fix: Query all validators for blockchain state, use longest chain MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Problem: When querying blockchain state via get_blockchain_state(), the backend only queried validator-1 (via _get_healthy_validator()). If validator-1 was behind other validators in block synchronization, the backend would return stale data without the latest blocks. This caused: Users' votes would be submitted to all validators and included in blocks, but when querying the blockchain, the backend would return an old state without those blocks. Root cause: No block synchronization between validators yet. When a validator creates a block, it doesn't immediately get to all peers. So different validators can have different chain lengths. Solution: Query ALL healthy validators for their blockchain state and return the state from the one with the longest chain. This ensures the client always gets the most up-to-date blockchain state. Implementation: - Loop through all healthy_validators - Query each one's blockchain endpoint - Track the state with the highest block count - Return that state This is a best-effort approach while block synchronization is being established between validators. 🤖 Generated with Claude Code Co-Authored-By: Claude --- e-voting-system/backend/blockchain_client.py | 51 ++++++++++++++------ 1 file changed, 35 insertions(+), 16 deletions(-) diff --git a/e-voting-system/backend/blockchain_client.py b/e-voting-system/backend/blockchain_client.py index e328dca..5e9f6e0 100644 --- a/e-voting-system/backend/blockchain_client.py +++ b/e-voting-system/backend/blockchain_client.py @@ -360,33 +360,52 @@ class BlockchainClient: """ Get the current state of the blockchain for an election. + Queries ALL healthy validators and returns the state from the validator + with the longest chain (to ensure latest blocks). + Args: election_id: Election ID Returns: Blockchain state with block count and verification status """ - validator = self._get_healthy_validator() - if not validator: + if not self.healthy_validators: return None - try: - if not self._client: - raise Exception("AsyncClient not initialized") + if not self._client: + raise Exception("AsyncClient not initialized") - # Query blockchain info endpoint on validator - response = await self._client.get( - f"{validator.rpc_url}/blockchain", - params={"election_id": election_id}, - timeout=self.timeout - ) + # Query all validators and get the one with longest chain + best_state = None + best_block_count = 0 - response.raise_for_status() - return response.json() + for validator in self.healthy_validators: + try: + logger.debug(f"Querying blockchain state from {validator.node_id}") + response = await self._client.get( + f"{validator.rpc_url}/blockchain", + params={"election_id": election_id}, + timeout=self.timeout + ) - except Exception as e: - logger.warning(f"Failed to get blockchain state: {e}") - return None + response.raise_for_status() + state = response.json() + + # Get block count from this validator + block_count = len(state.get("blocks", [])) + logger.debug(f"{validator.node_id} has {block_count} blocks") + + # Keep the state with the most blocks (longest chain) + if block_count > best_block_count: + best_state = state + best_block_count = block_count + logger.info(f"Using state from {validator.node_id} ({block_count} blocks)") + + except Exception as e: + logger.warning(f"Failed to get blockchain state from {validator.node_id}: {e}") + continue + + return best_state if best_state else None async def verify_blockchain_integrity(self, election_id: int) -> bool: """