Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add(mining): Restore internal miner #8906

Draft
wants to merge 10 commits into
base: main
Choose a base branch
from
1,379 changes: 725 additions & 654 deletions Cargo.lock

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ members = [
"zebra-node-services",
"zebra-test",
"zebra-utils",
"zebra-scan",
"zebra-grpc",
# "zebra-scan",
# "zebra-grpc",
"tower-batch-control",
"tower-fallback",
]
Expand Down
2 changes: 1 addition & 1 deletion docker/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
#
# Keep these argument defaults in sync with GitHub vars.RUST_PROD_FEATURES and vars.RUST_TEST_FEATURES
# https://github.com/ZcashFoundation/zebra/settings/variables/actions
ARG FEATURES="default-release-binaries"
ARG FEATURES="default-release-binaries internal-miner"
ARG TEST_FEATURES="lightwalletd-grpc-tests zebra-checkpoints"
ARG EXPERIMENTAL_FEATURES=""

Expand Down
9 changes: 7 additions & 2 deletions docker/entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -144,10 +144,15 @@ EOF
fi
fi

if [[ -n "${MINER_ADDRESS}" ]]; then
if [[ " ${FEATURES} " =~ " internal-miner " ]]; then
cat <<EOF >> "${ZEBRA_CONF_PATH}"
[mempool]
debug_enable_at_height = 0

[mining]
miner_address = "${MINER_ADDRESS}"
miner_address = "${MINER_ADDRESS:='t27eWDgjFYJGVXmzrXeVjnb5J3uXDM9xH9v'}"
internal_miner = true

EOF
fi
fi
Expand Down
Empty file added private-testnet.out
Empty file.
2 changes: 1 addition & 1 deletion zebra-chain/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ rand = { version = "0.8.5", optional = true }
rand_chacha = { version = "0.3.1", optional = true }

zebra-test = { path = "../zebra-test/", version = "1.0.0-beta.39", optional = true }

equihash-solver = { version = "0.2.0", git = "https://github.com/ZcashFoundation/librustzcash.git", branch = "equihash-solver-tromp", features = ["solver"], package = "equihash" }
[dev-dependencies]
# Benchmarks
criterion = { version = "0.5.1", features = ["html_reports"] }
Expand Down
87 changes: 81 additions & 6 deletions zebra-chain/src/work/equihash.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,16 +133,91 @@ impl Solution {
#[allow(clippy::unwrap_in_result)]
pub fn solve<F>(
mut header: Header,
mut _cancel_fn: F,
mut cancel_fn: F,
) -> Result<AtLeastOne<Header>, SolverCancelled>
where
F: FnMut() -> Result<(), SolverCancelled>,
{
// TODO: Function code was removed as part of https://github.com/ZcashFoundation/zebra/issues/8180
// Find the removed code at https://github.com/ZcashFoundation/zebra/blob/v1.5.1/zebra-chain/src/work/equihash.rs#L115-L166
// Restore the code when conditions are met. https://github.com/ZcashFoundation/zebra/issues/8183
header.solution = Solution::for_proposal();
Ok(AtLeastOne::from_one(header))
use crate::shutdown::is_shutting_down;

let mut input = Vec::new();
header
.zcash_serialize(&mut input)
.expect("serialization into a vec can't fail");
// Take the part of the header before the nonce and solution.
// This data is kept constant for this solver run.
let input = &input[0..Solution::INPUT_LENGTH];

while !is_shutting_down() {
// Don't run the solver if we'd just cancel it anyway.
cancel_fn()?;

let solutions = equihash_solver::tromp::solve_200_9_compressed(input, || {
// Cancel the solver if we have a new template.
if cancel_fn().is_err() {
return None;
}

// This skips the first nonce, which doesn't matter in practice.
Self::next_nonce(&mut header.nonce);
Some(*header.nonce)
});

let mut valid_solutions = Vec::new();

// If we got any solutions, try submitting them, because the new template might just
// contain some extra transactions. Mining extra transactions is optional.
for solution in &solutions {
header.solution = Self::from_bytes(solution)
.expect("unexpected invalid solution: incorrect length");

// TODO: work out why we sometimes get invalid solutions here
if let Err(error) = header.solution.check(&header) {
info!(?error, "found invalid solution for header");
continue;
}

if Self::difficulty_is_valid(&header) {
valid_solutions.push(header);
}
}

match valid_solutions.try_into() {
Ok(at_least_one_solution) => return Ok(at_least_one_solution),
Err(_is_empty_error) => debug!(
solutions = ?solutions.len(),
"found valid solutions which did not pass the validity or difficulty checks"
),
}
}

Err(SolverCancelled)
}

/// Returns `true` if the `nonce` and `solution` in `header` meet the difficulty threshold.
///
/// Assumes that the difficulty threshold in the header is valid.
#[cfg(feature = "internal-miner")]
fn difficulty_is_valid(header: &Header) -> bool {
// Simplified from zebra_consensus::block::check::difficulty_is_valid().
let difficulty_threshold = header
.difficulty_threshold
.to_expanded()
.expect("unexpected invalid header template: invalid difficulty threshold");

// TODO: avoid calculating this hash multiple times
let hash = header.hash();

// Note: this comparison is a u256 integer comparison, like zcashd and bitcoin. Greater
// values represent *less* work.
hash <= difficulty_threshold
}

/// Modifies `nonce` to be the next integer in big-endian order.
/// Wraps to zero if the next nonce would overflow.
#[cfg(feature = "internal-miner")]
fn next_nonce(nonce: &mut [u8; 32]) {
let _ignore_overflow = crate::primitives::byte_array::increment_big_endian(&mut nonce[..]);
}

// TODO: Some methods were removed as part of https://github.com/ZcashFoundation/zebra/issues/8180
Expand Down
2 changes: 1 addition & 1 deletion zebra-grpc/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
.protoc_arg("--experimental_allow_proto3_optional")
.type_attribute(".", "#[derive(serde::Deserialize, serde::Serialize)]")
.file_descriptor_set_path(out_dir.join("scanner_descriptor.bin"))
.compile(&["proto/scanner.proto"], &[""])?;
.compile_protos(&["proto/scanner.proto"], &[""])?;

Ok(())
}
2 changes: 1 addition & 1 deletion zebra-grpc/src/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -459,7 +459,7 @@ where
let service = ScannerRPC { scan_service };
let reflection_service = tonic_reflection::server::Builder::configure()
.register_encoded_file_descriptor_set(crate::scanner::FILE_DESCRIPTOR_SET)
.build()
.build_v1()
.unwrap();

