sip-022-emergency-pox-fix.md•17.1 kB
# Preamble
SIP Number: 022
Title: Emergency Fix to PoX Stacking Increases
Authors:
Aaron Blankstein <aaron@hiro.so>,
Brice Dobry <brice@hiro.so>,
Friedger Müffke <mail@friedger.de>,
Jude Nelson <jude@stacks.org>,
Pavitthra Pandurangan <pavi@stacks.org>,
Consideration: Technical, Governance
Type: Consensus
Status: Ratified
Created: 19 April 2023
License: BSD 2-Clause
Sign-off: Rafael Cárdenas <rafael@hiro.so> (SIP Editor), Jesse Wiley <jesse@stacks.org> (Acting Technical CAB Chair), Jason Schrader <jason@joinfreehold.com> (Governance CAB Chair), Jude Nelson <jude@stacks.org> (Steering Committee Chair)
Discussions-To: https://github.com/stacksgov/sips
# Abstract
On 19 April 2023, it was discovered that there was a bug in the PoX smart
contract which would allow a user to claim that they have stacked far, far more
STX than they had locked. Exploiting this bug both allows the user to increase
their PoX reward slot allotment, while also driving up the stacking minimum.
Furthermore, it creates the conditions for a network-wide crash: if the total
amount of STX stacked as reported by the PoX smart contract were to ever exceed
the total amount of liquid STX, then the node would crash into an irrecoverable
state. This bug has already been triggered in the wild.
This SIP proposes **two immediate consensus-breaking changes** that both prevents this bug
from being triggered in subsequent reward cycles, and replaces the current PoX
implementation with a new PoX implementation without the bug. If ratified, this
SIP would activate two hard forks 200 Bitcoin blocks prior to the start of
reward cycles #58 and #59.
This SIP would constitute two consensus-rules version bumps. During cycle #58, the system
version would be Stacks 2.2. During and after cycle #59, the system would be
Stacks 2.3.
# Addendum
*The following was added after this SIP was accepted, where some version number changes were necessary. The following section addresses these changes **without** changing the ratified text*
[SIP-023](./sips/sip-023/sip-023-emergency-fix-traits.md), which was accepted by the required CAB's on May 2nd, 2023 necessitates some changes to this SIP. The changes introduced in [SIP-023](./sips/sip-023/sip-023-emergency-fix-traits.md) are in response to a bug introduced by the first hard fork, referred to later in this SIP as `Stacks 2.2`. To address the issue, an intermediary version `Stacks 2.3` was created which supercedes the second hard fork as defined here.
As a result, the second hard fork defined later in this SIP, `Stacks 2.3` **is now changed to `Stacks 2.4`** due to the intermediary release required as a result of the first hard fork `Stacks 2.2`.
In addition to the version number change, [SIP-023](./sips/sip-023/sip-023-emergency-fix-traits.md) also necessitates a change to the peer network version defined later in this SIP as follows:
* Change the mainnet peer network version bits from `0x18000008` to `0x18000009`.
* Change the testnet peer network version bits from `0xfacade08` to `0xfacade09`.
# Addendum II
_The following was added after this SIP was accepted to reflect the updated activation height for cycle 60, where the current text addresses cycle 59 activation height. The following section addresses these changes **without** changing the ratified text_
[SIP-023](./sips/sip-023/sip-023-emergency-fix-traits.md), which was accepted by the required CAB's on May 2nd, 2023 defines specific [activation criteria](./sips/sip-022/sip-022-emergency-pox-fix.md#activation) for cycle #59. As a result of more testing being done prior to the release, this window shall be missed and the new target activation will be in cycle #60, or Bitcoin block `792051`.
Current text:
> The second hard fork will take effect 200 Bitcoin blocks prior to the start of reward cycle #59, which is Bitcoin block height 789751.
Is now changed to the following:
> The second hard fork will take effect 400 Bitcoin blocks prior to the start of the prepare phase for cycle #60, which is Bitcoin block height 791551.
The 3 values changed:
- Reward cycle is changing from `59` to `60`
- Activation height is change from `789751` to `791551 `
- Blocks prior to activation height for the start of the reward cycle is changed from `200` to `400` to allow more time for contract deployments and stacking transactions
# Introduction
[SIP-015](./sips/sip-015/sip-015-network-upgrade.md) proposed a new PoX smart
contract, `pox-2`, which included a new public function `stack-increase`. This
function allows a user to increase the amount of STX locked for PoX while the
account has locked STX. The `stack-increase` function calls an internal function
`increase-reward-cycle-entry` to update the PoX contract's data space to record
the increase.
The `increase-reward-cycle-entry` function has a bug in this code path. The bug
itself is annotated with comment lines starting with "(BUG)".
```clarity
(let ((existing-entry (unwrap-panic (map-get? reward-cycle-pox-address-list { reward-cycle: reward-cycle, index: reward-cycle-index })))
(existing-total (unwrap-panic (map-get? reward-cycle-total-stacked { reward-cycle: reward-cycle })))
(total-ustx (+ (get total-ustx existing-total) (get add-amount data))))
;; stacker must match
(asserts! (is-eq (get stacker existing-entry) (some (get stacker data))) none)
;; update the pox-address list
(map-set reward-cycle-pox-address-list
{ reward-cycle: reward-cycle, index: reward-cycle-index }
{ pox-addr: (get pox-addr existing-entry),
;; (BUG) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; (BUG) `total-ustx` is the amount of uSTX locked in this cycle, not
;; (BUG) the stacker's total amount of uSTX. This value ought to be
;; (BUG) `(+ (get total-ustx existing-entry) (get add-amount data))`
;; (BUG) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
total-ustx: total-ustx,
stacker: (some (get stacker data)) })
;; update the total
(map-set reward-cycle-total-stacked
{ reward-cycle: reward-cycle }
{ total-ustx: total-ustx })
(some { first-cycle: first-cycle,
reward-cycle: (+ u1 reward-cycle),
stacker: (get stacker data),
add-amount: (get add-amount data) })))))
```
The bug is reachable by any solo Stacker who calls `stack-increase`. Any solo
Stacker who increases their total STX locked will erroneously set the amount of
uSTX backing their PoX address to be equal to the current total number of uSTX
locked in this cycle (the value `total-ustx`), instead of the sum of their
current locked uSTX amount and their added amount. The act of triggering this
bug is an unavoidable consequence of calling the `stack-increase` function --
individuals who call `stack-increase` are not thought to be deliberately
exploiting the system.
This bug was first triggered in the wild by Stacks transaction
`20e708e250bad3fb5d5ab84a70365c3c1cf0520c7a9f67cd5ff6b9fa94335ea5`.
The immediate consequences for PoX of this bug being triggered are as follows:
* The total STX locked to the PoX address owned by the caller of `stack-increase`
will be potentially higher than the amount of STX that the caller possesses.
The caller will receive PoX payouts _as if_ they had locked that much STX. So,
the caller receives undue BTC.
* The stacking threshold is raised to account for the PoX contract's reported
increase in STX locked.
Similar consequences are expected when a Stacker contributes to two different PoX addresses and
at least one PoX address would benefit from auto-unlocking. This behavior has not been seen in the wild.
Furthermore, if this bug is triggered enough times, the Stacks network will crash. This is
because of a runtime assertion in the PoX reward set calculation logic that
verifies that the total locked STX does not exceed the total liquid STX. This
would no longer be true due to this bug. The offending assertion is detailed
below:
```rust
pub fn get_reward_threshold_and_participation(
pox_settings: &PoxConstants,
addresses: &[RawRewardSetEntry],
liquid_ustx: u128,
) -> (u128, u128) {
let participation = addresses
.iter()
.fold(0, |agg, entry| agg + entry.amount_stacked);
assert!(
participation <= liquid_ustx,
"CORRUPTION: More stacking participation than liquid STX"
);
```
The `RawRewardSetEntry` data are pulled directly from the
`reward-cycle-pox-address-list` data map, and the `.amount_stacked` field is
equal to the `total-ustx` field that was erroneously set in `stack-increase`.
Considering these consequences with respect to the [draft blockchain catastrophic
failure and recovery guidelines](https://github.com/stacksgov/sips/pull/10),
this bug would be categorized as requiring an _immediate_ hard fork to rectify.
The network has not yet crashed, but it is in imminent danger of crashing and
there is insufficient time to execute a Stacker-based SIP vote as has been customary for
past hard forks. This SIP instead proposes that this hard fork activate at the
start of the next reward cycle (#58) at the time of this writing.
There is less than one reward cycle remaining to fix this
problem, and yet a Stacker vote would require at least one complete reward cycle
to carry out a vote (not accounting for any additional time required for
sending out adequate public communications and tabulating the vote afterwards).
A subsequent hard fork to re-enable PoX would have the effect of restoring
Stacker voting.
# Specification
Given the lack of time to conduct a Stacker vote to activate this SIP, the
proposed fix in this SIP is as parsimonious and discreet as possible. It will
execute as a set of **two hard forks**.
The first hard fork, which activates before the start of reward cycle #58, will disable PoX beginning with cycle #58.
The `pox-2` contract will be considered defunct, just as the pre-2.1 `pox`
contract is. This hard fork would do the following:
* Prevent all stacking functions from being called in `pox-2`. The `pox-2`
contract would be considered defunct, like the `pox` contract is now.
* Set the minimum required block-commit memo bits to `0x07`. All block-commits
after the Bitcoin block activation height must have a memo value of at least
`0x07`. This ensures that miners that do not upgrade from Stacks 2.1 will not
be able to mine in Stacks 2.2.
* Set the mainnet peer network version bits to `0x18000007`. This ensures that follower
nodes that do not upgrade to Stacks 2.2 will not be able to talk to Stacks
2.2 nodes.
* Set the testnet peer network version bits to `0xfacade07`. This ensures that
testnet follower nodes that do not upgrade to Stacks 2.2 will not be able to
talk to Stacks 2.2 nodes.
The second hard fork will instantiate a new PoX
implementation `pox-3`. The `pox-3` contract code will:
* Fix the aforementioned `stacks-increase` bug.
* Prevent Stackers from contributing to two or more PoX addresses. In particular,
* Add a `delegated-to` field in the `stacking-state` data map, so that the
`stacking-state` entry for a PoX user will indicate the principal to which
their STX are delegated (if they are using delegated stacking).
* Add checks to the delegated stacking public functions that validate the
`delegated-to` field in the Stacker's `stacking-state` map entry.
* Set the minimum required block-commit memo bits to `0x08`. All block-commits
after the Bitcoin block activation height must have a memo value of at least
`0x08`. This ensures that miners that do not upgrade from Stacks 2.2 will not
be able to mine in Stacks 2.3.
* Set the mainnet peer network version bits to `0x18000008`. This ensures that follower
nodes that do not upgrade to Stacks 2.3 will not be able to talk to Stacks
2.3 nodes.
* Set the testnet peer network version bits to `0xfacade08`. This ensures that
testnet follower nodes that do not upgrade to Stacks 2.3 will not be able to
talk to Stacks 2.3 nodes.
## Alternative Approaches Considered
It is unusual that this SIP proposes two hard forks in rapid succession without
a Stacker-based vote for activating the new rules. The
reason for this is because PoX must first be disabled prior to the start of
reward cycle #58 in order to avert a network-wide
crash. This cannot be delayed, but there is insufficient time to prepare `pox-3`
or even execute a Stacker-based vote before this bug must be addressed.
Therefore, the first hard fork must happen immediately.
A second hard fork is necessary to instantiate `pox-3`, which must also happen
as soon as possible (no more than one reward cycle later) in order to restore
critical functionality to the Stacks blockchain. Because PoX would be disabled
before `pox-3` would activate, there would not exist a means of carrying out a
Stacker-based vote to upgrade the system (as had been the case in all prior
hard forks). Beyond receiving a BTC yield, **the lack of a functioning PoX contract
prevents consensus-level SIPs from being voted upon**, which necessitates an
expedient restoration.
A less-disruptive alternative approach of repairing the
`reward-cycle-pox-address-list` data map was considered. This would have only
required a single hard fork, and no disruption of PoX payouts (however,
`stack-increase` would have been disabled). This approach was ultimately dropped
because of the interactions between delegated stacking and solo stacking -- it
would not be possible to retroactively compute the correct values for the
`reward-cycle-pox-address-list` data map. This, in turn, meant that any repair
tactic would require the node to calculate the correct values for
`reward-cycle-pox-address-list` as the blocks in reward cycle #57 were
processed, which in turn meant that nodes would need to boot from genesis to
derive the correct data with which to repair the data map at the start of reward
cycle #58. There is insufficient time to implement, test, and deploy this
approach.
# Related Work
Consensus bugs requiring immediate attention such as this
have been detected and fixed in other blockchains. In the
absence of a means of gathering user comments on proposed fixes, the task of
activating these bugfixes has fallen to miners, exchanges, and node runners. As
long as sufficiently many participating entities upgrade, then a chain split is
avoided and the fixed blockchain survives. A prominent example was Bitcoin
[CVE-2010-5139](https://www.cvedetails.com/cve/CVE-2010-5139/), in which a
specially-crafted Bitcoin transaction could mint arbitrarily many BTC well above
the 21 million cap. The [developer
response](https://bitcointalk.org/index.php?topic=823.0) was to quickly release
a patched version of Bitcoin and rally enough miners and users to upgrade. In a
matter of hours, the canonical Bitcoin chain ceased to include any transactions
that minted too much BTC.
# Backwards Compatibility
There are no changes to the chainstate database schemas in this SIP. Everyone
who runs a Stacks 2.1 node today will be able to run a Stacks 2.2 node off of
their existing chainstates at the start of reward cycle #58,
as well as a Stacks 2.3 node off of a Stacks 2.2 chainstate at the start of
reward cycle #59.
Stacks 2.2 nodes will not interact with Stacks 2.1 nodes on the peer network
after the Bitcoin block activation height passes. In addition, Stacks 2.2 nodes
will ignore block-commits from Stacks 2.1 nodes. Similar changes were made for
Stacks 2.05 and Stacks 2.1 to ensure that the new network cleanly separates from
stragglers still following the old rules. This also applies to Stacks 2.3 nodes
and Stacks 2.2 nodes.
# Activation
This SIP shall be considered Activated if the Stacks 2.3 network is live at the
start of reward cycle #59.
The Bitcoin block activation height for the first hard fork will need to pass prior to the selection of
the PoX anchor block for reward cycle #58 (Bitcoin block height 787851). This
SIP proposes Bitcoin block height 787651, which is 200 Bitcoin blocks prior. In
other words, the Bitcoin block that is mined 100 blocks prior to the start of
the prepare phase for reward cycle #58 shall be the activation height.
The second hard fork will take effect 200 Bitcoin blocks prior to the start of reward cycle #59,
which is Bitcoin block height 789751.
The node software for Stacks 2.2 and 2.3 shall be merged to the `master` branch of the
reference implementation no later than three days prior to the activation
height. This means that everyone shall have at least three days to upgrade
their Stacks 2.1 nodes to Stacks 2.2, and at least three days to upgrade from
2.2 to 2.3.
# Reference Implementation
The reference implementation of this SIP can be found in the
`feat/sip-022-pox-disable` branch of
the Stacks blockchain reference implementation. It is available at
https://github.com/stacks-network/stacks-blockchain.