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

Reward set calculation and RPC endpoint #4311

Merged
merged 12 commits into from
Feb 6, 2024
1 change: 1 addition & 0 deletions .github/workflows/bitcoin-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ jobs:
- tests::nakamoto_integrations::mine_multiple_per_tenure_integration
- tests::nakamoto_integrations::block_proposal_api_endpoint
- tests::nakamoto_integrations::miner_writes_proposed_block_to_stackerdb
- tests::nakamoto_integrations::correct_burn_outs
- tests::signer::stackerdb_dkg_sign
- tests::signer::stackerdb_block_proposal
steps:
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ and this project adheres to the versioning scheme outlined in the [README.md](RE

### Added

- New RPC endpoint `/v2/stacker_set/{cycle_number}` to fetch stacker sets in PoX-4
- New `/new_pox_anchor` endpoint for broadcasting PoX anchor block processing.
- Stacker bitvec in NakamotoBlock

Expand Down
1 change: 1 addition & 0 deletions clarity/src/vm/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ pub enum RuntimeErrorType {
UnwrapFailure,
DefunctPoxContract,
PoxAlreadyLocked,
MetadataAlreadySet,
}

#[derive(Debug, PartialEq)]
Expand Down
4 changes: 4 additions & 0 deletions docs/rpc/api/core-node/get_stacker_set.400.example.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"response": "error",
"err_msg": "Could not read reward set. Prepare phase may not have started for this cycle yet. Cycle = 22, Err= PoXAnchorBlockRequired"
obycode marked this conversation as resolved.
Show resolved Hide resolved
}
25 changes: 25 additions & 0 deletions docs/rpc/api/core-node/get_stacker_set.example.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"stacker_set": {
"rewarded_addresses": [
{
"Standard": [
{
"bytes": "dc5f18421006ee2b98ab972edfa7268a981e3f00",
"version": 26
},
"SerializeP2PKH"
]
}
],
"signers": [
{
"signing_key": "02d0a27e4f1bf186b4391eecfcc4d4a0d403684ad089b477b8548a69dd6378bf26",
"slots": 1,
"stacked_amt": 2143020000000000
}
],
"start_cycle_state": {
"missed_reward_slots": []
}
}
}
24 changes: 24 additions & 0 deletions docs/rpc/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -583,3 +583,27 @@ paths:
application/json:
example:
$ref: ./api/core-node/post-block-proposal-req.example.json

/v2/stacker_set/{cycle_number}:
get:
summary: Fetch the stacker and signer set information for a given cycle.
tags:
- Mining
operationId: get_stacker_set
description: |
Used to get stacker and signer set information for a given cycle.

This will only return information for cycles started in Epoch-2.5 where PoX-4 was active.
obycode marked this conversation as resolved.
Show resolved Hide resolved
responses:
200:
description: Information for the given reward cycle
content:
application/json:
example:
$ref: ./api/core-node/get_stacker_set.example.json
400:
description: Could not fetch the given reward set
content:
application/json:
example:
$ref: ./api/core-node/get_stacker_set.400.example.json
12 changes: 12 additions & 0 deletions stackslib/src/burnchains/burnchain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -489,6 +489,18 @@ impl Burnchain {
.reward_cycle_to_block_height(self.first_block_height, reward_cycle)
}

pub fn next_reward_cycle(&self, block_height: u64) -> Option<u64> {
let cycle = self.block_height_to_reward_cycle(block_height)?;
let effective_height = block_height.checked_sub(self.first_block_height)?;
let next_bump = if effective_height % u64::from(self.pox_constants.reward_cycle_length) == 0
{
0
} else {
1
};
Some(cycle + next_bump)
}

pub fn block_height_to_reward_cycle(&self, block_height: u64) -> Option<u64> {
self.pox_constants
.block_height_to_reward_cycle(self.first_block_height, block_height)
Expand Down
73 changes: 53 additions & 20 deletions stackslib/src/chainstate/coordinator/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,15 @@ pub trait RewardSetProvider {
sortdb: &SortitionDB,
block_id: &StacksBlockId,
) -> Result<RewardSet, Error>;

fn get_reward_set_nakamoto(
&self,
cycle_start_burn_height: u64,
chainstate: &mut StacksChainState,
burnchain: &Burnchain,
sortdb: &SortitionDB,
block_id: &StacksBlockId,
) -> Result<RewardSet, Error>;
}

