错误处理与错误码参考

这是一份 Soroban Registry API 的错误码参考文档,系统整理了各类 HTTP 状态码对应的错误响应格式、常见错误原因、客户端处理建议以及 Python、JavaScript、Rust 的示例代码。文档还补充了速率限制、重试、日志追踪和可读错误提示等最佳实践。

错误处理与错误代码参考

概述

本文档提供了 Soroban Registry API 返回的所有错误代码的全面参考,包括其含义、原因以及如何在客户端应用程序中处理这些错误。

错误响应格式

所有 API 错误都遵循一致的 JSON 结构:

{
  "error_code": "BAD_REQUEST",
  "message": "Human-readable error description",
  "details": {
    "reason": "INVALID_CONTRACT_ID",
    "correlation_id": "550e8400-e29b-41d4-a716-446655440000"
  },
  "timestamp": "2026-02-24T12:34:56Z"
}

支持的标准顶级 error_code 值源自 HTTP 状态类:

BAD_REQUEST
UNAUTHORIZED
FORBIDDEN
NOT_FOUND
CONFLICT
UNPROCESSABLE_ENTITY
PAYLOAD_TOO_LARGE
RATE_LIMITED
INTERNAL_ERROR

details 包含用于客户端处理的特定端点上下文,例如验证字段错误:

{
  "error_code": "BAD_REQUEST",
  "message": "Validation failed for 2 fields",
  "details": {
    "reason": "VALIDATION_ERROR",
    "field_errors": [
      {"field": "contract_id", "message": "Invalid format"},
      {"field": "network", "message": "Unsupported network"}
    ],
    "correlation_id": "550e8400-e29b-41d4-a716-446655440000"
  }
}

响应字段

字段 类型 描述
error_code string 标准化的机器可读错误类
message string 人类可读的错误描述
details object 附加上下文(可选,因端点而异)
timestamp string 错误发生时的 ISO 8601 时间戳

HTTP 状态码

2xx 成功

代码 状态 含义
200 OK 请求成功
201 Created 资源创建成功
202 Accepted 请求已接受进行异步处理
204 No Content 请求成功且无响应体

4xx 客户端错误

客户端错误表示请求无效。用户可以修复这些错误。

400 Bad Request

请求格式错误或包含无效参数。

常见错误代码:

ERR_INVALID_INPUT
{
  "error": "ERR_INVALID_INPUT",
  "message": "Invalid input parameters",
  "code": 400,
  "details": {
    "field": "contract_id",
    "reason": "Contract ID must be 56 characters long"
  }
}

原因:

  • 合约 ID 格式无效
  • 缺少必填字段
  • 数据类型无效(例如应该是数字却提供了字符串)
  • 值超出范围

客户端操作: 在发送前验证输入。查看 API 文档以获取正确的格式。

示例 (Python):

def validate_contract_id(contract_id: str) -> bool:
    return len(contract_id) == 56 and contract_id[0] == 'C'

if not validate_contract_id(contract_id):
    raise ValueError("Invalid contract ID format")

ERR_INVALID_NETWORK
{
  "error": "ERR_INVALID_NETWORK",
  "message": "Invalid network specified",
  "code": 400,
  "details": {
    "network": "mainnett",
    "valid_networks": ["mainnet", "testnet", "futurenet"]
  }
}

原因:

  • 网络名称拼写错误
  • 不支持的网络

客户端操作: 使用以下之一:mainnettestnetfuturenet


ERR_INVALID_PAGINATION
{
  "error": "ERR_INVALID_PAGINATION",
  "message": "Invalid pagination parameters",
  "code": 400,
  "details": {
    "limit": 10000,
    "max_limit": 1000
  }
}

原因:

  • limit 超过最大值 (1000)
  • offset 为负数
  • 游标格式无效

客户端操作: 使用 limit <= 1000 和有效的分页参数。


ERR_MALFORMED_JSON
{
  "error": "ERR_MALFORMED_JSON",
  "message": "Request body is not valid JSON",
  "code": 400,
  "details": {
    "parse_error": "Expected ',' at line 5, column 12"
  }
}

原因:

  • JSON 中缺少引号、逗号或括号
  • 尾随逗号(在 JSON 中无效)
  • 无效的转义序列

