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" }
|
||||
tari_utilities = { version = "0.8" }
|
||||
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"
|
||||
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获取区块模板
|
||||
- 自动生成coinbase交易
|
||||
- 支持多种挖矿算法(SHA3X、RandomXM、RandomXT)
|
||||
|
||||
### 2. HTTP API集成
|
||||
- 通过HTTP请求获取上一个区块的`output_smt_size`
|
||||
- 缓存机制避免重复请求
|
||||
- 自动计算当前区块的`output_smt_size`
|
||||
|
||||
### 3. 任务管理
|
||||
- 生成16位16进制任务ID
|
||||
- 任务缓存管理(最多保存3个区块)
|
||||
- 支持任务状态跟踪
|
||||
|
||||
### 4. ZMQ通信
|
||||
- 发布挖矿任务消息
|
||||
- 接收矿工提交结果
|
||||
- 支持多种消息类型
|
||||
- 支持SHA3X挖矿算法
|
||||
- ZMQ推送标准JSON格式的挖矿任务
|
||||
- 每秒获取一次模板,高度变立即推送,否则每5秒推送一次最新模板
|
||||
|
||||
## 数据结构
|
||||
|
||||
### 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(挖矿任务)
|
||||
```rust
|
||||
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, // 目标难度
|
||||
pub block_header: BlockHeader,
|
||||
pub block_body: BlockBody,
|
||||
pub output_smt_size: u64,
|
||||
pub coinbase_hash: String,
|
||||
pub target: u64,
|
||||
pub created_at: u64,
|
||||
}
|
||||
```
|
||||
|
||||
### MiningMsg(挖矿消息)
|
||||
```rust
|
||||
pub struct MiningMsg {
|
||||
pub job_id: String, // 任务ID
|
||||
pub block_header: BlockHeader, // 区块头
|
||||
pub output_smt_size: u64, // output_smt_size
|
||||
pub coinbase_hash: String, // coinbase哈希
|
||||
pub target: u64, // 目标难度
|
||||
pub height: u64,
|
||||
pub mining_hash: String,
|
||||
pub target: u64,
|
||||
pub block_header: BlockHeader,
|
||||
pub block_body: BlockBody,
|
||||
}
|
||||
```
|
||||
|
||||
### SubmitRequest(提交请求)
|
||||
```rust
|
||||
pub struct SubmitRequest {
|
||||
pub job_id: String, // 任务ID
|
||||
pub nonce: u64, // 挖矿nonce
|
||||
pub solution: String, // 挖矿解
|
||||
pub job_id: String,
|
||||
pub nonce: u64,
|
||||
pub solution: String,
|
||||
}
|
||||
```
|
||||
|
||||
### SubmitResult(提交结果)
|
||||
```rust
|
||||
pub struct SubmitResult {
|
||||
pub job_id: String,
|
||||
pub result: u8, // 1表示成功,0表示失败
|
||||
}
|
||||
```
|
||||
|
||||
## 工作流程
|
||||
|
||||
### 1. 获取区块模板
|
||||
1. 通过gRPC获取区块模板
|
||||
2. 生成coinbase交易
|
||||
3. 通过HTTP获取上一个区块的`output_smt_size`
|
||||
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. 返回提交结果
|
||||
1. 每秒从BaseNode获取一次区块模板。
|
||||
2. 若区块高度变化,立即推送新任务;否则每5秒推送一次最新模板。
|
||||
3. 通过ZMQ发布标准JSON格式的MiningMsg消息,block_body为JSON对象。
|
||||
4. 任务缓存、job_id、submit相关逻辑已移除,代码结构更简洁。
|
||||
|
||||
## 配置参数
|
||||
|
||||
|
@ -89,8 +109,12 @@ pub struct SubmitRequest {
|
|||
| `--network` | `mainnet` | 网络类型 |
|
||||
| `--wallet-address` | 默认地址 | 钱包地址 |
|
||||
| `--coinbase-extra` | `m2pool.com` | Coinbase额外数据 |
|
||||
| `--zmq-pub-port` | `5555` | ZMQ发布端口 |
|
||||
| `--zmq-sub-port` | `5556` | ZMQ订阅端口 |
|
||||
| `--zmq-pub-port` | `31000` | 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 \
|
||||
--wallet-address YOUR_WALLET_ADDRESS \
|
||||
--coinbase-extra "your_pool_name" \
|
||||
--zmq-pub-port 5555 \
|
||||
--zmq-sub-port 5556
|
||||
--zmq-pub-port 31000 \
|
||||
--zmq-sub-port 31001
|
||||
```
|
||||
|
||||
## ZMQ消息格式
|
||||
|
@ -120,7 +144,7 @@ cargo build --release
|
|||
### 挖矿任务消息
|
||||
```
|
||||
Topic: "mining_msg"
|
||||
Data: JSON格式的MiningMsg
|
||||
Data: JSON格式的MiningMsg,block_body为标准JSON对象
|
||||
```
|
||||
|
||||
### 提交请求消息
|
||||
|
@ -138,11 +162,11 @@ Data: JSON格式的SubmitResult
|
|||
## 依赖项
|
||||
|
||||
- `reqwest`: HTTP客户端
|
||||
- `uuid`: 任务ID生成
|
||||
- `zmq`: ZMQ通信
|
||||
- `serde`: 序列化/反序列化
|
||||
- `tokio`: 异步运行时
|
||||
- `tonic`: gRPC客户端
|
||||
- 其它Tari相关依赖
|
||||
|
||||
## 注意事项
|
||||
|
||||
|
@ -154,8 +178,6 @@ Data: JSON格式的SubmitResult
|
|||
|
||||
## 故障排除
|
||||
|
||||
### 常见问题
|
||||
|
||||
1. **连接BaseNode失败**
|
||||
- 检查BaseNode是否运行
|
||||
- 验证gRPC地址和端口
|
||||
|
@ -178,5 +200,4 @@ Data: JSON格式的SubmitResult
|
|||
|
||||
## 开发
|
||||
|
||||
### 添加新功能
|
||||
1. 修改`src/main.rs`
|
||||
如需扩展功能,请直接修改`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?;
|
494
src/main.rs
494
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
|
||||
// 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 clap::Parser;
|
||||
use log::*;
|
||||
use reqwest::Client;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::{sync::Mutex, time::sleep};
|
||||
use tonic::transport::{Certificate, ClientTlsConfig, Endpoint};
|
||||
use uuid::Uuid;
|
||||
use zmq::{Context, Message, Socket};
|
||||
use zmq::{Context, Socket};
|
||||
|
||||
use minotari_app_grpc::{
|
||||
authentication::ClientAuthenticationInterceptor,
|
||||
conversions::transaction_output::grpc_output_with_payref,
|
||||
tari_rpc::{
|
||||
base_node_client::BaseNodeClient, pow_algo::PowAlgos, Block, NewBlockTemplateRequest, PowAlgo,
|
||||
TransactionInput, TransactionOutput, TransactionKernel,
|
||||
},
|
||||
};
|
||||
use minotari_app_utilities::parse_miner_input::BaseNodeGrpcClient;
|
||||
|
@ -57,6 +57,10 @@ use tari_core::{
|
|||
},
|
||||
};
|
||||
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";
|
||||
|
||||
|
@ -80,6 +84,7 @@ pub struct BlockHeader {
|
|||
pub total_script_offset: String,
|
||||
pub validator_node_mr: String,
|
||||
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)]
|
||||
pub struct BlockBody {
|
||||
pub inputs: Vec<String>,
|
||||
pub outputs: Vec<String>,
|
||||
pub kernels: Vec<String>,
|
||||
pub inputs: Vec<TransactionInput>,
|
||||
pub outputs: Vec<TransactionOutput>,
|
||||
pub kernels: Vec<TransactionKernel>,
|
||||
}
|
||||
|
||||
// 挖矿任务结构
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct MiningTask {
|
||||
pub job_id: String,
|
||||
pub block_header: BlockHeader,
|
||||
pub block_body: BlockBody,
|
||||
pub output_smt_size: u64,
|
||||
pub coinbase_hash: String,
|
||||
pub target: u64, // 新增:目标难度
|
||||
pub created_at: u64, // 新增:创建时间戳(Unix时间戳)
|
||||
}
|
||||
|
||||
// 挖矿消息结构
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct MiningMsg {
|
||||
pub job_id: String,
|
||||
pub height: u64,
|
||||
pub mining_hash: String,
|
||||
pub target: u64,
|
||||
pub block_header: BlockHeader,
|
||||
pub output_smt_size: u64,
|
||||
pub coinbase_hash: String,
|
||||
pub target: u64, // 新增:目标难度
|
||||
pub block_body: BlockBody,
|
||||
}
|
||||
|
||||
// 提交请求结构
|
||||
|
@ -133,6 +138,110 @@ pub struct SubmitResult {
|
|||
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)]
|
||||
pub struct GbtConfig {
|
||||
|
@ -163,13 +272,6 @@ pub struct GbtClient {
|
|||
#[allow(dead_code)]
|
||||
zmq_context: Context,
|
||||
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 {
|
||||
|
@ -197,23 +299,13 @@ impl GbtClient {
|
|||
let publisher_socket = zmq_context
|
||||
.socket(zmq::PUB)
|
||||
.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套接字
|
||||
let publisher_addr = format!("tcp://*:{}", config.zmq_publisher_port);
|
||||
let subscriber_addr = format!("tcp://localhost:{}", config.zmq_subscriber_port);
|
||||
|
||||
publisher_socket
|
||||
.bind(&publisher_addr)
|
||||
.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 {
|
||||
base_node_client,
|
||||
|
@ -224,9 +316,6 @@ impl GbtClient {
|
|||
config,
|
||||
zmq_context,
|
||||
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)
|
||||
}
|
||||
|
||||
// 生成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转换为自定义结构
|
||||
fn convert_block_to_structures(&self, block: &Block) -> Result<(BlockHeader, BlockBody)> {
|
||||
let header = block.header.as_ref()
|
||||
|
@ -360,23 +387,14 @@ impl GbtClient {
|
|||
total_script_offset: header.total_script_offset.to_hex(),
|
||||
validator_node_mr: header.validator_node_mr.to_hex(),
|
||||
validator_node_size: header.validator_node_size,
|
||||
output_smt_size: header.output_mmr_size, // 使用从gRPC获取的output_mmr_size作为初始值
|
||||
};
|
||||
|
||||
// 构造BlockBody - 使用serde_json序列化然后反序列化为字符串
|
||||
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();
|
||||
|
||||
// 构造BlockBody - 直接克隆原始结构体
|
||||
let block_body = BlockBody {
|
||||
inputs,
|
||||
outputs,
|
||||
kernels,
|
||||
inputs: body.inputs.clone(),
|
||||
outputs: body.outputs.clone(),
|
||||
kernels: body.kernels.clone(),
|
||||
};
|
||||
|
||||
Ok((block_header, block_body))
|
||||
|
@ -462,242 +480,90 @@ impl GbtClient {
|
|||
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
|
||||
let prev_output_smt_size = self.get_prev_output_smt_size(height).await?;
|
||||
// 先将 outputs 和 inputs 序列化为 JSON 字符串
|
||||
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
|
||||
let current_output_smt_size = prev_output_smt_size + block_body.outputs.len() as u64 - block_body.inputs.len() as u64;
|
||||
|
||||
// 生成任务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);
|
||||
// 更新block_header中的output_smt_size
|
||||
block_header.output_smt_size = current_output_smt_size;
|
||||
|
||||
let mining_task = MiningTask {
|
||||
job_id: job_id.clone(),
|
||||
block_header,
|
||||
block_header: block_header.clone(),
|
||||
block_body,
|
||||
output_smt_size: current_output_smt_size,
|
||||
coinbase_hash,
|
||||
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)
|
||||
}
|
||||
|
||||
// 发送挖矿消息
|
||||
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 {
|
||||
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,
|
||||
height: block_header.height,
|
||||
mining_hash: calculate_mining_hash(block_header)?,
|
||||
target,
|
||||
block_header: block_header.clone(),
|
||||
block_body: block_body.clone(),
|
||||
};
|
||||
|
||||
let msg_json = serde_json::to_string(&mining_msg).map_err(|e| anyhow!("Serialization error: {}", e))?;
|
||||
|
||||
self.publisher_socket
|
||||
.send_multipart(&["mining_msg".as_bytes(), msg_json.as_bytes()], 0)
|
||||
.map_err(|e| anyhow!("ZMQ send error: {}", e))?;
|
||||
|
||||
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);
|
||||
info!(target: LOG_TARGET, "Sent mining message for height {}, target {}", block_header.height, target);
|
||||
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<()> {
|
||||
info!(target: LOG_TARGET, "Starting GBT client");
|
||||
|
||||
let mut last_height: Option<u64> = None;
|
||||
let mut last_send_time: u64 = 0;
|
||||
loop {
|
||||
// 1. 获取区块模板和构造coinbase
|
||||
match self.get_block_template_and_coinbase().await {
|
||||
Ok(mining_task) => {
|
||||
// 2. 通过ZMQ发送挖矿消息
|
||||
if let Err(e) = self.send_mining_msg(&mining_task) {
|
||||
error!(target: LOG_TARGET, "Failed to send mining message: {}", e);
|
||||
}
|
||||
},
|
||||
// 1. 每秒请求一次模板
|
||||
let mining_task = match self.get_block_template_and_coinbase().await {
|
||||
Ok(task) => task,
|
||||
Err(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;
|
||||
}
|
||||
};
|
||||
let current_height = mining_task.block_header.height;
|
||||
let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs();
|
||||
let mut should_send = false;
|
||||
match last_height {
|
||||
None => {
|
||||
should_send = true;
|
||||
info!(target: LOG_TARGET, "First template, sending immediately");
|
||||
},
|
||||
}
|
||||
|
||||
// 3. 接收外部提交
|
||||
match self.receive_submit().await {
|
||||
Ok(Some(submit_request)) => {
|
||||
// 4. 提交区块到BaseNode
|
||||
match self.submit_block_to_base_node(&submit_request).await {
|
||||
Ok(_) => {
|
||||
info!(target: LOG_TARGET, "Successfully processed submit for job_id {}", submit_request.job_id);
|
||||
},
|
||||
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);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// 等待一段时间再获取下一个区块模板
|
||||
sleep(Duration::from_secs(1)).await;
|
||||
if should_send {
|
||||
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,
|
||||
|
||||
/// ZMQ publisher port
|
||||
#[arg(long, default_value = "5555")]
|
||||
#[arg(long, default_value = "31000")]
|
||||
zmq_pub_port: u16,
|
||||
|
||||
/// ZMQ subscriber port
|
||||
#[arg(long, default_value = "5556")]
|
||||
#[arg(long, default_value = "31001")]
|
||||
zmq_sub_port: u16,
|
||||
|
||||
/// 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