pub struct OnChainRewardSetProvider<'a, T: BlockEventDispatcher>(pub Option<&'a T>);
Expand All @@ -312,6 +321,16 @@ impl<'a, T: BlockEventDispatcher> RewardSetProvider for OnChainRewardSetProvider
let cycle = burnchain
.block_height_to_reward_cycle(cycle_start_burn_height)
.expect("FATAL: no reward cycle for burn height");
// `self.get_reward_set_nakamoto` reads the reward set from data written during
// updates to .signers
// `self.get_reward_set_epoch2` reads the reward set from the `.pox-*` contract
//
// Data **cannot** be read from `.signers` in epoch 2.5 because the write occurs
// in the first block of the prepare phase, but the PoX anchor block is *before*
// the prepare phase. Therefore, we fetch the reward set in the 2.x style, and then
// apply the necessary nakamoto assertions if the reward set is going to be
// active in Nakamoto (i.e., check for signer set existence).

let is_nakamoto_reward_set = match SortitionDB::get_stacks_epoch_by_epoch_id(
sortdb.conn(),
&StacksEpochId::Epoch30,
Expand All @@ -325,33 +344,47 @@ impl<'a, T: BlockEventDispatcher> RewardSetProvider for OnChainRewardSetProvider
// if epoch-3.0 isn't defined, then never use a nakamoto reward set.
None => false,
};
let reward_set = if !is_nakamoto_reward_set {
// Stacks 2.x epoch
self.get_reward_set_epoch2(
cycle_start_burn_height,
chainstate,
burnchain,
sortdb,
block_id,
cur_epoch,
)?
} else {
// Nakamoto epoch
self.get_reward_set_nakamoto(
cycle_start_burn_height,
chainstate,
burnchain,
sortdb,
block_id,
)?
};

let reward_set = self.get_reward_set_epoch2(
cycle_start_burn_height,
chainstate,
burnchain,
sortdb,
block_id,
cur_epoch,
)?;

if is_nakamoto_reward_set {
if reward_set.signers.is_none() || reward_set.signers == Some(vec![]) {
error!("FATAL: Signer sets are empty in a reward set that will be used in nakamoto"; "reward_set" => ?reward_set);
return Err(Error::PoXAnchorBlockRequired);
}
}

if let Some(dispatcher) = self.0 {
dispatcher.announce_reward_set(&reward_set, block_id, cycle);
}

Ok(reward_set)
}

fn get_reward_set_nakamoto(
&self,
cycle_start_burn_height: u64,
chainstate: &mut StacksChainState,
burnchain: &Burnchain,
sortdb: &SortitionDB,
block_id: &StacksBlockId,
) -> Result<RewardSet, Error> {
self.read_reward_set_nakamoto(
cycle_start_burn_height,
chainstate,
burnchain,
sortdb,
block_id,
false,
)
}
}

impl<'a, T: BlockEventDispatcher> OnChainRewardSetProvider<'a, T> {
Expand Down
11 changes: 11 additions & 0 deletions stackslib/src/chainstate/coordinator/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -520,6 +520,17 @@ impl RewardSetProvider for StubbedRewardSetProvider {
signers: None,
})
}

fn get_reward_set_nakamoto(
&self,
cycle_start_burn_height: u64,
chainstate: &mut StacksChainState,
burnchain: &Burnchain,
sortdb: &SortitionDB,
block_id: &StacksBlockId,
) -> Result<RewardSet, CoordError> {
panic!("Stubbed reward set provider cannot be invoked in nakamoto")
jcnelson marked this conversation as resolved.
Show resolved Hide resolved
}
}

fn make_reward_set_coordinator<'a>(
Expand Down
Loading