change(log): Log a cute message for blocks that were mined by Zebra (off by default) (#6098)
* Mark Zebra coinbase transactions with extra coinbase data * Log when we commit a block mined by Zebra to our state * Reduce logging instrumentation during block writes * Remove debug types in Zebra block log * Add network and commit to write task logs * Apply an allow-list before we log arbitrary user messages from blocks * Rate-limit Zebra mined block logging to once every 1000 blocks * Add mining configs for extra coinbase data and imitating zcashd, but don't use them yet * Check CoinbaseData size limit when building transparent transactions * Replace LIKE_ZCASHD constants with a config * Take extra coinbase data from the configured string * Update the zebrad configs in the tests with new config fields
This commit is contained in:
parent
a835270ff7
commit
ec43d63ed2
|
@ -15,6 +15,7 @@ impl Transaction {
|
||||||
network: Network,
|
network: Network,
|
||||||
height: Height,
|
height: Height,
|
||||||
outputs: impl IntoIterator<Item = (Amount<NonNegative>, transparent::Script)>,
|
outputs: impl IntoIterator<Item = (Amount<NonNegative>, transparent::Script)>,
|
||||||
|
extra_coinbase_data: Vec<u8>,
|
||||||
) -> Transaction {
|
) -> Transaction {
|
||||||
// # Consensus
|
// # Consensus
|
||||||
//
|
//
|
||||||
|
@ -36,13 +37,17 @@ impl Transaction {
|
||||||
//
|
//
|
||||||
// > A coinbase transaction script MUST have length in {2 .. 100} bytes.
|
// > A coinbase transaction script MUST have length in {2 .. 100} bytes.
|
||||||
//
|
//
|
||||||
// Zebra does not add any extra coinbase data.
|
// Zebra adds extra coinbase data if configured to do so.
|
||||||
//
|
//
|
||||||
// Since we're not using a lock time, any sequence number is valid here.
|
// Since we're not using a lock time, any sequence number is valid here.
|
||||||
// See `Transaction::lock_time()` for the relevant consensus rules.
|
// See `Transaction::lock_time()` for the relevant consensus rules.
|
||||||
//
|
//
|
||||||
// <https://zips.z.cash/protocol/protocol.pdf#txnconsensus>
|
// <https://zips.z.cash/protocol/protocol.pdf#txnconsensus>
|
||||||
let inputs = vec![transparent::Input::new_coinbase(height, None, None)];
|
let inputs = vec![transparent::Input::new_coinbase(
|
||||||
|
height,
|
||||||
|
Some(extra_coinbase_data),
|
||||||
|
None,
|
||||||
|
)];
|
||||||
|
|
||||||
// > The block subsidy is composed of a miner subsidy and a series of funding streams.
|
// > The block subsidy is composed of a miner subsidy and a series of funding streams.
|
||||||
//
|
//
|
||||||
|
@ -108,17 +113,23 @@ impl Transaction {
|
||||||
height: Height,
|
height: Height,
|
||||||
outputs: impl IntoIterator<Item = (Amount<NonNegative>, transparent::Script)>,
|
outputs: impl IntoIterator<Item = (Amount<NonNegative>, transparent::Script)>,
|
||||||
like_zcashd: bool,
|
like_zcashd: bool,
|
||||||
|
extra_coinbase_data: Vec<u8>,
|
||||||
) -> Transaction {
|
) -> Transaction {
|
||||||
// `zcashd` includes an extra byte after the coinbase height in the coinbase data,
|
|
||||||
// and a sequence number of u32::MAX.
|
|
||||||
let mut extra_data = None;
|
let mut extra_data = None;
|
||||||
let mut sequence = None;
|
let mut sequence = None;
|
||||||
|
|
||||||
|
// `zcashd` includes an extra byte after the coinbase height in the coinbase data,
|
||||||
|
// and a sequence number of u32::MAX.
|
||||||
if like_zcashd {
|
if like_zcashd {
|
||||||
extra_data = Some(vec![0x00]);
|
extra_data = Some(vec![0x00]);
|
||||||
sequence = Some(u32::MAX);
|
sequence = Some(u32::MAX);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Override like_zcashd if extra_coinbase_data was supplied
|
||||||
|
if !extra_coinbase_data.is_empty() {
|
||||||
|
extra_data = Some(extra_coinbase_data);
|
||||||
|
}
|
||||||
|
|
||||||
// # Consensus
|
// # Consensus
|
||||||
//
|
//
|
||||||
// See the other consensus rules above in new_v5_coinbase().
|
// See the other consensus rules above in new_v5_coinbase().
|
||||||
|
|
|
@ -1,5 +1,15 @@
|
||||||
//! Transparent-related (Bitcoin-inherited) functionality.
|
//! Transparent-related (Bitcoin-inherited) functionality.
|
||||||
|
|
||||||
|
use std::{collections::HashMap, fmt, iter};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
amount::{Amount, NonNegative},
|
||||||
|
block,
|
||||||
|
parameters::Network,
|
||||||
|
primitives::zcash_primitives,
|
||||||
|
transaction,
|
||||||
|
};
|
||||||
|
|
||||||
mod address;
|
mod address;
|
||||||
mod keys;
|
mod keys;
|
||||||
mod opcodes;
|
mod opcodes;
|
||||||
|
@ -9,7 +19,7 @@ mod utxo;
|
||||||
|
|
||||||
pub use address::Address;
|
pub use address::Address;
|
||||||
pub use script::Script;
|
pub use script::Script;
|
||||||
pub use serialize::GENESIS_COINBASE_DATA;
|
pub use serialize::{GENESIS_COINBASE_DATA, MAX_COINBASE_DATA_LEN, MAX_COINBASE_HEIGHT_DATA_LEN};
|
||||||
pub use utxo::{
|
pub use utxo::{
|
||||||
new_ordered_outputs, new_outputs, outputs_from_utxos, utxos_from_ordered_utxos,
|
new_ordered_outputs, new_outputs, outputs_from_utxos, utxos_from_ordered_utxos,
|
||||||
CoinbaseSpendRestriction, OrderedUtxo, Utxo,
|
CoinbaseSpendRestriction, OrderedUtxo, Utxo,
|
||||||
|
@ -20,24 +30,14 @@ pub use utxo::{
|
||||||
new_ordered_outputs_with_height, new_outputs_with_height, new_transaction_ordered_outputs,
|
new_ordered_outputs_with_height, new_outputs_with_height, new_transaction_ordered_outputs,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(any(test, feature = "proptest-impl"))]
|
|
||||||
use proptest_derive::Arbitrary;
|
|
||||||
|
|
||||||
#[cfg(any(test, feature = "proptest-impl"))]
|
#[cfg(any(test, feature = "proptest-impl"))]
|
||||||
mod arbitrary;
|
mod arbitrary;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests;
|
mod tests;
|
||||||
|
|
||||||
use crate::{
|
#[cfg(any(test, feature = "proptest-impl"))]
|
||||||
amount::{Amount, NonNegative},
|
use proptest_derive::Arbitrary;
|
||||||
block,
|
|
||||||
parameters::Network,
|
|
||||||
primitives::zcash_primitives,
|
|
||||||
transaction,
|
|
||||||
};
|
|
||||||
|
|
||||||
use std::{collections::HashMap, fmt, iter};
|
|
||||||
|
|
||||||
/// The maturity threshold for transparent coinbase outputs.
|
/// The maturity threshold for transparent coinbase outputs.
|
||||||
///
|
///
|
||||||
|
@ -48,16 +48,28 @@ use std::{collections::HashMap, fmt, iter};
|
||||||
/// [7.1](https://zips.z.cash/protocol/nu5.pdf#txnencodingandconsensus)
|
/// [7.1](https://zips.z.cash/protocol/nu5.pdf#txnencodingandconsensus)
|
||||||
pub const MIN_TRANSPARENT_COINBASE_MATURITY: u32 = 100;
|
pub const MIN_TRANSPARENT_COINBASE_MATURITY: u32 = 100;
|
||||||
|
|
||||||
|
/// Extra coinbase data that identifies some coinbase transactions generated by Zebra.
|
||||||
|
/// <https://emojipedia.org/zebra/>
|
||||||
|
//
|
||||||
|
// # Note
|
||||||
|
//
|
||||||
|
// rust-analyzer will crash in some editors when moving over an actual Zebra emoji,
|
||||||
|
// so we encode it here. This is a known issue in emacs-lsp and other lsp implementations:
|
||||||
|
// - https://github.com/rust-lang/rust-analyzer/issues/9121
|
||||||
|
// - https://github.com/emacs-lsp/lsp-mode/issues/2080
|
||||||
|
// - https://github.com/rust-lang/rust-analyzer/issues/13709
|
||||||
|
pub const EXTRA_ZEBRA_COINBASE_DATA: &str = "z\u{1F993}";
|
||||||
|
|
||||||
/// Arbitrary data inserted by miners into a coinbase transaction.
|
/// Arbitrary data inserted by miners into a coinbase transaction.
|
||||||
|
//
|
||||||
|
// TODO: rename to ExtraCoinbaseData, because height is also part of the coinbase data?
|
||||||
#[derive(Clone, Eq, PartialEq)]
|
#[derive(Clone, Eq, PartialEq)]
|
||||||
#[cfg_attr(any(test, feature = "proptest-impl"), derive(Serialize))]
|
#[cfg_attr(any(test, feature = "proptest-impl"), derive(Serialize))]
|
||||||
pub struct CoinbaseData(
|
pub struct CoinbaseData(
|
||||||
/// Invariant: this vec, together with the coinbase height, must be less than
|
/// Invariant: this vec, together with the coinbase height, must be less than
|
||||||
/// 100 bytes. We enforce this by only constructing CoinbaseData fields by
|
/// 100 bytes. We enforce this by only constructing CoinbaseData fields by
|
||||||
/// parsing blocks with 100-byte data fields. When we implement block
|
/// parsing blocks with 100-byte data fields, and checking newly created
|
||||||
/// creation, we should provide a constructor for the coinbase data field
|
/// CoinbaseData lengths in the transaction builder.
|
||||||
/// that restricts it to 95 = 100 -1 -4 bytes (safe for any block height up
|
|
||||||
/// to 500_000_000).
|
|
||||||
pub(super) Vec<u8>,
|
pub(super) Vec<u8>,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -182,17 +194,39 @@ impl fmt::Display for Input {
|
||||||
|
|
||||||
impl Input {
|
impl Input {
|
||||||
/// Returns a new coinbase input for `height` with optional `data` and `sequence`.
|
/// Returns a new coinbase input for `height` with optional `data` and `sequence`.
|
||||||
|
///
|
||||||
|
/// # Consensus
|
||||||
|
///
|
||||||
|
/// The combined serialized size of `height` and `data` can be at most 100 bytes.
|
||||||
|
///
|
||||||
|
/// > A coinbase transaction script MUST have length in {2 .. 100} bytes.
|
||||||
|
///
|
||||||
|
/// <https://zips.z.cash/protocol/protocol.pdf#txnconsensus>
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// If the coinbase data is greater than [`MAX_COINBASE_DATA_LEN`].
|
||||||
#[cfg(feature = "getblocktemplate-rpcs")]
|
#[cfg(feature = "getblocktemplate-rpcs")]
|
||||||
pub fn new_coinbase(
|
pub fn new_coinbase(
|
||||||
height: block::Height,
|
height: block::Height,
|
||||||
data: Option<Vec<u8>>,
|
data: Option<Vec<u8>>,
|
||||||
sequence: Option<u32>,
|
sequence: Option<u32>,
|
||||||
) -> Input {
|
) -> Input {
|
||||||
|
// "No extra coinbase data" is the default.
|
||||||
|
let data = data.unwrap_or_default();
|
||||||
|
let height_size = height.coinbase_zcash_serialized_size();
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
data.len() + height_size <= MAX_COINBASE_DATA_LEN,
|
||||||
|
"invalid coinbase data: extra data {} bytes + height {height_size} bytes \
|
||||||
|
must be {} or less",
|
||||||
|
data.len(),
|
||||||
|
MAX_COINBASE_DATA_LEN,
|
||||||
|
);
|
||||||
|
|
||||||
Input::Coinbase {
|
Input::Coinbase {
|
||||||
height,
|
height,
|
||||||
|
data: CoinbaseData(data),
|
||||||
// "No extra coinbase data" is the default.
|
|
||||||
data: CoinbaseData(data.unwrap_or_default()),
|
|
||||||
|
|
||||||
// If the caller does not specify the sequence number,
|
// If the caller does not specify the sequence number,
|
||||||
// use a sequence number that activates the LockTime.
|
// use a sequence number that activates the LockTime.
|
||||||
|
@ -200,6 +234,14 @@ impl Input {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the extra coinbase data in this input, if it is an [`Input::Coinbase`].
|
||||||
|
pub fn extra_coinbase_data(&self) -> Option<&CoinbaseData> {
|
||||||
|
match self {
|
||||||
|
Input::PrevOut { .. } => None,
|
||||||
|
Input::Coinbase { data, .. } => Some(data),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the input's sequence number.
|
/// Returns the input's sequence number.
|
||||||
pub fn sequence(&self) -> u32 {
|
pub fn sequence(&self) -> u32 {
|
||||||
match self {
|
match self {
|
||||||
|
|
|
@ -5,9 +5,9 @@ use std::io;
|
||||||
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
|
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
block,
|
block::{self, Height},
|
||||||
serialization::{
|
serialization::{
|
||||||
zcash_serialize_bytes, ReadZcashExt, SerializationError, ZcashDeserialize,
|
zcash_serialize_bytes, FakeWriter, ReadZcashExt, SerializationError, ZcashDeserialize,
|
||||||
ZcashDeserializeInto, ZcashSerialize,
|
ZcashDeserializeInto, ZcashSerialize,
|
||||||
},
|
},
|
||||||
transaction,
|
transaction,
|
||||||
|
@ -26,6 +26,16 @@ use super::{CoinbaseData, Input, OutPoint, Output, Script};
|
||||||
/// <https://zips.z.cash/protocol/protocol.pdf#txnconsensus>
|
/// <https://zips.z.cash/protocol/protocol.pdf#txnconsensus>
|
||||||
pub const MAX_COINBASE_DATA_LEN: usize = 100;
|
pub const MAX_COINBASE_DATA_LEN: usize = 100;
|
||||||
|
|
||||||
|
/// The maximum length of the encoded coinbase height.
|
||||||
|
///
|
||||||
|
/// # Consensus
|
||||||
|
///
|
||||||
|
/// > The length of heightBytes MUST be in the range {1 .. 5}. Then the encoding is the length
|
||||||
|
/// > of heightBytes encoded as one byte, followed by heightBytes itself.
|
||||||
|
///
|
||||||
|
/// <https://zips.z.cash/protocol/protocol.pdf#txnconsensus>
|
||||||
|
pub const MAX_COINBASE_HEIGHT_DATA_LEN: usize = 6;
|
||||||
|
|
||||||
/// The minimum length of the coinbase data.
|
/// The minimum length of the coinbase data.
|
||||||
///
|
///
|
||||||
/// Includes the encoded coinbase height, if any.
|
/// Includes the encoded coinbase height, if any.
|
||||||
|
@ -100,7 +110,6 @@ impl ZcashDeserialize for OutPoint {
|
||||||
pub(crate) fn parse_coinbase_height(
|
pub(crate) fn parse_coinbase_height(
|
||||||
mut data: Vec<u8>,
|
mut data: Vec<u8>,
|
||||||
) -> Result<(block::Height, CoinbaseData), SerializationError> {
|
) -> Result<(block::Height, CoinbaseData), SerializationError> {
|
||||||
use block::Height;
|
|
||||||
match (data.first(), data.len()) {
|
match (data.first(), data.len()) {
|
||||||
// Blocks 1 through 16 inclusive encode block height with OP_N opcodes.
|
// Blocks 1 through 16 inclusive encode block height with OP_N opcodes.
|
||||||
(Some(op_n @ 0x51..=0x60), len) if len >= 1 => Ok((
|
(Some(op_n @ 0x51..=0x60), len) if len >= 1 => Ok((
|
||||||
|
@ -226,6 +235,17 @@ pub(crate) fn write_coinbase_height<W: io::Write>(
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Height {
|
||||||
|
/// Get the size of `Height` when serialized into a coinbase input script.
|
||||||
|
pub fn coinbase_zcash_serialized_size(&self) -> usize {
|
||||||
|
let mut writer = FakeWriter(0);
|
||||||
|
let empty_data = CoinbaseData(Vec::new());
|
||||||
|
|
||||||
|
write_coinbase_height(*self, &empty_data, &mut writer).expect("writer should never fail");
|
||||||
|
writer.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl ZcashSerialize for Input {
|
impl ZcashSerialize for Input {
|
||||||
/// Serialize this transparent input.
|
/// Serialize this transparent input.
|
||||||
///
|
///
|
||||||
|
|
|
@ -262,6 +262,10 @@ where
|
||||||
/// no matter what the estimated height or local clock is.
|
/// no matter what the estimated height or local clock is.
|
||||||
debug_force_finished_sync: bool,
|
debug_force_finished_sync: bool,
|
||||||
|
|
||||||
|
/// Test-only option that makes RPC responses more like `zcashd`.
|
||||||
|
#[allow(dead_code)]
|
||||||
|
debug_like_zcashd: bool,
|
||||||
|
|
||||||
// Services
|
// Services
|
||||||
//
|
//
|
||||||
/// A handle to the mempool service.
|
/// A handle to the mempool service.
|
||||||
|
@ -301,6 +305,7 @@ where
|
||||||
app_version: Version,
|
app_version: Version,
|
||||||
network: Network,
|
network: Network,
|
||||||
debug_force_finished_sync: bool,
|
debug_force_finished_sync: bool,
|
||||||
|
debug_like_zcashd: bool,
|
||||||
mempool: Buffer<Mempool, mempool::Request>,
|
mempool: Buffer<Mempool, mempool::Request>,
|
||||||
state: State,
|
state: State,
|
||||||
latest_chain_tip: Tip,
|
latest_chain_tip: Tip,
|
||||||
|
@ -323,6 +328,7 @@ where
|
||||||
app_version,
|
app_version,
|
||||||
network,
|
network,
|
||||||
debug_force_finished_sync,
|
debug_force_finished_sync,
|
||||||
|
debug_like_zcashd,
|
||||||
mempool: mempool.clone(),
|
mempool: mempool.clone(),
|
||||||
state: state.clone(),
|
state: state.clone(),
|
||||||
latest_chain_tip: latest_chain_tip.clone(),
|
latest_chain_tip: latest_chain_tip.clone(),
|
||||||
|
@ -763,14 +769,14 @@ where
|
||||||
use zebra_chain::block::MAX_BLOCK_BYTES;
|
use zebra_chain::block::MAX_BLOCK_BYTES;
|
||||||
|
|
||||||
#[cfg(feature = "getblocktemplate-rpcs")]
|
#[cfg(feature = "getblocktemplate-rpcs")]
|
||||||
/// Determines whether the output of this RPC is sorted like zcashd
|
// Determines whether the output of this RPC is sorted like zcashd
|
||||||
const SHOULD_USE_ZCASHD_ORDER: bool = true;
|
let should_use_zcashd_order = self.debug_like_zcashd;
|
||||||
|
|
||||||
let mut mempool = self.mempool.clone();
|
let mut mempool = self.mempool.clone();
|
||||||
|
|
||||||
async move {
|
async move {
|
||||||
#[cfg(feature = "getblocktemplate-rpcs")]
|
#[cfg(feature = "getblocktemplate-rpcs")]
|
||||||
let request = if SHOULD_USE_ZCASHD_ORDER {
|
let request = if should_use_zcashd_order {
|
||||||
mempool::Request::FullTransactions
|
mempool::Request::FullTransactions
|
||||||
} else {
|
} else {
|
||||||
mempool::Request::TransactionIds
|
mempool::Request::TransactionIds
|
||||||
|
|
|
@ -17,7 +17,9 @@ use zebra_chain::{
|
||||||
parameters::Network,
|
parameters::Network,
|
||||||
primitives,
|
primitives,
|
||||||
serialization::ZcashDeserializeInto,
|
serialization::ZcashDeserializeInto,
|
||||||
transparent,
|
transparent::{
|
||||||
|
self, EXTRA_ZEBRA_COINBASE_DATA, MAX_COINBASE_DATA_LEN, MAX_COINBASE_HEIGHT_DATA_LEN,
|
||||||
|
},
|
||||||
work::difficulty::{ExpandedDifficulty, U256},
|
work::difficulty::{ExpandedDifficulty, U256},
|
||||||
};
|
};
|
||||||
use zebra_consensus::{
|
use zebra_consensus::{
|
||||||
|
@ -246,6 +248,14 @@ where
|
||||||
/// Zebra currently only supports transparent addresses.
|
/// Zebra currently only supports transparent addresses.
|
||||||
miner_address: Option<transparent::Address>,
|
miner_address: Option<transparent::Address>,
|
||||||
|
|
||||||
|
/// Extra data to include in coinbase transaction inputs.
|
||||||
|
/// Limited to around 95 bytes by the consensus rules.
|
||||||
|
extra_coinbase_data: Vec<u8>,
|
||||||
|
|
||||||
|
/// Should Zebra's block templates try to imitate `zcashd`?
|
||||||
|
/// Developer-only config.
|
||||||
|
debug_like_zcashd: bool,
|
||||||
|
|
||||||
// Services
|
// Services
|
||||||
//
|
//
|
||||||
/// A handle to the mempool service.
|
/// A handle to the mempool service.
|
||||||
|
@ -293,6 +303,10 @@ where
|
||||||
AddressBook: AddressBookPeers + Clone + Send + Sync + 'static,
|
AddressBook: AddressBookPeers + Clone + Send + Sync + 'static,
|
||||||
{
|
{
|
||||||
/// Create a new instance of the handler for getblocktemplate RPCs.
|
/// Create a new instance of the handler for getblocktemplate RPCs.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// If the `mining_config` is invalid.
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub fn new(
|
pub fn new(
|
||||||
network: Network,
|
network: Network,
|
||||||
|
@ -304,9 +318,38 @@ where
|
||||||
sync_status: SyncStatus,
|
sync_status: SyncStatus,
|
||||||
address_book: AddressBook,
|
address_book: AddressBook,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
|
// A limit on the configured extra coinbase data, regardless of the current block height.
|
||||||
|
// This is different from the consensus rule, which limits the total height + data.
|
||||||
|
const EXTRA_COINBASE_DATA_LIMIT: usize =
|
||||||
|
MAX_COINBASE_DATA_LEN - MAX_COINBASE_HEIGHT_DATA_LEN;
|
||||||
|
|
||||||
|
let debug_like_zcashd = mining_config.debug_like_zcashd;
|
||||||
|
|
||||||
|
// Hex-decode to bytes if possible, otherwise UTF-8 encode to bytes.
|
||||||
|
let extra_coinbase_data = mining_config.extra_coinbase_data.unwrap_or_else(|| {
|
||||||
|
if debug_like_zcashd {
|
||||||
|
""
|
||||||
|
} else {
|
||||||
|
EXTRA_ZEBRA_COINBASE_DATA
|
||||||
|
}
|
||||||
|
.to_string()
|
||||||
|
});
|
||||||
|
let extra_coinbase_data = hex::decode(&extra_coinbase_data)
|
||||||
|
.unwrap_or_else(|_error| extra_coinbase_data.as_bytes().to_vec());
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
extra_coinbase_data.len() <= EXTRA_COINBASE_DATA_LIMIT,
|
||||||
|
"extra coinbase data is {} bytes, but Zebra's limit is {}.\n\
|
||||||
|
Configure mining.extra_coinbase_data with a shorter string",
|
||||||
|
extra_coinbase_data.len(),
|
||||||
|
EXTRA_COINBASE_DATA_LIMIT,
|
||||||
|
);
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
network,
|
network,
|
||||||
miner_address: mining_config.miner_address,
|
miner_address: mining_config.miner_address,
|
||||||
|
extra_coinbase_data,
|
||||||
|
debug_like_zcashd,
|
||||||
mempool,
|
mempool,
|
||||||
state,
|
state,
|
||||||
latest_chain_tip,
|
latest_chain_tip,
|
||||||
|
@ -389,14 +432,11 @@ where
|
||||||
&self,
|
&self,
|
||||||
parameters: Option<get_block_template::JsonParameters>,
|
parameters: Option<get_block_template::JsonParameters>,
|
||||||
) -> BoxFuture<Result<get_block_template::Response>> {
|
) -> BoxFuture<Result<get_block_template::Response>> {
|
||||||
// Should we generate coinbase transactions that are exactly like zcashd's?
|
// Clone Configs
|
||||||
//
|
|
||||||
// This is useful for testing, but either way Zebra should obey the consensus rules.
|
|
||||||
const COINBASE_LIKE_ZCASHD: bool = true;
|
|
||||||
|
|
||||||
// Clone Config
|
|
||||||
let network = self.network;
|
let network = self.network;
|
||||||
let miner_address = self.miner_address;
|
let miner_address = self.miner_address;
|
||||||
|
let debug_like_zcashd = self.debug_like_zcashd;
|
||||||
|
let extra_coinbase_data = self.extra_coinbase_data.clone();
|
||||||
|
|
||||||
// Clone Services
|
// Clone Services
|
||||||
let mempool = self.mempool.clone();
|
let mempool = self.mempool.clone();
|
||||||
|
@ -634,14 +674,13 @@ where
|
||||||
);
|
);
|
||||||
|
|
||||||
// Randomly select some mempool transactions.
|
// Randomly select some mempool transactions.
|
||||||
//
|
|
||||||
// TODO: sort these transactions to match zcashd's order, to make testing easier.
|
|
||||||
let mempool_txs = zip317::select_mempool_transactions(
|
let mempool_txs = zip317::select_mempool_transactions(
|
||||||
network,
|
network,
|
||||||
next_block_height,
|
next_block_height,
|
||||||
miner_address,
|
miner_address,
|
||||||
mempool_txs,
|
mempool_txs,
|
||||||
COINBASE_LIKE_ZCASHD,
|
debug_like_zcashd,
|
||||||
|
extra_coinbase_data.clone(),
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
|
@ -662,7 +701,8 @@ where
|
||||||
server_long_poll_id,
|
server_long_poll_id,
|
||||||
mempool_txs,
|
mempool_txs,
|
||||||
submit_old,
|
submit_old,
|
||||||
COINBASE_LIKE_ZCASHD,
|
debug_like_zcashd,
|
||||||
|
extra_coinbase_data,
|
||||||
);
|
);
|
||||||
|
|
||||||
Ok(response.into())
|
Ok(response.into())
|
||||||
|
|
|
@ -5,7 +5,7 @@ use serde::{Deserialize, Serialize};
|
||||||
use zebra_chain::transparent;
|
use zebra_chain::transparent;
|
||||||
|
|
||||||
/// Mining configuration section.
|
/// Mining configuration section.
|
||||||
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
|
||||||
#[serde(deny_unknown_fields, default)]
|
#[serde(deny_unknown_fields, default)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
/// The address used for miner payouts.
|
/// The address used for miner payouts.
|
||||||
|
@ -14,4 +14,28 @@ pub struct Config {
|
||||||
/// Zebra sends mining fees and miner rewards to this address in the
|
/// Zebra sends mining fees and miner rewards to this address in the
|
||||||
/// `getblocktemplate` RPC coinbase transaction.
|
/// `getblocktemplate` RPC coinbase transaction.
|
||||||
pub miner_address: Option<transparent::Address>,
|
pub miner_address: Option<transparent::Address>,
|
||||||
|
|
||||||
|
/// Extra data to include in coinbase transaction inputs.
|
||||||
|
/// Limited to around 95 bytes by the consensus rules.
|
||||||
|
///
|
||||||
|
/// If this string is hex-encoded, it will be hex-decoded into bytes.
|
||||||
|
/// Otherwise, it will be UTF-8 encoded into bytes.
|
||||||
|
pub extra_coinbase_data: Option<String>,
|
||||||
|
|
||||||
|
/// Should Zebra's block templates try to imitate `zcashd`?
|
||||||
|
///
|
||||||
|
/// This developer-only config is not supported for general use.
|
||||||
|
pub debug_like_zcashd: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Config {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
miner_address: None,
|
||||||
|
// For now, act like `zcashd` as much as possible.
|
||||||
|
// TODO: do we want to default to v5 transactions and Zebra coinbase data?
|
||||||
|
extra_coinbase_data: None,
|
||||||
|
debug_like_zcashd: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -273,11 +273,18 @@ pub fn generate_coinbase_and_roots(
|
||||||
mempool_txs: &[VerifiedUnminedTx],
|
mempool_txs: &[VerifiedUnminedTx],
|
||||||
history_tree: Arc<zebra_chain::history_tree::HistoryTree>,
|
history_tree: Arc<zebra_chain::history_tree::HistoryTree>,
|
||||||
like_zcashd: bool,
|
like_zcashd: bool,
|
||||||
|
extra_coinbase_data: Vec<u8>,
|
||||||
) -> (TransactionTemplate<NegativeOrZero>, DefaultRoots) {
|
) -> (TransactionTemplate<NegativeOrZero>, DefaultRoots) {
|
||||||
// Generate the coinbase transaction
|
// Generate the coinbase transaction
|
||||||
let miner_fee = calculate_miner_fee(mempool_txs);
|
let miner_fee = calculate_miner_fee(mempool_txs);
|
||||||
let coinbase_txn =
|
let coinbase_txn = generate_coinbase_transaction(
|
||||||
generate_coinbase_transaction(network, height, miner_address, miner_fee, like_zcashd);
|
network,
|
||||||
|
height,
|
||||||
|
miner_address,
|
||||||
|
miner_fee,
|
||||||
|
like_zcashd,
|
||||||
|
extra_coinbase_data,
|
||||||
|
);
|
||||||
|
|
||||||
// Calculate block default roots
|
// Calculate block default roots
|
||||||
//
|
//
|
||||||
|
@ -301,13 +308,15 @@ pub fn generate_coinbase_transaction(
|
||||||
miner_address: transparent::Address,
|
miner_address: transparent::Address,
|
||||||
miner_fee: Amount<NonNegative>,
|
miner_fee: Amount<NonNegative>,
|
||||||
like_zcashd: bool,
|
like_zcashd: bool,
|
||||||
|
extra_coinbase_data: Vec<u8>,
|
||||||
) -> UnminedTx {
|
) -> UnminedTx {
|
||||||
let outputs = standard_coinbase_outputs(network, height, miner_address, miner_fee, like_zcashd);
|
let outputs = standard_coinbase_outputs(network, height, miner_address, miner_fee, like_zcashd);
|
||||||
|
|
||||||
if like_zcashd {
|
if like_zcashd {
|
||||||
Transaction::new_v4_coinbase(network, height, outputs, like_zcashd).into()
|
Transaction::new_v4_coinbase(network, height, outputs, like_zcashd, extra_coinbase_data)
|
||||||
|
.into()
|
||||||
} else {
|
} else {
|
||||||
Transaction::new_v5_coinbase(network, height, outputs).into()
|
Transaction::new_v5_coinbase(network, height, outputs, extra_coinbase_data).into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -182,6 +182,7 @@ impl GetBlockTemplate {
|
||||||
///
|
///
|
||||||
/// If `like_zcashd` is true, try to match the coinbase transactions generated by `zcashd`
|
/// If `like_zcashd` is true, try to match the coinbase transactions generated by `zcashd`
|
||||||
/// in the `getblocktemplate` RPC.
|
/// in the `getblocktemplate` RPC.
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub fn new(
|
pub fn new(
|
||||||
network: Network,
|
network: Network,
|
||||||
miner_address: transparent::Address,
|
miner_address: transparent::Address,
|
||||||
|
@ -190,6 +191,7 @@ impl GetBlockTemplate {
|
||||||
mempool_txs: Vec<VerifiedUnminedTx>,
|
mempool_txs: Vec<VerifiedUnminedTx>,
|
||||||
submit_old: Option<bool>,
|
submit_old: Option<bool>,
|
||||||
like_zcashd: bool,
|
like_zcashd: bool,
|
||||||
|
extra_coinbase_data: Vec<u8>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
// Calculate the next block height.
|
// Calculate the next block height.
|
||||||
let next_block_height =
|
let next_block_height =
|
||||||
|
@ -229,6 +231,7 @@ impl GetBlockTemplate {
|
||||||
&mempool_txs,
|
&mempool_txs,
|
||||||
chain_tip_and_local_time.history_tree.clone(),
|
chain_tip_and_local_time.history_tree.clone(),
|
||||||
like_zcashd,
|
like_zcashd,
|
||||||
|
extra_coinbase_data,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Convert difficulty
|
// Convert difficulty
|
||||||
|
|
|
@ -46,11 +46,17 @@ pub async fn select_mempool_transactions(
|
||||||
miner_address: transparent::Address,
|
miner_address: transparent::Address,
|
||||||
mempool_txs: Vec<VerifiedUnminedTx>,
|
mempool_txs: Vec<VerifiedUnminedTx>,
|
||||||
like_zcashd: bool,
|
like_zcashd: bool,
|
||||||
|
extra_coinbase_data: Vec<u8>,
|
||||||
) -> Vec<VerifiedUnminedTx> {
|
) -> Vec<VerifiedUnminedTx> {
|
||||||
// Use a fake coinbase transaction to break the dependency between transaction
|
// Use a fake coinbase transaction to break the dependency between transaction
|
||||||
// selection, the miner fee, and the fee payment in the coinbase transaction.
|
// selection, the miner fee, and the fee payment in the coinbase transaction.
|
||||||
let fake_coinbase_tx =
|
let fake_coinbase_tx = fake_coinbase_transaction(
|
||||||
fake_coinbase_transaction(network, next_block_height, miner_address, like_zcashd);
|
network,
|
||||||
|
next_block_height,
|
||||||
|
miner_address,
|
||||||
|
like_zcashd,
|
||||||
|
extra_coinbase_data,
|
||||||
|
);
|
||||||
|
|
||||||
// Setup the transaction lists.
|
// Setup the transaction lists.
|
||||||
let (mut conventional_fee_txs, mut low_fee_txs): (Vec<_>, Vec<_>) = mempool_txs
|
let (mut conventional_fee_txs, mut low_fee_txs): (Vec<_>, Vec<_>) = mempool_txs
|
||||||
|
@ -117,6 +123,7 @@ pub fn fake_coinbase_transaction(
|
||||||
height: Height,
|
height: Height,
|
||||||
miner_address: transparent::Address,
|
miner_address: transparent::Address,
|
||||||
like_zcashd: bool,
|
like_zcashd: bool,
|
||||||
|
extra_coinbase_data: Vec<u8>,
|
||||||
) -> TransactionTemplate<NegativeOrZero> {
|
) -> TransactionTemplate<NegativeOrZero> {
|
||||||
// Block heights are encoded as variable-length (script) and `u32` (lock time, expiry height).
|
// Block heights are encoded as variable-length (script) and `u32` (lock time, expiry height).
|
||||||
// They can also change the `u32` consensus branch id.
|
// They can also change the `u32` consensus branch id.
|
||||||
|
@ -129,8 +136,14 @@ pub fn fake_coinbase_transaction(
|
||||||
// https://developer.bitcoin.org/reference/transactions.html#txout-a-transaction-output
|
// https://developer.bitcoin.org/reference/transactions.html#txout-a-transaction-output
|
||||||
let miner_fee = 1.try_into().expect("amount is valid and non-negative");
|
let miner_fee = 1.try_into().expect("amount is valid and non-negative");
|
||||||
|
|
||||||
let coinbase_tx =
|
let coinbase_tx = generate_coinbase_transaction(
|
||||||
generate_coinbase_transaction(network, height, miner_address, miner_fee, like_zcashd);
|
network,
|
||||||
|
height,
|
||||||
|
miner_address,
|
||||||
|
miner_fee,
|
||||||
|
like_zcashd,
|
||||||
|
extra_coinbase_data,
|
||||||
|
);
|
||||||
|
|
||||||
TransactionTemplate::from_coinbase(&coinbase_tx, miner_fee)
|
TransactionTemplate::from_coinbase(&coinbase_tx, miner_fee)
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,6 +43,7 @@ proptest! {
|
||||||
"RPC test",
|
"RPC test",
|
||||||
Mainnet,
|
Mainnet,
|
||||||
false,
|
false,
|
||||||
|
true,
|
||||||
Buffer::new(mempool.clone(), 1),
|
Buffer::new(mempool.clone(), 1),
|
||||||
Buffer::new(state.clone(), 1),
|
Buffer::new(state.clone(), 1),
|
||||||
NoChainTip,
|
NoChainTip,
|
||||||
|
@ -96,6 +97,7 @@ proptest! {
|
||||||
"RPC test",
|
"RPC test",
|
||||||
Mainnet,
|
Mainnet,
|
||||||
false,
|
false,
|
||||||
|
true,
|
||||||
Buffer::new(mempool.clone(), 1),
|
Buffer::new(mempool.clone(), 1),
|
||||||
Buffer::new(state.clone(), 1),
|
Buffer::new(state.clone(), 1),
|
||||||
NoChainTip,
|
NoChainTip,
|
||||||
|
@ -154,6 +156,7 @@ proptest! {
|
||||||
"RPC test",
|
"RPC test",
|
||||||
Mainnet,
|
Mainnet,
|
||||||
false,
|
false,
|
||||||
|
true,
|
||||||
Buffer::new(mempool.clone(), 1),
|
Buffer::new(mempool.clone(), 1),
|
||||||
Buffer::new(state.clone(), 1),
|
Buffer::new(state.clone(), 1),
|
||||||
NoChainTip,
|
NoChainTip,
|
||||||
|
@ -220,6 +223,7 @@ proptest! {
|
||||||
"RPC test",
|
"RPC test",
|
||||||
Mainnet,
|
Mainnet,
|
||||||
false,
|
false,
|
||||||
|
true,
|
||||||
Buffer::new(mempool.clone(), 1),
|
Buffer::new(mempool.clone(), 1),
|
||||||
Buffer::new(state.clone(), 1),
|
Buffer::new(state.clone(), 1),
|
||||||
NoChainTip,
|
NoChainTip,
|
||||||
|
@ -275,6 +279,7 @@ proptest! {
|
||||||
"RPC test",
|
"RPC test",
|
||||||
Mainnet,
|
Mainnet,
|
||||||
false,
|
false,
|
||||||
|
true,
|
||||||
Buffer::new(mempool.clone(), 1),
|
Buffer::new(mempool.clone(), 1),
|
||||||
Buffer::new(state.clone(), 1),
|
Buffer::new(state.clone(), 1),
|
||||||
NoChainTip,
|
NoChainTip,
|
||||||
|
@ -328,6 +333,7 @@ proptest! {
|
||||||
"RPC test",
|
"RPC test",
|
||||||
Mainnet,
|
Mainnet,
|
||||||
false,
|
false,
|
||||||
|
true,
|
||||||
Buffer::new(mempool.clone(), 1),
|
Buffer::new(mempool.clone(), 1),
|
||||||
Buffer::new(state.clone(), 1),
|
Buffer::new(state.clone(), 1),
|
||||||
NoChainTip,
|
NoChainTip,
|
||||||
|
@ -424,6 +430,7 @@ proptest! {
|
||||||
"RPC test",
|
"RPC test",
|
||||||
Mainnet,
|
Mainnet,
|
||||||
false,
|
false,
|
||||||
|
true,
|
||||||
Buffer::new(mempool.clone(), 1),
|
Buffer::new(mempool.clone(), 1),
|
||||||
Buffer::new(state.clone(), 1),
|
Buffer::new(state.clone(), 1),
|
||||||
NoChainTip,
|
NoChainTip,
|
||||||
|
@ -481,6 +488,7 @@ proptest! {
|
||||||
"RPC test",
|
"RPC test",
|
||||||
Mainnet,
|
Mainnet,
|
||||||
false,
|
false,
|
||||||
|
true,
|
||||||
Buffer::new(mempool.clone(), 1),
|
Buffer::new(mempool.clone(), 1),
|
||||||
Buffer::new(state.clone(), 1),
|
Buffer::new(state.clone(), 1),
|
||||||
NoChainTip,
|
NoChainTip,
|
||||||
|
@ -527,6 +535,7 @@ proptest! {
|
||||||
"RPC test",
|
"RPC test",
|
||||||
network,
|
network,
|
||||||
false,
|
false,
|
||||||
|
true,
|
||||||
Buffer::new(mempool.clone(), 1),
|
Buffer::new(mempool.clone(), 1),
|
||||||
Buffer::new(state.clone(), 1),
|
Buffer::new(state.clone(), 1),
|
||||||
NoChainTip,
|
NoChainTip,
|
||||||
|
@ -576,6 +585,7 @@ proptest! {
|
||||||
"RPC test",
|
"RPC test",
|
||||||
network,
|
network,
|
||||||
false,
|
false,
|
||||||
|
true,
|
||||||
Buffer::new(mempool.clone(), 1),
|
Buffer::new(mempool.clone(), 1),
|
||||||
Buffer::new(state.clone(), 1),
|
Buffer::new(state.clone(), 1),
|
||||||
chain_tip,
|
chain_tip,
|
||||||
|
@ -661,6 +671,7 @@ proptest! {
|
||||||
"RPC test",
|
"RPC test",
|
||||||
network,
|
network,
|
||||||
false,
|
false,
|
||||||
|
true,
|
||||||
Buffer::new(mempool.clone(), 1),
|
Buffer::new(mempool.clone(), 1),
|
||||||
Buffer::new(state.clone(), 1),
|
Buffer::new(state.clone(), 1),
|
||||||
chain_tip,
|
chain_tip,
|
||||||
|
@ -723,6 +734,7 @@ proptest! {
|
||||||
"RPC test",
|
"RPC test",
|
||||||
network,
|
network,
|
||||||
false,
|
false,
|
||||||
|
true,
|
||||||
Buffer::new(mempool.clone(), 1),
|
Buffer::new(mempool.clone(), 1),
|
||||||
Buffer::new(state.clone(), 1),
|
Buffer::new(state.clone(), 1),
|
||||||
chain_tip,
|
chain_tip,
|
||||||
|
@ -773,6 +785,7 @@ proptest! {
|
||||||
"RPC test",
|
"RPC test",
|
||||||
Mainnet,
|
Mainnet,
|
||||||
false,
|
false,
|
||||||
|
true,
|
||||||
Buffer::new(mempool.clone(), 1),
|
Buffer::new(mempool.clone(), 1),
|
||||||
Buffer::new(state.clone(), 1),
|
Buffer::new(state.clone(), 1),
|
||||||
NoChainTip,
|
NoChainTip,
|
||||||
|
@ -861,6 +874,7 @@ proptest! {
|
||||||
"RPC test",
|
"RPC test",
|
||||||
Mainnet,
|
Mainnet,
|
||||||
false,
|
false,
|
||||||
|
true,
|
||||||
Buffer::new(mempool.clone(), 1),
|
Buffer::new(mempool.clone(), 1),
|
||||||
Buffer::new(state.clone(), 1),
|
Buffer::new(state.clone(), 1),
|
||||||
NoChainTip,
|
NoChainTip,
|
||||||
|
|
|
@ -70,6 +70,7 @@ async fn test_rpc_response_data_for_network(network: Network) {
|
||||||
"RPC test",
|
"RPC test",
|
||||||
network,
|
network,
|
||||||
false,
|
false,
|
||||||
|
true,
|
||||||
Buffer::new(mempool.clone(), 1),
|
Buffer::new(mempool.clone(), 1),
|
||||||
read_state,
|
read_state,
|
||||||
latest_chain_tip,
|
latest_chain_tip,
|
||||||
|
|
|
@ -98,6 +98,8 @@ pub async fn test_responses<State, ReadState>(
|
||||||
|
|
||||||
let mining_config = get_block_template_rpcs::config::Config {
|
let mining_config = get_block_template_rpcs::config::Config {
|
||||||
miner_address: Some(transparent::Address::from_script_hash(network, [0xad; 20])),
|
miner_address: Some(transparent::Address::from_script_hash(network, [0xad; 20])),
|
||||||
|
extra_coinbase_data: None,
|
||||||
|
debug_like_zcashd: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
// nu5 block height
|
// nu5 block height
|
||||||
|
|
|
@ -32,6 +32,7 @@ async fn rpc_getinfo() {
|
||||||
"RPC test",
|
"RPC test",
|
||||||
Mainnet,
|
Mainnet,
|
||||||
false,
|
false,
|
||||||
|
true,
|
||||||
Buffer::new(mempool.clone(), 1),
|
Buffer::new(mempool.clone(), 1),
|
||||||
Buffer::new(state.clone(), 1),
|
Buffer::new(state.clone(), 1),
|
||||||
NoChainTip,
|
NoChainTip,
|
||||||
|
@ -75,6 +76,7 @@ async fn rpc_getblock() {
|
||||||
"RPC test",
|
"RPC test",
|
||||||
Mainnet,
|
Mainnet,
|
||||||
false,
|
false,
|
||||||
|
true,
|
||||||
Buffer::new(mempool.clone(), 1),
|
Buffer::new(mempool.clone(), 1),
|
||||||
read_state,
|
read_state,
|
||||||
latest_chain_tip,
|
latest_chain_tip,
|
||||||
|
@ -225,6 +227,7 @@ async fn rpc_getblock_parse_error() {
|
||||||
"RPC test",
|
"RPC test",
|
||||||
Mainnet,
|
Mainnet,
|
||||||
false,
|
false,
|
||||||
|
true,
|
||||||
Buffer::new(mempool.clone(), 1),
|
Buffer::new(mempool.clone(), 1),
|
||||||
Buffer::new(state.clone(), 1),
|
Buffer::new(state.clone(), 1),
|
||||||
NoChainTip,
|
NoChainTip,
|
||||||
|
@ -266,6 +269,7 @@ async fn rpc_getblock_missing_error() {
|
||||||
"RPC test",
|
"RPC test",
|
||||||
Mainnet,
|
Mainnet,
|
||||||
false,
|
false,
|
||||||
|
true,
|
||||||
Buffer::new(mempool.clone(), 1),
|
Buffer::new(mempool.clone(), 1),
|
||||||
Buffer::new(state.clone(), 1),
|
Buffer::new(state.clone(), 1),
|
||||||
NoChainTip,
|
NoChainTip,
|
||||||
|
@ -333,6 +337,7 @@ async fn rpc_getbestblockhash() {
|
||||||
"RPC test",
|
"RPC test",
|
||||||
Mainnet,
|
Mainnet,
|
||||||
false,
|
false,
|
||||||
|
true,
|
||||||
Buffer::new(mempool.clone(), 1),
|
Buffer::new(mempool.clone(), 1),
|
||||||
read_state,
|
read_state,
|
||||||
latest_chain_tip,
|
latest_chain_tip,
|
||||||
|
@ -374,6 +379,7 @@ async fn rpc_getrawtransaction() {
|
||||||
"RPC test",
|
"RPC test",
|
||||||
Mainnet,
|
Mainnet,
|
||||||
false,
|
false,
|
||||||
|
true,
|
||||||
Buffer::new(mempool.clone(), 1),
|
Buffer::new(mempool.clone(), 1),
|
||||||
read_state,
|
read_state,
|
||||||
latest_chain_tip,
|
latest_chain_tip,
|
||||||
|
@ -461,6 +467,7 @@ async fn rpc_getaddresstxids_invalid_arguments() {
|
||||||
"RPC test",
|
"RPC test",
|
||||||
Mainnet,
|
Mainnet,
|
||||||
false,
|
false,
|
||||||
|
true,
|
||||||
Buffer::new(mempool.clone(), 1),
|
Buffer::new(mempool.clone(), 1),
|
||||||
Buffer::new(read_state.clone(), 1),
|
Buffer::new(read_state.clone(), 1),
|
||||||
latest_chain_tip,
|
latest_chain_tip,
|
||||||
|
@ -603,6 +610,7 @@ async fn rpc_getaddresstxids_response_with(
|
||||||
"RPC test",
|
"RPC test",
|
||||||
network,
|
network,
|
||||||
false,
|
false,
|
||||||
|
true,
|
||||||
Buffer::new(mempool.clone(), 1),
|
Buffer::new(mempool.clone(), 1),
|
||||||
Buffer::new(read_state.clone(), 1),
|
Buffer::new(read_state.clone(), 1),
|
||||||
latest_chain_tip,
|
latest_chain_tip,
|
||||||
|
@ -653,6 +661,7 @@ async fn rpc_getaddressutxos_invalid_arguments() {
|
||||||
"RPC test",
|
"RPC test",
|
||||||
Mainnet,
|
Mainnet,
|
||||||
false,
|
false,
|
||||||
|
true,
|
||||||
Buffer::new(mempool.clone(), 1),
|
Buffer::new(mempool.clone(), 1),
|
||||||
Buffer::new(state.clone(), 1),
|
Buffer::new(state.clone(), 1),
|
||||||
NoChainTip,
|
NoChainTip,
|
||||||
|
@ -700,6 +709,7 @@ async fn rpc_getaddressutxos_response() {
|
||||||
"RPC test",
|
"RPC test",
|
||||||
Mainnet,
|
Mainnet,
|
||||||
false,
|
false,
|
||||||
|
true,
|
||||||
Buffer::new(mempool.clone(), 1),
|
Buffer::new(mempool.clone(), 1),
|
||||||
Buffer::new(read_state.clone(), 1),
|
Buffer::new(read_state.clone(), 1),
|
||||||
latest_chain_tip,
|
latest_chain_tip,
|
||||||
|
@ -1116,7 +1126,11 @@ async fn rpc_getblocktemplate_mining_address(use_p2pkh: bool) {
|
||||||
true => Some(transparent::Address::from_pub_key_hash(Mainnet, [0x7e; 20])),
|
true => Some(transparent::Address::from_pub_key_hash(Mainnet, [0x7e; 20])),
|
||||||
};
|
};
|
||||||
|
|
||||||
let mining_config = Config { miner_address };
|
let mining_config = Config {
|
||||||
|
miner_address,
|
||||||
|
extra_coinbase_data: None,
|
||||||
|
debug_like_zcashd: true,
|
||||||
|
};
|
||||||
|
|
||||||
// nu5 block height
|
// nu5 block height
|
||||||
let fake_tip_height = NetworkUpgrade::Nu5.activation_height(Mainnet).unwrap();
|
let fake_tip_height = NetworkUpgrade::Nu5.activation_height(Mainnet).unwrap();
|
||||||
|
@ -1510,6 +1524,8 @@ async fn rpc_getdifficulty() {
|
||||||
|
|
||||||
let mining_config = Config {
|
let mining_config = Config {
|
||||||
miner_address: None,
|
miner_address: None,
|
||||||
|
extra_coinbase_data: None,
|
||||||
|
debug_like_zcashd: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
// nu5 block height
|
// nu5 block height
|
||||||
|
|
|
@ -145,7 +145,7 @@ impl RpcServer {
|
||||||
// Initialize the getblocktemplate rpc method handler
|
// Initialize the getblocktemplate rpc method handler
|
||||||
let get_block_template_rpc_impl = GetBlockTemplateRpcImpl::new(
|
let get_block_template_rpc_impl = GetBlockTemplateRpcImpl::new(
|
||||||
network,
|
network,
|
||||||
mining_config,
|
mining_config.clone(),
|
||||||
mempool.clone(),
|
mempool.clone(),
|
||||||
state.clone(),
|
state.clone(),
|
||||||
latest_chain_tip.clone(),
|
latest_chain_tip.clone(),
|
||||||
|
@ -162,6 +162,10 @@ impl RpcServer {
|
||||||
app_version.clone(),
|
app_version.clone(),
|
||||||
network,
|
network,
|
||||||
config.debug_force_finished_sync,
|
config.debug_force_finished_sync,
|
||||||
|
#[cfg(feature = "getblocktemplate-rpcs")]
|
||||||
|
mining_config.debug_like_zcashd,
|
||||||
|
#[cfg(not(feature = "getblocktemplate-rpcs"))]
|
||||||
|
true,
|
||||||
mempool,
|
mempool,
|
||||||
state,
|
state,
|
||||||
latest_chain_tip,
|
latest_chain_tip,
|
||||||
|
|
|
@ -338,16 +338,19 @@ impl StateService {
|
||||||
tokio::sync::mpsc::unbounded_channel();
|
tokio::sync::mpsc::unbounded_channel();
|
||||||
|
|
||||||
let finalized_state_for_writing = finalized_state.clone();
|
let finalized_state_for_writing = finalized_state.clone();
|
||||||
|
let span = Span::current();
|
||||||
let block_write_task = std::thread::spawn(move || {
|
let block_write_task = std::thread::spawn(move || {
|
||||||
write::write_blocks_from_channels(
|
span.in_scope(move || {
|
||||||
finalized_block_write_receiver,
|
write::write_blocks_from_channels(
|
||||||
non_finalized_block_write_receiver,
|
finalized_block_write_receiver,
|
||||||
finalized_state_for_writing,
|
non_finalized_block_write_receiver,
|
||||||
non_finalized_state,
|
finalized_state_for_writing,
|
||||||
invalid_block_reset_sender,
|
non_finalized_state,
|
||||||
chain_tip_sender,
|
invalid_block_reset_sender,
|
||||||
non_finalized_state_sender,
|
chain_tip_sender,
|
||||||
)
|
non_finalized_state_sender,
|
||||||
|
)
|
||||||
|
})
|
||||||
});
|
});
|
||||||
let block_write_task = Arc::new(block_write_task);
|
let block_write_task = Arc::new(block_write_task);
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,10 @@ use tokio::sync::{
|
||||||
watch,
|
watch,
|
||||||
};
|
};
|
||||||
|
|
||||||
use zebra_chain::block::{self, Height};
|
use zebra_chain::{
|
||||||
|
block::{self, Height},
|
||||||
|
transparent::EXTRA_ZEBRA_COINBASE_DATA,
|
||||||
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
constants::MAX_BLOCK_REORG_HEIGHT,
|
constants::MAX_BLOCK_REORG_HEIGHT,
|
||||||
|
@ -34,7 +37,15 @@ const PARENT_ERROR_MAP_LIMIT: usize = MAX_BLOCK_REORG_HEIGHT as usize * 2;
|
||||||
|
|
||||||
/// Run contextual validation on the prepared block and add it to the
|
/// Run contextual validation on the prepared block and add it to the
|
||||||
/// non-finalized state if it is contextually valid.
|
/// non-finalized state if it is contextually valid.
|
||||||
#[tracing::instrument(level = "debug", skip(prepared), fields(height = ?prepared.height, hash = %prepared.hash))]
|
#[tracing::instrument(
|
||||||
|
level = "debug",
|
||||||
|
skip(finalized_state, non_finalized_state, prepared),
|
||||||
|
fields(
|
||||||
|
height = ?prepared.height,
|
||||||
|
hash = %prepared.hash,
|
||||||
|
chains = non_finalized_state.chain_set.len()
|
||||||
|
)
|
||||||
|
)]
|
||||||
pub(crate) fn validate_and_commit_non_finalized(
|
pub(crate) fn validate_and_commit_non_finalized(
|
||||||
finalized_state: &ZebraDb,
|
finalized_state: &ZebraDb,
|
||||||
non_finalized_state: &mut NonFinalizedState,
|
non_finalized_state: &mut NonFinalizedState,
|
||||||
|
@ -56,16 +67,28 @@ pub(crate) fn validate_and_commit_non_finalized(
|
||||||
/// channels with the latest non-finalized [`ChainTipBlock`] and
|
/// channels with the latest non-finalized [`ChainTipBlock`] and
|
||||||
/// [`Chain`].
|
/// [`Chain`].
|
||||||
///
|
///
|
||||||
|
/// `last_zebra_mined_log_height` is used to rate-limit logging.
|
||||||
|
///
|
||||||
/// Returns the latest non-finalized chain tip height.
|
/// Returns the latest non-finalized chain tip height.
|
||||||
///
|
///
|
||||||
/// # Panics
|
/// # Panics
|
||||||
///
|
///
|
||||||
/// If the `non_finalized_state` is empty.
|
/// If the `non_finalized_state` is empty.
|
||||||
#[instrument(level = "debug", skip(chain_tip_sender, non_finalized_state_sender))]
|
#[instrument(
|
||||||
|
level = "debug",
|
||||||
|
skip(
|
||||||
|
non_finalized_state,
|
||||||
|
chain_tip_sender,
|
||||||
|
non_finalized_state_sender,
|
||||||
|
last_zebra_mined_log_height
|
||||||
|
),
|
||||||
|
fields(chains = non_finalized_state.chain_set.len())
|
||||||
|
)]
|
||||||
fn update_latest_chain_channels(
|
fn update_latest_chain_channels(
|
||||||
non_finalized_state: &NonFinalizedState,
|
non_finalized_state: &NonFinalizedState,
|
||||||
chain_tip_sender: &mut ChainTipSender,
|
chain_tip_sender: &mut ChainTipSender,
|
||||||
non_finalized_state_sender: &watch::Sender<NonFinalizedState>,
|
non_finalized_state_sender: &watch::Sender<NonFinalizedState>,
|
||||||
|
last_zebra_mined_log_height: &mut Option<Height>,
|
||||||
) -> block::Height {
|
) -> block::Height {
|
||||||
let best_chain = non_finalized_state.best_chain().expect("unexpected empty non-finalized state: must commit at least one block before updating channels");
|
let best_chain = non_finalized_state.best_chain().expect("unexpected empty non-finalized state: must commit at least one block before updating channels");
|
||||||
|
|
||||||
|
@ -75,6 +98,8 @@ fn update_latest_chain_channels(
|
||||||
.clone();
|
.clone();
|
||||||
let tip_block = ChainTipBlock::from(tip_block);
|
let tip_block = ChainTipBlock::from(tip_block);
|
||||||
|
|
||||||
|
log_if_mined_by_zebra(&tip_block, last_zebra_mined_log_height);
|
||||||
|
|
||||||
let tip_block_height = tip_block.height;
|
let tip_block_height = tip_block.height;
|
||||||
|
|
||||||
// If the final receiver was just dropped, ignore the error.
|
// If the final receiver was just dropped, ignore the error.
|
||||||
|
@ -90,13 +115,21 @@ fn update_latest_chain_channels(
|
||||||
/// `non_finalized_state_sender`.
|
/// `non_finalized_state_sender`.
|
||||||
// TODO: make the task an object
|
// TODO: make the task an object
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
#[instrument(skip(
|
#[instrument(
|
||||||
finalized_block_write_receiver,
|
level = "debug",
|
||||||
non_finalized_block_write_receiver,
|
skip(
|
||||||
invalid_block_reset_sender,
|
finalized_block_write_receiver,
|
||||||
chain_tip_sender,
|
non_finalized_block_write_receiver,
|
||||||
non_finalized_state_sender,
|
finalized_state,
|
||||||
))]
|
non_finalized_state,
|
||||||
|
invalid_block_reset_sender,
|
||||||
|
chain_tip_sender,
|
||||||
|
non_finalized_state_sender,
|
||||||
|
),
|
||||||
|
fields(
|
||||||
|
network = %non_finalized_state.network
|
||||||
|
)
|
||||||
|
)]
|
||||||
pub fn write_blocks_from_channels(
|
pub fn write_blocks_from_channels(
|
||||||
mut finalized_block_write_receiver: UnboundedReceiver<QueuedFinalized>,
|
mut finalized_block_write_receiver: UnboundedReceiver<QueuedFinalized>,
|
||||||
mut non_finalized_block_write_receiver: UnboundedReceiver<QueuedNonFinalized>,
|
mut non_finalized_block_write_receiver: UnboundedReceiver<QueuedNonFinalized>,
|
||||||
|
@ -106,6 +139,8 @@ pub fn write_blocks_from_channels(
|
||||||
mut chain_tip_sender: ChainTipSender,
|
mut chain_tip_sender: ChainTipSender,
|
||||||
non_finalized_state_sender: watch::Sender<NonFinalizedState>,
|
non_finalized_state_sender: watch::Sender<NonFinalizedState>,
|
||||||
) {
|
) {
|
||||||
|
let mut last_zebra_mined_log_height = None;
|
||||||
|
|
||||||
// Write all the finalized blocks sent by the state,
|
// Write all the finalized blocks sent by the state,
|
||||||
// until the state closes the finalized block channel's sender.
|
// until the state closes the finalized block channel's sender.
|
||||||
while let Some(ordered_block) = finalized_block_write_receiver.blocking_recv() {
|
while let Some(ordered_block) = finalized_block_write_receiver.blocking_recv() {
|
||||||
|
@ -147,6 +182,8 @@ pub fn write_blocks_from_channels(
|
||||||
Ok(finalized) => {
|
Ok(finalized) => {
|
||||||
let tip_block = ChainTipBlock::from(finalized);
|
let tip_block = ChainTipBlock::from(finalized);
|
||||||
|
|
||||||
|
log_if_mined_by_zebra(&tip_block, &mut last_zebra_mined_log_height);
|
||||||
|
|
||||||
chain_tip_sender.set_finalized_tip(tip_block);
|
chain_tip_sender.set_finalized_tip(tip_block);
|
||||||
}
|
}
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
|
@ -243,6 +280,7 @@ pub fn write_blocks_from_channels(
|
||||||
&non_finalized_state,
|
&non_finalized_state,
|
||||||
&mut chain_tip_sender,
|
&mut chain_tip_sender,
|
||||||
&non_finalized_state_sender,
|
&non_finalized_state_sender,
|
||||||
|
&mut last_zebra_mined_log_height,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Update the caller with the result.
|
// Update the caller with the result.
|
||||||
|
@ -285,3 +323,72 @@ pub fn write_blocks_from_channels(
|
||||||
finalized_state.db.shutdown(true);
|
finalized_state.db.shutdown(true);
|
||||||
std::mem::drop(finalized_state);
|
std::mem::drop(finalized_state);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Log a message if this block was mined by Zebra.
|
||||||
|
///
|
||||||
|
/// Does not detect early Zebra blocks, and blocks with custom coinbase transactions.
|
||||||
|
/// Rate-limited to every 1000 blocks using `last_zebra_mined_log_height`.
|
||||||
|
fn log_if_mined_by_zebra(
|
||||||
|
tip_block: &ChainTipBlock,
|
||||||
|
last_zebra_mined_log_height: &mut Option<Height>,
|
||||||
|
) {
|
||||||
|
// This logs at most every 2-3 checkpoints, which seems fine.
|
||||||
|
const LOG_RATE_LIMIT: u32 = 1000;
|
||||||
|
|
||||||
|
let height = tip_block.height.0;
|
||||||
|
|
||||||
|
if let Some(last_height) = last_zebra_mined_log_height {
|
||||||
|
if height < last_height.0 + LOG_RATE_LIMIT {
|
||||||
|
// If we logged in the last 1000 blocks, don't log anything now.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// This code is rate-limited, so we can do expensive transformations here.
|
||||||
|
let coinbase_data = tip_block.transactions[0].inputs()[0]
|
||||||
|
.extra_coinbase_data()
|
||||||
|
.expect("valid blocks must start with a coinbase input")
|
||||||
|
.clone();
|
||||||
|
|
||||||
|
if coinbase_data
|
||||||
|
.as_ref()
|
||||||
|
.starts_with(EXTRA_ZEBRA_COINBASE_DATA.as_bytes())
|
||||||
|
{
|
||||||
|
let text = String::from_utf8_lossy(coinbase_data.as_ref());
|
||||||
|
|
||||||
|
*last_zebra_mined_log_height = Some(Height(height));
|
||||||
|
|
||||||
|
// No need for hex-encoded data if it's exactly what we expected.
|
||||||
|
if coinbase_data.as_ref() == EXTRA_ZEBRA_COINBASE_DATA.as_bytes() {
|
||||||
|
info!(
|
||||||
|
%text,
|
||||||
|
%height,
|
||||||
|
hash = %tip_block.hash,
|
||||||
|
"looks like this block was mined by Zebra!"
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// # Security
|
||||||
|
//
|
||||||
|
// Use the extra data as an allow-list, replacing unknown characters.
|
||||||
|
// This makes sure control characters and harmful messages don't get logged
|
||||||
|
// to the terminal.
|
||||||
|
let text = text.replace(
|
||||||
|
|c: char| {
|
||||||
|
!EXTRA_ZEBRA_COINBASE_DATA
|
||||||
|
.to_ascii_lowercase()
|
||||||
|
.contains(c.to_ascii_lowercase())
|
||||||
|
},
|
||||||
|
"?",
|
||||||
|
);
|
||||||
|
let data = hex::encode(coinbase_data.as_ref());
|
||||||
|
|
||||||
|
info!(
|
||||||
|
%text,
|
||||||
|
%data,
|
||||||
|
%height,
|
||||||
|
hash = %tip_block.hash,
|
||||||
|
"looks like this block was mined by Zebra!"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,73 @@
|
||||||
|
# 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://doc.zebra.zfnd.org/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.
|
||||||
|
|
||||||
|
[consensus]
|
||||||
|
checkpoint_sync = true
|
||||||
|
debug_skip_parameter_preload = false
|
||||||
|
|
||||||
|
[mempool]
|
||||||
|
eviction_memory_time = "1h"
|
||||||
|
tx_cost_limit = 80000000
|
||||||
|
|
||||||
|
[metrics]
|
||||||
|
|
||||||
|
[mining]
|
||||||
|
debug_like_zcashd = true
|
||||||
|
|
||||||
|
[network]
|
||||||
|
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"
|
||||||
|
network = "Mainnet"
|
||||||
|
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…
Reference in New Issue