Specification
Definitions
- We use the following constants and functions defined in :
+ We use the following constants and functions defined in , , and :
- BlossomActivationHeight
- PostBlossomHalvingInterval
- Halving(height)
+ -
+ \(\mathsf{BlossomActivationHeight}\)
+
+ -
+ \(\mathsf{PostBlossomHalvingInterval}\)
+
+ -
+ \(\mathsf{Halving}(\mathsf{height})\)
+
+ -
+ \(\mathsf{BlockSubsidy}(\mathsf{height})\)
+
+ -
+ \(\mathsf{RedeemScriptHash}(\mathsf{height})\)
+ .
- We also define the following constants and functions:
+ We also define the following function:
- SlowStartShift
as-defined in .
- HeightForHalving(halving)
: Smallest height
such that Halving(height) = halving
+ -
+ \(\mathsf{HeightForHalving}(\mathsf{halving})\)
+ : Smallest
+ \(\mathsf{height}\)
+ such that
+ \(\mathsf{Halving}(\mathsf{height}) = \mathsf{halving}\)
+
Funding streams
- A funding stream is defined by a block reward fraction (represented as a numerator and a denominator), a start height (inclusive), and an end height (exclusive).
- By defining the issuance as a proportion of the total block issuance, rather than absolute zatoshis, this ZIP dovetails with any changes to both block target times and issuance-per-block rates while maintaining an unchanged target-time-based issuance schedule. We anticipate such target-time / issuance rate changes in other ZIPs (for example, ).
+ A funding stream is defined by a block subsidy fraction (represented as a numerator and a denominator), a start height (inclusive), and an end height (exclusive).
+ By defining the issuance as a proportion of the total block subsidy, rather than absolute zatoshis, this ZIP dovetails with any changes to both block target spacing and issuance-per-block rates. Such a change occurred at the Blossom network upgrade, for example.
The value of a funding stream at a given block height is defined as:
- FundingStream[FUND].Value(height) =
- floor((
- BlockReward(height) * FundingStream[FUND].ValueNumerator
- ) / FundingStream[FUND].ValueDenominator)
+ \(\mathsf{FundingStream[FUND].Value}(\mathsf{height}) =
+ \mathsf{floor}\left(
+ \frac{\mathsf{BlockSubsidy}(\mathsf{height}) \,\cdot\, \mathsf{FundingStream[FUND].ValueNumerator}}{\mathsf{FundingStream[FUND].ValueDenominator}}
+ \right)\)
An active funding stream at a given block height is defined as a funding stream for which the block height is less than its end height, but not less than its start height.
- Each funding stream has an associated set of recipient addresses. Each address is used for at most 1/48th of a halving interval, creating a roughly-monthly sequence of funding periods. The address to be used for a given block height is defined as follows:
- AddressChangeInterval = PostBlossomHalvingInterval / 48
-AddressPeriod(height) =
- floor((
- height + PostBlossomHalvingInterval - HeightForHalving(1)
- ) / AddressChangeInterval)
-FundingStream[FUND].AddressIndex(height) =
- AddressPeriod(height) - AddressPeriod(FundingStream[FUND].StartHeight)
-Address(height) = FundingStream[FUND].Addresses[FundingStream[FUND].AddressIndex(height)]
+ Each funding stream has an associated sequence of recipient addresses, each of which MUST be either a transparent P2SH address or a Sapling address.
+ Each address is used for at most 1/48th of a halving interval, creating a roughly-monthly sequence of funding periods. The address to be used for a given block height is defined as follows:
+ \(\begin{eqnarray*}
+ \mathsf{AddressChangeInterval} &=& \mathsf{PostBlossomHalvingInterval} / 48 \\
+ \mathsf{AddressPeriod}(\mathsf{height}) &=&
+ \mathsf{floor}\left(
+ {\small\frac{\mathsf{height} + \mathsf{PostBlossomHalvingInterval} - \mathsf{HeightForHalving}(1)}{\mathsf{AddressChangeInterval}}}
+ \right) \\
+ \mathsf{FundingStream[FUND].AddressIndex}(\mathsf{height}) &=&
+ \mathsf{AddressPeriod}(\mathsf{height}) - \\&&\hspace{2em} \mathsf{AddressPeriod}(\mathsf{FundingStream[FUND].StartHeight}) \\
+ \mathsf{Address}(\mathsf{height}) &=& \mathsf{FundingStream[FUND].Addresses[} \\&&\hspace{2em} \mathsf{FundingStream[FUND].AddressIndex}(\mathsf{height})\mathsf{]}
+\end{eqnarray*}\)
This has the property that all active funding streams change the address they are using on the same block height schedule, aligned to the height of the first halving so that 48 funding periods fit cleanly within a halving interval. This can be leveraged to simplify implementations, by batching the necessary outputs for each funding period.
Below is a visual representation of how stream addresses align with funding periods:
-
-
-
- Example height |
- Stream A |
- Stream B |
- Stream C |
-
-
-
-
- AddressChangeInterval - 2 |
- A0 |
- |
- |
-
-
- AddressChangeInterval - 1 |
- A0 |
- |
- |
-
-
- AddressChangeInterval |
- A1 |
- B0 |
- C0 |
-
-
- AddressChangeInterval + 1 |
- A1 |
- B0 |
- C0 |
-
-
- ... |
- |
- |
- |
-
-
- 2*AddressChangeInterval - 2 |
- A1 |
- B0 |
- C0 |
-
-
- 2*AddressChangeInterval - 1 |
- A1 |
- B0 |
- C0 |
-
-
- 2*AddressChangeInterval |
- A2 |
- |
- C1 |
-
-
- 2*AddressChangeInterval + 1 |
- A2 |
- |
- C1 |
-
-
- ... |
- |
- |
- |
-
-
- PostBlossomHalvingInterval - 2 |
- A2 |
- |
- C1 |
-
-
- PostBlossomHalvingInterval - 1 |
- A2 |
- |
- C1 |
-
-
- PostBlossomHalvingInterval |
- |
- |
- C2 |
-
-
- PostBlossomHalvingInterval + 1 |
- |
- |
- C2 |
-
-
-
- Note that this is not intended to align with the end of a pre-Blossom Founders' Reward address period (as defined by FounderAddressChangeInterval
in ). There will be a shortened Founders' Reward address period prior to Blossom activation.
+
+
+
+
+ Example height |
+ Stream A |
+ Stream B |
+ Stream C |
+
+
+
+
+ AddressChangeInterval - 2 |
+ A0 |
+ |
+ |
+
+
+ AddressChangeInterval - 1 |
+ A0 |
+ |
+ |
+
+
+ AddressChangeInterval |
+ A1 |
+ B0 |
+ C0 |
+
+
+ AddressChangeInterval + 1 |
+ A1 |
+ B0 |
+ C0 |
+
+
+ ... |
+ |
+ |
+ |
+
+
+ 2*AddressChangeInterval - 2 |
+ A1 |
+ B0 |
+ C0 |
+
+
+ 2*AddressChangeInterval - 1 |
+ A1 |
+ B0 |
+ C0 |
+
+
+ 2*AddressChangeInterval |
+ A2 |
+ |
+ C1 |
+
+
+ 2*AddressChangeInterval + 1 |
+ A2 |
+ |
+ C1 |
+
+
+ ... |
+ |
+ |
+ |
+
+
+ PostBlossomHalvingInterval - 2 |
+ A2 |
+ |
+ C1 |
+
+
+ PostBlossomHalvingInterval - 1 |
+ A2 |
+ |
+ C1 |
+
+
+ PostBlossomHalvingInterval |
+ |
+ |
+ C2 |
+
+
+ PostBlossomHalvingInterval + 1 |
+ |
+ |
+ C2 |
+
+
+
+
+ On Mainnet, ${NU4} is planned to activate exactly at the point when the Founders' Reward expires, at block height 1046400. On Testnet, there will be a shortened Founders' Reward address period prior to ${NU4} activation.
Consensus rules
- Prior to activation of the Blossom network upgrade, the existing consensus rule for payment of the original Founders' Reward is enforced.
- Once the Blossom network upgrade activates:
-
- - The existing consensus rule is no longer active.
- - The coinbase transaction in each block MUST contain at least one output per active funding stream that pays the stream's value to the stream's recipient address for the block's height.
-
-
- Stream definitions
- The consensus-defined funding streams described above each start at the Blossom activation height, and end at the first block reward halving. They are defined as follows:
-
-
-
- Stream |
- Value numerator |
- Value denominator |
- Start height |
- End height |
-
-
-
-
- FS 1 |
- 3 |
- 40 |
- BlossomActivationHeight |
- HeightForHalving(1) |
-
-
- FS 2 |
- 1 |
- 25 |
- BlossomActivationHeight |
- HeightForHalving(1) |
-
-
- FS 3 |
- 1 |
- 40 |
- BlossomActivationHeight |
- HeightForHalving(1) |
-
-
- FS 4 |
- 1 |
- 50 |
- BlossomActivationHeight |
- HeightForHalving(1) |
-
-
- FS 5 |
- 1 |
- 80 |
- BlossomActivationHeight |
- HeightForHalving(1) |
-
-
- FS 6 |
- 1 |
- 80 |
- BlossomActivationHeight |
- HeightForHalving(1) |
-
-
- FS 7 |
- 1 |
- 100 |
- BlossomActivationHeight |
- HeightForHalving(1) |
-
-
- FS 8 |
- 1 |
- 200 |
- BlossomActivationHeight |
- HeightForHalving(1) |
-
-
-
-
- - To-do: specify the correct values.
-
- The sum of the block reward fractions for the above funding streams is 1/5
, equal to the original Founders' Reward (as-defined by FoundersFraction
in ).
- The sets of recipient addresses are defined as follows:
- std::vector<std::string> FS_1_ADDRESSES = [
- "tFS1INVALIDADDRESS00",
- "tFS1INVALIDADDRESS01",
- "tFS1INVALIDADDRESS02",
- "tFS1INVALIDADDRESS03",
- "tFS1INVALIDADDRESS04",
- "tFS1INVALIDADDRESS05",
- "tFS1INVALIDADDRESS06",
- "tFS1INVALIDADDRESS07",
- "tFS1INVALIDADDRESS08",
- "tFS1INVALIDADDRESS09",
- "tFS1INVALIDADDRESS10",
- "tFS1INVALIDADDRESS11",
-];
-
-std::vector<std::string> FS_2_ADDRESSES = [
- "tFS2INVALIDADDRESS00",
- "tFS2INVALIDADDRESS01",
- "tFS2INVALIDADDRESS02",
- "tFS2INVALIDADDRESS03",
- "tFS2INVALIDADDRESS04",
- "tFS2INVALIDADDRESS05",
- "tFS2INVALIDADDRESS06",
- "tFS2INVALIDADDRESS07",
- "tFS2INVALIDADDRESS08",
- "tFS2INVALIDADDRESS09",
- "tFS2INVALIDADDRESS10",
- "tFS2INVALIDADDRESS11",
-];
-
-std::vector<std::string> FS_3_ADDRESSES = [
- "tFS3INVALIDADDRESS00",
- "tFS3INVALIDADDRESS01",
- "tFS3INVALIDADDRESS02",
- "tFS3INVALIDADDRESS03",
- "tFS3INVALIDADDRESS04",
- "tFS3INVALIDADDRESS05",
- "tFS3INVALIDADDRESS06",
- "tFS3INVALIDADDRESS07",
- "tFS3INVALIDADDRESS08",
- "tFS3INVALIDADDRESS09",
- "tFS3INVALIDADDRESS10",
- "tFS3INVALIDADDRESS11",
-];
-
-std::vector<std::string> FS_4_ADDRESSES = [
- "tFS4INVALIDADDRESS00",
- "tFS4INVALIDADDRESS01",
- "tFS4INVALIDADDRESS02",
- "tFS4INVALIDADDRESS03",
- "tFS4INVALIDADDRESS04",
- "tFS4INVALIDADDRESS05",
- "tFS4INVALIDADDRESS06",
- "tFS4INVALIDADDRESS07",
- "tFS4INVALIDADDRESS08",
- "tFS4INVALIDADDRESS09",
- "tFS4INVALIDADDRESS10",
- "tFS4INVALIDADDRESS11",
-];
-
-std::vector<std::string> FS_5_ADDRESSES = [
- "tFS5INVALIDADDRESS00",
- "tFS5INVALIDADDRESS01",
- "tFS5INVALIDADDRESS02",
- "tFS5INVALIDADDRESS03",
- "tFS5INVALIDADDRESS04",
- "tFS5INVALIDADDRESS05",
- "tFS5INVALIDADDRESS06",
- "tFS5INVALIDADDRESS07",
- "tFS5INVALIDADDRESS08",
- "tFS5INVALIDADDRESS09",
- "tFS5INVALIDADDRESS10",
- "tFS5INVALIDADDRESS11",
-];
-
-std::vector<std::string> FS_6_ADDRESSES = [
- "tFS6INVALIDADDRESS00",
- "tFS6INVALIDADDRESS01",
- "tFS6INVALIDADDRESS02",
- "tFS6INVALIDADDRESS03",
- "tFS6INVALIDADDRESS04",
- "tFS6INVALIDADDRESS05",
- "tFS6INVALIDADDRESS06",
- "tFS6INVALIDADDRESS07",
- "tFS6INVALIDADDRESS08",
- "tFS6INVALIDADDRESS09",
- "tFS6INVALIDADDRESS10",
- "tFS6INVALIDADDRESS11",
-];
-
-std::vector<std::string> FS_7_ADDRESSES = [
- "tFS7INVALIDADDRESS00",
- "tFS7INVALIDADDRESS01",
- "tFS7INVALIDADDRESS02",
- "tFS7INVALIDADDRESS03",
- "tFS7INVALIDADDRESS04",
- "tFS7INVALIDADDRESS05",
- "tFS7INVALIDADDRESS06",
- "tFS7INVALIDADDRESS07",
- "tFS7INVALIDADDRESS08",
- "tFS7INVALIDADDRESS09",
- "tFS7INVALIDADDRESS10",
- "tFS7INVALIDADDRESS11",
-];
-
-std::vector<std::string> FS_8_ADDRESSES = [
- "tFS8INVALIDADDRESS00",
- "tFS8INVALIDADDRESS01",
- "tFS8INVALIDADDRESS02",
- "tFS8INVALIDADDRESS03",
- "tFS8INVALIDADDRESS04",
- "tFS8INVALIDADDRESS05",
- "tFS8INVALIDADDRESS06",
- "tFS8INVALIDADDRESS07",
- "tFS8INVALIDADDRESS08",
- "tFS8INVALIDADDRESS09",
- "tFS8INVALIDADDRESS10",
- "tFS8INVALIDADDRESS11",
-];
+ Prior to activation of the ${NU4} network upgrade, the existing consensus rule for payment of the original Founders' Reward is enforced.
+ Once the ${NU4} network upgrade activates:
- - To-do: specify the correct sets of FR addresses.
- - To-do: require that the FR address sets are PGP-signed with appropriate keys.
+ - The existing consensus rule for payment of the Founders' Reward is no longer active. (This would be the case under the preexisting consensus rules for Mainnet, but not for Testnet.)
+ - The coinbase transaction in each block MUST contain at least one output per active funding stream that pays the stream's value in the prescribed way to the stream's recipient address for the block's height.
+ - The "prescribed way" to pay a transparent P2SH address is to use a standard P2SH script of the form
OP_HASH160 RedeemScriptHash(height) OP_EQUAL
as the scriptPubKey
.
+ - The "prescribed way" to pay a Sapling address is as defined in . That is, all Sapling outputs in coinbase transactions (including, but not limited to, outputs for funding streams) MUST have valid note commitments when recovered using a 32-byte array of zeroes as the outgoing viewing key.
+ For the funding stream definitions to be activated at ${NU4}, see ZIP 214. Funding stream definitions can be added, changed, or deleted in ZIPs associated with subsequent network upgrades, subject to the ZIP process.
Example implementation
struct FundingPeriod {
@@ -373,17 +216,14 @@
};
enum FundingStream {
- FS_1,
- FS_2,
- FS_3,
- FS_4,
- FS_5,
- FS_6,
- FS_7,
- FS_8,
+ FS_ECC,
+ FS_ZF,
+ FS_MG,
MAX_FUNDING_STREAMS,
};
+const auto FIRST_FUNDING_STREAM = FS_ECC;
+
struct Params {
...
int nFundingPeriodLength;
@@ -396,15 +236,17 @@
Consensus::FundingStream idx,
std::vector<std::string> addresses,
uint64_t valueNumerator,
- uint64_t valueDenominator)
+ uint64_t valueDenominator,
+ int startHeight,
+ int endHeight)
{
assert(valueNumerator < valueDenominator);
+ assert(valueNumerator < INT64_MAX / MAX_MONEY);
params.vFundingPeriods[idx].addresses = addresses;
params.vFundingPeriods[idx].valueNumerator = valueNumerator;
params.vFundingPeriods[idx].valueDenominator = valueDenominator;
- params.vFundingPeriods[idx].startHeight =
- params.vUpgrades[Consensus::UPGRADE_BLOSSOM].nActivationHeight;
- params.vFundingPeriods[idx].endHeight = HeightForHalving(params, 1);
+ params.vFundingPeriods[idx].startHeight = startHeight;
+ params.vFundingPeriods[idx].endHeight = endHeight;
assert(params.vFundingPeriods[idx].startHeight < params.vFundingPeriods[idx].endHeight);
};
@@ -413,14 +255,11 @@
consensus.nFundingPeriodLength = consensus.nSubsidyPostBlossomHalvingInterval / 48;
- AddZIP207FundingStream(consensus, Consensus::FS_1, FS_1_ADDRESSES, 3, 40);
- AddZIP207FundingStream(consensus, Consensus::FS_2, FS_2_ADDRESSES, 1, 25);
- AddZIP207FundingStream(consensus, Consensus::FS_3, FS_3_ADDRESSES, 1, 40);
- AddZIP207FundingStream(consensus, Consensus::FS_4, FS_4_ADDRESSES, 1, 50);
- AddZIP207FundingStream(consensus, Consensus::FS_5, FS_5_ADDRESSES, 1, 80);
- AddZIP207FundingStream(consensus, Consensus::FS_6, FS_6_ADDRESSES, 1, 80);
- AddZIP207FundingStream(consensus, Consensus::FS_7, FS_7_ADDRESSES, 1, 100);
- AddZIP207FundingStream(consensus, Consensus::FS_8, FS_8_ADDRESSES, 1, 200);
+ int devFundStartHeight = HeightForHalving(params, 1);
+ int devFundEndHeight = HeightForHalving(params, 2);
+ AddZIP207FundingStream(consensus, Consensus::FS_ECC, FS_ECC_ADDRESSES, 7, 100, devFundStartHeight, devFundEndHeight);
+ AddZIP207FundingStream(consensus, Consensus::FS_ZF, FS_ZF_ADDRESSES, 5, 100, devFundStartHeight, devFundEndHeight);
+ AddZIP207FundingStream(consensus, Consensus::FS_MG, FS_MG_ADDRESSES, 8, 100, devFundStartHeight, devFundEndHeight);
...
}
@@ -430,17 +269,23 @@
const Consensus::Params& params,
Consensus::FundingStream idx)
{
- // Integer division is floor division in C++
- auto curPeriod = (
- nHeight + params.nSubsidyPostBlossomHalvingInterval - HeightForHalving(params, 1)
- ) / params.nFundingPeriodLength;
- auto startPeriod = (
- params.vFundingPeriods[idx].startHeight
- + params.nSubsidyPostBlossomHalvingInterval
- - HeightForHalving(params, 1)
- ) / params.nFundingPeriodLength;
+ assert(nHeight <= INT_MAX - params.nSubsidyPostBlossomHalvingInterval);
+ assert(params.vFundingPeriods[idx].startHeight <= INT_MAX - params.nSubsidyPostBlossomHalvingInterval);
+
+ int curPeriodNumerator = nHeight + params.nSubsidyPostBlossomHalvingInterval - HeightForHalving(params, 1);
+ int startPeriodNumerator = params.vFundingPeriods[idx].startHeight + params.nSubsidyPostBlossomHalvingInterval
+ - HeightForHalving(params, 1);
+
+ // Integer division is floor division for nonnegative integers in C++
+ assert(curPeriodNumerator >= 0);
+ assert(startPeriodNumerator >= 0);
+ auto curPeriod = curPeriodNumerator / params.nFundingPeriodLength;
+ auto startPeriod = startPeriodNumerator / params.nFundingPeriodLength;
auto addressIndex = curPeriod - startPeriod;
- return params.vFundingPeriods[idx].addresses[addressIndex];
+
+ auto addresses = params.vFundingPeriods[idx].addresses;
+ assert(addressIndex >= 0 && addressIndex < addresses.size());
+ return addresses[addressIndex];
};
CAmount FundingStreamValue(
@@ -448,7 +293,7 @@
const Consensus::Params& params,
Consensus::FundingStream idx)
{
- // Integer division is floor division in C++
+ // Integer division is floor division for nonnegative integers in C++
return CAmount((
GetBlockSubsidy(nHeight, params) * params.vFundingPeriods[idx].valueNumerator
) / params.vFundingPeriods[idx].valueDenominator);
@@ -459,7 +304,7 @@
const Consensus::Params& params)
{
std::set<std::pair<CScript, CAmount>> requiredStreams;
- for (int idx = Consensus::FS_ZECC_EF; idx < Consensus::MAX_FUNDING_STREAMS; idx++) {
+ for (int idx = Consensus::FIRST_FUNDING_STREAM; idx < Consensus::MAX_FUNDING_STREAMS; idx++) {
// Funding period is [startHeight, endHeight)
if (nHeight >= params.vFundingPeriods[idx].startHeight &&
nHeight < params.vFundingPeriods[idx].endHeight)
@@ -476,7 +321,7 @@
{
...
- if (NetworkUpgradeActive(nHeight, consensusParams, Consensus::UPGRADE_BLOSSOM)) {
+ if (NetworkUpgradeActive(nHeight, consensusParams, Consensus::UPGRADE_NU4)) {
// Coinbase transaction must include outputs corresponding to the consensus
// funding streams active at the current block height.
auto requiredStreams = GetActiveFundingStreams(nHeight, consensusParams);
@@ -491,14 +336,15 @@
}
if (!requiredStreams.empty()) {
- return state.DoS(100, error("%s: funding stream missing", __func__), REJECT_INVALID, "cb-funding-stream-missing");
+ return state.DoS(100, error("%s: funding stream missing", __func__),
+ REJECT_INVALID, "cb-funding-stream-missing");
}
} else {
// Coinbase transaction must include an output sending 20% of
- // the block reward to a founders reward script, until the last founders
- // reward block is reached, with exception of the genesis block.
- // The last founders reward block is defined as the block just before the
- // first subsidy halving block, which occurs at halving_interval + slow_start_shift
+ // the block subsidy to a Founders' Reward script, until the last Founders'
+ // Reward block is reached, with exception of the genesis block.
+ // The last Founders' Reward block is defined as the block just before the
+ // first subsidy halving block.
if ((nHeight > 0) && (nHeight <= consensusParams.GetLastFoundersRewardBlockHeight())) {
bool found = false;
@@ -512,7 +358,8 @@
}
if (!found) {
- return state.DoS(100, error("%s: founders reward missing", __func__), REJECT_INVALID, "cb-no-founders-reward");
+ return state.DoS(100, error("%s: founders reward missing", __func__),
+ REJECT_INVALID, "cb-no-founders-reward");
}
}
}
@@ -522,15 +369,10 @@