客户端操作: 在发送前验证 JSON。使用 JSON.stringify() 或等效方法。


401 Unauthorized

需要身份验证,但未提供或无效。

ERR_MISSING_AUTH
{
  "error": "ERR_MISSING_AUTH",
  "message": "Authentication required for this endpoint",
  "code": 401
}

原因:

  • 未提供 Authorization 标头
  • 端点需要身份验证

客户端操作:Authorization 标头中包含有效的 API key。

示例:

GET /api/contracts/private
Authorization: Bearer your-api-key-here

ERR_INVALID_TOKEN
{
  "error": "ERR_INVALID_TOKEN",
  "message": "Invalid or expired authentication token",
  "code": 401
}

原因:

  • API key 无效
  • Token 已过期
  • Token 已撤销

客户端操作: 获取新的 API key 或刷新 Token。


403 Forbidden

客户端已通过身份验证,但没有访问该资源的权限。

ERR_FORBIDDEN
{
  "error": "ERR_FORBIDDEN",
  "message": "You don't have permission to access this resource",
  "code": 403,
  "details": {
    "required_permission": "admin:write"
  }
}

原因:

  • 权限不足
  • 尝试修改其他用户的资源
  • IP 地址被封锁

客户端操作: 申请适当的权限或联系管理员。


404 Not Found

请求的资源不存在。

ERR_CONTRACT_NOT_FOUND
{
  "error": "ERR_CONTRACT_NOT_FOUND",
  "message": "Contract not found",
  "code": 404,
  "details": {
    "contract_id": "CDLZFC3...",
    "network": "mainnet"
  }
}

原因:

  • 合约 ID 不存在
  • 合约已删除
  • 指定了错误的网络

客户端操作: 验证合约 ID 和网络。检查合约是否在链上存在。


ERR_PUBLISHER_NOT_FOUND
{
  "error": "ERR_PUBLISHER_NOT_FOUND",
  "message": "Publisher not found",
  "code": 404,
  "details": {
    "publisher_id": "pub_abc123"
  }
}

原因:

  • 发布者 ID 无效
  • 发布者账户已删除

客户端操作: 验证发布者 ID 或按名称搜索。


ERR_VERIFICATION_NOT_FOUND
{
  "error": "ERR_VERIFICATION_NOT_FOUND",
  "message": "Verification record not found",
  "code": 404,
  "details": {
    "verification_id": "ver_xyz789"
  }
}

原因:

  • 验证 ID 不存在
  • 验证已过期

客户端操作: 检查验证 ID 或发起新的验证。


409 Conflict

请求与资源的当前状态冲突。

ERR_ALREADY_EXISTS
{
  "error": "ERR_ALREADY_EXISTS",
  "message": "Resource already exists",
  "code": 409,
  "details": {
    "resource_type": "contract",
    "contract_id": "CDLZFC3..."
  }
}

原因:

  • 尝试创建重复的合约条目
  • 合约已验证

客户端操作: 使用 UPDATE 端点而不是 CREATE,或检查现有资源。


ERR_VERIFICATION_IN_PROGRESS
{
  "error": "ERR_VERIFICATION_IN_PROGRESS",
  "message": "Verification already in progress for this contract",
  "code": 409,
  "details": {
    "verification_id": "ver_abc123",
    "status": "pending"
  }
}

原因:

  • 上一次验证仍在运行中

客户端操作: 等待现有验证完成或先将其取消。


422 Unprocessable Entity

请求格式正确,但语义无效。

ERR_INVALID_CONTRACT_SOURCE
{
  "error": "ERR_INVALID_CONTRACT_SOURCE",
  "message": "Contract source code is invalid",
  "code": 422,
  "details": {
    "reason": "Missing Cargo.toml",
    "required_files": ["Cargo.toml", "src/lib.rs"]
  }
}

原因:

  • 源代码不完整
  • 缺少必填文件
  • 文件结构无效

客户端操作: 确保源代码包含所有必填文件。


ERR_UNSUPPORTED_COMPILER
{
  "error": "ERR_UNSUPPORTED_COMPILER",
  "message": "Compiler version not supported",
  "code": 422,
  "details": {
    "requested_version": "19.0.0",
    "supported_versions": ["20.0.0", "20.5.0", "21.0.0", "21.2.0"]
  }
}

