feat(rpc): Implement the `getblocksubsidy` RPC (#6032)
* Make it clearer that Zebra only supports transparent funding streams * Initial getblocksubsidy RPC types and method, without ZEC conversion * Add a ZEC fixed-point format wrapper to Amount * Format getblocksubsidy fields in Zec * Add snapshot tests for getblocksubsidy RPC * Re-order RPC output to match zcashd * Switch to formatting Zec with f64, because the getblocksubsidy RPC requires JSON numbers * Sort RPC responses in zcashd funding stream order * Add getblocksubsidy snapshots * Fix a doc link * Move Zec JSON formatter from zebra-chain to zebra-rpc * Remove Ord impl for Zec, it's just for formatting * Use fully-qualified path for serde derives * Fix a clippy warning for the doc link fix * Fix RPC comments and an error message Co-authored-by: Alfredo Garcia <oxarbitrage@gmail.com> --------- Co-authored-by: Alfredo Garcia <oxarbitrage@gmail.com>
This commit is contained in:
parent
4167380f58
commit
4dd9426e48
|
@ -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<T, E = Error> = std::result::Result<T, E>;
|
||||
|
||||
/// 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<C = NegativeAllowed>(
|
|||
PhantomData<C>,
|
||||
);
|
||||
|
||||
impl<C> std::fmt::Debug for Amount<C> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
impl<C> fmt::Display for Amount<C> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let zats = self.zatoshis();
|
||||
|
||||
f.pad_integral(zats > 0, "", &zats.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl<C> fmt::Debug for Amount<C> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_tuple(&format!("Amount<{}>", std::any::type_name::<C>()))
|
||||
.field(&self.0)
|
||||
.finish()
|
||||
|
@ -59,6 +72,11 @@ impl<C> Amount<C> {
|
|||
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<C>> for Result<Amount<C>>
|
||||
where
|
||||
C: Constraint + std::marker::Copy + 'amt,
|
||||
C: Constraint + Copy + 'amt,
|
||||
{
|
||||
fn sum<I: Iterator<Item = &'amt Amount<C>>>(iter: I) -> Self {
|
||||
iter.copied().sum()
|
||||
|
|
|
@ -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<Output> {
|
||||
/// 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<transparent::Output> {
|
||||
transaction
|
||||
.outputs()
|
||||
.iter()
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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<FundingStreamReceiver, &'static str> = {
|
||||
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].
|
||||
///
|
||||
|
|
|
@ -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<Result<Vec<PeerInfo>>>;
|
||||
|
||||
/// 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<u32>) -> BoxFuture<Result<BlockSubsidy>>;
|
||||
}
|
||||
|
||||
/// RPC method implementations.
|
||||
|
@ -748,6 +768,67 @@ where
|
|||
}
|
||||
.boxed()
|
||||
}
|
||||
|
||||
fn get_block_subsidy(&self, height: Option<u32>) -> BoxFuture<Result<BlockSubsidy>> {
|
||||
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.
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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<FundingStream>,
|
||||
|
||||
/// The mining reward amount in ZEC.
|
||||
///
|
||||
/// This does not include the miner fee.
|
||||
pub miner: Zec<NonNegative>,
|
||||
|
||||
/// 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<NonNegative>,
|
||||
}
|
||||
|
||||
/// 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<NonNegative>,
|
||||
|
||||
/// The funding stream amount in zatoshis.
|
||||
#[serde(rename = "valueZat")]
|
||||
pub value_zat: Amount<NonNegative>,
|
||||
|
||||
/// 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<NonNegative>,
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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`:
|
||||
/// <https://github.com/zcash/zcash/blob/f6a4f68115ea4c58d55c8538579d0877ba9c8f79/src/rpc/server.cpp#L134>
|
||||
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:
|
||||
/// <https://github.com/zcash/zcash/blob/f6a4f68115ea4c58d55c8538579d0877ba9c8f79/src/rpc/server.cpp#L134>
|
||||
#[derive(Clone, Copy, serde::Serialize, serde::Deserialize)]
|
||||
#[serde(try_from = "f64")]
|
||||
#[serde(into = "f64")]
|
||||
#[serde(bound = "C: Constraint + Clone")]
|
||||
pub struct Zec<C: Constraint>(Amount<C>);
|
||||
|
||||
impl<C: Constraint> Zec<C> {
|
||||
/// 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<Self, BoxError> {
|
||||
// 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<C: Constraint> From<Zec<C>> for f64 {
|
||||
fn from(zec: Zec<C>) -> f64 {
|
||||
zec.lossy_zec()
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: Constraint> TryFrom<f64> for Zec<C> {
|
||||
type Error = BoxError;
|
||||
|
||||
fn try_from(value: f64) -> Result<Self, Self::Error> {
|
||||
Self::from_lossy_zec(value)
|
||||
}
|
||||
}
|
||||
|
||||
// This formatter should not be used for consensus-critical outputs.
|
||||
impl<C: Constraint> fmt::Display for Zec<C> {
|
||||
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<C: Constraint> FromStr for Zec<C> {
|
||||
type Err = BoxError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let lossy_zec: f64 = s.parse()?;
|
||||
|
||||
Self::from_lossy_zec(lossy_zec)
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: Constraint> std::fmt::Debug for Zec<C> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct(&format!("Zec<{}>", std::any::type_name::<C>()))
|
||||
.field("ZEC", &self.to_string())
|
||||
.field("zat", &self.0)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: Constraint> ops::Deref for Zec<C> {
|
||||
type Target = Amount<C>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: Constraint> ops::DerefMut for Zec<C> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: Constraint> From<Amount<C>> for Zec<C> {
|
||||
fn from(amount: Amount<C>) -> Self {
|
||||
Self(amount)
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: Constraint> From<Zec<C>> for Amount<C> {
|
||||
fn from(zec: Zec<C>) -> Amount<C> {
|
||||
zec.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: Constraint> From<Zec<C>> for i64 {
|
||||
fn from(zec: Zec<C>) -> i64 {
|
||||
zec.0.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: Constraint> TryFrom<i64> for Zec<C> {
|
||||
type Error = amount::Error;
|
||||
|
||||
fn try_from(value: i64) -> Result<Self, Self::Error> {
|
||||
Ok(Self(Amount::try_from(value)?))
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: Constraint> Hash for Zec<C> {
|
||||
/// Zecs with the same value are equal, even if they have different constraints
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.0.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl<C1: Constraint, C2: Constraint> PartialEq<Zec<C2>> for Zec<C1> {
|
||||
fn eq(&self, other: &Zec<C2>) -> bool {
|
||||
self.0.eq(&other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: Constraint> PartialEq<i64> for Zec<C> {
|
||||
fn eq(&self, other: &i64) -> bool {
|
||||
self.0.eq(other)
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: Constraint> PartialEq<Zec<C>> for i64 {
|
||||
fn eq(&self, other: &Zec<C>) -> bool {
|
||||
self.eq(&other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl<C1: Constraint, C2: Constraint> PartialEq<Amount<C2>> for Zec<C1> {
|
||||
fn eq(&self, other: &Amount<C2>) -> bool {
|
||||
self.0.eq(other)
|
||||
}
|
||||
}
|
||||
|
||||
impl<C1: Constraint, C2: Constraint> PartialEq<Zec<C2>> for Amount<C1> {
|
||||
fn eq(&self, other: &Zec<C2>) -> bool {
|
||||
self.eq(&other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: Constraint> Eq for Zec<C> {}
|
|
@ -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<State, ReadState>(
|
|||
.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<PeerInfo>, settings: &insta::Settings) {
|
||||
settings.bind(|| insta::assert_json_snapshot!("get_peer_info", get_peer_info));
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
Loading…
Reference in New Issue