zebra/zebra-consensus/src/block/subsidy/funding_streams.rs

170 lines
6.7 KiB
Rust

//! Funding Streams calculations. - [§7.8][7.8]
//!
//! [7.8]: https://zips.z.cash/protocol/protocol.pdf#subsidies
use std::{collections::HashMap, str::FromStr};
use zebra_chain::{
amount::{Amount, Error, NonNegative},
block::Height,
parameters::{Network, NetworkUpgrade::*},
transaction::Transaction,
transparent::{self, Script},
};
use crate::{block::subsidy::general::block_subsidy, parameters::subsidy::*};
#[cfg(test)]
mod tests;
/// Returns the `fs.Value(height)` for each stream receiver
/// as described in [protocol specification §7.8][7.8]
///
/// [7.8]: https://zips.z.cash/protocol/protocol.pdf#subsidies
pub fn funding_stream_values(
height: Height,
network: Network,
) -> Result<HashMap<FundingStreamReceiver, Amount<NonNegative>>, Error> {
let canopy_height = Canopy.activation_height(network).unwrap();
let mut results = HashMap::new();
if height >= canopy_height {
let range = FUNDING_STREAM_HEIGHT_RANGES.get(&network).unwrap();
if range.contains(&height) {
let block_subsidy = block_subsidy(height, network)?;
for (&receiver, &numerator) in FUNDING_STREAM_RECEIVER_NUMERATORS.iter() {
// - Spec equation: `fs.value = floor(block_subsidy(height)*(fs.numerator/fs.denominator))`:
// https://zips.z.cash/protocol/protocol.pdf#subsidies
// - In Rust, "integer division rounds towards zero":
// https://doc.rust-lang.org/stable/reference/expressions/operator-expr.html#arithmetic-and-logical-binary-operators
// This is the same as `floor()`, because these numbers are all positive.
let amount_value =
((block_subsidy * numerator)? / FUNDING_STREAM_RECEIVER_DENOMINATOR)?;
results.insert(receiver, amount_value);
}
}
}
Ok(results)
}
/// Returns the address change period
/// as described in [protocol specification §7.10][7.10]
///
/// [7.10]: https://zips.z.cash/protocol/protocol.pdf#fundingstreams
fn funding_stream_address_period(height: Height, network: Network) -> u32 {
// Spec equation: `address_period = floor((height - (height_for_halving(1) - post_blossom_halving_interval))/funding_stream_address_change_interval)`,
// <https://zips.z.cash/protocol/protocol.pdf#fundingstreams>
//
// Note that the brackets make it so the post blossom halving interval is added to the total.
//
// In Rust, "integer division rounds towards zero":
// <https://doc.rust-lang.org/stable/reference/expressions/operator-expr.html#arithmetic-and-logical-binary-operators>
// This is the same as `floor()`, because these numbers are all positive.
let height_after_first_halving = height - network.height_for_first_halving();
let address_period = (height_after_first_halving + POST_BLOSSOM_HALVING_INTERVAL)
/ FUNDING_STREAM_ADDRESS_CHANGE_INTERVAL;
address_period
.try_into()
.expect("all values are positive and smaller than the input height")
}
/// Returns the position in the address slice for each funding stream
/// as described in [protocol specification §7.10][7.10]
///
/// [7.10]: https://zips.z.cash/protocol/protocol.pdf#fundingstreams
fn funding_stream_address_index(height: Height, network: Network) -> usize {
let num_addresses = network.num_funding_streams();
let index = 1u32
.checked_add(funding_stream_address_period(height, network))
.expect("no overflow should happen in this sum")
.checked_sub(funding_stream_address_period(
FUNDING_STREAM_HEIGHT_RANGES.get(&network).unwrap().start,
network,
))
.expect("no overflow should happen in this sub") as usize;
assert!(index > 0 && index <= num_addresses);
// spec formula will output an index starting at 1 but
// Zebra indices for addresses start at zero, return converted.
index - 1
}
/// 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,
) -> 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];
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: transparent::Address) -> bool {
assert!(
address.is_script_hash(),
"incorrect funding stream address constant: {address} \
Zcash only supports transparent 'pay to script hash' (P2SH) addresses",
);
// Verify a Bitcoin P2SH single or multisig address.
let standard_script_hash = new_coinbase_script(address);
lock_script == &standard_script_hash
}
/// Returns a new funding stream coinbase output lock script, which pays to the P2SH `address`.
pub fn new_coinbase_script(address: transparent::Address) -> Script {
assert!(
address.is_script_hash(),
"incorrect coinbase script address: {address} \
Funding streams only support transparent 'pay to script hash' (P2SH) addresses",
);
// > The “prescribed way” to pay a transparent P2SH address is to use a standard P2SH script
// > of the form OP_HASH160 fs.RedeemScriptHash(height) OP_EQUAL as the scriptPubKey.
//
// [7.10]: https://zips.z.cash/protocol/protocol.pdf#fundingstreams
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: transparent::Address,
) -> Vec<transparent::Output> {
transaction
.outputs()
.iter()
.filter(|o| check_script_form(&o.lock_script, address))
.cloned()
.collect()
}