From 676e82ff30559e654950a78e132d411018067ca0 Mon Sep 17 00:00:00 2001 From: teor Date: Wed, 25 Jan 2023 11:07:55 +1000 Subject: [PATCH 01/15] Make it clearer that Zebra only supports transparent funding streams --- .../src/block/subsidy/funding_streams.rs | 20 ++++++++++++------- .../block/subsidy/funding_streams/tests.rs | 3 ++- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/zebra-consensus/src/block/subsidy/funding_streams.rs b/zebra-consensus/src/block/subsidy/funding_streams.rs index 8e6f76665c2..5bcd6605182 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,28 @@ 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") } /// 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 +139,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 +153,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 ad42db8eea5..cddf66d6d4d 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, From 090ce480c92ee6d10ad18d83b582422a66d948ba Mon Sep 17 00:00:00 2001 From: teor Date: Wed, 25 Jan 2023 11:08:33 +1000 Subject: [PATCH 02/15] Initial getblocksubsidy RPC types and method, without ZEC conversion --- .../src/block/subsidy/funding_streams.rs | 11 +++ zebra-consensus/src/lib.rs | 5 +- zebra-consensus/src/parameters/subsidy.rs | 21 +++++ .../src/methods/get_block_template_rpcs.rs | 77 ++++++++++++++++++- .../methods/get_block_template_rpcs/types.rs | 1 + .../get_block_template_rpcs/types/subsidy.rs | 72 +++++++++++++++++ 6 files changed, 183 insertions(+), 4 deletions(-) create mode 100644 zebra-rpc/src/methods/get_block_template_rpcs/types/subsidy.rs diff --git a/zebra-consensus/src/block/subsidy/funding_streams.rs b/zebra-consensus/src/block/subsidy/funding_streams.rs index 5bcd6605182..d661223c515 100644 --- a/zebra-consensus/src/block/subsidy/funding_streams.rs +++ b/zebra-consensus/src/block/subsidy/funding_streams.rs @@ -121,6 +121,17 @@ pub fn funding_stream_address( 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] /// diff --git a/zebra-consensus/src/lib.rs b/zebra-consensus/src/lib.rs index f07511d2a26..1a3d1d304b8 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 7bd813fa20f..7d56e66b5bd 100644 --- a/zebra-consensus/src/parameters/subsidy.rs +++ b/zebra-consensus/src/parameters/subsidy.rs @@ -63,7 +63,28 @@ 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 a2dbbf57e3f..4a549ef92e5 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}; @@ -31,8 +35,13 @@ use crate::methods::{ 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 +165,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 and the founders reward. + /// + /// `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 +767,58 @@ 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 funding stream 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 funding_streams = funding_streams + .iter() + .map(|(receiver, value)| { + let address = funding_stream_address(height, network, *receiver); + FundingStream::new(*receiver, *value, address) + }) + .collect(); + + Ok(BlockSubsidy { + miner, + founders, + 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/types.rs b/zebra-rpc/src/methods/get_block_template_rpcs/types.rs index 7de7ceaf604..4914cc9dc1a 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,5 @@ pub mod hex_data; pub mod long_poll; pub mod peer_info; pub mod submit_block; +pub mod subsidy; pub mod transaction; 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 00000000000..e809fd88c8a --- /dev/null +++ b/zebra-rpc/src/methods/get_block_template_rpcs/types/subsidy.rs @@ -0,0 +1,72 @@ +//! Types for the `getblocksubsidy` RPC. + +use zebra_chain::{ + amount::{Amount, NonNegative}, + transparent, +}; +use zebra_consensus::{funding_stream_recipient_info, FundingStreamReceiver}; + +/// A response to a `getblocksubsidy` RPC request +#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +pub struct BlockSubsidy { + /// The mining reward amount in ZEC. + /// + /// This does not include the miner fee. + // + // TODO: format ZEC amounts as fixed-point decimal, like `zcashd`: + // https://github.com/zcash/zcash/blob/f6a4f68115ea4c58d55c8538579d0877ba9c8f79/src/rpc/server.cpp#L127-L135 + pub miner: Amount, + + /// The founders' reward amount in ZEC. + /// + /// Zebra returns an error when asked for founders reward heights, + /// because it checkpoints those blocks instead. + pub founders: Amount, + + /// 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, +} + +/// 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: Amount, + + /// 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_zat: value, + address, + } + } +} From 07ecfbe5519c34772b7ba04b1ccd9f1ddec34eb0 Mon Sep 17 00:00:00 2001 From: teor Date: Wed, 25 Jan 2023 12:59:06 +1000 Subject: [PATCH 03/15] Add a ZEC fixed-point format wrapper to Amount --- zebra-chain/src/amount.rs | 28 ++++- zebra-chain/src/amount/zec.rs | 211 ++++++++++++++++++++++++++++++++++ 2 files changed, 236 insertions(+), 3 deletions(-) create mode 100644 zebra-chain/src/amount/zec.rs diff --git a/zebra-chain/src/amount.rs b/zebra-chain/src/amount.rs index e8b0d79a9ac..d5c46fc066c 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, @@ -15,6 +16,10 @@ use std::{ use crate::serialization::{ZcashDeserialize, ZcashSerialize}; use byteorder::{ByteOrder, LittleEndian, ReadBytesExt, WriteBytesExt}; +pub mod zec; + +pub use zec::Zec; + #[cfg(any(test, feature = "proptest-impl"))] pub mod arbitrary; @@ -25,6 +30,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 +51,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 +76,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 +413,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-chain/src/amount/zec.rs b/zebra-chain/src/amount/zec.rs new file mode 100644 index 00000000000..0b240c41950 --- /dev/null +++ b/zebra-chain/src/amount/zec.rs @@ -0,0 +1,211 @@ +//! ZEC-specific formatting, similar to the `zcashd` implementation. + +use std::{ + cmp::Ordering, + fmt, + hash::{Hash, Hasher}, + ops, + str::FromStr, +}; + +use serde_with::{DeserializeFromStr, SerializeDisplay}; + +use crate::{ + amount::{self, Amount, Constraint, COIN}, + BoxError, +}; + +/// 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_ZAT_PRECISION: usize = 8; + +/// A wrapper type that formats [`Amount`]s as ZEC, +/// using fixed-point integer calculations. +#[derive(Clone, Copy, SerializeDisplay, DeserializeFromStr)] +pub struct Zec(Amount); + +impl fmt::Display for Zec { + // We don't use floating-point here, because it is inaccurate + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let zats = self.zatoshis(); + + // Get the fixed-point ZEC and remaining zats values + let abs_coins = zats + .checked_abs() + .expect("-MAX_MONEY is much smaller than i64::MIN") + / COIN; + let positive_remainder = zats.rem_euclid(COIN); + + // Format just like `zcashd` by default + let decimals = f.precision().unwrap_or(MAX_ZAT_PRECISION); + let string = format!("{abs_coins}.{positive_remainder:.decimals$}"); + f.pad_integral(zats >= 0, "", &string) + } +} + +// This is mainly used in tests +impl FromStr for Zec { + type Err = BoxError; + + // We don't use floating-point here, because it is inaccurate + fn from_str(s: &str) -> Result { + // Get the fixed-point ZEC and remaining zats values + let (signed_coins, abs_remainder) = match s.trim().split_once('.') { + Some(("", positive_zats)) => ("0", positive_zats), + Some((signed_coins, "")) => (signed_coins, "0"), + Some((signed_coins, abs_remainder)) => (signed_coins, abs_remainder), + None => (s, "0"), + }; + + // Convert ZEC to integer + let signed_coins: i64 = signed_coins.parse()?; + + // Check for spurious + or - after the decimal point + if !abs_remainder + .chars() + .next() + .expect("just checked for empty strings") + .is_numeric() + { + return Err("invalid ZEC amount: fractional part must start with 0-9".into()); + } + + // Check for loss of precision, after removing trailing zeroes + let abs_remainder = abs_remainder.trim_end_matches('0'); + if abs_remainder.len() > MAX_ZAT_PRECISION { + return Err("loss of precision: ZEC value contains fractional zatoshis".into()); + } + + // Zero-pad to an amount in zats, then parse + let abs_remainder = + abs_remainder.to_owned() + &"0".repeat(MAX_ZAT_PRECISION - abs_remainder.len()); + let abs_remainder: u32 = abs_remainder.parse()?; + let abs_remainder: i64 = abs_remainder.into(); + assert!( + abs_remainder < COIN, + "unexpected parsing error: just checked zat value was less than one ZEC in length" + ); + + // Now convert everything to zatoshi amounts, with the correct sign + let zats = signed_coins + .checked_mul(COIN) + .ok_or("ZEC value too large")?; + let remainder = if zats > 0 { + abs_remainder + } else { + -abs_remainder + }; + + let mut zats: Amount = zats.try_into()?; + let remainder: Amount = remainder + .try_into() + .expect("already checked range and sign"); + + zats = (zats + remainder)?; + + Ok(Self(zats)) + } +} + +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 {} + +impl PartialOrd> for Zec { + fn partial_cmp(&self, other: &Zec) -> Option { + self.0.partial_cmp(&other.0) + } +} + +impl Ord for Zec { + fn cmp(&self, other: &Zec) -> Ordering { + self.0.cmp(&other.0) + } +} From 1920c0103d4a13e21ec41a46bb12f3ce6ae0de5e Mon Sep 17 00:00:00 2001 From: teor Date: Wed, 25 Jan 2023 13:01:36 +1000 Subject: [PATCH 04/15] Format getblocksubsidy fields in Zec --- zebra-rpc/src/methods/get_block_template_rpcs.rs | 4 ++-- .../get_block_template_rpcs/types/subsidy.rs | 13 +++++-------- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/zebra-rpc/src/methods/get_block_template_rpcs.rs b/zebra-rpc/src/methods/get_block_template_rpcs.rs index 4a549ef92e5..ce42f047698 100644 --- a/zebra-rpc/src/methods/get_block_template_rpcs.rs +++ b/zebra-rpc/src/methods/get_block_template_rpcs.rs @@ -812,8 +812,8 @@ where .collect(); Ok(BlockSubsidy { - miner, - founders, + miner: miner.into(), + founders: founders.into(), funding_streams, }) } 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 index e809fd88c8a..7d9c579a0a6 100644 --- a/zebra-rpc/src/methods/get_block_template_rpcs/types/subsidy.rs +++ b/zebra-rpc/src/methods/get_block_template_rpcs/types/subsidy.rs @@ -1,7 +1,7 @@ //! Types for the `getblocksubsidy` RPC. use zebra_chain::{ - amount::{Amount, NonNegative}, + amount::{Amount, NonNegative, Zec}, transparent, }; use zebra_consensus::{funding_stream_recipient_info, FundingStreamReceiver}; @@ -12,16 +12,13 @@ pub struct BlockSubsidy { /// The mining reward amount in ZEC. /// /// This does not include the miner fee. - // - // TODO: format ZEC amounts as fixed-point decimal, like `zcashd`: - // https://github.com/zcash/zcash/blob/f6a4f68115ea4c58d55c8538579d0877ba9c8f79/src/rpc/server.cpp#L127-L135 - pub miner: Amount, + 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: Amount, + pub founders: Zec, /// An array of funding stream descriptions. /// Always present, because Zebra returns an error for heights before the first halving. @@ -39,7 +36,7 @@ pub struct FundingStream { pub specification: String, /// The funding stream amount in ZEC. - pub value: Amount, + pub value: Zec, /// The funding stream amount in zatoshis. #[serde(rename = "valueZat")] @@ -64,7 +61,7 @@ impl FundingStream { FundingStream { recipient: recipient.to_string(), specification: specification.to_string(), - value, + value: value.into(), value_zat: value, address, } From 40ff553b5a597447c020a785ad2ad7b62cede3d4 Mon Sep 17 00:00:00 2001 From: teor Date: Wed, 25 Jan 2023 14:07:55 +1000 Subject: [PATCH 05/15] Add snapshot tests for getblocksubsidy RPC --- .../tests/snapshot/get_block_template_rpcs.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) 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 d7ef39bed9c..5c25d70ab9b 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)); From b550e6b50a0199777ae507e6d5225660fb264638 Mon Sep 17 00:00:00 2001 From: teor Date: Wed, 25 Jan 2023 14:17:16 +1000 Subject: [PATCH 06/15] Re-order RPC output to match zcashd --- .../methods/get_block_template_rpcs/types/subsidy.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) 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 index 7d9c579a0a6..03b19207699 100644 --- a/zebra-rpc/src/methods/get_block_template_rpcs/types/subsidy.rs +++ b/zebra-rpc/src/methods/get_block_template_rpcs/types/subsidy.rs @@ -9,6 +9,11 @@ use zebra_consensus::{funding_stream_recipient_info, FundingStreamReceiver}; /// 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. @@ -19,11 +24,6 @@ pub struct BlockSubsidy { /// Zebra returns an error when asked for founders reward heights, /// because it checkpoints those blocks instead. pub founders: Zec, - - /// 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, } /// A single funding stream's information in a `getblocksubsidy` RPC request From 9efb8f9cdd461ff28e2b7fbd1e7f64ccc6652ba5 Mon Sep 17 00:00:00 2001 From: teor Date: Wed, 25 Jan 2023 15:17:48 +1000 Subject: [PATCH 07/15] Switch to formatting Zec with f64, because the getblocksubsidy RPC requires JSON numbers --- zebra-chain/src/amount/zec.rs | 163 ++++++++++++++++++---------------- 1 file changed, 87 insertions(+), 76 deletions(-) diff --git a/zebra-chain/src/amount/zec.rs b/zebra-chain/src/amount/zec.rs index 0b240c41950..ffff85e8f69 100644 --- a/zebra-chain/src/amount/zec.rs +++ b/zebra-chain/src/amount/zec.rs @@ -1,4 +1,7 @@ -//! ZEC-specific formatting, similar to the `zcashd` implementation. +//! 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::{ cmp::Ordering, @@ -8,8 +11,6 @@ use std::{ str::FromStr, }; -use serde_with::{DeserializeFromStr, SerializeDisplay}; - use crate::{ amount::{self, Amount, Constraint, COIN}, BoxError, @@ -20,93 +21,103 @@ use crate::{ /// /// This is the same as the `getblocksubsidy` RPC in `zcashd`: /// -pub const MAX_ZAT_PRECISION: usize = 8; +pub const MAX_ZEC_FORMAT_PRECISION: usize = 8; -/// A wrapper type that formats [`Amount`]s as ZEC, -/// using fixed-point integer calculations. -#[derive(Clone, Copy, SerializeDisplay, DeserializeFromStr)] +/// 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, Serialize, Deserialize)] +#[serde(try_from = "f64")] +#[serde(into = "f64")] +#[serde(bound = "C: Constraint + Clone")] pub struct Zec(Amount); -impl fmt::Display for Zec { - // We don't use floating-point here, because it is inaccurate - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { +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(), + ); + } - // Get the fixed-point ZEC and remaining zats values - let abs_coins = zats - .checked_abs() - .expect("-MAX_MONEY is much smaller than i64::MIN") - / COIN; - let positive_remainder = zats.rem_euclid(COIN); + // We know this conversion is exact, because we just checked. + let zats = zats as i64; + let zats = Amount::try_from(zats)?; - // Format just like `zcashd` by default - let decimals = f.precision().unwrap_or(MAX_ZAT_PRECISION); - let string = format!("{abs_coins}.{positive_remainder:.decimals$}"); - f.pad_integral(zats >= 0, "", &string) + Ok(Self(zats)) } } -// This is mainly used in tests +// 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; - // We don't use floating-point here, because it is inaccurate fn from_str(s: &str) -> Result { - // Get the fixed-point ZEC and remaining zats values - let (signed_coins, abs_remainder) = match s.trim().split_once('.') { - Some(("", positive_zats)) => ("0", positive_zats), - Some((signed_coins, "")) => (signed_coins, "0"), - Some((signed_coins, abs_remainder)) => (signed_coins, abs_remainder), - None => (s, "0"), - }; - - // Convert ZEC to integer - let signed_coins: i64 = signed_coins.parse()?; - - // Check for spurious + or - after the decimal point - if !abs_remainder - .chars() - .next() - .expect("just checked for empty strings") - .is_numeric() - { - return Err("invalid ZEC amount: fractional part must start with 0-9".into()); - } - - // Check for loss of precision, after removing trailing zeroes - let abs_remainder = abs_remainder.trim_end_matches('0'); - if abs_remainder.len() > MAX_ZAT_PRECISION { - return Err("loss of precision: ZEC value contains fractional zatoshis".into()); - } - - // Zero-pad to an amount in zats, then parse - let abs_remainder = - abs_remainder.to_owned() + &"0".repeat(MAX_ZAT_PRECISION - abs_remainder.len()); - let abs_remainder: u32 = abs_remainder.parse()?; - let abs_remainder: i64 = abs_remainder.into(); - assert!( - abs_remainder < COIN, - "unexpected parsing error: just checked zat value was less than one ZEC in length" - ); - - // Now convert everything to zatoshi amounts, with the correct sign - let zats = signed_coins - .checked_mul(COIN) - .ok_or("ZEC value too large")?; - let remainder = if zats > 0 { - abs_remainder - } else { - -abs_remainder - }; - - let mut zats: Amount = zats.try_into()?; - let remainder: Amount = remainder - .try_into() - .expect("already checked range and sign"); - - zats = (zats + remainder)?; + let lossy_zec: f64 = s.parse()?; - Ok(Self(zats)) + Self::from_lossy_zec(lossy_zec) } } From e25a8dff00882846b99fb6d92773dfdf013ec38c Mon Sep 17 00:00:00 2001 From: teor Date: Wed, 25 Jan 2023 15:39:42 +1000 Subject: [PATCH 08/15] Sort RPC responses in zcashd funding stream order --- zebra-consensus/src/parameters/subsidy.rs | 3 +-- zebra-rpc/src/methods/get_block_template_rpcs.rs | 14 ++++++++++++-- .../methods/get_block_template_rpcs/constants.rs | 8 ++++++++ 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/zebra-consensus/src/parameters/subsidy.rs b/zebra-consensus/src/parameters/subsidy.rs index 7d56e66b5bd..a6b0d3808c6 100644 --- a/zebra-consensus/src/parameters/subsidy.rs +++ b/zebra-consensus/src/parameters/subsidy.rs @@ -71,8 +71,7 @@ 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`]. + /// 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 diff --git a/zebra-rpc/src/methods/get_block_template_rpcs.rs b/zebra-rpc/src/methods/get_block_template_rpcs.rs index ce42f047698..a6d7c867cfc 100644 --- a/zebra-rpc/src/methods/get_block_template_rpcs.rs +++ b/zebra-rpc/src/methods/get_block_template_rpcs.rs @@ -29,6 +29,7 @@ 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, @@ -803,14 +804,23 @@ where message: error.to_string(), data: None, })?; - let funding_streams = funding_streams + let mut funding_streams: Vec<_> = funding_streams .iter() .map(|(receiver, value)| { let address = funding_stream_address(height, network, *receiver); - FundingStream::new(*receiver, *value, address) + (*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(), 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 ed624e4dc83..8658f9299a1 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]; From 344dcf74c725be1a48c47abf6163e0d499a483eb Mon Sep 17 00:00:00 2001 From: teor Date: Wed, 25 Jan 2023 15:46:40 +1000 Subject: [PATCH 09/15] Add getblocksubsidy snapshots --- .../get_block_subsidy@mainnet_10.snap | 31 +++++++++++++++++++ .../get_block_subsidy@testnet_10.snap | 31 +++++++++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 zebra-rpc/src/methods/tests/snapshot/snapshots/get_block_subsidy@mainnet_10.snap create mode 100644 zebra-rpc/src/methods/tests/snapshot/snapshots/get_block_subsidy@testnet_10.snap 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 00000000000..023860146b1 --- /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 00000000000..ff14493b2ec --- /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 +} From cc55bda12378fe5bbfed8ea109ce3251a11041e4 Mon Sep 17 00:00:00 2001 From: teor Date: Fri, 27 Jan 2023 07:28:23 +1000 Subject: [PATCH 10/15] Fix a doc link --- zebra-chain/src/amount/zec.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/zebra-chain/src/amount/zec.rs b/zebra-chain/src/amount/zec.rs index ffff85e8f69..ebf24a11048 100644 --- a/zebra-chain/src/amount/zec.rs +++ b/zebra-chain/src/amount/zec.rs @@ -16,6 +16,10 @@ use crate::{ BoxError, }; +// Doc links only +#[allow(clippy::unused_imports)] +use crate::amount::MAX_MONEY; + /// The maximum precision of a zatoshi in ZEC. /// Also used as the default decimal precision for ZEC formatting. /// From 6d5e59f46563bdcb8be110fb4ce36dde5542f112 Mon Sep 17 00:00:00 2001 From: teor Date: Fri, 27 Jan 2023 07:34:25 +1000 Subject: [PATCH 11/15] Move Zec JSON formatter from zebra-chain to zebra-rpc --- zebra-chain/src/amount.rs | 4 ---- zebra-rpc/src/methods/get_block_template_rpcs/types.rs | 1 + .../src/methods/get_block_template_rpcs/types/subsidy.rs | 4 +++- .../src/methods/get_block_template_rpcs/types}/zec.rs | 9 ++++----- 4 files changed, 8 insertions(+), 10 deletions(-) rename {zebra-chain/src/amount => zebra-rpc/src/methods/get_block_template_rpcs/types}/zec.rs (98%) diff --git a/zebra-chain/src/amount.rs b/zebra-chain/src/amount.rs index d5c46fc066c..ce36c043172 100644 --- a/zebra-chain/src/amount.rs +++ b/zebra-chain/src/amount.rs @@ -16,10 +16,6 @@ use std::{ use crate::serialization::{ZcashDeserialize, ZcashSerialize}; use byteorder::{ByteOrder, LittleEndian, ReadBytesExt, WriteBytesExt}; -pub mod zec; - -pub use zec::Zec; - #[cfg(any(test, feature = "proptest-impl"))] pub mod arbitrary; 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 4914cc9dc1a..717072d9cdc 100644 --- a/zebra-rpc/src/methods/get_block_template_rpcs/types.rs +++ b/zebra-rpc/src/methods/get_block_template_rpcs/types.rs @@ -9,3 +9,4 @@ 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 index 03b19207699..51c63636cab 100644 --- a/zebra-rpc/src/methods/get_block_template_rpcs/types/subsidy.rs +++ b/zebra-rpc/src/methods/get_block_template_rpcs/types/subsidy.rs @@ -1,11 +1,13 @@ //! Types for the `getblocksubsidy` RPC. use zebra_chain::{ - amount::{Amount, NonNegative, Zec}, + 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 { diff --git a/zebra-chain/src/amount/zec.rs b/zebra-rpc/src/methods/get_block_template_rpcs/types/zec.rs similarity index 98% rename from zebra-chain/src/amount/zec.rs rename to zebra-rpc/src/methods/get_block_template_rpcs/types/zec.rs index ebf24a11048..65573d24d22 100644 --- a/zebra-chain/src/amount/zec.rs +++ b/zebra-rpc/src/methods/get_block_template_rpcs/types/zec.rs @@ -11,14 +11,13 @@ use std::{ str::FromStr, }; -use crate::{ - amount::{self, Amount, Constraint, COIN}, - BoxError, -}; +use zebra_chain::amount::{self, Amount, Constraint, COIN}; + +use zebra_node_services::BoxError; // Doc links only #[allow(clippy::unused_imports)] -use crate::amount::MAX_MONEY; +use zebra_chain::amount::MAX_MONEY; /// The maximum precision of a zatoshi in ZEC. /// Also used as the default decimal precision for ZEC formatting. From 69a2e623224cbae7e78d66307f3f5965d7bb4ae7 Mon Sep 17 00:00:00 2001 From: teor Date: Fri, 27 Jan 2023 07:34:58 +1000 Subject: [PATCH 12/15] Remove Ord impl for Zec, it's just for formatting --- .../methods/get_block_template_rpcs/types/zec.rs | 13 ------------- 1 file changed, 13 deletions(-) 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 index 65573d24d22..a96dd191e9a 100644 --- a/zebra-rpc/src/methods/get_block_template_rpcs/types/zec.rs +++ b/zebra-rpc/src/methods/get_block_template_rpcs/types/zec.rs @@ -4,7 +4,6 @@ //! The values themselves are accurate, but any calculations using them could be lossy. use std::{ - cmp::Ordering, fmt, hash::{Hash, Hasher}, ops, @@ -211,15 +210,3 @@ impl PartialEq> for Amount { } impl Eq for Zec {} - -impl PartialOrd> for Zec { - fn partial_cmp(&self, other: &Zec) -> Option { - self.0.partial_cmp(&other.0) - } -} - -impl Ord for Zec { - fn cmp(&self, other: &Zec) -> Ordering { - self.0.cmp(&other.0) - } -} From e7a0c8bb3acb3d558ebda9c06778e5f937ac6e5c Mon Sep 17 00:00:00 2001 From: teor Date: Fri, 27 Jan 2023 09:54:12 +1000 Subject: [PATCH 13/15] Use fully-qualified path for serde derives --- zebra-rpc/src/methods/get_block_template_rpcs/types/zec.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index a96dd191e9a..0b616c57b3d 100644 --- a/zebra-rpc/src/methods/get_block_template_rpcs/types/zec.rs +++ b/zebra-rpc/src/methods/get_block_template_rpcs/types/zec.rs @@ -37,7 +37,7 @@ pub const MAX_ZEC_FORMAT_PRECISION: usize = 8; /// 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, Serialize, Deserialize)] +#[derive(Clone, Copy, serde::Serialize, serde::Deserialize)] #[serde(try_from = "f64")] #[serde(into = "f64")] #[serde(bound = "C: Constraint + Clone")] From e07747c78031c3a6b6418540bbdb922cbd174eb6 Mon Sep 17 00:00:00 2001 From: teor Date: Fri, 27 Jan 2023 10:13:56 +1000 Subject: [PATCH 14/15] Fix a clippy warning for the doc link fix --- zebra-rpc/src/methods/get_block_template_rpcs/types/zec.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index 0b616c57b3d..7aea6da72bf 100644 --- a/zebra-rpc/src/methods/get_block_template_rpcs/types/zec.rs +++ b/zebra-rpc/src/methods/get_block_template_rpcs/types/zec.rs @@ -15,7 +15,7 @@ use zebra_chain::amount::{self, Amount, Constraint, COIN}; use zebra_node_services::BoxError; // Doc links only -#[allow(clippy::unused_imports)] +#[allow(unused_imports)] use zebra_chain::amount::MAX_MONEY; /// The maximum precision of a zatoshi in ZEC. From 4c9d14804013392fc9406e0ba8dfa8a140646390 Mon Sep 17 00:00:00 2001 From: teor Date: Mon, 30 Jan 2023 07:33:17 +1000 Subject: [PATCH 15/15] Fix RPC comments and an error message Co-authored-by: Alfredo Garcia --- zebra-rpc/src/methods/get_block_template_rpcs.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/zebra-rpc/src/methods/get_block_template_rpcs.rs b/zebra-rpc/src/methods/get_block_template_rpcs.rs index a6d7c867cfc..7b48351e6b9 100644 --- a/zebra-rpc/src/methods/get_block_template_rpcs.rs +++ b/zebra-rpc/src/methods/get_block_template_rpcs.rs @@ -167,8 +167,8 @@ pub trait GetBlockTemplateRpc { #[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 and the founders reward. + /// 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. @@ -783,7 +783,7 @@ where if height < height_for_first_halving(network) { return Err(Error { code: ErrorCode::ServerError(0), - message: "Zebra does not support funding stream subsidies, \ + message: "Zebra does not support founders' reward subsidies, \ use a block height that is after the first halving" .into(), data: None,