Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(rpc): Implement the getblocksubsidy RPC #6032

Merged
merged 15 commits into from
Jan 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 21 additions & 3 deletions zebra-chain/src/amount.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

use std::{
cmp::Ordering,
fmt,
hash::{Hash, Hasher},
marker::PhantomData,
ops::RangeInclusive,
Expand All @@ -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")]
Expand All @@ -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()
Expand All @@ -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];
Expand Down Expand Up @@ -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()
Expand Down
31 changes: 24 additions & 7 deletions zebra-consensus/src/block/subsidy/funding_streams.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::*};
Expand Down Expand Up @@ -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} \
Expand All @@ -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} \
Expand All @@ -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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
5 changes: 4 additions & 1 deletion zebra-consensus/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
20 changes: 20 additions & 0 deletions zebra-consensus/src/parameters/subsidy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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].
///
Expand Down
87 changes: 84 additions & 3 deletions zebra-rpc/src/methods/get_block_template_rpcs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,18 @@ 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,
parameters::Network,
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};
Expand All @@ -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,
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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.
8 changes: 8 additions & 0 deletions zebra-rpc/src/methods/get_block_template_rpcs/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.)
///
Expand Down Expand Up @@ -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];
2 changes: 2 additions & 0 deletions zebra-rpc/src/methods/get_block_template_rpcs/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Loading