From 7e1367719704531658b7af073b1faa8fcc53d78d Mon Sep 17 00:00:00 2001 From: teor Date: Thu, 10 Nov 2022 10:12:27 +1000 Subject: [PATCH] change(rpc): generate coinbase transactions in the getblocktemplate RPC (#5580) * Add a getblocktemplate-rpcs feature to zebra-chain, and fix missing feature deps * Add a coinbase transaction creation stub * Add coinbase creation to zebra-chain * Add coinbase creation and miner subsidy to zebra-consensus * Add the miner config to the GetBlockTemplateRpcImpl * Generate the coinbase transaction in the getblocktemplate RPC * Provide fake valid block heights to getblocktemplate RPC tests * Update getblocktemplate RPC snapshots * Add a getblocktemplate.coinbase_tx deserialized transaction snapshot test * Update snapshots * Return funding stream outputs in the same order every time * Update snapshots * Fix a script bytes bug * Update snapshots --- zebra-chain/src/transaction.rs | 3 + zebra-chain/src/transaction/builder.rs | 86 ++++++++++++ zebra-chain/src/transparent.rs | 28 ++++ zebra-chain/src/transparent/serialize.rs | 13 +- zebra-consensus/src/block.rs | 2 +- .../src/block/subsidy/funding_streams.rs | 45 +++++-- zebra-consensus/src/block/subsidy/general.rs | 12 +- zebra-consensus/src/lib.rs | 6 +- .../src/methods/get_block_template_rpcs.rs | 122 ++++++++++-------- .../methods/get_block_template_rpcs/config.rs | 6 +- .../tests/snapshot/get_block_template_rpcs.rs | 41 +++++- .../snapshots/get_block_count@mainnet_10.snap | 2 +- .../snapshots/get_block_count@testnet_10.snap | 2 +- ...block_template.coinbase_tx@mainnet_10.snap | 42 ++++++ ...block_template.coinbase_tx@testnet_10.snap | 42 ++++++ .../get_block_template@mainnet_10.snap | 11 +- .../get_block_template@testnet_10.snap | 11 +- zebra-rpc/src/methods/tests/vectors.rs | 43 ++++-- zebra-rpc/src/server.rs | 13 +- zebra-rpc/src/server/tests/vectors.rs | 6 + zebrad/src/commands/start.rs | 4 + 21 files changed, 426 insertions(+), 114 deletions(-) create mode 100644 zebra-chain/src/transaction/builder.rs create mode 100644 zebra-rpc/src/methods/tests/snapshot/snapshots/get_block_template.coinbase_tx@mainnet_10.snap create mode 100644 zebra-rpc/src/methods/tests/snapshot/snapshots/get_block_template.coinbase_tx@testnet_10.snap diff --git a/zebra-chain/src/transaction.rs b/zebra-chain/src/transaction.rs index 48660a8e4..e2b02d278 100644 --- a/zebra-chain/src/transaction.rs +++ b/zebra-chain/src/transaction.rs @@ -14,6 +14,9 @@ mod sighash; mod txid; mod unmined; +#[cfg(feature = "getblocktemplate-rpcs")] +pub mod builder; + #[cfg(any(test, feature = "proptest-impl"))] #[allow(clippy::unwrap_in_result)] pub mod arbitrary; diff --git a/zebra-chain/src/transaction/builder.rs b/zebra-chain/src/transaction/builder.rs new file mode 100644 index 000000000..93b2ecbf8 --- /dev/null +++ b/zebra-chain/src/transaction/builder.rs @@ -0,0 +1,86 @@ +//! Methods for building transactions. + +use crate::{ + amount::{Amount, NonNegative}, + block::Height, + parameters::{Network, NetworkUpgrade}, + transaction::{LockTime, Transaction}, + transparent, +}; + +impl Transaction { + /// Returns a new version 5 coinbase transaction for `network` and `height`, + /// which contains the specified `outputs`. + pub fn new_v5_coinbase( + network: Network, + height: Height, + outputs: impl IntoIterator, transparent::Script)>, + ) -> Transaction { + // # Consensus + // + // These consensus rules apply to v5 coinbase transactions after NU5 activation: + // + // > A coinbase transaction for a block at block height greater than 0 MUST have + // > a script that, as its first item, encodes the block height height as follows. ... + // > let heightBytes be the signed little-endian representation of height, + // > using the minimum nonzero number of bytes such that the most significant byte + // > is < 0x80. 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. This matches the encoding used by Bitcoin + // > in the implementation of [BIP-34] + // > (but the description here is to be considered normative). + // + // > A coinbase transaction script MUST have length in {2 .. 100} bytes. + // + // Zebra does not add any extra coinbase data. + // + // Since we're not using a lock time, any sequence number is valid here. + // See `Transaction::lock_time()` for the relevant consensus rules. + // + // + let inputs = vec![transparent::Input::new_coinbase(height, None, None)]; + + // > The block subsidy is composed of a miner subsidy and a series of funding streams. + // + // + // + // > The total value in zatoshi of transparent outputs from a coinbase transaction, + // > minus vbalanceSapling, minus vbalanceOrchard, MUST NOT be greater than + // > the value in zatoshi of block subsidy plus the transaction fees + // > paid by transactions in this block. + // + // + let outputs = outputs + .into_iter() + .map(|(amount, lock_script)| transparent::Output::new_coinbase(amount, lock_script)) + .collect(); + + Transaction::V5 { + // > The transaction version number MUST be 4 or 5. ... + // > If the transaction version number is 5 then the version group ID MUST be 0x26A7270A. + // > If effectiveVersion ≥ 5, the nConsensusBranchId field MUST match the consensus + // > branch ID used for SIGHASH transaction hashes, as specified in [ZIP-244]. + network_upgrade: NetworkUpgrade::current(network, height), + + // There is no documented consensus rule for the lock time field in coinbase transactions, + // so we just leave it unlocked. + lock_time: LockTime::unlocked(), + + // > The nExpiryHeight field of a coinbase transaction MUST be equal to its block height. + expiry_height: height, + + inputs, + outputs, + + // Zebra does not support shielded coinbase yet. + // + // > In a version 5 coinbase transaction, the enableSpendsOrchard flag MUST be 0. + // > In a version 5 transaction, the reserved bits 2 .. 7 of the flagsOrchard field + // > MUST be zero. + // + // See the Zcash spec for additional shielded coinbase consensus rules. + sapling_shielded_data: None, + orchard_shielded_data: None, + } + } +} diff --git a/zebra-chain/src/transparent.rs b/zebra-chain/src/transparent.rs index 4d8d53fe4..5bb5a0e0d 100644 --- a/zebra-chain/src/transparent.rs +++ b/zebra-chain/src/transparent.rs @@ -180,6 +180,25 @@ impl fmt::Display for Input { } impl Input { + /// Returns a new coinbase input for `height` with optional `data` and `sequence`. + #[cfg(feature = "getblocktemplate-rpcs")] + pub fn new_coinbase( + height: block::Height, + data: Option>, + sequence: Option, + ) -> Input { + Input::Coinbase { + height, + + // "No extra coinbase data" is the default. + data: CoinbaseData(data.unwrap_or_default()), + + // If the caller does not specify the sequence number, + // use a sequence number that activates the LockTime. + sequence: sequence.unwrap_or(0), + } + } + /// Returns the input's sequence number. pub fn sequence(&self) -> u32 { match self { @@ -333,6 +352,15 @@ pub struct Output { } impl Output { + /// Returns a new coinbase output that pays `amount` using `lock_script`. + #[cfg(feature = "getblocktemplate-rpcs")] + pub fn new_coinbase(amount: Amount, lock_script: Script) -> Output { + Output { + value: amount, + lock_script, + } + } + /// Get the value contained in this output. /// This amount is subtracted from the transaction value pool by this output. pub fn value(&self) -> Amount { diff --git a/zebra-chain/src/transparent/serialize.rs b/zebra-chain/src/transparent/serialize.rs index f896c5062..9512f3c65 100644 --- a/zebra-chain/src/transparent/serialize.rs +++ b/zebra-chain/src/transparent/serialize.rs @@ -19,19 +19,22 @@ use super::{CoinbaseData, Input, OutPoint, Output, Script}; /// /// Includes the encoded coinbase height, if any. /// -/// > The number of bytes in the coinbase script, up to a maximum of 100 bytes. +/// # Consensus /// -/// +/// > A coinbase transaction script MUST have length in {2 .. 100} bytes. +/// +/// pub const MAX_COINBASE_DATA_LEN: usize = 100; /// The minimum length of the coinbase data. /// /// Includes the encoded coinbase height, if any. /// -// TODO: Update the link below once the constant is documented in the -// protocol. +/// # Consensus /// -/// +/// > A coinbase transaction script MUST have length in {2 .. 100} bytes. +/// +/// pub const MIN_COINBASE_DATA_LEN: usize = 2; /// The coinbase data for a genesis block. diff --git a/zebra-consensus/src/block.rs b/zebra-consensus/src/block.rs index 3788f7680..b33152c59 100644 --- a/zebra-consensus/src/block.rs +++ b/zebra-consensus/src/block.rs @@ -33,7 +33,7 @@ use zebra_state as zs; use crate::{error::*, transaction as tx, BoxError}; pub mod check; -mod subsidy; +pub mod subsidy; #[cfg(test)] mod tests; diff --git a/zebra-consensus/src/block/subsidy/funding_streams.rs b/zebra-consensus/src/block/subsidy/funding_streams.rs index 9801295be..ddac2f948 100644 --- a/zebra-consensus/src/block/subsidy/funding_streams.rs +++ b/zebra-consensus/src/block/subsidy/funding_streams.rs @@ -124,20 +124,43 @@ pub fn funding_stream_address( /// /// [7.10]: https://zips.z.cash/protocol/protocol.pdf#fundingstreams pub fn check_script_form(lock_script: &Script, address: Address) -> bool { - let mut address_hash = address + // TODO: Verify P2SH multisig funding stream addresses (#5577). + // As of NU5, the funding streams do not use multisig addresses, + // so this is optional. + // + // # Consensus + // + // > The standard redeem script hash is specified in [Bitcoin-Multisig] for P2SH multisig + // > addresses... + // [protocol specification §7.10][7.10] + // + // [7.10]: https://zips.z.cash/protocol/protocol.pdf#fundingstreams + // [Bitcoin-Multisig]: https://developer.bitcoin.org/ devguide/transactions.html#multisig + + // Verify a Bitcoin P2SH single address. + let standard_script_hash = new_coinbase_script(address); + + lock_script == &standard_script_hash +} + +/// Returns a new funding stream coinbase output lock script, which pays to `address`. +pub fn new_coinbase_script(address: Address) -> Script { + let address_hash = address .zcash_serialize_to_vec() .expect("we should get address bytes here"); - address_hash = address_hash[2..22].to_vec(); - address_hash.insert(0, OpCode::Push20Bytes as u8); - address_hash.insert(0, OpCode::Hash160 as u8); - address_hash.insert(address_hash.len(), OpCode::Equal as u8); - if lock_script.as_raw_bytes().len() == address_hash.len() - && *lock_script == Script::new(&address_hash) - { - return true; - } - false + // > The “prescribed way” to pay a transparent P2SH address is to use a standard P2SH script + // > of the form OP_HASH160 fs.RedeemScriptHash(height) OP_EQUAL as the scriptPubKey. + // + // [7.10]: https://zips.z.cash/protocol/protocol.pdf#fundingstreams + let mut script_bytes = Vec::new(); + + script_bytes.push(OpCode::Hash160 as u8); + script_bytes.push(OpCode::Push20Bytes as u8); + script_bytes.extend(&address_hash[2..22]); + script_bytes.push(OpCode::Equal as u8); + + Script::new(&script_bytes) } /// Returns a list of outputs in `Transaction`, which have a script address equal to `Address`. diff --git a/zebra-consensus/src/block/subsidy/general.rs b/zebra-consensus/src/block/subsidy/general.rs index 4d0a5c859..9b2e69dee 100644 --- a/zebra-consensus/src/block/subsidy/general.rs +++ b/zebra-consensus/src/block/subsidy/general.rs @@ -11,7 +11,7 @@ use zebra_chain::{ transaction::Transaction, }; -use crate::parameters::subsidy::*; +use crate::{funding_stream_values, parameters::subsidy::*}; /// The divisor used for halvings. /// @@ -68,6 +68,16 @@ pub fn block_subsidy(height: Height, network: Network) -> Result Result, Error> { + let total_funding_stream_amount: Result, _> = + funding_stream_values(height, network)?.values().sum(); + + block_subsidy(height, network)? - total_funding_stream_amount? +} + /// Returns all output amounts in `Transaction`. pub fn output_amounts(transaction: &Transaction) -> HashSet> { transaction diff --git a/zebra-consensus/src/lib.rs b/zebra-consensus/src/lib.rs index cb5d9480a..a10850b70 100644 --- a/zebra-consensus/src/lib.rs +++ b/zebra-consensus/src/lib.rs @@ -46,7 +46,11 @@ pub mod chain; #[allow(missing_docs)] pub mod error; -pub use block::{VerifyBlockError, MAX_BLOCK_SIGOPS}; +pub use block::{ + subsidy::funding_streams::funding_stream_address, + subsidy::funding_streams::funding_stream_values, subsidy::funding_streams::new_coinbase_script, + subsidy::general::miner_subsidy, VerifyBlockError, MAX_BLOCK_SIGOPS, +}; pub use chain::VerifyChainError; pub use checkpoint::{ CheckpointList, VerifyCheckpointError, MAX_CHECKPOINT_BYTE_COUNT, MAX_CHECKPOINT_HEIGHT_GAP, diff --git a/zebra-rpc/src/methods/get_block_template_rpcs.rs b/zebra-rpc/src/methods/get_block_template_rpcs.rs index 348fd9500..eda2cbc30 100644 --- a/zebra-rpc/src/methods/get_block_template_rpcs.rs +++ b/zebra-rpc/src/methods/get_block_template_rpcs.rs @@ -18,10 +18,12 @@ use zebra_chain::{ chain_tip::ChainTip, parameters::Network, serialization::ZcashDeserializeInto, - transaction::{UnminedTx, VerifiedUnminedTx}, + transaction::{Transaction, UnminedTx, VerifiedUnminedTx}, + transparent, }; use zebra_consensus::{ - BlockError, VerifyBlockError, VerifyChainError, VerifyCheckpointError, MAX_BLOCK_SIGOPS, + funding_stream_address, funding_stream_values, miner_subsidy, new_coinbase_script, BlockError, + VerifyBlockError, VerifyChainError, VerifyCheckpointError, MAX_BLOCK_SIGOPS, }; use zebra_node_services::mempool; @@ -134,10 +136,13 @@ where // Configuration // - // TODO: add mining config for getblocktemplate RPC miner address - // /// The configured network for this RPC service. - _network: Network, + network: Network, + + /// The configured miner address for this RPC service. + /// + /// Zebra currently only supports single-signature P2SH transparent addresses. + miner_address: Option, // Services // @@ -179,13 +184,15 @@ where /// Create a new instance of the handler for getblocktemplate RPCs. pub fn new( network: Network, + mining_config: config::Config, mempool: Buffer, state: State, latest_chain_tip: Tip, chain_verifier: ChainVerifier, ) -> Self { Self { - _network: network, + network, + miner_address: mining_config.miner_address, mempool, state, latest_chain_tip, @@ -258,66 +265,31 @@ where } fn get_block_template(&self) -> BoxFuture> { + let network = self.network; + let miner_address = self.miner_address; + let mempool = self.mempool.clone(); let latest_chain_tip = self.latest_chain_tip.clone(); // Since this is a very large RPC, we use separate functions for each group of fields. async move { - let _tip_height = best_chain_tip_height(&latest_chain_tip)?; + let miner_address = miner_address.ok_or_else(|| Error { + code: ErrorCode::ServerError(0), + message: "configure mining.miner_address in zebrad.toml \ + with a transparent P2SH single signature address" + .to_string(), + data: None, + })?; + + let tip_height = best_chain_tip_height(&latest_chain_tip)?; let mempool_txs = select_mempool_transactions(mempool).await?; let miner_fee = miner_fee(&mempool_txs); - /* - Fake a "coinbase" transaction by duplicating a mempool transaction, - or fake a response. - - This is temporarily required for the tests to pass. - - TODO: create a method Transaction::new_v5_coinbase(network, tip_height, miner_fee), - and call it here. - */ - let coinbase_tx = if mempool_txs.is_empty() { - let empty_string = String::from(""); - return Ok(GetBlockTemplate { - capabilities: vec![], - version: ZCASH_BLOCK_VERSION, - previous_block_hash: GetBlockHash([0; 32].into()), - block_commitments_hash: [0; 32].into(), - light_client_root_hash: [0; 32].into(), - final_sapling_root_hash: [0; 32].into(), - default_roots: DefaultRoots { - merkle_root: [0; 32].into(), - chain_history_root: [0; 32].into(), - auth_data_root: [0; 32].into(), - block_commitments_hash: [0; 32].into(), - }, - transactions: Vec::new(), - coinbase_txn: TransactionTemplate { - data: Vec::new().into(), - hash: [0; 32].into(), - auth_digest: [0; 32].into(), - depends: Vec::new(), - fee: Amount::zero(), - sigops: 0, - required: true, - }, - target: empty_string.clone(), - min_time: 0, - mutable: constants::GET_BLOCK_TEMPLATE_MUTABLE_FIELD - .iter() - .map(ToString::to_string) - .collect(), - nonce_range: constants::GET_BLOCK_TEMPLATE_NONCE_RANGE_FIELD.to_string(), - sigop_limit: MAX_BLOCK_SIGOPS, - size_limit: MAX_BLOCK_BYTES, - cur_time: 0, - bits: empty_string, - height: 0, - }); - } else { - mempool_txs[0].transaction.clone() - }; + let block_height = (tip_height + 1).expect("tip is far below Height::MAX"); + let outputs = + standard_coinbase_outputs(network, block_height, miner_address, miner_fee); + let coinbase_tx = Transaction::new_v5_coinbase(network, block_height, outputs).into(); let (merkle_root, auth_data_root) = calculate_transaction_roots(&coinbase_tx, &mempool_txs); @@ -490,6 +462,42 @@ pub fn miner_fee(mempool_txs: &[VerifiedUnminedTx]) -> Amount { ) } +/// Returns the standard funding stream and miner reward transparent output scripts +/// for `network`, `height` and `miner_fee`. +/// +/// Only works for post-Canopy heights. +pub fn standard_coinbase_outputs( + network: Network, + height: Height, + miner_address: transparent::Address, + miner_fee: Amount, +) -> Vec<(Amount, transparent::Script)> { + let funding_streams = funding_stream_values(height, network) + .expect("funding stream value calculations are valid for reasonable chain heights"); + + let mut funding_streams: Vec<(Amount, transparent::Address)> = funding_streams + .iter() + .map(|(receiver, amount)| (*amount, funding_stream_address(height, network, *receiver))) + .collect(); + // The HashMap returns funding streams in an arbitrary order, + // but Zebra's snapshot tests expect the same order every time. + funding_streams.sort_by_key(|(amount, _address)| *amount); + + let miner_reward = miner_subsidy(height, network) + .expect("reward calculations are valid for reasonable chain heights") + + miner_fee; + let miner_reward = + miner_reward.expect("reward calculations are valid for reasonable chain heights"); + + let mut coinbase_outputs = funding_streams; + coinbase_outputs.push((miner_reward, miner_address)); + + coinbase_outputs + .iter() + .map(|(amount, address)| (*amount, new_coinbase_script(*address))) + .collect() +} + /// Returns the transaction effecting and authorizing roots /// for `coinbase_tx` and `mempool_txs`. // diff --git a/zebra-rpc/src/methods/get_block_template_rpcs/config.rs b/zebra-rpc/src/methods/get_block_template_rpcs/config.rs index ae6e80915..91fb7a35a 100644 --- a/zebra-rpc/src/methods/get_block_template_rpcs/config.rs +++ b/zebra-rpc/src/methods/get_block_template_rpcs/config.rs @@ -2,16 +2,16 @@ use serde::{Deserialize, Serialize}; -use zebra_chain::transparent::Address; +use zebra_chain::transparent; /// Mining configuration section. #[derive(Clone, Debug, Default, Deserialize, Serialize)] #[serde(deny_unknown_fields, default)] pub struct Config { /// The address used for miner payouts. - /// Currently, Zebra only supports transparent addresses. + /// Zebra currently only supports single-signature P2SH transparent addresses. /// /// Zebra sends mining fees and miner rewards to this address in the /// `getblocktemplate` RPC coinbase transaction. - pub miner_address: Option
, + pub miner_address: Option, } diff --git a/zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs b/zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs index 6ddd6ccf5..a97557d60 100644 --- a/zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs +++ b/zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs @@ -8,15 +8,22 @@ use insta::Settings; use tower::{buffer::Buffer, Service}; -use zebra_chain::parameters::Network; +use zebra_chain::{ + chain_tip::mock::MockChainTip, + parameters::{Network, NetworkUpgrade}, + serialization::ZcashDeserializeInto, + transaction::Transaction, + transparent, +}; use zebra_node_services::mempool; use zebra_state::LatestChainTip; use zebra_test::mock_service::{MockService, PanicAssertion}; use crate::methods::{ - get_block_template_rpcs::types::{ - get_block_template::GetBlockTemplate, hex_data::HexData, submit_block, + get_block_template_rpcs::{ + self, + types::{get_block_template::GetBlockTemplate, hex_data::HexData, submit_block}, }, GetBlockHash, GetBlockTemplateRpc, GetBlockTemplateRpcImpl, }; @@ -31,7 +38,7 @@ pub async fn test_responses( >, state: State, read_state: ReadState, - latest_chain_tip: LatestChainTip, + _latest_chain_tip: LatestChainTip, settings: Settings, ) where State: Service< @@ -66,11 +73,19 @@ pub async fn test_responses( ) .await; + let mining_config = get_block_template_rpcs::config::Config { + miner_address: Some(transparent::Address::from_script_hash(network, [0xad; 20])), + }; + + let (mock_chain_tip, mock_chain_tip_sender) = MockChainTip::new(); + mock_chain_tip_sender.send_best_tip_height(NetworkUpgrade::Nu5.activation_height(network)); + let get_block_template_rpc = GetBlockTemplateRpcImpl::new( network, + mining_config, Buffer::new(mempool.clone(), 1), read_state, - latest_chain_tip, + mock_chain_tip, chain_verifier, ); @@ -102,7 +117,14 @@ pub async fn test_responses( .expect("unexpected panic in getblocktemplate RPC task") .expect("unexpected error in getblocktemplate RPC call"); - snapshot_rpc_getblocktemplate(get_block_template, &settings); + let coinbase_tx: Transaction = get_block_template + .coinbase_txn + .data + .as_ref() + .zcash_deserialize_into() + .expect("coinbase bytes are valid"); + + snapshot_rpc_getblocktemplate(get_block_template, coinbase_tx, &settings); // `submitblock` let submit_block = get_block_template_rpc @@ -124,8 +146,13 @@ fn snapshot_rpc_getblockhash(block_hash: GetBlockHash, settings: &insta::Setting } /// Snapshot `getblocktemplate` response, using `cargo insta` and JSON serialization. -fn snapshot_rpc_getblocktemplate(block_template: GetBlockTemplate, settings: &insta::Settings) { +fn snapshot_rpc_getblocktemplate( + block_template: GetBlockTemplate, + coinbase_tx: Transaction, + settings: &insta::Settings, +) { settings.bind(|| insta::assert_json_snapshot!("get_block_template", block_template)); + settings.bind(|| insta::assert_json_snapshot!("get_block_template.coinbase_tx", coinbase_tx)); } /// Snapshot `submitblock` response, using `cargo insta` and JSON serialization. diff --git a/zebra-rpc/src/methods/tests/snapshot/snapshots/get_block_count@mainnet_10.snap b/zebra-rpc/src/methods/tests/snapshot/snapshots/get_block_count@mainnet_10.snap index 685fdfb96..07a15a784 100644 --- a/zebra-rpc/src/methods/tests/snapshot/snapshots/get_block_count@mainnet_10.snap +++ b/zebra-rpc/src/methods/tests/snapshot/snapshots/get_block_count@mainnet_10.snap @@ -2,4 +2,4 @@ source: zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs expression: block_count --- -10 +1687104 diff --git a/zebra-rpc/src/methods/tests/snapshot/snapshots/get_block_count@testnet_10.snap b/zebra-rpc/src/methods/tests/snapshot/snapshots/get_block_count@testnet_10.snap index 685fdfb96..21ed9c9bf 100644 --- a/zebra-rpc/src/methods/tests/snapshot/snapshots/get_block_count@testnet_10.snap +++ b/zebra-rpc/src/methods/tests/snapshot/snapshots/get_block_count@testnet_10.snap @@ -2,4 +2,4 @@ source: zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs expression: block_count --- -10 +1842420 diff --git a/zebra-rpc/src/methods/tests/snapshot/snapshots/get_block_template.coinbase_tx@mainnet_10.snap b/zebra-rpc/src/methods/tests/snapshot/snapshots/get_block_template.coinbase_tx@mainnet_10.snap new file mode 100644 index 000000000..914020b56 --- /dev/null +++ b/zebra-rpc/src/methods/tests/snapshot/snapshots/get_block_template.coinbase_tx@mainnet_10.snap @@ -0,0 +1,42 @@ +--- +source: zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs +expression: coinbase_tx +--- +{ + "V5": { + "network_upgrade": "Nu5", + "lock_time": { + "Height": 0 + }, + "expiry_height": 1687105, + "inputs": [ + { + "Coinbase": { + "height": 1687105, + "data": [], + "sequence": 0 + } + } + ], + "outputs": [ + { + "value": 15625000, + "lock_script": "a914d45cb1adffb5215a42720532a076f02c7c778c9087" + }, + { + "value": 21875000, + "lock_script": "a91469a9f95a98fe581b6eb52841ef4806dc4402eb9087" + }, + { + "value": 25000000, + "lock_script": "a914931fec54c1fea86e574462cc32013f5400b8912987" + }, + { + "value": 250000000, + "lock_script": "a914adadadadadadadadadadadadadadadadadadadad87" + } + ], + "sapling_shielded_data": null, + "orchard_shielded_data": null + } +} diff --git a/zebra-rpc/src/methods/tests/snapshot/snapshots/get_block_template.coinbase_tx@testnet_10.snap b/zebra-rpc/src/methods/tests/snapshot/snapshots/get_block_template.coinbase_tx@testnet_10.snap new file mode 100644 index 000000000..3a4eb7ae7 --- /dev/null +++ b/zebra-rpc/src/methods/tests/snapshot/snapshots/get_block_template.coinbase_tx@testnet_10.snap @@ -0,0 +1,42 @@ +--- +source: zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs +expression: coinbase_tx +--- +{ + "V5": { + "network_upgrade": "Nu5", + "lock_time": { + "Height": 0 + }, + "expiry_height": 1842421, + "inputs": [ + { + "Coinbase": { + "height": 1842421, + "data": [], + "sequence": 0 + } + } + ], + "outputs": [ + { + "value": 15625000, + "lock_script": "a9140c0bcca02f3cba01a5d7423ac3903d40586399eb87" + }, + { + "value": 21875000, + "lock_script": "a9144e3f0d9a33a2721604cbae2de8d9171e21f8fbe487" + }, + { + "value": 25000000, + "lock_script": "a91471e1df05024288a00802de81e08c437859586c8787" + }, + { + "value": 250000000, + "lock_script": "a914adadadadadadadadadadadadadadadadadadadad87" + } + ], + "sapling_shielded_data": null, + "orchard_shielded_data": null + } +} diff --git a/zebra-rpc/src/methods/tests/snapshot/snapshots/get_block_template@mainnet_10.snap b/zebra-rpc/src/methods/tests/snapshot/snapshots/get_block_template@mainnet_10.snap index 21fd8d572..6a701693c 100644 --- a/zebra-rpc/src/methods/tests/snapshot/snapshots/get_block_template@mainnet_10.snap +++ b/zebra-rpc/src/methods/tests/snapshot/snapshots/get_block_template@mainnet_10.snap @@ -1,6 +1,5 @@ --- source: zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs -assertion_line: 128 expression: block_template --- { @@ -11,16 +10,16 @@ expression: block_template "lightclientroothash": "0000000000000000000000000000000000000000000000000000000000000000", "finalsaplingroothash": "0000000000000000000000000000000000000000000000000000000000000000", "defaultroots": { - "merkleroot": "0000000000000000000000000000000000000000000000000000000000000000", + "merkleroot": "6b370584714ab567c9c014ce72d325ab6c5927e181ac891acb35e6d4b6cc19a1", "chainhistoryroot": "0000000000000000000000000000000000000000000000000000000000000000", - "authdataroot": "0000000000000000000000000000000000000000000000000000000000000000", + "authdataroot": "0dbb78de9fdcd494307971e36dd049fc82d0ee9ee53aec8fd2a54dc0e426289b", "blockcommitmentshash": "0000000000000000000000000000000000000000000000000000000000000000" }, "transactions": [], "coinbasetxn": { - "data": "", - "hash": "0000000000000000000000000000000000000000000000000000000000000000", - "authdigest": "0000000000000000000000000000000000000000000000000000000000000000", + "data": "050000800a27a726b4d0d6c20000000041be1900010000000000000000000000000000000000000000000000000000000000000000ffffffff040341be190000000004286bee000000000017a914d45cb1adffb5215a42720532a076f02c7c778c908738c94d010000000017a91469a9f95a98fe581b6eb52841ef4806dc4402eb908740787d010000000017a914931fec54c1fea86e574462cc32013f5400b891298780b2e60e0000000017a914adadadadadadadadadadadadadadadadadadadad87000000", + "hash": "6b370584714ab567c9c014ce72d325ab6c5927e181ac891acb35e6d4b6cc19a1", + "authdigest": "0dbb78de9fdcd494307971e36dd049fc82d0ee9ee53aec8fd2a54dc0e426289b", "depends": [], "fee": 0, "sigops": 0, diff --git a/zebra-rpc/src/methods/tests/snapshot/snapshots/get_block_template@testnet_10.snap b/zebra-rpc/src/methods/tests/snapshot/snapshots/get_block_template@testnet_10.snap index 21fd8d572..10ac2ec57 100644 --- a/zebra-rpc/src/methods/tests/snapshot/snapshots/get_block_template@testnet_10.snap +++ b/zebra-rpc/src/methods/tests/snapshot/snapshots/get_block_template@testnet_10.snap @@ -1,6 +1,5 @@ --- source: zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs -assertion_line: 128 expression: block_template --- { @@ -11,16 +10,16 @@ expression: block_template "lightclientroothash": "0000000000000000000000000000000000000000000000000000000000000000", "finalsaplingroothash": "0000000000000000000000000000000000000000000000000000000000000000", "defaultroots": { - "merkleroot": "0000000000000000000000000000000000000000000000000000000000000000", + "merkleroot": "623400cc122baa015d3a4209f5903ebe215170c7e6e74831dce8372c5fd5b3cc", "chainhistoryroot": "0000000000000000000000000000000000000000000000000000000000000000", - "authdataroot": "0000000000000000000000000000000000000000000000000000000000000000", + "authdataroot": "a44375f0c0dd5ba612bd7b0efd77683cde8edf5055aff9fbfda443cc8d46bd3e", "blockcommitmentshash": "0000000000000000000000000000000000000000000000000000000000000000" }, "transactions": [], "coinbasetxn": { - "data": "", - "hash": "0000000000000000000000000000000000000000000000000000000000000000", - "authdigest": "0000000000000000000000000000000000000000000000000000000000000000", + "data": "050000800a27a726b4d0d6c200000000f51c1c00010000000000000000000000000000000000000000000000000000000000000000ffffffff0403f51c1c0000000004286bee000000000017a9140c0bcca02f3cba01a5d7423ac3903d40586399eb8738c94d010000000017a9144e3f0d9a33a2721604cbae2de8d9171e21f8fbe48740787d010000000017a91471e1df05024288a00802de81e08c437859586c878780b2e60e0000000017a914adadadadadadadadadadadadadadadadadadadad87000000", + "hash": "623400cc122baa015d3a4209f5903ebe215170c7e6e74831dce8372c5fd5b3cc", + "authdigest": "a44375f0c0dd5ba612bd7b0efd77683cde8edf5055aff9fbfda443cc8d46bd3e", "depends": [], "fee": 0, "sigops": 0, diff --git a/zebra-rpc/src/methods/tests/vectors.rs b/zebra-rpc/src/methods/tests/vectors.rs index 647e5be18..c8967141d 100644 --- a/zebra-rpc/src/methods/tests/vectors.rs +++ b/zebra-rpc/src/methods/tests/vectors.rs @@ -651,6 +651,7 @@ async fn rpc_getblockcount() { // Init RPC let get_block_template_rpc = get_block_template_rpcs::GetBlockTemplateRpcImpl::new( Mainnet, + Default::default(), Buffer::new(mempool.clone(), 1), read_state, latest_chain_tip.clone(), @@ -695,6 +696,7 @@ async fn rpc_getblockcount_empty_state() { // Init RPC let get_block_template_rpc = get_block_template_rpcs::GetBlockTemplateRpcImpl::new( Mainnet, + Default::default(), Buffer::new(mempool.clone(), 1), read_state, latest_chain_tip.clone(), @@ -745,6 +747,7 @@ async fn rpc_getblockhash() { // Init RPC let get_block_template_rpc = get_block_template_rpcs::GetBlockTemplateRpcImpl::new( Mainnet, + Default::default(), Buffer::new(mempool.clone(), 1), read_state, latest_chain_tip.clone(), @@ -785,7 +788,11 @@ async fn rpc_getblocktemplate() { use crate::methods::get_block_template_rpcs::constants::{ GET_BLOCK_TEMPLATE_MUTABLE_FIELD, GET_BLOCK_TEMPLATE_NONCE_RANGE_FIELD, }; - use zebra_chain::block::{MAX_BLOCK_BYTES, ZCASH_BLOCK_VERSION}; + use zebra_chain::{ + amount::{Amount, NonNegative}, + block::{MAX_BLOCK_BYTES, ZCASH_BLOCK_VERSION}, + chain_tip::mock::MockChainTip, + }; use zebra_consensus::MAX_BLOCK_SIGOPS; let _init_guard = zebra_test::init(); @@ -798,7 +805,7 @@ async fn rpc_getblocktemplate() { let mut mempool: MockService<_, _, _, BoxError> = MockService::build().for_unit_tests(); // Create a populated state service - let (state, read_state, latest_chain_tip, _chain_tip_change) = + let (state, read_state, _latest_chain_tip, _chain_tip_change) = zebra_state::populated_state(blocks.clone(), Mainnet).await; let ( @@ -814,12 +821,20 @@ async fn rpc_getblocktemplate() { ) .await; + let mining_config = get_block_template_rpcs::config::Config { + miner_address: Some(transparent::Address::from_script_hash(Mainnet, [0x7e; 20])), + }; + + let (mock_chain_tip, mock_chain_tip_sender) = MockChainTip::new(); + mock_chain_tip_sender.send_best_tip_height(NetworkUpgrade::Nu5.activation_height(Mainnet)); + // Init RPC let get_block_template_rpc = get_block_template_rpcs::GetBlockTemplateRpcImpl::new( Mainnet, + mining_config, Buffer::new(mempool.clone(), 1), read_state, - latest_chain_tip.clone(), + mock_chain_tip, tower::ServiceBuilder::new().service(chain_verifier), ); @@ -859,6 +874,18 @@ async fn rpc_getblocktemplate() { assert!(get_block_template.bits.is_empty()); assert_eq!(get_block_template.height, 0); + // Coinbase transaction checks. + assert!(get_block_template.coinbase_txn.required); + assert!(!get_block_template.coinbase_txn.data.as_ref().is_empty()); + assert_eq!(get_block_template.coinbase_txn.depends.len(), 0); + // TODO: should a coinbase transaction have sigops? + assert_eq!(get_block_template.coinbase_txn.sigops, 0); + // Coinbase transaction checks for empty blocks. + assert_eq!( + get_block_template.coinbase_txn.fee, + Amount::::zero() + ); + mempool.expect_no_requests().await; } @@ -879,15 +906,6 @@ async fn rpc_submitblock_errors() { zebra_state::populated_state(blocks, Mainnet).await; // Init RPCs - let _rpc = RpcImpl::new( - "RPC test", - Mainnet, - false, - Buffer::new(mempool.clone(), 1), - Buffer::new(read_state.clone(), 1), - latest_chain_tip.clone(), - ); - let ( chain_verifier, _transaction_verifier, @@ -904,6 +922,7 @@ async fn rpc_submitblock_errors() { // Init RPC let get_block_template_rpc = get_block_template_rpcs::GetBlockTemplateRpcImpl::new( Mainnet, + Default::default(), Buffer::new(mempool.clone(), 1), read_state, latest_chain_tip.clone(), diff --git a/zebra-rpc/src/server.rs b/zebra-rpc/src/server.rs index adcfe5eec..9dc6c9574 100644 --- a/zebra-rpc/src/server.rs +++ b/zebra-rpc/src/server.rs @@ -31,7 +31,7 @@ use crate::{ }; #[cfg(feature = "getblocktemplate-rpcs")] -use crate::methods::{GetBlockTemplateRpc, GetBlockTemplateRpcImpl}; +use crate::methods::{get_block_template_rpcs, GetBlockTemplateRpc, GetBlockTemplateRpcImpl}; pub mod compatibility; mod tracing_middleware; @@ -44,9 +44,17 @@ mod tests; pub struct RpcServer; impl RpcServer { - /// Start a new RPC server endpoint + /// Start a new RPC server endpoint. + // + // TODO: put some of the configs or services in their own struct? + #[allow(clippy::too_many_arguments)] pub fn spawn( config: Config, + #[cfg(feature = "getblocktemplate-rpcs")] + mining_config: get_block_template_rpcs::config::Config, + #[cfg(not(feature = "getblocktemplate-rpcs"))] + #[allow(unused_variables)] + mining_config: (), app_version: Version, mempool: Buffer, state: State, @@ -92,6 +100,7 @@ impl RpcServer { // Initialize the getblocktemplate rpc method handler let get_block_template_rpc_impl = GetBlockTemplateRpcImpl::new( network, + mining_config, mempool.clone(), state.clone(), latest_chain_tip.clone(), diff --git a/zebra-rpc/src/server/tests/vectors.rs b/zebra-rpc/src/server/tests/vectors.rs index 9db823857..703256903 100644 --- a/zebra-rpc/src/server/tests/vectors.rs +++ b/zebra-rpc/src/server/tests/vectors.rs @@ -53,6 +53,7 @@ fn rpc_server_spawn(parallel_cpu_threads: bool) { let (rpc_server_task_handle, rpc_tx_queue_task_handle) = RpcServer::spawn( config, + Default::default(), "RPC server test", Buffer::new(mempool.clone(), 1), Buffer::new(state.clone(), 1), @@ -124,6 +125,7 @@ fn rpc_server_spawn_unallocated_port(parallel_cpu_threads: bool) { let (rpc_server_task_handle, rpc_tx_queue_task_handle) = RpcServer::spawn( config, + Default::default(), "RPC server test", Buffer::new(mempool.clone(), 1), Buffer::new(state.clone(), 1), @@ -182,6 +184,7 @@ fn rpc_server_spawn_port_conflict() { let (_rpc_server_1_task_handle, _rpc_tx_queue_1_task_handle) = RpcServer::spawn( config.clone(), + Default::default(), "RPC server 1 test", Buffer::new(mempool.clone(), 1), Buffer::new(state.clone(), 1), @@ -196,6 +199,7 @@ fn rpc_server_spawn_port_conflict() { let (rpc_server_2_task_handle, _rpc_tx_queue_2_task_handle) = RpcServer::spawn( config, + Default::default(), "RPC server 2 conflict test", Buffer::new(mempool.clone(), 1), Buffer::new(state.clone(), 1), @@ -283,6 +287,7 @@ fn rpc_server_spawn_port_conflict_parallel_auto() { let (_rpc_server_1_task_handle, _rpc_tx_queue_1_task_handle) = RpcServer::spawn( config.clone(), + Default::default(), "RPC server 1 test", Buffer::new(mempool.clone(), 1), Buffer::new(state.clone(), 1), @@ -297,6 +302,7 @@ fn rpc_server_spawn_port_conflict_parallel_auto() { let (rpc_server_2_task_handle, _rpc_tx_queue_2_task_handle) = RpcServer::spawn( config, + Default::default(), "RPC server 2 conflict test", Buffer::new(mempool.clone(), 1), Buffer::new(state.clone(), 1), diff --git a/zebrad/src/commands/start.rs b/zebrad/src/commands/start.rs index 5fdfb3e70..6ff6a9e87 100644 --- a/zebrad/src/commands/start.rs +++ b/zebrad/src/commands/start.rs @@ -178,6 +178,10 @@ impl StartCmd { // Launch RPC server let (rpc_task_handle, rpc_tx_queue_task_handle) = RpcServer::spawn( config.rpc, + #[cfg(feature = "getblocktemplate-rpcs")] + config.mining, + #[cfg(not(feature = "getblocktemplate-rpcs"))] + (), app_version(), mempool.clone(), read_only_state_service,