let tcp_listener = tokio::net::TcpListener::bind(listen_addr).await?;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,9 @@ use zebra_consensus::{
use zebra_node_services::mempool;
use zebra_state::GetBlockTemplateChainInfo;

use crate::methods::{
errors::OkOrServerError,
get_block_template_rpcs::{
constants::{MAX_ESTIMATED_DISTANCE_TO_NETWORK_CHAIN_TIP, NOT_SYNCED_ERROR_CODE},
types::{default_roots::DefaultRoots, transaction::TransactionTemplate},
},
use crate::methods::get_block_template_rpcs::{
constants::NOT_SYNCED_ERROR_CODE,
types::{default_roots::DefaultRoots, transaction::TransactionTemplate},
};

pub use crate::methods::get_block_template_rpcs::types::get_block_template::*;
Expand Down Expand Up @@ -162,50 +159,29 @@ where
/// Returns early with `Ok(())` if Proof-of-Work is disabled on the provided `network`.
/// This error might be incorrect if the local clock is skewed.
pub fn check_synced_to_tip<Tip, SyncStatus>(
network: &Network,
_network: &Network,
latest_chain_tip: Tip,
sync_status: SyncStatus,
_sync_status: SyncStatus,
) -> Result<()>
where
Tip: ChainTip + Clone + Send + Sync + 'static,
SyncStatus: ChainSyncStatus + Clone + Send + Sync + 'static,
{
// TODO:
// - Add a `disable_peers` field to `Network` to check instead of `disable_pow()` (#8361)
// - Check the field in `sync_status` so it applies to the mempool as well.
if network.disable_pow() {
return Ok(());
}

// The tip estimate may not be the same as the one coming from the state
// but this is ok for an estimate
let (estimated_distance_to_chain_tip, local_tip_height) = latest_chain_tip
.estimate_distance_to_network_chain_tip(network)
.ok_or_server_error("no chain tip available yet")?;

if !sync_status.is_close_to_tip()
|| estimated_distance_to_chain_tip > MAX_ESTIMATED_DISTANCE_TO_NETWORK_CHAIN_TIP
{
tracing::info!(
?estimated_distance_to_chain_tip,
?local_tip_height,
"Zebra has not synced to the chain tip. \
Hint: check your network connection, clock, and time zone settings."
);

return Err(Error {
let local_tip_height = latest_chain_tip.best_tip_height();
if local_tip_height > Some(Height(3001875)) {
Ok(())
} else {
Err(Error {
code: NOT_SYNCED_ERROR_CODE,
message: format!(
"Zebra has not synced to the chain tip, \
estimated distance: {estimated_distance_to_chain_tip:?}, \
estimated network tip: 3001875, \
local tip: {local_tip_height:?}. \
Hint: check your network connection, clock, and time zone settings."
),
data: None,
});
})
}

Ok(())
}

// - State and mempool data fetches
Expand Down
6 changes: 5 additions & 1 deletion zebrad/src/components/miner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ where
}

/// Generates block templates using `rpc`, and sends them to mining threads using `template_sender`.
#[instrument(skip(rpc, template_sender))]
#[instrument(skip(rpc, template_sender, network))]
pub async fn generate_block_templates<
Mempool,
State,
Expand Down Expand Up @@ -264,6 +264,10 @@ where
while !template_sender.is_closed() && !is_shutting_down() {
let template: Result<_, _> = rpc.get_block_template(Some(parameters.clone())).await;

if template.is_err() {
info!(?template, "error getting block template")
}

// Wait for the chain to sync so we get a valid template.
let Ok(template) = template else {
info!(
Expand Down
84 changes: 84 additions & 0 deletions zebrad/tests/common/configs/v1.9.0-internal-miner.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# Default configuration for zebrad.
#
# This file can be used as a skeleton for custom configs.
#
# Unspecified fields use default values. Optional fields are Some(field) if the
# field is present and None if it is absent.
#
# This file is generated as an example using zebrad's current defaults.
# You should set only the config options you want to keep, and delete the rest.
# Only a subset of fields are present in the skeleton, since optional values
# whose default is None are omitted.
#
# The config format (including a complete list of sections and fields) is
# documented here:
# https://docs.rs/zebrad/latest/zebrad/config/struct.ZebradConfig.html
#
# zebrad attempts to load configs in the following order:
#
# 1. The -c flag on the command line, e.g., `zebrad -c myconfig.toml start`;
# 2. The file `zebrad.toml` in the users's preference directory (platform-dependent);
# 3. The default config.
#
# The user's preference directory and the default path to the `zebrad` config are platform dependent,
# based on `dirs::preference_dir`, see https://docs.rs/dirs/latest/dirs/fn.preference_dir.html :
#
# | Platform | Value | Example |
# | -------- | ------------------------------------- | ---------------------------------------------- |
# | Linux | `$XDG_CONFIG_HOME` or `$HOME/.config` | `/home/alice/.config/zebrad.toml` |
# | macOS | `$HOME/Library/Preferences` | `/Users/Alice/Library/Preferences/zebrad.toml` |
# | Windows | `{FOLDERID_RoamingAppData}` | `C:\Users\Alice\AppData\Local\zebrad.toml` |

[consensus]
checkpoint_sync = true

[mempool]
eviction_memory_time = "1h"
tx_cost_limit = 80000000
debug_enable_at_height = 0

[metrics]

[mining]
miner_address = 't27eWDgjFYJGVXmzrXeVjnb5J3uXDM9xH9v'
internal_miner = true

[network]
cache_dir = true
crawl_new_peer_interval = "1m 1s"
initial_mainnet_peers = [
"dnsseed.z.cash:8233",
"dnsseed.str4d.xyz:8233",
"mainnet.seeder.zfnd.org:8233",
"mainnet.is.yolo.money:8233",
]
initial_testnet_peers = [
"dnsseed.testnet.z.cash:18233",
"testnet.seeder.zfnd.org:18233",
"testnet.is.yolo.money:18233",
]
listen_addr = "0.0.0.0:8233"
max_connections_per_ip = 1
network = "Testnet"
peerset_initial_target_size = 25

[rpc]
debug_force_finished_sync = false
parallel_cpu_threads = 0

[state]
cache_dir = "cache_dir"
delete_old_database = true
ephemeral = false

[sync]
checkpoint_verify_concurrency_limit = 1000
download_concurrency_limit = 50
full_verify_concurrency_limit = 20
parallel_cpu_threads = 0

[tracing]
buffer_limit = 128000
force_use_color = false
use_color = true
use_journald = false
Loading