Compare commits

...

7 Commits
2 ... main

Author SHA1 Message Date
lzx e0f77b4b91 update 2025-07-02 17:01:00 +08:00
lzx 58d4635c8e update caculate output_smt_size 2025-06-30 13:51:05 +08:00
lzx e95771709c update hex16 and get output_smt_size 2025-06-27 18:22:48 +08:00
lzx eff35c4cb4 add output_smt_size 2025-06-26 19:39:53 +08:00
lzx 0463129165 Merge branch 'main' of http://47.129.22.53:22345/lizixuan/tari-rust into merge-2-3 2025-06-26 19:20:47 +08:00
lzx bc9ca3ece5 add base64 to hex
add output_smt_size but encording still is base64
2025-06-26 17:07:49 +08:00
lzx 93244ecf62 add output_smt_size but encording still is base64 2025-06-26 17:01:59 +08:00
6 changed files with 470 additions and 638 deletions

View File

@ -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
View File

@ -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格式的MiningMsgblock_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`,结构清晰,易于维护。

View File

@ -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项目构建完成!"

View File

@ -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?;

View File

@ -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

View File

@ -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(())
}
}