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)