Skip to content

Commit 20a4f8f

Browse files
committed
Network Sustainability Mechanism - ZIP 234 and 235 implementation
1 parent 7a42ea4 commit 20a4f8f

File tree

45 files changed

+1164
-520
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+1164
-520
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -291,5 +291,5 @@ lto = "thin"
291291
# The linter should ignore these expected config flags/values
292292
unexpected_cfgs = { level = "warn", check-cfg = [
293293
'cfg(tokio_unstable)', # Used by tokio-console
294-
'cfg(zcash_unstable, values("zfuture", "nu6.1", "nu7"))' # Used in Zebra and librustzcash
294+
'cfg(zcash_unstable, values("zfuture", "nu6.1", "nu7", "zip234", "zip235"))' # Used in Zebra and librustzcash
295295
] }

zebra-chain/src/block.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,15 @@ use crate::{
1919
};
2020

2121
mod commitment;
22-
mod error;
2322
mod hash;
2423
mod header;
2524
mod height;
2625
mod serialize;
2726

27+
pub mod error;
2828
pub mod genesis;
2929
pub mod merkle;
30+
pub mod subsidy;
3031

3132
#[cfg(any(test, feature = "proptest-impl"))]
3233
pub mod arbitrary;

zebra-consensus/src/block/subsidy.rs renamed to zebra-chain/src/block/subsidy.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
//!
33
//! [7.8]: https://zips.z.cash/protocol/protocol.pdf#subsidies
44
5-
use zebra_chain::transparent::{self, Script};
5+
use crate::transparent::{self, Script};
66

77
/// Funding Streams functions apply for blocks at and after Canopy.
88
pub mod funding_streams;

