NAC_Blockchain/nac-wallet-core/src/cee_client.rs

396 lines
11 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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());
}
}