原因:

  • 请求了不支持的编译器版本

客户端操作: 使用受支持的编译器版本。


429 Too Many Requests

超出频率限制。参见 API 频率限制

ERR_RATE_LIMIT_EXCEEDED
{
  "error": "ERR_RATE_LIMIT_EXCEEDED",
  "message": "Too many requests. Please retry after the indicated time.",
  "code": 429,
  "timestamp": "2026-02-24T12:34:56Z",
  "correlation_id": "550e8400-e29b-41d4-a716-446655440000"
}

响应标头:

X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 42
Retry-After: 42

原因:

  • 超出了你层级的频率限制
  • 时间窗口内请求过多

客户端操作:

  1. 在重试前等待 Retry-After
  2. 实施指数退避
  3. 监控 X-RateLimit-Remaining 标头
  4. 如果需要,申请更高层级

示例 (Python):

import time

response = requests.get(url)
if response.status_code == 429:
    retry_after = int(response.headers.get('Retry-After', 60))
    print(f"Rate limited. Waiting {retry_after}s...")
    time.sleep(retry_after)
    response = requests.get(url)  # 重试

5xx 服务器错误

服务器错误表示服务器端出现问题。用户应使用指数退避进行重试。

500 Internal Server Error

意外的服务器错误。

ERR_INTERNAL_SERVER_ERROR
{
  "error": "ERR_INTERNAL_SERVER_ERROR",
  "message": "An unexpected error occurred",
  "code": 500,
  "correlation_id": "550e8400-e29b-41d4-a716-446655440000"
}

原因:

  • 未处理的异常
  • 服务器代码中的 Bug
  • 意外的数据状态

客户端操作:

  1. 使用指数退避重试(3-5 次尝试)
  2. 如果问题持续存在,请带上 correlation_id 报告问题

示例 (JavaScript):

async function retryOnError(fn, maxRetries = 3) {
  for (let i = 0; i < maxRetries; i++) {
    try {
      return await fn();
    } catch (error) {
      if (error.status === 500 && i < maxRetries - 1) {
        const delay = Math.pow(2, i) * 1000; // 指数退避
        await new Promise(resolve => setTimeout(resolve, delay));
        continue;
      }
      throw error;
    }
  }
}

ERR_DATABASE_ERROR
{
  "error": "ERR_DATABASE_ERROR",
  "message": "Database operation failed",
  "code": 500,
  "correlation_id": "550e8400-e29b-41d4-a716-446655440000"
}

原因:

  • 数据库连接丢失
  • 数据库超时
  • 数据库约束违规

客户端操作: 在短暂延迟后重试。如果问题持续存在,请报告问题。


ERR_COMPILATION_ERROR
{
  "error": "ERR_COMPILATION_ERROR",
  "message": "Contract compilation failed",
  "code": 500,
  "details": {
    "compiler_output": "error: could not compile `contract`..."
  }
}

原因:

  • 内部编译错误
  • 编译器崩溃
  • 构建环境问题

客户端操作: 检查源代码的有效性。如果源码正确,请报告问题。


502 Bad Gateway

上游服务(例如 Stellar RPC)不可用。

ERR_RPC_ERROR
{
  "error": "ERR_RPC_ERROR",
  "message": "Failed to communicate with Stellar RPC",
  "code": 502,
  "details": {
    "rpc_endpoint": "https://soroban-testnet.stellar.org",
    "reason": "Connection timeout"
  }
}

原因:

  • Stellar RPC 挂了
  • 网络连接问题
  • RPC 超时

客户端操作: 使用指数退避重试。检查 Stellar 网络状态。


503 Service Unavailable

服务暂时不可用。

ERR_SERVICE_UNAVAILABLE
{
  "error": "ERR_SERVICE_UNAVAILABLE",
  "message": "Service temporarily unavailable",
  "code": 503,
  "details": {
    "reason": "Maintenance in progress",
    "retry_after": 300
  }
}

响应标头:

Retry-After: 300

原因:

  • 计划维护
  • 系统过载
  • 正在部署

