Skip to content
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

Feat/cap discount mining #3135

Closed
wants to merge 29 commits into from
Closed

Feat/cap discount mining #3135

wants to merge 29 commits into from

Conversation

jcnelson
Copy link
Member

@jcnelson jcnelson commented May 14, 2022

This PR addresses #3095 (and #2613) by capping the amount of BTC a PoX output can pay. If a miner wants to spend more BTC than this amount, then they must burn it via the OP_RETURN output. This puts an upper bound on how much of a discount a miner who stacks can expect to receive.

The PoX cutoff is calculated on a reward cycle basis within the prepare phase. Specifically, the code examines the sequence of BTC burns B by the winning block-commits in the prepare phase, and sets the cutoff C = 4 * min(mean(B), median(B)). If a miner wants to spend S > C BTC, they must burn S - C BTC. In addition, if the miner spends S <= C BTC, they must spend it by sending it to PoX outputs (i.e. they may not burn it). This only applies to reward cycles with PoX; nothing changes for PoB reward cycles, or for miners who want to mine off of a fork that does not descend from the anchor block.

The rationale for this cutoff calculation is as follows. Consider a miner who wants to mine at a steep discount of their choosing by stacking STX and spending an unbound amount of BTC when their reward addresses come up for payment. The cutoff calculation is designed so that the only way a miner can unilaterally choose their discount is by executing a 51% attack on the blockchain during the prepare-phase. To set a target cutoff C, the miner must both ensure that the majority of winning block-commits spend at least 0.25C BTC (i.e. they must win at least 51 blocks to guarantee this), and must ensure that the average winning block-commit spends at least 0.25C BTC (i.e. they have to burn enough BTC, no matter their win rate, to push the average up). The 4x multiplier was chosen by examining the cutoff value for all of the system's reward cycles to date: in all but one reward cycle (the 3rd one in the system, when there was a large fluctuation in mining payouts), this cutoff is always higher than the network's PoX outputs.

When working on this PR, I noticed that the now-obsolete sunset_burn field was not being used to calculate the sortition weight of a block-commit when calculating the burn distribution. This was likely a bug, but one that we didn't ever see in the wild because the required sunset burn has been 0. Nevertheless, this PR (1) makes it so that the total sortition weight of a block-commit includes both the payment to PoX outputs and the burnt amount, and (2) gates this calculation so that it only applies in 2.1. Prior to this, a block-commit that burns BTC via its OP_RETURN will be treated as if it did not do any burn at all -- i.e. the burn won't improve the block-commit's chances of winning, which is in-line with how 2.05 behaves today.

