no output_smt_size param gbt
This commit is contained in:
commit
28aa4e4650
|
@ -0,0 +1,70 @@
|
|||
[package]
|
||||
name = "gbt"
|
||||
authors = ["The Tari Development Community"]
|
||||
description = "Tari GetBlockTemplate client with ZMQ communication"
|
||||
repository = "https://github.com/tari-project/tari"
|
||||
license = "BSD-3-Clause"
|
||||
version = "1.0.0"
|
||||
edition = "2021"
|
||||
|
||||
[workspace]
|
||||
|
||||
[dependencies]
|
||||
tari_core = { path = "../base_layer/core", default-features = false }
|
||||
tari_common = { path = "../common" }
|
||||
tari_common_types = { path = "../base_layer/common_types" }
|
||||
tari_max_size = { path = "../infrastructure/max_size" }
|
||||
minotari_app_utilities = { path = "../applications/minotari_app_utilities", features = [
|
||||
"miner_input",
|
||||
] }
|
||||
minotari_app_grpc = { path = "../applications/minotari_app_grpc" }
|
||||
tari_utilities = { version = "0.8" }
|
||||
|
||||
base64 = "0.13.0"
|
||||
borsh = "1.5.7"
|
||||
bufstream = "0.1"
|
||||
chrono = { version = "0.4.39", default-features = false }
|
||||
clap = { version = "4.0", features = ["derive"] }
|
||||
crossbeam = "0.8"
|
||||
crossterm = { version = "0.28" }
|
||||
derivative = "2.2.0"
|
||||
futures = "0.3"
|
||||
hex = "0.4.2"
|
||||
log = { version = "0.4", features = ["std"] }
|
||||
log4rs = { version = "1.3.0", default-features = false, features = [
|
||||
"config_parsing",
|
||||
"threshold_filter",
|
||||
"yaml_format",
|
||||
"console_appender",
|
||||
"rolling_file_appender",
|
||||
"compound_policy",
|
||||
"size_trigger",
|
||||
"fixed_window_roller",
|
||||
] }
|
||||
native-tls = "0.2"
|
||||
num_cpus = "1.13"
|
||||
rand = "0.8"
|
||||
serde = { version = "1.0", default-features = false, features = ["derive"] }
|
||||
serde_json = "1.0.57"
|
||||
thiserror = "1.0"
|
||||
tokio = { version = "1.44", default-features = false, features = [
|
||||
"rt-multi-thread",
|
||||
"fs",
|
||||
"time",
|
||||
] }
|
||||
tonic = { version = "0.13.1", features = ["tls-ring", "tls-native-roots"] }
|
||||
zmq = "0.10"
|
||||
env_logger = "0.10"
|
||||
anyhow = "1.0"
|
||||
|
||||
[dev-dependencies]
|
||||
prost-types = "0.13.3"
|
||||
chrono = { version = "0.4.39", default-features = false }
|
||||
config = "0.14.0"
|
||||
|
||||
[package.metadata.cargo-machete]
|
||||
ignored = [
|
||||
# We need to specify extra features for log4rs even though it is not used directly in this crate
|
||||
"log4rs",
|
||||
"prost",
|
||||
]
|
|
@ -0,0 +1,152 @@
|
|||
# Tari GBT (GetBlockTemplate) Client
|
||||
|
||||
这是一个独立的Tari GetBlockTemplate客户端,用于获取区块模板、构造coinbase交易,并通过ZMQ与外部挖矿程序通信。
|
||||
|
||||
## 功能特性
|
||||
|
||||
- ✅ 连接Tari BaseNode获取区块模板
|
||||
- ✅ 自动构造coinbase交易
|
||||
- ✅ 通过ZMQ发送挖矿任务
|
||||
- ✅ 接收外部挖矿结果
|
||||
- ✅ 提交完成的区块到BaseNode
|
||||
- ✅ 支持多种网络(mainnet、nextnet、testnet)
|
||||
- ✅ 支持TLS加密连接
|
||||
- ✅ 命令行参数配置
|
||||
|
||||
## 编译
|
||||
|
||||
```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": "{...序列化的区块模板...}"
|
||||
}
|
||||
```
|
||||
|
||||
### 接收的提交请求(submit)
|
||||
|
||||
```json
|
||||
{
|
||||
"height": 12345,
|
||||
"nonce": 67890,
|
||||
"solution": "solution_hash...",
|
||||
"block_data": "{...序列化的区块数据...}"
|
||||
}
|
||||
```
|
||||
|
||||
## 工作流程
|
||||
|
||||
1. **连接BaseNode**: 建立与Tari BaseNode的gRPC连接
|
||||
2. **获取区块模板**: 从BaseNode获取新区块模板
|
||||
3. **构造coinbase**: 生成coinbase交易并添加到区块模板
|
||||
4. **发送挖矿任务**: 通过ZMQ发布挖矿任务给外部挖矿程序
|
||||
5. **接收挖矿结果**: 通过ZMQ接收外部挖矿程序提交的结果
|
||||
6. **提交区块**: 将完成的区块提交给BaseNode
|
||||
7. **循环**: 重复上述过程
|
||||
|
||||
## 依赖要求
|
||||
|
||||
- Rust 1.70+
|
||||
- Tari BaseNode运行中并启用gRPC
|
||||
- 有效的Tari钱包地址
|
||||
|
||||
## 网络配置
|
||||
|
||||
### Mainnet
|
||||
```bash
|
||||
./target/release/gbt --network mainnet --wallet-address <主网钱包地址>
|
||||
```
|
||||
|
||||
### Nextnet
|
||||
```bash
|
||||
./target/release/gbt --network nextnet --wallet-address <测试网钱包地址>
|
||||
```
|
||||
|
||||
### Testnet
|
||||
```bash
|
||||
./target/release/gbt --network testnet --wallet-address <测试网钱包地址>
|
||||
```
|
||||
|
||||
## 故障排除
|
||||
|
||||
### 连接错误
|
||||
- 确保BaseNode正在运行
|
||||
- 检查gRPC端口是否正确
|
||||
- 确认BaseNode已启用gRPC服务
|
||||
|
||||
### ZMQ错误
|
||||
- 检查ZMQ端口是否被占用
|
||||
- 确保外部挖矿程序正确连接到ZMQ端口
|
||||
|
||||
### 钱包地址错误
|
||||
- 确保钱包地址格式正确
|
||||
- 检查钱包地址是否属于指定网络
|
||||
|
||||
## 开发
|
||||
|
||||
### 添加新功能
|
||||
1. 修改`src/main.rs`
|
||||
2. 更新依赖项(如需要)
|
||||
3. 重新编译
|
||||
|
||||
### 调试
|
||||
```bash
|
||||
# 启用详细日志
|
||||
RUST_LOG=debug cargo run -- --wallet-address <地址>
|
||||
```
|
||||
|
||||
## 许可证
|
||||
|
||||
BSD-3-Clause License
|
|
@ -0,0 +1,100 @@
|
|||
@echo off
|
||||
setlocal enabledelayedexpansion
|
||||
|
||||
REM GBT项目构建脚本 (Windows版本)
|
||||
|
||||
echo 🚀 开始构建GBT项目...
|
||||
|
||||
REM 检查Rust环境
|
||||
where cargo >nul 2>&1
|
||||
if %errorlevel% neq 0 (
|
||||
echo ❌ 错误: 未找到cargo,请先安装Rust
|
||||
echo 访问: https://rustup.rs/
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
REM 检查Rust版本
|
||||
for /f "tokens=2" %%i in ('rustc --version') do set RUST_VERSION=%%i
|
||||
echo 📦 Rust版本: %RUST_VERSION%
|
||||
|
||||
REM 检查是否在正确的目录
|
||||
if not exist "Cargo.toml" (
|
||||
echo ❌ 错误: 未找到Cargo.toml,请在gbt目录下运行此脚本
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
REM 清理之前的构建
|
||||
echo 🧹 清理之前的构建...
|
||||
cargo clean
|
||||
if %errorlevel% neq 0 (
|
||||
echo ❌ 清理失败
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
REM 更新依赖
|
||||
echo 📥 更新依赖...
|
||||
cargo update
|
||||
if %errorlevel% neq 0 (
|
||||
echo ❌ 更新依赖失败
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
REM 检查代码
|
||||
echo 🔍 检查代码...
|
||||
cargo check
|
||||
if %errorlevel% neq 0 (
|
||||
echo ❌ 代码检查失败
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
REM 运行测试
|
||||
echo 🧪 运行测试...
|
||||
cargo test
|
||||
if %errorlevel% neq 0 (
|
||||
echo ❌ 测试失败
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
REM 构建发布版本
|
||||
echo 🔨 构建发布版本...
|
||||
cargo build --release
|
||||
if %errorlevel% neq 0 (
|
||||
echo ❌ 构建失败
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
REM 检查构建结果
|
||||
if exist "target\release\gbt.exe" (
|
||||
echo ✅ 构建成功!
|
||||
echo 📁 可执行文件位置: target\release\gbt.exe
|
||||
|
||||
REM 显示文件信息
|
||||
echo 📊 文件信息:
|
||||
dir target\release\gbt.exe
|
||||
|
||||
REM 显示版本信息
|
||||
echo ℹ️ 版本信息:
|
||||
target\release\gbt.exe --version 2>nul || echo 无法获取版本信息
|
||||
|
||||
) else (
|
||||
echo ❌ 构建失败!
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
echo 🎉 GBT项目构建完成!
|
||||
echo.
|
||||
echo 📖 使用方法:
|
||||
echo target\release\gbt.exe --wallet-address ^<YOUR_WALLET_ADDRESS^>
|
||||
echo.
|
||||
echo 📖 更多选项:
|
||||
echo target\release\gbt.exe --help
|
||||
echo.
|
||||
pause
|
|
@ -0,0 +1,58 @@
|
|||
#!/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项目构建完成!"
|
|
@ -0,0 +1,40 @@
|
|||
# GBT Client Configuration
|
||||
|
||||
[base_node]
|
||||
# BaseNode gRPC地址
|
||||
grpc_address = "127.0.0.1:18102"
|
||||
|
||||
# 网络类型 (mainnet, nextnet, testnet)
|
||||
network = "mainnet"
|
||||
|
||||
# gRPC认证方式 (None, JWT)
|
||||
authentication = "None"
|
||||
|
||||
# TLS配置
|
||||
tls_enabled = false
|
||||
tls_domain_name = ""
|
||||
tls_ca_cert_filename = ""
|
||||
|
||||
[mining]
|
||||
# 钱包支付地址
|
||||
wallet_payment_address = "14H4atSbXqSLFHDvhjx83ASCJDv3iCDu4T6DotCiCVCYq67koEJbgcbmYpeBpRjcZdRYtJ5CDw9gWRNXpe8chfnQSVU"
|
||||
|
||||
# coinbase额外数据
|
||||
coinbase_extra = "m2pool.com"
|
||||
|
||||
# 范围证明类型
|
||||
range_proof_type = "BulletProofPlus"
|
||||
|
||||
[zmq]
|
||||
# ZMQ发布端口
|
||||
publisher_port = 5555
|
||||
|
||||
# ZMQ订阅端口
|
||||
subscriber_port = 5556
|
||||
|
||||
[logging]
|
||||
# 日志级别 (trace, debug, info, warn, error)
|
||||
level = "info"
|
||||
|
||||
# 日志文件路径
|
||||
file_path = "gbt.log"
|
|
@ -0,0 +1,14 @@
|
|||
// 自建矿池直接与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?;
|
|
@ -0,0 +1,501 @@
|
|||
// Copyright 2024. The Tari Project
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
|
||||
// following conditions are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following
|
||||
// disclaimer.
|
||||
//
|
||||
// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
|
||||
// following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
//
|
||||
// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote
|
||||
// products derived from this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
|
||||
// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
// 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 anyhow::{anyhow, Result};
|
||||
use clap::Parser;
|
||||
use log::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::{sync::Mutex, time::sleep};
|
||||
use tonic::transport::{Certificate, ClientTlsConfig, Endpoint};
|
||||
use zmq::{Context, Message, 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,
|
||||
},
|
||||
};
|
||||
use minotari_app_utilities::parse_miner_input::BaseNodeGrpcClient;
|
||||
use std::str::FromStr;
|
||||
use tari_common::configuration::Network;
|
||||
use tari_common::MAX_GRPC_MESSAGE_SIZE;
|
||||
use tari_common_types::{grpc_authentication::GrpcAuthentication, tari_address::TariAddress};
|
||||
use tari_core::{
|
||||
consensus::ConsensusManager,
|
||||
transactions::{
|
||||
generate_coinbase,
|
||||
tari_amount::MicroMinotari,
|
||||
transaction_components::{
|
||||
encrypted_data::{PaymentId, TxType},
|
||||
CoinBaseExtra, RangeProofType,
|
||||
},
|
||||
transaction_key_manager::{create_memory_db_key_manager, MemoryDbKeyManager},
|
||||
},
|
||||
};
|
||||
use tari_utilities::hex::Hex;
|
||||
|
||||
const LOG_TARGET: &str = "gbt::main";
|
||||
|
||||
// ZMQ消息结构
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct MiningTask {
|
||||
pub coinbase_hash: String,
|
||||
pub height: u64,
|
||||
pub target: u64,
|
||||
pub block_template: String, // 序列化的区块模板
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct SubmitRequest {
|
||||
pub height: u64,
|
||||
pub nonce: u64,
|
||||
pub solution: String,
|
||||
pub block_data: String, // 序列化的区块数据
|
||||
}
|
||||
|
||||
// 配置结构
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct GbtConfig {
|
||||
pub base_node_grpc_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>,
|
||||
pub config_dir: PathBuf,
|
||||
pub network: Network,
|
||||
pub wallet_payment_address: String,
|
||||
pub coinbase_extra: String,
|
||||
pub range_proof_type: RangeProofType,
|
||||
pub zmq_publisher_port: u16,
|
||||
pub zmq_subscriber_port: u16,
|
||||
}
|
||||
|
||||
// GBT客户端
|
||||
pub struct GbtClient {
|
||||
base_node_client: BaseNodeGrpcClient,
|
||||
key_manager: MemoryDbKeyManager,
|
||||
consensus_manager: ConsensusManager,
|
||||
wallet_payment_address: TariAddress,
|
||||
config: GbtConfig,
|
||||
|
||||
// ZMQ相关
|
||||
#[allow(dead_code)]
|
||||
zmq_context: Context,
|
||||
publisher_socket: Socket,
|
||||
subscriber_socket: Socket,
|
||||
|
||||
// 挖矿任务缓存
|
||||
mining_tasks: Arc<Mutex<HashMap<String, MiningTask>>>,
|
||||
}
|
||||
|
||||
impl GbtClient {
|
||||
pub async fn new(config: GbtConfig) -> Result<Self> {
|
||||
// 创建BaseNode客户端
|
||||
let base_node_client = Self::connect_base_node(&config).await?;
|
||||
|
||||
// 创建密钥管理器
|
||||
let key_manager = create_memory_db_key_manager().map_err(|e| anyhow!("Key manager error: {}", e))?;
|
||||
|
||||
// 创建共识管理器
|
||||
let consensus_manager = ConsensusManager::builder(config.network)
|
||||
.build()
|
||||
.map_err(|e| anyhow!("Consensus manager error: {}", e))?;
|
||||
|
||||
// 解析钱包地址
|
||||
let wallet_payment_address = TariAddress::from_str(&config.wallet_payment_address)
|
||||
.map_err(|e| anyhow!("Invalid wallet address: {}", e))?;
|
||||
|
||||
// 创建ZMQ上下文和套接字
|
||||
let zmq_context = Context::new();
|
||||
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,
|
||||
key_manager,
|
||||
consensus_manager,
|
||||
wallet_payment_address,
|
||||
config,
|
||||
zmq_context,
|
||||
publisher_socket,
|
||||
subscriber_socket,
|
||||
mining_tasks: Arc::new(Mutex::new(HashMap::new())),
|
||||
})
|
||||
}
|
||||
|
||||
// 连接BaseNode
|
||||
async fn connect_base_node(config: &GbtConfig) -> Result<BaseNodeGrpcClient> {
|
||||
info!(target: LOG_TARGET, "Connecting to base node at {}", config.base_node_grpc_address);
|
||||
let address = format!("http://{}", config.base_node_grpc_address);
|
||||
let mut endpoint = Endpoint::new(address)?;
|
||||
|
||||
// 配置TLS(如果需要)
|
||||
if let Some(domain_name) = config.base_node_grpc_tls_domain_name.as_ref() {
|
||||
if let Some(cert_filename) = config.base_node_grpc_ca_cert_filename.as_ref() {
|
||||
let cert_path = config.config_dir.join(cert_filename);
|
||||
let pem = tokio::fs::read(cert_path)
|
||||
.await
|
||||
.map_err(|e| anyhow!("TLS certificate read error: {}", e))?;
|
||||
let ca = Certificate::from_pem(pem);
|
||||
|
||||
let tls = ClientTlsConfig::new().ca_certificate(ca).domain_name(domain_name);
|
||||
endpoint = endpoint
|
||||
.tls_config(tls)
|
||||
.map_err(|e| anyhow!("TLS config error: {}", e))?;
|
||||
}
|
||||
}
|
||||
|
||||
let channel = endpoint
|
||||
.connect()
|
||||
.await
|
||||
.map_err(|e| anyhow!("Connection error: {}", e))?;
|
||||
|
||||
let node_conn = BaseNodeClient::with_interceptor(
|
||||
channel,
|
||||
ClientAuthenticationInterceptor::create(&config.base_node_grpc_authentication)
|
||||
.map_err(|e| anyhow!("Authentication error: {}", e))?,
|
||||
)
|
||||
.max_encoding_message_size(MAX_GRPC_MESSAGE_SIZE)
|
||||
.max_decoding_message_size(MAX_GRPC_MESSAGE_SIZE);
|
||||
|
||||
Ok(node_conn)
|
||||
}
|
||||
|
||||
pub async fn get_block_template_and_coinbase(&mut self) -> Result<MiningTask> {
|
||||
info!(target: LOG_TARGET, "Getting new block template");
|
||||
|
||||
// 获取区块模板
|
||||
let pow_algo = PowAlgo {
|
||||
pow_algo: PowAlgos::Sha3x.into(),
|
||||
};
|
||||
|
||||
let request = NewBlockTemplateRequest {
|
||||
algo: Some(pow_algo),
|
||||
max_weight: 0,
|
||||
};
|
||||
|
||||
let template_response = self
|
||||
.base_node_client
|
||||
.get_new_block_template(request)
|
||||
.await?
|
||||
.into_inner();
|
||||
|
||||
let mut block_template = template_response
|
||||
.new_block_template
|
||||
.clone()
|
||||
.ok_or_else(|| anyhow!("No block template received"))?;
|
||||
|
||||
let height = block_template
|
||||
.header
|
||||
.as_ref()
|
||||
.ok_or_else(|| anyhow!("No header in block template"))?
|
||||
.height;
|
||||
|
||||
// 获取挖矿数据
|
||||
let miner_data = template_response
|
||||
.miner_data
|
||||
.ok_or_else(|| anyhow!("No miner data received"))?;
|
||||
|
||||
let fee = MicroMinotari::from(miner_data.total_fees);
|
||||
let reward = MicroMinotari::from(miner_data.reward);
|
||||
let target_difficulty = miner_data.target_difficulty;
|
||||
|
||||
info!(target: LOG_TARGET, "Generating coinbase for height {}", height);
|
||||
|
||||
// 生成coinbase
|
||||
let (coinbase_output, coinbase_kernel) = generate_coinbase(
|
||||
fee,
|
||||
reward,
|
||||
height,
|
||||
&CoinBaseExtra::try_from(self.config.coinbase_extra.as_bytes().to_vec())?,
|
||||
&self.key_manager,
|
||||
&self.wallet_payment_address,
|
||||
true,
|
||||
self.consensus_manager.consensus_constants(height),
|
||||
self.config.range_proof_type,
|
||||
PaymentId::Open {
|
||||
user_data: vec![],
|
||||
tx_type: TxType::Coinbase,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.map_err(|e| anyhow!("Coinbase generation error: {}", e))?;
|
||||
|
||||
// 将coinbase添加到区块模板
|
||||
let body = block_template
|
||||
.body
|
||||
.as_mut()
|
||||
.ok_or_else(|| anyhow!("No body in block template"))?;
|
||||
|
||||
let grpc_output = grpc_output_with_payref(coinbase_output.clone(), None)
|
||||
.map_err(|e| anyhow!("Output conversion error: {}", e))?;
|
||||
|
||||
body.outputs.push(grpc_output);
|
||||
body.kernels.push(coinbase_kernel.into());
|
||||
|
||||
// 获取完整的区块
|
||||
let block_result = self.base_node_client.get_new_block(block_template).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();
|
||||
|
||||
// 序列化区块模板
|
||||
let block_template_json = serde_json::to_string(&block).map_err(|e| anyhow!("Serialization error: {}", e))?;
|
||||
|
||||
let mining_task = MiningTask {
|
||||
coinbase_hash,
|
||||
height,
|
||||
target: target_difficulty,
|
||||
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)
|
||||
}
|
||||
|
||||
// 通过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))?;
|
||||
|
||||
self.publisher_socket
|
||||
.send_multipart(&["mining_task".as_bytes(), task_json.as_bytes()], 0)
|
||||
.map_err(|e| anyhow!("ZMQ send error: {}", e))?;
|
||||
|
||||
info!(target: LOG_TARGET, "Sent mining task for height {} with target {}", task.height, task.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> {
|
||||
// 反序列化区块数据
|
||||
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<()> {
|
||||
info!(target: LOG_TARGET, "Starting GBT client");
|
||||
|
||||
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);
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
error!(target: LOG_TARGET, "Failed to get block template: {}", e);
|
||||
sleep(Duration::from_secs(5)).await;
|
||||
continue;
|
||||
},
|
||||
}
|
||||
|
||||
// 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);
|
||||
},
|
||||
}
|
||||
},
|
||||
Ok(None) => {
|
||||
// 没有提交,继续循环
|
||||
},
|
||||
Err(e) => {
|
||||
error!(target: LOG_TARGET, "Failed to receive submit: {}", e);
|
||||
},
|
||||
}
|
||||
|
||||
// 等待一段时间再获取下一个区块模板
|
||||
sleep(Duration::from_secs(1)).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for GbtClient {
|
||||
fn drop(&mut self) {
|
||||
info!(target: LOG_TARGET, "GBT client shutting down");
|
||||
// ZMQ套接字会在Context销毁时自动关闭
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(author, version, about, long_about = None)]
|
||||
struct Args {
|
||||
/// BaseNode gRPC address
|
||||
#[arg(short, long, default_value = "127.0.0.1:18102")]
|
||||
base_node: String,
|
||||
|
||||
/// Network (mainnet, nextnet, testnet)
|
||||
#[arg(short, long, default_value = "mainnet")]
|
||||
network: String,
|
||||
|
||||
/// Wallet payment address
|
||||
#[arg(
|
||||
short,
|
||||
long,
|
||||
default_value = "14H4atSbXqSLFHDvhjx83ASCJDv3iCDu4T6DotCiCVCYq67koEJbgcbmYpeBpRjcZdRYtJ5CDw9gWRNXpe8chfnQSVU"
|
||||
)]
|
||||
wallet_address: String,
|
||||
|
||||
/// Coinbase extra data
|
||||
#[arg(short, long, default_value = "m2pool.com")]
|
||||
coinbase_extra: String,
|
||||
|
||||
/// ZMQ publisher port
|
||||
#[arg(long, default_value = "5555")]
|
||||
zmq_pub_port: u16,
|
||||
|
||||
/// ZMQ subscriber port
|
||||
#[arg(long, default_value = "5556")]
|
||||
zmq_sub_port: u16,
|
||||
|
||||
/// Enable TLS
|
||||
#[arg(long)]
|
||||
tls: bool,
|
||||
|
||||
/// TLS domain name
|
||||
#[arg(long)]
|
||||
tls_domain: Option<String>,
|
||||
|
||||
/// TLS CA certificate file
|
||||
#[arg(long)]
|
||||
tls_ca_cert: Option<String>,
|
||||
|
||||
/// Config directory
|
||||
#[arg(long, default_value = ".")]
|
||||
config_dir: String,
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
// 初始化日志
|
||||
env_logger::init();
|
||||
|
||||
let args = Args::parse();
|
||||
|
||||
// 解析网络
|
||||
let network = match args.network.as_str() {
|
||||
"mainnet" => Network::MainNet,
|
||||
"nextnet" => Network::NextNet,
|
||||
"testnet" => Network::NextNet, // 使用NextNet作为testnet
|
||||
_ => return Err(anyhow!("Invalid network: {}", args.network)),
|
||||
};
|
||||
|
||||
// 创建配置
|
||||
let config = GbtConfig {
|
||||
base_node_grpc_address: args.base_node,
|
||||
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,
|
||||
config_dir: PathBuf::from(args.config_dir),
|
||||
network,
|
||||
wallet_payment_address: args.wallet_address,
|
||||
coinbase_extra: args.coinbase_extra,
|
||||
range_proof_type: RangeProofType::BulletProofPlus,
|
||||
zmq_publisher_port: args.zmq_pub_port,
|
||||
zmq_subscriber_port: args.zmq_sub_port,
|
||||
};
|
||||
|
||||
info!(target: LOG_TARGET, "Starting GBT client with network: {:?}", network);
|
||||
|
||||
// 创建GBT客户端
|
||||
let mut client = GbtClient::new(config).await?;
|
||||
|
||||
// 运行客户端
|
||||
client.run().await?;
|
||||
|
||||
Ok(())
|
||||
}
|
Loading…
Reference in New Issue