zebra-consensus/src/block/subsidy/funding_streams.rs renamed to zebra-chain/src/block/subsidy/funding_streams.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
//!
33
//! [7.8]: https://zips.z.cash/protocol/protocol.pdf#subsidies
44
5-
use zebra_chain::{
5+
use crate::{
66
block::Height,
77
parameters::{subsidy::*, Network},
88
transparent::{self},

zebra-consensus/src/block/subsidy/funding_streams/tests.rs renamed to zebra-chain/src/block/subsidy/funding_streams/tests.rs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,15 @@
44

55
use std::collections::HashMap;
66

7+
use crate::{
8+
amount::Amount,
9+
parameters::{
10+
subsidy::{block_subsidy_pre_nsm, funding_stream_values, FundingStreamReceiver},
11+
NetworkKind,
12+
NetworkUpgrade::*,
13+
},
14+
};
715
use color_eyre::Report;
8-
use zebra_chain::amount::Amount;
9-
use zebra_chain::parameters::NetworkUpgrade::*;
10-
use zebra_chain::parameters::{subsidy::FundingStreamReceiver, NetworkKind};
1116

1217
use crate::block::subsidy::new_coinbase_script;
1318

@@ -89,7 +94,8 @@ fn test_funding_stream_values() -> Result<(), Report> {
8994
nu6_1_fund_height_range.end,
9095
nu6_1_fund_height_range.end.next().unwrap(),
9196
] {
92-
let fsv = funding_stream_values(height, network, block_subsidy(height, network)?).unwrap();
97+
let fsv = funding_stream_values(height, network, block_subsidy_pre_nsm(height, network)?)
98+
.unwrap();
9399

94100
if height < canopy_activation_height {
95101
assert!(fsv.is_empty());

zebra-chain/src/error.rs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ use std::{io, sync::Arc};
44
use thiserror::Error;
55
use zcash_protocol::value::BalanceError;
66

7+
use crate::parameters::subsidy::SubsidyError;
8+
79
// TODO: Move all these enums into a common enum at the bottom.
810

911
/// Errors related to random bytes generation.
@@ -112,3 +114,47 @@ impl PartialEq for Error {
112114
}
113115

114116
impl Eq for Error {}
117+
118+
#[derive(Error, Clone, Debug, PartialEq, Eq)]
119+
#[allow(missing_docs)]
120+
pub enum CoinbaseTransactionError {
121+
#[error("block has no transactions")]
122+
NoTransactions,
123+
124+
#[error("first transaction must be coinbase")]
125+
Position,
126+
127+
#[error("coinbase input found in non-coinbase transaction")]
128+
AfterFirst,
129+
130+
#[error("coinbase transaction MUST NOT have any JoinSplit descriptions")]
131+
HasJoinSplit,
132+
133+
#[error("coinbase transaction MUST NOT have any Spend descriptions")]
134+
HasSpend,
135+
136+
#[error("coinbase transaction MUST NOT have any Output descriptions pre-Heartwood")]
137+
HasOutputPreHeartwood,
138+
139+
#[error("coinbase transaction MUST NOT have the EnableSpendsOrchard flag set")]
140+
HasEnableSpendsOrchard,
141+
142+
#[error("coinbase transaction Sapling or Orchard outputs MUST be decryptable with an all-zero outgoing viewing key")]
143+
OutputsNotDecryptable,
144+
145+
#[error("coinbase inputs MUST NOT exist in mempool")]
146+
InMempool,
147+
148+
#[error(
149+
"coinbase expiry {expiry_height:?} must be the same as the block {block_height:?} \
150+
after NU5 activation, failing transaction: {transaction_hash:?}"
151+
)]
152+
ExpiryBlockHeight {
153+
expiry_height: Option<crate::block::Height>,
154+
block_height: crate::block::Height,
155+
transaction_hash: crate::transaction::Hash,
156+
},
157+
158+
#[error("coinbase transaction failed subsidy validation")]
159+
Subsidy(#[from] SubsidyError),
160+
}

zebra-chain/src/parameters/network/subsidy.rs

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -789,6 +789,12 @@ pub enum SubsidyError {
789789

790790
#[error("invalid amount")]
791791
InvalidAmount(#[from] amount::Error),
792+
793+
#[error("invalid zip233 amount")]
794+
InvalidZip233Amount,
795+
796+
#[error("unexpected error occurred: {0}")]
797+
Other(String),
792798
}
793799

794800
/// The divisor used for halvings.
@@ -834,10 +840,34 @@ pub fn num_halvings(height: Height, network: &Network) -> u32 {
834840
.expect("already checked for negatives")
835841
}
836842

843+
#[cfg(zcash_unstable = "zip234")]
844+
pub fn block_subsidy(
845+
height: Height,
846+
network: &Network,
847+
money_reserve: Amount<NonNegative>,
848+
) -> Result<Amount<NonNegative>, SubsidyError> {
849+
let Some(nsm_activation_height) = NetworkUpgrade::Nu7.activation_height(network) else {
850+
return block_subsidy_pre_nsm(height, network);
851+
};
852+
if height < nsm_activation_height {
853+
return block_subsidy_pre_nsm(height, network);
854+
}
855+
let money_reserve: i64 = money_reserve.into();
856+
let money_reserve: i128 = money_reserve.into();
857+
const BLOCK_SUBSIDY_DENOMINATOR: i128 = 10_000_000_000;
858+
const BLOCK_SUBSIDY_NUMERATOR: i128 = 4_126;
859+
860+
// calculate the block subsidy (in zatoshi) using the money reserve, note the rounding up
861+
let subsidy = (money_reserve * BLOCK_SUBSIDY_NUMERATOR + (BLOCK_SUBSIDY_DENOMINATOR - 1))
862+
/ BLOCK_SUBSIDY_DENOMINATOR;
863+
864+
Ok(subsidy.try_into().expect("subsidy should fit in Amount"))
865+
}
866+
837867
/// `BlockSubsidy(height)` as described in [protocol specification §7.8][7.8]
838868
///
839869
/// [7.8]: https://zips.z.cash/protocol/protocol.pdf#subsidies
840-
pub fn block_subsidy(
870+
pub fn block_subsidy_pre_nsm(
841871
height: Height,
842872
network: &Network,
843873
) -> Result<Amount<NonNegative>, SubsidyError> {

zebra-chain/src/parameters/network/tests.rs

Lines changed: 79 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use crate::{
1111
block::Height,
1212
parameters::{
1313
subsidy::{
14-
block_subsidy, halving_divisor, height_for_halving, num_halvings,
14+
block_subsidy_pre_nsm, halving_divisor, height_for_halving, num_halvings,
1515
ParameterSubsidy as _, POST_BLOSSOM_HALVING_INTERVAL,
1616
},
1717
NetworkUpgrade,
@@ -163,32 +163,32 @@ fn block_subsidy_for_network(network: &Network) -> Result<(), Report> {
163163
// https://z.cash/support/faq/#what-is-slow-start-mining
164164
assert_eq!(
165165
Amount::<NonNegative>::try_from(1_250_000_000)?,
166-
block_subsidy((network.slow_start_interval() + 1).unwrap(), network)?
166+
block_subsidy_pre_nsm((network.slow_start_interval() + 1).unwrap(), network)?
167167
);
168168
assert_eq!(
169169
Amount::<NonNegative>::try_from(1_250_000_000)?,
170-
block_subsidy((blossom_height - 1).unwrap(), network)?
170+
block_subsidy_pre_nsm((blossom_height - 1).unwrap(), network)?
171171
);
172172

173173
// After Blossom the block subsidy is reduced to 6.25 ZEC without halving
174174
// https://z.cash/upgrade/blossom/
175175
assert_eq!(
176176
Amount::<NonNegative>::try_from(625_000_000)?,
177-
block_subsidy(blossom_height, network)?
177+
block_subsidy_pre_nsm(blossom_height, network)?
178178
);
179179

180180
// After the 1st halving, the block subsidy is reduced to 3.125 ZEC
181181
// https://z.cash/upgrade/canopy/
182182
assert_eq!(
183183
Amount::<NonNegative>::try_from(312_500_000)?,
184-
block_subsidy(first_halving_height, network)?
184+
block_subsidy_pre_nsm(first_halving_height, network)?
185185
);
186186

187187
// After the 2nd halving, the block subsidy is reduced to 1.5625 ZEC
188188
// See "7.8 Calculation of Block Subsidy and Founders' Reward"
189189
assert_eq!(
190190
Amount::<NonNegative>::try_from(156_250_000)?,
191-
block_subsidy(
191+
block_subsidy_pre_nsm(
192192
(first_halving_height + POST_BLOSSOM_HALVING_INTERVAL).unwrap(),
193193
network
194194
)?
@@ -198,7 +198,7 @@ fn block_subsidy_for_network(network: &Network) -> Result<(), Report> {
198198
// Check that the block subsidy rounds down correctly, and there are no errors
199199
assert_eq!(
200200
Amount::<NonNegative>::try_from(4_882_812)?,
201-
block_subsidy(
201+
block_subsidy_pre_nsm(
202202
(first_halving_height + (POST_BLOSSOM_HALVING_INTERVAL * 6)).unwrap(),
203203
network
204204
)?
@@ -208,7 +208,7 @@ fn block_subsidy_for_network(network: &Network) -> Result<(), Report> {
208208
// Check that the block subsidy is calculated correctly at the limit
209209
assert_eq!(
210210
Amount::<NonNegative>::try_from(1)?,
211-
block_subsidy(
211+
block_subsidy_pre_nsm(
212212
(first_halving_height + (POST_BLOSSOM_HALVING_INTERVAL * 28)).unwrap(),
213213
network
214214
)?
@@ -218,31 +218,31 @@ fn block_subsidy_for_network(network: &Network) -> Result<(), Report> {
218218
// Check that there are no errors
219219
assert_eq!(
220220
Amount::<NonNegative>::try_from(0)?,
221-
block_subsidy(
221+
block_subsidy_pre_nsm(
222222
(first_halving_height + (POST_BLOSSOM_HALVING_INTERVAL * 29)).unwrap(),
223223
network
224224
)?
225225
);
226226

227227
assert_eq!(
228228
Amount::<NonNegative>::try_from(0)?,
229-
block_subsidy(
229+
block_subsidy_pre_nsm(
230230
(first_halving_height + (POST_BLOSSOM_HALVING_INTERVAL * 39)).unwrap(),
231231
network
232232
)?
233233
);
234234

235235
assert_eq!(
236236
Amount::<NonNegative>::try_from(0)?,
237-
block_subsidy(
237+
block_subsidy_pre_nsm(
238238
(first_halving_height + (POST_BLOSSOM_HALVING_INTERVAL * 49)).unwrap(),
239239
network
240240
)?
241241
);
242242

243243
assert_eq!(
244244
Amount::<NonNegative>::try_from(0)?,
245-
block_subsidy(
245+
block_subsidy_pre_nsm(
246246
(first_halving_height + (POST_BLOSSOM_HALVING_INTERVAL * 59)).unwrap(),
247247
network
248248
)?
@@ -251,7 +251,7 @@ fn block_subsidy_for_network(network: &Network) -> Result<(), Report> {
251251
// The largest possible integer divisor
252252
assert_eq!(
253253
Amount::<NonNegative>::try_from(0)?,
254-
block_subsidy(
254+
block_subsidy_pre_nsm(
255255
(first_halving_height + (POST_BLOSSOM_HALVING_INTERVAL * 62)).unwrap(),
256256
network
257257
)?
@@ -260,33 +260,94 @@ fn block_subsidy_for_network(network: &Network) -> Result<(), Report> {
260260
// Other large divisors which should also result in zero
261261
assert_eq!(
262262
Amount::<NonNegative>::try_from(0)?,
263-
block_subsidy(
263+
block_subsidy_pre_nsm(
264264
(first_halving_height + (POST_BLOSSOM_HALVING_INTERVAL * 63)).unwrap(),
265265
network
266266
)?
267267
);
268268

269269
assert_eq!(
270270
Amount::<NonNegative>::try_from(0)?,
271-
block_subsidy(
271+
block_subsidy_pre_nsm(
272272
(first_halving_height + (POST_BLOSSOM_HALVING_INTERVAL * 64)).unwrap(),
273273
network
274274
)?
275275
);
276276

277277
assert_eq!(
278278
Amount::<NonNegative>::try_from(0)?,
279-
block_subsidy(Height(Height::MAX_AS_U32 / 4), network)?
279+
block_subsidy_pre_nsm(Height(Height::MAX_AS_U32 / 4), network)?
280280
);
281281

282282
assert_eq!(
283283
Amount::<NonNegative>::try_from(0)?,
284-
block_subsidy(Height(Height::MAX_AS_U32 / 2), network)?
284+
block_subsidy_pre_nsm(Height(Height::MAX_AS_U32 / 2), network)?
285285
);
286286

287287
assert_eq!(
288288
Amount::<NonNegative>::try_from(0)?,
289-
block_subsidy(Height::MAX, network)?
289+
block_subsidy_pre_nsm(Height::MAX, network)?
290+
);
291+
292+
Ok(())
293+
}
294+
295+
#[cfg(all(feature = "tx_v6", zcash_unstable = "zip234"))]
296+
#[test]
297+
fn block_subsidy_reserve_decreases_test() -> Result<(), Report> {
298+
use crate::{
299+
amount::MAX_MONEY,
300+
parameters::{
301+
subsidy::block_subsidy,
302+
testnet::{self, ConfiguredActivationHeights},
303+
},
304+
};
305+
306+
let network = testnet::Parameters::build()
307+
.with_activation_heights(ConfiguredActivationHeights {
308+
nu7: Some(1),
309+
..Default::default()
310+
})
311+
.clear_funding_streams()
312+
.to_network();
313+
314+
let mut reserve: Amount<NonNegative> = MAX_MONEY.try_into()?;
315+
let first_height = (network.slow_start_interval() + 1).unwrap();
316+
let last_height = Height::MAX;
317+
318+
// Test a range of heights, e.g., every 100_000 blocks, plus the first and last
319+
let mut heights = vec![first_height, last_height];
320+
let mut h = first_height;
321+
while h < last_height {
322+
heights.push(h);
323+
h = (h + 100_000).unwrap_or(last_height);
324+
}
325+
heights.sort_unstable();
326+
heights.dedup();
327+
328+
let mut prev_subsidy = reserve;
329+
for &height in &heights {
330+
let subsidy = block_subsidy(height, &network, reserve)?;
331+
assert!(
332+
subsidy < reserve,
333+
"subsidy exceeds reserve at height {height:?}"
334+
);
335+
assert!(
336+
subsidy < prev_subsidy,
337+
"subsidy did not decrease at height {height:?}"
338+
);
339+
prev_subsidy = subsidy;
340+
// Reserve should decrease by the subsidy amount
341+
let result = reserve.checked_sub(subsidy).expect("reserve went negative");
342+
343+
reserve = Amount::<NonNegative>::try_from(i64::from(result))
344+
.expect("reserve should be non-negative");
345+
}
346+
347+
// At the end, reserve should be >= 0
348+
assert!(
349+
reserve >= Amount::<NonNegative>::try_from(0)?.into(),
350+
"reserve went negative"
290351
);
291352

292353
Ok(())

0 commit comments

Comments
 (0)