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:
teor 2023-02-01 06:41:34 +10:00 committed by GitHub
parent 4167380f58
commit 4dd9426e48
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 524 additions and 15 deletions

View File

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

View File

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

View File

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

View File

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

View File

@ -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].
///

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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