客户端操作: 等待 Retry-After 秒后重试。


ERR_MAINTENANCE_MODE
{
  "error": "ERR_MAINTENANCE_MODE",
  "message": "System is in maintenance mode",
  "code": 503,
  "details": {
    "estimated_duration": "30 minutes",
    "started_at": "2026-02-24T12:00:00Z"
  }
}

原因:

  • 计划维护窗口

客户端操作: 等待维护完成。检查状态页面。


504 Gateway Timeout

上游服务响应时间过长。

ERR_TIMEOUT
{
  "error": "ERR_TIMEOUT",
  "message": "Request timeout",
  "code": 504,
  "details": {
    "timeout_seconds": 30,
    "operation": "contract_verification"
  }
}

原因:

  • 操作耗时超过超时限制
  • 上游服务缓慢

客户端操作: 重试请求。对于验证操作,考虑使用异步端点。


验证特定错误

ERR_BYTECODE_MISMATCH

{
  "error": "ERR_BYTECODE_MISMATCH",
  "message": "Compiled bytecode does not match on-chain bytecode",
  "code": 422,
  "details": {
    "expected_hash": "a3f2b8c9d1e4f5a6b7c8d9e0...",
    "actual_hash": "9f1a2b3c4d5e6f7a8b9c0d1e...",
    "possible_causes": [
      "Wrong compiler version",
      "Different optimization level",
      "Dependency version mismatch"
    ]
  }
}

客户端操作: 参见 验证故障排除

ERR_COMPILATION_FAILED

{
  "error": "ERR_COMPILATION_FAILED",
  "message": "Source code failed to compile",
  "code": 422,
  "details": {
    "compiler_output": "error[E0425]: cannot find function `transfer` in this scope\n --> src/lib.rs:42:5"
  }
}

客户端操作: 修复源代码中的编译错误。

ERR_ABI_MISMATCH

{
  "error": "ERR_ABI_MISMATCH",
  "message": "Contract ABI does not match declared interface",
  "code": 422,
  "details": {
    "function": "transfer",
    "expected_signature": "(Address, Address, i128)",
    "actual_signature": "(Address, i128)"
  }
}

客户端操作: 确保源代码与部署版本匹配。

错误处理最佳实践

1. 始终检查状态码

response = requests.get(url)
if response.status_code >= 400:
    error_data = response.json()
    handle_error(error_data)

2. 解析错误响应

async function callApi(url) {
  const response = await fetch(url);

  if (!response.ok) {
    const error = await response.json();
    throw new ApiError(
      error.error,
      error.message,
      error.correlation_id
    );
  }

  return response.json();
}

3. 实施重试逻辑

重试针对:

  • 5xx 错误(服务器问题)
  • 429(频率限制 - 配合退避)
  • 502, 503, 504(上游/可用性问题)

不要重试针对:

  • 4xx 错误(除 429 外)- 这些需要修复请求本身
async fn call_with_retry<T>(
    operation: impl Fn() -> Result<T>,
    max_retries: u32,
) -> Result<T> {
    for attempt in 0..max_retries {
        match operation() {
            Ok(value) => return Ok(value),
            Err(e) if e.is_retryable() && attempt < max_retries - 1 => {
                let delay = Duration::from_secs(2_u64.pow(attempt));
                tokio::time::sleep(delay).await;
                continue;
            }
            Err(e) => return Err(e),
        }
    }
    unreachable!()
}

4. 记录相关性 ID

始终记录 correlation_id 以便调试:

try:
    response = api_client.get_contract(contract_id)
except ApiError as e:
    logger.error(
        f"API error: {e.error_code}",
        extra={
            "correlation_id": e.correlation_id,
            "contract_id": contract_id
        }
    )
    raise

5. 主动处理频率限制

class ApiClient {
  async request(url) {
    const response = await fetch(url);

    // 检查频率限制标头
    const remaining = parseInt(response.headers.get('X-RateLimit-Remaining'));
    if (remaining < 10) {
      console.warn(`Only ${remaining} requests remaining!`);
    }

    if (response.status === 429) {
      const retryAfter = parseInt(response.headers.get('Retry-After'));
      await this.sleep(retryAfter * 1000);
      return this.request(url); // 重试
    }

    return response.json();
  }
}

