Compare commits
2 Commits
Author | SHA1 | Date |
---|---|---|
|
e0f77b4b91 | |
|
58d4635c8e |
|
@ -20,6 +20,10 @@ minotari_app_utilities = { path = "../applications/minotari_app_utilities", feat
|
||||||
minotari_app_grpc = { path = "../applications/minotari_app_grpc" }
|
minotari_app_grpc = { path = "../applications/minotari_app_grpc" }
|
||||||
tari_utilities = { version = "0.8" }
|
tari_utilities = { version = "0.8" }
|
||||||
jmt = { version = "0.11.0", features = ["mocks"] }
|
jmt = { version = "0.11.0", features = ["mocks"] }
|
||||||
|
tari_hashing = { path = "../hashing" }
|
||||||
|
tari_crypto = { version = "0.18" }
|
||||||
|
blake2 = "0.10"
|
||||||
|
digest = "0.10"
|
||||||
|
|
||||||
base64 = "0.13.0"
|
base64 = "0.13.0"
|
||||||
borsh = "1.5.7"
|
borsh = "1.5.7"
|
||||||
|
|
|
@ -1,290 +0,0 @@
|
||||||
# GBT项目修改实现总结
|
|
||||||
|
|
||||||
## 概述
|
|
||||||
|
|
||||||
根据您的要求,对GBT项目进行了全面的重构和功能增强,实现了以下核心功能:
|
|
||||||
|
|
||||||
1. **HTTP API集成**:通过HTTP请求获取上一个区块的`output_smt_size`
|
|
||||||
2. **任务ID生成**:生成16位16进制任务ID
|
|
||||||
3. **简化的output_smt_size计算**:使用`outputs长度 - inputs长度`的差值计算
|
|
||||||
4. **新的数据结构**:重新设计了MiningTask、MiningMsg等结构
|
|
||||||
5. **ZMQ通信优化**:改进了消息格式和通信流程
|
|
||||||
|
|
||||||
## 主要修改
|
|
||||||
|
|
||||||
### 1. 依赖项更新 (`Cargo.toml`)
|
|
||||||
|
|
||||||
新增依赖:
|
|
||||||
- `reqwest = { version = "0.11", features = ["json"] }` - HTTP客户端
|
|
||||||
- `uuid = { version = "1.0", features = ["v4"] }` - 任务ID生成
|
|
||||||
|
|
||||||
### 2. 数据结构重新设计
|
|
||||||
|
|
||||||
#### 原结构 → 新结构
|
|
||||||
|
|
||||||
**MiningTask**:
|
|
||||||
```rust
|
|
||||||
// 旧结构
|
|
||||||
pub struct MiningTask {
|
|
||||||
pub coinbase_hash: String,
|
|
||||||
pub height: u64,
|
|
||||||
pub target: u64,
|
|
||||||
pub output_smt_size: u64,
|
|
||||||
pub block_template: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
// 新结构
|
|
||||||
pub struct MiningTask {
|
|
||||||
pub job_id: String, // 新增:16位16进制任务ID
|
|
||||||
pub block_header: BlockHeader, // 新增:完整的区块头
|
|
||||||
pub block_body: BlockBody, // 新增:完整的区块体
|
|
||||||
pub output_smt_size: u64, // 保留:当前output_smt_size
|
|
||||||
pub coinbase_hash: String, // 保留:coinbase哈希
|
|
||||||
pub target: u64, // 保留:目标难度(从gRPC获取)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**新增结构**:
|
|
||||||
```rust
|
|
||||||
// 区块头结构
|
|
||||||
pub struct BlockHeader {
|
|
||||||
pub hash: String,
|
|
||||||
pub version: u32,
|
|
||||||
pub height: u64,
|
|
||||||
pub prev_hash: String,
|
|
||||||
pub timestamp: u64,
|
|
||||||
pub output_mr: String,
|
|
||||||
pub block_output_mr: String,
|
|
||||||
pub kernel_mr: String,
|
|
||||||
pub input_mr: String,
|
|
||||||
pub total_kernel_offset: String,
|
|
||||||
pub nonce: u64, // 初始为0,等待后续修改
|
|
||||||
pub pow: ProofOfWork,
|
|
||||||
pub kernel_mmr_size: u64,
|
|
||||||
pub output_mmr_size: u64,
|
|
||||||
pub total_script_offset: String,
|
|
||||||
pub validator_node_mr: String,
|
|
||||||
pub validator_node_size: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
// 区块体结构
|
|
||||||
pub struct BlockBody {
|
|
||||||
pub inputs: Vec<String>,
|
|
||||||
pub outputs: Vec<String>,
|
|
||||||
pub kernels: Vec<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
// 挖矿消息结构
|
|
||||||
pub struct MiningMsg {
|
|
||||||
pub job_id: String,
|
|
||||||
pub block_header: BlockHeader,
|
|
||||||
pub output_smt_size: u64,
|
|
||||||
pub coinbase_hash: String,
|
|
||||||
pub target: u64, // 新增:目标难度
|
|
||||||
}
|
|
||||||
|
|
||||||
// 提交请求结构
|
|
||||||
pub struct SubmitRequest {
|
|
||||||
pub job_id: String, // 改为任务ID
|
|
||||||
pub nonce: u64,
|
|
||||||
pub solution: String,
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. 核心功能实现
|
|
||||||
|
|
||||||
#### 3.1 HTTP API集成
|
|
||||||
|
|
||||||
```rust
|
|
||||||
// 通过HTTP获取上一个区块的output_smt_size
|
|
||||||
async fn get_prev_output_smt_size(&self, height: u64) -> Result<u64> {
|
|
||||||
let prev_height = height - 1;
|
|
||||||
|
|
||||||
// 检查缓存
|
|
||||||
{
|
|
||||||
let cache = self.prev_output_smt_size_cache.lock().await;
|
|
||||||
if let Some(&size) = cache.get(&prev_height) {
|
|
||||||
return Ok(size);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 通过HTTP请求获取
|
|
||||||
let url = format!("http://{}/get_header_by_height?height={}",
|
|
||||||
self.config.base_node_http_address, prev_height);
|
|
||||||
|
|
||||||
let response = self.http_client.get(&url).send().await?;
|
|
||||||
let header_data: serde_json::Value = response.json().await?;
|
|
||||||
|
|
||||||
let output_smt_size = header_data["output_smt_size"]
|
|
||||||
.as_u64()
|
|
||||||
.ok_or_else(|| anyhow!("Missing output_smt_size in response"))?;
|
|
||||||
|
|
||||||
// 更新缓存
|
|
||||||
{
|
|
||||||
let mut cache = self.prev_output_smt_size_cache.lock().await;
|
|
||||||
cache.insert(prev_height, output_smt_size);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(output_smt_size)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 3.2 任务ID生成
|
|
||||||
|
|
||||||
```rust
|
|
||||||
// 生成16位16进制任务ID
|
|
||||||
fn generate_job_id() -> String {
|
|
||||||
let uuid = Uuid::new_v4();
|
|
||||||
uuid.to_string().replace("-", "")[..16].to_string()
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 3.3 简化的output_smt_size计算
|
|
||||||
|
|
||||||
```rust
|
|
||||||
// 计算当前output_smt_size
|
|
||||||
let current_output_smt_size = prev_output_smt_size +
|
|
||||||
block_body.outputs.len() as u64 -
|
|
||||||
block_body.inputs.len() as u64;
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 3.4 ZMQ消息格式
|
|
||||||
|
|
||||||
**发送挖矿消息**:
|
|
||||||
```rust
|
|
||||||
let mining_msg = MiningMsg {
|
|
||||||
job_id: mining_task.job_id.clone(),
|
|
||||||
block_header: mining_task.block_header.clone(),
|
|
||||||
output_smt_size: mining_task.output_smt_size,
|
|
||||||
coinbase_hash: mining_task.coinbase_hash.clone(),
|
|
||||||
target: mining_task.target,
|
|
||||||
};
|
|
||||||
|
|
||||||
let msg_json = serde_json::to_string(&mining_msg)?;
|
|
||||||
self.publisher_socket.send_multipart(&["mining_msg".as_bytes(), msg_json.as_bytes()], 0)?;
|
|
||||||
```
|
|
||||||
|
|
||||||
**接收提交请求**:
|
|
||||||
```rust
|
|
||||||
// 消息格式: "submit {json_data}"
|
|
||||||
let submit_json = &message_str[7..]; // 去掉"submit "前缀
|
|
||||||
let submit_request: SubmitRequest = serde_json::from_str(submit_json)?;
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. 工作流程
|
|
||||||
|
|
||||||
#### 4.1 获取区块模板流程
|
|
||||||
|
|
||||||
1. **获取区块模板**:通过gRPC从BaseNode获取
|
|
||||||
2. **生成coinbase**:自动生成coinbase交易
|
|
||||||
3. **获取target**:从`miner_data.target_difficulty`获取目标难度
|
|
||||||
4. **HTTP请求**:获取上一个区块的`output_smt_size`
|
|
||||||
5. **计算output_smt_size**:`当前 = 上一个 + outputs长度 - inputs长度`
|
|
||||||
6. **生成任务ID**:16位16进制字符串
|
|
||||||
7. **构造MiningTask**:包含完整信息(包括target)
|
|
||||||
8. **发送ZMQ消息**:发布挖矿任务
|
|
||||||
|
|
||||||
#### 4.2 提交处理流程
|
|
||||||
|
|
||||||
1. **接收ZMQ消息**:监听submit主题
|
|
||||||
2. **验证任务ID**:查找对应的MiningTask
|
|
||||||
3. **构造区块**:使用提交的nonce和solution
|
|
||||||
4. **提交到BaseNode**:通过gRPC提交
|
|
||||||
5. **返回结果**:发送submit_result消息
|
|
||||||
|
|
||||||
### 5. 配置参数
|
|
||||||
|
|
||||||
新增配置:
|
|
||||||
- `--base-node-http`:BaseNode HTTP地址(默认:127.0.0.1:9000)
|
|
||||||
|
|
||||||
### 6. 缓存机制
|
|
||||||
|
|
||||||
- **任务缓存**:按height保存,最多3个区块
|
|
||||||
- **output_smt_size缓存**:避免重复HTTP请求
|
|
||||||
|
|
||||||
## 测试工具
|
|
||||||
|
|
||||||
### Python测试脚本 (`test_gbt.py`)
|
|
||||||
|
|
||||||
功能:
|
|
||||||
- 模拟ZMQ客户端
|
|
||||||
- 接收挖矿任务
|
|
||||||
- 自动提交模拟结果
|
|
||||||
- 统计信息显示
|
|
||||||
|
|
||||||
使用方法:
|
|
||||||
```bash
|
|
||||||
python3 test_gbt.py
|
|
||||||
```
|
|
||||||
|
|
||||||
## 构建脚本
|
|
||||||
|
|
||||||
### Linux/macOS (`build.sh`)
|
|
||||||
- 检查Rust环境
|
|
||||||
- 检查系统依赖
|
|
||||||
- 自动构建release版本
|
|
||||||
|
|
||||||
### Windows (`build.bat`)
|
|
||||||
- 检查Rust环境
|
|
||||||
- 检查Windows依赖
|
|
||||||
- 自动构建release版本
|
|
||||||
|
|
||||||
## 使用示例
|
|
||||||
|
|
||||||
### 基本运行
|
|
||||||
```bash
|
|
||||||
./target/release/gbt
|
|
||||||
```
|
|
||||||
|
|
||||||
### 自定义配置
|
|
||||||
```bash
|
|
||||||
./target/release/gbt \
|
|
||||||
--base-node 127.0.0.1:18102 \
|
|
||||||
--base-node-http 127.0.0.1:9000 \
|
|
||||||
--network mainnet \
|
|
||||||
--wallet-address YOUR_WALLET_ADDRESS \
|
|
||||||
--coinbase-extra "your_pool_name"
|
|
||||||
```
|
|
||||||
|
|
||||||
## 消息格式示例
|
|
||||||
|
|
||||||
### 挖矿任务消息
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"job_id": "a1b2c3d4e5f67890",
|
|
||||||
"block_header": {
|
|
||||||
"hash": "hash_here",
|
|
||||||
"height": 12345,
|
|
||||||
"nonce": 0,
|
|
||||||
// ... 其他字段
|
|
||||||
},
|
|
||||||
"output_smt_size": 1000,
|
|
||||||
"coinbase_hash": "coinbase_hash_here",
|
|
||||||
"target": 1000000
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 提交请求消息
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"job_id": "a1b2c3d4e5f67890",
|
|
||||||
"nonce": 123456789,
|
|
||||||
"solution": "solution_hash_here"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 总结
|
|
||||||
|
|
||||||
本次修改完全实现了您要求的功能:
|
|
||||||
|
|
||||||
1. ✅ **HTTP API集成**:通过HTTP获取上一个区块的output_smt_size
|
|
||||||
2. ✅ **任务ID生成**:16位16进制随机ID
|
|
||||||
3. ✅ **简化的计算**:output_smt_size = 上一个 + outputs - inputs
|
|
||||||
4. ✅ **新的数据结构**:完整的MiningTask、MiningMsg等
|
|
||||||
5. ✅ **ZMQ通信**:优化的消息格式和流程
|
|
||||||
6. ✅ **任务管理**:基于job_id的任务跟踪
|
|
||||||
7. ✅ **缓存机制**:避免重复HTTP请求
|
|
||||||
8. ✅ **测试工具**:Python测试脚本
|
|
||||||
9. ✅ **构建脚本**:跨平台构建支持
|
|
||||||
|
|
||||||
所有功能都已实现并通过测试,可以直接使用。
|
|
141
README.md
141
README.md
|
@ -4,81 +4,101 @@
|
||||||
|
|
||||||
## 功能特性
|
## 功能特性
|
||||||
|
|
||||||
### 1. 区块模板获取
|
|
||||||
- 通过gRPC从BaseNode获取区块模板
|
- 通过gRPC从BaseNode获取区块模板
|
||||||
- 自动生成coinbase交易
|
- 自动生成coinbase交易
|
||||||
- 支持多种挖矿算法(SHA3X、RandomXM、RandomXT)
|
- 支持SHA3X挖矿算法
|
||||||
|
- ZMQ推送标准JSON格式的挖矿任务
|
||||||
### 2. HTTP API集成
|
- 每秒获取一次模板,高度变立即推送,否则每5秒推送一次最新模板
|
||||||
- 通过HTTP请求获取上一个区块的`output_smt_size`
|
|
||||||
- 缓存机制避免重复请求
|
|
||||||
- 自动计算当前区块的`output_smt_size`
|
|
||||||
|
|
||||||
### 3. 任务管理
|
|
||||||
- 生成16位16进制任务ID
|
|
||||||
- 任务缓存管理(最多保存3个区块)
|
|
||||||
- 支持任务状态跟踪
|
|
||||||
|
|
||||||
### 4. ZMQ通信
|
|
||||||
- 发布挖矿任务消息
|
|
||||||
- 接收矿工提交结果
|
|
||||||
- 支持多种消息类型
|
|
||||||
|
|
||||||
## 数据结构
|
## 数据结构
|
||||||
|
|
||||||
|
### BlockHeader(区块头)
|
||||||
|
```rust
|
||||||
|
pub struct BlockHeader {
|
||||||
|
pub hash: String,
|
||||||
|
pub version: u32,
|
||||||
|
pub height: u64,
|
||||||
|
pub prev_hash: String,
|
||||||
|
pub timestamp: u64,
|
||||||
|
pub output_mr: String,
|
||||||
|
pub block_output_mr: String,
|
||||||
|
pub kernel_mr: String,
|
||||||
|
pub input_mr: String,
|
||||||
|
pub total_kernel_offset: String,
|
||||||
|
pub nonce: u64,
|
||||||
|
pub pow: ProofOfWork,
|
||||||
|
pub kernel_mmr_size: u64,
|
||||||
|
pub output_mmr_size: u64,
|
||||||
|
pub total_script_offset: String,
|
||||||
|
pub validator_node_mr: String,
|
||||||
|
pub validator_node_size: u64,
|
||||||
|
pub output_smt_size: u64,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### ProofOfWork
|
||||||
|
```rust
|
||||||
|
pub struct ProofOfWork {
|
||||||
|
pub pow_algo: u64,
|
||||||
|
pub pow_data: String,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### BlockBody(区块体)
|
||||||
|
```rust
|
||||||
|
pub struct BlockBody {
|
||||||
|
pub inputs: Vec<TransactionInput>,
|
||||||
|
pub outputs: Vec<TransactionOutput>,
|
||||||
|
pub kernels: Vec<TransactionKernel>,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
### MiningTask(挖矿任务)
|
### MiningTask(挖矿任务)
|
||||||
```rust
|
```rust
|
||||||
pub struct MiningTask {
|
pub struct MiningTask {
|
||||||
pub job_id: String, // 16位16进制任务ID
|
pub block_header: BlockHeader,
|
||||||
pub block_header: BlockHeader, // 区块头信息
|
pub block_body: BlockBody,
|
||||||
pub block_body: BlockBody, // 区块体信息
|
pub output_smt_size: u64,
|
||||||
pub output_smt_size: u64, // 当前output_smt_size
|
pub coinbase_hash: String,
|
||||||
pub coinbase_hash: String, // coinbase哈希
|
pub target: u64,
|
||||||
pub target: u64, // 目标难度
|
pub created_at: u64,
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### MiningMsg(挖矿消息)
|
### MiningMsg(挖矿消息)
|
||||||
```rust
|
```rust
|
||||||
pub struct MiningMsg {
|
pub struct MiningMsg {
|
||||||
pub job_id: String, // 任务ID
|
pub height: u64,
|
||||||
pub block_header: BlockHeader, // 区块头
|
pub mining_hash: String,
|
||||||
pub output_smt_size: u64, // output_smt_size
|
pub target: u64,
|
||||||
pub coinbase_hash: String, // coinbase哈希
|
pub block_header: BlockHeader,
|
||||||
pub target: u64, // 目标难度
|
pub block_body: BlockBody,
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### SubmitRequest(提交请求)
|
### SubmitRequest(提交请求)
|
||||||
```rust
|
```rust
|
||||||
pub struct SubmitRequest {
|
pub struct SubmitRequest {
|
||||||
pub job_id: String, // 任务ID
|
pub job_id: String,
|
||||||
pub nonce: u64, // 挖矿nonce
|
pub nonce: u64,
|
||||||
pub solution: String, // 挖矿解
|
pub solution: String,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### SubmitResult(提交结果)
|
||||||
|
```rust
|
||||||
|
pub struct SubmitResult {
|
||||||
|
pub job_id: String,
|
||||||
|
pub result: u8, // 1表示成功,0表示失败
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## 工作流程
|
## 工作流程
|
||||||
|
|
||||||
### 1. 获取区块模板
|
1. 每秒从BaseNode获取一次区块模板。
|
||||||
1. 通过gRPC获取区块模板
|
2. 若区块高度变化,立即推送新任务;否则每5秒推送一次最新模板。
|
||||||
2. 生成coinbase交易
|
3. 通过ZMQ发布标准JSON格式的MiningMsg消息,block_body为JSON对象。
|
||||||
3. 通过HTTP获取上一个区块的`output_smt_size`
|
4. 任务缓存、job_id、submit相关逻辑已移除,代码结构更简洁。
|
||||||
4. 计算当前`output_smt_size = 上一个output_smt_size + outputs长度 - inputs长度`
|
|
||||||
5. 生成任务ID
|
|
||||||
6. 构造MiningTask实例
|
|
||||||
|
|
||||||
### 2. 发送挖矿任务
|
|
||||||
1. 构造MiningMsg实例
|
|
||||||
2. 通过ZMQ发布消息
|
|
||||||
3. 缓存任务信息
|
|
||||||
|
|
||||||
### 3. 接收提交结果
|
|
||||||
1. 监听ZMQ提交消息
|
|
||||||
2. 验证任务ID
|
|
||||||
3. 构造完整区块
|
|
||||||
4. 提交到BaseNode
|
|
||||||
5. 返回提交结果
|
|
||||||
|
|
||||||
## 配置参数
|
## 配置参数
|
||||||
|
|
||||||
|
@ -89,8 +109,12 @@ pub struct SubmitRequest {
|
||||||
| `--network` | `mainnet` | 网络类型 |
|
| `--network` | `mainnet` | 网络类型 |
|
||||||
| `--wallet-address` | 默认地址 | 钱包地址 |
|
| `--wallet-address` | 默认地址 | 钱包地址 |
|
||||||
| `--coinbase-extra` | `m2pool.com` | Coinbase额外数据 |
|
| `--coinbase-extra` | `m2pool.com` | Coinbase额外数据 |
|
||||||
| `--zmq-pub-port` | `5555` | ZMQ发布端口 |
|
| `--zmq-pub-port` | `31000` | ZMQ发布端口 |
|
||||||
| `--zmq-sub-port` | `5556` | ZMQ订阅端口 |
|
| `--zmq-sub-port` | `31001` | ZMQ订阅端口 |
|
||||||
|
| `--tls` | 关闭 | 启用TLS |
|
||||||
|
| `--tls-domain` | 无 | TLS域名 |
|
||||||
|
| `--tls-ca-cert` | 无 | TLS CA证书文件 |
|
||||||
|
| `--config-dir` | `.` | 配置目录 |
|
||||||
|
|
||||||
## 构建和运行
|
## 构建和运行
|
||||||
|
|
||||||
|
@ -111,8 +135,8 @@ cargo build --release
|
||||||
--network mainnet \
|
--network mainnet \
|
||||||
--wallet-address YOUR_WALLET_ADDRESS \
|
--wallet-address YOUR_WALLET_ADDRESS \
|
||||||
--coinbase-extra "your_pool_name" \
|
--coinbase-extra "your_pool_name" \
|
||||||
--zmq-pub-port 5555 \
|
--zmq-pub-port 31000 \
|
||||||
--zmq-sub-port 5556
|
--zmq-sub-port 31001
|
||||||
```
|
```
|
||||||
|
|
||||||
## ZMQ消息格式
|
## ZMQ消息格式
|
||||||
|
@ -120,7 +144,7 @@ cargo build --release
|
||||||
### 挖矿任务消息
|
### 挖矿任务消息
|
||||||
```
|
```
|
||||||
Topic: "mining_msg"
|
Topic: "mining_msg"
|
||||||
Data: JSON格式的MiningMsg
|
Data: JSON格式的MiningMsg,block_body为标准JSON对象
|
||||||
```
|
```
|
||||||
|
|
||||||
### 提交请求消息
|
### 提交请求消息
|
||||||
|
@ -138,11 +162,11 @@ Data: JSON格式的SubmitResult
|
||||||
## 依赖项
|
## 依赖项
|
||||||
|
|
||||||
- `reqwest`: HTTP客户端
|
- `reqwest`: HTTP客户端
|
||||||
- `uuid`: 任务ID生成
|
|
||||||
- `zmq`: ZMQ通信
|
- `zmq`: ZMQ通信
|
||||||
- `serde`: 序列化/反序列化
|
- `serde`: 序列化/反序列化
|
||||||
- `tokio`: 异步运行时
|
- `tokio`: 异步运行时
|
||||||
- `tonic`: gRPC客户端
|
- `tonic`: gRPC客户端
|
||||||
|
- 其它Tari相关依赖
|
||||||
|
|
||||||
## 注意事项
|
## 注意事项
|
||||||
|
|
||||||
|
@ -154,8 +178,6 @@ Data: JSON格式的SubmitResult
|
||||||
|
|
||||||
## 故障排除
|
## 故障排除
|
||||||
|
|
||||||
### 常见问题
|
|
||||||
|
|
||||||
1. **连接BaseNode失败**
|
1. **连接BaseNode失败**
|
||||||
- 检查BaseNode是否运行
|
- 检查BaseNode是否运行
|
||||||
- 验证gRPC地址和端口
|
- 验证gRPC地址和端口
|
||||||
|
@ -178,5 +200,4 @@ Data: JSON格式的SubmitResult
|
||||||
|
|
||||||
## 开发
|
## 开发
|
||||||
|
|
||||||
### 添加新功能
|
如需扩展功能,请直接修改`src/main.rs`,结构清晰,易于维护。
|
||||||
1. 修改`src/main.rs`
|
|
69
build.bat
69
build.bat
|
@ -1,69 +0,0 @@
|
||||||
@echo off
|
|
||||||
REM GBT构建脚本 (Windows版本)
|
|
||||||
REM 用于编译GBT客户端
|
|
||||||
|
|
||||||
echo === Tari GBT客户端构建脚本 ===
|
|
||||||
echo.
|
|
||||||
|
|
||||||
REM 检查Rust环境
|
|
||||||
where cargo >nul 2>nul
|
|
||||||
if %errorlevel% neq 0 (
|
|
||||||
echo 错误: 未找到cargo,请先安装Rust
|
|
||||||
echo 访问 https://rustup.rs/ 安装Rust
|
|
||||||
pause
|
|
||||||
exit /b 1
|
|
||||||
)
|
|
||||||
|
|
||||||
echo ✓ Rust环境检查通过
|
|
||||||
for /f "tokens=2" %%i in ('cargo --version') do echo Rust版本: %%i
|
|
||||||
|
|
||||||
echo.
|
|
||||||
echo 检查依赖项...
|
|
||||||
|
|
||||||
REM 检查ZMQ (Windows下通常通过vcpkg安装)
|
|
||||||
where zmq >nul 2>nul
|
|
||||||
if %errorlevel% neq 0 (
|
|
||||||
echo 警告: 未找到ZMQ,可能需要安装
|
|
||||||
echo 建议使用vcpkg安装: vcpkg install zeromq
|
|
||||||
echo.
|
|
||||||
echo 继续构建,但可能遇到ZMQ相关错误...
|
|
||||||
)
|
|
||||||
|
|
||||||
echo ✓ 依赖检查完成
|
|
||||||
|
|
||||||
REM 清理旧的构建
|
|
||||||
echo.
|
|
||||||
echo 清理旧的构建文件...
|
|
||||||
cargo clean
|
|
||||||
|
|
||||||
REM 构建
|
|
||||||
echo.
|
|
||||||
echo 开始构建GBT客户端...
|
|
||||||
echo 构建模式: release
|
|
||||||
echo.
|
|
||||||
|
|
||||||
cargo build --release
|
|
||||||
|
|
||||||
if %errorlevel% equ 0 (
|
|
||||||
echo.
|
|
||||||
echo ✓ 构建成功!
|
|
||||||
echo.
|
|
||||||
echo 可执行文件位置: target\release\gbt.exe
|
|
||||||
echo.
|
|
||||||
echo 使用方法:
|
|
||||||
echo target\release\gbt.exe --help
|
|
||||||
echo.
|
|
||||||
echo 示例:
|
|
||||||
echo target\release\gbt.exe --base-node 127.0.0.1:18102 --base-node-http 127.0.0.1:9000
|
|
||||||
echo.
|
|
||||||
echo 测试ZMQ通信:
|
|
||||||
echo python test_gbt.py
|
|
||||||
) else (
|
|
||||||
echo.
|
|
||||||
echo ✗ 构建失败!
|
|
||||||
echo 请检查错误信息并修复问题
|
|
||||||
pause
|
|
||||||
exit /b 1
|
|
||||||
)
|
|
||||||
|
|
||||||
pause
|
|
85
build.sh
85
build.sh
|
@ -1,85 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# GBT项目构建脚本
|
|
||||||
# 支持Linux和macOS
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
echo "🚀 开始构建GBT项目..."
|
|
||||||
|
|
||||||
# 检查Rust环境
|
|
||||||
if ! command -v cargo &> /dev/null; then
|
|
||||||
echo "❌ 错误: 未找到cargo,请先安装Rust"
|
|
||||||
echo "访问 https://rustup.rs/ 安装Rust"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 检查Rust版本
|
|
||||||
RUST_VERSION=$(rustc --version | cut -d' ' -f2)
|
|
||||||
echo "📦 Rust版本: $RUST_VERSION"
|
|
||||||
|
|
||||||
# 检查依赖
|
|
||||||
echo ""
|
|
||||||
echo "检查依赖项..."
|
|
||||||
|
|
||||||
# 检查ZMQ
|
|
||||||
if ! pkg-config --exists libzmq3; then
|
|
||||||
echo "警告: 未找到ZMQ开发库"
|
|
||||||
echo "Ubuntu/Debian: sudo apt-get install libzmq3-dev"
|
|
||||||
echo "CentOS/RHEL: sudo yum install zeromq-devel"
|
|
||||||
echo "macOS: brew install zeromq"
|
|
||||||
echo ""
|
|
||||||
echo "继续构建,但可能遇到ZMQ相关错误..."
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 检查OpenSSL
|
|
||||||
if ! pkg-config --exists openssl; then
|
|
||||||
echo "警告: 未找到OpenSSL开发库"
|
|
||||||
echo "Ubuntu/Debian: sudo apt-get install libssl-dev"
|
|
||||||
echo "CentOS/RHEL: sudo yum install openssl-devel"
|
|
||||||
echo "macOS: brew install openssl"
|
|
||||||
echo ""
|
|
||||||
echo "继续构建,但可能遇到TLS相关错误..."
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "✓ 依赖检查完成"
|
|
||||||
|
|
||||||
# 清理之前的构建
|
|
||||||
echo "🧹 清理之前的构建..."
|
|
||||||
cargo clean
|
|
||||||
|
|
||||||
# 更新依赖
|
|
||||||
echo "📥 更新依赖..."
|
|
||||||
cargo update
|
|
||||||
|
|
||||||
# 检查代码
|
|
||||||
echo "🔍 检查代码..."
|
|
||||||
cargo check
|
|
||||||
|
|
||||||
# 运行测试
|
|
||||||
echo "🧪 运行测试..."
|
|
||||||
cargo test
|
|
||||||
|
|
||||||
# 构建发布版本
|
|
||||||
echo "🔨 构建发布版本..."
|
|
||||||
cargo build --release
|
|
||||||
|
|
||||||
# 检查构建结果
|
|
||||||
if [ -f "target/release/gbt" ]; then
|
|
||||||
echo "✅ 构建成功!"
|
|
||||||
echo "📁 可执行文件位置: target/release/gbt"
|
|
||||||
|
|
||||||
# 显示文件信息
|
|
||||||
echo "📊 文件信息:"
|
|
||||||
ls -lh target/release/gbt
|
|
||||||
|
|
||||||
# 显示版本信息
|
|
||||||
echo "ℹ️ 版本信息:"
|
|
||||||
./target/release/gbt --version 2>/dev/null || echo "无法获取版本信息"
|
|
||||||
|
|
||||||
else
|
|
||||||
echo "❌ 构建失败!"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "🎉 GBT项目构建完成!"
|
|
14
gbt_process
14
gbt_process
|
@ -1,14 +0,0 @@
|
||||||
// 自建矿池直接与BaseNode交互
|
|
||||||
let base_node_client = connect_base_node(&config).await?;
|
|
||||||
|
|
||||||
// 获取区块模板
|
|
||||||
let template_response = base_node_client.get_new_block_template(request).await?;
|
|
||||||
|
|
||||||
// 生成币基交易
|
|
||||||
let (coinbase_output, coinbase_kernel) = generate_coinbase(...).await?;
|
|
||||||
|
|
||||||
// 构建完整区块
|
|
||||||
let block_result = base_node_client.get_new_block(block_template).await?;
|
|
||||||
|
|
||||||
// 提交区块
|
|
||||||
base_node_client.submit_block(block).await?;
|
|
492
src/main.rs
492
src/main.rs
|
@ -20,23 +20,23 @@
|
||||||
// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
|
// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
|
||||||
// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
use std::{collections::HashMap, path::PathBuf, sync::Arc, time::Duration};
|
use std::{path::PathBuf, time::{SystemTime, UNIX_EPOCH}};
|
||||||
|
use tokio::time::sleep;
|
||||||
|
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use log::*;
|
use log::*;
|
||||||
use reqwest::Client;
|
use reqwest::Client;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use tokio::{sync::Mutex, time::sleep};
|
|
||||||
use tonic::transport::{Certificate, ClientTlsConfig, Endpoint};
|
use tonic::transport::{Certificate, ClientTlsConfig, Endpoint};
|
||||||
use uuid::Uuid;
|
use zmq::{Context, Socket};
|
||||||
use zmq::{Context, Message, Socket};
|
|
||||||
|
|
||||||
use minotari_app_grpc::{
|
use minotari_app_grpc::{
|
||||||
authentication::ClientAuthenticationInterceptor,
|
authentication::ClientAuthenticationInterceptor,
|
||||||
conversions::transaction_output::grpc_output_with_payref,
|
conversions::transaction_output::grpc_output_with_payref,
|
||||||
tari_rpc::{
|
tari_rpc::{
|
||||||
base_node_client::BaseNodeClient, pow_algo::PowAlgos, Block, NewBlockTemplateRequest, PowAlgo,
|
base_node_client::BaseNodeClient, pow_algo::PowAlgos, Block, NewBlockTemplateRequest, PowAlgo,
|
||||||
|
TransactionInput, TransactionOutput, TransactionKernel,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use minotari_app_utilities::parse_miner_input::BaseNodeGrpcClient;
|
use minotari_app_utilities::parse_miner_input::BaseNodeGrpcClient;
|
||||||
|
@ -57,6 +57,10 @@ use tari_core::{
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use tari_utilities::hex::Hex;
|
use tari_utilities::hex::Hex;
|
||||||
|
use tari_hashing::DomainSeparatedBorshHasher;
|
||||||
|
use blake2::Blake2b;
|
||||||
|
use digest::consts::U32;
|
||||||
|
use tari_core::blocks::BlocksHashDomain;
|
||||||
|
|
||||||
const LOG_TARGET: &str = "gbt::main";
|
const LOG_TARGET: &str = "gbt::main";
|
||||||
|
|
||||||
|
@ -80,6 +84,7 @@ pub struct BlockHeader {
|
||||||
pub total_script_offset: String,
|
pub total_script_offset: String,
|
||||||
pub validator_node_mr: String,
|
pub validator_node_mr: String,
|
||||||
pub validator_node_size: u64,
|
pub validator_node_size: u64,
|
||||||
|
pub output_smt_size: u64, // 新增:output_smt_size字段
|
||||||
}
|
}
|
||||||
|
|
||||||
// 工作量证明结构
|
// 工作量证明结构
|
||||||
|
@ -92,30 +97,30 @@ pub struct ProofOfWork {
|
||||||
// 区块体结构
|
// 区块体结构
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
pub struct BlockBody {
|
pub struct BlockBody {
|
||||||
pub inputs: Vec<String>,
|
pub inputs: Vec<TransactionInput>,
|
||||||
pub outputs: Vec<String>,
|
pub outputs: Vec<TransactionOutput>,
|
||||||
pub kernels: Vec<String>,
|
pub kernels: Vec<TransactionKernel>,
|
||||||
}
|
}
|
||||||
|
|
||||||
// 挖矿任务结构
|
// 挖矿任务结构
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
pub struct MiningTask {
|
pub struct MiningTask {
|
||||||
pub job_id: String,
|
|
||||||
pub block_header: BlockHeader,
|
pub block_header: BlockHeader,
|
||||||
pub block_body: BlockBody,
|
pub block_body: BlockBody,
|
||||||
pub output_smt_size: u64,
|
pub output_smt_size: u64,
|
||||||
pub coinbase_hash: String,
|
pub coinbase_hash: String,
|
||||||
pub target: u64, // 新增:目标难度
|
pub target: u64, // 新增:目标难度
|
||||||
|
pub created_at: u64, // 新增:创建时间戳(Unix时间戳)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 挖矿消息结构
|
// 挖矿消息结构
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
pub struct MiningMsg {
|
pub struct MiningMsg {
|
||||||
pub job_id: String,
|
pub height: u64,
|
||||||
|
pub mining_hash: String,
|
||||||
|
pub target: u64,
|
||||||
pub block_header: BlockHeader,
|
pub block_header: BlockHeader,
|
||||||
pub output_smt_size: u64,
|
pub block_body: BlockBody,
|
||||||
pub coinbase_hash: String,
|
|
||||||
pub target: u64, // 新增:目标难度
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 提交请求结构
|
// 提交请求结构
|
||||||
|
@ -133,6 +138,110 @@ pub struct SubmitResult {
|
||||||
pub result: u8, // 1表示成功,0表示失败
|
pub result: u8, // 1表示成功,0表示失败
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// === 新增:output_smt_size 计算方法 ===
|
||||||
|
fn calculate_output_smt_size(
|
||||||
|
prev_output_smt_size: u64,
|
||||||
|
outputs: &[String], // JSON字符串格式
|
||||||
|
inputs: &[String], // JSON字符串格式
|
||||||
|
) -> u64 {
|
||||||
|
let mut size = prev_output_smt_size;
|
||||||
|
let mut new_leaves = 0u64;
|
||||||
|
let mut stale_leaves = 0u64;
|
||||||
|
|
||||||
|
// 计算新叶子数量(排除销毁输出)
|
||||||
|
for (i, output_json) in outputs.iter().enumerate() {
|
||||||
|
match serde_json::from_str::<serde_json::Value>(output_json) {
|
||||||
|
Ok(output) => {
|
||||||
|
// 尝试多种可能的JSON结构
|
||||||
|
let is_burned = if let Some(features) = output.get("features") {
|
||||||
|
if let Some(output_type) = features.get("output_type") {
|
||||||
|
output_type.as_u64() == Some(2) // Burn = 2
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
} else if let Some(output_type) = output.get("output_type") {
|
||||||
|
output_type.as_u64() == Some(2) // Burn = 2
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
|
|
||||||
|
if !is_burned {
|
||||||
|
new_leaves += 1;
|
||||||
|
debug!(target: LOG_TARGET, "Output {}: not burned, adding leaf", i);
|
||||||
|
} else {
|
||||||
|
debug!(target: LOG_TARGET, "Output {}: burned, skipping", i);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
warn!(target: LOG_TARGET, "Failed to parse output {} JSON: {}, assuming not burned", i, e);
|
||||||
|
new_leaves += 1; // 保守策略:假设不是销毁输出
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算移除叶子数量(排除销毁输入)
|
||||||
|
for (i, input_json) in inputs.iter().enumerate() {
|
||||||
|
match serde_json::from_str::<serde_json::Value>(input_json) {
|
||||||
|
Ok(input) => {
|
||||||
|
// 尝试多种可能的JSON结构
|
||||||
|
let is_burned = if let Some(features) = input.get("features") {
|
||||||
|
if let Some(output_type) = features.get("output_type") {
|
||||||
|
output_type.as_u64() == Some(2) // Burn = 2
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
} else if let Some(output_type) = input.get("output_type") {
|
||||||
|
output_type.as_u64() == Some(2) // Burn = 2
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
|
|
||||||
|
if !is_burned {
|
||||||
|
stale_leaves += 1;
|
||||||
|
debug!(target: LOG_TARGET, "Input {}: not burned, removing leaf", i);
|
||||||
|
} else {
|
||||||
|
debug!(target: LOG_TARGET, "Input {}: burned, skipping", i);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
warn!(target: LOG_TARGET, "Failed to parse input {} JSON: {}, assuming not burned", i, e);
|
||||||
|
stale_leaves += 1; // 保守策略:假设不是销毁输入
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
size += new_leaves;
|
||||||
|
size = size.saturating_sub(stale_leaves);
|
||||||
|
|
||||||
|
info!(target: LOG_TARGET, "output_smt_size calculation: {} (prev) + {} (new_leaves) - {} (stale_leaves) = {} (current)",
|
||||||
|
prev_output_smt_size, new_leaves, stale_leaves, size);
|
||||||
|
|
||||||
|
size
|
||||||
|
}
|
||||||
|
|
||||||
|
// === 新增:mining_hash 计算方法 ===
|
||||||
|
fn calculate_mining_hash(header: &BlockHeader) -> Result<String> {
|
||||||
|
let mut hasher = DomainSeparatedBorshHasher::<BlocksHashDomain, Blake2b<U32>>::new_with_label("block_header");
|
||||||
|
|
||||||
|
hasher.update_consensus_encode(&header.version);
|
||||||
|
hasher.update_consensus_encode(&header.height);
|
||||||
|
hasher.update_consensus_encode(&header.prev_hash);
|
||||||
|
hasher.update_consensus_encode(&header.timestamp);
|
||||||
|
hasher.update_consensus_encode(&header.input_mr);
|
||||||
|
hasher.update_consensus_encode(&header.output_mr);
|
||||||
|
hasher.update_consensus_encode(&header.output_smt_size);
|
||||||
|
hasher.update_consensus_encode(&header.block_output_mr);
|
||||||
|
hasher.update_consensus_encode(&header.kernel_mr);
|
||||||
|
hasher.update_consensus_encode(&header.kernel_mmr_size);
|
||||||
|
hasher.update_consensus_encode(&header.total_kernel_offset);
|
||||||
|
hasher.update_consensus_encode(&header.total_script_offset);
|
||||||
|
hasher.update_consensus_encode(&header.validator_node_mr);
|
||||||
|
hasher.update_consensus_encode(&header.validator_node_size);
|
||||||
|
|
||||||
|
let mining_hash = hasher.finalize();
|
||||||
|
Ok(hex::encode(mining_hash.as_slice()))
|
||||||
|
}
|
||||||
|
|
||||||
// 配置结构
|
// 配置结构
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct GbtConfig {
|
pub struct GbtConfig {
|
||||||
|
@ -163,13 +272,6 @@ pub struct GbtClient {
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
zmq_context: Context,
|
zmq_context: Context,
|
||||||
publisher_socket: Socket,
|
publisher_socket: Socket,
|
||||||
subscriber_socket: Socket,
|
|
||||||
|
|
||||||
// 挖矿任务缓存,按height保存,最多保存3个区块
|
|
||||||
mining_tasks: Arc<Mutex<HashMap<u64, MiningTask>>>,
|
|
||||||
|
|
||||||
// 缓存上一个区块的output_smt_size,避免重复请求
|
|
||||||
prev_output_smt_size_cache: Arc<Mutex<HashMap<u64, u64>>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GbtClient {
|
impl GbtClient {
|
||||||
|
@ -197,23 +299,13 @@ impl GbtClient {
|
||||||
let publisher_socket = zmq_context
|
let publisher_socket = zmq_context
|
||||||
.socket(zmq::PUB)
|
.socket(zmq::PUB)
|
||||||
.map_err(|e| anyhow!("ZMQ publisher error: {}", e))?;
|
.map_err(|e| anyhow!("ZMQ publisher error: {}", e))?;
|
||||||
let subscriber_socket = zmq_context
|
|
||||||
.socket(zmq::SUB)
|
|
||||||
.map_err(|e| anyhow!("ZMQ subscriber error: {}", e))?;
|
|
||||||
|
|
||||||
// 绑定ZMQ套接字
|
// 绑定ZMQ套接字
|
||||||
let publisher_addr = format!("tcp://*:{}", config.zmq_publisher_port);
|
let publisher_addr = format!("tcp://*:{}", config.zmq_publisher_port);
|
||||||
let subscriber_addr = format!("tcp://localhost:{}", config.zmq_subscriber_port);
|
|
||||||
|
|
||||||
publisher_socket
|
publisher_socket
|
||||||
.bind(&publisher_addr)
|
.bind(&publisher_addr)
|
||||||
.map_err(|e| anyhow!("ZMQ bind error: {}", e))?;
|
.map_err(|e| anyhow!("ZMQ bind error: {}", e))?;
|
||||||
subscriber_socket
|
|
||||||
.connect(&subscriber_addr)
|
|
||||||
.map_err(|e| anyhow!("ZMQ connect error: {}", e))?;
|
|
||||||
subscriber_socket
|
|
||||||
.set_subscribe(b"submit")
|
|
||||||
.map_err(|e| anyhow!("ZMQ subscribe error: {}", e))?;
|
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
base_node_client,
|
base_node_client,
|
||||||
|
@ -224,9 +316,6 @@ impl GbtClient {
|
||||||
config,
|
config,
|
||||||
zmq_context,
|
zmq_context,
|
||||||
publisher_socket,
|
publisher_socket,
|
||||||
subscriber_socket,
|
|
||||||
mining_tasks: Arc::new(Mutex::new(HashMap::new())),
|
|
||||||
prev_output_smt_size_cache: Arc::new(Mutex::new(HashMap::new())),
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -268,68 +357,6 @@ impl GbtClient {
|
||||||
Ok(node_conn)
|
Ok(node_conn)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 生成16位16进制任务ID
|
|
||||||
fn generate_job_id() -> String {
|
|
||||||
let uuid = Uuid::new_v4();
|
|
||||||
uuid.to_string().replace("-", "")[..16].to_string()
|
|
||||||
}
|
|
||||||
|
|
||||||
// 通过HTTP获取上一个区块的output_smt_size
|
|
||||||
async fn get_prev_output_smt_size(&self, height: u64) -> Result<u64> {
|
|
||||||
let prev_height = height - 1;
|
|
||||||
|
|
||||||
// 检查缓存
|
|
||||||
{
|
|
||||||
let cache = self.prev_output_smt_size_cache.lock().await;
|
|
||||||
if let Some(&size) = cache.get(&prev_height) {
|
|
||||||
return Ok(size);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 通过HTTP请求获取
|
|
||||||
let url = format!("http://{}/get_header_by_height?height={}",
|
|
||||||
self.config.base_node_http_address, prev_height);
|
|
||||||
|
|
||||||
info!(target: LOG_TARGET, "Fetching previous block output_smt_size from: {}", url);
|
|
||||||
|
|
||||||
let response = self.http_client
|
|
||||||
.get(&url)
|
|
||||||
.send()
|
|
||||||
.await
|
|
||||||
.map_err(|e| anyhow!("HTTP request error: {}", e))?;
|
|
||||||
|
|
||||||
if !response.status().is_success() {
|
|
||||||
return Err(anyhow!("HTTP request failed with status: {}", response.status()));
|
|
||||||
}
|
|
||||||
|
|
||||||
let header_data: serde_json::Value = response
|
|
||||||
.json()
|
|
||||||
.await
|
|
||||||
.map_err(|e| anyhow!("JSON parsing error: {}", e))?;
|
|
||||||
|
|
||||||
let output_smt_size = header_data["output_smt_size"]
|
|
||||||
.as_u64()
|
|
||||||
.ok_or_else(|| anyhow!("Missing output_smt_size in response"))?;
|
|
||||||
|
|
||||||
// 更新缓存
|
|
||||||
{
|
|
||||||
let mut cache = self.prev_output_smt_size_cache.lock().await;
|
|
||||||
cache.insert(prev_height, output_smt_size);
|
|
||||||
|
|
||||||
// 清理缓存,只保留最近3个区块
|
|
||||||
if cache.len() > 3 {
|
|
||||||
let mut heights: Vec<u64> = cache.keys().cloned().collect();
|
|
||||||
heights.sort();
|
|
||||||
for height in heights.iter().take(heights.len() - 3) {
|
|
||||||
cache.remove(height);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
info!(target: LOG_TARGET, "Previous block output_smt_size: {}", output_smt_size);
|
|
||||||
Ok(output_smt_size)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 将gRPC Block转换为自定义结构
|
// 将gRPC Block转换为自定义结构
|
||||||
fn convert_block_to_structures(&self, block: &Block) -> Result<(BlockHeader, BlockBody)> {
|
fn convert_block_to_structures(&self, block: &Block) -> Result<(BlockHeader, BlockBody)> {
|
||||||
let header = block.header.as_ref()
|
let header = block.header.as_ref()
|
||||||
|
@ -360,23 +387,14 @@ impl GbtClient {
|
||||||
total_script_offset: header.total_script_offset.to_hex(),
|
total_script_offset: header.total_script_offset.to_hex(),
|
||||||
validator_node_mr: header.validator_node_mr.to_hex(),
|
validator_node_mr: header.validator_node_mr.to_hex(),
|
||||||
validator_node_size: header.validator_node_size,
|
validator_node_size: header.validator_node_size,
|
||||||
|
output_smt_size: header.output_mmr_size, // 使用从gRPC获取的output_mmr_size作为初始值
|
||||||
};
|
};
|
||||||
|
|
||||||
// 构造BlockBody - 使用serde_json序列化然后反序列化为字符串
|
// 构造BlockBody - 直接克隆原始结构体
|
||||||
let inputs: Vec<String> = body.inputs.iter()
|
|
||||||
.map(|i| serde_json::to_string(i).unwrap_or_default())
|
|
||||||
.collect();
|
|
||||||
let outputs: Vec<String> = body.outputs.iter()
|
|
||||||
.map(|o| serde_json::to_string(o).unwrap_or_default())
|
|
||||||
.collect();
|
|
||||||
let kernels: Vec<String> = body.kernels.iter()
|
|
||||||
.map(|k| serde_json::to_string(k).unwrap_or_default())
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let block_body = BlockBody {
|
let block_body = BlockBody {
|
||||||
inputs,
|
inputs: body.inputs.clone(),
|
||||||
outputs,
|
outputs: body.outputs.clone(),
|
||||||
kernels,
|
kernels: body.kernels.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok((block_header, block_body))
|
Ok((block_header, block_body))
|
||||||
|
@ -462,242 +480,90 @@ impl GbtClient {
|
||||||
let coinbase_hash = coinbase_output.hash().to_hex();
|
let coinbase_hash = coinbase_output.hash().to_hex();
|
||||||
|
|
||||||
// 转换为自定义结构
|
// 转换为自定义结构
|
||||||
let (block_header, block_body) = self.convert_block_to_structures(&block)?;
|
let (mut block_header, block_body) = self.convert_block_to_structures(&block)?;
|
||||||
|
|
||||||
// 获取上一个区块的output_smt_size
|
// 先将 outputs 和 inputs 序列化为 JSON 字符串
|
||||||
let prev_output_smt_size = self.get_prev_output_smt_size(height).await?;
|
let output_jsons: Vec<String> = block_body.outputs.iter()
|
||||||
|
.map(|o| serde_json::to_string(o).unwrap_or_default())
|
||||||
|
.collect();
|
||||||
|
let input_jsons: Vec<String> = block_body.inputs.iter()
|
||||||
|
.map(|i| serde_json::to_string(i).unwrap_or_default())
|
||||||
|
.collect();
|
||||||
|
let current_output_smt_size = calculate_output_smt_size(0, &output_jsons, &input_jsons);
|
||||||
|
|
||||||
// 计算当前output_smt_size
|
// 更新block_header中的output_smt_size
|
||||||
let current_output_smt_size = prev_output_smt_size + block_body.outputs.len() as u64 - block_body.inputs.len() as u64;
|
block_header.output_smt_size = current_output_smt_size;
|
||||||
|
|
||||||
// 生成任务ID
|
|
||||||
let job_id = Self::generate_job_id();
|
|
||||||
|
|
||||||
info!(target: LOG_TARGET, "Generated job_id: {}, output_smt_size: {} (prev: {}), target: {}",
|
|
||||||
job_id, current_output_smt_size, prev_output_smt_size, target_difficulty);
|
|
||||||
|
|
||||||
let mining_task = MiningTask {
|
let mining_task = MiningTask {
|
||||||
job_id: job_id.clone(),
|
block_header: block_header.clone(),
|
||||||
block_header,
|
|
||||||
block_body,
|
block_body,
|
||||||
output_smt_size: current_output_smt_size,
|
output_smt_size: current_output_smt_size,
|
||||||
coinbase_hash,
|
coinbase_hash,
|
||||||
target: target_difficulty,
|
target: target_difficulty,
|
||||||
|
created_at: SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs(),
|
||||||
};
|
};
|
||||||
|
|
||||||
// 缓存挖矿任务
|
|
||||||
{
|
|
||||||
let mut tasks = self.mining_tasks.lock().await;
|
|
||||||
tasks.insert(height, mining_task.clone());
|
|
||||||
|
|
||||||
// 清理缓存,只保留最近3个区块
|
|
||||||
if tasks.len() > 3 {
|
|
||||||
let mut heights: Vec<u64> = tasks.keys().cloned().collect();
|
|
||||||
heights.sort();
|
|
||||||
for height in heights.iter().take(heights.len() - 3) {
|
|
||||||
tasks.remove(height);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(mining_task)
|
Ok(mining_task)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 发送挖矿消息
|
// 发送挖矿消息
|
||||||
pub fn send_mining_msg(&self, mining_task: &MiningTask) -> Result<()> {
|
pub fn send_mining_msg(&self, block_header: &BlockHeader, block_body: &BlockBody, target: u64) -> Result<()> {
|
||||||
let mining_msg = MiningMsg {
|
let mining_msg = MiningMsg {
|
||||||
job_id: mining_task.job_id.clone(),
|
height: block_header.height,
|
||||||
block_header: mining_task.block_header.clone(),
|
mining_hash: calculate_mining_hash(block_header)?,
|
||||||
output_smt_size: mining_task.output_smt_size,
|
target,
|
||||||
coinbase_hash: mining_task.coinbase_hash.clone(),
|
block_header: block_header.clone(),
|
||||||
target: mining_task.target,
|
block_body: block_body.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let msg_json = serde_json::to_string(&mining_msg).map_err(|e| anyhow!("Serialization error: {}", e))?;
|
let msg_json = serde_json::to_string(&mining_msg).map_err(|e| anyhow!("Serialization error: {}", e))?;
|
||||||
|
|
||||||
self.publisher_socket
|
self.publisher_socket
|
||||||
.send_multipart(&["mining_msg".as_bytes(), msg_json.as_bytes()], 0)
|
.send_multipart(&["mining_msg".as_bytes(), msg_json.as_bytes()], 0)
|
||||||
.map_err(|e| anyhow!("ZMQ send error: {}", e))?;
|
.map_err(|e| anyhow!("ZMQ send error: {}", e))?;
|
||||||
|
info!(target: LOG_TARGET, "Sent mining message for height {}, target {}", block_header.height, target);
|
||||||
info!(target: LOG_TARGET, "Sent mining message for job_id {} with height {}, output_smt_size {}, and target {}",
|
|
||||||
mining_task.job_id, mining_task.block_header.height, mining_task.output_smt_size, mining_task.target);
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
// 接收提交请求
|
|
||||||
pub async fn receive_submit(&mut self) -> Result<Option<SubmitRequest>> {
|
|
||||||
let mut message = Message::new();
|
|
||||||
|
|
||||||
// 非阻塞接收
|
|
||||||
match self.subscriber_socket.recv(&mut message, zmq::DONTWAIT) {
|
|
||||||
Ok(_) => {
|
|
||||||
let message_str = message.as_str().ok_or_else(|| anyhow!("Message decode error"))?;
|
|
||||||
|
|
||||||
if message_str.starts_with("submit ") {
|
|
||||||
let submit_json = &message_str[7..]; // 去掉"submit "前缀
|
|
||||||
let submit_request: SubmitRequest =
|
|
||||||
serde_json::from_str(submit_json).map_err(|e| anyhow!("Deserialization error: {}", e))?;
|
|
||||||
|
|
||||||
info!(target: LOG_TARGET, "Received submit for job_id {} with nonce {}",
|
|
||||||
submit_request.job_id, submit_request.nonce);
|
|
||||||
|
|
||||||
Ok(Some(submit_request))
|
|
||||||
} else {
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Err(zmq::Error::EAGAIN) => {
|
|
||||||
// 没有消息可读
|
|
||||||
Ok(None)
|
|
||||||
},
|
|
||||||
Err(e) => Err(anyhow!("ZMQ receive error: {}", e)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 提交区块到BaseNode
|
|
||||||
pub async fn submit_block_to_base_node(&mut self, submit_request: &SubmitRequest) -> Result<SubmitResult> {
|
|
||||||
// 查找对应的挖矿任务
|
|
||||||
let mining_task = {
|
|
||||||
let tasks = self.mining_tasks.lock().await;
|
|
||||||
let mut found_task = None;
|
|
||||||
for task in tasks.values() {
|
|
||||||
if task.job_id == submit_request.job_id {
|
|
||||||
found_task = Some(task.clone());
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
found_task.ok_or_else(|| anyhow!("Mining task not found for job_id: {}", submit_request.job_id))?
|
|
||||||
};
|
|
||||||
|
|
||||||
// 构造提交区块
|
|
||||||
let mut submit_block = Block {
|
|
||||||
header: None,
|
|
||||||
body: None,
|
|
||||||
};
|
|
||||||
|
|
||||||
// 构造header
|
|
||||||
let mut header = minotari_app_grpc::tari_rpc::BlockHeader::default();
|
|
||||||
header.hash = hex::decode(&mining_task.block_header.hash)?;
|
|
||||||
header.version = mining_task.block_header.version;
|
|
||||||
header.height = mining_task.block_header.height;
|
|
||||||
header.prev_hash = hex::decode(&mining_task.block_header.prev_hash)?;
|
|
||||||
header.timestamp = mining_task.block_header.timestamp;
|
|
||||||
header.output_mr = hex::decode(&mining_task.block_header.output_mr)?;
|
|
||||||
header.block_output_mr = hex::decode(&mining_task.block_header.block_output_mr)?;
|
|
||||||
header.kernel_mr = hex::decode(&mining_task.block_header.kernel_mr)?;
|
|
||||||
header.input_mr = hex::decode(&mining_task.block_header.input_mr)?;
|
|
||||||
header.total_kernel_offset = hex::decode(&mining_task.block_header.total_kernel_offset)?;
|
|
||||||
header.nonce = submit_request.nonce; // 使用提交的nonce
|
|
||||||
header.pow = Some(minotari_app_grpc::tari_rpc::ProofOfWork {
|
|
||||||
pow_algo: mining_task.block_header.pow.pow_algo,
|
|
||||||
pow_data: hex::decode(&mining_task.block_header.pow.pow_data)?,
|
|
||||||
});
|
|
||||||
header.kernel_mmr_size = mining_task.block_header.kernel_mmr_size;
|
|
||||||
header.output_mmr_size = mining_task.output_smt_size; // 使用计算出的output_smt_size
|
|
||||||
header.total_script_offset = hex::decode(&mining_task.block_header.total_script_offset)?;
|
|
||||||
header.validator_node_mr = hex::decode(&mining_task.block_header.validator_node_mr)?;
|
|
||||||
header.validator_node_size = mining_task.block_header.validator_node_size;
|
|
||||||
|
|
||||||
submit_block.header = Some(header);
|
|
||||||
|
|
||||||
// 构造body - 从JSON字符串反序列化
|
|
||||||
let mut body = minotari_app_grpc::tari_rpc::AggregateBody::default();
|
|
||||||
|
|
||||||
// 反序列化inputs
|
|
||||||
for input_str in &mining_task.block_body.inputs {
|
|
||||||
if let Ok(input) = serde_json::from_str(input_str) {
|
|
||||||
body.inputs.push(input);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 反序列化outputs
|
|
||||||
for output_str in &mining_task.block_body.outputs {
|
|
||||||
if let Ok(output) = serde_json::from_str(output_str) {
|
|
||||||
body.outputs.push(output);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 反序列化kernels
|
|
||||||
for kernel_str in &mining_task.block_body.kernels {
|
|
||||||
if let Ok(kernel) = serde_json::from_str(kernel_str) {
|
|
||||||
body.kernels.push(kernel);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
submit_block.body = Some(body);
|
|
||||||
|
|
||||||
info!(target: LOG_TARGET, "Submitting block to base node for job_id {}", submit_request.job_id);
|
|
||||||
|
|
||||||
// 提交区块
|
|
||||||
let response = self.base_node_client.submit_block(submit_block).await?;
|
|
||||||
let response_inner = response.into_inner();
|
|
||||||
|
|
||||||
// 比较结果 - 使用Debug格式进行比较
|
|
||||||
let result = if format!("{:?}", response_inner) == submit_request.solution {
|
|
||||||
1
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
};
|
|
||||||
|
|
||||||
let submit_result = SubmitResult {
|
|
||||||
job_id: submit_request.job_id.clone(),
|
|
||||||
result,
|
|
||||||
};
|
|
||||||
|
|
||||||
// 发送提交结果
|
|
||||||
let result_json = serde_json::to_string(&submit_result).map_err(|e| anyhow!("Serialization error: {}", e))?;
|
|
||||||
self.publisher_socket
|
|
||||||
.send_multipart(&["submit_result".as_bytes(), result_json.as_bytes()], 0)
|
|
||||||
.map_err(|e| anyhow!("ZMQ send error: {}", e))?;
|
|
||||||
|
|
||||||
info!(target: LOG_TARGET, "Submitted block result for job_id {}: {}", submit_request.job_id, result);
|
|
||||||
|
|
||||||
Ok(submit_result)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 主循环
|
|
||||||
pub async fn run(&mut self) -> Result<()> {
|
pub async fn run(&mut self) -> Result<()> {
|
||||||
info!(target: LOG_TARGET, "Starting GBT client");
|
info!(target: LOG_TARGET, "Starting GBT client");
|
||||||
|
let mut last_height: Option<u64> = None;
|
||||||
|
let mut last_send_time: u64 = 0;
|
||||||
loop {
|
loop {
|
||||||
// 1. 获取区块模板和构造coinbase
|
// 1. 每秒请求一次模板
|
||||||
match self.get_block_template_and_coinbase().await {
|
let mining_task = match self.get_block_template_and_coinbase().await {
|
||||||
Ok(mining_task) => {
|
Ok(task) => task,
|
||||||
// 2. 通过ZMQ发送挖矿消息
|
|
||||||
if let Err(e) = self.send_mining_msg(&mining_task) {
|
|
||||||
error!(target: LOG_TARGET, "Failed to send mining message: {}", e);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!(target: LOG_TARGET, "Failed to get block template: {}", e);
|
error!(target: LOG_TARGET, "Failed to get block template: {}", e);
|
||||||
sleep(Duration::from_secs(5)).await;
|
sleep(std::time::Duration::from_secs(1)).await;
|
||||||
continue;
|
continue;
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
// 3. 接收外部提交
|
let current_height = mining_task.block_header.height;
|
||||||
match self.receive_submit().await {
|
let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs();
|
||||||
Ok(Some(submit_request)) => {
|
let mut should_send = false;
|
||||||
// 4. 提交区块到BaseNode
|
match last_height {
|
||||||
match self.submit_block_to_base_node(&submit_request).await {
|
None => {
|
||||||
Ok(_) => {
|
should_send = true;
|
||||||
info!(target: LOG_TARGET, "Successfully processed submit for job_id {}", submit_request.job_id);
|
info!(target: LOG_TARGET, "First template, sending immediately");
|
||||||
},
|
|
||||||
Err(e) => {
|
|
||||||
error!(target: LOG_TARGET, "Failed to submit block: {}", e);
|
|
||||||
},
|
},
|
||||||
|
Some(prev_height) => {
|
||||||
|
if current_height != prev_height {
|
||||||
|
should_send = true;
|
||||||
|
info!(target: LOG_TARGET, "Height changed: {} -> {}, sending immediately", prev_height, current_height);
|
||||||
|
} else if now >= last_send_time + 5 {
|
||||||
|
should_send = true;
|
||||||
|
info!(target: LOG_TARGET, "Same height {}, sending after 5 seconds", current_height);
|
||||||
}
|
}
|
||||||
},
|
|
||||||
Ok(None) => {
|
|
||||||
// 没有提交,继续循环
|
|
||||||
},
|
|
||||||
Err(e) => {
|
|
||||||
error!(target: LOG_TARGET, "Failed to receive submit: {}", e);
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// 等待一段时间再获取下一个区块模板
|
if should_send {
|
||||||
sleep(Duration::from_secs(1)).await;
|
if let Err(e) = self.send_mining_msg(&mining_task.block_header, &mining_task.block_body, mining_task.target) {
|
||||||
|
error!(target: LOG_TARGET, "Failed to send mining message: {}", e);
|
||||||
|
} else {
|
||||||
|
last_height = Some(current_height);
|
||||||
|
last_send_time = now;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sleep(std::time::Duration::from_secs(1)).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -737,11 +603,11 @@ struct Args {
|
||||||
coinbase_extra: String,
|
coinbase_extra: String,
|
||||||
|
|
||||||
/// ZMQ publisher port
|
/// ZMQ publisher port
|
||||||
#[arg(long, default_value = "5555")]
|
#[arg(long, default_value = "31000")]
|
||||||
zmq_pub_port: u16,
|
zmq_pub_port: u16,
|
||||||
|
|
||||||
/// ZMQ subscriber port
|
/// ZMQ subscriber port
|
||||||
#[arg(long, default_value = "5556")]
|
#[arg(long, default_value = "31001")]
|
||||||
zmq_sub_port: u16,
|
zmq_sub_port: u16,
|
||||||
|
|
||||||
/// Enable TLS
|
/// Enable TLS
|
||||||
|
|
152
test_gbt.py
152
test_gbt.py
|
@ -1,152 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
"""
|
|
||||||
GBT测试脚本
|
|
||||||
用于测试GBT客户端的ZMQ通信功能
|
|
||||||
"""
|
|
||||||
|
|
||||||
import zmq
|
|
||||||
import json
|
|
||||||
import time
|
|
||||||
import threading
|
|
||||||
from typing import Dict, Any
|
|
||||||
|
|
||||||
class GbtTester:
|
|
||||||
def __init__(self, pub_port: int = 5555, sub_port: int = 5556):
|
|
||||||
self.pub_port = pub_port
|
|
||||||
self.sub_port = sub_port
|
|
||||||
self.context = zmq.Context()
|
|
||||||
|
|
||||||
# 订阅者套接字(接收挖矿任务)
|
|
||||||
self.subscriber = self.context.socket(zmq.SUB)
|
|
||||||
self.subscriber.connect(f"tcp://localhost:{self.sub_port}")
|
|
||||||
self.subscriber.setsockopt_string(zmq.SUBSCRIBE, "mining_msg")
|
|
||||||
|
|
||||||
# 发布者套接字(发送提交结果)
|
|
||||||
self.publisher = self.context.socket(zmq.PUB)
|
|
||||||
self.publisher.bind(f"tcp://*:{self.pub_port}")
|
|
||||||
|
|
||||||
self.running = False
|
|
||||||
self.received_tasks: Dict[str, Dict[str, Any]] = {}
|
|
||||||
|
|
||||||
def start(self):
|
|
||||||
"""启动测试器"""
|
|
||||||
self.running = True
|
|
||||||
|
|
||||||
# 启动接收线程
|
|
||||||
self.receive_thread = threading.Thread(target=self._receive_loop)
|
|
||||||
self.receive_thread.daemon = True
|
|
||||||
self.receive_thread.start()
|
|
||||||
|
|
||||||
print(f"GBT测试器已启动")
|
|
||||||
print(f"订阅端口: {self.sub_port}")
|
|
||||||
print(f"发布端口: {self.pub_port}")
|
|
||||||
|
|
||||||
def stop(self):
|
|
||||||
"""停止测试器"""
|
|
||||||
self.running = False
|
|
||||||
self.subscriber.close()
|
|
||||||
self.publisher.close()
|
|
||||||
self.context.term()
|
|
||||||
|
|
||||||
def _receive_loop(self):
|
|
||||||
"""接收消息循环"""
|
|
||||||
while self.running:
|
|
||||||
try:
|
|
||||||
# 非阻塞接收
|
|
||||||
message = self.subscriber.recv_multipart(flags=zmq.NOBLOCK)
|
|
||||||
if message:
|
|
||||||
topic = message[0].decode('utf-8')
|
|
||||||
data = message[1].decode('utf-8')
|
|
||||||
|
|
||||||
if topic == "mining_msg":
|
|
||||||
self._handle_mining_msg(data)
|
|
||||||
|
|
||||||
except zmq.Again:
|
|
||||||
# 没有消息,继续循环
|
|
||||||
time.sleep(0.1)
|
|
||||||
continue
|
|
||||||
except Exception as e:
|
|
||||||
print(f"接收消息错误: {e}")
|
|
||||||
time.sleep(1)
|
|
||||||
|
|
||||||
def _handle_mining_msg(self, data: str):
|
|
||||||
"""处理挖矿消息"""
|
|
||||||
try:
|
|
||||||
mining_msg = json.loads(data)
|
|
||||||
job_id = mining_msg.get('job_id')
|
|
||||||
|
|
||||||
print(f"\n收到挖矿任务: {job_id}")
|
|
||||||
print(f"区块高度: {mining_msg.get('block_header', {}).get('height')}")
|
|
||||||
print(f"output_smt_size: {mining_msg.get('output_smt_size')}")
|
|
||||||
print(f"target: {mining_msg.get('target')}")
|
|
||||||
print(f"coinbase_hash: {mining_msg.get('coinbase_hash')[:16]}...")
|
|
||||||
|
|
||||||
# 保存任务
|
|
||||||
self.received_tasks[job_id] = mining_msg
|
|
||||||
|
|
||||||
# 模拟挖矿(延迟1秒后提交)
|
|
||||||
threading.Timer(1.0, self._submit_result, args=[job_id]).start()
|
|
||||||
|
|
||||||
except json.JSONDecodeError as e:
|
|
||||||
print(f"JSON解析错误: {e}")
|
|
||||||
except Exception as e:
|
|
||||||
print(f"处理挖矿消息错误: {e}")
|
|
||||||
|
|
||||||
def _submit_result(self, job_id: str):
|
|
||||||
"""提交挖矿结果"""
|
|
||||||
if job_id not in self.received_tasks:
|
|
||||||
print(f"任务 {job_id} 不存在")
|
|
||||||
return
|
|
||||||
|
|
||||||
# 构造提交请求
|
|
||||||
submit_request = {
|
|
||||||
"job_id": job_id,
|
|
||||||
"nonce": 12345, # 模拟nonce
|
|
||||||
"solution": "test_solution_hash_12345" # 模拟solution
|
|
||||||
}
|
|
||||||
|
|
||||||
try:
|
|
||||||
# 发送提交请求
|
|
||||||
message = f"submit {json.dumps(submit_request)}"
|
|
||||||
self.publisher.send_string(message)
|
|
||||||
|
|
||||||
print(f"已提交挖矿结果: {job_id}")
|
|
||||||
print(f"nonce: {submit_request['nonce']}")
|
|
||||||
print(f"solution: {submit_request['solution']}")
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print(f"提交结果错误: {e}")
|
|
||||||
|
|
||||||
def get_stats(self) -> Dict[str, Any]:
|
|
||||||
"""获取统计信息"""
|
|
||||||
return {
|
|
||||||
"received_tasks": len(self.received_tasks),
|
|
||||||
"task_ids": list(self.received_tasks.keys())
|
|
||||||
}
|
|
||||||
|
|
||||||
def main():
|
|
||||||
"""主函数"""
|
|
||||||
print("GBT ZMQ通信测试器")
|
|
||||||
print("=" * 50)
|
|
||||||
|
|
||||||
# 创建测试器
|
|
||||||
tester = GbtTester()
|
|
||||||
|
|
||||||
try:
|
|
||||||
# 启动测试器
|
|
||||||
tester.start()
|
|
||||||
|
|
||||||
# 主循环
|
|
||||||
while True:
|
|
||||||
time.sleep(5)
|
|
||||||
stats = tester.get_stats()
|
|
||||||
print(f"\n统计信息: 已接收 {stats['received_tasks']} 个任务")
|
|
||||||
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
print("\n正在停止测试器...")
|
|
||||||
finally:
|
|
||||||
tester.stop()
|
|
||||||
print("测试器已停止")
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
Loading…
Reference in New Issue