错误处理与错误码参考
这是一份 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"]
}
}
原因:
- 网络名称拼写错误
- 不支持的网络
客户端操作: 使用以下之一:mainnet、testnet、futurenet
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
原因:
- 超出了你层级的频率限制
- 时间窗口内请求过多
客户端操作:
- 在重试前等待
Retry-After秒 - 实施指数退避
- 监控
X-RateLimit-Remaining标头 - 如果需要,申请更高层级
示例 (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
- 意外的数据状态
客户端操作:
- 使用指数退避重试(3-5 次尝试)
- 如果问题持续存在,请带上
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 Issues: https://github.com/ALIPHATICHYD/Soroban-Registry/issues
- 标签:
api,errors
- 原文链接: github.com/ALIPHATICHYD/...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~