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:
parent
ff81432582
commit
7e13677197
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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> {
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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`.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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`.
|
||||
//
|
||||
|
|
|
@ -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>,
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -2,4 +2,4 @@
|
|||
source: zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs
|
||||
expression: block_count
|
||||
---
|
||||
10
|
||||
1687104
|
||||
|
|
|
@ -2,4 +2,4 @@
|
|||
source: zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs
|
||||
expression: block_count
|
||||
---
|
||||
10
|
||||
1842420
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in New Issue