-
Notifications
You must be signed in to change notification settings - Fork 212
Draft RFC: Block subsidy #1129
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Draft RFC: Block subsidy #1129
Changes from all commits
Commits
Show all changes
62 commits
Select commit
Hold shift + click to select a range
34284f8
create block subsidy rfc
oxarbitrage 52bd951
update funding stream constants
oxarbitrage f68f127
Update xxxx-block-subsidy.md
oxarbitrage 8b06235
Apply suggestions from code review
oxarbitrage ab88d50
add reference in miner subsidy
oxarbitrage 2ace690
fix table
oxarbitrage e9661d3
Update xxxx-block-subsidy.md
oxarbitrage 518e146
remove FundingStreamsHeightIntervals
oxarbitrage bd57900
remove `or 0` from function descriptions.
oxarbitrage c76d064
add links to subsidy categories
oxarbitrage 90e5f36
fix design in Funding streams parameter constants
oxarbitrage 92219a5
fix order in FUNDING_STREAM_RECEIVER_NUMERATORS
oxarbitrage 135ea12
return `PayToScriptHash` in `funding_stream_address`
oxarbitrage 3d6c392
fix definitions
oxarbitrage 2b51780
change summary
oxarbitrage a4580ae
change motivation
oxarbitrage 2732d07
change consensus rules intro
oxarbitrage f06ebbf
add types to constants
oxarbitrage c2f2a53
change `transaction_fees()` to `miner_fees()`
oxarbitrage 8de943e
add shielded coinbase
oxarbitrage b84bf0b
move shielded coinbase link
oxarbitrage ad80a67
change return type of `funding_stream_address`
oxarbitrage e5083b2
Apply suggestions from code review
oxarbitrage 421f1ea
fix table intervals
oxarbitrage 589cef3
Use Range for FUNDING_STREAM_HEIGHT_RANGES
oxarbitrage 134bc32
improve transparent value pool section
oxarbitrage 9ef7dec
Apply suggestions from code review
oxarbitrage 37fb20b
add a test plan section
oxarbitrage a8d32c5
fix extra paste
oxarbitrage d33027d
rename `subsidy_is_correct` to `subsidy_is_valid`
oxarbitrage 5cff70b
add funding streams address constants
oxarbitrage bb933dd
fix funding stream addresses
oxarbitrage 6c97019
add errors
oxarbitrage 6304a81
remove ending dot for error descriptions
oxarbitrage 5ee8efc
modify founders reward
oxarbitrage 2c99a7f
Apply suggestions from code review
oxarbitrage 617b3fe
change ECC to BP
oxarbitrage a30af1b
add note about state needed for transparent pool
oxarbitrage 648e3f4
Apply suggestions from code review
oxarbitrage 60eda9c
split constants
oxarbitrage 978005e
Update book/src/dev/rfcs/xxxx-block-subsidy.md
oxarbitrage 5eb45fc
Update book/src/dev/rfcs/xxxx-block-subsidy.md
oxarbitrage 357406a
Tweak block subsidy RFC wording
teor2345 d3a3930
Spacing
teor2345 574a259
Clarify transaction fees definition
teor2345 9ae3321
Use consistent fee terminology
teor2345 4df5d82
Clarify transparent value pool
teor2345 ccd9118
Revise implementation plan based on latest priorities
teor2345 b074106
Change tests based on new priorities
teor2345 531de21
Fix markdown
teor2345 07634b4
Fix value pool description
teor2345 55c67a0
update some definitions
oxarbitrage 60f1d8b
change block and miner subsidy definitions
oxarbitrage 6e44892
fee pool definitions, fix all links to protocol
oxarbitrage ce81d5b
add reference to zip209
oxarbitrage 29bd28e
add note for MG stream number of addresses
oxarbitrage 8f91397
Be explicit in miner subsidy definition
oxarbitrage fc893e2
be more clear in NU definition
oxarbitrage 62b3344
Add extra checks
oxarbitrage dda122c
Mark as draft and update ticket and PR links
teor2345 fe59379
Rename book/src/dev/rfcs/xxxx-block-subsidy.md to book/src/dev/rfcs/d…
teor2345 eae0dab
Add some TODOs from outstanding comments
teor2345 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,311 @@ | ||
| - Feature Name: Block subsidy | ||
| - Start Date: 2020-10-05 | ||
| - Design PR: [ZcashFoundation/zebra#1129](https://github.com/ZcashFoundation/zebra/pull/1129) | ||
| - Zebra Issue: [ZcashFoundation/zebra#338](https://github.com/ZcashFoundation/zebra/issues/338) | ||
|
|
||
| # Draft | ||
|
|
||
| Note: This is a draft Zebra RFC. See | ||
| [ZcashFoundation/zebra#338](https://github.com/ZcashFoundation/zebra/issues/338) | ||
| for more details. | ||
|
|
||
| # Summary | ||
| [summary]: #summary | ||
|
|
||
| Zebra manages [semantic verification](https://github.com/ZcashFoundation/zebra/blob/main/book/src/dev/rfcs/0002-parallel-verification.md#definitions) in the `zebra-consensus` crate, this is done for all incoming blocks. Inside each block the coinbase transaction is special, it holds the subsidy rewards that are paid to different participants (miners, founders, funding stream receivers). This RFC describes how to implement the needed calculations and verification for block subsidy and miner fees. | ||
|
|
||
| # Motivation | ||
| [motivation]: #motivation | ||
|
|
||
| All incoming blocks must be validated with the protocol specifications, there is no way to avoid including this logic in any Zcash protocol compatible implementation such as Zebra. | ||
|
|
||
| Block subsidy and miner fees are part of the protocol, the semantic verification of them apply to the coinbase transaction and they must be verified in Zebra. | ||
|
|
||
| # Definitions | ||
| [definitions]: #definitions | ||
|
|
||
| - **block subsidy**: Value created from mining composed of miner subsidy plus founders reward or funding stream if any of the 2 are active ([Subsidy Concepts](https://zips.z.cash/protocol/protocol.pdf#subsidyconcepts)). | ||
| - **coinbase transaction**: The first transaction in a block; this is the transaction that handles block subsidy. | ||
| - **founders reward**: The portion of the block subsidy that goes into a pre defined founder address in a single output. | ||
| - **funding streams**: The portion of the block subsidy that goes into one or more pre defined funding stream addresses. Payment is done with one output for each active funding stream of the block. | ||
| - **miner subsidy**: The portion of the block subsidy that goes into the miner of the block, excluding fees. The miner may split this amount into any number of outputs, to any addresses, including extra outputs to founders rewards and funding stream addresses. | ||
| - **network upgrade**: A rule change that creates a new consensus rule branch. Up-to-date nodes follow the new branch created by the network upgrade. | ||
| - **transaction fees**: The sum of the extra [transparent value pool](#transparent-value-pool-calculation) and shielded values, for all the transactions in a block. This amount can be spent by the miner in the coinbase transaction. | ||
|
|
||
| ### Transaction fees calculation | ||
|
|
||
| There is a value pool inside each block called "[chain value pool balance](https://zips.z.cash/zip-0209#terminology)". Balance of that pool works as follows: | ||
|
|
||
| Transparent: | ||
|
|
||
| - `tx_in` adds to the pool. | ||
| - `tx_out` subtracts from the pool. | ||
|
|
||
| https://zips.z.cash/protocol/protocol.pdf#txnencodingandconsensus | ||
|
|
||
| Sprout: | ||
|
|
||
| - `vpub_new` values from `vJoinSplit` of the transaction add to the pool. | ||
| - `vpub_old` values from `vJoinSplit` of the transaction subtract from the pool. | ||
|
|
||
| https://zips.z.cash/protocol/protocol.pdf#joinsplitencodingandconsensus | ||
|
|
||
| Sapling: | ||
|
|
||
| - `valueBalance` adds to the pool. It itself is equal to `sum(SaplingSpends) - sum(SaplingOutputs)`, so it has the same functional effect as for transparent and Sprout. | ||
|
|
||
| https://zips.z.cash/protocol/protocol.pdf#txnencodingandconsensus | ||
|
|
||
| The balance rule is that this pool must have non-negative value (see [ZIP 209](https://zips.z.cash/zip-0209)), and its net value is the miner fee for the transaction. Non-negative transparent and block value pool balances are implied by the other consensus rules. Zebra should check or assert that these balances are non-negative, so invalid blocks don't pass verification. (And so that we guard against implementation bugs.) | ||
|
|
||
| **Note:** To compute the value pool we need blockchain state. The `tx_in` is always a reference to a previous transaction output, this is different from Sprout and Sapling pools where everything we need is in the same transaction. The details about how this is going to be implemented are outside of the scope of this RFC. They will be documented in a separate contextual validation RFC. | ||
|
|
||
| # Guide-level explanation | ||
| [guide-level-explanation]: #guide-level-explanation | ||
|
|
||
| In Zebra the consensus related code lives in the `zebra-consensus` crate. The block subsidy checks are implemented in `zebra-consensus`, in the following module: | ||
|
|
||
| - `zebra-consensus/src/block/subsidy/subsidy.rs` | ||
|
|
||
| Inside `zebra-consensus/src/block/subsidy/` the following submodules will be created: | ||
|
|
||
| - `general.rs`: General block reward functions and utilities. | ||
| - `founders_reward.rs`: Specific functions related to funders reward. | ||
| - `funding_streams.rs`: Specific functions for funding streams. | ||
|
|
||
| In addition to calculations the block subsidy requires constants defined in the protocol. The implementation will also create additional constants, all of them will live at: | ||
|
|
||
| - `zebra-consensus/src/parameters/subsidy.rs` | ||
|
|
||
| Checking functions for blocks are implemented at `zebra-consensus/src/block/check.rs` and they are called at `zebra-consensus/src/block.rs`. This follows the already existing structure for block validation in Zebra. | ||
|
|
||
| It is important to note that all blocks before Sapling are verified by the CheckpointVerifier, so they are not considered in this design. The following table will show what periods are included and what is not in this proposal: | ||
|
|
||
| | Height | Miner | Founder reward | Funding streams | Shielded Coinbase | Target Spacing | | ||
| |----------------------------------------|----------|----------------|-----------------|-------------------|----------------| | ||
| | ~Genesis~ | ~**0%**~ | ~**0%**~ | ~**0%**~ | ~**No**~ | ~**None**~ | | ||
| | ~(Genesis + 1)..Sapling~ | ~80%~ | ~20%~ | ~0%~ | ~No~ | ~150 seconds~ | | ||
| | Sapling..Blossom | 80% | 20% | 0% | No | 150 seconds | | ||
| | Blossom..Heartwood | 80% | 20% | 0% | No | 75 seconds | | ||
| | Heartwood..Canopy | 80% | 20% | 0% | Yes | 75 seconds | | ||
| | Canopy..Second Halving | 80% | 0% | 20% | Yes | 75 seconds | | ||
| | Second Halving.. | 100% | 0% | 0% | Yes | 75 seconds | | ||
|
|
||
| # Reference-level explanation | ||
| [reference-level-explanation]: #reference-level-explanation | ||
|
|
||
| Given the module structure proposed above, the block subsidy calculations from the protocol must be implemented. The final goal is to do semantic validation for each incoming block and make sure they pass the consensus rules. | ||
|
|
||
| To do the calculations and checks the following constants, types and functions need to be introduced: | ||
|
|
||
| ## Constants | ||
|
|
||
| ### General | ||
|
|
||
| - `SLOW_START_INTERVAL: Height` | ||
| - `SLOW_START_SHIFT: Height` | ||
| - `MAX_BLOCK_SUBSIDY: u64` | ||
| - `BLOSSOM_POW_TARGET_SPACING_RATIO: u64` | ||
| - `PRE_BLOSSOM_HALVING_INTERVAL: Height` | ||
| - `POST_BLOSSOM_HALVING_INTERVAL: Height` | ||
|
|
||
| ### Founders Rewards | ||
|
|
||
| - `FOUNDERS_FRACTION_DIVISOR: u64` | ||
| - `FOUNDERS_ADDRESS_COUNT: u32` | ||
| - `FOUNDER_ADDRESSES_MAINNET: [&str; 48]` | ||
| - `FOUNDER_ADDRESSES_TESTNET: [&str; 48]` | ||
|
|
||
| ### Funding streams | ||
|
|
||
| The design suggests to implement the parameters needed for funding streams as: | ||
|
|
||
| ``` | ||
| /// The funding stream receiver categories | ||
| pub enum FundingStreamReceiver { | ||
| BootstrapProject, // TODO: it might be clearer to call this ECC, no-one uses the legal name | ||
| ZcashFoundation, | ||
| MajorGrants, | ||
| } | ||
|
|
||
| /// The numerator for each funding stream receiving category | ||
| /// as described in [protocol specification §7.9.1][7.9.1]. | ||
| /// | ||
| /// [7.9.1]: https://zips.z.cash/protocol/protocol.pdf#zip214fundingstreams | ||
| const FUNDING_STREAM_RECEIVER_NUMERATORS: &[(FundingStreamReceiver, u64)] = &[ | ||
| (FundingStreamReceiver::BootstrapProject, 7), | ||
| (FundingStreamReceiver::ZcashFoundation, 5), | ||
| (FundingStreamReceiver::MajorGrants, 8), | ||
| ]; | ||
|
|
||
| /// Denominator as described in [protocol specification §7.9.1][7.9.1]. | ||
| /// | ||
| /// [7.9.1]: https://zips.z.cash/protocol/protocol.pdf#zip214fundingstreams | ||
| pub const FUNDING_STREAM_RECEIVER_DENOMINATOR: u64 = 100; | ||
|
|
||
|
|
||
| /// Start and end Heights for funding streams | ||
| /// as described in [protocol specification §7.9.1][7.9.1]. | ||
| /// | ||
| /// [7.9.1]: https://zips.z.cash/protocol/protocol.pdf#zip214fundingstreams | ||
| const FUNDING_STREAM_HEIGHT_RANGES: &[(Network, Range<_>)] = &[ | ||
| (Network::Mainnet, Height(1_046_400)..Height(2_726_400)), | ||
| (Network::Testnet, Height(1_028_500)..Height(2_796_000)), | ||
| ]; | ||
| ``` | ||
|
|
||
| Each `FundingStreamReceiver` for the mainnet must have 48 payment addresses associated with and 51 for the testnet.* | ||
|
|
||
| https://zips.z.cash/zip-0214#mainnet-recipient-addresses | ||
|
|
||
| The following constants are needed: | ||
|
|
||
| - `FUNDING_STREAM_BP_ADDRESSES_MAINNET: [&str; 48]` | ||
| - `FUNDING_STREAM_ZF_ADDRESSES_MAINNET: [&str; 48]` | ||
| - `FUNDING_STREAM_MG_ADDRESSES_MAINNET: [&str; 48]` | ||
| - `FUNDING_STREAM_BP_ADDRESSES_TESTNET: [&str; 51]` | ||
| - `FUNDING_STREAM_ZF_ADDRESSES_TESTNET: [&str; 51]` | ||
| - `FUNDING_STREAM_MG_ADDRESSES_TESTNET: [&str; 51]` | ||
|
|
||
| \* The MG stream is subject to change. [ZIP 1014](https://zips.z.cash/zip-1014#direct-grant-option) and [ZIP 214](https://zips.z.cash/zip-0214) explicitly permit splitting the stream to allow for direct grant recipients that could change the number of addresses. This are not considered in this spec. | ||
|
|
||
| TODO: describe how we would handle this sort of change. For example: add an enum to `FUNDING_STREAM_HEIGHT_RANGES` for each different stream split. | ||
|
|
||
| ## General subsidy | ||
|
|
||
| The block subsidy and other utility functions are inside the general subsidy category. | ||
|
|
||
| https://zips.z.cash/protocol/protocol.pdf#subsidyconcepts | ||
|
|
||
| https://zips.z.cash/protocol/protocol.pdf#subsidies | ||
|
|
||
| - `block_subsidy(Height, Network) -> Result<Amount<NonNegative>, Error>` - Total block subsidy. | ||
| - `miner_subsidy(Height, Network) -> Result<Amount<NonNegative>, Error>` - Miner portion. | ||
| - `find_output_with_amount(&Transaction, Amount<NonNegative>) -> Vec<transparent::Output>` - Outputs where value equal to Amount. | ||
| - `shielded_coinbase(Height, Network, &Transaction) -> Result<(), Error>` - Validate shielded coinbase rules. | ||
|
|
||
| ## Founders reward | ||
|
|
||
| Only functions specific to calculation of founders reward. | ||
|
|
||
| https://zips.z.cash/protocol/protocol.pdf#foundersreward | ||
|
|
||
| - `founders_reward(Height, Network) -> Result<Amount<NonNegative>, Error>` - Founders reward portion for this block. | ||
| - `founders_address_change_interval() -> Height` - Calculates the founders reward change interval. `FounderAddressChangeInterval` in the protocol specs. | ||
| - `founders_reward_address(Height, Network) -> Result<PayToScriptHash, Error>` - Address of the receiver founder at this block. All specified founders reward addresses are transparent `zebra_chain::transparent:Address::PayToScriptHash` addresses. (Even after the shielded coinbase changes in ZIP-213, introduced in Heartwood.) | ||
| - `find_output_with_address(&Transaction, Address)` - Outputs where `lock_script` equal to `Address`. | ||
|
|
||
| ## Funding streams | ||
|
|
||
| Only functions specific to the calculation of funding streams. | ||
|
|
||
| https://zips.z.cash/protocol/protocol.pdf#fundingstreams | ||
|
|
||
| https://zips.z.cash/zip-0207 | ||
|
|
||
| https://zips.z.cash/zip-0214 | ||
|
|
||
| - `funding_stream(height, newtork) -> Result<Amount<NonNegative>, Error>` - Funding stream portion for this block. | ||
| - `funding_stream_address(height, network) -> Result<FundingStreamAddress, Error>` - Address of the funding stream receiver at this block. The funding streams addresses can be transparent `zebra_chain::transparent:Address::PayToScriptHash` or `zebra_chain::sapling:Address` addresses. | ||
|
|
||
| ## Consensus rules | ||
|
|
||
| `zebra-consensus/src/block.rs` will call the following function implemented inside `zebra-consensus/src/block/check.rs`: | ||
|
|
||
| `subsidy_is_valid(Network, &Block) -> Result<(), BlockError>` | ||
|
|
||
| `subsidy_is_valid()` will call individual functions(also implemented in `zebra-consensus/src/block/check.rs`) to verify the following consensus rules: | ||
|
|
||
| ### 1 - Founders reward: | ||
|
|
||
| *[Pre-Canopy] A coinbase transaction at `height` **MUST** include at least one output that pays exactly `FoundersReward(height)` zatoshi with a standard P2SH script of the form `OP_HASH160 FounderRedeemScriptHash (height) OP_EQUAL` as its scriptPubKey.* https://zips.z.cash/protocol/protocol.pdf#foundersreward | ||
|
|
||
| We make use of the founders reward functions here. We get the amount of the reward with `founders_reward(height, network)` and the address for the reward at height with `founders_reward_address(height, network)`. Next we get a list of outputs that match the amount with the utility function `find_output_with_amount()`. Finally with this list, we check if any of the output scripts addresses matches our computed address. | ||
|
|
||
| ### 2 - Funding stream: | ||
|
|
||
| *[Canopy onward] The coinbase transaction at `height` **MUST** contain at least one output per funding stream `fs` active at | ||
| height, that pays `fs.Value(height)` zatoshi in the prescribed way to the stream’s recipient address represented by `fs.AddressList` of `fs.AddressIndex(height)`.* https://zips.z.cash/protocol/protocol.pdf#fundingstreams | ||
|
|
||
| We make use of the funding streams functions here, similar to founders reward . We get the amount of the reward using `funding_stream(height, network)` and then the address with `funding_stream_address(height, network)`. Next we get a list of outputs that match the amount with the utility function `find_output_with_amount()`. Finally with this list, we check if any of the output scripts matches the address we have computed. | ||
|
|
||
| ### 3 - Shielded coinbase: | ||
|
|
||
| *[Pre-Heartwood] A coinbase transaction MUST NOT have any JoinSplit descriptions, Spend descriptions, or Output descriptions.* | ||
|
|
||
| *[Heartwood onward] A coinbase transaction MUST NOT have any JoinSplit descriptions or Spend descriptions.* | ||
|
|
||
| *[Heartwood onward] The consensus rules applied to `valueBalance`, `vShieldedOutput`, and `bindingSig` in non-coinbase transactions MUST also be applied to coinbase transactions.* | ||
|
|
||
| https://zips.z.cash/zip-0213#specification | ||
|
|
||
| This rules are implemented inside `shielded_coinbase()`. | ||
|
|
||
| ### Validation errors | ||
|
|
||
| A `SubsidyError` type will be created to handle validation of the above consensus rules: | ||
|
|
||
| ``` | ||
| pub enum SubsidyError { | ||
| #[error("not a coinbase transaction")] | ||
| NoCoinbase, | ||
|
|
||
| #[error("founders reward amount not found")] | ||
| FoundersRewardAmountNotFound, | ||
|
|
||
| #[error("founders reward address not found")] | ||
| FoundersRewardAddressNotFound, | ||
|
|
||
| #[error("funding stream amount not found")] | ||
| FundingStreamAmountNotFound, | ||
|
|
||
| #[error("funding stream address not found")] | ||
| FundingStreamAddressNotFound, | ||
|
|
||
| #[error("invalid shielded descriptions found")] | ||
| ShieldedDescriptionsInvalid, | ||
|
|
||
| #[error("broken rule in shielded transaction inside coinbase")] | ||
| ShieldedRuleBroken, | ||
| } | ||
| ``` | ||
| ## Implementation Plan | ||
|
|
||
| Required: | ||
| 1. Funding Streams Amounts (defer Shielded Coinbase) | ||
| 2. Funding Streams Addresses | ||
| 3. Shielded Coinbase for Funding Streams | ||
| 4. Miner Subsidy Amounts | ||
| 5. Shielded Coinbase for Miner Subsidy | ||
|
|
||
| Optional - might be replaced by a Canopy checkpoint: | ||
| 1. Founders Reward Amounts | ||
| 2. Founders Reward Addresses | ||
|
|
||
| The following contextual validation checks are out of scope for this RFC: | ||
| * Transaction Fees | ||
|
|
||
| Each stage should have code, unit tests, block test vector tests, and property tests, before moving on to the next stage. (A stage can be implemented in multiple PRs.) | ||
|
|
||
| ## Test Plan | ||
|
|
||
| For each network(Mainnet, Testnet), calculation of subsidy amounts need a `Height` as input and will output different amounts according to it. | ||
|
|
||
| - Test all subsidy amount functions(`block_subsidy()`, `miner_subsidy()` and `funding_stream()`) against all network upgrade events of each network and make sure they return the expected amounts according to known outputs. | ||
|
|
||
| For each network, the address of the reward receiver on each block will depend on the `Height`. | ||
|
|
||
| - Test the subsidy address function (`funding_stream_address()`) against all network upgrade events of each network and make sure they return the expected addresses. | ||
|
|
||
| - Note: the founders reward tests are a low priority. | ||
|
|
||
| Validation tests will test the consensus rules using real blocks from `zebra-test` crate. For both networks, blocks for all network upgrades were added to the crate in [#1096](https://github.com/ZcashFoundation/zebra/pull/1096). Blocks containing shielded coinbase were also introduced at [#1116](https://github.com/ZcashFoundation/zebra/pull/1116) | ||
|
|
||
| - Test validation functions(`subsidy_is_valid()`) against all the blocks zebra haves available in the test vectors collection for both networks(`zebra_test::vectors::MAINNET_BLOCKS` and `zebra_test::vectors::TESTNET_BLOCKS`), all blocks should pass validation. | ||
|
|
||
| The validation errors at `SubsidyError` must be tested at least once. | ||
|
|
||
| - Create tests to trigger each error from `SubsidyError`. | ||
|
|
||
| - Create a property test for each consensus rule, which makes sure that the rule is satisfied by arbitrary (randomised) blocks and transactions | ||
| - Create or update `Arbitrary` trait implementations for blocks and transactions, as needed | ||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.