1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
//! 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.kind()).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.kind())
                .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.kind())
        .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()
}