diff --git a/zebra-chain/src/amount.rs b/zebra-chain/src/amount.rs index e8b0d79a9..ce36c0431 100644 --- a/zebra-chain/src/amount.rs +++ b/zebra-chain/src/amount.rs @@ -7,6 +7,7 @@ use std::{ cmp::Ordering, + fmt, hash::{Hash, Hasher}, marker::PhantomData, ops::RangeInclusive, @@ -25,6 +26,10 @@ mod tests; pub type Result = std::result::Result; /// A runtime validated type for representing amounts of zatoshis +// +// TODO: +// - remove the default NegativeAllowed bound, to make consensus rule reviews easier +// - put a Constraint bound on the type generic, not just some implementations #[derive(Clone, Copy, Serialize, Deserialize)] #[serde(try_from = "i64")] #[serde(into = "i64")] @@ -42,8 +47,16 @@ pub struct Amount( PhantomData, ); -impl std::fmt::Debug for Amount { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl fmt::Display for Amount { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let zats = self.zatoshis(); + + f.pad_integral(zats > 0, "", &zats.to_string()) + } +} + +impl fmt::Debug for Amount { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_tuple(&format!("Amount<{}>", std::any::type_name::())) .field(&self.0) .finish() @@ -59,6 +72,11 @@ impl Amount { self.0.try_into() } + /// Returns the number of zatoshis in this amount. + pub fn zatoshis(&self) -> i64 { + self.0 + } + /// To little endian byte array pub fn to_bytes(&self) -> [u8; 8] { let mut buf: [u8; 8] = [0; 8]; @@ -391,7 +409,7 @@ where impl<'amt, C> std::iter::Sum<&'amt Amount> for Result> where - C: Constraint + std::marker::Copy + 'amt, + C: Constraint + Copy + 'amt, { fn sum>>(iter: I) -> Self { iter.copied().sum() diff --git a/zebra-consensus/src/block/subsidy/funding_streams.rs b/zebra-consensus/src/block/subsidy/funding_streams.rs index 8e6f76665..d661223c5 100644 --- a/zebra-consensus/src/block/subsidy/funding_streams.rs +++ b/zebra-consensus/src/block/subsidy/funding_streams.rs @@ -9,7 +9,7 @@ use zebra_chain::{ block::Height, parameters::{Network, NetworkUpgrade::*}, transaction::Transaction, - transparent::{Address, Output, Script}, + transparent::{self, Script}, }; use crate::{block::subsidy::general::block_subsidy, parameters::subsidy::*}; @@ -104,25 +104,39 @@ fn funding_stream_address_index(height: Height, network: Network) -> usize { } /// Return the address corresponding to given height, network and funding stream receiver. +/// +/// This function only returns transparent addresses, because the current Zcash funding streams +/// only use transparent addresses, pub fn funding_stream_address( height: Height, network: Network, receiver: FundingStreamReceiver, -) -> Address { +) -> transparent::Address { let index = funding_stream_address_index(height, network); let address = &FUNDING_STREAM_ADDRESSES .get(&network) .expect("there is always another hash map as value for a given valid network") .get(&receiver) .expect("in the inner hash map there is always a vector of strings with addresses")[index]; - Address::from_str(address).expect("address should deserialize") + transparent::Address::from_str(address).expect("address should deserialize") +} + +/// Return a human-readable name and a specification URL for the funding stream `receiver`. +pub fn funding_stream_recipient_info( + receiver: FundingStreamReceiver, +) -> (&'static str, &'static str) { + let name = FUNDING_STREAM_NAMES + .get(&receiver) + .expect("all funding streams have a name"); + + (name, FUNDING_STREAM_SPECIFICATION) } /// Given a funding stream P2SH address, create a script and check if it is the same /// as the given lock_script as described in [protocol specification §7.10][7.10] /// /// [7.10]: https://zips.z.cash/protocol/protocol.pdf#fundingstreams -pub fn check_script_form(lock_script: &Script, address: Address) -> bool { +pub fn check_script_form(lock_script: &Script, address: transparent::Address) -> bool { assert!( address.is_script_hash(), "incorrect funding stream address constant: {address} \ @@ -136,7 +150,7 @@ pub fn check_script_form(lock_script: &Script, address: Address) -> bool { } /// Returns a new funding stream coinbase output lock script, which pays to the P2SH `address`. -pub fn new_coinbase_script(address: Address) -> Script { +pub fn new_coinbase_script(address: transparent::Address) -> Script { assert!( address.is_script_hash(), "incorrect coinbase script address: {address} \ @@ -150,8 +164,11 @@ pub fn new_coinbase_script(address: Address) -> Script { address.create_script_from_address() } -/// Returns a list of outputs in `Transaction`, which have a script address equal to `Address`. -pub fn filter_outputs_by_address(transaction: &Transaction, address: Address) -> Vec { +/// Returns a list of outputs in `transaction`, which have a script address equal to `address`. +pub fn filter_outputs_by_address( + transaction: &Transaction, + address: transparent::Address, +) -> Vec { transaction .outputs() .iter() diff --git a/zebra-consensus/src/block/subsidy/funding_streams/tests.rs b/zebra-consensus/src/block/subsidy/funding_streams/tests.rs index ad42db8ee..cddf66d6d 100644 --- a/zebra-consensus/src/block/subsidy/funding_streams/tests.rs +++ b/zebra-consensus/src/block/subsidy/funding_streams/tests.rs @@ -65,7 +65,8 @@ fn test_funding_stream_addresses() -> Result<(), Report> { for (network, receivers) in FUNDING_STREAM_ADDRESSES.iter() { for (receiver, addresses) in receivers { for address in addresses { - let address = Address::from_str(address).expect("address should deserialize"); + let address = + transparent::Address::from_str(address).expect("address should deserialize"); assert_eq!( &address.network(), network, diff --git a/zebra-consensus/src/lib.rs b/zebra-consensus/src/lib.rs index f07511d2a..1a3d1d304 100644 --- a/zebra-consensus/src/lib.rs +++ b/zebra-consensus/src/lib.rs @@ -48,7 +48,10 @@ pub mod error; pub use block::{ subsidy::{ - funding_streams::{funding_stream_address, funding_stream_values, new_coinbase_script}, + funding_streams::{ + funding_stream_address, funding_stream_recipient_info, funding_stream_values, + height_for_first_halving, new_coinbase_script, + }, general::miner_subsidy, }, Request, VerifyBlockError, MAX_BLOCK_SIGOPS, diff --git a/zebra-consensus/src/parameters/subsidy.rs b/zebra-consensus/src/parameters/subsidy.rs index 7bd813fa2..a6b0d3808 100644 --- a/zebra-consensus/src/parameters/subsidy.rs +++ b/zebra-consensus/src/parameters/subsidy.rs @@ -63,7 +63,27 @@ pub enum FundingStreamReceiver { /// [7.10.1]: https://zips.z.cash/protocol/protocol.pdf#zip214fundingstreams pub const FUNDING_STREAM_RECEIVER_DENOMINATOR: u64 = 100; +/// The specification for all current funding stream receivers, a URL that links to [ZIP-214]. +/// +/// [ZIP-214]: https://zips.z.cash/zip-0214 +pub const FUNDING_STREAM_SPECIFICATION: &str = "https://zips.z.cash/zip-0214"; + +// TODO: use a struct for the info for each funding stream, like zcashd does: +// https://github.com/zcash/zcash/blob/3f09cfa00a3c90336580a127e0096d99e25a38d6/src/consensus/funding.cpp#L13-L32 lazy_static! { + /// The name for each funding stream receiver, as described in [ZIP-1014] and [`zcashd`]. + /// + /// [ZIP-1014]: https://zips.z.cash/zip-1014#abstract + /// [`zcashd`]: https://github.com/zcash/zcash/blob/3f09cfa00a3c90336580a127e0096d99e25a38d6/src/consensus/funding.cpp#L13-L32 + pub static ref FUNDING_STREAM_NAMES: HashMap = { + let mut hash_map = HashMap::new(); + hash_map.insert(FundingStreamReceiver::Ecc, "Electric Coin Company"); + hash_map.insert(FundingStreamReceiver::ZcashFoundation, "Zcash Foundation"); + hash_map.insert(FundingStreamReceiver::MajorGrants, "Major Grants"); + hash_map + }; + + /// The numerator for each funding stream receiver category /// as described in [protocol specification §7.10.1][7.10.1]. /// diff --git a/zebra-rpc/src/methods/get_block_template_rpcs.rs b/zebra-rpc/src/methods/get_block_template_rpcs.rs index a2dbbf57e..7b48351e6 100644 --- a/zebra-rpc/src/methods/get_block_template_rpcs.rs +++ b/zebra-rpc/src/methods/get_block_template_rpcs.rs @@ -8,6 +8,7 @@ use jsonrpc_derive::rpc; use tower::{buffer::Buffer, Service, ServiceExt}; use zebra_chain::{ + amount::Amount, block::{self, Block, Height}, chain_sync_status::ChainSyncStatus, chain_tip::ChainTip, @@ -15,7 +16,10 @@ use zebra_chain::{ serialization::ZcashDeserializeInto, transparent, }; -use zebra_consensus::VerifyChainError; +use zebra_consensus::{ + funding_stream_address, funding_stream_values, height_for_first_halving, miner_subsidy, + VerifyChainError, +}; use zebra_network::AddressBookPeers; use zebra_node_services::mempool; use zebra_state::{ReadRequest, ReadResponse}; @@ -25,14 +29,20 @@ use crate::methods::{ get_block_template_rpcs::{ constants::{ DEFAULT_SOLUTION_RATE_WINDOW_SIZE, GET_BLOCK_TEMPLATE_MEMPOOL_LONG_POLL_INTERVAL, + ZCASHD_FUNDING_STREAM_ORDER, }, get_block_template::{ check_miner_address, check_synced_to_tip, fetch_mempool_transactions, fetch_state_tip_and_local_time, validate_block_proposal, }, types::{ - get_block_template::GetBlockTemplate, get_mining_info, hex_data::HexData, - long_poll::LongPollInput, peer_info::PeerInfo, submit_block, + get_block_template::GetBlockTemplate, + get_mining_info, + hex_data::HexData, + long_poll::LongPollInput, + peer_info::PeerInfo, + submit_block, + subsidy::{BlockSubsidy, FundingStream}, }, }, height_from_signed_int, GetBlockHash, MISSING_BLOCK_ERROR_CODE, @@ -156,6 +166,16 @@ pub trait GetBlockTemplateRpc { /// zcashd reference: [`getpeerinfo`](https://zcash.github.io/rpc/getpeerinfo.html) #[rpc(name = "getpeerinfo")] fn get_peer_info(&self) -> BoxFuture>>; + + /// Returns the block subsidy reward of the block at `height`, taking into account the mining slow start. + /// Returns an error if `height` is less than the height of the first halving for the current network. + /// + /// `height` can be any valid current or future height. + /// If `height` is not supplied, uses the tip height. + /// + /// zcashd reference: [`getblocksubsidy`](https://zcash.github.io/rpc/getblocksubsidy.html) + #[rpc(name = "getblocksubsidy")] + fn get_block_subsidy(&self, height: Option) -> BoxFuture>; } /// RPC method implementations. @@ -748,6 +768,67 @@ where } .boxed() } + + fn get_block_subsidy(&self, height: Option) -> BoxFuture> { + let latest_chain_tip = self.latest_chain_tip.clone(); + let network = self.network; + + async move { + let height = if let Some(height) = height { + Height(height) + } else { + best_chain_tip_height(&latest_chain_tip)? + }; + + if height < height_for_first_halving(network) { + return Err(Error { + code: ErrorCode::ServerError(0), + message: "Zebra does not support founders' reward subsidies, \ + use a block height that is after the first halving" + .into(), + data: None, + }); + } + + let miner = miner_subsidy(height, network).map_err(|error| Error { + code: ErrorCode::ServerError(0), + message: error.to_string(), + data: None, + })?; + // Always zero for post-halving blocks + let founders = Amount::zero(); + + let funding_streams = + funding_stream_values(height, network).map_err(|error| Error { + code: ErrorCode::ServerError(0), + message: error.to_string(), + data: None, + })?; + let mut funding_streams: Vec<_> = funding_streams + .iter() + .map(|(receiver, value)| { + let address = funding_stream_address(height, network, *receiver); + (*receiver, FundingStream::new(*receiver, *value, address)) + }) + .collect(); + + // Use the same funding stream order as zcashd + funding_streams.sort_by_key(|(receiver, _funding_stream)| { + ZCASHD_FUNDING_STREAM_ORDER + .iter() + .position(|zcashd_receiver| zcashd_receiver == receiver) + }); + + let (_receivers, funding_streams): (Vec<_>, _) = funding_streams.into_iter().unzip(); + + Ok(BlockSubsidy { + miner: miner.into(), + founders: founders.into(), + funding_streams, + }) + } + .boxed() + } } // Put support functions in a submodule, to keep this file small. diff --git a/zebra-rpc/src/methods/get_block_template_rpcs/constants.rs b/zebra-rpc/src/methods/get_block_template_rpcs/constants.rs index ed624e4dc..8658f9299 100644 --- a/zebra-rpc/src/methods/get_block_template_rpcs/constants.rs +++ b/zebra-rpc/src/methods/get_block_template_rpcs/constants.rs @@ -2,6 +2,8 @@ use jsonrpc_core::ErrorCode; +use zebra_consensus::FundingStreamReceiver::{self, *}; + /// When long polling, the amount of time we wait between mempool queries. /// (And sync status queries, which we do right before mempool queries.) /// @@ -52,3 +54,9 @@ pub const NOT_SYNCED_ERROR_CODE: ErrorCode = ErrorCode::ServerError(-10); /// /// Based on default value in zcashd. pub const DEFAULT_SOLUTION_RATE_WINDOW_SIZE: usize = 120; + +/// The funding stream order in `zcashd` RPC responses. +/// +/// [`zcashd`]: https://github.com/zcash/zcash/blob/3f09cfa00a3c90336580a127e0096d99e25a38d6/src/consensus/funding.cpp#L13-L32 +pub const ZCASHD_FUNDING_STREAM_ORDER: &[FundingStreamReceiver] = + &[Ecc, ZcashFoundation, MajorGrants]; diff --git a/zebra-rpc/src/methods/get_block_template_rpcs/types.rs b/zebra-rpc/src/methods/get_block_template_rpcs/types.rs index 7de7ceaf6..717072d9c 100644 --- a/zebra-rpc/src/methods/get_block_template_rpcs/types.rs +++ b/zebra-rpc/src/methods/get_block_template_rpcs/types.rs @@ -7,4 +7,6 @@ pub mod hex_data; pub mod long_poll; pub mod peer_info; pub mod submit_block; +pub mod subsidy; pub mod transaction; +pub mod zec; diff --git a/zebra-rpc/src/methods/get_block_template_rpcs/types/subsidy.rs b/zebra-rpc/src/methods/get_block_template_rpcs/types/subsidy.rs new file mode 100644 index 000000000..51c63636c --- /dev/null +++ b/zebra-rpc/src/methods/get_block_template_rpcs/types/subsidy.rs @@ -0,0 +1,71 @@ +//! Types for the `getblocksubsidy` RPC. + +use zebra_chain::{ + amount::{Amount, NonNegative}, + transparent, +}; +use zebra_consensus::{funding_stream_recipient_info, FundingStreamReceiver}; + +use crate::methods::get_block_template_rpcs::types::zec::Zec; + +/// A response to a `getblocksubsidy` RPC request +#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +pub struct BlockSubsidy { + /// An array of funding stream descriptions. + /// Always present, because Zebra returns an error for heights before the first halving. + #[serde(rename = "fundingstreams")] + pub funding_streams: Vec, + + /// The mining reward amount in ZEC. + /// + /// This does not include the miner fee. + pub miner: Zec, + + /// The founders' reward amount in ZEC. + /// + /// Zebra returns an error when asked for founders reward heights, + /// because it checkpoints those blocks instead. + pub founders: Zec, +} + +/// A single funding stream's information in a `getblocksubsidy` RPC request +#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +pub struct FundingStream { + /// A description of the funding stream recipient. + pub recipient: String, + + /// A URL for the specification of this funding stream. + pub specification: String, + + /// The funding stream amount in ZEC. + pub value: Zec, + + /// The funding stream amount in zatoshis. + #[serde(rename = "valueZat")] + pub value_zat: Amount, + + /// The transparent or Sapling address of the funding stream recipient. + /// + /// The current Zcash funding streams only use transparent addresses, + /// so Zebra doesn't support Sapling addresses in this RPC. + pub address: transparent::Address, +} + +impl FundingStream { + /// Convert a `receiver`, `value`, and `address` into a `FundingStream` response. + pub fn new( + receiver: FundingStreamReceiver, + value: Amount, + address: transparent::Address, + ) -> FundingStream { + let (recipient, specification) = funding_stream_recipient_info(receiver); + + FundingStream { + recipient: recipient.to_string(), + specification: specification.to_string(), + value: value.into(), + value_zat: value, + address, + } + } +} diff --git a/zebra-rpc/src/methods/get_block_template_rpcs/types/zec.rs b/zebra-rpc/src/methods/get_block_template_rpcs/types/zec.rs new file mode 100644 index 000000000..7aea6da72 --- /dev/null +++ b/zebra-rpc/src/methods/get_block_template_rpcs/types/zec.rs @@ -0,0 +1,212 @@ +//! ZEC amount formatting. +//! +//! The `f64` values returned by this type should not be used in consensus-critical code. +//! The values themselves are accurate, but any calculations using them could be lossy. + +use std::{ + fmt, + hash::{Hash, Hasher}, + ops, + str::FromStr, +}; + +use zebra_chain::amount::{self, Amount, Constraint, COIN}; + +use zebra_node_services::BoxError; + +// Doc links only +#[allow(unused_imports)] +use zebra_chain::amount::MAX_MONEY; + +/// The maximum precision of a zatoshi in ZEC. +/// Also used as the default decimal precision for ZEC formatting. +/// +/// This is the same as the `getblocksubsidy` RPC in `zcashd`: +/// +pub const MAX_ZEC_FORMAT_PRECISION: usize = 8; + +/// A wrapper type that formats [`Amount`]s as ZEC, using double-precision floating point. +/// +/// This formatting is accurate to the nearest zatoshi, as long as the number of floating-point +/// calculations is very small. This is because [`MAX_MONEY`] uses 51 bits, but [`f64`] has +/// [53 bits of precision](f64::MANTISSA_DIGITS). +/// +/// Rust uses [`roundTiesToEven`](f32), which can lose one bit of precision per calculation +/// in the worst case. (Assuming the platform implements it correctly.) +/// +/// Unlike `zcashd`, Zebra doesn't have control over its JSON number precision, +/// because it uses `serde_json`'s formatter. But `zcashd` uses a fixed-point calculation: +/// +#[derive(Clone, Copy, serde::Serialize, serde::Deserialize)] +#[serde(try_from = "f64")] +#[serde(into = "f64")] +#[serde(bound = "C: Constraint + Clone")] +pub struct Zec(Amount); + +impl Zec { + /// Returns the `f64` ZEC value for the inner amount. + /// + /// The returned value should not be used for consensus-critical calculations, + /// because it is lossy. + pub fn lossy_zec(&self) -> f64 { + let zats = self.zatoshis(); + // These conversions are exact, because f64 has 53 bits of precision, + // MAX_MONEY has <51, and COIN has <27, so we have 2 extra bits of precision. + let zats = zats as f64; + let coin = COIN as f64; + + // After this calculation, we might have lost one bit of precision, + // leaving us with only 1 extra bit. + zats / coin + } + + /// Converts a `f64` ZEC value to a [`Zec`] amount. + /// + /// This method should not be used for consensus-critical calculations, because it is lossy. + pub fn from_lossy_zec(lossy_zec: f64) -> Result { + // This conversion is exact, because f64 has 53 bits of precision, but COIN has <27 + let coin = COIN as f64; + + // After this calculation, we might have lost one bit of precision + let zats = lossy_zec * coin; + + if zats != zats.trunc() { + return Err( + "loss of precision parsing ZEC value: floating point had fractional zatoshis" + .into(), + ); + } + + // We know this conversion is exact, because we just checked. + let zats = zats as i64; + let zats = Amount::try_from(zats)?; + + Ok(Self(zats)) + } +} + +// These conversions are lossy, so they should not be used in consensus-critical code +impl From> for f64 { + fn from(zec: Zec) -> f64 { + zec.lossy_zec() + } +} + +impl TryFrom for Zec { + type Error = BoxError; + + fn try_from(value: f64) -> Result { + Self::from_lossy_zec(value) + } +} + +// This formatter should not be used for consensus-critical outputs. +impl fmt::Display for Zec { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let zec = self.lossy_zec(); + + // Try to format like `zcashd` by default + let decimals = f.precision().unwrap_or(MAX_ZEC_FORMAT_PRECISION); + let string = format!("{zec:.decimals$}"); + f.pad_integral(zec >= 0.0, "", &string) + } +} + +// This parser should not be used for consensus-critical inputs. +impl FromStr for Zec { + type Err = BoxError; + + fn from_str(s: &str) -> Result { + let lossy_zec: f64 = s.parse()?; + + Self::from_lossy_zec(lossy_zec) + } +} + +impl std::fmt::Debug for Zec { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct(&format!("Zec<{}>", std::any::type_name::())) + .field("ZEC", &self.to_string()) + .field("zat", &self.0) + .finish() + } +} + +impl ops::Deref for Zec { + type Target = Amount; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl ops::DerefMut for Zec { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl From> for Zec { + fn from(amount: Amount) -> Self { + Self(amount) + } +} + +impl From> for Amount { + fn from(zec: Zec) -> Amount { + zec.0 + } +} + +impl From> for i64 { + fn from(zec: Zec) -> i64 { + zec.0.into() + } +} + +impl TryFrom for Zec { + type Error = amount::Error; + + fn try_from(value: i64) -> Result { + Ok(Self(Amount::try_from(value)?)) + } +} + +impl Hash for Zec { + /// Zecs with the same value are equal, even if they have different constraints + fn hash(&self, state: &mut H) { + self.0.hash(state); + } +} + +impl PartialEq> for Zec { + fn eq(&self, other: &Zec) -> bool { + self.0.eq(&other.0) + } +} + +impl PartialEq for Zec { + fn eq(&self, other: &i64) -> bool { + self.0.eq(other) + } +} + +impl PartialEq> for i64 { + fn eq(&self, other: &Zec) -> bool { + self.eq(&other.0) + } +} + +impl PartialEq> for Zec { + fn eq(&self, other: &Amount) -> bool { + self.0.eq(other) + } +} + +impl PartialEq> for Amount { + fn eq(&self, other: &Zec) -> bool { + self.eq(&other.0) + } +} + +impl Eq for Zec {} 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 d7ef39bed..5c25d70ab 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 @@ -41,6 +41,7 @@ use crate::methods::{ long_poll::{LongPollId, LONG_POLL_ID_LENGTH}, peer_info::PeerInfo, submit_block, + subsidy::BlockSubsidy, }, }, tests::utils::fake_history_tree, @@ -158,6 +159,14 @@ pub async fn test_responses( .expect("We should have a success response"); snapshot_rpc_getmininginfo(get_mining_info, &settings); + // `getblocksubsidy` + let fake_future_block_height = fake_tip_height.0 + 100_000; + let get_block_subsidy = get_block_template_rpc + .get_block_subsidy(Some(fake_future_block_height)) + .await + .expect("We should have a success response"); + snapshot_rpc_getblocksubsidy(get_block_subsidy, &settings); + // `getpeerinfo` let get_peer_info = get_block_template_rpc .get_peer_info() @@ -413,6 +422,11 @@ fn snapshot_rpc_getmininginfo( settings.bind(|| insta::assert_json_snapshot!("get_mining_info", get_mining_info)); } +/// Snapshot `getblocksubsidy` response, using `cargo insta` and JSON serialization. +fn snapshot_rpc_getblocksubsidy(get_block_subsidy: BlockSubsidy, settings: &insta::Settings) { + settings.bind(|| insta::assert_json_snapshot!("get_block_subsidy", get_block_subsidy)); +} + /// Snapshot `getpeerinfo` response, using `cargo insta` and JSON serialization. fn snapshot_rpc_getpeerinfo(get_peer_info: Vec, settings: &insta::Settings) { settings.bind(|| insta::assert_json_snapshot!("get_peer_info", get_peer_info)); diff --git a/zebra-rpc/src/methods/tests/snapshot/snapshots/get_block_subsidy@mainnet_10.snap b/zebra-rpc/src/methods/tests/snapshot/snapshots/get_block_subsidy@mainnet_10.snap new file mode 100644 index 000000000..023860146 --- /dev/null +++ b/zebra-rpc/src/methods/tests/snapshot/snapshots/get_block_subsidy@mainnet_10.snap @@ -0,0 +1,31 @@ +--- +source: zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs +expression: get_block_subsidy +--- +{ + "fundingstreams": [ + { + "recipient": "Electric Coin Company", + "specification": "https://zips.z.cash/zip-0214", + "value": 0.21875, + "valueZat": 21875000, + "address": "t3PdBRr2S1XTDzrV8bnZkXF3SJcrzHWe1wj" + }, + { + "recipient": "Zcash Foundation", + "specification": "https://zips.z.cash/zip-0214", + "value": 0.15625, + "valueZat": 15625000, + "address": "t3dvVE3SQEi7kqNzwrfNePxZ1d4hUyztBA1" + }, + { + "recipient": "Major Grants", + "specification": "https://zips.z.cash/zip-0214", + "value": 0.25, + "valueZat": 25000000, + "address": "t3XyYW8yBFRuMnfvm5KLGFbEVz25kckZXym" + } + ], + "miner": 2.5, + "founders": 0.0 +} diff --git a/zebra-rpc/src/methods/tests/snapshot/snapshots/get_block_subsidy@testnet_10.snap b/zebra-rpc/src/methods/tests/snapshot/snapshots/get_block_subsidy@testnet_10.snap new file mode 100644 index 000000000..ff14493b2 --- /dev/null +++ b/zebra-rpc/src/methods/tests/snapshot/snapshots/get_block_subsidy@testnet_10.snap @@ -0,0 +1,31 @@ +--- +source: zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs +expression: get_block_subsidy +--- +{ + "fundingstreams": [ + { + "recipient": "Electric Coin Company", + "specification": "https://zips.z.cash/zip-0214", + "value": 0.21875, + "valueZat": 21875000, + "address": "t2HxbP5keQSx7p592zWQ5bJ5GrMmGDsV2Xa" + }, + { + "recipient": "Zcash Foundation", + "specification": "https://zips.z.cash/zip-0214", + "value": 0.15625, + "valueZat": 15625000, + "address": "t27eWDgjFYJGVXmzrXeVjnb5J3uXDM9xH9v" + }, + { + "recipient": "Major Grants", + "specification": "https://zips.z.cash/zip-0214", + "value": 0.25, + "valueZat": 25000000, + "address": "t2Gvxv2uNM7hbbACjNox4H6DjByoKZ2Fa3P" + } + ], + "miner": 2.5, + "founders": 0.0 +}