change(rpc): Modify `getblocksubsidy` for NU6 (#8742)

* set testnet activation height

* add lockbox object to getblocksubsidy RPC method

* add totals to getblocksubsidy

* fix comment

* use vectors instead of options for arrays

* update getblocksubsidy

* match zcashd output

* fix network consistency check for nu6 testnet

* doc changes

* fix typo

* change `is_nu6`

* Suggestion for "change(rpc): Modify `getblocksubsidy` for NU6" (#8766)

* refactors get_block_subsidy RPC method

* replaces a `Default` impl with derived impls

* removes NU6 testnet activation height

* updates snapshot test to use a configured Testnet for the post-NU6 getblocksubsidy output

* fixes snapshot test

* fixes snapshot

* Add a snapshot of getblockchaininfo with a future nu6 height

---------

Co-authored-by: Pili Guerra <mpguerra@users.noreply.github.com>
Co-authored-by: Arya <aryasolhi@gmail.com>
This commit is contained in:
Alfredo Garcia 2024-08-15 19:51:14 -03:00 committed by GitHub
parent 9dfb14365f
commit 11b0833374
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 245 additions and 87 deletions

View File

@ -69,24 +69,34 @@ pub enum FundingStreamReceiver {
impl FundingStreamReceiver {
/// Returns a human-readable name and a specification URL for the receiver, as described in
/// [ZIP-1014] and [`zcashd`].
/// [ZIP-1014] and [`zcashd`] before NU6. After NU6, the specification is in the [ZIP-lockbox].
///
/// [ZIP-1014]: https://zips.z.cash/zip-1014#abstract
/// [`zcashd`]: https://github.com/zcash/zcash/blob/3f09cfa00a3c90336580a127e0096d99e25a38d6/src/consensus/funding.cpp#L13-L32
// TODO: Update method documentation with a reference to https://zips.z.cash/draft-nuttycom-funding-allocation once its
// status is updated to 'Proposed'.
pub fn info(&self) -> (&'static str, &'static str) {
/// [ZIP-lockbox]: https://zips.z.cash/draft-nuttycom-funding-allocation#alternative-2-hybrid-deferred-dev-fund-transitioning-to-a-non-direct-funding-model
pub fn info(&self, is_nu6: bool) -> (&'static str, &'static str) {
if is_nu6 {
(
match self {
FundingStreamReceiver::Ecc => "Electric Coin Company",
FundingStreamReceiver::ZcashFoundation => "Zcash Foundation",
FundingStreamReceiver::MajorGrants => "Zcash Community Grants NU6",
FundingStreamReceiver::Deferred => "Lockbox NU6",
},
LOCKBOX_SPECIFICATION,
)
} else {
(
match self {
FundingStreamReceiver::Ecc => "Electric Coin Company",
FundingStreamReceiver::ZcashFoundation => "Zcash Foundation",
FundingStreamReceiver::MajorGrants => "Major Grants",
// TODO: Find out what this should be called and update the funding stream name
FundingStreamReceiver::Deferred => "Lockbox",
FundingStreamReceiver::Deferred => "Lockbox NU6",
},
FUNDING_STREAM_SPECIFICATION,
)
}
}
}
/// Denominator as described in [protocol specification §7.10.1][7.10.1].
@ -94,12 +104,16 @@ impl FundingStreamReceiver {
/// [7.10.1]: https://zips.z.cash/protocol/protocol.pdf#zip214fundingstreams
pub const FUNDING_STREAM_RECEIVER_DENOMINATOR: u64 = 100;
// TODO: Update the link for post-NU6 funding streams.
/// The specification for all current funding stream receivers, a URL that links to [ZIP-214].
/// The specification for pre-NU6 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";
/// The specification for post-NU6 funding stream and lockbox receivers, a URL that links to [ZIP-lockbox].
///
/// [ZIP-lockbox]: https://zips.z.cash/draft-nuttycom-funding-allocation#alternative-2-hybrid-deferred-dev-fund-transitioning-to-a-non-direct-funding-model
pub const LOCKBOX_SPECIFICATION: &str = "https://zips.z.cash/draft-nuttycom-funding-allocation#alternative-2-hybrid-deferred-dev-fund-transitioning-to-a-non-direct-funding-model";
/// Funding stream recipients and height ranges.
#[derive(Deserialize, Clone, Debug, Eq, PartialEq)]
pub struct FundingStreams {

View File

@ -125,8 +125,7 @@ pub(super) const TESTNET_ACTIVATION_HEIGHTS: &[(block::Height, NetworkUpgrade)]
(block::Height(903_800), Heartwood),
(block::Height(1_028_500), Canopy),
(block::Height(1_842_420), Nu5),
// TODO: Add NU6
// (block::Height(2_942_000), Nu6),
// TODO: Add NU6 activation height once it's been specified.
];
/// Fake testnet network upgrade activation heights, used in tests.

View File

@ -194,7 +194,7 @@ mod test {
let _init_guard = zebra_test::init();
let highest_network_upgrade = NetworkUpgrade::current(network, block::Height::MAX);
assert!(highest_network_upgrade == Nu5 || highest_network_upgrade == Canopy,
assert!(highest_network_upgrade == Nu6 || highest_network_upgrade == Nu5,
"expected coverage of all network upgrades: add the new network upgrade to the list in this test");
for &network_upgrade in &[
@ -205,6 +205,7 @@ mod test {
Heartwood,
Canopy,
Nu5,
Nu6,
] {
let height = network_upgrade.activation_height(network);
if let Some(height) = height {

View File

@ -10,11 +10,14 @@ use tower::{Service, ServiceExt};
use zcash_address::{unified::Encoding, TryFromAddress};
use zebra_chain::{
amount::Amount,
amount::{self, Amount, NonNegative},
block::{self, Block, Height, TryIntoHeight},
chain_sync_status::ChainSyncStatus,
chain_tip::ChainTip,
parameters::{subsidy::ParameterSubsidy, Network, NetworkKind, POW_AVERAGING_WINDOW},
parameters::{
subsidy::{FundingStreamReceiver, ParameterSubsidy},
Network, NetworkKind, NetworkUpgrade, POW_AVERAGING_WINDOW,
},
primitives,
serialization::ZcashDeserializeInto,
transparent::{
@ -31,6 +34,7 @@ use zebra_state::{ReadRequest, ReadResponse};
use crate::methods::{
best_chain_tip_height,
errors::MapServerError,
get_block_template_rpcs::{
constants::{
DEFAULT_SOLUTION_RATE_WINDOW_SIZE, GET_BLOCK_TEMPLATE_MEMPOOL_LONG_POLL_INTERVAL,
@ -1178,35 +1182,27 @@ where
});
}
let expected_block_subsidy =
block_subsidy(height, &network).map_err(|error| Error {
code: ErrorCode::ServerError(0),
message: error.to_string(),
data: None,
})?;
let miner_subsidy =
miner_subsidy(height, &network, expected_block_subsidy).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, expected_block_subsidy)
.map_err(|error| Error {
code: ErrorCode::ServerError(0),
message: error.to_string(),
data: None,
})?;
let mut funding_streams: Vec<_> = funding_streams
.iter()
.filter_map(|(receiver, value)| {
let address = funding_stream_address(height, &network, *receiver)?;
Some((*receiver, FundingStream::new(*receiver, *value, address)))
})
.collect();
let total_block_subsidy = block_subsidy(height, &network).map_server_error()?;
let miner_subsidy =
miner_subsidy(height, &network, total_block_subsidy).map_server_error()?;
let (lockbox_streams, mut funding_streams): (Vec<_>, Vec<_>) =
funding_stream_values(height, &network, total_block_subsidy)
.map_server_error()?
.into_iter()
// Separate the funding streams into deferred and non-deferred streams
.partition(|(receiver, _)| matches!(receiver, FundingStreamReceiver::Deferred));
let is_nu6 = NetworkUpgrade::current(&network, height) == NetworkUpgrade::Nu6;
let [lockbox_total, funding_streams_total]: [std::result::Result<
Amount<NonNegative>,
amount::Error,
>; 2] = [&lockbox_streams, &funding_streams]
.map(|streams| streams.iter().map(|&(_, amount)| amount).sum());
// Use the same funding stream order as zcashd
funding_streams.sort_by_key(|(receiver, _funding_stream)| {
@ -1215,12 +1211,26 @@ where
.position(|zcashd_receiver| zcashd_receiver == receiver)
});
let (_receivers, funding_streams): (Vec<_>, _) = funding_streams.into_iter().unzip();
// Format the funding streams and lockbox streams
let [funding_streams, lockbox_streams]: [Vec<_>; 2] =
[funding_streams, lockbox_streams].map(|streams| {
streams
.into_iter()
.map(|(receiver, value)| {
let address = funding_stream_address(height, &network, receiver);
FundingStream::new(is_nu6, receiver, value, address)
})
.collect()
});
Ok(BlockSubsidy {
miner: miner_subsidy.into(),
founders: founders.into(),
funding_streams,
lockbox_streams,
funding_streams_total: funding_streams_total.map_server_error()?.into(),
lockbox_total: lockbox_total.map_server_error()?.into(),
total_block_subsidy: total_block_subsidy.into(),
})
}
.boxed()

View File

@ -9,13 +9,18 @@ use zebra_chain::{
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)]
#[derive(Clone, Debug, PartialEq, Eq, Default, 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")]
/// Always present before NU6, because Zebra returns an error for heights before the first halving.
#[serde(rename = "fundingstreams", skip_serializing_if = "Vec::is_empty")]
pub funding_streams: Vec<FundingStream>,
/// An array of lockbox stream descriptions.
/// Always present after NU6.
#[serde(rename = "lockboxstreams", skip_serializing_if = "Vec::is_empty")]
pub lockbox_streams: Vec<FundingStream>,
/// The mining reward amount in ZEC.
///
/// This does not include the miner fee.
@ -26,16 +31,20 @@ pub struct BlockSubsidy {
/// Zebra returns an error when asked for founders reward heights,
/// because it checkpoints those blocks instead.
pub founders: Zec<NonNegative>,
}
impl Default for BlockSubsidy {
fn default() -> Self {
Self {
funding_streams: vec![],
miner: Zec::from_lossy_zec(0.0).unwrap(),
founders: Zec::from_lossy_zec(0.0).unwrap(),
}
}
/// The total funding stream amount in ZEC.
#[serde(rename = "fundingstreamstotal")]
pub funding_streams_total: Zec<NonNegative>,
/// The total lockbox stream amount in ZEC.
#[serde(rename = "lockboxtotal")]
pub lockbox_total: Zec<NonNegative>,
/// The total block subsidy amount in ZEC.
///
/// This does not include the miner fee.
#[serde(rename = "totalblocksubsidy")]
pub total_block_subsidy: Zec<NonNegative>,
}
/// A single funding stream's information in a `getblocksubsidy` RPC request
@ -58,24 +67,28 @@ pub struct FundingStream {
///
/// The current Zcash funding streams only use transparent addresses,
/// so Zebra doesn't support Sapling addresses in this RPC.
pub address: transparent::Address,
///
/// This is optional so we can support funding streams with no addresses (lockbox streams).
#[serde(skip_serializing_if = "Option::is_none")]
pub address: Option<transparent::Address>,
}
impl FundingStream {
/// Convert a `receiver`, `value`, and `address` into a `FundingStream` response.
pub fn new(
is_nu6: bool,
receiver: FundingStreamReceiver,
value: Amount<NonNegative>,
address: &transparent::Address,
address: Option<&transparent::Address>,
) -> FundingStream {
let (name, specification) = receiver.info();
let (name, specification) = receiver.info(is_nu6);
FundingStream {
recipient: name.to_string(),
specification: specification.to_string(),
value: value.into(),
value_zat: value,
address: address.clone(),
address: address.cloned(),
}
}
}

View File

@ -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:
/// <https://github.com/zcash/zcash/blob/f6a4f68115ea4c58d55c8538579d0877ba9c8f79/src/rpc/server.cpp#L134>
#[derive(Clone, Copy, serde::Serialize, serde::Deserialize)]
#[derive(Clone, Copy, serde::Serialize, serde::Deserialize, Default)]
#[serde(try_from = "f64")]
#[serde(into = "f64")]
#[serde(bound = "C: Constraint + Clone")]

View File

@ -15,7 +15,8 @@ use zebra_chain::{
chain_tip::mock::MockChainTip,
orchard,
parameters::{
testnet::{ConfiguredActivationHeights, Parameters},
subsidy::POST_NU6_FUNDING_STREAMS_TESTNET,
testnet::{self, ConfiguredActivationHeights, Parameters},
Network::Mainnet,
},
sapling,
@ -40,10 +41,19 @@ pub const EXCESSIVE_BLOCK_HEIGHT: u32 = MAX_ON_DISK_HEIGHT.0 + 1;
async fn test_rpc_response_data() {
let _init_guard = zebra_test::init();
let default_testnet = Network::new_default_testnet();
let nu6_testnet = testnet::Parameters::build()
.with_network_name("NU6Testnet")
.with_activation_heights(ConfiguredActivationHeights {
blossom: Some(584_000),
nu6: Some(POST_NU6_FUNDING_STREAMS_TESTNET.height_range().start.0),
..Default::default()
})
.to_network();
tokio::join!(
test_rpc_response_data_for_network(&Mainnet),
test_rpc_response_data_for_network(&default_testnet),
test_rpc_response_data_for_network(&nu6_testnet),
test_mocked_rpc_response_data_for_network(&Mainnet),
test_mocked_rpc_response_data_for_network(&default_testnet),
);
@ -196,6 +206,16 @@ async fn test_rpc_response_data_for_network(network: &Network) {
latest_chain_tip,
);
// We only want a snapshot of the `getblocksubsidy` and `getblockchaininfo` methods for the non-default Testnet (with an NU6 activation height).
if network.is_a_test_network() && !network.is_default_testnet() {
let get_blockchain_info = rpc
.get_blockchain_info()
.expect("We should have a GetBlockChainInfo struct");
snapshot_rpc_getblockchaininfo("_future_nu6_height", get_blockchain_info, &settings);
return;
}
// `getinfo`
let get_info = rpc.get_info().expect("We should have a GetInfo struct");
snapshot_rpc_getinfo(get_info, &settings);
@ -204,7 +224,7 @@ async fn test_rpc_response_data_for_network(network: &Network) {
let get_blockchain_info = rpc
.get_blockchain_info()
.expect("We should have a GetBlockChainInfo struct");
snapshot_rpc_getblockchaininfo(get_blockchain_info, &settings);
snapshot_rpc_getblockchaininfo("", get_blockchain_info, &settings);
// get the first transaction of the first block which is not the genesis
let first_block_first_transaction = &blocks[1].transactions[0];
@ -531,9 +551,13 @@ fn snapshot_rpc_getinfo(info: GetInfo, settings: &insta::Settings) {
}
/// Snapshot `getblockchaininfo` response, using `cargo insta` and JSON serialization.
fn snapshot_rpc_getblockchaininfo(info: GetBlockChainInfo, settings: &insta::Settings) {
fn snapshot_rpc_getblockchaininfo(
variant_suffix: &str,
info: GetBlockChainInfo,
settings: &insta::Settings,
) {
settings.bind(|| {
insta::assert_json_snapshot!("get_blockchain_info", info, {
insta::assert_json_snapshot!(format!("get_blockchain_info{variant_suffix}"), info, {
".estimatedheight" => dynamic_redaction(|value, _path| {
// assert that the value looks like a valid height here
assert!(u32::try_from(value.as_u64().unwrap()).unwrap() < Height::MAX_AS_U32);

View File

@ -148,6 +148,18 @@ pub async fn test_responses<State, ReadState>(
mock_address_book,
);
if network.is_a_test_network() && !network.is_default_testnet() {
let fake_future_nu6_block_height =
NetworkUpgrade::Nu6.activation_height(network).unwrap().0 + 100_000;
let get_block_subsidy = get_block_template_rpc
.get_block_subsidy(Some(fake_future_nu6_block_height))
.await
.expect("We should have a success response");
snapshot_rpc_getblocksubsidy("future_nu6_height", get_block_subsidy, &settings);
// We only want a snapshot of the `getblocksubsidy` method for the non-default Testnet (with an NU6 activation height).
return;
}
// `getblockcount`
let get_block_count = get_block_template_rpc
.get_block_count()

View File

@ -3,7 +3,9 @@ source: zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs
expression: get_block_subsidy
---
{
"fundingstreams": [],
"miner": 0.00610351,
"founders": 0.0
"founders": 0.0,
"fundingstreamstotal": 0.0,
"lockboxtotal": 0.0,
"totalblocksubsidy": 0.00610351
}

View File

@ -3,7 +3,9 @@ source: zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs
expression: get_block_subsidy
---
{
"fundingstreams": [],
"miner": 0.00610351,
"founders": 0.0
"founders": 0.0,
"fundingstreamstotal": 0.0,
"lockboxtotal": 0.0,
"totalblocksubsidy": 0.00610351
}

View File

@ -27,5 +27,8 @@ expression: get_block_subsidy
}
],
"miner": 2.5,
"founders": 0.0
"founders": 0.0,
"fundingstreamstotal": 0.625,
"lockboxtotal": 0.0,
"totalblocksubsidy": 3.125
}

View File

@ -27,5 +27,8 @@ expression: get_block_subsidy
}
],
"miner": 2.5,
"founders": 0.0
"founders": 0.0,
"fundingstreamstotal": 0.625,
"lockboxtotal": 0.0,
"totalblocksubsidy": 3.125
}

View File

@ -0,0 +1,28 @@
---
source: zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs
expression: get_block_subsidy
---
{
"fundingstreams": [
{
"recipient": "Zcash Community Grants NU6",
"specification": "https://zips.z.cash/draft-nuttycom-funding-allocation#alternative-2-hybrid-deferred-dev-fund-transitioning-to-a-non-direct-funding-model",
"value": 0.125,
"valueZat": 12500000,
"address": "t2HifwjUj9uyxr9bknR8LFuQbc98c3vkXtu"
}
],
"lockboxstreams": [
{
"recipient": "Lockbox NU6",
"specification": "https://zips.z.cash/draft-nuttycom-funding-allocation#alternative-2-hybrid-deferred-dev-fund-transitioning-to-a-non-direct-funding-model",
"value": 0.1875,
"valueZat": 18750000
}
],
"miner": 1.25,
"founders": 0.0,
"fundingstreamstotal": 0.125,
"lockboxtotal": 0.1875,
"totalblocksubsidy": 1.5625
}

View File

@ -27,5 +27,8 @@ expression: get_block_subsidy
}
],
"miner": 2.5,
"founders": 0.0
"founders": 0.0,
"fundingstreamstotal": 0.625,
"lockboxtotal": 0.0,
"totalblocksubsidy": 3.125
}

View File

@ -27,5 +27,8 @@ expression: get_block_subsidy
}
],
"miner": 2.5,
"founders": 0.0
"founders": 0.0,
"fundingstreamstotal": 0.625,
"lockboxtotal": 0.0,
"totalblocksubsidy": 3.125
}

View File

@ -1,10 +0,0 @@
---
source: zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs
expression: difficulty
---
{
"Err": {
"code": 0,
"message": "Zebra's state only has a few blocks, wait until it syncs to the chain tip"
}
}

View File

@ -0,0 +1,51 @@
---
source: zebra-rpc/src/methods/tests/snapshot.rs
expression: info
---
{
"chain": "test",
"blocks": 10,
"bestblockhash": "079f4c752729be63e6341ee9bce42fbbe37236aba22e3deb82405f3c2805c112",
"estimatedheight": "[Height]",
"upgrades": {
"5ba81b19": {
"name": "Overwinter",
"activationheight": 584000,
"status": "pending"
},
"76b809bb": {
"name": "Sapling",
"activationheight": 584000,
"status": "pending"
},
"2bb40e60": {
"name": "Blossom",
"activationheight": 584000,
"status": "pending"
},
"f5b9230b": {
"name": "Heartwood",
"activationheight": 2942000,
"status": "pending"
},
"e9ff75a6": {
"name": "Canopy",
"activationheight": 2942000,
"status": "pending"
},
"c2d6d0b4": {
"name": "NU5",
"activationheight": 2942000,
"status": "pending"
},
"c8e71055": {
"name": "Nu6",
"activationheight": 2942000,
"status": "pending"
}
},
"consensus": {
"chaintip": "00000000",
"nextblock": "00000000"
}
}