implemented auto-unlock, now for testing...

This commit is contained in:
Aaron Blankstein
2022-08-17 16:58:55 -05:00
parent 59b5c4bd6b
commit 7bc7bccdd5
4 changed files with 142 additions and 46 deletions

View File

@@ -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));

View File

@@ -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,

View File

@@ -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(

View File

@@ -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)
}
}