The PoX cutoff itself is stored in the sortition DB as part of its MARF, so each reward cycle has a distinct PoX cutoff value. This value is not queried except in epoch 2.1, so it should be safe to migrate the existing chainstate to this node's new chainstate as long as its done before 2.1 activates. To accommodate this, there's a special rule for the PoX cutoff in the event that 2.1 activates in the middle of a reward cycle: in this partially-2.1 reward cycle, the cutoff is u64::MAX (i.e. there's no cutoff). The cutoff is only calculated and used in the first full 2.1 reward cycle.

As for testing, I've add the following coverage:

  • The block-commit validation tests use a mocked PoX reward cycle in order to verify that a block-commit must burn the right amount of BTC if it overpays, and verifies that the block-commit cannot underpay PoX outputs by burning if it does not meet the cutoff.

  • The sortition-processing validation tests verify that the epoch gating for calculating a block-commit's sortition weight applies, and that a block-commit's destroyed BTC gets applied only in epoch 2.1.

  • The sortition DB testing covers the new PoX cutoff calculation logic, as well as the cutoff storage and retrieval

  • There is a neon integration test that verifies that a miner that overpays BTC in PoX cannot win sortitions

EDIT: For clarity, this PR does not make it so that a discount-miner can receive a maximum 4x discount, or anything like that. What it does is it makes it so that a discount-miner must maintain multiple independent UTXO chains (i.e. run multiple separate miner nodes) in order to avoid burning BTC. This raises the cost of attempting to discount-mine.

Today, a discount-miner would need to spend their target BTC for the 3 blocks prior to the block in which they intend to receive a discount. This is because the sortition weight of a miner is the minimum of the last BTC spend they made, and the median spend they made in the last 6 blocks (as tracked by the miner's UTXO chain). With this PR, the amount of BTC the miner would be allowed to recover per UTXO chain is capped by a function of the BTC burnt in the last prepare phase. So in order to receive more BTC back than this threshold C, the discount-miner would be required to run multiple UTXO-chains. If the miner wanted to spend k * C BTC, they would need to maintain k UTXO chains, and pay k times more BTC transaction fees (whereas today, they only need to pay the fees for a single UTXO chain).

jcnelson added 25 commits May 9, 2022 15:02
…se when finding the PoX anchor block, and add a test for it
…y weight the destroyed tokens to be paid if the commit exceeds the PoX limit for this reward cycle.
…d) when calculating a sortition's total burn
…y PoX output that exceeds 4 * min(mean(prepare_phase_winning_burns), median(prepare_phase_winning_burns)) must burn the difference via the OP_RETURN output. This information is tracked on a per-PoX-fork basis (i.e. in the sortition MARF). To support migration, the `sunset` column of block-commits is repurposed to track the extra burnchain tokens burnt.
…mine the `burn_fee` to include the PoX-cutoff-destroyed tokens after 2.1
…ts total spend and the PoX cutoff in order to be valid.
…ic uses `.total_spend()` instead of `.sortition_spend()` to determine the next reward cycle's PoX cutoff (TL;DR: it's safe to use this in an ungated fashion because the code that uses it is gated).
…och so we don't count `destroyed` tokens in block-commit OP_RETURNs until Stacks 2.1
…ted by epoch to compensate for the PoX cutoff feature, when determining the spends for a block-commit and the sortition that contains it.
…ivates, we look to the right contract at the right time.
…_burn` BTC for purposes of both paying it and assessing it in fee calculations
…when sending a block-commit (the logic for loading the PoX commit works in all epochs -- the "right value" will be returned pre-2.1 to make this calculation result in a 0)
…s PoX outputs and exceeds the PoX cutoff will be rejected by the network in 2.1
@codecov
Copy link

codecov bot commented May 14, 2022

Codecov Report

Merging #3135 (be7b63f) into next (e7f3c55) will decrease coverage by 1.91%.
The diff coverage is 80.66%.

@@            Coverage Diff             @@
##             next    #3135      +/-   ##
==========================================
- Coverage   83.64%   81.72%   -1.92%     
==========================================
  Files         267      267              
  Lines      209188   210838    +1650     
==========================================
- Hits       174981   172316    -2665     
- Misses      34207    38522    +4315     
Impacted Files Coverage Δ
src/chainstate/burn/operations/mod.rs 45.39% <0.00%> (-0.66%) ⬇️
src/main.rs 0.08% <0.00%> (-0.01%) ⬇️
testnet/stacks-node/src/tests/epoch_205.rs 30.59% <0.00%> (-67.45%) ⬇️
testnet/stacks-node/src/tests/epoch_21.rs 0.00% <0.00%> (ø)
src/chainstate/coordinator/mod.rs 89.65% <68.75%> (-0.94%) ⬇️
src/net/rpc.rs 24.30% <73.33%> (-7.41%) ⬇️
...-node/src/burnchains/bitcoin_regtest_controller.rs 84.00% <76.92%> (-1.90%) ⬇️
testnet/stacks-node/src/neon_node.rs 79.86% <92.30%> (-0.27%) ⬇️
src/chainstate/burn/db/sortdb.rs 95.51% <98.91%> (+0.28%) ⬆️
.../chainstate/burn/operations/leader_block_commit.rs 96.25% <99.30%> (+0.66%) ⬆️
... and 74 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update f38a266...be7b63f. Read the comment docs.

@jcnelson
Copy link
Member Author

Need to re-think the cap function so that a miner can't just run a large number of small miners during the reward phase in order to give itself more room to discount mine.

@jcnelson
Copy link
Member Author

Spoke more with @kantai about this. I don't think this PR does enough to really make a difference in discount-mining. The fact that we have (1) a mining window where a miner's sortition weight is min(last_commit, median_commit) and (2) the reject-pox function are both good enough for the time being to deter discount-mining. We can revisit this in a subsequent network upgrade.

@jcnelson jcnelson closed this May 17, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

1 participant