add tari-gbt(rust)
This commit is contained in:
68
internal/gbt/tari/minotari_miner/Cargo.toml
Normal file
68
internal/gbt/tari/minotari_miner/Cargo.toml
Normal file
@@ -0,0 +1,68 @@
|
||||
[package]
|
||||
name = "minotari_miner"
|
||||
authors = ["The Tari Development Community"]
|
||||
description = "The tari miner implementation"
|
||||
repository = "https://github.com/tari-project/tari"
|
||||
license = "BSD-3-Clause"
|
||||
version = "4.9.0"
|
||||
edition = "2018"
|
||||
|
||||
[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 = "../minotari_app_utilities", features = [
|
||||
"miner_input",
|
||||
] }
|
||||
minotari_app_grpc = { path = "../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 = "3.2", 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",
|
||||
] }
|
||||
tonic = { version = "0.13.1", features = ["tls-ring", "tls-native-roots"] }
|
||||
zmq = "0.10"
|
||||
anyhow = "1.0"
|
||||
|
||||
[dev-dependencies]
|
||||
prost-types = "0.13.3"
|
||||
chrono = { version = "0.4.39", default-features = false }
|
||||
config = "0.14.0"
|
||||
|
||||
zmq = "0.10"
|
||||
anyhow = "1.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",
|
||||
]
|
||||
39
internal/gbt/tari/minotari_miner/README.md
Normal file
39
internal/gbt/tari/minotari_miner/README.md
Normal file
@@ -0,0 +1,39 @@
|
||||
# Standalone miner for the Minotari network
|
||||
|
||||
The Minotari Miner application should be running connected to the Minotari Base Node and the Minotari Console Wallet,
|
||||
allowing it insert the coinbase transactions created by the Minotari Wallet and to to mine blocks matching the
|
||||
required network difficulty. This also serves as a reference implementation for custom miners that which can be derived
|
||||
from it.
|
||||
|
||||
### Installation
|
||||
|
||||
Please refer to the relevant section in the main
|
||||
[README installation section](https://github.com/tari-project/tari/blob/development/README.md#install-and-run).
|
||||
|
||||
### Configuration
|
||||
|
||||
When running with local versions of the Minotari Base Node and the Minotari Wallet, no additional configuration other
|
||||
than what is described in the main
|
||||
[README configuration section](https://github.com/tari-project/tari/blob/development/README.md#tari-sha3-mining)
|
||||
is required. The Minotari Miner can also be located on a remote workstation.
|
||||
|
||||
Configuration options for the Minotari Miner are as follows:
|
||||
|
||||
- `base_node_grpc_address` - this is IPv4/IPv6 address including port number, by which the Minotari Base Node can be found;
|
||||
- `num_mining_threads` - the number of mining threads, which defaults to the number of CPU cores;
|
||||
- `mine_on_tip_only` - mining will only start when the Minotari Base Node reports it is in the bootstrapped state;
|
||||
- `proof_of_work_algo` - The proof of work algorithm to use
|
||||
- `validate_tip_timeout_sec` - the interval at which the current block height will be checked to determine if mining
|
||||
must be restarted, whereby the tip might have advanced passed the block height that is in use in the current template.
|
||||
- `mining_pool_address` - Stratum Mode configuration - mining pool address
|
||||
- `mining_wallet_address` - `Stratum Mode configuration - mining wallet address/public key`
|
||||
- `mining_worker_name` - `Stratum Mode configuration - mining worker name`
|
||||
- `coinbase_extra` - Note that this data is publicly readable, but it is suggested you populate it so that pool
|
||||
dominance can be seen before any one party has more than 51%.
|
||||
- `network` - "Selected network"
|
||||
- `wait_timeout_on_error` - "Base node reconnect timeout after any gRPC or miner error"
|
||||
- `wallet_payment_address` - "The Tari wallet address where the mining funds will be sent to"
|
||||
|
||||
### Caveats
|
||||
|
||||
Currently, the Minotari Miner only supports SHA3 mining; this is adequate for the current Tari protocol.
|
||||
33
internal/gbt/tari/minotari_miner/build.rs
Normal file
33
internal/gbt/tari/minotari_miner/build.rs
Normal file
@@ -0,0 +1,33 @@
|
||||
// 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.
|
||||
|
||||
#[cfg(windows)]
|
||||
fn main() {
|
||||
use std::env;
|
||||
println!("cargo:rerun-if-changed=icon.res");
|
||||
let mut path = env::current_dir().unwrap();
|
||||
path.push("icon.res");
|
||||
println!("cargo:rustc-link-arg={}", path.into_os_string().into_string().unwrap());
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
fn main() {}
|
||||
BIN
internal/gbt/tari/minotari_miner/icon.ico
Normal file
BIN
internal/gbt/tari/minotari_miner/icon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 279 KiB |
1
internal/gbt/tari/minotari_miner/icon.rc
Normal file
1
internal/gbt/tari/minotari_miner/icon.rc
Normal file
@@ -0,0 +1 @@
|
||||
miner ICON "icon.ico"
|
||||
BIN
internal/gbt/tari/minotari_miner/icon.res
Normal file
BIN
internal/gbt/tari/minotari_miner/icon.res
Normal file
Binary file not shown.
@@ -0,0 +1,53 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
#
|
||||
echo
|
||||
echo "Starting Miner"
|
||||
echo
|
||||
|
||||
# Initialize
|
||||
if [ -z "${use_parent_paths}" ]
|
||||
then
|
||||
base_path="$( cd "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )"
|
||||
config_path="${base_path}/config"
|
||||
exe_path="${base_path}/runtime"
|
||||
fi
|
||||
"${exe_path}/start_tor.sh"
|
||||
|
||||
if [ ! -f "${config_path}/log4rs_miner.yml" ]
|
||||
then
|
||||
echo Creating new "${config_path}/log4rs_miner.yml";
|
||||
init_flag="--init"
|
||||
else
|
||||
echo Using existing "${config_path}/log4rs_miner.yml";
|
||||
init_flag=""
|
||||
fi
|
||||
echo
|
||||
|
||||
# Run
|
||||
echo Spawning Miner into new terminal..
|
||||
echo
|
||||
gnome-terminal --working-directory="$PWD" -- "${exe_path}/minotari_miner" ${init_flag} --config "${config_path}/config.toml" --log_config "${config_path}/log4rs_miner.yml" --base-path ${base_path}
|
||||
@@ -0,0 +1 @@
|
||||
./runtime/start_minotari_miner.sh
|
||||
62
internal/gbt/tari/minotari_miner/log4rs_sample.yml
Normal file
62
internal/gbt/tari/minotari_miner/log4rs_sample.yml
Normal file
@@ -0,0 +1,62 @@
|
||||
# A sample log configuration file for running in release mode. By default, this configuration splits up log messages to
|
||||
# three destinations:
|
||||
# * Console: For log messages with level INFO and higher
|
||||
# * log/miner/network.log: INFO-level logs related to the comms crate. This file will be quite busy since there
|
||||
# are lots of P2P debug messages, and so this traffic is segregated from the application log messages
|
||||
#
|
||||
# See https://docs.rs/log4rs/0.8.3/log4rs/encode/pattern/index.html for deciphering the log pattern. The log format
|
||||
# used in this sample configuration prints messages as:
|
||||
# timestamp [target] LEVEL message
|
||||
refresh_rate: 30 seconds
|
||||
appenders:
|
||||
# An appender named "stdout" that writes to stdout
|
||||
stdout:
|
||||
kind: console
|
||||
encoder:
|
||||
pattern: "{d(%H:%M)} {h({l}):5} {m}{n}"
|
||||
filters:
|
||||
- kind: threshold
|
||||
level: info
|
||||
# An appender named "base_layer" that writes to a file with a custom pattern encoder
|
||||
miner:
|
||||
kind: rolling_file
|
||||
path: "{{log_dir}}/log/miner/miner.log"
|
||||
policy:
|
||||
kind: compound
|
||||
trigger:
|
||||
kind: size
|
||||
limit: 10mb
|
||||
roller:
|
||||
kind: fixed_window
|
||||
base: 1
|
||||
count: 5
|
||||
pattern: "{{log_dir}}/log/miner/miner.{}.log"
|
||||
encoder:
|
||||
pattern: "{d(%Y-%m-%d %H:%M:%S.%f)} [{t}] [Thread:{I}] {l:5} {m}{n}"
|
||||
|
||||
# Set the default logging level to "warn" and attach the "stdout" appender to the root
|
||||
root:
|
||||
level: warn
|
||||
appenders:
|
||||
- stdout
|
||||
|
||||
loggers:
|
||||
# miner
|
||||
minotari::application:
|
||||
level: debug
|
||||
appenders:
|
||||
- miner
|
||||
- stdout
|
||||
additive: false
|
||||
minotari::miner:
|
||||
level: debug
|
||||
appenders:
|
||||
- miner
|
||||
- stdout
|
||||
additive: false
|
||||
minotari_miner:
|
||||
level: debug
|
||||
appenders:
|
||||
- miner
|
||||
- stdout
|
||||
additive: false
|
||||
@@ -0,0 +1,63 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
#
|
||||
echo
|
||||
echo "Starting Miner"
|
||||
echo
|
||||
|
||||
# Initialize
|
||||
if [ -z "${use_parent_paths}" ]
|
||||
then
|
||||
base_path="$( cd "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )"
|
||||
config_path="${base_path}/config"
|
||||
exe_path="${base_path}/runtime"
|
||||
fi
|
||||
|
||||
tor_running=$(lsof -nP -iTCP:9050)
|
||||
if [ -z "${tor_running}" ]
|
||||
then
|
||||
echo "Starting Tor"
|
||||
open -a Terminal.app "${exe_path}/start_tor.sh"
|
||||
ping -c 15 localhost > /dev/null
|
||||
fi
|
||||
|
||||
if [ ! -f "${config_path}/log4rs_miner.yml" ]
|
||||
then
|
||||
echo Creating new "${config_path}/log4rs_miner.yml";
|
||||
init_flag="--init"
|
||||
else
|
||||
echo Using existing "${config_path}/log4rs_miner.yml";
|
||||
init_flag=""
|
||||
fi
|
||||
echo
|
||||
|
||||
# Run
|
||||
echo Spawning Console Wallet into new terminal..
|
||||
echo "${exe_path}/minotari_miner" ${init_flag} --config="${config_path}/config.toml" --log_config="${config_path}/log4rs_miner.yml" --base-path=${base_path} > $exe_path/tari_miner_command.sh
|
||||
chmod +x $exe_path/minotari_miner_command.sh
|
||||
|
||||
open -a terminal $exe_path/minotari_miner_command.sh
|
||||
echo
|
||||
@@ -0,0 +1 @@
|
||||
./runtime/start_minotari_miner.sh
|
||||
70
internal/gbt/tari/minotari_miner/src/cli.rs
Normal file
70
internal/gbt/tari/minotari_miner/src/cli.rs
Normal file
@@ -0,0 +1,70 @@
|
||||
// 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 clap::Parser;
|
||||
use minotari_app_utilities::common_cli_args::CommonCliArgs;
|
||||
use tari_common::configuration::{ConfigOverrideProvider, Network};
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[clap(author, version, about, long_about = None)]
|
||||
#[clap(propagate_version = true)]
|
||||
pub struct Cli {
|
||||
#[clap(flatten)]
|
||||
pub common: CommonCliArgs,
|
||||
#[clap(long, alias = "mine-until-height")]
|
||||
pub mine_until_height: Option<u64>,
|
||||
#[clap(long, alias = "max-blocks")]
|
||||
pub miner_max_blocks: Option<u64>,
|
||||
#[clap(long, alias = "min-difficulty")]
|
||||
pub miner_min_diff: Option<u64>,
|
||||
#[clap(long, alias = "max-difficulty")]
|
||||
pub miner_max_diff: Option<u64>,
|
||||
#[clap(short, long, alias = "non-interactive", env = "TARI_NON_INTERACTIVE")]
|
||||
pub non_interactive_mode: bool,
|
||||
|
||||
|
||||
#[clap(long, alias = "zmq-pub-port", default_value = "11113")]
|
||||
zmq_pub_port: u16,
|
||||
#[clap(long, alias = "zmq-sub-port", default_value = "11112")]
|
||||
zmq_sub_port: u16,
|
||||
}
|
||||
|
||||
impl ConfigOverrideProvider for Cli {
|
||||
/// Get the configuration property overrides for the given network. In case of duplicates, the final override
|
||||
/// added to the list will have preference.
|
||||
fn get_config_property_overrides(&self, network: &Network) -> Vec<(String, String)> {
|
||||
// Config file overrides
|
||||
let mut overrides = vec![("miner.network".to_string(), network.to_string())];
|
||||
// Command-line overrides
|
||||
let command_line_overrides = self.common.get_config_property_overrides(network);
|
||||
command_line_overrides.iter().for_each(|(k, v)| {
|
||||
replace_or_add_override(&mut overrides, k, v);
|
||||
});
|
||||
overrides
|
||||
}
|
||||
}
|
||||
|
||||
fn replace_or_add_override(overrides: &mut Vec<(String, String)>, key: &str, value: &str) {
|
||||
if let Some(index) = overrides.iter().position(|(k, _)| k == key) {
|
||||
overrides.remove(index);
|
||||
}
|
||||
overrides.push((key.to_string(), value.to_string()));
|
||||
}
|
||||
184
internal/gbt/tari/minotari_miner/src/config.rs
Normal file
184
internal/gbt/tari/minotari_miner/src/config.rs
Normal file
@@ -0,0 +1,184 @@
|
||||
// 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. Minotari Miner Node derives all
|
||||
// configuration management
|
||||
//! from [tari_common] crate, also extending with few
|
||||
//! specific options:
|
||||
//! - base_node_grpc_address - is IPv4/IPv6 address including port number, by which Minotari Base Node can be found
|
||||
//! - num_mining_threads - number of mining threads, defaults to number of cpu cores
|
||||
//! - mine_on_tip_only - will start mining only when node is reporting bootstrapped state
|
||||
//! - validate_tip_timeout_sec - will check tip with node every N seconds to validate that still mining on a tip All
|
||||
//! miner options configured under `[miner]` section of Minotari's `config.toml`.
|
||||
|
||||
use std::{
|
||||
path::{Path, PathBuf},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use minotari_app_grpc::tari_rpc::{pow_algo::PowAlgos, NewBlockTemplateRequest, PowAlgo};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tari_common::{
|
||||
configuration::{utils::deserialize_string_or_struct, Network},
|
||||
SubConfigPath,
|
||||
};
|
||||
use tari_common_types::{grpc_authentication::GrpcAuthentication, tari_address::TariAddress};
|
||||
use tari_core::{proof_of_work::PowAlgorithm, transactions::transaction_components::RangeProofType};
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct MinerConfig {
|
||||
/// gRPC address of base node
|
||||
pub base_node_grpc_address: Option<String>,
|
||||
/// GRPC authentication for base node
|
||||
pub base_node_grpc_authentication: GrpcAuthentication,
|
||||
/// GRPC domain name for node TLS validation
|
||||
pub base_node_grpc_tls_domain_name: Option<String>,
|
||||
/// GRPC ca cert name for TLS
|
||||
pub base_node_grpc_ca_cert_filename: String,
|
||||
/// Number of mining threads
|
||||
pub num_mining_threads: usize,
|
||||
/// Start mining only when base node is bootstrapped and current block height is on the tip of network
|
||||
pub mine_on_tip_only: bool,
|
||||
/// The proof of work algorithm to use (Sha3x or RandomXT)
|
||||
#[serde(deserialize_with = "deserialize_string_or_struct")]
|
||||
pub proof_of_work_algo: PowAlgorithm,
|
||||
/// Will check tip with node every N seconds and restart mining if height already taken and option
|
||||
/// `mine_on_tip_only` is set to true
|
||||
pub validate_tip_timeout_sec: u64,
|
||||
/// Stratum Mode configuration - mining pool address
|
||||
pub stratum_mining_pool_address: String,
|
||||
/// Stratum Mode configuration - mining wallet address/public key
|
||||
pub stratum_mining_wallet_address: String,
|
||||
/// Stratum Mode configuration - mining worker name
|
||||
pub mining_worker_name: String,
|
||||
/// The extra data to store in the coinbase, usually some data about the mining pool.
|
||||
/// Note that this data is publicly readable, but it is suggested you populate it so that
|
||||
/// pool dominance can be seen before any one party has more than 51%.
|
||||
pub coinbase_extra: String,
|
||||
/// Selected network
|
||||
pub network: Network,
|
||||
/// Base node reconnect timeout after any gRPC or miner error
|
||||
pub wait_timeout_on_error: u64,
|
||||
/// The relative path to store persistent config
|
||||
pub config_dir: PathBuf,
|
||||
/// The Tari wallet address (valid address in hex) where the mining funds will be sent to - must be assigned
|
||||
pub wallet_payment_address: String,
|
||||
/// Range proof type - revealed_value or bullet_proof_plus: (default = revealed_value)
|
||||
pub range_proof_type: RangeProofType,
|
||||
/// SHA based p2pool decentralized mining enabled or not
|
||||
pub sha_p2pool_enabled: bool,
|
||||
|
||||
pub zmq_publisher_port: u16,
|
||||
pub zmq_subscriber_port: u16,
|
||||
}
|
||||
|
||||
impl SubConfigPath for MinerConfig {
|
||||
fn main_key_prefix() -> &'static str {
|
||||
"miner"
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for MinerConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
base_node_grpc_address: None,
|
||||
base_node_grpc_authentication: GrpcAuthentication::default(),
|
||||
base_node_grpc_tls_domain_name: None,
|
||||
base_node_grpc_ca_cert_filename: "node_ca.pem".to_string(),
|
||||
num_mining_threads: num_cpus::get(),
|
||||
mine_on_tip_only: true,
|
||||
proof_of_work_algo: PowAlgorithm::Sha3x,
|
||||
validate_tip_timeout_sec: 30,
|
||||
stratum_mining_pool_address: String::new(),
|
||||
stratum_mining_wallet_address: String::new(),
|
||||
mining_worker_name: String::new(),
|
||||
coinbase_extra: "m2pool.com".to_string(),
|
||||
network: Default::default(),
|
||||
wait_timeout_on_error: 10,
|
||||
config_dir: PathBuf::from("config/miner"),
|
||||
wallet_payment_address: TariAddress::default().to_base58(),
|
||||
range_proof_type: RangeProofType::RevealedValue,
|
||||
sha_p2pool_enabled: false,
|
||||
zmq_publisher_port: 11112,
|
||||
zmq_subscriber_port: 11113,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MinerConfig {
|
||||
pub fn pow_algo_request(&self) -> NewBlockTemplateRequest {
|
||||
let algo = match self.proof_of_work_algo {
|
||||
PowAlgorithm::Sha3x => Some(PowAlgo {
|
||||
pow_algo: PowAlgos::Sha3x.into(),
|
||||
}),
|
||||
PowAlgorithm::RandomXM => Some(PowAlgo {
|
||||
pow_algo: PowAlgos::Randomxm.into(),
|
||||
}),
|
||||
PowAlgorithm::RandomXT => Some(PowAlgo {
|
||||
pow_algo: PowAlgos::Randomxt.into(),
|
||||
}),
|
||||
};
|
||||
NewBlockTemplateRequest { algo, max_weight: 0 }
|
||||
}
|
||||
|
||||
pub fn wait_timeout(&self) -> Duration {
|
||||
Duration::from_secs(self.wait_timeout_on_error)
|
||||
}
|
||||
|
||||
pub fn validate_tip_interval(&self) -> Duration {
|
||||
Duration::from_secs(self.validate_tip_timeout_sec)
|
||||
}
|
||||
|
||||
pub fn set_base_path<P: AsRef<Path>>(&mut self, base_path: P) {
|
||||
if !self.config_dir.is_absolute() {
|
||||
self.config_dir = base_path.as_ref().join(self.config_dir.as_path());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
|
||||
use tari_common::DefaultConfigLoader;
|
||||
|
||||
use crate::config::MinerConfig;
|
||||
|
||||
#[test]
|
||||
fn miner_configuration() {
|
||||
const CONFIG: &str = r#"
|
||||
[miner]
|
||||
num_mining_threads=2
|
||||
base_node_grpc_address = "http://my_base_node:1234"
|
||||
mine_on_tip_only = false
|
||||
"#;
|
||||
let mut cfg: config::Config = config::Config::default();
|
||||
#[allow(deprecated)]
|
||||
cfg.merge(config::File::from_str(CONFIG, config::FileFormat::Toml))
|
||||
.unwrap();
|
||||
let config = MinerConfig::load_from(&cfg).expect("Failed to load config");
|
||||
assert_eq!(config.num_mining_threads, 2);
|
||||
assert_eq!(
|
||||
config.base_node_grpc_address,
|
||||
Some("http://my_base_node:1234".to_string())
|
||||
);
|
||||
assert!(!config.mine_on_tip_only);
|
||||
}
|
||||
}
|
||||
163
internal/gbt/tari/minotari_miner/src/difficulty.rs
Normal file
163
internal/gbt/tari/minotari_miner/src/difficulty.rs
Normal file
@@ -0,0 +1,163 @@
|
||||
// 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::convert::TryInto;
|
||||
|
||||
use minotari_app_grpc::tari_rpc::BlockHeader as grpc_header;
|
||||
use tari_common_types::types::FixedHash;
|
||||
use tari_core::{
|
||||
blocks::BlockHeader,
|
||||
proof_of_work::{randomx_factory::RandomXFactory, sha3x_difficulty, tari_randomx_difficulty},
|
||||
};
|
||||
use tari_utilities::epoch_time::EpochTime;
|
||||
|
||||
use crate::errors::MinerError;
|
||||
|
||||
pub type Difficulty = u64;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct BlockHeaderSha3 {
|
||||
pub header: BlockHeader,
|
||||
pub hashes: u64,
|
||||
pub vm_key: FixedHash,
|
||||
pub rx_factory: Option<RandomXFactory>,
|
||||
}
|
||||
|
||||
impl BlockHeaderSha3 {
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
#[allow(clippy::cast_sign_loss)]
|
||||
pub fn new(header: grpc_header, vm_key: FixedHash, rx_factory: Option<RandomXFactory>) -> Result<Self, MinerError> {
|
||||
let header: BlockHeader = header.try_into().map_err(MinerError::BlockHeader)?;
|
||||
Ok(Self {
|
||||
header,
|
||||
hashes: 0,
|
||||
vm_key,
|
||||
rx_factory,
|
||||
})
|
||||
}
|
||||
|
||||
/// This function will update the timestamp of the header, but only if the new timestamp is greater than the current
|
||||
/// one.
|
||||
pub fn set_forward_timestamp(&mut self, timestamp: u64) {
|
||||
// if the timestamp has been advanced by the base_node due to the median time we should not reverse it but we
|
||||
// should only change the timestamp if we move it forward.
|
||||
if timestamp > self.header.timestamp.as_u64() {
|
||||
self.header.timestamp = EpochTime::from(timestamp);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn random_nonce(&mut self) {
|
||||
use rand::{rngs::OsRng, RngCore};
|
||||
self.header.nonce = OsRng.next_u64();
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn inc_nonce(&mut self) {
|
||||
self.header.nonce = self.header.nonce.wrapping_add(1);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn difficulty_sha3(&mut self) -> Result<Difficulty, MinerError> {
|
||||
self.hashes = self.hashes.saturating_add(1);
|
||||
Ok(sha3x_difficulty(&self.header)?.as_u64())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn difficulty_rx(&mut self) -> Result<Difficulty, MinerError> {
|
||||
self.hashes = self.hashes.saturating_add(1);
|
||||
let randomx_factory = self.rx_factory.as_ref().ok_or(MinerError::RandomXFactoryNotSet)?;
|
||||
Ok(tari_randomx_difficulty(&self.header, randomx_factory, &self.vm_key)?.as_u64())
|
||||
}
|
||||
|
||||
#[allow(clippy::cast_possible_wrap)]
|
||||
pub fn create_header(&self) -> grpc_header {
|
||||
self.header.clone().into()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn height(&self) -> u64 {
|
||||
self.header.height
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod test {
|
||||
use chrono::{DateTime, NaiveDate, Utc};
|
||||
use tari_core::proof_of_work::sha3x_difficulty as core_sha3x_difficulty;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[allow(clippy::cast_sign_loss)]
|
||||
pub fn get_header() -> (grpc_header, BlockHeader) {
|
||||
let mut header = BlockHeader::new(0);
|
||||
header.timestamp = (DateTime::<Utc>::from_naive_utc_and_offset(
|
||||
NaiveDate::from_ymd_opt(2000, 1, 1)
|
||||
.unwrap()
|
||||
.and_hms_opt(1, 1, 1)
|
||||
.unwrap(),
|
||||
Utc,
|
||||
)
|
||||
.timestamp() as u64)
|
||||
.into();
|
||||
header.pow.pow_algo = tari_core::proof_of_work::PowAlgorithm::Sha3x;
|
||||
(header.clone().into(), header)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validate_nonce_difficulty() {
|
||||
let (mut header, mut core_header) = get_header();
|
||||
header.nonce = 1;
|
||||
core_header.nonce = 1;
|
||||
let mut hasher = BlockHeaderSha3::new(header, FixedHash::zero(), None).unwrap();
|
||||
for _ in 0..1000 {
|
||||
assert_eq!(
|
||||
hasher.difficulty_sha3().unwrap(),
|
||||
core_sha3x_difficulty(&core_header).unwrap().as_u64(),
|
||||
"with nonces = {}:{}",
|
||||
hasher.header.nonce,
|
||||
core_header.nonce
|
||||
);
|
||||
core_header.nonce += 1;
|
||||
hasher.inc_nonce();
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validate_timestamp_difficulty() {
|
||||
let (mut header, mut core_header) = get_header();
|
||||
header.nonce = 1;
|
||||
core_header.nonce = 1;
|
||||
let mut hasher = BlockHeaderSha3::new(header, FixedHash::zero(), None).unwrap();
|
||||
let mut timestamp = core_header.timestamp;
|
||||
for _ in 0..1000 {
|
||||
assert_eq!(
|
||||
hasher.difficulty_sha3().unwrap(),
|
||||
core_sha3x_difficulty(&core_header).unwrap().as_u64(),
|
||||
"with timestamp = {}",
|
||||
timestamp
|
||||
);
|
||||
timestamp = timestamp.checked_add(EpochTime::from(1)).unwrap();
|
||||
core_header.timestamp = timestamp;
|
||||
hasher.set_forward_timestamp(timestamp.as_u64());
|
||||
}
|
||||
}
|
||||
}
|
||||
72
internal/gbt/tari/minotari_miner/src/errors.rs
Normal file
72
internal/gbt/tari/minotari_miner/src/errors.rs
Normal file
@@ -0,0 +1,72 @@
|
||||
// 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 minotari_app_grpc::authentication::BasicAuthError;
|
||||
use minotari_app_utilities::parse_miner_input::ParseInputError;
|
||||
use tari_max_size::MaxSizeBytesError;
|
||||
use thiserror::Error;
|
||||
use tonic::codegen::http::uri::InvalidUri;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum MinerError {
|
||||
#[error("I/O error")]
|
||||
IOError(#[from] std::io::Error),
|
||||
#[error("gRPC error: {0}")]
|
||||
GrpcStatus(#[from] tonic::Status),
|
||||
#[error("Connection error: {0}")]
|
||||
GrpcConnection(#[from] tonic::transport::Error),
|
||||
#[error("Node not ready")]
|
||||
NodeNotReady,
|
||||
#[error("Blockchain reached specified height {0}, mining will be stopped")]
|
||||
MineUntilHeightReached(u64),
|
||||
#[error("Block height {0} already mined")]
|
||||
MinerLostBlock(u64),
|
||||
#[error("Expected non empty {0}")]
|
||||
EmptyObject(String),
|
||||
#[error("Invalid block header {0}")]
|
||||
BlockHeader(String),
|
||||
#[error("Conversion error: {0}")]
|
||||
Conversion(String),
|
||||
#[error("Invalid gRPC credentials: {0}")]
|
||||
BasicAuthError(#[from] BasicAuthError),
|
||||
#[error("Invalid gRPC url: {0}")]
|
||||
InvalidUri(#[from] InvalidUri),
|
||||
#[error("TLS connection error: {0}")]
|
||||
TlsConnectionError(String),
|
||||
#[error("Coinbase error: {0}")]
|
||||
CoinbaseError(String),
|
||||
#[error("Consensus build error: {0}")]
|
||||
ParseInputError(#[from] ParseInputError),
|
||||
#[error("Base node not responding to gRPC requests: {0}")]
|
||||
BaseNodeNotResponding(String),
|
||||
#[error("Limit error {0}")]
|
||||
MaxSizeBytesError(#[from] MaxSizeBytesError),
|
||||
#[error("Miner is set to use Rx, but not RX factory is set")]
|
||||
RandomXFactoryNotSet,
|
||||
#[error("RandomX error: {0}")]
|
||||
MergeMineError(#[from] tari_core::proof_of_work::monero_rx::MergeMineError),
|
||||
#[error("Difficulty error: {0}")]
|
||||
DifficultyError(#[from] tari_core::proof_of_work::DifficultyError),
|
||||
}
|
||||
|
||||
pub fn err_empty(name: &str) -> MinerError {
|
||||
MinerError::EmptyObject(name.to_string())
|
||||
}
|
||||
39
internal/gbt/tari/minotari_miner/src/lib.rs
Normal file
39
internal/gbt/tari/minotari_miner/src/lib.rs
Normal file
@@ -0,0 +1,39 @@
|
||||
// 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.
|
||||
|
||||
// non-64-bit not supported
|
||||
minotari_app_utilities::deny_non_64_bit_archs!();
|
||||
|
||||
mod cli;
|
||||
pub use cli::Cli;
|
||||
use tari_common::exit_codes::ExitError;
|
||||
mod run_miner;
|
||||
use run_miner::start_miner;
|
||||
mod config;
|
||||
mod difficulty;
|
||||
mod errors;
|
||||
mod miner;
|
||||
mod stratum;
|
||||
|
||||
pub async fn run_miner(cli: Cli) -> Result<(), ExitError> {
|
||||
start_miner(cli).await
|
||||
}
|
||||
76
internal/gbt/tari/minotari_miner/src/main.rs
Normal file
76
internal/gbt/tari/minotari_miner/src/main.rs
Normal file
@@ -0,0 +1,76 @@
|
||||
// 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::io::stdout;
|
||||
|
||||
use clap::Parser;
|
||||
use crossterm::{execute, terminal::SetTitle};
|
||||
use log::*;
|
||||
use minotari_app_utilities::consts;
|
||||
use run_miner::start_miner;
|
||||
use tari_common::{exit_codes::ExitError, initialize_logging};
|
||||
|
||||
use crate::cli::Cli;
|
||||
|
||||
pub const LOG_TARGET: &str = "minotari::miner::main";
|
||||
pub const LOG_TARGET_FILE: &str = "minotari::logging::miner::main";
|
||||
|
||||
mod cli;
|
||||
mod config;
|
||||
mod difficulty;
|
||||
mod errors;
|
||||
mod miner;
|
||||
mod run_miner;
|
||||
mod stratum;
|
||||
|
||||
/// Application entry point
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
let terminal_title = format!("Minotari Miner - Version {}", consts::APP_VERSION);
|
||||
if let Err(e) = execute!(stdout(), SetTitle(terminal_title.as_str())) {
|
||||
println!("Error setting terminal title. {}", e)
|
||||
}
|
||||
match main_inner().await {
|
||||
Ok(_) => std::process::exit(0),
|
||||
Err(err) => {
|
||||
error!(target: LOG_TARGET, "Fatal error: {}", err);
|
||||
let exit_code = err.exit_code;
|
||||
error!(target: LOG_TARGET, "Exiting with code: {}", exit_code);
|
||||
std::process::exit(exit_code as i32)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
async fn main_inner() -> Result<(), ExitError> {
|
||||
let cli = Cli::parse();
|
||||
initialize_logging(
|
||||
&cli.common.log_config_path("miner"),
|
||||
&cli.common.get_base_path(),
|
||||
include_str!("../log4rs_sample.yml"),
|
||||
)?;
|
||||
info!(
|
||||
target: LOG_TARGET,
|
||||
"Starting Minotari Miner version: {}",
|
||||
consts::APP_VERSION
|
||||
);
|
||||
start_miner(cli).await
|
||||
}
|
||||
505
internal/gbt/tari/minotari_miner/src/miner.rs
Normal file
505
internal/gbt/tari/minotari_miner/src/miner.rs
Normal file
@@ -0,0 +1,505 @@
|
||||
// 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::{
|
||||
panic::panic_any,
|
||||
pin::Pin,
|
||||
task::{Context, Poll, Waker},
|
||||
thread,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use chrono::Utc;
|
||||
use crossbeam::channel::{bounded, Select, Sender, TrySendError};
|
||||
use futures::Stream;
|
||||
use log::*;
|
||||
use minotari_app_grpc::tari_rpc::BlockHeader;
|
||||
use tari_common_types::types::FixedHash;
|
||||
use tari_core::proof_of_work::randomx_factory::RandomXFactory;
|
||||
use thread::JoinHandle;
|
||||
|
||||
use super::difficulty::BlockHeaderSha3;
|
||||
|
||||
use zmq::{Socket};
|
||||
|
||||
use anyhow::{Result};
|
||||
|
||||
use serde::{Serialize, Deserialize};
|
||||
|
||||
use std::sync::{Arc};
|
||||
use tokio::sync::{Mutex, };
|
||||
use minotari_app_utilities::parse_miner_input::BaseNodeGrpcClient;
|
||||
use crate::errors::MinerError;
|
||||
|
||||
pub const LOG_TARGET: &str = "minotari::miner::standalone";
|
||||
|
||||
// Identify how often mining thread is reporting / checking context
|
||||
// ~400_000 hashes per second
|
||||
const REPORTING_FREQUENCY_RX: u64 = 300;
|
||||
const REPORTING_FREQUENCY_SHA3: u64 = 3_000_000;
|
||||
|
||||
// Thread's stack size, ideally we would fit all thread's data in the CPU L1 cache
|
||||
const STACK_SIZE: usize = 320_000;
|
||||
|
||||
/// Miner will send regular reports from every mining threads
|
||||
#[derive(Debug)]
|
||||
pub struct MiningReport {
|
||||
pub miner: usize,
|
||||
pub target_difficulty: u64,
|
||||
pub difficulty: u64,
|
||||
pub hashes: u64,
|
||||
pub elapsed: Duration,
|
||||
/// Will be set for when mined header is matching required difficulty
|
||||
pub header: Option<BlockHeader>,
|
||||
pub height: u64,
|
||||
}
|
||||
|
||||
/// Miner is starting number of mining threads and implements Stream for async reports polling
|
||||
/// Communication with async world is performed via channel and waker so should be quite efficient
|
||||
pub struct Miner {
|
||||
threads: Vec<JoinHandle<()>>,
|
||||
channels: Vec<crossbeam::channel::Receiver<MiningReport>>,
|
||||
num_threads: usize,
|
||||
header: BlockHeader,
|
||||
target_difficulty: u64,
|
||||
share_mode: bool,
|
||||
vm_key: FixedHash,
|
||||
rx_factory: Option<RandomXFactory>,
|
||||
|
||||
publisher_socket: Arc<Mutex<Socket>>,
|
||||
subscriber_socket: Arc<Mutex<Socket>>,
|
||||
base_node_client: BaseNodeGrpcClient,
|
||||
}
|
||||
|
||||
impl Miner {
|
||||
pub fn init_mining(
|
||||
base_node_client: BaseNodeGrpcClient,
|
||||
header: BlockHeader,
|
||||
target_difficulty: u64,
|
||||
num_threads: usize,
|
||||
share_mode: bool,
|
||||
vm_key: FixedHash,
|
||||
rx_factory: Option<RandomXFactory>,
|
||||
publisher_socket: Arc<Mutex<Socket>>,
|
||||
subscriber_socket: Arc<Mutex<Socket>>,
|
||||
) -> Self {
|
||||
Self {
|
||||
threads: vec![],
|
||||
channels: vec![],
|
||||
header,
|
||||
num_threads,
|
||||
target_difficulty,
|
||||
share_mode,
|
||||
vm_key,
|
||||
rx_factory,
|
||||
publisher_socket,
|
||||
subscriber_socket,
|
||||
base_node_client,
|
||||
}
|
||||
}
|
||||
|
||||
// this will kill all mining threads currently active and attached to this miner
|
||||
pub fn kill_threads(&mut self) {
|
||||
self.channels.clear();
|
||||
}
|
||||
|
||||
// Start mining threads with async context waker
|
||||
fn start_threads(&mut self, ctx: &Context<'_>) {
|
||||
let miners = (0..self.num_threads)
|
||||
.map(|i| {
|
||||
(
|
||||
thread::Builder::new()
|
||||
.name(format!("cpu-miner-{}", i))
|
||||
.stack_size(STACK_SIZE),
|
||||
i,
|
||||
)
|
||||
})
|
||||
.map(|(thread, i)| {
|
||||
let (tx, rx) = bounded(1);
|
||||
let header = self.header.clone();
|
||||
let waker = ctx.waker().clone();
|
||||
let difficulty = self.target_difficulty;
|
||||
let share_mode = self.share_mode;
|
||||
let vm_key = self.vm_key;
|
||||
let rx_factory = self.rx_factory.clone();
|
||||
|
||||
let pub_socket = Arc::clone(&self.publisher_socket);
|
||||
let sub_socket = Arc::clone(&self.subscriber_socket);
|
||||
let base_node_client_clone = self.base_node_client.clone();
|
||||
let handle = thread
|
||||
.spawn(move || mining_task(base_node_client_clone, header, difficulty, tx, waker, i, share_mode, vm_key, rx_factory, pub_socket, sub_socket))
|
||||
.expect("Failed to create mining thread");
|
||||
(handle, rx)
|
||||
});
|
||||
|
||||
let (threads, channels) = miners.unzip();
|
||||
self.threads = threads;
|
||||
self.channels = channels;
|
||||
}
|
||||
}
|
||||
|
||||
impl Stream for Miner {
|
||||
type Item = MiningReport;
|
||||
|
||||
fn poll_next(mut self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
trace!(target: LOG_TARGET, "Polling Miner");
|
||||
// First poll would start all the threads passing async context waker
|
||||
if self.threads.is_empty() && self.num_threads > 0 {
|
||||
debug!(
|
||||
target: LOG_TARGET,
|
||||
"Starting {} mining threads for target difficulty {}", self.num_threads, self.target_difficulty
|
||||
);
|
||||
self.start_threads(ctx);
|
||||
return Poll::Pending;
|
||||
} else if self.num_threads == 0 {
|
||||
error!(target: LOG_TARGET, "Cannot mine: no mining threads");
|
||||
return Poll::Ready(None);
|
||||
} else if self.channels.is_empty() {
|
||||
debug!(target: LOG_TARGET, "Finished mining");
|
||||
return Poll::Ready(None);
|
||||
} else {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
// Non blocking select from all miner's receiver channels
|
||||
let mut sel = Select::new();
|
||||
for rx in &self.channels {
|
||||
sel.recv(rx);
|
||||
}
|
||||
let report = match sel.try_select() {
|
||||
Ok(oper) => {
|
||||
let idx = oper.index();
|
||||
match oper.recv(&self.channels[idx]) {
|
||||
Ok(report) => report,
|
||||
Err(_) => {
|
||||
// Received error would mean thread is disconnected already
|
||||
trace!("Thread {} disconnected.", idx);
|
||||
return Poll::Ready(None);
|
||||
},
|
||||
}
|
||||
},
|
||||
Err(_) => {
|
||||
// No reports
|
||||
return Poll::Pending;
|
||||
},
|
||||
};
|
||||
if report.header.is_some() && !self.share_mode {
|
||||
// Dropping recipients would stop miners next time they try to report
|
||||
self.channels.clear();
|
||||
}
|
||||
Poll::Ready(Some(report))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct GbtMsg {
|
||||
pub height: u64,
|
||||
pub header: String,
|
||||
pub u64target: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct SubmitRequest {
|
||||
pub id: u64,
|
||||
pub user: String,
|
||||
pub miner: String,
|
||||
pub index: String,
|
||||
pub header: String,
|
||||
pub nonce: u64,
|
||||
pub pow: String,
|
||||
pub subidx: i64,
|
||||
pub height: u64,
|
||||
pub submitidx: u64,
|
||||
}
|
||||
|
||||
fn send_gbt_message(socket: &Arc<Mutex<Socket>>, msg: &GbtMsg) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let msg_json = serde_json::to_string(msg)?;
|
||||
if let Ok(pub_socket) = socket.try_lock() {
|
||||
pub_socket.send_multipart(&["jobsha3x".as_bytes(), msg_json.as_bytes()], 0)?;
|
||||
println!("send_gbt_message");
|
||||
Ok(())
|
||||
} else {
|
||||
Err("Socket lock busy".into())
|
||||
}
|
||||
}
|
||||
|
||||
fn try_receive_nonce(socket: &Arc<Mutex<Socket>>) -> Option<u64> {
|
||||
let sub_socket = match socket.try_lock() {
|
||||
Ok(sub_socket) => sub_socket,
|
||||
Err(_) => return None,
|
||||
};
|
||||
sub_socket.set_rcvtimeo(100).expect("Set timeout failed");
|
||||
let frames = match sub_socket.recv_multipart(zmq::DONTWAIT) {
|
||||
Ok(frames) => frames,
|
||||
Err(zmq::Error::EAGAIN) => return None,
|
||||
Err(e) => {
|
||||
error!("ZMQ receive error: {}", e);
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
drop(sub_socket);
|
||||
|
||||
|
||||
if frames.len() < 2 {
|
||||
warn!("Invalid message format: expected at least 2 frames");
|
||||
return None;
|
||||
}
|
||||
|
||||
//println!("First frame: {:?}", frames.first().unwrap().as_slice());
|
||||
//println!("Nonce frame: {:?}", &frames[1]);
|
||||
|
||||
if frames.first().map(Vec::as_slice) != Some(b"blksha3x") {
|
||||
return None;
|
||||
}
|
||||
|
||||
|
||||
match serde_json::from_slice::<SubmitRequest>(&frames[1]) {
|
||||
Ok(request) => Some(request.nonce),
|
||||
Err(e) => {
|
||||
warn!("Failed to parse nonce: {}", e);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Miner starts with a random nonce and iterates until it finds a header hash that meets the desired
|
||||
/// target
|
||||
pub fn mining_task(
|
||||
mut node_conn: BaseNodeGrpcClient,
|
||||
header: BlockHeader,
|
||||
target_difficulty: u64,
|
||||
sender: Sender<MiningReport>,
|
||||
waker: Waker,
|
||||
miner: usize,
|
||||
share_mode: bool,
|
||||
vm_key: FixedHash,
|
||||
rx_factory: Option<RandomXFactory>,
|
||||
publisher_socket: Arc<Mutex<Socket>>,
|
||||
subscriber_socket: Arc<Mutex<Socket>>,
|
||||
) {
|
||||
let mining_algorithm = if rx_factory.is_some() { "RandomXT" } else { "Sha3X" };
|
||||
let start = Instant::now();
|
||||
let mut hasher = match BlockHeaderSha3::new(header, vm_key, rx_factory) {
|
||||
Ok(hasher) => hasher,
|
||||
Err(err) => {
|
||||
let err = format!(
|
||||
"Miner {} on {} failed to create hasher: {:?}",
|
||||
miner, mining_algorithm, err
|
||||
);
|
||||
error!(target: LOG_TARGET, "{}", err);
|
||||
panic_any(err);
|
||||
},
|
||||
};
|
||||
hasher.random_nonce();
|
||||
|
||||
let gbt_msg = GbtMsg {
|
||||
height: hasher.header.height,
|
||||
header: hasher.header.mining_hash().to_string(),
|
||||
u64target: target_difficulty,
|
||||
};
|
||||
|
||||
|
||||
if let Err(e) = send_gbt_message(&publisher_socket, &gbt_msg) {
|
||||
error!(
|
||||
target: LOG_TARGET,
|
||||
"Miner {} failed to send GBT message: {}", miner, e
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// We're mining over here!
|
||||
trace!(target: LOG_TARGET, "Mining thread {} started for {}", miner, mining_algorithm);
|
||||
|
||||
let mut check_count:u64 = 0;
|
||||
// Mining work
|
||||
loop {
|
||||
if let Some(nonce) = try_receive_nonce(&subscriber_socket) {
|
||||
check_count = 0;
|
||||
hasher.header.nonce = nonce;
|
||||
println!("nonce {} {}", nonce, hasher.header.nonce);
|
||||
trace!(
|
||||
target: LOG_TARGET,
|
||||
"Miner {} received new nonce: {}", miner, nonce
|
||||
);
|
||||
} else {
|
||||
check_count += 1;
|
||||
if check_count > 10 {
|
||||
check_count = 0;
|
||||
let result = futures::executor::block_on(validate_tip(&mut node_conn, hasher.header.height));
|
||||
match result {
|
||||
Ok(()) => {}
|
||||
Err(e) => {
|
||||
error!("Tip validation error: {:?}", e);
|
||||
let difficulty = 0;
|
||||
let res = sender.try_send(MiningReport {
|
||||
miner,
|
||||
difficulty,
|
||||
hashes: hasher.hashes,
|
||||
elapsed: start.elapsed(),
|
||||
header: None,
|
||||
height: hasher.height(),
|
||||
target_difficulty,
|
||||
});
|
||||
waker.wake_by_ref();
|
||||
trace!(target: LOG_TARGET, "Reporting from {} on {} result {:?}", miner, mining_algorithm, res);
|
||||
if let Err(TrySendError::Disconnected(_)) = res {
|
||||
info!(target: LOG_TARGET, "Mining thread {} on {} disconnected", miner, mining_algorithm);
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
/*check_count += 1;
|
||||
let reporting_frequency = if hasher.rx_factory.is_some() {
|
||||
REPORTING_FREQUENCY_RX
|
||||
} else {
|
||||
REPORTING_FREQUENCY_SHA3
|
||||
};
|
||||
if check_count > reporting_frequency {
|
||||
check_count = 0;
|
||||
let difficulty = 0;
|
||||
let res = sender.try_send(MiningReport {
|
||||
miner,
|
||||
difficulty,
|
||||
hashes: hasher.hashes,
|
||||
elapsed: start.elapsed(),
|
||||
header: None,
|
||||
height: hasher.height(),
|
||||
target_difficulty,
|
||||
});
|
||||
waker.wake_by_ref();
|
||||
trace!(target: LOG_TARGET, "Reporting from {} on {} result {:?}", miner, mining_algorithm, res);
|
||||
if let Err(TrySendError::Disconnected(_)) = res {
|
||||
info!(target: LOG_TARGET, "Mining thread {} on {} disconnected", miner, mining_algorithm);
|
||||
return;
|
||||
}
|
||||
|
||||
}*/
|
||||
continue;
|
||||
}
|
||||
println!("get nonce {}", hasher.header.nonce);
|
||||
let hashed = if hasher.rx_factory.is_some() {
|
||||
hasher.difficulty_rx()
|
||||
} else {
|
||||
println!("calc diff sha3x");
|
||||
hasher.difficulty_sha3()
|
||||
};
|
||||
|
||||
println!("check diff");
|
||||
let difficulty = match hashed {
|
||||
Ok(difficulty) => difficulty,
|
||||
Err(err) => {
|
||||
let err = format!(
|
||||
"Miner {} failed to calculate difficulty on {}: {:?}",
|
||||
miner, mining_algorithm, err
|
||||
);
|
||||
error!(target: LOG_TARGET, "{}", err);
|
||||
panic_any(err);
|
||||
},
|
||||
};
|
||||
|
||||
println!("difficulty {},{}", difficulty, target_difficulty);
|
||||
|
||||
if difficulty >= target_difficulty {
|
||||
debug!(
|
||||
target: LOG_TARGET,
|
||||
"Miner {} found nonce {} with matching difficulty {} on {} height {}",
|
||||
miner, hasher.header.nonce, difficulty, mining_algorithm, hasher.header.height
|
||||
);
|
||||
println!(
|
||||
"Miner {} found nonce {} with matching difficulty {} on {} height {}",
|
||||
miner, hasher.header.nonce, difficulty, mining_algorithm, hasher.header.height
|
||||
);
|
||||
if let Err(err) = sender.try_send(MiningReport {
|
||||
miner,
|
||||
difficulty,
|
||||
hashes: hasher.hashes,
|
||||
elapsed: start.elapsed(),
|
||||
height: hasher.height(),
|
||||
header: Some(hasher.create_header()),
|
||||
target_difficulty,
|
||||
}) {
|
||||
error!(target: LOG_TARGET, "Miner {} on {} failed to send report: {}", miner, mining_algorithm, err);
|
||||
}
|
||||
// If we are mining in share mode, this share might not be a block, so we need to keep mining till we get a
|
||||
// new job
|
||||
if share_mode {
|
||||
waker.wake_by_ref();
|
||||
} else {
|
||||
waker.wake();
|
||||
trace!(target: LOG_TARGET, "Mining thread {} on {} stopped", miner, mining_algorithm);
|
||||
println!("Mining thread {} on {} stopped", miner, mining_algorithm);
|
||||
return;
|
||||
}
|
||||
}
|
||||
let reporting_frequency = if hasher.rx_factory.is_some() {
|
||||
REPORTING_FREQUENCY_RX
|
||||
} else {
|
||||
REPORTING_FREQUENCY_SHA3
|
||||
};
|
||||
if hasher.header.nonce % reporting_frequency == 0 {
|
||||
let res = sender.try_send(MiningReport {
|
||||
miner,
|
||||
difficulty,
|
||||
hashes: hasher.hashes,
|
||||
elapsed: start.elapsed(),
|
||||
header: None,
|
||||
height: hasher.height(),
|
||||
target_difficulty,
|
||||
});
|
||||
waker.wake_by_ref();
|
||||
trace!(target: LOG_TARGET, "Reporting from {} on {} result {:?}", miner, mining_algorithm, res);
|
||||
if let Err(TrySendError::Disconnected(_)) = res {
|
||||
info!(target: LOG_TARGET, "Mining thread {} on {} disconnected", miner, mining_algorithm);
|
||||
return;
|
||||
}
|
||||
/*if !(share_mode) {
|
||||
hasher.set_forward_timestamp(Utc::now().timestamp() as u64);
|
||||
}*/
|
||||
}
|
||||
//hasher.inc_nonce();
|
||||
}
|
||||
}
|
||||
|
||||
async fn validate_tip(
|
||||
node_conn: &mut BaseNodeGrpcClient,
|
||||
height: u64,
|
||||
) -> Result<(), MinerError> {
|
||||
let tip = node_conn
|
||||
.get_tip_info(minotari_app_grpc::tari_rpc::Empty {})
|
||||
.await?
|
||||
.into_inner();
|
||||
let longest_height = tip.clone().metadata.unwrap().best_block_height;
|
||||
if height <= longest_height {
|
||||
return Err(MinerError::MinerLostBlock(height));
|
||||
}
|
||||
if !tip.initial_sync_achieved || tip.metadata.is_none() {
|
||||
return Err(MinerError::NodeNotReady);
|
||||
}
|
||||
if height <= longest_height {
|
||||
return Err(MinerError::MinerLostBlock(height));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
735
internal/gbt/tari/minotari_miner/src/run_miner.rs
Normal file
735
internal/gbt/tari/minotari_miner/src/run_miner.rs
Normal file
@@ -0,0 +1,735 @@
|
||||
// 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::{convert::TryFrom, sync::Arc, thread, time::Instant};
|
||||
|
||||
use futures::stream::StreamExt;
|
||||
use log::*;
|
||||
use minotari_app_grpc::{
|
||||
authentication::ClientAuthenticationInterceptor,
|
||||
conversions::transaction_output::grpc_output_with_payref,
|
||||
tari_rpc::{
|
||||
base_node_client::BaseNodeClient,
|
||||
pow_algo::PowAlgos,
|
||||
sha_p2_pool_client::ShaP2PoolClient,
|
||||
Block,
|
||||
GetNewBlockRequest,
|
||||
PowAlgo,
|
||||
SubmitBlockRequest,
|
||||
SubmitBlockResponse,
|
||||
},
|
||||
};
|
||||
use minotari_app_utilities::parse_miner_input::{
|
||||
prompt_for_base_node_address,
|
||||
prompt_for_p2pool_address,
|
||||
verify_base_node_grpc_mining_responses,
|
||||
wallet_payment_address,
|
||||
BaseNodeGrpcClient,
|
||||
ShaP2PoolGrpcClient,
|
||||
};
|
||||
use tari_common::{
|
||||
exit_codes::{ExitCode, ExitError},
|
||||
load_configuration,
|
||||
DefaultConfigLoader,
|
||||
MAX_GRPC_MESSAGE_SIZE,
|
||||
};
|
||||
use tari_common_types::{
|
||||
tari_address::TariAddress,
|
||||
types::{FixedHash, UncompressedPublicKey},
|
||||
};
|
||||
use tari_core::{
|
||||
blocks::BlockHeader,
|
||||
consensus::ConsensusManager,
|
||||
proof_of_work::{randomx_factory::RandomXFactory, PowAlgorithm},
|
||||
transactions::{
|
||||
generate_coinbase,
|
||||
tari_amount::MicroMinotari,
|
||||
transaction_components::{
|
||||
payment_id::{PaymentId, TxType},
|
||||
CoinBaseExtra,
|
||||
},
|
||||
transaction_key_manager::{create_memory_db_key_manager, MemoryDbKeyManager},
|
||||
},
|
||||
};
|
||||
use tari_utilities::hex::Hex;
|
||||
use tokio::{sync::Mutex, time::sleep};
|
||||
use tonic::transport::{Certificate, ClientTlsConfig, Endpoint};
|
||||
use zmq::{Context as ZmqContext, Socket, SUB};
|
||||
use anyhow::anyhow;
|
||||
use anyhow::{Context, Result};
|
||||
use crate::{
|
||||
cli::Cli,
|
||||
config::MinerConfig,
|
||||
errors::{err_empty, MinerError},
|
||||
miner::{Miner, MiningReport},
|
||||
stratum::stratum_controller::controller::Controller,
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
pub const LOG_TARGET: &str = "minotari::miner::main";
|
||||
pub const LOG_TARGET_FILE: &str = "minotari::logging::miner::main";
|
||||
|
||||
#[allow(clippy::too_many_lines)]
|
||||
pub async fn start_miner(cli: Cli) -> Result<(), ExitError> {
|
||||
let config_path = cli.common.config_path();
|
||||
let cfg = load_configuration(
|
||||
config_path.as_path(),
|
||||
true,
|
||||
cli.non_interactive_mode,
|
||||
&cli,
|
||||
cli.common.network,
|
||||
)?;
|
||||
let mut config = MinerConfig::load_from(&cfg).expect("Failed to load config");
|
||||
config.set_base_path(cli.common.get_base_path());
|
||||
|
||||
debug!(target: LOG_TARGET_FILE, "{:?}", config);
|
||||
let key_manager = create_memory_db_key_manager().map_err(|err| {
|
||||
ExitError::new(
|
||||
ExitCode::KeyManagerServiceError,
|
||||
"'wallet_payment_address' ".to_owned() + &err.to_string(),
|
||||
)
|
||||
})?;
|
||||
let wallet_payment_address = wallet_payment_address(config.wallet_payment_address.clone(), config.network)
|
||||
.map_err(|err| {
|
||||
ExitError::new(
|
||||
ExitCode::WalletPaymentAddress,
|
||||
"'wallet_payment_address' ".to_owned() + &err.to_string(),
|
||||
)
|
||||
})?;
|
||||
debug!(target: LOG_TARGET_FILE, "wallet_payment_address: {}", wallet_payment_address);
|
||||
let consensus_manager = ConsensusManager::builder(config.network)
|
||||
.build()
|
||||
.map_err(|err| ExitError::new(ExitCode::ConsensusManagerBuilderError, err.to_string()))?;
|
||||
|
||||
if !config.stratum_mining_wallet_address.is_empty() && !config.stratum_mining_pool_address.is_empty() {
|
||||
let url = config.stratum_mining_pool_address.clone();
|
||||
let mut miner_address = config.stratum_mining_wallet_address.clone();
|
||||
let _unused = UncompressedPublicKey::from_hex(&miner_address).map_err(|_| {
|
||||
ExitError::new(
|
||||
ExitCode::ConfigError,
|
||||
"Miner is not configured with a valid wallet address.",
|
||||
)
|
||||
})?;
|
||||
if !config.mining_worker_name.is_empty() {
|
||||
miner_address += &format!("{}{}", ".", config.mining_worker_name);
|
||||
}
|
||||
let mut mc = Controller::new(config.num_mining_threads).map_err(|e| {
|
||||
debug!(target: LOG_TARGET_FILE, "Error loading mining controller: {}", e);
|
||||
ExitError::new(
|
||||
ExitCode::UnknownError,
|
||||
format!("Error loading mining controller: {}", e),
|
||||
)
|
||||
})?;
|
||||
let cc = crate::stratum::controller::Controller::new(&url, Some(miner_address), None, None, mc.tx.clone())
|
||||
.map_err(|e| {
|
||||
debug!(
|
||||
target: LOG_TARGET_FILE,
|
||||
"Error loading stratum client controller: {:?}", e
|
||||
);
|
||||
ExitError::new(
|
||||
ExitCode::UnknownError,
|
||||
format!("Error loading mining controller: {}", e),
|
||||
)
|
||||
})?;
|
||||
mc.set_client_tx(cc.tx.clone());
|
||||
|
||||
let _join_handle = thread::Builder::new()
|
||||
.name("client_controller".to_string())
|
||||
.spawn(move || {
|
||||
cc.run();
|
||||
});
|
||||
|
||||
mc.run()
|
||||
.await
|
||||
.map_err(|err| ExitError::new(ExitCode::UnknownError, format!("Stratum error: {:?}", err)))?;
|
||||
|
||||
Ok(())
|
||||
} else {
|
||||
let node_clients = connect(&config)
|
||||
.await
|
||||
.map_err(|e| ExitError::new(ExitCode::GrpcError, e.to_string()))?;
|
||||
let mut base_node_client = node_clients.base_node_client;
|
||||
let mut p2pool_node_client = node_clients.p2pool_node_client;
|
||||
|
||||
if let Err(e) = verify_base_node_responses(&mut base_node_client, &config).await {
|
||||
if let MinerError::BaseNodeNotResponding(_) = e {
|
||||
error!(target: LOG_TARGET, "{}", e);
|
||||
println!();
|
||||
let msg = "Could not connect to the base node. \nAre the base node's gRPC mining methods allowed in \
|
||||
its 'config.toml'? Please ensure these methods are enabled in:\n \
|
||||
'grpc_server_allow_methods': \"get_new_block_template\", \"get_tip_info\", \
|
||||
\"get_new_block\", \"submit_block\"";
|
||||
println!("{}", msg);
|
||||
println!();
|
||||
return Err(ExitError::new(ExitCode::GrpcError, e.to_string()));
|
||||
}
|
||||
}
|
||||
config.zmq_publisher_port = 11113;
|
||||
config.zmq_subscriber_port = 11112;
|
||||
println!("pub port:{}, sub port {}", config.zmq_publisher_port, config.zmq_subscriber_port);
|
||||
let zmq_context = ZmqContext::new();
|
||||
let publisher_socket = zmq_context
|
||||
.socket(zmq::PUB)
|
||||
.map_err(|e| anyhow!("ZMQ publisher error: {}", e))?;
|
||||
|
||||
let publisher_addr = format!("tcp://0.0.0.0:{}", config.zmq_publisher_port);
|
||||
println!("publisher_addr:{}",publisher_addr);
|
||||
publisher_socket
|
||||
.bind(&publisher_addr)
|
||||
.map_err(|e| anyhow!("ZMQ bind error: {}", e))?;
|
||||
|
||||
let subscriber_socket = zmq_context
|
||||
.socket(SUB)
|
||||
.context("Failed to create SUB socket")?;
|
||||
let subscriber_addr = format!("tcp://172.17.0.1:{}", config.zmq_subscriber_port);
|
||||
println!("subscriber_addr:{}",subscriber_addr);
|
||||
subscriber_socket
|
||||
.connect(&subscriber_addr)
|
||||
.context("Failed to connect to PUB server")?;
|
||||
subscriber_socket
|
||||
.set_subscribe(b"")
|
||||
.context("Failed to subscribe to all messages")?;
|
||||
|
||||
let publisher = Arc::new(Mutex::new(publisher_socket));
|
||||
let subscriber = Arc::new(Mutex::new(subscriber_socket));
|
||||
|
||||
let mut blocks_found: u64 = 0;
|
||||
loop {
|
||||
debug!(target: LOG_TARGET, "Starting new mining cycle");
|
||||
match mining_cycle(
|
||||
&mut base_node_client,
|
||||
p2pool_node_client.clone(),
|
||||
&config,
|
||||
&cli,
|
||||
&key_manager,
|
||||
&wallet_payment_address,
|
||||
&consensus_manager,
|
||||
publisher.clone(),
|
||||
subscriber.clone(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
err @ Err(MinerError::GrpcConnection(_)) | err @ Err(MinerError::GrpcStatus(_)) => {
|
||||
// Any GRPC error we will try to reconnect with a standard delay
|
||||
error!(target: LOG_TARGET, "Connection error: {:?}", err);
|
||||
loop {
|
||||
info!(target: LOG_TARGET, "Holding for {:?}", config.wait_timeout());
|
||||
sleep(config.wait_timeout()).await;
|
||||
match connect(&config).await {
|
||||
Ok(nc) => {
|
||||
base_node_client = nc.base_node_client;
|
||||
p2pool_node_client = nc.p2pool_node_client;
|
||||
break;
|
||||
},
|
||||
Err(err) => {
|
||||
error!(target: LOG_TARGET, "Connection error: {:?}", err);
|
||||
continue;
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(MinerError::MineUntilHeightReached(h)) => {
|
||||
warn!(
|
||||
target: LOG_TARGET,
|
||||
"Prescribed blockchain height {} reached. Aborting ...", h
|
||||
);
|
||||
return Ok(());
|
||||
},
|
||||
Err(MinerError::MinerLostBlock(h)) => {
|
||||
warn!(
|
||||
target: LOG_TARGET,
|
||||
"Height {} already mined by other node. Restarting ...", h
|
||||
);
|
||||
},
|
||||
Err(err) => {
|
||||
error!(target: LOG_TARGET, "Error: {:?}", err);
|
||||
sleep(config.wait_timeout()).await;
|
||||
},
|
||||
Ok(submitted) => {
|
||||
info!(target: LOG_TARGET, "💰 Found block");
|
||||
|
||||
if submitted {
|
||||
blocks_found += 1;
|
||||
}
|
||||
println!("blocks_found: {} {}", blocks_found, cli.miner_max_blocks.unwrap_or(0));
|
||||
if let Some(max_blocks) = cli.miner_max_blocks {
|
||||
if blocks_found >= max_blocks {
|
||||
println!("blocks_found >= max_blocks exit");
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct NodeClientResult {
|
||||
base_node_client: BaseNodeGrpcClient,
|
||||
p2pool_node_client: Option<ShaP2PoolGrpcClient>,
|
||||
}
|
||||
|
||||
async fn connect(config: &MinerConfig) -> Result<NodeClientResult, MinerError> {
|
||||
// always connect to base node first
|
||||
let base_node_client = match connect_base_node(config).await {
|
||||
Ok(client) => client,
|
||||
Err(e) => {
|
||||
error!(target: LOG_TARGET, "Could not connect to base node: {}", e);
|
||||
let msg = "Could not connect to base node. \nIs the base node's gRPC running? Try running it with \
|
||||
`--enable-grpc` or enable it in the config.";
|
||||
println!("{}", msg);
|
||||
return Err(e);
|
||||
},
|
||||
};
|
||||
|
||||
// init client to sha p2pool grpc if enabled
|
||||
let mut p2pool_node_client = None;
|
||||
if config.sha_p2pool_enabled {
|
||||
p2pool_node_client = match connect_sha_p2pool(config).await {
|
||||
Ok(client) => Some(client),
|
||||
Err(e) => {
|
||||
error!(target: LOG_TARGET, "Could not connect to base node: {}", e);
|
||||
let msg = "Could not connect to base node. \nIs the base node's gRPC running? Try running it with \
|
||||
`--enable-grpc` or enable it in the config.";
|
||||
println!("{}", msg);
|
||||
return Err(e);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
Ok(NodeClientResult {
|
||||
base_node_client,
|
||||
p2pool_node_client,
|
||||
})
|
||||
}
|
||||
|
||||
async fn connect_sha_p2pool(config: &MinerConfig) -> Result<ShaP2PoolGrpcClient, MinerError> {
|
||||
let p2pool_node_addr;
|
||||
if let Some(ref a) = config.base_node_grpc_address {
|
||||
p2pool_node_addr = a.clone();
|
||||
} else {
|
||||
p2pool_node_addr = prompt_for_p2pool_address()?;
|
||||
}
|
||||
|
||||
info!(target: LOG_TARGET, "👛 Connecting to p2pool node at {}", p2pool_node_addr);
|
||||
let mut endpoint = Endpoint::new(p2pool_node_addr)?;
|
||||
|
||||
if let Some(domain_name) = config.base_node_grpc_tls_domain_name.as_ref() {
|
||||
let pem = tokio::fs::read(config.config_dir.join(&config.base_node_grpc_ca_cert_filename))
|
||||
.await
|
||||
.map_err(|e| MinerError::TlsConnectionError(e.to_string()))?;
|
||||
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| MinerError::TlsConnectionError(e.to_string()))?;
|
||||
}
|
||||
|
||||
let channel = endpoint
|
||||
.connect()
|
||||
.await
|
||||
.map_err(|e| MinerError::TlsConnectionError(e.to_string()))?;
|
||||
let node_conn = ShaP2PoolClient::with_interceptor(
|
||||
channel,
|
||||
ClientAuthenticationInterceptor::create(&config.base_node_grpc_authentication)?,
|
||||
)
|
||||
.max_encoding_message_size(MAX_GRPC_MESSAGE_SIZE)
|
||||
.max_decoding_message_size(MAX_GRPC_MESSAGE_SIZE);
|
||||
|
||||
Ok(node_conn)
|
||||
}
|
||||
|
||||
async fn connect_base_node(config: &MinerConfig) -> Result<BaseNodeGrpcClient, MinerError> {
|
||||
let base_node_addr;
|
||||
if let Some(ref a) = config.base_node_grpc_address {
|
||||
base_node_addr = a.clone();
|
||||
} else {
|
||||
base_node_addr = prompt_for_base_node_address(config.network)?;
|
||||
}
|
||||
|
||||
info!(target: LOG_TARGET, "👛 Connecting to base node at {}", base_node_addr);
|
||||
let mut endpoint = Endpoint::new(base_node_addr)?;
|
||||
|
||||
if let Some(domain_name) = config.base_node_grpc_tls_domain_name.as_ref() {
|
||||
let pem = tokio::fs::read(config.config_dir.join(&config.base_node_grpc_ca_cert_filename))
|
||||
.await
|
||||
.map_err(|e| MinerError::TlsConnectionError(e.to_string()))?;
|
||||
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| MinerError::TlsConnectionError(e.to_string()))?;
|
||||
}
|
||||
|
||||
let channel = endpoint
|
||||
.connect()
|
||||
.await
|
||||
.map_err(|e| MinerError::TlsConnectionError(e.to_string()))?;
|
||||
let node_conn = BaseNodeClient::with_interceptor(
|
||||
channel,
|
||||
ClientAuthenticationInterceptor::create(&config.base_node_grpc_authentication)?,
|
||||
)
|
||||
.max_encoding_message_size(MAX_GRPC_MESSAGE_SIZE)
|
||||
.max_decoding_message_size(MAX_GRPC_MESSAGE_SIZE);
|
||||
|
||||
Ok(node_conn)
|
||||
}
|
||||
|
||||
async fn verify_base_node_responses(
|
||||
node_conn: &mut BaseNodeGrpcClient,
|
||||
config: &MinerConfig,
|
||||
) -> Result<(), MinerError> {
|
||||
if let Err(e) = verify_base_node_grpc_mining_responses(node_conn, config.pow_algo_request()).await {
|
||||
return Err(MinerError::BaseNodeNotResponding(e));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
struct GetNewBlockResponse {
|
||||
block: Block,
|
||||
target_difficulty: u64,
|
||||
vm_key: FixedHash,
|
||||
}
|
||||
|
||||
/// Gets a new block from base node or p2pool node if its enabled in config
|
||||
async fn get_new_block(
|
||||
base_node_client: &mut BaseNodeGrpcClient,
|
||||
sha_p2pool_client: Arc<Mutex<Option<ShaP2PoolGrpcClient>>>,
|
||||
config: &MinerConfig,
|
||||
cli: &Cli,
|
||||
key_manager: &MemoryDbKeyManager,
|
||||
wallet_payment_address: &TariAddress,
|
||||
consensus_manager: &ConsensusManager,
|
||||
) -> Result<GetNewBlockResponse, MinerError> {
|
||||
if config.sha_p2pool_enabled {
|
||||
if let Some(client) = sha_p2pool_client.lock().await.as_mut() {
|
||||
return get_new_block_p2pool_node(config, client, wallet_payment_address).await;
|
||||
}
|
||||
}
|
||||
|
||||
get_new_block_base_node(
|
||||
base_node_client,
|
||||
config,
|
||||
cli,
|
||||
key_manager,
|
||||
wallet_payment_address,
|
||||
consensus_manager,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn get_new_block_base_node(
|
||||
base_node_client: &mut BaseNodeGrpcClient,
|
||||
config: &MinerConfig,
|
||||
cli: &Cli,
|
||||
key_manager: &MemoryDbKeyManager,
|
||||
wallet_payment_address: &TariAddress,
|
||||
consensus_manager: &ConsensusManager,
|
||||
) -> Result<GetNewBlockResponse, MinerError> {
|
||||
debug!(target: LOG_TARGET, "Getting new block template");
|
||||
let template_response = base_node_client
|
||||
.get_new_block_template(config.pow_algo_request())
|
||||
.await?
|
||||
.into_inner();
|
||||
let mut block_template = template_response
|
||||
.new_block_template
|
||||
.clone()
|
||||
.ok_or_else(|| err_empty("new_block_template"))?;
|
||||
let height = block_template
|
||||
.header
|
||||
.as_ref()
|
||||
.ok_or_else(|| err_empty("header"))?
|
||||
.height;
|
||||
|
||||
if config.mine_on_tip_only {
|
||||
debug!(
|
||||
target: LOG_TARGET,
|
||||
"Checking if base node is synced, because mine_on_tip_only is true"
|
||||
);
|
||||
validate_tip(base_node_client, height, cli.mine_until_height).await?;
|
||||
}
|
||||
|
||||
debug!(target: LOG_TARGET, "Getting coinbase");
|
||||
let miner_data = template_response.miner_data.ok_or_else(|| err_empty("miner_data"))?;
|
||||
let fee = MicroMinotari::from(miner_data.total_fees);
|
||||
let reward = MicroMinotari::from(miner_data.reward);
|
||||
let (coinbase_output, coinbase_kernel) = generate_coinbase(
|
||||
fee,
|
||||
reward,
|
||||
height,
|
||||
&CoinBaseExtra::try_from(config.coinbase_extra.as_bytes().to_vec())?,
|
||||
key_manager,
|
||||
wallet_payment_address,
|
||||
true,
|
||||
consensus_manager.consensus_constants(height),
|
||||
config.range_proof_type,
|
||||
PaymentId::Open {
|
||||
user_data: vec![],
|
||||
tx_type: TxType::Coinbase,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.map_err(|e| MinerError::CoinbaseError(e.to_string()))?;
|
||||
debug!(target: LOG_TARGET, "Coinbase kernel: {}", coinbase_kernel);
|
||||
debug!(target: LOG_TARGET, "Coinbase output: {}", coinbase_output);
|
||||
|
||||
let body = block_template
|
||||
.body
|
||||
.as_mut()
|
||||
.ok_or_else(|| err_empty("new_block_template.body"))?;
|
||||
let grpc_output =
|
||||
grpc_output_with_payref(coinbase_output.clone(), None).map_err(|e| MinerError::Conversion(e.to_string()))?;
|
||||
|
||||
body.outputs.push(grpc_output);
|
||||
body.kernels.push(coinbase_kernel.into());
|
||||
let target_difficulty = miner_data.target_difficulty;
|
||||
|
||||
debug!(target: LOG_TARGET, "Asking base node to assemble the MMR roots");
|
||||
let block_result = base_node_client.get_new_block(block_template).await?.into_inner();
|
||||
|
||||
Ok(GetNewBlockResponse {
|
||||
block: block_result.block.ok_or_else(|| err_empty("block"))?,
|
||||
target_difficulty,
|
||||
vm_key: FixedHash::try_from(block_result.vm_key).map_err(|_| MinerError::Conversion("vm_key".to_string()))?,
|
||||
})
|
||||
}
|
||||
|
||||
async fn get_new_block_p2pool_node(
|
||||
config: &MinerConfig,
|
||||
sha_p2pool_client: &mut ShaP2PoolGrpcClient,
|
||||
wallet_payment_address: &TariAddress,
|
||||
) -> Result<GetNewBlockResponse, MinerError> {
|
||||
let pow_algo = PowAlgo {
|
||||
pow_algo: PowAlgos::Sha3x.into(),
|
||||
};
|
||||
let coinbase_extra = if config.coinbase_extra.trim().is_empty() {
|
||||
String::new()
|
||||
} else {
|
||||
config.coinbase_extra.clone()
|
||||
};
|
||||
let block_result = sha_p2pool_client
|
||||
.get_new_block(GetNewBlockRequest {
|
||||
pow: Some(pow_algo),
|
||||
coinbase_extra,
|
||||
wallet_payment_address: wallet_payment_address.to_base58(),
|
||||
})
|
||||
.await?
|
||||
.into_inner();
|
||||
let new_block_result = block_result.block.ok_or_else(|| err_empty("block result"))?;
|
||||
let block = new_block_result.block.ok_or_else(|| err_empty("block response"))?;
|
||||
Ok(GetNewBlockResponse {
|
||||
block,
|
||||
target_difficulty: block_result.target_difficulty,
|
||||
vm_key: FixedHash::zero(),
|
||||
})
|
||||
}
|
||||
|
||||
async fn submit_block(
|
||||
config: &MinerConfig,
|
||||
base_node_client: &mut BaseNodeGrpcClient,
|
||||
sha_p2pool_client: Option<&mut ShaP2PoolGrpcClient>,
|
||||
block: Block,
|
||||
wallet_payment_address: &TariAddress,
|
||||
) -> Result<SubmitBlockResponse, MinerError> {
|
||||
if config.sha_p2pool_enabled {
|
||||
if let Some(client) = sha_p2pool_client {
|
||||
return Ok(client
|
||||
.submit_block(SubmitBlockRequest {
|
||||
block: Some(block),
|
||||
wallet_payment_address: wallet_payment_address.to_hex(),
|
||||
})
|
||||
.await
|
||||
.map_err(MinerError::GrpcStatus)?
|
||||
.into_inner());
|
||||
}
|
||||
}
|
||||
|
||||
Ok(base_node_client
|
||||
.submit_block(block)
|
||||
.await
|
||||
.map_err(MinerError::GrpcStatus)?
|
||||
.into_inner())
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_lines)]
|
||||
async fn mining_cycle(
|
||||
base_node_client: &mut BaseNodeGrpcClient,
|
||||
sha_p2pool_client: Option<ShaP2PoolGrpcClient>,
|
||||
config: &MinerConfig,
|
||||
cli: &Cli,
|
||||
key_manager: &MemoryDbKeyManager,
|
||||
wallet_payment_address: &TariAddress,
|
||||
consensus_manager: &ConsensusManager,
|
||||
publisher_socket: Arc<Mutex<Socket>>,
|
||||
subscriber_socket: Arc<Mutex<Socket>>,
|
||||
) -> Result<bool, MinerError> {
|
||||
let sha_p2pool_client = Arc::new(Mutex::new(sha_p2pool_client));
|
||||
let block_result = get_new_block(
|
||||
base_node_client,
|
||||
sha_p2pool_client.clone(),
|
||||
config,
|
||||
cli,
|
||||
key_manager,
|
||||
wallet_payment_address,
|
||||
consensus_manager,
|
||||
)
|
||||
.await?;
|
||||
let block = block_result.block;
|
||||
let header = block.clone().header.ok_or_else(|| err_empty("block.header"))?;
|
||||
|
||||
debug!(target: LOG_TARGET, "Initializing miner");
|
||||
|
||||
let rx_factory = if config.proof_of_work_algo == PowAlgorithm::RandomXT {
|
||||
Some(RandomXFactory::new(config.num_mining_threads))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let mut reports = Miner::init_mining(
|
||||
base_node_client.clone(),
|
||||
header.clone(),
|
||||
block_result.target_difficulty,
|
||||
config.num_mining_threads,
|
||||
false,
|
||||
block_result.vm_key,
|
||||
rx_factory,
|
||||
publisher_socket,
|
||||
subscriber_socket,
|
||||
);
|
||||
let mut reporting_timeout = Instant::now();
|
||||
let mut block_submitted = false;
|
||||
while let Some(report) = reports.next().await {
|
||||
if let Some(header) = report.header.clone() {
|
||||
let mut submit = true;
|
||||
if let Some(min_diff) = cli.miner_min_diff {
|
||||
if report.difficulty < min_diff {
|
||||
submit = false;
|
||||
debug!(
|
||||
target: LOG_TARGET_FILE,
|
||||
"Mined difficulty {} below minimum difficulty {}. Not submitting.", report.difficulty, min_diff
|
||||
);
|
||||
}
|
||||
}
|
||||
if let Some(max_diff) = cli.miner_max_diff {
|
||||
if report.difficulty > max_diff {
|
||||
submit = false;
|
||||
debug!(
|
||||
target: LOG_TARGET_FILE,
|
||||
"Mined difficulty {} greater than maximum difficulty {}. Not submitting.",
|
||||
report.difficulty,
|
||||
max_diff
|
||||
);
|
||||
}
|
||||
}
|
||||
if submit {
|
||||
// Mined a block fitting the difficulty
|
||||
let block_header = BlockHeader::try_from(header.clone()).map_err(MinerError::Conversion)?;
|
||||
debug!(
|
||||
target: LOG_TARGET,
|
||||
"Miner found block header {} with difficulty {:?}", block_header, report.difficulty,
|
||||
);
|
||||
println!(
|
||||
"Miner found block header {} with difficulty {:?}", block_header, report.difficulty,
|
||||
);
|
||||
let mut mined_block = block.clone();
|
||||
mined_block.header = Some(header);
|
||||
// 5. Sending block to the node
|
||||
submit_block(
|
||||
config,
|
||||
base_node_client,
|
||||
sha_p2pool_client.lock().await.as_mut(),
|
||||
mined_block,
|
||||
wallet_payment_address,
|
||||
)
|
||||
.await?;
|
||||
block_submitted = true;
|
||||
break;
|
||||
} else {
|
||||
display_report(&report, config.num_mining_threads).await;
|
||||
}
|
||||
} else {
|
||||
display_report(&report, config.num_mining_threads).await;
|
||||
}
|
||||
//if config.mine_on_tip_only && reporting_timeout.elapsed() > config.validate_tip_interval() {
|
||||
validate_tip(base_node_client, report.height, cli.mine_until_height).await?;
|
||||
reporting_timeout = Instant::now();
|
||||
//}
|
||||
}
|
||||
|
||||
// Not waiting for threads to stop, they should stop in a short while after `reports` dropped
|
||||
Ok(block_submitted)
|
||||
}
|
||||
|
||||
pub async fn display_report(report: &MiningReport, num_mining_threads: usize) {
|
||||
let mut hashrate = report.hashes as f64 / report.elapsed.as_secs() as f64;
|
||||
let display_string = if hashrate > 1_000_000.0 {
|
||||
hashrate /= 1_000_000.0;
|
||||
"MH/s"
|
||||
} else {
|
||||
hashrate /= 1_000.0;
|
||||
"KH/s"
|
||||
};
|
||||
info!(
|
||||
target: LOG_TARGET,
|
||||
"⛏ Miner {:0>2} reported {:.2}{} with total {:.2}{} over {} threads. Height: {}. Target: {})",
|
||||
report.miner,
|
||||
hashrate,
|
||||
display_string,
|
||||
hashrate * num_mining_threads as f64,
|
||||
display_string,
|
||||
num_mining_threads,
|
||||
report.height,
|
||||
report.target_difficulty,
|
||||
);
|
||||
}
|
||||
|
||||
/// If config
|
||||
async fn validate_tip(
|
||||
node_conn: &mut BaseNodeGrpcClient,
|
||||
height: u64,
|
||||
mine_until_height: Option<u64>,
|
||||
) -> Result<(), MinerError> {
|
||||
let tip = node_conn
|
||||
.get_tip_info(minotari_app_grpc::tari_rpc::Empty {})
|
||||
.await?
|
||||
.into_inner();
|
||||
let longest_height = tip.clone().metadata.unwrap().best_block_height;
|
||||
if let Some(height) = mine_until_height {
|
||||
if longest_height >= height {
|
||||
return Err(MinerError::MineUntilHeightReached(height));
|
||||
}
|
||||
}
|
||||
if height <= longest_height {
|
||||
return Err(MinerError::MinerLostBlock(height));
|
||||
}
|
||||
if !tip.initial_sync_achieved || tip.metadata.is_none() {
|
||||
return Err(MinerError::NodeNotReady);
|
||||
}
|
||||
if height <= longest_height {
|
||||
return Err(MinerError::MinerLostBlock(height));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
419
internal/gbt/tari/minotari_miner/src/stratum/controller.rs
Normal file
419
internal/gbt/tari/minotari_miner/src/stratum/controller.rs
Normal file
@@ -0,0 +1,419 @@
|
||||
// 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::{
|
||||
io::{BufRead, ErrorKind, Write},
|
||||
sync::mpsc,
|
||||
thread,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use log::*;
|
||||
|
||||
use crate::stratum::{error::Error, stratum_types as types, stream::Stream};
|
||||
|
||||
pub const LOG_TARGET: &str = "minotari::miner::stratum::controller";
|
||||
pub const LOG_TARGET_FILE: &str = "minotari::logging::miner::stratum::controller";
|
||||
|
||||
pub struct Controller {
|
||||
server_url: String,
|
||||
server_login: Option<String>,
|
||||
server_password: Option<String>,
|
||||
server_tls_enabled: Option<bool>,
|
||||
stream: Option<Stream>,
|
||||
rx: mpsc::Receiver<types::client_message::ClientMessage>,
|
||||
pub tx: mpsc::Sender<types::client_message::ClientMessage>,
|
||||
miner_tx: mpsc::Sender<types::miner_message::MinerMessage>,
|
||||
last_request_id: String,
|
||||
}
|
||||
|
||||
// fn invalid_error_response() -> types::RpcError {
|
||||
// types::RpcError {
|
||||
// code: 0,
|
||||
// message: "Invalid error response received".to_owned(),
|
||||
// }
|
||||
// }
|
||||
|
||||
impl Controller {
|
||||
pub fn new(
|
||||
server_url: &str,
|
||||
server_login: Option<String>,
|
||||
server_password: Option<String>,
|
||||
server_tls_enabled: Option<bool>,
|
||||
miner_tx: mpsc::Sender<types::miner_message::MinerMessage>,
|
||||
) -> Result<Controller, Error> {
|
||||
let (tx, rx) = mpsc::channel::<types::client_message::ClientMessage>();
|
||||
Ok(Controller {
|
||||
server_url: server_url.to_string(),
|
||||
server_login,
|
||||
server_password,
|
||||
server_tls_enabled,
|
||||
stream: None,
|
||||
tx,
|
||||
rx,
|
||||
miner_tx,
|
||||
last_request_id: "".to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn try_connect(&mut self) -> Result<(), Error> {
|
||||
self.stream = None;
|
||||
let stream = Stream::try_connect(&self.server_url, self.server_tls_enabled)?;
|
||||
self.stream = Some(stream);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn stream(&mut self) -> Result<&mut Stream, Error> {
|
||||
self.stream.as_mut().ok_or(Error::NotConnected)
|
||||
}
|
||||
|
||||
fn read_message(&mut self) -> Result<Option<String>, Error> {
|
||||
let mut line = String::new();
|
||||
match self.stream()?.read_line(&mut line) {
|
||||
Ok(_) => {
|
||||
// stream is not returning a proper error on disconnect
|
||||
if line.is_empty() {
|
||||
return Err(Error::Connection("broken pipe".to_string()));
|
||||
}
|
||||
Ok(Some(line))
|
||||
},
|
||||
Err(ref e) if e.kind() == ErrorKind::BrokenPipe => Err(Error::Connection("broken pipe".to_string())),
|
||||
Err(ref e) if e.kind() == ErrorKind::WouldBlock => Ok(None),
|
||||
Err(e) => {
|
||||
error!(target: LOG_TARGET, "Communication error with stratum server: {}", e);
|
||||
Err(Error::Connection("broken pipe".to_string()))
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn send_message(&mut self, message: &str) -> Result<(), Error> {
|
||||
let stream = self.stream()?;
|
||||
debug!(target: LOG_TARGET_FILE, "sending request: {}", message);
|
||||
stream.write_all(message.as_bytes())?;
|
||||
stream.write_all(b"\n")?;
|
||||
stream.flush()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn send_message_get_job_template(&mut self) -> Result<(), Error> {
|
||||
let params = types::worker_identifier::WorkerIdentifier {
|
||||
id: self.last_request_id.clone(),
|
||||
};
|
||||
let req = types::rpc_request::RpcRequest {
|
||||
id: Some(self.last_request_id.clone()),
|
||||
jsonrpc: "2.0".to_string(),
|
||||
method: "getjob".to_string(),
|
||||
params: Some(serde_json::to_value(params)?),
|
||||
};
|
||||
let req_str = serde_json::to_string(&req)?;
|
||||
self.send_message(&req_str)
|
||||
}
|
||||
|
||||
fn send_login(&mut self) -> Result<(), Error> {
|
||||
// only send the login request if a login string is configured
|
||||
let login_str = match self.server_login.clone() {
|
||||
None => "".to_string(),
|
||||
Some(server_login) => server_login,
|
||||
};
|
||||
if login_str.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
let password_str = match self.server_password.clone() {
|
||||
None => "".to_string(),
|
||||
Some(server_password) => server_password,
|
||||
};
|
||||
let params = types::login_params::LoginParams {
|
||||
login: login_str,
|
||||
pass: password_str,
|
||||
agent: "minotari-miner".to_string(),
|
||||
};
|
||||
let req_id = self.last_request_id.to_string();
|
||||
let req = types::rpc_request::RpcRequest {
|
||||
id: if req_id.is_empty() {
|
||||
Some("0".to_string())
|
||||
} else {
|
||||
Some(req_id)
|
||||
},
|
||||
jsonrpc: "2.0".to_string(),
|
||||
method: "login".to_string(),
|
||||
params: Some(serde_json::to_value(params)?),
|
||||
};
|
||||
let req_str = serde_json::to_string(&req)?;
|
||||
self.send_message(&req_str)
|
||||
}
|
||||
|
||||
fn send_keepalive(&mut self) -> Result<(), Error> {
|
||||
let req = types::rpc_request::RpcRequest {
|
||||
id: Some(self.last_request_id.to_string()),
|
||||
jsonrpc: "2.0".to_string(),
|
||||
method: "keepalive".to_string(),
|
||||
params: None,
|
||||
};
|
||||
let req_str = serde_json::to_string(&req)?;
|
||||
self.send_message(&req_str)
|
||||
}
|
||||
|
||||
fn send_message_submit(&mut self, job_id: u64, hash: String, nonce: u64) -> Result<(), Error> {
|
||||
debug!(
|
||||
target: LOG_TARGET,
|
||||
"Submitting share with hash {} and nonce {}", hash, nonce
|
||||
);
|
||||
let params_in = types::submit_params::SubmitParams {
|
||||
id: self.last_request_id.to_string(),
|
||||
job_id,
|
||||
hash,
|
||||
nonce,
|
||||
};
|
||||
let params = serde_json::to_string(¶ms_in)?;
|
||||
let req = types::rpc_request::RpcRequest {
|
||||
id: Some(self.last_request_id.to_string()),
|
||||
jsonrpc: "2.0".to_string(),
|
||||
method: "submit".to_string(),
|
||||
params: Some(serde_json::from_str(¶ms)?),
|
||||
};
|
||||
let req_str = serde_json::to_string(&req)?;
|
||||
self.send_message(&req_str)
|
||||
}
|
||||
|
||||
fn send_miner_job(&mut self, job: types::job_params::JobParams) -> Result<(), Error> {
|
||||
let blob_bytes =
|
||||
base64::decode(&job.blob).map_err(|_| Error::General("Invalid base64 byte string received".to_string()))?;
|
||||
let miner_message = types::miner_message::MinerMessage::ReceivedJob(
|
||||
job.height,
|
||||
job.job_id.parse::<u64>()?,
|
||||
job.target.parse::<u64>()?,
|
||||
blob_bytes,
|
||||
);
|
||||
self.miner_tx.send(miner_message).map_err(Error::from)
|
||||
}
|
||||
|
||||
fn send_miner_stop(&mut self) -> Result<(), Error> {
|
||||
let miner_message = types::miner_message::MinerMessage::StopJob;
|
||||
self.miner_tx.send(miner_message).map_err(Error::from)
|
||||
}
|
||||
|
||||
fn send_miner_resume(&mut self) -> Result<(), Error> {
|
||||
let miner_message = types::miner_message::MinerMessage::ResumeJob;
|
||||
self.miner_tx.send(miner_message).map_err(Error::from)
|
||||
}
|
||||
|
||||
pub fn handle_request(&mut self, req: types::rpc_request::RpcRequest) -> Result<(), Error> {
|
||||
debug!(target: LOG_TARGET_FILE, "Received request type: {}", req.method);
|
||||
match req.method.as_str() {
|
||||
"job" => match req.params {
|
||||
None => Err(Error::Request("No params in job request".to_owned())),
|
||||
Some(params) => {
|
||||
let job = serde_json::from_value::<types::job_params::JobParams>(params)?;
|
||||
info!(
|
||||
target: LOG_TARGET,
|
||||
"Got a new job for height {} with target difficulty {}", job.height, job.target
|
||||
);
|
||||
self.send_miner_job(job)
|
||||
},
|
||||
},
|
||||
_ => Err(Error::Request("Unknown method".to_owned())),
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_error(&mut self, error: types::rpc_error::RpcError) {
|
||||
if [-1, 24].contains(&error.code) {
|
||||
// unauthorized
|
||||
let _result = self.send_login();
|
||||
} else if [21, 20, 22, 23, 25].contains(&error.code) {
|
||||
// problem with template
|
||||
let _result = self.send_message_get_job_template();
|
||||
} else {
|
||||
// unhandled
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::cognitive_complexity)]
|
||||
pub fn handle_response(&mut self, res: types::rpc_response::RpcResponse) -> Result<(), Error> {
|
||||
debug!(target: LOG_TARGET_FILE, "Received response with id: {}", res.id);
|
||||
match res.result {
|
||||
Some(result) => {
|
||||
let login_response = serde_json::from_value::<types::login_response::LoginResponse>(result.clone());
|
||||
if let Ok(st) = login_response {
|
||||
info!(
|
||||
target: LOG_TARGET,
|
||||
"Successful login to server, worker identifier is {}", st.id
|
||||
);
|
||||
info!(
|
||||
target: LOG_TARGET,
|
||||
"Got a new job for height {} with target difficulty {}", st.job.height, st.job.target
|
||||
);
|
||||
self.last_request_id = st.id;
|
||||
let _result = self.send_miner_job(st.job);
|
||||
return Ok(());
|
||||
};
|
||||
let job_response = serde_json::from_value::<types::job_params::JobParams>(result.clone());
|
||||
if let Ok(st) = job_response {
|
||||
info!(
|
||||
target: LOG_TARGET,
|
||||
"Got a new job for height {} with target difficulty {}", st.height, st.target
|
||||
);
|
||||
let _result = self.send_miner_job(st);
|
||||
return Ok(());
|
||||
};
|
||||
let submit_response = serde_json::from_value::<types::submit_response::SubmitResponse>(result.clone());
|
||||
if let Ok(st) = submit_response {
|
||||
let error = st.error;
|
||||
if let Some(error) = error {
|
||||
// rejected share
|
||||
self.handle_error(error);
|
||||
warn!(target: LOG_TARGET, "Rejected");
|
||||
} else {
|
||||
// accepted share
|
||||
debug!(target: LOG_TARGET, "Share accepted: {:?}", st.status);
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
let rpc_response = serde_json::from_value::<types::rpc_response::RpcResponse>(result);
|
||||
if let Ok(st) = rpc_response {
|
||||
let error = st.error;
|
||||
if let Some(error) = error {
|
||||
self.handle_error(error);
|
||||
}
|
||||
return Ok(());
|
||||
} else {
|
||||
debug!(target: LOG_TARGET_FILE, "RPC Response: {:?}", rpc_response);
|
||||
};
|
||||
},
|
||||
None => {
|
||||
error!(target: LOG_TARGET, "RPC error: {:?}", res);
|
||||
},
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(clippy::cognitive_complexity)]
|
||||
pub fn run(mut self) {
|
||||
let server_read_interval = Duration::from_secs(1);
|
||||
let server_retry_interval = Duration::from_secs(5);
|
||||
let mut next_server_read = Instant::now() + server_read_interval;
|
||||
let mut next_server_retry = Instant::now();
|
||||
// Request the first job template
|
||||
thread::sleep(Duration::from_secs(1));
|
||||
let mut was_disconnected = true;
|
||||
loop {
|
||||
// Check our connection status, and try to correct if possible
|
||||
if self.stream.is_none() {
|
||||
if !was_disconnected {
|
||||
let _result = self.send_miner_stop();
|
||||
}
|
||||
was_disconnected = true;
|
||||
if Instant::now() > next_server_retry {
|
||||
if self.try_connect().is_err() {
|
||||
let status = format!(
|
||||
"Connection Status: Can't establish server connection to {}. Will retry every {} seconds",
|
||||
self.server_url,
|
||||
server_retry_interval.as_secs()
|
||||
);
|
||||
warn!("{}", status);
|
||||
self.stream = None;
|
||||
} else {
|
||||
let status = format!("Connection Status: Connected to server at {}.", self.server_url);
|
||||
info!(target: LOG_TARGET, "{}", status);
|
||||
}
|
||||
next_server_retry = Instant::now() + server_retry_interval;
|
||||
if self.stream.is_none() {
|
||||
thread::sleep(std::time::Duration::from_secs(1));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// get new job template
|
||||
if was_disconnected {
|
||||
was_disconnected = false;
|
||||
let _result = self.send_login();
|
||||
let _result = self.send_miner_resume();
|
||||
}
|
||||
// read messages from server
|
||||
if Instant::now() > next_server_read {
|
||||
match self.read_message() {
|
||||
Ok(Some(m)) => {
|
||||
// figure out what kind of message,
|
||||
// and dispatch appropriately
|
||||
debug!(target: LOG_TARGET_FILE, "Received message: {}", m);
|
||||
// Deserialize to see what type of object it is
|
||||
if let Ok(v) = serde_json::from_str::<serde_json::Value>(&m) {
|
||||
// Is this a response or request?
|
||||
if v["method"] == "job" {
|
||||
// this is a request
|
||||
match serde_json::from_str::<types::rpc_request::RpcRequest>(&m) {
|
||||
Err(e) => error!(target: LOG_TARGET, "Error parsing request {} : {:?}", m, e),
|
||||
Ok(request) => {
|
||||
if let Err(err) = self.handle_request(request) {
|
||||
error!(target: LOG_TARGET, "Error handling request {} : :{:?}", m, err)
|
||||
}
|
||||
},
|
||||
}
|
||||
} else {
|
||||
// this is a response
|
||||
match serde_json::from_str::<types::rpc_response::RpcResponse>(&m) {
|
||||
Err(e) => error!(target: LOG_TARGET, "Error parsing response {} : {:?}", m, e),
|
||||
Ok(response) => {
|
||||
if let Err(err) = self.handle_response(response) {
|
||||
error!(target: LOG_TARGET, "Error handling response {} : :{:?}", m, err)
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
continue;
|
||||
} else {
|
||||
error!(target: LOG_TARGET, "Error parsing message: {}", m)
|
||||
}
|
||||
},
|
||||
Ok(None) => {
|
||||
// noop, nothing to read for this interval
|
||||
},
|
||||
Err(e) => {
|
||||
error!(target: LOG_TARGET, "Error reading message: {:?}", e);
|
||||
self.stream = None;
|
||||
continue;
|
||||
},
|
||||
}
|
||||
next_server_read = Instant::now() + server_read_interval;
|
||||
}
|
||||
}
|
||||
|
||||
// Talk to the miner algorithm
|
||||
while let Some(message) = self.rx.try_iter().next() {
|
||||
debug!(target: LOG_TARGET_FILE, "Client received message: {:?}", message);
|
||||
let result = match message {
|
||||
types::client_message::ClientMessage::FoundSolution(job_id, hash, nonce) => {
|
||||
self.send_message_submit(job_id, hash, nonce)
|
||||
},
|
||||
types::client_message::ClientMessage::KeepAlive => self.send_keepalive(),
|
||||
types::client_message::ClientMessage::Shutdown => {
|
||||
debug!(target: LOG_TARGET_FILE, "Shutting down client controller");
|
||||
return;
|
||||
},
|
||||
};
|
||||
if let Err(e) = result {
|
||||
error!(target: LOG_TARGET, "Mining Controller Error {:?}", e);
|
||||
self.stream = None;
|
||||
}
|
||||
}
|
||||
thread::sleep(Duration::from_millis(10));
|
||||
} // loop
|
||||
}
|
||||
}
|
||||
76
internal/gbt/tari/minotari_miner/src/stratum/error.rs
Normal file
76
internal/gbt/tari/minotari_miner/src/stratum/error.rs
Normal file
@@ -0,0 +1,76 @@
|
||||
// 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 tari_max_size::MaxSizeBytesError;
|
||||
use thiserror::Error;
|
||||
|
||||
#[allow(clippy::enum_variant_names)]
|
||||
#[derive(Debug, Error)]
|
||||
pub enum Error {
|
||||
#[error("Connection error: {0}")]
|
||||
Connection(String),
|
||||
#[error("Request error: {0}")]
|
||||
Request(String),
|
||||
// ResponseError(String),
|
||||
#[error("Failed to parse JSON: {0}")]
|
||||
Json(#[from] serde_json::error::Error),
|
||||
#[error("Blob is not a valid hex value: {0}")]
|
||||
Hex(#[from] hex::FromHexError),
|
||||
#[error("System time error: {0}")]
|
||||
Time(#[from] std::time::SystemTimeError),
|
||||
#[error("Client Tx is not set")]
|
||||
ClientTxNotSet,
|
||||
#[error("Io error: {0}")]
|
||||
Io(#[from] std::io::Error),
|
||||
#[error("Can't create TLS connector: {0}")]
|
||||
Tls(#[from] native_tls::Error),
|
||||
#[error("Can't establish TLS connection: {0}")]
|
||||
Tcp(#[from] Box<native_tls::HandshakeError<std::net::TcpStream>>),
|
||||
#[error("No connected stream")]
|
||||
NotConnected,
|
||||
#[error("Can't parse int: {0}")]
|
||||
Parse(#[from] std::num::ParseIntError),
|
||||
#[error("General error: {0}")]
|
||||
General(String),
|
||||
#[error("Missing Data error: {0}")]
|
||||
MissingData(String),
|
||||
#[error("Limit exceeded error: {0}")]
|
||||
MaxSizeBytesError(#[from] MaxSizeBytesError),
|
||||
}
|
||||
|
||||
impl<T> From<std::sync::PoisonError<T>> for Error {
|
||||
fn from(error: std::sync::PoisonError<T>) -> Self {
|
||||
Error::General(format!("Failed to get lock: {:?}", error))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<std::sync::mpsc::SendError<T>> for Error {
|
||||
fn from(error: std::sync::mpsc::SendError<T>) -> Self {
|
||||
Error::General(format!("Failed to send to a channel: {:?}", error))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<native_tls::HandshakeError<std::net::TcpStream>> for Error {
|
||||
fn from(error: native_tls::HandshakeError<std::net::TcpStream>) -> Self {
|
||||
Error::General(format!("TLS handshake error: {:?}", error))
|
||||
}
|
||||
}
|
||||
26
internal/gbt/tari/minotari_miner/src/stratum/mod.rs
Normal file
26
internal/gbt/tari/minotari_miner/src/stratum/mod.rs
Normal file
@@ -0,0 +1,26 @@
|
||||
// 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.
|
||||
pub mod controller;
|
||||
pub mod error;
|
||||
pub mod stratum_controller;
|
||||
pub mod stratum_types;
|
||||
pub mod stream;
|
||||
@@ -0,0 +1,221 @@
|
||||
// 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::{convert::TryFrom, sync::mpsc, thread, time::SystemTime};
|
||||
|
||||
use borsh::BorshDeserialize;
|
||||
use futures::stream::StreamExt;
|
||||
use log::*;
|
||||
use minotari_app_grpc::tari_rpc::BlockHeader;
|
||||
//use tari_common_types::types::FixedHash;
|
||||
use tari_max_size::MaxSizeBytes;
|
||||
use tari_utilities::{hex::Hex, ByteArray};
|
||||
|
||||
|
||||
|
||||
use crate::{
|
||||
miner::Miner,
|
||||
run_miner::display_report,
|
||||
stratum::{error::Error, stratum_types as types},
|
||||
};
|
||||
|
||||
pub const LOG_TARGET: &str = "minotari::miner::stratum::controller";
|
||||
pub const LOG_TARGET_FILE: &str = "minotari::logging::miner::stratum::controller";
|
||||
|
||||
type CurrentBlob = MaxSizeBytes<{ 4 * 1024 * 1024 }>; // 4 MiB
|
||||
|
||||
pub struct Controller {
|
||||
rx: mpsc::Receiver<types::miner_message::MinerMessage>,
|
||||
pub tx: mpsc::Sender<types::miner_message::MinerMessage>,
|
||||
client_tx: Option<mpsc::Sender<types::client_message::ClientMessage>>,
|
||||
current_height: u64,
|
||||
current_job_id: u64,
|
||||
current_difficulty_target: u64,
|
||||
current_blob: CurrentBlob,
|
||||
current_header: Option<BlockHeader>,
|
||||
keep_alive_time: SystemTime,
|
||||
num_mining_threads: usize,
|
||||
|
||||
}
|
||||
|
||||
impl Controller {
|
||||
pub fn new(num_mining_threads: usize) -> Result<Controller, String> {
|
||||
let (tx, rx) = mpsc::channel::<types::miner_message::MinerMessage>();
|
||||
Ok(Controller {
|
||||
rx,
|
||||
tx,
|
||||
client_tx: None,
|
||||
current_height: 0,
|
||||
current_job_id: 0,
|
||||
current_difficulty_target: 0,
|
||||
current_blob: CurrentBlob::default(),
|
||||
current_header: None,
|
||||
keep_alive_time: SystemTime::now(),
|
||||
num_mining_threads,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn set_client_tx(&mut self, client_tx: mpsc::Sender<types::client_message::ClientMessage>) {
|
||||
self.client_tx = Some(client_tx);
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_lines)]
|
||||
pub async fn run(&mut self) -> Result<(), Error> {
|
||||
let mut miner: Option<Miner> = None;
|
||||
loop {
|
||||
// lets see if we need to change the state of the miner.
|
||||
while let Some(message) = self.rx.try_iter().next() {
|
||||
debug!(target: LOG_TARGET_FILE, "Miner received message: {:?}", message);
|
||||
match message {
|
||||
types::miner_message::MinerMessage::ReceivedJob(height, job_id, diff, blob) => {
|
||||
match self.should_we_update_job(height, job_id, diff, CurrentBlob::try_from(blob)?) {
|
||||
Ok(should_we_update) => {
|
||||
if should_we_update {
|
||||
/*let header = self
|
||||
.current_header
|
||||
.clone()
|
||||
.ok_or_else(|| Error::MissingData("Header".to_string()))?;
|
||||
if let Some(acive_miner) = miner.as_mut() {
|
||||
acive_miner.kill_threads();
|
||||
}
|
||||
|
||||
miner = Some(Miner::init_mining(header, self.current_difficulty_target, self.num_mining_threads, true, FixedHash::zero(), None, ));*/
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
debug!(
|
||||
target: LOG_TARGET_FILE,
|
||||
"Miner could not decipher miner message: {:?}", e
|
||||
);
|
||||
// lets wait a second before we try again
|
||||
thread::sleep(std::time::Duration::from_millis(1000));
|
||||
continue;
|
||||
},
|
||||
}
|
||||
},
|
||||
types::miner_message::MinerMessage::StopJob => {
|
||||
debug!(target: LOG_TARGET_FILE, "Stopping jobs");
|
||||
miner = None;
|
||||
continue;
|
||||
},
|
||||
types::miner_message::MinerMessage::ResumeJob => {
|
||||
debug!(target: LOG_TARGET_FILE, "Resuming jobs");
|
||||
miner = None;
|
||||
continue;
|
||||
},
|
||||
types::miner_message::MinerMessage::Shutdown => {
|
||||
debug!(
|
||||
target: LOG_TARGET_FILE,
|
||||
"Stopping jobs and Shutting down mining controller"
|
||||
);
|
||||
miner = None;
|
||||
},
|
||||
};
|
||||
}
|
||||
let mut submit = true;
|
||||
if let Some(reporter) = miner.as_mut() {
|
||||
if let Some(report) = (*reporter).next().await {
|
||||
if let Some(header) = report.header.clone() {
|
||||
if report.difficulty < self.current_difficulty_target {
|
||||
submit = false;
|
||||
debug!(
|
||||
target: LOG_TARGET_FILE,
|
||||
"Mined difficulty {} below target difficulty {}. Not submitting.",
|
||||
report.difficulty,
|
||||
self.current_difficulty_target
|
||||
);
|
||||
}
|
||||
|
||||
if submit {
|
||||
// Mined a block fitting the difficulty
|
||||
let block_header: tari_core::blocks::BlockHeader =
|
||||
tari_core::blocks::BlockHeader::try_from(header).map_err(Error::MissingData)?;
|
||||
let hash = block_header.hash().to_hex();
|
||||
info!(
|
||||
target: LOG_TARGET,
|
||||
"Miner found share with hash {}, nonce {} and difficulty {:?}",
|
||||
hash,
|
||||
block_header.nonce,
|
||||
report.difficulty
|
||||
);
|
||||
debug!(
|
||||
target: LOG_TARGET_FILE,
|
||||
"Miner found share with hash {}, difficulty {:?} and data {:?}",
|
||||
hash,
|
||||
report.difficulty,
|
||||
block_header
|
||||
);
|
||||
self.client_tx
|
||||
.as_mut()
|
||||
.ok_or_else(|| Error::Connection("No connection to pool".to_string()))?
|
||||
.send(types::client_message::ClientMessage::FoundSolution(
|
||||
self.current_job_id,
|
||||
hash,
|
||||
block_header.nonce,
|
||||
))?;
|
||||
self.keep_alive_time = SystemTime::now();
|
||||
continue;
|
||||
} else {
|
||||
display_report(&report, self.num_mining_threads).await;
|
||||
}
|
||||
} else {
|
||||
display_report(&report, self.num_mining_threads).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
if self.keep_alive_time.elapsed()?.as_secs() >= 30 {
|
||||
self.keep_alive_time = SystemTime::now();
|
||||
self.client_tx
|
||||
.as_mut()
|
||||
.ok_or(Error::ClientTxNotSet)?
|
||||
.send(types::client_message::ClientMessage::KeepAlive)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn should_we_update_job(
|
||||
&mut self,
|
||||
height: u64,
|
||||
job_id: u64,
|
||||
diff: u64,
|
||||
blob: CurrentBlob,
|
||||
) -> Result<bool, Error> {
|
||||
if height != self.current_height ||
|
||||
job_id != self.current_job_id ||
|
||||
diff != self.current_difficulty_target ||
|
||||
blob != self.current_blob
|
||||
{
|
||||
self.current_height = height;
|
||||
self.current_job_id = job_id;
|
||||
self.current_blob = blob.clone();
|
||||
self.current_difficulty_target = diff;
|
||||
let mut buffer = blob.as_bytes();
|
||||
let tari_header: tari_core::blocks::BlockHeader = BorshDeserialize::deserialize(&mut buffer)
|
||||
.map_err(|_| Error::General("Byte Blob is not a valid header".to_string()))?;
|
||||
self.current_header = Some(minotari_app_grpc::tari_rpc::BlockHeader::from(tari_header));
|
||||
Ok(true)
|
||||
} else {
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
// 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.
|
||||
pub(crate) mod controller;
|
||||
@@ -0,0 +1,30 @@
|
||||
// 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 serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub enum ClientMessage {
|
||||
// job_id, hash, nonce
|
||||
FoundSolution(u64, String, u64),
|
||||
KeepAlive,
|
||||
Shutdown,
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
// 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 serde::{Deserialize, Serialize};
|
||||
use tari_core::blocks::Block;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct Job {
|
||||
pub job_id: u64,
|
||||
pub block: Option<Block>,
|
||||
pub target: u64,
|
||||
pub height: u64,
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
// 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 serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct JobParams {
|
||||
pub job_id: String,
|
||||
pub blob: String,
|
||||
pub target: String,
|
||||
pub height: u64,
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
// 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 derivative::Derivative;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Serialize, Deserialize, Derivative)]
|
||||
#[derivative(Debug)]
|
||||
pub struct LoginParams {
|
||||
pub login: String,
|
||||
#[derivative(Debug = "ignore")]
|
||||
#[allow(dead_code)]
|
||||
#[serde(skip_serializing)]
|
||||
pub pass: String,
|
||||
pub agent: String,
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
// 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 serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::stratum::stratum_types::job_params::JobParams;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct LoginResponse {
|
||||
pub id: String,
|
||||
pub job: JobParams,
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
// 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 serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub enum MinerMessage {
|
||||
// Height, Id, difficulty, HeaderBlob
|
||||
ReceivedJob(u64, u64, u64, Vec<u8>),
|
||||
ResumeJob,
|
||||
StopJob,
|
||||
Shutdown,
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
// 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.
|
||||
pub(crate) mod client_message;
|
||||
pub(crate) mod job;
|
||||
pub(crate) mod job_params;
|
||||
pub(crate) mod login_params;
|
||||
pub(crate) mod login_response;
|
||||
pub(crate) mod miner_message;
|
||||
pub(crate) mod rpc_error;
|
||||
pub(crate) mod rpc_request;
|
||||
pub(crate) mod rpc_response;
|
||||
pub(crate) mod submit_params;
|
||||
pub(crate) mod submit_response;
|
||||
pub(crate) mod worker_identifier;
|
||||
@@ -0,0 +1,28 @@
|
||||
// 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 serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct RpcError {
|
||||
pub code: i32,
|
||||
pub message: String,
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
// 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 serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct RpcRequest {
|
||||
pub id: Option<String>,
|
||||
pub jsonrpc: String,
|
||||
pub method: String,
|
||||
pub params: Option<Value>,
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
// 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 serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::stratum::stratum_types::rpc_error::RpcError;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct RpcResponse {
|
||||
pub id: String,
|
||||
pub result: Option<Value>,
|
||||
pub error: Option<RpcError>,
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
// 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 serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct SubmitParams {
|
||||
pub id: String,
|
||||
pub job_id: u64,
|
||||
pub nonce: u64,
|
||||
pub hash: String,
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
// 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 serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::stratum::stratum_types::rpc_error::RpcError;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct SubmitResponse {
|
||||
pub status: Option<String>,
|
||||
pub error: Option<RpcError>,
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
// 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 serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct WorkerIdentifier {
|
||||
pub id: String,
|
||||
}
|
||||
107
internal/gbt/tari/minotari_miner/src/stratum/stream.rs
Normal file
107
internal/gbt/tari/minotari_miner/src/stratum/stream.rs
Normal file
@@ -0,0 +1,107 @@
|
||||
// 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::{
|
||||
io::{self, BufRead, Read, Write},
|
||||
net::TcpStream,
|
||||
};
|
||||
|
||||
use bufstream::BufStream;
|
||||
use native_tls::{TlsConnector, TlsStream};
|
||||
|
||||
use crate::stratum::error::Error;
|
||||
|
||||
pub(crate) enum Stream {
|
||||
Stream(BufStream<TcpStream>),
|
||||
TlsStream(Box<BufStream<TlsStream<TcpStream>>>),
|
||||
}
|
||||
|
||||
impl Stream {
|
||||
pub fn try_connect(server_url: &str, tls: Option<bool>) -> Result<Self, Error> {
|
||||
let conn = TcpStream::connect(server_url)?;
|
||||
if let Some(true) = tls {
|
||||
let connector = TlsConnector::new()?;
|
||||
let url_port: Vec<&str> = server_url.split(':').collect();
|
||||
let split_url: Vec<&str> = url_port[0].split('.').collect();
|
||||
let base_host = format!("{}.{}", split_url[split_url.len() - 2], split_url[split_url.len() - 1]);
|
||||
let mut stream = connector.connect(&base_host, conn).map_err(Box::new)?;
|
||||
stream.get_mut().set_nonblocking(true)?;
|
||||
Ok(Self::TlsStream(Box::from(BufStream::new(stream))))
|
||||
} else {
|
||||
conn.set_nonblocking(true)?;
|
||||
Ok(Self::Stream(BufStream::new(conn)))
|
||||
}
|
||||
}
|
||||
|
||||
fn reader(&mut self) -> &mut dyn Read {
|
||||
match self {
|
||||
Self::TlsStream(tls_stream) => tls_stream,
|
||||
Self::Stream(stream) => stream,
|
||||
}
|
||||
}
|
||||
|
||||
fn writer(&mut self) -> &mut dyn Write {
|
||||
match self {
|
||||
Self::TlsStream(tls_stream) => tls_stream,
|
||||
Self::Stream(stream) => stream,
|
||||
}
|
||||
}
|
||||
|
||||
fn buf_reader(&mut self) -> &mut dyn BufRead {
|
||||
match self {
|
||||
Self::TlsStream(tls_stream) => tls_stream,
|
||||
Self::Stream(stream) => stream,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Write for Stream {
|
||||
fn write(&mut self, b: &[u8]) -> Result<usize, io::Error> {
|
||||
self.writer().write(b)
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> Result<(), io::Error> {
|
||||
self.writer().flush()
|
||||
}
|
||||
}
|
||||
impl Read for Stream {
|
||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||
self.reader().read(buf)
|
||||
}
|
||||
}
|
||||
|
||||
impl BufRead for Stream {
|
||||
fn fill_buf(&mut self) -> io::Result<&[u8]> {
|
||||
self.buf_reader().fill_buf()
|
||||
}
|
||||
|
||||
fn consume(&mut self, amt: usize) {
|
||||
self.buf_reader().consume(amt)
|
||||
}
|
||||
|
||||
fn read_until(&mut self, byte: u8, buf: &mut Vec<u8>) -> io::Result<usize> {
|
||||
self.buf_reader().read_until(byte, buf)
|
||||
}
|
||||
|
||||
fn read_line(&mut self, string: &mut String) -> io::Result<usize> {
|
||||
self.buf_reader().read_line(string)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
@echo off
|
||||
title Minotari Miner
|
||||
|
||||
rem Verify arguments
|
||||
if ["%base_path%"]==[""] (
|
||||
echo Problem with "base_path" environment variable: '%base_path%'
|
||||
pause
|
||||
exit /b 10101
|
||||
)
|
||||
if not exist "%base_path%" (
|
||||
echo Path as per "base_path" environment variable not found: '%base_path%'
|
||||
pause
|
||||
exit /b 10101
|
||||
)
|
||||
if ["%my_exe%"]==[""] (
|
||||
echo Problem with "my_exe" environment variable: '%my_exe%'
|
||||
pause
|
||||
exit /b 10101
|
||||
)
|
||||
|
||||
rem Find the miner executable
|
||||
if exist "%my_exe_path%\%my_exe%" (
|
||||
set miner=%my_exe_path%\%my_exe%
|
||||
echo.
|
||||
echo Using "%my_exe%" found in %my_exe_path%
|
||||
echo.
|
||||
) else (
|
||||
if exist "%base_path%\%my_exe%" (
|
||||
set miner=%base_path%\%my_exe%
|
||||
echo.
|
||||
echo Using "%my_exe%" found in base_path
|
||||
echo.
|
||||
) else (
|
||||
set FOUND=
|
||||
for %%X in (%my_exe%) do (set FOUND=%%~$PATH:X)
|
||||
if defined FOUND (
|
||||
set miner=%my_exe%
|
||||
echo.
|
||||
echo Using "%my_exe%" found in system path:
|
||||
where "%my_exe%"
|
||||
echo.
|
||||
) else (
|
||||
echo.
|
||||
echo Runtime "%my_exe%" not found in %my_exe_path%, base_path or the system path
|
||||
echo.
|
||||
pause
|
||||
exit /b 10101
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
echo.
|
||||
echo.
|
||||
|
||||
cd "%base_path%"
|
||||
"%miner%" --base-path "%base_path%"
|
||||
@@ -0,0 +1,28 @@
|
||||
@echo off
|
||||
|
||||
echo.
|
||||
echo Set up environment variables
|
||||
echo ----------------------------
|
||||
rem These are the miner executable and SQLite dynamic link library names
|
||||
set my_exe=minotari_miner.exe
|
||||
|
||||
rem The default location for the miner executable
|
||||
set my_exe_path=%~dp0
|
||||
if %my_exe_path:~-1%==\ set my_exe_path=%my_exe_path:~0,-1%
|
||||
echo my_exe_path = %my_exe_path%
|
||||
|
||||
rem The base folder where the database and log files will be located
|
||||
set base_path=%~dp0..
|
||||
echo base_path = %base_path%
|
||||
|
||||
echo.
|
||||
echo Run the miner
|
||||
echo ----------------------
|
||||
call "%my_exe_path%\source_miner_env.bat"
|
||||
|
||||
goto END:
|
||||
|
||||
|
||||
:END
|
||||
echo.
|
||||
pause
|
||||
Binary file not shown.
Reference in New Issue
Block a user