Compare commits
7 Commits
Author | SHA1 | Date |
---|---|---|
|
e0f77b4b91 | |
|
58d4635c8e | |
|
e95771709c | |
|
eff35c4cb4 | |
|
0463129165 | |
|
bc9ca3ece5 | |
|
93244ecf62 |
|
@ -19,6 +19,11 @@ 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"
|
||||
|
@ -44,6 +49,7 @@ log4rs = { version = "1.3.0", default-features = false, features = [
|
|||
native-tls = "0.2"
|
||||
num_cpus = "1.13"
|
||||
rand = "0.8"
|
||||
reqwest = { version = "0.11", features = ["json"] }
|
||||
serde = { version = "1.0", default-features = false, features = ["derive"] }
|
||||
serde_json = "1.0.57"
|
||||
thiserror = "1.0"
|
||||
|
@ -53,6 +59,7 @@ tokio = { version = "1.44", default-features = false, features = [
|
|||
"time",
|
||||
] }
|
||||
tonic = { version = "0.13.1", features = ["tls-ring", "tls-native-roots"] }
|
||||
uuid = { version = "1.0", features = ["v4"] }
|
||||
zmq = "0.10"
|
||||
env_logger = "0.10"
|
||||
anyhow = "1.0"
|
||||
|
|
287
README.md
287
README.md
|
@ -1,152 +1,203 @@
|
|||
# Tari GBT (GetBlockTemplate) Client
|
||||
# Tari GBT (Get Block Template) Client
|
||||
|
||||
这是一个独立的Tari GetBlockTemplate客户端,用于获取区块模板、构造coinbase交易,并通过ZMQ与外部挖矿程序通信。
|
||||
这是一个Tari区块链的GetBlockTemplate客户端,支持ZMQ通信协议,用于挖矿池和矿工之间的通信。
|
||||
|
||||
## 功能特性
|
||||
|
||||
- ✅ 连接Tari BaseNode获取区块模板
|
||||
- ✅ 自动构造coinbase交易
|
||||
- ✅ 通过ZMQ发送挖矿任务
|
||||
- ✅ 接收外部挖矿结果
|
||||
- ✅ 提交完成的区块到BaseNode
|
||||
- ✅ 支持多种网络(mainnet、nextnet、testnet)
|
||||
- ✅ 支持TLS加密连接
|
||||
- ✅ 命令行参数配置
|
||||
- 通过gRPC从BaseNode获取区块模板
|
||||
- 自动生成coinbase交易
|
||||
- 支持SHA3X挖矿算法
|
||||
- ZMQ推送标准JSON格式的挖矿任务
|
||||
- 每秒获取一次模板,高度变立即推送,否则每5秒推送一次最新模板
|
||||
|
||||
## 编译
|
||||
## 数据结构
|
||||
|
||||
```bash
|
||||
# 在gbt目录下编译
|
||||
cargo build --release
|
||||
|
||||
# 编译后的可执行文件位于 target/release/gbt
|
||||
```
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 基本用法
|
||||
|
||||
```bash
|
||||
# 连接到本地BaseNode
|
||||
./target/release/gbt --wallet-address <钱包地址>
|
||||
|
||||
# 指定BaseNode地址
|
||||
./target/release/gbt --base-node 192.168.1.100:18142 --wallet-address <钱包地址>
|
||||
|
||||
# 指定网络
|
||||
./target/release/gbt --network testnet --wallet-address <钱包地址>
|
||||
```
|
||||
|
||||
### 完整参数
|
||||
|
||||
```bash
|
||||
./target/release/gbt \
|
||||
--base-node 127.0.0.1:18142 \
|
||||
--network mainnet \
|
||||
--wallet-address <钱包地址> \
|
||||
--coinbase-extra "GBT Miner" \
|
||||
--zmq-pub-port 5555 \
|
||||
--zmq-sub-port 5556
|
||||
```
|
||||
|
||||
### 参数说明
|
||||
|
||||
- `--base-node`: BaseNode gRPC地址(默认:127.0.0.1:18142)
|
||||
- `--network`: 网络类型(mainnet/nextnet/testnet,默认:mainnet)
|
||||
- `--wallet-address`: 钱包支付地址(必需)
|
||||
- `--coinbase-extra`: coinbase额外数据(默认:GBT)
|
||||
- `--zmq-pub-port`: ZMQ发布端口(默认:5555)
|
||||
- `--zmq-sub-port`: ZMQ订阅端口(默认:5556)
|
||||
- `--tls`: 启用TLS加密
|
||||
- `--tls-domain`: TLS域名
|
||||
- `--tls-ca-cert`: TLS CA证书文件
|
||||
- `--config-dir`: 配置目录(默认:当前目录)
|
||||
|
||||
## ZMQ消息格式
|
||||
|
||||
### 发送的挖矿任务(mining_task)
|
||||
|
||||
```json
|
||||
{
|
||||
"coinbase_hash": "abc123...",
|
||||
"height": 12345,
|
||||
"target": 1000000,
|
||||
"block_template": "{...序列化的区块模板...}"
|
||||
### 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,
|
||||
}
|
||||
```
|
||||
|
||||
### 接收的提交请求(submit)
|
||||
### ProofOfWork
|
||||
```rust
|
||||
pub struct ProofOfWork {
|
||||
pub pow_algo: u64,
|
||||
pub pow_data: String,
|
||||
}
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"height": 12345,
|
||||
"nonce": 67890,
|
||||
"solution": "solution_hash...",
|
||||
"block_data": "{...序列化的区块数据...}"
|
||||
### BlockBody(区块体)
|
||||
```rust
|
||||
pub struct BlockBody {
|
||||
pub inputs: Vec<TransactionInput>,
|
||||
pub outputs: Vec<TransactionOutput>,
|
||||
pub kernels: Vec<TransactionKernel>,
|
||||
}
|
||||
```
|
||||
|
||||
### MiningTask(挖矿任务)
|
||||
```rust
|
||||
pub struct MiningTask {
|
||||
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 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,
|
||||
pub nonce: u64,
|
||||
pub solution: String,
|
||||
}
|
||||
```
|
||||
|
||||
### SubmitResult(提交结果)
|
||||
```rust
|
||||
pub struct SubmitResult {
|
||||
pub job_id: String,
|
||||
pub result: u8, // 1表示成功,0表示失败
|
||||
}
|
||||
```
|
||||
|
||||
## 工作流程
|
||||
|
||||
1. **连接BaseNode**: 建立与Tari BaseNode的gRPC连接
|
||||
2. **获取区块模板**: 从BaseNode获取新区块模板
|
||||
3. **构造coinbase**: 生成coinbase交易并添加到区块模板
|
||||
4. **发送挖矿任务**: 通过ZMQ发布挖矿任务给外部挖矿程序
|
||||
5. **接收挖矿结果**: 通过ZMQ接收外部挖矿程序提交的结果
|
||||
6. **提交区块**: 将完成的区块提交给BaseNode
|
||||
7. **循环**: 重复上述过程
|
||||
1. 每秒从BaseNode获取一次区块模板。
|
||||
2. 若区块高度变化,立即推送新任务;否则每5秒推送一次最新模板。
|
||||
3. 通过ZMQ发布标准JSON格式的MiningMsg消息,block_body为JSON对象。
|
||||
4. 任务缓存、job_id、submit相关逻辑已移除,代码结构更简洁。
|
||||
|
||||
## 依赖要求
|
||||
## 配置参数
|
||||
|
||||
- Rust 1.70+
|
||||
- Tari BaseNode运行中并启用gRPC
|
||||
- 有效的Tari钱包地址
|
||||
| 参数 | 默认值 | 说明 |
|
||||
|------|--------|------|
|
||||
| `--base-node` | `127.0.0.1:18102` | BaseNode gRPC地址 |
|
||||
| `--base-node-http` | `127.0.0.1:9000` | BaseNode HTTP地址 |
|
||||
| `--network` | `mainnet` | 网络类型 |
|
||||
| `--wallet-address` | 默认地址 | 钱包地址 |
|
||||
| `--coinbase-extra` | `m2pool.com` | Coinbase额外数据 |
|
||||
| `--zmq-pub-port` | `31000` | ZMQ发布端口 |
|
||||
| `--zmq-sub-port` | `31001` | ZMQ订阅端口 |
|
||||
| `--tls` | 关闭 | 启用TLS |
|
||||
| `--tls-domain` | 无 | TLS域名 |
|
||||
| `--tls-ca-cert` | 无 | TLS CA证书文件 |
|
||||
| `--config-dir` | `.` | 配置目录 |
|
||||
|
||||
## 网络配置
|
||||
## 构建和运行
|
||||
|
||||
### Mainnet
|
||||
### 构建
|
||||
```bash
|
||||
./target/release/gbt --network mainnet --wallet-address <主网钱包地址>
|
||||
cargo build --release
|
||||
```
|
||||
|
||||
### Nextnet
|
||||
### 运行
|
||||
```bash
|
||||
./target/release/gbt --network nextnet --wallet-address <测试网钱包地址>
|
||||
# 基本运行
|
||||
./target/release/gbt
|
||||
|
||||
# 自定义配置
|
||||
./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" \
|
||||
--zmq-pub-port 31000 \
|
||||
--zmq-sub-port 31001
|
||||
```
|
||||
|
||||
### Testnet
|
||||
```bash
|
||||
./target/release/gbt --network testnet --wallet-address <测试网钱包地址>
|
||||
## ZMQ消息格式
|
||||
|
||||
### 挖矿任务消息
|
||||
```
|
||||
Topic: "mining_msg"
|
||||
Data: JSON格式的MiningMsg,block_body为标准JSON对象
|
||||
```
|
||||
|
||||
### 提交请求消息
|
||||
```
|
||||
Topic: "submit"
|
||||
Data: JSON格式的SubmitRequest
|
||||
```
|
||||
|
||||
### 提交结果消息
|
||||
```
|
||||
Topic: "submit_result"
|
||||
Data: JSON格式的SubmitResult
|
||||
```
|
||||
|
||||
## 依赖项
|
||||
|
||||
- `reqwest`: HTTP客户端
|
||||
- `zmq`: ZMQ通信
|
||||
- `serde`: 序列化/反序列化
|
||||
- `tokio`: 异步运行时
|
||||
- `tonic`: gRPC客户端
|
||||
- 其它Tari相关依赖
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. 确保BaseNode的gRPC和HTTP服务正在运行
|
||||
2. 确保ZMQ端口未被占用
|
||||
3. 钱包地址必须是有效的Tari地址
|
||||
4. 网络配置必须与BaseNode一致
|
||||
5. 建议在生产环境中启用TLS
|
||||
|
||||
## 故障排除
|
||||
|
||||
### 连接错误
|
||||
- 确保BaseNode正在运行
|
||||
- 检查gRPC端口是否正确
|
||||
- 确认BaseNode已启用gRPC服务
|
||||
1. **连接BaseNode失败**
|
||||
- 检查BaseNode是否运行
|
||||
- 验证gRPC地址和端口
|
||||
- 检查网络配置
|
||||
|
||||
### ZMQ错误
|
||||
- 检查ZMQ端口是否被占用
|
||||
- 确保外部挖矿程序正确连接到ZMQ端口
|
||||
2. **HTTP请求失败**
|
||||
- 检查BaseNode HTTP服务
|
||||
- 验证HTTP地址和端口
|
||||
- 检查网络连接
|
||||
|
||||
### 钱包地址错误
|
||||
- 确保钱包地址格式正确
|
||||
- 检查钱包地址是否属于指定网络
|
||||
3. **ZMQ通信失败**
|
||||
- 检查ZMQ端口是否被占用
|
||||
- 验证防火墙设置
|
||||
- 检查ZMQ库安装
|
||||
|
||||
4. **任务提交失败**
|
||||
- 检查任务ID是否有效
|
||||
- 验证nonce和solution格式
|
||||
- 检查BaseNode状态
|
||||
|
||||
## 开发
|
||||
|
||||
### 添加新功能
|
||||
1. 修改`src/main.rs`
|
||||
2. 更新依赖项(如需要)
|
||||
3. 重新编译
|
||||
|
||||
### 调试
|
||||
```bash
|
||||
# 启用详细日志
|
||||
RUST_LOG=debug cargo run -- --wallet-address <地址>
|
||||
```
|
||||
|
||||
## 许可证
|
||||
|
||||
BSD-3-Clause License
|
||||
如需扩展功能,请直接修改`src/main.rs`,结构清晰,易于维护。
|
58
build.sh
58
build.sh
|
@ -1,58 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
# GBT项目构建脚本
|
||||
# 支持Linux和macOS
|
||||
|
||||
set -e
|
||||
|
||||
echo "🚀 开始构建GBT项目..."
|
||||
|
||||
# 检查Rust环境
|
||||
if ! command -v cargo &> /dev/null; then
|
||||
echo "❌ 错误: 未找到cargo,请先安装Rust"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 检查Rust版本
|
||||
RUST_VERSION=$(rustc --version | cut -d' ' -f2)
|
||||
echo "📦 Rust版本: $RUST_VERSION"
|
||||
|
||||
# 清理之前的构建
|
||||
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?;
|
108
log4rs_gbt.yml
108
log4rs_gbt.yml
|
@ -1,108 +0,0 @@
|
|||
# GBT Client 日志配置文件
|
||||
# 基于 log4rs 配置,用于GBT客户端的日志管理
|
||||
refresh_rate: 30 seconds
|
||||
|
||||
appenders:
|
||||
# 控制台输出
|
||||
stdout:
|
||||
kind: console
|
||||
encoder:
|
||||
pattern: "{d(%H:%M)} {h({l}):5} {m}{n}"
|
||||
filters:
|
||||
- kind: threshold
|
||||
level: info
|
||||
|
||||
# GBT客户端日志文件
|
||||
gbt:
|
||||
kind: rolling_file
|
||||
path: "{{log_dir}}/log/gbt/gbt.log"
|
||||
policy:
|
||||
kind: compound
|
||||
trigger:
|
||||
kind: size
|
||||
limit: 10mb
|
||||
roller:
|
||||
kind: fixed_window
|
||||
base: 1
|
||||
count: 5
|
||||
pattern: "{{log_dir}}/log/gbt/gbt.{}.log"
|
||||
encoder:
|
||||
pattern: "{d(%Y-%m-%d %H:%M:%S.%f)} [{t}] {l:5} {m}{n}"
|
||||
|
||||
# ZMQ通信日志
|
||||
zmq:
|
||||
kind: rolling_file
|
||||
path: "{{log_dir}}/log/gbt/zmq.log"
|
||||
policy:
|
||||
kind: compound
|
||||
trigger:
|
||||
kind: size
|
||||
limit: 10mb
|
||||
roller:
|
||||
kind: fixed_window
|
||||
base: 1
|
||||
count: 5
|
||||
pattern: "{{log_dir}}/log/gbt/zmq.{}.log"
|
||||
encoder:
|
||||
pattern: "{d(%Y-%m-%d %H:%M:%S.%f)} [{t}] {l:5} {m}{n}"
|
||||
|
||||
# gRPC通信日志
|
||||
grpc:
|
||||
kind: rolling_file
|
||||
path: "{{log_dir}}/log/gbt/grpc.log"
|
||||
policy:
|
||||
kind: compound
|
||||
trigger:
|
||||
kind: size
|
||||
limit: 10mb
|
||||
roller:
|
||||
kind: fixed_window
|
||||
base: 1
|
||||
count: 5
|
||||
pattern: "{{log_dir}}/log/gbt/grpc.{}.log"
|
||||
encoder:
|
||||
pattern: "{d(%Y-%m-%d %H:%M:%S.%f)} [{t}] {l:5} {m}{n}"
|
||||
|
||||
# 根日志配置
|
||||
root:
|
||||
level: warn
|
||||
appenders:
|
||||
- stdout
|
||||
|
||||
loggers:
|
||||
# GBT客户端日志
|
||||
gbt:
|
||||
level: debug
|
||||
appenders:
|
||||
- gbt
|
||||
- stdout
|
||||
additive: false
|
||||
|
||||
# ZMQ相关日志
|
||||
zmq:
|
||||
level: debug
|
||||
appenders:
|
||||
- zmq
|
||||
- stdout
|
||||
additive: false
|
||||
|
||||
# gRPC相关日志
|
||||
tonic:
|
||||
level: debug
|
||||
appenders:
|
||||
- grpc
|
||||
- stdout
|
||||
additive: false
|
||||
|
||||
# 其他第三方库日志
|
||||
tokio_util:
|
||||
level: warn
|
||||
appenders:
|
||||
- gbt
|
||||
additive: false
|
||||
|
||||
h2:
|
||||
level: warn
|
||||
appenders:
|
||||
- grpc
|
||||
additive: false
|
634
src/main.rs
634
src/main.rs
|
@ -20,22 +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 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,
|
||||
SubmitBlockResponse,
|
||||
TransactionInput, TransactionOutput, TransactionKernel,
|
||||
},
|
||||
};
|
||||
use minotari_app_utilities::parse_miner_input::BaseNodeGrpcClient;
|
||||
|
@ -56,218 +57,196 @@ use tari_core::{
|
|||
},
|
||||
};
|
||||
use tari_utilities::hex::Hex;
|
||||
use hex::FromHex;
|
||||
use tari_hashing::DomainSeparatedBorshHasher;
|
||||
use blake2::Blake2b;
|
||||
use digest::consts::U32;
|
||||
use tari_core::blocks::BlocksHashDomain;
|
||||
|
||||
const LOG_TARGET: &str = "gbt::main";
|
||||
|
||||
// 自定义的Block结构体,用于16进制序列化
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct HexBlock {
|
||||
header: Option<HexBlockHeader>,
|
||||
body: Option<serde_json::Value>, // 保持原有的body结构
|
||||
// 区块头结构
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
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, // 新增:output_smt_size字段
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct HexBlockHeader {
|
||||
#[serde(serialize_with = "serialize_bytes_as_hex")]
|
||||
hash: Vec<u8>,
|
||||
version: u32,
|
||||
height: u64,
|
||||
#[serde(serialize_with = "serialize_bytes_as_hex")]
|
||||
prev_hash: Vec<u8>,
|
||||
timestamp: u64,
|
||||
#[serde(serialize_with = "serialize_bytes_as_hex")]
|
||||
output_mr: Vec<u8>,
|
||||
#[serde(serialize_with = "serialize_bytes_as_hex")]
|
||||
block_output_mr: Vec<u8>,
|
||||
#[serde(serialize_with = "serialize_bytes_as_hex")]
|
||||
kernel_mr: Vec<u8>,
|
||||
#[serde(serialize_with = "serialize_bytes_as_hex")]
|
||||
input_mr: Vec<u8>,
|
||||
#[serde(serialize_with = "serialize_bytes_as_hex")]
|
||||
total_kernel_offset: Vec<u8>,
|
||||
nonce: u64,
|
||||
pow: HexProofOfWork,
|
||||
kernel_mmr_size: u64,
|
||||
output_mmr_size: u64,
|
||||
#[serde(serialize_with = "serialize_bytes_as_hex")]
|
||||
total_script_offset: Vec<u8>,
|
||||
#[serde(serialize_with = "serialize_bytes_as_hex")]
|
||||
validator_node_mr: Vec<u8>,
|
||||
validator_node_size: u64,
|
||||
// 工作量证明结构
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct ProofOfWork {
|
||||
pub pow_algo: u64,
|
||||
pub pow_data: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct HexProofOfWork {
|
||||
pow_algo: u64,
|
||||
#[serde(serialize_with = "serialize_bytes_as_hex")]
|
||||
pow_data: Vec<u8>,
|
||||
// 区块体结构
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct BlockBody {
|
||||
pub inputs: Vec<TransactionInput>,
|
||||
pub outputs: Vec<TransactionOutput>,
|
||||
pub kernels: Vec<TransactionKernel>,
|
||||
}
|
||||
|
||||
// 自定义序列化函数:将字节数组序列化为16进制字符串
|
||||
fn serialize_bytes_as_hex<S>(bytes: &[u8], serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
let hex_string = hex::encode(bytes);
|
||||
serializer.serialize_str(&hex_string)
|
||||
}
|
||||
|
||||
// 将Block转换为HexBlock
|
||||
fn convert_block_to_hex(block: &Block) -> Result<HexBlock> {
|
||||
let header = if let Some(ref h) = block.header {
|
||||
Some(HexBlockHeader {
|
||||
hash: h.hash.clone(),
|
||||
version: h.version,
|
||||
height: h.height,
|
||||
prev_hash: h.prev_hash.clone(),
|
||||
timestamp: h.timestamp,
|
||||
output_mr: h.output_mr.clone(),
|
||||
block_output_mr: h.block_output_mr.clone(),
|
||||
kernel_mr: h.kernel_mr.clone(),
|
||||
input_mr: h.input_mr.clone(),
|
||||
total_kernel_offset: h.total_kernel_offset.clone(),
|
||||
nonce: h.nonce,
|
||||
pow: HexProofOfWork {
|
||||
pow_algo: h.pow.as_ref().map(|p| p.pow_algo).unwrap_or(0),
|
||||
pow_data: h.pow.as_ref().map(|p| p.pow_data.clone()).unwrap_or_default(),
|
||||
},
|
||||
kernel_mmr_size: h.kernel_mmr_size,
|
||||
output_mmr_size: h.output_mmr_size,
|
||||
total_script_offset: h.total_script_offset.clone(),
|
||||
validator_node_mr: h.validator_node_mr.clone(),
|
||||
validator_node_size: h.validator_node_size,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// 将body转换为JSON值,保持原有结构
|
||||
let body_json = serde_json::to_value(&block.body).map_err(|e| anyhow!("Body serialization error: {}", e))?;
|
||||
|
||||
Ok(HexBlock {
|
||||
header,
|
||||
body: Some(body_json),
|
||||
})
|
||||
}
|
||||
|
||||
// 自定义反序列化函数:将16进制字符串反序列化为字节数组
|
||||
fn deserialize_hex_to_bytes<'de, D>(deserializer: D) -> Result<Vec<u8>, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
let hex_string = String::deserialize(deserializer)?;
|
||||
hex::FromHex::from_hex(&hex_string).map_err(|e: hex::FromHexError| serde::de::Error::custom(e.to_string()))
|
||||
}
|
||||
|
||||
// 用于反序列化的结构体
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct HexBlockDeserialize {
|
||||
header: Option<HexBlockHeaderDeserialize>,
|
||||
body: Option<serde_json::Value>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct HexBlockHeaderDeserialize {
|
||||
#[serde(deserialize_with = "deserialize_hex_to_bytes")]
|
||||
hash: Vec<u8>,
|
||||
version: u32,
|
||||
height: u64,
|
||||
#[serde(deserialize_with = "deserialize_hex_to_bytes")]
|
||||
prev_hash: Vec<u8>,
|
||||
timestamp: u64,
|
||||
#[serde(deserialize_with = "deserialize_hex_to_bytes")]
|
||||
output_mr: Vec<u8>,
|
||||
#[serde(deserialize_with = "deserialize_hex_to_bytes")]
|
||||
block_output_mr: Vec<u8>,
|
||||
#[serde(deserialize_with = "deserialize_hex_to_bytes")]
|
||||
kernel_mr: Vec<u8>,
|
||||
#[serde(deserialize_with = "deserialize_hex_to_bytes")]
|
||||
input_mr: Vec<u8>,
|
||||
#[serde(deserialize_with = "deserialize_hex_to_bytes")]
|
||||
total_kernel_offset: Vec<u8>,
|
||||
nonce: u64,
|
||||
pow: HexProofOfWorkDeserialize,
|
||||
kernel_mmr_size: u64,
|
||||
output_mmr_size: u64,
|
||||
#[serde(deserialize_with = "deserialize_hex_to_bytes")]
|
||||
total_script_offset: Vec<u8>,
|
||||
#[serde(deserialize_with = "deserialize_hex_to_bytes")]
|
||||
validator_node_mr: Vec<u8>,
|
||||
validator_node_size: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct HexProofOfWorkDeserialize {
|
||||
pow_algo: u64,
|
||||
#[serde(deserialize_with = "deserialize_hex_to_bytes")]
|
||||
pow_data: Vec<u8>,
|
||||
}
|
||||
|
||||
// 将16进制JSON转换回Block
|
||||
fn convert_hex_to_block(hex_block: &HexBlockDeserialize) -> Result<Block> {
|
||||
let header = if let Some(ref h) = hex_block.header {
|
||||
Some(minotari_app_grpc::tari_rpc::BlockHeader {
|
||||
hash: h.hash.clone(),
|
||||
version: h.version,
|
||||
height: h.height,
|
||||
prev_hash: h.prev_hash.clone(),
|
||||
timestamp: h.timestamp,
|
||||
output_mr: h.output_mr.clone(),
|
||||
block_output_mr: h.block_output_mr.clone(),
|
||||
kernel_mr: h.kernel_mr.clone(),
|
||||
input_mr: h.input_mr.clone(),
|
||||
total_kernel_offset: h.total_kernel_offset.clone(),
|
||||
nonce: h.nonce,
|
||||
pow: Some(minotari_app_grpc::tari_rpc::ProofOfWork {
|
||||
pow_algo: h.pow.pow_algo,
|
||||
pow_data: h.pow.pow_data.clone(),
|
||||
}),
|
||||
kernel_mmr_size: h.kernel_mmr_size,
|
||||
output_mmr_size: h.output_mmr_size,
|
||||
total_script_offset: h.total_script_offset.clone(),
|
||||
validator_node_mr: h.validator_node_mr.clone(),
|
||||
validator_node_size: h.validator_node_size,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// 将body从JSON值转换回原始结构
|
||||
let body = if let Some(ref body_json) = hex_block.body {
|
||||
serde_json::from_value(body_json.clone()).map_err(|e| anyhow!("Body deserialization error: {}", e))?
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Ok(Block {
|
||||
header,
|
||||
body,
|
||||
})
|
||||
}
|
||||
|
||||
// ZMQ消息结构
|
||||
// 挖矿任务结构
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct MiningTask {
|
||||
pub block_header: BlockHeader,
|
||||
pub block_body: BlockBody,
|
||||
pub output_smt_size: u64,
|
||||
pub coinbase_hash: String,
|
||||
pub height: u64,
|
||||
pub target: u64,
|
||||
pub output_smt_size: u64, // 新增:output_smt_size
|
||||
pub block_template: String, // 序列化的区块模板
|
||||
pub target: u64, // 新增:目标难度
|
||||
pub created_at: u64, // 新增:创建时间戳(Unix时间戳)
|
||||
}
|
||||
|
||||
// 挖矿消息结构
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct MiningMsg {
|
||||
pub height: u64,
|
||||
pub mining_hash: String,
|
||||
pub target: u64,
|
||||
pub block_header: BlockHeader,
|
||||
pub block_body: BlockBody,
|
||||
}
|
||||
|
||||
// 提交请求结构
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct SubmitRequest {
|
||||
pub height: u64,
|
||||
pub job_id: String,
|
||||
pub nonce: u64,
|
||||
pub solution: String,
|
||||
pub block_data: String, // 序列化的区块数据
|
||||
}
|
||||
|
||||
// 提交结果结构
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct SubmitResult {
|
||||
pub job_id: String,
|
||||
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 {
|
||||
pub base_node_grpc_address: String,
|
||||
pub base_node_http_address: String,
|
||||
pub base_node_grpc_authentication: GrpcAuthentication,
|
||||
pub base_node_grpc_tls_domain_name: Option<String>,
|
||||
pub base_node_grpc_ca_cert_filename: Option<String>,
|
||||
|
@ -283,6 +262,7 @@ pub struct GbtConfig {
|
|||
// GBT客户端
|
||||
pub struct GbtClient {
|
||||
base_node_client: BaseNodeGrpcClient,
|
||||
http_client: Client,
|
||||
key_manager: MemoryDbKeyManager,
|
||||
consensus_manager: ConsensusManager,
|
||||
wallet_payment_address: TariAddress,
|
||||
|
@ -292,10 +272,6 @@ pub struct GbtClient {
|
|||
#[allow(dead_code)]
|
||||
zmq_context: Context,
|
||||
publisher_socket: Socket,
|
||||
subscriber_socket: Socket,
|
||||
|
||||
// 挖矿任务缓存
|
||||
mining_tasks: Arc<Mutex<HashMap<String, MiningTask>>>,
|
||||
}
|
||||
|
||||
impl GbtClient {
|
||||
|
@ -303,6 +279,9 @@ impl GbtClient {
|
|||
// 创建BaseNode客户端
|
||||
let base_node_client = Self::connect_base_node(&config).await?;
|
||||
|
||||
// 创建HTTP客户端
|
||||
let http_client = Client::new();
|
||||
|
||||
// 创建密钥管理器
|
||||
let key_manager = create_memory_db_key_manager().map_err(|e| anyhow!("Key manager error: {}", e))?;
|
||||
|
||||
|
@ -320,34 +299,23 @@ 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,
|
||||
http_client,
|
||||
key_manager,
|
||||
consensus_manager,
|
||||
wallet_payment_address,
|
||||
config,
|
||||
zmq_context,
|
||||
publisher_socket,
|
||||
subscriber_socket,
|
||||
mining_tasks: Arc::new(Mutex::new(HashMap::new())),
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -389,6 +357,49 @@ impl GbtClient {
|
|||
Ok(node_conn)
|
||||
}
|
||||
|
||||
// 将gRPC Block转换为自定义结构
|
||||
fn convert_block_to_structures(&self, block: &Block) -> Result<(BlockHeader, BlockBody)> {
|
||||
let header = block.header.as_ref()
|
||||
.ok_or_else(|| anyhow!("No header in block"))?;
|
||||
|
||||
let body = block.body.as_ref()
|
||||
.ok_or_else(|| anyhow!("No body in block"))?;
|
||||
|
||||
// 构造BlockHeader
|
||||
let block_header = BlockHeader {
|
||||
hash: header.hash.to_hex(),
|
||||
version: header.version,
|
||||
height: header.height,
|
||||
prev_hash: header.prev_hash.to_hex(),
|
||||
timestamp: header.timestamp,
|
||||
output_mr: header.output_mr.to_hex(),
|
||||
block_output_mr: header.block_output_mr.to_hex(),
|
||||
kernel_mr: header.kernel_mr.to_hex(),
|
||||
input_mr: header.input_mr.to_hex(),
|
||||
total_kernel_offset: header.total_kernel_offset.to_hex(),
|
||||
nonce: 0, // 初始为0,等待后续修改
|
||||
pow: ProofOfWork {
|
||||
pow_algo: header.pow.as_ref().map(|p| p.pow_algo).unwrap_or(0),
|
||||
pow_data: header.pow.as_ref().map(|p| p.pow_data.to_hex()).unwrap_or_default(),
|
||||
},
|
||||
kernel_mmr_size: header.kernel_mmr_size,
|
||||
output_mmr_size: header.output_mmr_size,
|
||||
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 - 直接克隆原始结构体
|
||||
let block_body = BlockBody {
|
||||
inputs: body.inputs.clone(),
|
||||
outputs: body.outputs.clone(),
|
||||
kernels: body.kernels.clone(),
|
||||
};
|
||||
|
||||
Ok((block_header, block_body))
|
||||
}
|
||||
|
||||
pub async fn get_block_template_and_coinbase(&mut self) -> Result<MiningTask> {
|
||||
info!(target: LOG_TARGET, "Getting new block template");
|
||||
|
||||
|
@ -419,13 +430,6 @@ impl GbtClient {
|
|||
.ok_or_else(|| anyhow!("No header in block template"))?
|
||||
.height;
|
||||
|
||||
// 获取output_smt_size
|
||||
let output_smt_size = block_template
|
||||
.header
|
||||
.as_ref()
|
||||
.ok_or_else(|| anyhow!("No header in block template"))?
|
||||
.output_smt_size;
|
||||
|
||||
// 获取挖矿数据
|
||||
let miner_data = template_response
|
||||
.miner_data
|
||||
|
@ -433,9 +437,9 @@ impl GbtClient {
|
|||
|
||||
let fee = MicroMinotari::from(miner_data.total_fees);
|
||||
let reward = MicroMinotari::from(miner_data.reward);
|
||||
let target_difficulty = miner_data.target_difficulty;
|
||||
let target_difficulty = miner_data.target_difficulty; // 获取目标难度
|
||||
|
||||
info!(target: LOG_TARGET, "Generating coinbase for height {}", height);
|
||||
info!(target: LOG_TARGET, "Generating coinbase for height {} with target difficulty {}", height, target_difficulty);
|
||||
|
||||
// 生成coinbase
|
||||
let (coinbase_output, coinbase_kernel) = generate_coinbase(
|
||||
|
@ -469,143 +473,97 @@ impl GbtClient {
|
|||
body.kernels.push(coinbase_kernel.into());
|
||||
|
||||
// 获取完整的区块
|
||||
let block_result = self.base_node_client.get_new_block(block_template).await?.into_inner();
|
||||
let block_result = self.base_node_client.get_new_block(block_template.clone()).await?.into_inner();
|
||||
let block = block_result.block.ok_or_else(|| anyhow!("No block in response"))?;
|
||||
|
||||
// 计算coinbase哈希
|
||||
let coinbase_hash = coinbase_output.hash().to_hex();
|
||||
|
||||
// 使用自定义的16进制序列化
|
||||
let hex_block = convert_block_to_hex(&block)?;
|
||||
let block_template_json = serde_json::to_string(&hex_block).map_err(|e| anyhow!("Serialization error: {}", e))?;
|
||||
// 转换为自定义结构
|
||||
let (mut block_header, block_body) = self.convert_block_to_structures(&block)?;
|
||||
|
||||
// 先将 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);
|
||||
|
||||
// 更新block_header中的output_smt_size
|
||||
block_header.output_smt_size = current_output_smt_size;
|
||||
|
||||
let mining_task = MiningTask {
|
||||
block_header: block_header.clone(),
|
||||
block_body,
|
||||
output_smt_size: current_output_smt_size,
|
||||
coinbase_hash,
|
||||
height,
|
||||
target: target_difficulty,
|
||||
output_smt_size,
|
||||
block_template: block_template_json,
|
||||
created_at: SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs(),
|
||||
};
|
||||
|
||||
// 缓存挖矿任务
|
||||
{
|
||||
let mut tasks = self.mining_tasks.lock().await;
|
||||
tasks.insert(mining_task.coinbase_hash.clone(), mining_task.clone());
|
||||
}
|
||||
|
||||
Ok(mining_task)
|
||||
}
|
||||
|
||||
// 通过ZMQ发送挖矿任务
|
||||
pub fn send_mining_task(&self, task: &MiningTask) -> Result<()> {
|
||||
let task_json = serde_json::to_string(task).map_err(|e| anyhow!("Serialization error: {}", e))?;
|
||||
|
||||
// 发送挖矿消息
|
||||
pub fn send_mining_msg(&self, block_header: &BlockHeader, block_body: &BlockBody, target: u64) -> Result<()> {
|
||||
let mining_msg = MiningMsg {
|
||||
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_task".as_bytes(), task_json.as_bytes()], 0)
|
||||
.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 task for height {} with target {} and output_smt_size {}",
|
||||
task.height, task.target, task.output_smt_size);
|
||||
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 height {} with nonce {}",
|
||||
submit_request.height, 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<SubmitBlockResponse> {
|
||||
// 反序列化区块数据(支持16进制格式)
|
||||
let block: Block = if submit_request.block_data.contains("\"hash\":") {
|
||||
// 尝试解析为16进制格式
|
||||
let hex_block: HexBlockDeserialize = serde_json::from_str(&submit_request.block_data)
|
||||
.map_err(|e| anyhow!("Hex block deserialization error: {}", e))?;
|
||||
convert_hex_to_block(&hex_block)?
|
||||
} else {
|
||||
// 尝试解析为原始Base64格式(向后兼容)
|
||||
serde_json::from_str(&submit_request.block_data)
|
||||
.map_err(|e| anyhow!("Block deserialization error: {}", e))?
|
||||
};
|
||||
|
||||
info!(target: LOG_TARGET, "Submitting block to base node for height {}", submit_request.height);
|
||||
|
||||
// 提交区块
|
||||
let response = self.base_node_client.submit_block(block).await?;
|
||||
|
||||
info!(target: LOG_TARGET, "Block submitted successfully for height {}", submit_request.height);
|
||||
|
||||
Ok(response.into_inner())
|
||||
}
|
||||
|
||||
// 主循环
|
||||
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_task(&mining_task) {
|
||||
error!(target: LOG_TARGET, "Failed to send mining task: {}", 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 submitted block for height {}", submit_request.height);
|
||||
},
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -624,6 +582,10 @@ struct Args {
|
|||
#[arg(short, long, default_value = "127.0.0.1:18102")]
|
||||
base_node: String,
|
||||
|
||||
/// BaseNode HTTP address
|
||||
#[arg(long, default_value = "127.0.0.1:9000")]
|
||||
base_node_http: String,
|
||||
|
||||
/// Network (mainnet, nextnet, testnet)
|
||||
#[arg(short, long, default_value = "mainnet")]
|
||||
network: String,
|
||||
|
@ -641,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
|
||||
|
@ -667,17 +629,8 @@ struct Args {
|
|||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
// 初始化日志系统
|
||||
let log_config_path = PathBuf::from("log4rs_gbt.yml");
|
||||
if log_config_path.exists() {
|
||||
// 使用log4rs配置文件
|
||||
let base_path = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
|
||||
tari_common::initialize_logging(&log_config_path, &base_path, "")
|
||||
.map_err(|e| anyhow!("Failed to initialize logging: {}", e))?;
|
||||
} else {
|
||||
// 回退到env_logger
|
||||
env_logger::init();
|
||||
}
|
||||
// 初始化日志
|
||||
env_logger::init();
|
||||
|
||||
let args = Args::parse();
|
||||
|
||||
|
@ -692,6 +645,7 @@ async fn main() -> Result<()> {
|
|||
// 创建配置
|
||||
let config = GbtConfig {
|
||||
base_node_grpc_address: args.base_node,
|
||||
base_node_http_address: args.base_node_http,
|
||||
base_node_grpc_authentication: GrpcAuthentication::None,
|
||||
base_node_grpc_tls_domain_name: args.tls_domain,
|
||||
base_node_grpc_ca_cert_filename: args.tls_ca_cert,
|
||||
|
@ -713,4 +667,4 @@ async fn main() -> Result<()> {
|
|||
client.run().await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue