From 28aa4e4650efef08d44f3e4bb4366fdc4ee9defd Mon Sep 17 00:00:00 2001 From: lzx <393768033@qq.com> Date: Wed, 25 Jun 2025 20:34:00 +0800 Subject: [PATCH] no output_smt_size param gbt --- Cargo.toml | 70 ++++++++ README.md | 152 ++++++++++++++++ build.bat | 100 +++++++++++ build.sh | 58 ++++++ config.toml | 40 +++++ gbt_process | 14 ++ src/main.rs | 501 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 935 insertions(+) create mode 100644 Cargo.toml create mode 100644 README.md create mode 100644 build.bat create mode 100644 build.sh create mode 100644 config.toml create mode 100644 gbt_process create mode 100644 src/main.rs diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..79fcc56 --- /dev/null +++ b/Cargo.toml @@ -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", +] \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..05e0604 --- /dev/null +++ b/README.md @@ -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 \ No newline at end of file diff --git a/build.bat b/build.bat new file mode 100644 index 0000000..c01d45d --- /dev/null +++ b/build.bat @@ -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 ^ +echo. +echo 📖 更多选项: +echo target\release\gbt.exe --help +echo. +pause \ No newline at end of file diff --git a/build.sh b/build.sh new file mode 100644 index 0000000..38cc290 --- /dev/null +++ b/build.sh @@ -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项目构建完成!" \ No newline at end of file diff --git a/config.toml b/config.toml new file mode 100644 index 0000000..1b43647 --- /dev/null +++ b/config.toml @@ -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" \ No newline at end of file diff --git a/gbt_process b/gbt_process new file mode 100644 index 0000000..5957cdb --- /dev/null +++ b/gbt_process @@ -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?; \ No newline at end of file diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..3042c75 --- /dev/null +++ b/src/main.rs @@ -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, + pub base_node_grpc_ca_cert_filename: Option, + 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>>, +} + +impl GbtClient { + pub async fn new(config: GbtConfig) -> Result { + // 创建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 { + 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 { + 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> { + 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 { + // 反序列化区块数据 + 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, + + /// TLS CA certificate file + #[arg(long)] + tls_ca_cert: Option, + + /// 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(()) +}