From 88ed8a70ccfe6c4c0b7619de68ad3200d12b2d4a Mon Sep 17 00:00:00 2001 From: aitbc Date: Tue, 19 May 2026 17:06:42 +0200 Subject: [PATCH] fix: parent block validation in importBlock + no retry on 4xx in AITBCHTTPClient - importBlock now returns 400 'Parent block not found' when parent_hash is not in the DB (skipped for genesis height 1) - AITBCHTTPClient._retry_request now immediately re-raises HTTPError for 4xx responses instead of retrying - fixes RetryError wrapping 400/409 in test_block_import_complete --- aitbc/network/http_client.py | 13 +++++++++++++ apps/blockchain-node/src/aitbc_chain/rpc/router.py | 12 ++++++++++++ 2 files changed, 25 insertions(+) diff --git a/aitbc/network/http_client.py b/aitbc/network/http_client.py index 782d78d6..b06f593e 100644 --- a/aitbc/network/http_client.py +++ b/aitbc/network/http_client.py @@ -163,6 +163,19 @@ class AITBCHTTPClient: time.sleep(backoff_time) return request_func(*args, **kwargs) + except requests.HTTPError as e: + # Never retry client errors (4xx) - they are deterministic + if e.response is not None and 400 <= e.response.status_code < 500: + raise + last_error = e + if attempt < self.max_retries: + if self.enable_logging: + self.logger.warning(f"Request failed (attempt {attempt + 1}/{self.max_retries + 1}): {e}") + continue + else: + if self.enable_logging: + self.logger.error(f"All retry attempts exhausted: {e}") + raise RetryError(f"Retry attempts exhausted: {e}") except requests.RequestException as e: last_error = e if attempt < self.max_retries: diff --git a/apps/blockchain-node/src/aitbc_chain/rpc/router.py b/apps/blockchain-node/src/aitbc_chain/rpc/router.py index 9f7b16ff..f037216e 100644 --- a/apps/blockchain-node/src/aitbc_chain/rpc/router.py +++ b/apps/blockchain-node/src/aitbc_chain/rpc/router.py @@ -912,6 +912,18 @@ async def import_block( detail=f"Block height {block_height} already exists with different hash", ) + # Validate parent block exists (skip for genesis block height 1) + parent_hash = block_data["parent_hash"] + if block_height > 1: + parent_block = session.exec( + select(Block).where(Block.hash == parent_hash) + ).first() + if parent_block is None: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="Parent block not found", + ) + # Check for hash conflicts across chains existing_block = session.execute( select(Block).where(Block.hash == block_hash)