396 lines
11 KiB
Rust
396 lines
11 KiB
Rust
// NAC CEE Client - 宪法执行引擎客户端
|
||
// 用于请求和验证宪法收据(Constitutional Receipt)
|
||
|
||
use serde::{Deserialize, Serialize};
|
||
use std::time::Duration;
|
||
use crate::constitutional_receipt::ConstitutionalReceipt;
|
||
|
||
/// CEE错误类型
|
||
#[derive(Debug, thiserror::Error)]
|
||
pub enum CeeError {
|
||
#[error("网络错误: {0}")]
|
||
Network(String),
|
||
|
||
#[error("JSON解析错误: {0}")]
|
||
JsonParse(String),
|
||
|
||
#[error("CEE错误: {0}")]
|
||
Cee(String),
|
||
|
||
#[error("验证失败: {0}")]
|
||
Validation(String),
|
||
|
||
#[error("超时")]
|
||
Timeout,
|
||
|
||
#[error("无效响应")]
|
||
InvalidResponse,
|
||
}
|
||
|
||
/// CR请求
|
||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||
pub struct CrRequest {
|
||
/// 交易数据(十六进制)
|
||
pub transaction_data: String,
|
||
|
||
/// 发送者地址
|
||
pub from: String,
|
||
|
||
/// 接收者地址
|
||
pub to: String,
|
||
|
||
/// 金额
|
||
pub value: String,
|
||
|
||
/// 资产ID
|
||
pub asset_id: String,
|
||
|
||
/// 资产GNACS编码
|
||
pub gnacs_code: Option<String>,
|
||
|
||
/// KYC凭证哈希
|
||
pub kyc_credential_hash: Option<String>,
|
||
|
||
/// 额外元数据
|
||
pub metadata: std::collections::HashMap<String, String>,
|
||
}
|
||
|
||
/// CR响应
|
||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||
pub struct CrResponse {
|
||
/// 宪法收据
|
||
pub receipt: ConstitutionalReceipt,
|
||
|
||
/// CEE节点ID
|
||
pub cee_node_id: String,
|
||
|
||
/// 响应时间戳
|
||
pub timestamp: u64,
|
||
}
|
||
|
||
/// 宪法哈希响应
|
||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||
pub struct ConstitutionalHashResponse {
|
||
/// 当前宪法哈希
|
||
pub hash: String,
|
||
|
||
/// 宪法版本
|
||
pub version: u64,
|
||
|
||
/// 生效时间
|
||
pub effective_time: u64,
|
||
}
|
||
|
||
/// 健康检查响应
|
||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||
pub struct HealthResponse {
|
||
/// 状态
|
||
pub status: String,
|
||
|
||
/// CEE节点ID
|
||
pub node_id: String,
|
||
|
||
/// 版本
|
||
pub version: String,
|
||
|
||
/// 当前负载
|
||
pub load: f64,
|
||
}
|
||
|
||
/// NAC CEE客户端
|
||
pub struct NacCeeClient {
|
||
url: String,
|
||
client: reqwest::Client,
|
||
}
|
||
|
||
impl NacCeeClient {
|
||
/// 创建新的CEE客户端
|
||
pub fn new(url: impl Into<String>) -> Self {
|
||
let client = reqwest::Client::builder()
|
||
.timeout(Duration::from_secs(30))
|
||
.build()
|
||
.expect("Failed to create HTTP client");
|
||
|
||
Self {
|
||
url: url.into(),
|
||
client,
|
||
}
|
||
}
|
||
|
||
/// 请求宪法收据
|
||
pub async fn request_cr(&self, request: &CrRequest) -> Result<ConstitutionalReceipt, CeeError> {
|
||
let url = format!("{}/api/v1/cr/request", self.url);
|
||
|
||
let response = self.client
|
||
.post(&url)
|
||
.json(request)
|
||
.send()
|
||
.await
|
||
.map_err(|e| CeeError::Network(e.to_string()))?;
|
||
|
||
if !response.status().is_success() {
|
||
let error_text = response.text().await.unwrap_or_else(|_| "Unknown error".to_string());
|
||
return Err(CeeError::Cee(error_text));
|
||
}
|
||
|
||
let cr_response: CrResponse = response
|
||
.json()
|
||
.await
|
||
.map_err(|e| CeeError::JsonParse(e.to_string()))?;
|
||
|
||
Ok(cr_response.receipt)
|
||
}
|
||
|
||
/// 验证宪法收据
|
||
pub async fn verify_cr(&self, cr: &ConstitutionalReceipt) -> Result<bool, CeeError> {
|
||
let url = format!("{}/api/v1/cr/verify", self.url);
|
||
|
||
let response = self.client
|
||
.post(&url)
|
||
.json(cr)
|
||
.send()
|
||
.await
|
||
.map_err(|e| CeeError::Network(e.to_string()))?;
|
||
|
||
if !response.status().is_success() {
|
||
return Ok(false);
|
||
}
|
||
|
||
#[derive(Deserialize)]
|
||
struct VerifyResponse {
|
||
valid: bool,
|
||
}
|
||
|
||
let verify_response: VerifyResponse = response
|
||
.json()
|
||
.await
|
||
.map_err(|e| CeeError::JsonParse(e.to_string()))?;
|
||
|
||
Ok(verify_response.valid)
|
||
}
|
||
|
||
/// 获取当前宪法哈希
|
||
pub async fn get_constitutional_hash(&self) -> Result<ConstitutionalHashResponse, CeeError> {
|
||
let url = format!("{}/api/v1/constitution/hash", self.url);
|
||
|
||
let response = self.client
|
||
.get(&url)
|
||
.send()
|
||
.await
|
||
.map_err(|e| CeeError::Network(e.to_string()))?;
|
||
|
||
if !response.status().is_success() {
|
||
let error_text = response.text().await.unwrap_or_else(|_| "Unknown error".to_string());
|
||
return Err(CeeError::Cee(error_text));
|
||
}
|
||
|
||
let hash_response: ConstitutionalHashResponse = response
|
||
.json()
|
||
.await
|
||
.map_err(|e| CeeError::JsonParse(e.to_string()))?;
|
||
|
||
Ok(hash_response)
|
||
}
|
||
|
||
/// 健康检查
|
||
pub async fn health_check(&self) -> Result<HealthResponse, CeeError> {
|
||
let url = format!("{}/api/v1/health", self.url);
|
||
|
||
let response = self.client
|
||
.get(&url)
|
||
.send()
|
||
.await
|
||
.map_err(|e| CeeError::Network(e.to_string()))?;
|
||
|
||
if !response.status().is_success() {
|
||
return Err(CeeError::Cee("Health check failed".to_string()));
|
||
}
|
||
|
||
let health_response: HealthResponse = response
|
||
.json()
|
||
.await
|
||
.map_err(|e| CeeError::JsonParse(e.to_string()))?;
|
||
|
||
Ok(health_response)
|
||
}
|
||
|
||
/// 本地验证宪法收据
|
||
pub fn validate_cr_locally(&self, cr: &ConstitutionalReceipt) -> Result<(), CeeError> {
|
||
// 1. 检查有效期
|
||
let now = std::time::SystemTime::now()
|
||
.duration_since(std::time::UNIX_EPOCH)
|
||
.unwrap()
|
||
.as_secs();
|
||
|
||
if now < cr.timestamp {
|
||
return Err(CeeError::Validation("CR时间戳在未来".to_string()));
|
||
}
|
||
|
||
if now > cr.timestamp + cr.validity_window {
|
||
return Err(CeeError::Validation("CR已过期".to_string()));
|
||
}
|
||
|
||
// 2. 检查签名数量
|
||
if cr.signatures.is_empty() {
|
||
return Err(CeeError::Validation("CR缺少签名".to_string()));
|
||
}
|
||
|
||
// 3. 检查receipt_id不为空
|
||
if cr.receipt_id == [0u8; 32] {
|
||
return Err(CeeError::Validation("CR ID无效".to_string()));
|
||
}
|
||
|
||
Ok(())
|
||
}
|
||
}
|
||
|
||
/// 多CEE节点管理器
|
||
pub struct CeeNodeManager {
|
||
nodes: Vec<NacCeeClient>,
|
||
current_index: std::sync::atomic::AtomicUsize,
|
||
}
|
||
|
||
impl CeeNodeManager {
|
||
/// 创建新的CEE节点管理器
|
||
pub fn new(urls: Vec<String>) -> Self {
|
||
let nodes = urls.into_iter()
|
||
.map(|url| NacCeeClient::new(url))
|
||
.collect();
|
||
|
||
Self {
|
||
nodes,
|
||
current_index: std::sync::atomic::AtomicUsize::new(0),
|
||
}
|
||
}
|
||
|
||
/// 获取下一个节点
|
||
fn next_node(&self) -> &NacCeeClient {
|
||
let index = self.current_index.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
|
||
&self.nodes[index % self.nodes.len()]
|
||
}
|
||
|
||
/// 请求CR(带重试和负载均衡)
|
||
pub async fn request_cr(&self, request: &CrRequest) -> Result<ConstitutionalReceipt, CeeError> {
|
||
let mut last_error = None;
|
||
|
||
// 尝试所有节点
|
||
for _ in 0..self.nodes.len() {
|
||
let node = self.next_node();
|
||
|
||
match node.request_cr(request).await {
|
||
Ok(cr) => return Ok(cr),
|
||
Err(e) => {
|
||
eprintln!("CEE节点请求失败: {}", e);
|
||
last_error = Some(e);
|
||
}
|
||
}
|
||
}
|
||
|
||
Err(last_error.unwrap_or(CeeError::Cee("所有CEE节点都不可用".to_string())))
|
||
}
|
||
|
||
/// 验证CR
|
||
pub async fn verify_cr(&self, cr: &ConstitutionalReceipt) -> Result<bool, CeeError> {
|
||
// 先本地验证
|
||
self.nodes[0].validate_cr_locally(cr)?;
|
||
|
||
// 再远程验证
|
||
let node = self.next_node();
|
||
node.verify_cr(cr).await
|
||
}
|
||
|
||
/// 获取宪法哈希
|
||
pub async fn get_constitutional_hash(&self) -> Result<ConstitutionalHashResponse, CeeError> {
|
||
let node = self.next_node();
|
||
node.get_constitutional_hash().await
|
||
}
|
||
|
||
/// 健康检查所有节点
|
||
pub async fn health_check_all(&self) -> Vec<Result<HealthResponse, CeeError>> {
|
||
let mut results = Vec::new();
|
||
|
||
for node in &self.nodes {
|
||
results.push(node.health_check().await);
|
||
}
|
||
|
||
results
|
||
}
|
||
}
|
||
|
||
#[cfg(test)]
|
||
mod tests {
|
||
use super::*;
|
||
|
||
#[test]
|
||
fn test_cee_client_creation() {
|
||
let client = NacCeeClient::new("https://cee.newassetchain.com");
|
||
assert_eq!(client.url, "https://cee.newassetchain.com");
|
||
}
|
||
|
||
#[test]
|
||
fn test_node_manager_creation() {
|
||
let manager = CeeNodeManager::new(vec![
|
||
"https://cee1.newassetchain.com".to_string(),
|
||
"https://cee2.newassetchain.com".to_string(),
|
||
]);
|
||
assert_eq!(manager.nodes.len(), 2);
|
||
}
|
||
|
||
#[test]
|
||
fn test_node_manager_round_robin() {
|
||
let manager = CeeNodeManager::new(vec![
|
||
"https://cee1.newassetchain.com".to_string(),
|
||
"https://cee2.newassetchain.com".to_string(),
|
||
]);
|
||
|
||
let node1 = manager.next_node();
|
||
let node2 = manager.next_node();
|
||
let node3 = manager.next_node();
|
||
|
||
assert_eq!(node1.url, "https://cee1.newassetchain.com");
|
||
assert_eq!(node2.url, "https://cee2.newassetchain.com");
|
||
assert_eq!(node3.url, "https://cee1.newassetchain.com");
|
||
}
|
||
|
||
#[test]
|
||
fn test_validate_cr_locally() {
|
||
let client = NacCeeClient::new("https://cee.newassetchain.com");
|
||
|
||
let now = std::time::SystemTime::now()
|
||
.duration_since(std::time::UNIX_EPOCH)
|
||
.unwrap()
|
||
.as_secs();
|
||
|
||
let cr = ConstitutionalReceipt {
|
||
transaction_hash: [1u8; 32],
|
||
constitutional_hash: [2u8; 32],
|
||
clause_mask: 0xFF,
|
||
execution_result_hash: [3u8; 32],
|
||
timestamp: now,
|
||
validity_window: 3600,
|
||
signatures: vec![vec![4u8; 64]],
|
||
receipt_id: [5u8; 32],
|
||
};
|
||
|
||
assert!(client.validate_cr_locally(&cr).is_ok());
|
||
}
|
||
|
||
#[test]
|
||
fn test_validate_cr_expired() {
|
||
let client = NacCeeClient::new("https://cee.newassetchain.com");
|
||
|
||
let cr = ConstitutionalReceipt {
|
||
transaction_hash: [1u8; 32],
|
||
constitutional_hash: [2u8; 32],
|
||
clause_mask: 0xFF,
|
||
execution_result_hash: [3u8; 32],
|
||
timestamp: 1000000, // 很久以前
|
||
validity_window: 3600,
|
||
signatures: vec![vec![4u8; 64]],
|
||
receipt_id: [5u8; 32],
|
||
};
|
||
|
||
assert!(client.validate_cr_locally(&cr).is_err());
|
||
}
|
||
}
|