274 lines
9.0 KiB
Rust
274 lines
9.0 KiB
Rust
// nac-daemon/src/main.rs
|
||
// Issue #67: NAC公链本地守护进程 (nacd)
|
||
// 提供本地 HTTP API,供开发者网站和 CLI 工具调用
|
||
|
||
use actix_web::{web, App, HttpServer, HttpResponse};
|
||
use serde::Serialize;
|
||
use std::sync::Arc;
|
||
use tokio::sync::RwLock;
|
||
use tracing::info;
|
||
|
||
mod config;
|
||
mod node_status;
|
||
mod wallet;
|
||
mod contract;
|
||
mod network;
|
||
|
||
use config::DaemonConfig;
|
||
use node_status::NodeStatus;
|
||
|
||
/// 守护进程全局状态
|
||
pub struct DaemonState {
|
||
pub config: DaemonConfig,
|
||
pub node_status: NodeStatus,
|
||
pub start_time: u64,
|
||
}
|
||
|
||
impl DaemonState {
|
||
pub fn new(config: DaemonConfig) -> Self {
|
||
DaemonState {
|
||
config,
|
||
node_status: NodeStatus::default(),
|
||
start_time: chrono::Utc::now().timestamp() as u64,
|
||
}
|
||
}
|
||
}
|
||
|
||
type SharedState = Arc<RwLock<DaemonState>>;
|
||
|
||
/// API 响应包装
|
||
#[derive(Serialize)]
|
||
pub struct ApiResponse<T: Serialize> {
|
||
pub success: bool,
|
||
pub data: Option<T>,
|
||
pub error: Option<String>,
|
||
pub timestamp: u64,
|
||
}
|
||
|
||
impl<T: Serialize> ApiResponse<T> {
|
||
pub fn ok(data: T) -> Self {
|
||
ApiResponse {
|
||
success: true,
|
||
data: Some(data),
|
||
error: None,
|
||
timestamp: chrono::Utc::now().timestamp() as u64,
|
||
}
|
||
}
|
||
|
||
pub fn err(msg: &str) -> ApiResponse<()> {
|
||
ApiResponse {
|
||
success: false,
|
||
data: None,
|
||
error: Some(msg.to_string()),
|
||
timestamp: chrono::Utc::now().timestamp() as u64,
|
||
}
|
||
}
|
||
}
|
||
|
||
// ============================================================
|
||
// API 端点
|
||
// ============================================================
|
||
|
||
/// GET /api/v1/status - 节点状态
|
||
async fn get_status(state: web::Data<SharedState>) -> HttpResponse {
|
||
let state = state.read().await;
|
||
let status = &state.node_status;
|
||
HttpResponse::Ok().json(ApiResponse::ok(serde_json::json!({
|
||
"version": env!("CARGO_PKG_VERSION"),
|
||
"chain": "NAC",
|
||
"network": status.network_name,
|
||
"block_height": status.block_height,
|
||
"connected_peers": status.connected_peers,
|
||
"syncing": status.is_syncing,
|
||
"uptime_seconds": chrono::Utc::now().timestamp() as u64 - state.start_time,
|
||
"nac_lens_endpoint": state.config.nac_lens_endpoint,
|
||
"cbpp_epoch": status.current_epoch,
|
||
})))
|
||
}
|
||
|
||
/// GET /api/v1/node/info - 节点详细信息
|
||
async fn get_node_info(state: web::Data<SharedState>) -> HttpResponse {
|
||
let state = state.read().await;
|
||
HttpResponse::Ok().json(ApiResponse::ok(serde_json::json!({
|
||
"node_id": state.node_status.node_id,
|
||
"node_type": state.config.node_type,
|
||
"cbpp_role": state.node_status.cbpp_role,
|
||
"csnp_address": state.config.csnp_listen_addr,
|
||
"nac_lens_version": "4.0",
|
||
"nvm_version": "1.0",
|
||
"charter_version": "1.0",
|
||
"cnnl_version": "1.0",
|
||
"jurisdictions": state.node_status.active_jurisdictions,
|
||
})))
|
||
}
|
||
|
||
/// GET /api/v1/wallet/balance/:address - 查询余额
|
||
async fn get_balance(
|
||
path: web::Path<String>,
|
||
state: web::Data<SharedState>,
|
||
) -> HttpResponse {
|
||
let address = path.into_inner();
|
||
let state = state.read().await;
|
||
|
||
// 通过 NAC_lens 查询余额
|
||
let endpoint = format!("{}/balance/{}", state.config.nac_lens_endpoint, address);
|
||
match reqwest::get(&endpoint).await {
|
||
Ok(resp) => {
|
||
match resp.json::<serde_json::Value>().await {
|
||
Ok(data) => HttpResponse::Ok().json(ApiResponse::ok(data)),
|
||
Err(e) => HttpResponse::InternalServerError()
|
||
.json(ApiResponse::<()>::err(&e.to_string())),
|
||
}
|
||
}
|
||
Err(_) => {
|
||
// NAC_lens 不可用时返回模拟数据(开发模式)
|
||
if state.config.dev_mode {
|
||
HttpResponse::Ok().json(ApiResponse::ok(serde_json::json!({
|
||
"address": address,
|
||
"xtzh_balance": "0",
|
||
"xic_balance": "0",
|
||
"nac_balance": "0",
|
||
"note": "dev_mode: NAC_lens not connected"
|
||
})))
|
||
} else {
|
||
HttpResponse::ServiceUnavailable()
|
||
.json(ApiResponse::<()>::err("NAC_lens endpoint unreachable"))
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/// POST /api/v1/contract/compile - 编译 Charter 合约
|
||
async fn compile_contract(
|
||
body: web::Json<serde_json::Value>,
|
||
state: web::Data<SharedState>,
|
||
) -> HttpResponse {
|
||
let state = state.read().await;
|
||
let source = match body.get("source").and_then(|s| s.as_str()) {
|
||
Some(s) => s.to_string(),
|
||
None => return HttpResponse::BadRequest()
|
||
.json(ApiResponse::<()>::err("缺少 source 字段")),
|
||
};
|
||
|
||
// 调用 CNNL 编译服务
|
||
let cnnl_endpoint = format!("{}/api/v1/compile", state.config.cnnl_service_endpoint);
|
||
let client = reqwest::Client::new();
|
||
match client.post(&cnnl_endpoint)
|
||
.json(&serde_json::json!({ "source": source }))
|
||
.send()
|
||
.await
|
||
{
|
||
Ok(resp) => {
|
||
match resp.json::<serde_json::Value>().await {
|
||
Ok(data) => HttpResponse::Ok().json(ApiResponse::ok(data)),
|
||
Err(e) => HttpResponse::InternalServerError()
|
||
.json(ApiResponse::<()>::err(&e.to_string())),
|
||
}
|
||
}
|
||
Err(_) => HttpResponse::ServiceUnavailable()
|
||
.json(ApiResponse::<()>::err("CNNL 编译服务不可用")),
|
||
}
|
||
}
|
||
|
||
/// POST /api/v1/contract/validate - 验证 Charter 合约语法
|
||
async fn validate_contract(
|
||
body: web::Json<serde_json::Value>,
|
||
state: web::Data<SharedState>,
|
||
) -> HttpResponse {
|
||
let state = state.read().await;
|
||
let source = match body.get("source").and_then(|s| s.as_str()) {
|
||
Some(s) => s.to_string(),
|
||
None => return HttpResponse::BadRequest()
|
||
.json(ApiResponse::<()>::err("缺少 source 字段")),
|
||
};
|
||
|
||
let cnnl_endpoint = format!("{}/api/v1/validate", state.config.cnnl_service_endpoint);
|
||
let client = reqwest::Client::new();
|
||
match client.post(&cnnl_endpoint)
|
||
.json(&serde_json::json!({ "source": source }))
|
||
.send()
|
||
.await
|
||
{
|
||
Ok(resp) => {
|
||
match resp.json::<serde_json::Value>().await {
|
||
Ok(data) => HttpResponse::Ok().json(ApiResponse::ok(data)),
|
||
Err(e) => HttpResponse::InternalServerError()
|
||
.json(ApiResponse::<()>::err(&e.to_string())),
|
||
}
|
||
}
|
||
Err(_) => HttpResponse::ServiceUnavailable()
|
||
.json(ApiResponse::<()>::err("CNNL 验证服务不可用")),
|
||
}
|
||
}
|
||
|
||
/// GET /api/v1/network/peers - 获取连接的节点列表
|
||
async fn get_peers(state: web::Data<SharedState>) -> HttpResponse {
|
||
let state = state.read().await;
|
||
HttpResponse::Ok().json(ApiResponse::ok(serde_json::json!({
|
||
"peers": state.node_status.peers,
|
||
"total": state.node_status.connected_peers,
|
||
})))
|
||
}
|
||
|
||
/// GET /api/v1/constitution/clauses - 获取当前宪法条款
|
||
async fn get_constitution(state: web::Data<SharedState>) -> HttpResponse {
|
||
let state = state.read().await;
|
||
HttpResponse::Ok().json(ApiResponse::ok(serde_json::json!({
|
||
"version": state.node_status.constitution_version,
|
||
"clause_count": 43,
|
||
"last_updated_epoch": state.node_status.current_epoch,
|
||
"state_hash": state.node_status.constitution_hash,
|
||
})))
|
||
}
|
||
|
||
/// GET /api/v1/health - 健康检查
|
||
async fn health_check() -> HttpResponse {
|
||
HttpResponse::Ok().json(serde_json::json!({
|
||
"status": "ok",
|
||
"service": "nac-daemon",
|
||
"version": env!("CARGO_PKG_VERSION"),
|
||
}))
|
||
}
|
||
|
||
// ============================================================
|
||
// 主函数
|
||
// ============================================================
|
||
|
||
#[tokio::main]
|
||
async fn main() -> anyhow::Result<()> {
|
||
tracing_subscriber::fmt::init();
|
||
|
||
// 加载配置
|
||
let config = DaemonConfig::load()?;
|
||
let listen_addr = config.listen_addr.clone();
|
||
let state = Arc::new(RwLock::new(DaemonState::new(config)));
|
||
|
||
info!("NAC 守护进程启动中...");
|
||
info!("监听地址: {}", listen_addr);
|
||
info!("NAC_lens 端点: {}", state.read().await.config.nac_lens_endpoint);
|
||
|
||
HttpServer::new(move || {
|
||
App::new()
|
||
.app_data(web::Data::new(state.clone()))
|
||
// 状态和节点信息
|
||
.route("/api/v1/status", web::get().to(get_status))
|
||
.route("/api/v1/node/info", web::get().to(get_node_info))
|
||
// 钱包
|
||
.route("/api/v1/wallet/balance/{address}", web::get().to(get_balance))
|
||
// 合约
|
||
.route("/api/v1/contract/compile", web::post().to(compile_contract))
|
||
.route("/api/v1/contract/validate", web::post().to(validate_contract))
|
||
// 网络
|
||
.route("/api/v1/network/peers", web::get().to(get_peers))
|
||
// 宪法
|
||
.route("/api/v1/constitution/clauses", web::get().to(get_constitution))
|
||
// 健康检查
|
||
.route("/api/v1/health", web::get().to(health_check))
|
||
})
|
||
.bind(&listen_addr)?
|
||
.run()
|
||
.await?;
|
||
|
||
Ok(())
|
||
}
|