6. 提供用户友好的消息

不要直接向最终用户展示原始错误代码:

USER_FRIENDLY_MESSAGES = {
    "ERR_CONTRACT_NOT_FOUND": "We couldn't find that contract. Please check the contract ID.",
    "ERR_RATE_LIMIT_EXCEEDED": "Too many requests. Please wait a moment and try again.",
    "ERR_INTERNAL_SERVER_ERROR": "Something went wrong on our end. Please try again later.",
}

def format_error_for_user(error_code):
    return USER_FRIENDLY_MESSAGES.get(
        error_code,
        "An unexpected error occurred. Please try again."
    )

各语言代码示例

Python

import requests
import time
from typing import Optional

class SorobanRegistryClient:
    def __init__(self, base_url: str, api_key: Optional[str] = None):
        self.base_url = base_url
        self.session = requests.Session()
        if api_key:
            self.session.headers['Authorization'] = f'Bearer {api_key}'

    def get_contract(self, contract_id: str):
        try:
            response = self.session.get(f'{self.base_url}/api/contracts/{contract_id}')
            response.raise_for_status()
            return response.json()
        except requests.HTTPError as e:
            if e.response.status_code == 404:
                raise ContractNotFoundError(contract_id)
            elif e.response.status_code == 429:
                retry_after = int(e.response.headers.get('Retry-After', 60))
                raise RateLimitError(retry_after)
            elif e.response.status_code >= 500:
                error_data = e.response.json()
                raise ServerError(error_data['correlation_id'])
            else:
                raise

JavaScript/TypeScript

interface ApiError {
  error: string;
  message: string;
  code: number;
  correlation_id: string;
  details?: any;
}

class SorobanRegistryClient {
  constructor(
    private baseUrl: string,
    private apiKey?: string
  ) {}

  async getContract(contractId: string) {
    const response = await fetch(
      `${this.baseUrl}/api/contracts/${contractId}`,
      {
        headers: this.apiKey
          ? { 'Authorization': `Bearer ${this.apiKey}` }
          : {}
      }
    );

    if (!response.ok) {
      const error: ApiError = await response.json();

      switch (error.code) {
        case 404:
          throw new ContractNotFoundError(contractId);
        case 429:
          const retryAfter = parseInt(response.headers.get('Retry-After') || '60');
          throw new RateLimitError(retryAfter);
        case 500:
        case 502:
        case 503:
          throw new ServerError(error.correlation_id);
        default:
          throw new ApiError(error.error, error.message);
      }
    }

    return response.json();
  }
}

Rust

use reqwest::StatusCode;
use serde::Deserialize;

##[derive(Debug, Deserialize)]
struct ApiError {
    error: String,
    message: String,
    code: u16,
    correlation_id: String,
}

pub async fn get_contract(
    client: &reqwest::Client,
    base_url: &str,
    contract_id: &str,
) -> Result<Contract, Box<dyn std::error::Error>> {
    let url = format!("{}/api/contracts/{}", base_url, contract_id);
    let response = client.get(&url).send().await?;

    match response.status() {
        StatusCode::OK => {
            let contract = response.json::<Contract>().await?;
            Ok(contract)
        }
        StatusCode::NOT_FOUND => {
            Err(format!("Contract {} not found", contract_id).into())
        }
        StatusCode::TOO_MANY_REQUESTS => {
            let retry_after = response
                .headers()
                .get("retry-after")
                .and_then(|h| h.to_str().ok())
                .and_then(|s| s.parse::<u64>().ok())
                .unwrap_or(60);
            Err(format!("Rate limited. Retry after {}s", retry_after).into())
        }
        _ if response.status().is_server_error() => {
            let error = response.json::<ApiError>().await?;
            Err(format!(
                "Server error: {} (correlation_id: {})",
                error.message, error.correlation_id
            )
            .into())
        }
        _ => {
            let error = response.json::<ApiError>().await?;
            Err(error.message.into())
        }
    }
}

相关文档

支持

关于错误相关的问题:

  • 原文链接: github.com/ALIPHATICHYD/...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
ALIPHATICHYD
ALIPHATICHYD
江湖只有他的大名,没有他的介绍。