mirror of
https://github.com/alexgo-io/stacks-puppet-node.git
synced 2026-05-25 18:21:36 +08:00
implemented auto-unlock, now for testing...
This commit is contained in:
@@ -50,6 +50,7 @@ use crate::chainstate::burn::{BlockSnapshot, ConsensusHash, OpsHash, SortitionHa
|
||||
use crate::chainstate::coordinator::{
|
||||
Error as CoordinatorError, PoxAnchorBlockStatus, RewardCycleInfo,
|
||||
};
|
||||
use crate::chainstate::stacks::boot::PoxStartCycleInfo;
|
||||
use crate::chainstate::stacks::db::{StacksChainState, StacksHeaderInfo};
|
||||
use crate::chainstate::stacks::index::marf::MARFOpenOpts;
|
||||
use crate::chainstate::stacks::index::marf::MarfConnection;
|
||||
@@ -748,6 +749,10 @@ impl db_keys {
|
||||
"sortition_db::last_anchor_block"
|
||||
}
|
||||
|
||||
pub fn pox_reward_cycle_unlocks(cycle: u64) -> String {
|
||||
format!("sortition_db::reward_set_unlocks::{}", cycle)
|
||||
}
|
||||
|
||||
pub fn pox_reward_set_size() -> &'static str {
|
||||
"sortition_db::reward_set::size"
|
||||
}
|
||||
@@ -1182,9 +1187,9 @@ impl<'a> SortitionHandleTx<'a> {
|
||||
test_debug!(
|
||||
"Pick recipients for anchor block {} -- {} reward recipient(s)",
|
||||
anchor_block,
|
||||
reward_set.len()
|
||||
reward_set.rewarded_addresses.len()
|
||||
);
|
||||
if reward_set.len() == 0 {
|
||||
if reward_set.rewarded_addresses.len() == 0 {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
@@ -1194,6 +1199,7 @@ impl<'a> SortitionHandleTx<'a> {
|
||||
|
||||
let chosen_recipients = reward_set_vrf_seed.choose_two(
|
||||
reward_set
|
||||
.rewarded_addresses
|
||||
.len()
|
||||
.try_into()
|
||||
.expect("BUG: u32 overflow in PoX outputs per commit"),
|
||||
@@ -1204,7 +1210,7 @@ impl<'a> SortitionHandleTx<'a> {
|
||||
recipients: chosen_recipients
|
||||
.into_iter()
|
||||
.map(|ix| {
|
||||
let recipient = reward_set[ix as usize].clone();
|
||||
let recipient = reward_set.rewarded_addresses[ix as usize].clone();
|
||||
info!("PoX recipient chosen";
|
||||
"recipient" => recipient.clone().to_b58(),
|
||||
"block_height" => block_height);
|
||||
@@ -1513,6 +1519,19 @@ impl<'a> SortitionHandleConn<'a> {
|
||||
Ok(anchor_block_hash)
|
||||
}
|
||||
|
||||
pub fn get_reward_cycle_unlocks(
|
||||
&mut self,
|
||||
cycle: u64,
|
||||
) -> Result<Option<PoxStartCycleInfo>, db_error> {
|
||||
let start_info = self
|
||||
.get_tip_indexed(&db_keys::pox_reward_cycle_unlocks(cycle))?
|
||||
.map(|x| {
|
||||
PoxStartCycleInfo::deserialize(&x)
|
||||
.expect("CORRUPTION: Failed to deserialize PoxStartCycleInfo from database")
|
||||
});
|
||||
Ok(start_info)
|
||||
}
|
||||
|
||||
fn get_reward_set_size(&self) -> Result<u16, db_error> {
|
||||
self.get_tip_indexed(&db_keys::pox_reward_set_size())
|
||||
.map(|x| {
|
||||
@@ -4366,7 +4385,7 @@ impl<'a> SortitionHandleTx<'a> {
|
||||
// if we've selected an anchor _and_ know of the anchor,
|
||||
// write the reward set information
|
||||
if let Some(mut reward_set) = reward_info.known_selected_anchor_block_owned() {
|
||||
if reward_set.len() > 0 {
|
||||
if reward_set.rewarded_addresses.len() > 0 {
|
||||
// if we have a reward set, then we must also have produced a recipient
|
||||
// info for this block
|
||||
let mut recipients_to_remove: Vec<_> = recipient_info
|
||||
@@ -4378,17 +4397,31 @@ impl<'a> SortitionHandleTx<'a> {
|
||||
recipients_to_remove.sort_unstable_by(|(_, a), (_, b)| b.cmp(a));
|
||||
// remove from the reward set any consumed addresses in this first reward block
|
||||
for (addr, ix) in recipients_to_remove.iter() {
|
||||
assert_eq!(&reward_set.remove(*ix as usize), addr,
|
||||
assert_eq!(&reward_set.rewarded_addresses.remove(*ix as usize), addr,
|
||||
"BUG: Attempted to remove used address from reward set, but failed to do so safely");
|
||||
}
|
||||
}
|
||||
|
||||
keys.push(db_keys::pox_reward_set_size().to_string());
|
||||
values.push(db_keys::reward_set_size_to_string(reward_set.len()));
|
||||
for (ix, address) in reward_set.iter().enumerate() {
|
||||
values.push(db_keys::reward_set_size_to_string(
|
||||
reward_set.rewarded_addresses.len(),
|
||||
));
|
||||
for (ix, address) in reward_set.rewarded_addresses.iter().enumerate() {
|
||||
keys.push(db_keys::pox_reward_set_entry(ix as u16));
|
||||
values.push(address.to_string());
|
||||
}
|
||||
// if there are qualifying auto-unlocks, record them
|
||||
if !reward_set.start_cycle_state.is_empty() {
|
||||
let cycle_number = Burnchain::static_block_height_to_reward_cycle(
|
||||
snapshot.block_height,
|
||||
self.context.first_block_height,
|
||||
self.context.pox_constants.reward_cycle_length.into(),
|
||||
)
|
||||
.expect("FATAL: PoX reward cycle started before first block height");
|
||||
|
||||
keys.push(db_keys::pox_reward_cycle_unlocks(cycle_number));
|
||||
values.push(reward_set.start_cycle_state.serialize());
|
||||
}
|
||||
} else {
|
||||
keys.push(db_keys::pox_reward_set_size().to_string());
|
||||
values.push(db_keys::reward_set_size_to_string(0));
|
||||
|
||||
@@ -63,6 +63,8 @@ use crate::chainstate::stacks::index::marf::MARFOpenOpts;
|
||||
|
||||
pub use self::comm::CoordinatorCommunication;
|
||||
|
||||
use super::stacks::boot::RewardSet;
|
||||
|
||||
pub mod comm;
|
||||
#[cfg(test)]
|
||||
pub mod tests;
|
||||
@@ -71,7 +73,7 @@ pub mod tests;
|
||||
/// reward cycle's relationship to its PoX anchor
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum PoxAnchorBlockStatus {
|
||||
SelectedAndKnown(BlockHeaderHash, Vec<StacksAddress>),
|
||||
SelectedAndKnown(BlockHeaderHash, RewardSet),
|
||||
SelectedAndUnknown(BlockHeaderHash),
|
||||
NotSelected,
|
||||
}
|
||||
@@ -96,7 +98,7 @@ impl RewardCycleInfo {
|
||||
SelectedAndKnown(_, _) | NotSelected => true,
|
||||
}
|
||||
}
|
||||
pub fn known_selected_anchor_block(&self) -> Option<&Vec<StacksAddress>> {
|
||||
pub fn known_selected_anchor_block(&self) -> Option<&RewardSet> {
|
||||
use self::PoxAnchorBlockStatus::*;
|
||||
match self.anchor_status {
|
||||
SelectedAndUnknown(_) => None,
|
||||
@@ -104,7 +106,7 @@ impl RewardCycleInfo {
|
||||
NotSelected => None,
|
||||
}
|
||||
}
|
||||
pub fn known_selected_anchor_block_owned(self) -> Option<Vec<StacksAddress>> {
|
||||
pub fn known_selected_anchor_block_owned(self) -> Option<RewardSet> {
|
||||
use self::PoxAnchorBlockStatus::*;
|
||||
match self.anchor_status {
|
||||
SelectedAndUnknown(_) => None,
|
||||
@@ -209,7 +211,7 @@ pub trait RewardSetProvider {
|
||||
burnchain: &Burnchain,
|
||||
sortdb: &SortitionDB,
|
||||
block_id: &StacksBlockId,
|
||||
) -> Result<Vec<StacksAddress>, Error>;
|
||||
) -> Result<RewardSet, Error>;
|
||||
}
|
||||
|
||||
pub struct OnChainRewardSetProvider();
|
||||
@@ -222,7 +224,7 @@ impl RewardSetProvider for OnChainRewardSetProvider {
|
||||
burnchain: &Burnchain,
|
||||
sortdb: &SortitionDB,
|
||||
block_id: &StacksBlockId,
|
||||
) -> Result<Vec<StacksAddress>, Error> {
|
||||
) -> Result<RewardSet, Error> {
|
||||
let registered_addrs =
|
||||
chainstate.get_reward_addresses(burnchain, sortdb, current_burn_height, block_id)?;
|
||||
|
||||
@@ -243,7 +245,7 @@ impl RewardSetProvider for OnChainRewardSetProvider {
|
||||
"participation" => participation,
|
||||
"liquid_ustx" => liquid_ustx,
|
||||
"registered_addrs" => registered_addrs.len());
|
||||
return Ok(vec![]);
|
||||
return Ok(RewardSet::empty());
|
||||
} else {
|
||||
info!("PoX reward cycle threshold computed";
|
||||
"burn_height" => current_burn_height,
|
||||
|
||||
@@ -28,8 +28,8 @@ use crate::chainstate::stacks::index::marf::MarfConnection;
|
||||
use crate::chainstate::stacks::Error;
|
||||
use crate::clarity_vm::clarity::ClarityConnection;
|
||||
use crate::clarity_vm::clarity::ClarityTransactionConnection;
|
||||
use crate::clarity_vm::database::PoxStartCycleInfo;
|
||||
use crate::core::{POX_MAXIMAL_SCALING, POX_THRESHOLD_STEPS_USTX};
|
||||
use crate::util_lib::strings::VecDisplay;
|
||||
use clarity::types::chainstate::BlockHeaderHash;
|
||||
use clarity::vm::contexts::ContractContext;
|
||||
use clarity::vm::costs::{
|
||||
@@ -159,8 +159,45 @@ pub struct RawRewardSetEntry {
|
||||
pub stacker: Option<PrincipalData>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct PoxStartCycleInfo {
|
||||
pub missed_reward_slots: Vec<(PrincipalData, u128)>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct RewardSet {
|
||||
pub rewarded_addresses: Vec<StacksAddress>,
|
||||
pub start_cycle_state: PoxStartCycleInfo,
|
||||
}
|
||||
|
||||
const POX_CYCLE_START_HANDLED_VALUE: &'static str = "1";
|
||||
|
||||
impl PoxStartCycleInfo {
|
||||
pub fn serialize(&self) -> String {
|
||||
serde_json::to_string(self).expect("FATAL: failure to serialize internal struct")
|
||||
}
|
||||
|
||||
pub fn deserialize(from: &str) -> Option<PoxStartCycleInfo> {
|
||||
serde_json::from_str(from).ok()
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.missed_reward_slots.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
impl RewardSet {
|
||||
/// Create an empty reward set where no one gets an early unlock
|
||||
pub fn empty() -> RewardSet {
|
||||
RewardSet {
|
||||
rewarded_addresses: vec![],
|
||||
start_cycle_state: PoxStartCycleInfo {
|
||||
missed_reward_slots: vec![],
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl StacksChainState {
|
||||
/// Return the MARF key used to store whether or not a given PoX
|
||||
/// cycle's "start" has been handled by the Stacks fork yet. This
|
||||
@@ -184,6 +221,8 @@ impl StacksChainState {
|
||||
db.put(&db_key, &POX_CYCLE_START_HANDLED_VALUE.to_string());
|
||||
}
|
||||
|
||||
/// Do all the necessary Clarity operations at the start of a PoX reward cycle.
|
||||
/// Currently, this just means applying any auto-unlocks to Stackers who qualified.
|
||||
pub fn handle_pox_cycle_start(
|
||||
clarity: &mut ClarityTransactionConnection,
|
||||
cycle_number: u64,
|
||||
@@ -563,25 +602,32 @@ impl StacksChainState {
|
||||
/// are repeated floor(stacked_amt / threshold) times.
|
||||
/// If an address appears in `addresses` multiple times, then the address's associated amounts
|
||||
/// are summed.
|
||||
pub fn make_reward_set(
|
||||
threshold: u128,
|
||||
mut addresses: Vec<RawRewardSetEntry>,
|
||||
) -> Vec<StacksAddress> {
|
||||
pub fn make_reward_set(threshold: u128, mut addresses: Vec<RawRewardSetEntry>) -> RewardSet {
|
||||
let mut reward_set = vec![];
|
||||
let mut missed_slots = vec![];
|
||||
// the way that we sum addresses relies on sorting.
|
||||
addresses.sort_by_key(|k| k.reward_address.bytes.0);
|
||||
while let Some(RawRewardSetEntry {
|
||||
reward_address: address,
|
||||
amount_stacked: mut stacked_amt,
|
||||
..
|
||||
stacker,
|
||||
}) = addresses.pop()
|
||||
{
|
||||
let mut contributed_stackers = vec![];
|
||||
if let Some(stacker) = stacker.as_ref() {
|
||||
contributed_stackers.push((stacker.clone(), stacked_amt));
|
||||
}
|
||||
// peak at the next address in the set, and see if we need to sum
|
||||
while addresses.last().map(|x| &x.reward_address) == Some(&address) {
|
||||
let additional_amt = addresses
|
||||
let next_contrib = addresses
|
||||
.pop()
|
||||
.expect("BUG: first() returned some, but pop() is none.")
|
||||
.amount_stacked;
|
||||
.expect("BUG: first() returned some, but pop() is none.");
|
||||
let additional_amt = next_contrib.amount_stacked;
|
||||
|
||||
if let Some(stacker) = next_contrib.stacker {
|
||||
contributed_stackers.push((stacker.clone(), additional_amt));
|
||||
}
|
||||
|
||||
stacked_amt = stacked_amt
|
||||
.checked_add(additional_amt)
|
||||
.expect("CORRUPTION: Stacker stacked > u128 max amount");
|
||||
@@ -599,9 +645,28 @@ impl StacksChainState {
|
||||
test_debug!("Add to PoX reward set: {:?}", &address);
|
||||
reward_set.push(address.clone());
|
||||
}
|
||||
// if stacker did not qualify for a slot *and* they have a stacker
|
||||
// pointer set by the PoX contract, then add them to auto-unlock list
|
||||
if slots_taken == 0 && !contributed_stackers.is_empty() {
|
||||
debug!(
|
||||
"Stacker missed reward slot, added to unlock list";
|
||||
// "stackers" => %VecDisplay(&contributed_stackers),
|
||||
"reward_address" => %address.clone().to_b58(),
|
||||
"threshold" => threshold,
|
||||
"stacked_amount" => stacked_amt
|
||||
);
|
||||
for (contributor, amt) in contributed_stackers {
|
||||
missed_slots.push((contributor, amt));
|
||||
}
|
||||
}
|
||||
}
|
||||
info!("Reward set calculated"; "slots_occuppied" => reward_set.len());
|
||||
reward_set
|
||||
RewardSet {
|
||||
rewarded_addresses: reward_set,
|
||||
start_cycle_state: PoxStartCycleInfo {
|
||||
missed_reward_slots: missed_slots,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_threshold_from_participation(
|
||||
|
||||
@@ -4,6 +4,7 @@ use rusqlite::{Connection, OptionalExtension};
|
||||
use crate::chainstate::burn::db::sortdb::{
|
||||
SortitionDB, SortitionDBConn, SortitionHandleConn, SortitionHandleTx,
|
||||
};
|
||||
use crate::chainstate::stacks::boot::PoxStartCycleInfo;
|
||||
use crate::chainstate::stacks::db::accounts::MinerReward;
|
||||
use crate::chainstate::stacks::db::{MinerPaymentSchedule, StacksChainState, StacksHeaderInfo};
|
||||
use crate::chainstate::stacks::index::MarfTrieId;
|
||||
@@ -237,8 +238,22 @@ pub trait SortitionDBRef: BurnStateDB {
|
||||
) -> Result<Option<PoxStartCycleInfo>, ChainstateError>;
|
||||
}
|
||||
|
||||
pub struct PoxStartCycleInfo {
|
||||
pub missed_reward_slots: Vec<(PrincipalData, u128)>,
|
||||
fn get_pox_start_cycle_info(
|
||||
handle: &mut SortitionHandleConn,
|
||||
parent_stacks_block_burn_ht: u64,
|
||||
cycle_index: u64,
|
||||
) -> Result<Option<PoxStartCycleInfo>, ChainstateError> {
|
||||
let descended_from_last_pox_anchor = match handle.get_last_anchor_block_hash()? {
|
||||
Some(pox_anchor) => handle.descended_from(parent_stacks_block_burn_ht, &pox_anchor)?,
|
||||
None => return Ok(None),
|
||||
};
|
||||
|
||||
if !descended_from_last_pox_anchor {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let start_info = handle.get_reward_cycle_unlocks(cycle_index)?;
|
||||
Ok(start_info)
|
||||
}
|
||||
|
||||
impl SortitionDBRef for SortitionHandleTx<'_> {
|
||||
@@ -256,18 +271,7 @@ impl SortitionDBRef for SortitionHandleTx<'_> {
|
||||
context.chain_tip = sortition_id.clone();
|
||||
let mut handle = SortitionHandleConn::new(&readonly_marf, context);
|
||||
|
||||
let descended_from_last_pox_anchor = match handle.get_last_anchor_block_hash()? {
|
||||
Some(pox_anchor) => handle.descended_from(parent_stacks_block_burn_ht, &pox_anchor)?,
|
||||
None => return Ok(None),
|
||||
};
|
||||
|
||||
if !descended_from_last_pox_anchor {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
Ok(Some(PoxStartCycleInfo {
|
||||
missed_reward_slots: vec![],
|
||||
}))
|
||||
get_pox_start_cycle_info(&mut handle, parent_stacks_block_burn_ht, cycle_index)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -279,15 +283,7 @@ impl SortitionDBRef for SortitionDBConn<'_> {
|
||||
cycle_index: u64,
|
||||
) -> Result<Option<PoxStartCycleInfo>, ChainstateError> {
|
||||
let mut handle = self.as_handle(sortition_id);
|
||||
|
||||
let descended_from_last_pox_anchor = match handle.get_last_anchor_block_hash()? {
|
||||
Some(pox_anchor) => handle.descended_from(parent_stacks_block_burn_ht, &pox_anchor)?,
|
||||
None => return Ok(None),
|
||||
};
|
||||
|
||||
Ok(Some(PoxStartCycleInfo {
|
||||
missed_reward_slots: vec![],
|
||||
}))
|
||||
get_pox_start_cycle_info(&mut handle, parent_stacks_block_burn_ht, cycle_index)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user