Compare commits
6 Commits
Author | SHA1 | Date |
---|---|---|
|
e0f77b4b91 | |
|
58d4635c8e | |
|
e95771709c | |
|
eff35c4cb4 | |
|
0463129165 | |
|
bc9ca3ece5 |
|
@ -19,6 +19,11 @@ minotari_app_utilities = { path = "../applications/minotari_app_utilities", feat
|
||||||
] }
|
] }
|
||||||
minotari_app_grpc = { path = "../applications/minotari_app_grpc" }
|
minotari_app_grpc = { path = "../applications/minotari_app_grpc" }
|
||||||
tari_utilities = { version = "0.8" }
|
tari_utilities = { version = "0.8" }
|
||||||
|
jmt = { version = "0.11.0", features = ["mocks"] }
|
||||||
|
tari_hashing = { path = "../hashing" }
|
||||||
|
tari_crypto = { version = "0.18" }
|
||||||
|
blake2 = "0.10"
|
||||||
|
digest = "0.10"
|
||||||
|
|
||||||
base64 = "0.13.0"
|
base64 = "0.13.0"
|
||||||
borsh = "1.5.7"
|
borsh = "1.5.7"
|
||||||
|
@ -44,6 +49,7 @@ log4rs = { version = "1.3.0", default-features = false, features = [
|
||||||
native-tls = "0.2"
|
native-tls = "0.2"
|
||||||
num_cpus = "1.13"
|
num_cpus = "1.13"
|
||||||
rand = "0.8"
|
rand = "0.8"
|
||||||
|
reqwest = { version = "0.11", features = ["json"] }
|
||||||
serde = { version = "1.0", default-features = false, features = ["derive"] }
|
serde = { version = "1.0", default-features = false, features = ["derive"] }
|
||||||
serde_json = "1.0.57"
|
serde_json = "1.0.57"
|
||||||
thiserror = "1.0"
|
thiserror = "1.0"
|
||||||
|
@ -53,6 +59,7 @@ tokio = { version = "1.44", default-features = false, features = [
|
||||||
"time",
|
"time",
|
||||||
] }
|
] }
|
||||||
tonic = { version = "0.13.1", features = ["tls-ring", "tls-native-roots"] }
|
tonic = { version = "0.13.1", features = ["tls-ring", "tls-native-roots"] }
|
||||||
|
uuid = { version = "1.0", features = ["v4"] }
|
||||||
zmq = "0.10"
|
zmq = "0.10"
|
||||||
env_logger = "0.10"
|
env_logger = "0.10"
|
||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
|
|
283
README.md
283
README.md
|
@ -1,152 +1,203 @@
|
||||||
# Tari GBT (Get Block Template) Client
|
# Tari GBT (Get Block Template) Client
|
||||||
|
|
||||||
这是一个独立的Tari GetBlockTemplate客户端,用于获取区块模板、构造coinbase交易,并通过ZMQ与外部挖矿程序通信。
|
这是一个Tari区块链的GetBlockTemplate客户端,支持ZMQ通信协议,用于挖矿池和矿工之间的通信。
|
||||||
|
|
||||||
## 功能特性
|
## 功能特性
|
||||||
|
|
||||||
- ✅ 连接Tari BaseNode获取区块模板
|
- 通过gRPC从BaseNode获取区块模板
|
||||||
- ✅ 自动构造coinbase交易
|
- 自动生成coinbase交易
|
||||||
- ✅ 通过ZMQ发送挖矿任务
|
- 支持SHA3X挖矿算法
|
||||||
- ✅ 接收外部挖矿结果
|
- ZMQ推送标准JSON格式的挖矿任务
|
||||||
- ✅ 提交完成的区块到BaseNode
|
- 每秒获取一次模板,高度变立即推送,否则每5秒推送一次最新模板
|
||||||
- ✅ 支持多种网络(mainnet、nextnet、testnet)
|
|
||||||
- ✅ 支持TLS加密连接
|
|
||||||
- ✅ 命令行参数配置
|
|
||||||
|
|
||||||
## 编译
|
## 数据结构
|
||||||
|
|
||||||
```bash
|
### BlockHeader(区块头)
|
||||||
# 在gbt目录下编译
|
```rust
|
||||||
cargo build --release
|
pub struct BlockHeader {
|
||||||
|
pub hash: String,
|
||||||
# 编译后的可执行文件位于 target/release/gbt
|
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,
|
||||||
```bash
|
pub input_mr: String,
|
||||||
# 连接到本地BaseNode
|
pub total_kernel_offset: String,
|
||||||
./target/release/gbt --wallet-address <钱包地址>
|
pub nonce: u64,
|
||||||
|
pub pow: ProofOfWork,
|
||||||
# 指定BaseNode地址
|
pub kernel_mmr_size: u64,
|
||||||
./target/release/gbt --base-node 192.168.1.100:18142 --wallet-address <钱包地址>
|
pub output_mmr_size: u64,
|
||||||
|
pub total_script_offset: String,
|
||||||
# 指定网络
|
pub validator_node_mr: String,
|
||||||
./target/release/gbt --network testnet --wallet-address <钱包地址>
|
pub validator_node_size: u64,
|
||||||
```
|
pub output_smt_size: u64,
|
||||||
|
|
||||||
### 完整参数
|
|
||||||
|
|
||||||
```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": "{...序列化的区块模板...}"
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### 接收的提交请求(submit)
|
### ProofOfWork
|
||||||
|
```rust
|
||||||
|
pub struct ProofOfWork {
|
||||||
|
pub pow_algo: u64,
|
||||||
|
pub pow_data: String,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
```json
|
### BlockBody(区块体)
|
||||||
{
|
```rust
|
||||||
"height": 12345,
|
pub struct BlockBody {
|
||||||
"nonce": 67890,
|
pub inputs: Vec<TransactionInput>,
|
||||||
"solution": "solution_hash...",
|
pub outputs: Vec<TransactionOutput>,
|
||||||
"block_data": "{...序列化的区块数据...}"
|
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连接
|
1. 每秒从BaseNode获取一次区块模板。
|
||||||
2. **获取区块模板**: 从BaseNode获取新区块模板
|
2. 若区块高度变化,立即推送新任务;否则每5秒推送一次最新模板。
|
||||||
3. **构造coinbase**: 生成coinbase交易并添加到区块模板
|
3. 通过ZMQ发布标准JSON格式的MiningMsg消息,block_body为JSON对象。
|
||||||
4. **发送挖矿任务**: 通过ZMQ发布挖矿任务给外部挖矿程序
|
4. 任务缓存、job_id、submit相关逻辑已移除,代码结构更简洁。
|
||||||
5. **接收挖矿结果**: 通过ZMQ接收外部挖矿程序提交的结果
|
|
||||||
6. **提交区块**: 将完成的区块提交给BaseNode
|
|
||||||
7. **循环**: 重复上述过程
|
|
||||||
|
|
||||||
## 依赖要求
|
## 配置参数
|
||||||
|
|
||||||
- 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
|
```bash
|
||||||
./target/release/gbt --network mainnet --wallet-address <主网钱包地址>
|
cargo build --release
|
||||||
```
|
```
|
||||||
|
|
||||||
### Nextnet
|
### 运行
|
||||||
```bash
|
```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
|
## ZMQ消息格式
|
||||||
```bash
|
|
||||||
./target/release/gbt --network testnet --wallet-address <测试网钱包地址>
|
### 挖矿任务消息
|
||||||
```
|
```
|
||||||
|
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
|
||||||
|
|
||||||
## 故障排除
|
## 故障排除
|
||||||
|
|
||||||
### 连接错误
|
1. **连接BaseNode失败**
|
||||||
- 确保BaseNode正在运行
|
- 检查BaseNode是否运行
|
||||||
- 检查gRPC端口是否正确
|
- 验证gRPC地址和端口
|
||||||
- 确认BaseNode已启用gRPC服务
|
- 检查网络配置
|
||||||
|
|
||||||
### ZMQ错误
|
2. **HTTP请求失败**
|
||||||
|
- 检查BaseNode HTTP服务
|
||||||
|
- 验证HTTP地址和端口
|
||||||
|
- 检查网络连接
|
||||||
|
|
||||||
|
3. **ZMQ通信失败**
|
||||||
- 检查ZMQ端口是否被占用
|
- 检查ZMQ端口是否被占用
|
||||||
- 确保外部挖矿程序正确连接到ZMQ端口
|
- 验证防火墙设置
|
||||||
|
- 检查ZMQ库安装
|
||||||
|
|
||||||
### 钱包地址错误
|
4. **任务提交失败**
|
||||||
- 确保钱包地址格式正确
|
- 检查任务ID是否有效
|
||||||
- 检查钱包地址是否属于指定网络
|
- 验证nonce和solution格式
|
||||||
|
- 检查BaseNode状态
|
||||||
|
|
||||||
## 开发
|
## 开发
|
||||||
|
|
||||||
### 添加新功能
|
如需扩展功能,请直接修改`src/main.rs`,结构清晰,易于维护。
|
||||||
1. 修改`src/main.rs`
|
|
||||||
2. 更新依赖项(如需要)
|
|
||||||
3. 重新编译
|
|
||||||
|
|
||||||
### 调试
|
|
||||||
```bash
|
|
||||||
# 启用详细日志
|
|
||||||
RUST_LOG=debug cargo run -- --wallet-address <地址>
|
|
||||||
```
|
|
||||||
|
|
||||||
## 许可证
|
|
||||||
|
|
||||||
BSD-3-Clause License
|
|
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?;
|
|
472
src/main.rs
472
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
|
// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
|
||||||
// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
use std::{collections::HashMap, path::PathBuf, sync::Arc, time::Duration};
|
use std::{path::PathBuf, time::{SystemTime, UNIX_EPOCH}};
|
||||||
|
use tokio::time::sleep;
|
||||||
|
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use log::*;
|
use log::*;
|
||||||
|
use reqwest::Client;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use tokio::{sync::Mutex, time::sleep};
|
|
||||||
use tonic::transport::{Certificate, ClientTlsConfig, Endpoint};
|
use tonic::transport::{Certificate, ClientTlsConfig, Endpoint};
|
||||||
use zmq::{Context, Message, Socket};
|
use zmq::{Context, Socket};
|
||||||
|
|
||||||
use minotari_app_grpc::{
|
use minotari_app_grpc::{
|
||||||
authentication::ClientAuthenticationInterceptor,
|
authentication::ClientAuthenticationInterceptor,
|
||||||
conversions::transaction_output::grpc_output_with_payref,
|
conversions::transaction_output::grpc_output_with_payref,
|
||||||
tari_rpc::{
|
tari_rpc::{
|
||||||
base_node_client::BaseNodeClient, pow_algo::PowAlgos, Block, NewBlockTemplateRequest, PowAlgo,
|
base_node_client::BaseNodeClient, pow_algo::PowAlgos, Block, NewBlockTemplateRequest, PowAlgo,
|
||||||
SubmitBlockResponse,
|
TransactionInput, TransactionOutput, TransactionKernel,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use minotari_app_utilities::parse_miner_input::BaseNodeGrpcClient;
|
use minotari_app_utilities::parse_miner_input::BaseNodeGrpcClient;
|
||||||
|
@ -56,36 +57,196 @@ use tari_core::{
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use tari_utilities::hex::Hex;
|
use tari_utilities::hex::Hex;
|
||||||
use tari_utilities::ByteArray;
|
use tari_hashing::DomainSeparatedBorshHasher;
|
||||||
use jmt::{JellyfishMerkleTree, KeyHash};
|
use blake2::Blake2b;
|
||||||
use jmt::mock::MockTreeStore;
|
use digest::consts::U32;
|
||||||
use tari_core::chain_storage::SmtHasher;
|
use tari_core::blocks::BlocksHashDomain;
|
||||||
use tari_core::blocks::Block as CoreBlock;
|
|
||||||
|
|
||||||
const LOG_TARGET: &str = "gbt::main";
|
const LOG_TARGET: &str = "gbt::main";
|
||||||
|
|
||||||
// ZMQ消息结构
|
// 区块头结构
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
pub struct MiningTask {
|
pub struct BlockHeader {
|
||||||
pub coinbase_hash: String,
|
pub hash: String,
|
||||||
|
pub version: u32,
|
||||||
pub height: u64,
|
pub height: u64,
|
||||||
pub target: u64,
|
pub prev_hash: String,
|
||||||
pub output_smt_size: u64, // 新增:output_smt_size
|
pub timestamp: u64,
|
||||||
pub block_template: String, // 序列化的区块模板
|
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, Clone)]
|
||||||
|
pub struct ProofOfWork {
|
||||||
|
pub pow_algo: u64,
|
||||||
|
pub pow_data: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 区块体结构
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
|
pub struct BlockBody {
|
||||||
|
pub inputs: Vec<TransactionInput>,
|
||||||
|
pub outputs: Vec<TransactionOutput>,
|
||||||
|
pub kernels: Vec<TransactionKernel>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 挖矿任务结构
|
||||||
|
#[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 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)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
pub struct SubmitRequest {
|
pub struct SubmitRequest {
|
||||||
pub height: u64,
|
pub job_id: String,
|
||||||
pub nonce: u64,
|
pub nonce: u64,
|
||||||
pub solution: String,
|
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)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct GbtConfig {
|
pub struct GbtConfig {
|
||||||
pub base_node_grpc_address: String,
|
pub base_node_grpc_address: String,
|
||||||
|
pub base_node_http_address: String,
|
||||||
pub base_node_grpc_authentication: GrpcAuthentication,
|
pub base_node_grpc_authentication: GrpcAuthentication,
|
||||||
pub base_node_grpc_tls_domain_name: Option<String>,
|
pub base_node_grpc_tls_domain_name: Option<String>,
|
||||||
pub base_node_grpc_ca_cert_filename: Option<String>,
|
pub base_node_grpc_ca_cert_filename: Option<String>,
|
||||||
|
@ -101,6 +262,7 @@ pub struct GbtConfig {
|
||||||
// GBT客户端
|
// GBT客户端
|
||||||
pub struct GbtClient {
|
pub struct GbtClient {
|
||||||
base_node_client: BaseNodeGrpcClient,
|
base_node_client: BaseNodeGrpcClient,
|
||||||
|
http_client: Client,
|
||||||
key_manager: MemoryDbKeyManager,
|
key_manager: MemoryDbKeyManager,
|
||||||
consensus_manager: ConsensusManager,
|
consensus_manager: ConsensusManager,
|
||||||
wallet_payment_address: TariAddress,
|
wallet_payment_address: TariAddress,
|
||||||
|
@ -110,10 +272,6 @@ pub struct GbtClient {
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
zmq_context: Context,
|
zmq_context: Context,
|
||||||
publisher_socket: Socket,
|
publisher_socket: Socket,
|
||||||
subscriber_socket: Socket,
|
|
||||||
|
|
||||||
// 挖矿任务缓存
|
|
||||||
mining_tasks: Arc<Mutex<HashMap<String, MiningTask>>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GbtClient {
|
impl GbtClient {
|
||||||
|
@ -121,6 +279,9 @@ impl GbtClient {
|
||||||
// 创建BaseNode客户端
|
// 创建BaseNode客户端
|
||||||
let base_node_client = Self::connect_base_node(&config).await?;
|
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))?;
|
let key_manager = create_memory_db_key_manager().map_err(|e| anyhow!("Key manager error: {}", e))?;
|
||||||
|
|
||||||
|
@ -138,34 +299,23 @@ impl GbtClient {
|
||||||
let publisher_socket = zmq_context
|
let publisher_socket = zmq_context
|
||||||
.socket(zmq::PUB)
|
.socket(zmq::PUB)
|
||||||
.map_err(|e| anyhow!("ZMQ publisher error: {}", e))?;
|
.map_err(|e| anyhow!("ZMQ publisher error: {}", e))?;
|
||||||
let subscriber_socket = zmq_context
|
|
||||||
.socket(zmq::SUB)
|
|
||||||
.map_err(|e| anyhow!("ZMQ subscriber error: {}", e))?;
|
|
||||||
|
|
||||||
// 绑定ZMQ套接字
|
// 绑定ZMQ套接字
|
||||||
let publisher_addr = format!("tcp://*:{}", config.zmq_publisher_port);
|
let publisher_addr = format!("tcp://*:{}", config.zmq_publisher_port);
|
||||||
let subscriber_addr = format!("tcp://localhost:{}", config.zmq_subscriber_port);
|
|
||||||
|
|
||||||
publisher_socket
|
publisher_socket
|
||||||
.bind(&publisher_addr)
|
.bind(&publisher_addr)
|
||||||
.map_err(|e| anyhow!("ZMQ bind error: {}", e))?;
|
.map_err(|e| anyhow!("ZMQ bind error: {}", e))?;
|
||||||
subscriber_socket
|
|
||||||
.connect(&subscriber_addr)
|
|
||||||
.map_err(|e| anyhow!("ZMQ connect error: {}", e))?;
|
|
||||||
subscriber_socket
|
|
||||||
.set_subscribe(b"submit")
|
|
||||||
.map_err(|e| anyhow!("ZMQ subscribe error: {}", e))?;
|
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
base_node_client,
|
base_node_client,
|
||||||
|
http_client,
|
||||||
key_manager,
|
key_manager,
|
||||||
consensus_manager,
|
consensus_manager,
|
||||||
wallet_payment_address,
|
wallet_payment_address,
|
||||||
config,
|
config,
|
||||||
zmq_context,
|
zmq_context,
|
||||||
publisher_socket,
|
publisher_socket,
|
||||||
subscriber_socket,
|
|
||||||
mining_tasks: Arc::new(Mutex::new(HashMap::new())),
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -207,44 +357,47 @@ impl GbtClient {
|
||||||
Ok(node_conn)
|
Ok(node_conn)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 计算output_smt_size
|
// 将gRPC Block转换为自定义结构
|
||||||
fn calculate_output_smt_size(&self, block: &CoreBlock, prev_output_smt_size: u64) -> Result<u64> {
|
fn convert_block_to_structures(&self, block: &Block) -> Result<(BlockHeader, BlockBody)> {
|
||||||
// 创建JellyfishMerkleTree用于计算
|
let header = block.header.as_ref()
|
||||||
let mock_store = MockTreeStore::new(true);
|
.ok_or_else(|| anyhow!("No header in block"))?;
|
||||||
let output_smt = JellyfishMerkleTree::<_, SmtHasher>::new(&mock_store);
|
|
||||||
|
|
||||||
let mut batch = Vec::new();
|
let body = block.body.as_ref()
|
||||||
|
.ok_or_else(|| anyhow!("No body in block"))?;
|
||||||
|
|
||||||
// 处理所有输出(添加新的叶子节点)
|
// 构造BlockHeader
|
||||||
for output in block.body.outputs() {
|
let block_header = BlockHeader {
|
||||||
if !output.is_burned() {
|
hash: header.hash.to_hex(),
|
||||||
let smt_key = KeyHash(
|
version: header.version,
|
||||||
output.commitment.as_bytes().try_into().expect("commitment is 32 bytes")
|
height: header.height,
|
||||||
);
|
prev_hash: header.prev_hash.to_hex(),
|
||||||
let smt_value = output.smt_hash(block.header.height);
|
timestamp: header.timestamp,
|
||||||
batch.push((smt_key, Some(smt_value.to_vec())));
|
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 - 直接克隆原始结构体
|
||||||
for input in block.body.inputs() {
|
let block_body = BlockBody {
|
||||||
let smt_key = KeyHash(
|
inputs: body.inputs.clone(),
|
||||||
input.commitment()?.as_bytes().try_into().expect("Commitment is 32 bytes")
|
outputs: body.outputs.clone(),
|
||||||
);
|
kernels: body.kernels.clone(),
|
||||||
batch.push((smt_key, None));
|
};
|
||||||
}
|
|
||||||
|
|
||||||
// 计算SMT变化
|
Ok((block_header, block_body))
|
||||||
let (_, changes) = output_smt
|
|
||||||
.put_value_set(batch, block.header.height)
|
|
||||||
.map_err(|e| anyhow!("SMT calculation error: {}", e))?;
|
|
||||||
|
|
||||||
// 计算新的output_smt_size
|
|
||||||
let mut size = prev_output_smt_size;
|
|
||||||
size += changes.node_stats.first().map(|s| s.new_leaves).unwrap_or(0) as u64;
|
|
||||||
size = size.saturating_sub(changes.node_stats.first().map(|s| s.stale_leaves).unwrap_or(0) as u64);
|
|
||||||
|
|
||||||
Ok(size)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_block_template_and_coinbase(&mut self) -> Result<MiningTask> {
|
pub async fn get_block_template_and_coinbase(&mut self) -> Result<MiningTask> {
|
||||||
|
@ -284,9 +437,9 @@ impl GbtClient {
|
||||||
|
|
||||||
let fee = MicroMinotari::from(miner_data.total_fees);
|
let fee = MicroMinotari::from(miner_data.total_fees);
|
||||||
let reward = MicroMinotari::from(miner_data.reward);
|
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
|
// 生成coinbase
|
||||||
let (coinbase_output, coinbase_kernel) = generate_coinbase(
|
let (coinbase_output, coinbase_kernel) = generate_coinbase(
|
||||||
|
@ -326,145 +479,91 @@ impl GbtClient {
|
||||||
// 计算coinbase哈希
|
// 计算coinbase哈希
|
||||||
let coinbase_hash = coinbase_output.hash().to_hex();
|
let coinbase_hash = coinbase_output.hash().to_hex();
|
||||||
|
|
||||||
// 将gRPC Block转换为CoreBlock以便计算output_smt_size
|
// 转换为自定义结构
|
||||||
let core_block: CoreBlock = block.clone().try_into()
|
let (mut block_header, block_body) = self.convert_block_to_structures(&block)?;
|
||||||
.map_err(|e| anyhow!("Block conversion error: {}", e))?;
|
|
||||||
|
|
||||||
// 获取前一个区块的output_smt_size(从区块模板头中获取)
|
// 先将 outputs 和 inputs 序列化为 JSON 字符串
|
||||||
let prev_output_smt_size = block_template
|
let output_jsons: Vec<String> = block_body.outputs.iter()
|
||||||
.header
|
.map(|o| serde_json::to_string(o).unwrap_or_default())
|
||||||
.as_ref()
|
.collect();
|
||||||
.ok_or_else(|| anyhow!("No header in block template"))?
|
let input_jsons: Vec<String> = block_body.inputs.iter()
|
||||||
.output_smt_size;
|
.map(|i| serde_json::to_string(i).unwrap_or_default())
|
||||||
|
.collect();
|
||||||
|
let current_output_smt_size = calculate_output_smt_size(0, &output_jsons, &input_jsons);
|
||||||
|
|
||||||
// 计算新的output_smt_size
|
// 更新block_header中的output_smt_size
|
||||||
let calculated_output_smt_size = self.calculate_output_smt_size(&core_block, prev_output_smt_size)?;
|
block_header.output_smt_size = current_output_smt_size;
|
||||||
|
|
||||||
info!(target: LOG_TARGET, "Calculated output_smt_size: {} (prev: {})",
|
|
||||||
calculated_output_smt_size, prev_output_smt_size);
|
|
||||||
|
|
||||||
// 序列化区块模板
|
|
||||||
let block_template_json = serde_json::to_string(&block).map_err(|e| anyhow!("Serialization error: {}", e))?;
|
|
||||||
|
|
||||||
let mining_task = MiningTask {
|
let mining_task = MiningTask {
|
||||||
|
block_header: block_header.clone(),
|
||||||
|
block_body,
|
||||||
|
output_smt_size: current_output_smt_size,
|
||||||
coinbase_hash,
|
coinbase_hash,
|
||||||
height,
|
|
||||||
target: target_difficulty,
|
target: target_difficulty,
|
||||||
output_smt_size: calculated_output_smt_size, // 使用计算出的值
|
created_at: SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs(),
|
||||||
block_template: block_template_json,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 缓存挖矿任务
|
|
||||||
{
|
|
||||||
let mut tasks = self.mining_tasks.lock().await;
|
|
||||||
tasks.insert(mining_task.coinbase_hash.clone(), mining_task.clone());
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(mining_task)
|
Ok(mining_task)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 通过ZMQ发送挖矿任务
|
// 发送挖矿消息
|
||||||
pub fn send_mining_task(&self, task: &MiningTask) -> Result<()> {
|
pub fn send_mining_msg(&self, block_header: &BlockHeader, block_body: &BlockBody, target: u64) -> Result<()> {
|
||||||
let task_json = serde_json::to_string(task).map_err(|e| anyhow!("Serialization error: {}", e))?;
|
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
|
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))?;
|
.map_err(|e| anyhow!("ZMQ send error: {}", e))?;
|
||||||
|
info!(target: LOG_TARGET, "Sent mining message for height {}, target {}", block_header.height, target);
|
||||||
info!(target: LOG_TARGET, "Sent mining task for height {} with target {} and output_smt_size {}",
|
|
||||||
task.height, task.target, task.output_smt_size);
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
// 接收外部提交的挖矿结果
|
|
||||||
pub async fn receive_submit(&mut self) -> Result<Option<SubmitRequest>> {
|
|
||||||
let mut message = Message::new();
|
|
||||||
|
|
||||||
// 非阻塞接收
|
|
||||||
match self.subscriber_socket.recv(&mut message, zmq::DONTWAIT) {
|
|
||||||
Ok(_) => {
|
|
||||||
let message_str = message.as_str().ok_or_else(|| anyhow!("Message decode error"))?;
|
|
||||||
|
|
||||||
if message_str.starts_with("submit ") {
|
|
||||||
let submit_json = &message_str[7..]; // 去掉"submit "前缀
|
|
||||||
let submit_request: SubmitRequest =
|
|
||||||
serde_json::from_str(submit_json).map_err(|e| anyhow!("Deserialization error: {}", e))?;
|
|
||||||
|
|
||||||
info!(target: LOG_TARGET, "Received submit for 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> {
|
|
||||||
// 反序列化区块数据
|
|
||||||
let block: Block = 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<()> {
|
pub async fn run(&mut self) -> Result<()> {
|
||||||
info!(target: LOG_TARGET, "Starting GBT client");
|
info!(target: LOG_TARGET, "Starting GBT client");
|
||||||
|
let mut last_height: Option<u64> = None;
|
||||||
|
let mut last_send_time: u64 = 0;
|
||||||
loop {
|
loop {
|
||||||
// 1. 获取区块模板和构造coinbase
|
// 1. 每秒请求一次模板
|
||||||
match self.get_block_template_and_coinbase().await {
|
let mining_task = match self.get_block_template_and_coinbase().await {
|
||||||
Ok(mining_task) => {
|
Ok(task) => task,
|
||||||
// 2. 通过ZMQ发送挖矿任务
|
|
||||||
if let Err(e) = self.send_mining_task(&mining_task) {
|
|
||||||
error!(target: LOG_TARGET, "Failed to send mining task: {}", e);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!(target: LOG_TARGET, "Failed to get block template: {}", e);
|
error!(target: LOG_TARGET, "Failed to get block template: {}", e);
|
||||||
sleep(Duration::from_secs(5)).await;
|
sleep(std::time::Duration::from_secs(1)).await;
|
||||||
continue;
|
continue;
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
// 3. 接收外部提交
|
let current_height = mining_task.block_header.height;
|
||||||
match self.receive_submit().await {
|
let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs();
|
||||||
Ok(Some(submit_request)) => {
|
let mut should_send = false;
|
||||||
// 4. 提交区块到BaseNode
|
match last_height {
|
||||||
match self.submit_block_to_base_node(&submit_request).await {
|
None => {
|
||||||
Ok(_) => {
|
should_send = true;
|
||||||
info!(target: LOG_TARGET, "Successfully submitted block for height {}", submit_request.height);
|
info!(target: LOG_TARGET, "First template, sending immediately");
|
||||||
},
|
|
||||||
Err(e) => {
|
|
||||||
error!(target: LOG_TARGET, "Failed to submit block: {}", e);
|
|
||||||
},
|
},
|
||||||
|
Some(prev_height) => {
|
||||||
|
if current_height != prev_height {
|
||||||
|
should_send = true;
|
||||||
|
info!(target: LOG_TARGET, "Height changed: {} -> {}, sending immediately", prev_height, current_height);
|
||||||
|
} else if now >= last_send_time + 5 {
|
||||||
|
should_send = true;
|
||||||
|
info!(target: LOG_TARGET, "Same height {}, sending after 5 seconds", current_height);
|
||||||
}
|
}
|
||||||
},
|
|
||||||
Ok(None) => {
|
|
||||||
// 没有提交,继续循环
|
|
||||||
},
|
|
||||||
Err(e) => {
|
|
||||||
error!(target: LOG_TARGET, "Failed to receive submit: {}", e);
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// 等待一段时间再获取下一个区块模板
|
if should_send {
|
||||||
sleep(Duration::from_secs(1)).await;
|
if let Err(e) = self.send_mining_msg(&mining_task.block_header, &mining_task.block_body, mining_task.target) {
|
||||||
|
error!(target: LOG_TARGET, "Failed to send mining message: {}", e);
|
||||||
|
} else {
|
||||||
|
last_height = Some(current_height);
|
||||||
|
last_send_time = now;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sleep(std::time::Duration::from_secs(1)).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -483,6 +582,10 @@ struct Args {
|
||||||
#[arg(short, long, default_value = "127.0.0.1:18102")]
|
#[arg(short, long, default_value = "127.0.0.1:18102")]
|
||||||
base_node: String,
|
base_node: String,
|
||||||
|
|
||||||
|
/// BaseNode HTTP address
|
||||||
|
#[arg(long, default_value = "127.0.0.1:9000")]
|
||||||
|
base_node_http: String,
|
||||||
|
|
||||||
/// Network (mainnet, nextnet, testnet)
|
/// Network (mainnet, nextnet, testnet)
|
||||||
#[arg(short, long, default_value = "mainnet")]
|
#[arg(short, long, default_value = "mainnet")]
|
||||||
network: String,
|
network: String,
|
||||||
|
@ -500,11 +603,11 @@ struct Args {
|
||||||
coinbase_extra: String,
|
coinbase_extra: String,
|
||||||
|
|
||||||
/// ZMQ publisher port
|
/// ZMQ publisher port
|
||||||
#[arg(long, default_value = "5555")]
|
#[arg(long, default_value = "31000")]
|
||||||
zmq_pub_port: u16,
|
zmq_pub_port: u16,
|
||||||
|
|
||||||
/// ZMQ subscriber port
|
/// ZMQ subscriber port
|
||||||
#[arg(long, default_value = "5556")]
|
#[arg(long, default_value = "31001")]
|
||||||
zmq_sub_port: u16,
|
zmq_sub_port: u16,
|
||||||
|
|
||||||
/// Enable TLS
|
/// Enable TLS
|
||||||
|
@ -542,6 +645,7 @@ async fn main() -> Result<()> {
|
||||||
// 创建配置
|
// 创建配置
|
||||||
let config = GbtConfig {
|
let config = GbtConfig {
|
||||||
base_node_grpc_address: args.base_node,
|
base_node_grpc_address: args.base_node,
|
||||||
|
base_node_http_address: args.base_node_http,
|
||||||
base_node_grpc_authentication: GrpcAuthentication::None,
|
base_node_grpc_authentication: GrpcAuthentication::None,
|
||||||
base_node_grpc_tls_domain_name: args.tls_domain,
|
base_node_grpc_tls_domain_name: args.tls_domain,
|
||||||
base_node_grpc_ca_cert_filename: args.tls_ca_cert,
|
base_node_grpc_ca_cert_filename: args.tls_ca_cert,
|
||||||
|
|
Loading…
Reference in New Issue