From 476eb496e8c277c7452ab6eda73d6acc28e72c7d Mon Sep 17 00:00:00 2001 From: aitbc Date: Sat, 9 May 2026 19:26:23 +0200 Subject: [PATCH] Fix genesis state root sync issue: use RPC-provided state_root during bootstrap - Modify _load_genesis_allocations_from_rpc() to extract and return genesis_state_root from RPC response - Update _initialize_genesis_allocations() to receive and store the RPC genesis_state_root - Modify _ensure_genesis_block() to use RPC-provided state_root instead of computing it locally - Remove state root recomputation after account initialization This fixes the state root mismatch between aitbc and aitbc1 genesis blocks that was preventing successful block sync. When aitbc1 bootstraps via RPC, it now uses the original genesis state_root from aitbc instead of recomputing it locally, ensuring state roots match during sync. --- .../src/aitbc_chain/consensus/poa.py | 52 ++++++++++--------- 1 file changed, 28 insertions(+), 24 deletions(-) diff --git a/apps/blockchain-node/src/aitbc_chain/consensus/poa.py b/apps/blockchain-node/src/aitbc_chain/consensus/poa.py index 59e23a8e..3402c35e 100755 --- a/apps/blockchain-node/src/aitbc_chain/consensus/poa.py +++ b/apps/blockchain-node/src/aitbc_chain/consensus/poa.py @@ -442,8 +442,17 @@ class PoAProposer: # Load genesis allocations for embedding in metadata genesis_allocations = self._load_genesis_allocations_for_metadata() - # Compute state root for genesis block - state_root = _compute_state_root(session, self._config.chain_id) + # Initialize accounts from genesis allocations file or RPC bootstrap + await self._initialize_genesis_allocations(session) + + # Use RPC-provided state_root if available, otherwise compute locally + if hasattr(self, '_rpc_genesis_state_root') and self._rpc_genesis_state_root: + state_root = self._rpc_genesis_state_root + self._logger.info(f"Using RPC-provided genesis state_root: {state_root}") + else: + # Compute state root for genesis block (local file case) + state_root = _compute_state_root(session, self._config.chain_id) + self._logger.info(f"Computed local genesis state_root: {state_root}") genesis = Block( chain_id=self._config.chain_id, @@ -464,17 +473,6 @@ class PoAProposer: session.rollback() return - # Initialize accounts from genesis allocations file (if present) - await self._initialize_genesis_allocations(session) - - # Recompute state root after accounts are initialized - new_state_root = _compute_state_root(session, self._config.chain_id) - if new_state_root: - genesis.state_root = new_state_root - session.add(genesis) - session.commit() - self._logger.info(f"Updated genesis block state_root: {new_state_root}") - # Broadcast genesis block for initial sync await gossip_broker.publish( f"blocks.{self._config.chain_id}", @@ -504,18 +502,17 @@ class PoAProposer: # Try RPC bootstrap self._logger.info(f"Local genesis file not found for chain {self._config.chain_id}, attempting RPC bootstrap") try: - rpc_allocations = await self._load_genesis_allocations_from_rpc() + rpc_allocations, rpc_genesis_state_root = await self._load_genesis_allocations_from_rpc() if rpc_allocations: self._logger.info(f"Loaded {len(rpc_allocations)} allocations via RPC bootstrap for chain {self._config.chain_id}") self._create_accounts_from_allocations(session, rpc_allocations) - # Use the RPC genesis state_root if available, otherwise recompute - if hasattr(self, '_rpc_genesis_state_root') and self._rpc_genesis_state_root: - self._logger.info(f"Using RPC genesis state_root: {self._rpc_genesis_state_root}") - return self._rpc_genesis_state_root + # Store the RPC genesis state_root for use in genesis block creation + if rpc_genesis_state_root: + self._rpc_genesis_state_root = rpc_genesis_state_root + self._logger.info(f"Stored RPC genesis state_root: {rpc_genesis_state_root}") else: - self._logger.info(f"RPC bootstrap completed successfully for chain {self._config.chain_id}, skipping local file") - return # Return early to avoid falling back to local file + self._logger.info(f"RPC bootstrap completed successfully for chain {self._config.chain_id}, but no state_root provided") else: self._logger.warning(f"RPC bootstrap returned no allocations for chain {self._config.chain_id}, skipping account initialization") except Exception as e: @@ -549,8 +546,12 @@ class PoAProposer: # Skip loading for metadata if we used RPC bootstrap return [] - async def _load_genesis_allocations_from_rpc(self) -> list: - """Load genesis allocations from trusted peer via RPC.""" + async def _load_genesis_allocations_from_rpc(self) -> tuple[list, Optional[str]]: + """Load genesis allocations and state_root from trusted peer via RPC. + + Returns: + Tuple of (allocations list, genesis_state_root string or None) + """ import httpx # Try multiple trusted peers @@ -578,9 +579,12 @@ class PoAProposer: data = response.json() self._logger.info(f"RPC response from {peer_url}: {data}") allocations = data.get("allocations", []) + genesis_state_root = data.get("genesis_state_root") if allocations: self._logger.info(f"Successfully loaded {len(allocations)} allocations from {peer_url}") - return allocations + if genesis_state_root: + self._logger.info(f"RPC provided genesis state_root: {genesis_state_root}") + return allocations, genesis_state_root else: self._logger.warning(f"RPC returned empty allocations from {peer_url}") except Exception as e: @@ -588,7 +592,7 @@ class PoAProposer: continue self._logger.error("RPC bootstrap failed for all peers") - return [] + return [], None def _create_accounts_from_allocations(self, session: Session, allocations: list) -> None: """Create Account entries from allocation data."""