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
This commit is contained in:
teor 2022-11-10 10:12:27 +10:00 committed by GitHub
parent ff81432582
commit 7e13677197
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 426 additions and 114 deletions

View File

@ -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;

View File

@ -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<Item = (Amount<NonNegative>, 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.
//
// <https://zips.z.cash/protocol/protocol.pdf#txnconsensus>
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.
//
// <https://zips.z.cash/protocol/protocol.pdf#subsidyconcepts>
//
// > 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.
//
// <https://zips.z.cash/protocol/protocol.pdf#txnconsensus>
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,
}
}
}

View File

@ -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<Vec<u8>>,
sequence: Option<u32>,
) -> 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<NonNegative>, 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<NonNegative> {

View File

@ -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
///
/// <https://developer.bitcoin.org/reference/transactions.html#coinbase-input-the-input-of-the-first-transaction-in-a-block>
/// > A coinbase transaction script MUST have length in {2 .. 100} bytes.
///
/// <https://zips.z.cash/protocol/protocol.pdf#txnconsensus>
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
///
/// <https://github.com/zcash/zips/issues/589>
/// > A coinbase transaction script MUST have length in {2 .. 100} bytes.
///
/// <https://zips.z.cash/protocol/protocol.pdf#txnconsensus>
pub const MIN_COINBASE_DATA_LEN: usize = 2;
/// The coinbase data for a genesis block.

View File

@ -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;

View File

@ -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`.

View File

@ -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<Amount<NonNegat
}
}
/// `MinerSubsidy(height)` as described in [protocol specification §7.8][7.8]
///
/// [7.8]: https://zips.z.cash/protocol/protocol.pdf#subsidies
pub fn miner_subsidy(height: Height, network: Network) -> Result<Amount<NonNegative>, Error> {
let total_funding_stream_amount: Result<Amount<NonNegative>, _> =
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<Amount<NonNegative>> {
transaction

View File

@ -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,

View File

@ -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<transparent::Address>,
// 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<Mempool, mempool::Request>,
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<Result<GetBlockTemplate>> {
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<NonNegative> {
)
}
/// 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<NonNegative>,
) -> Vec<(Amount<NonNegative>, 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<NonNegative>, 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`.
//

View File

@ -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<Address>,
pub miner_address: Option<transparent::Address>,
}

View File

@ -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, ReadState>(
>,
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<State, ReadState>(
)
.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<State, ReadState>(
.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.

View File

@ -2,4 +2,4 @@
source: zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs
expression: block_count
---
10
1687104

View File

@ -2,4 +2,4 @@
source: zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs
expression: block_count
---
10
1842420

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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,

View File

@ -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,

View File

@ -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::<NonNegative>::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(),

View File

@ -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<Version, Mempool, State, Tip, ChainVerifier>(
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<Mempool, mempool::Request>,
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(),

View File

@ -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),

View File

@ -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,