correlate non-delegated stack-stx lockups with stacker

This commit is contained in:
Aaron Blankstein
2022-08-11 10:22:55 -05:00
parent 43b3398c42
commit 811257e69b
2 changed files with 299 additions and 118 deletions

View File

@@ -148,6 +148,12 @@ fn tuple_to_pox_addr(tuple_data: TupleData) -> (AddressHashMode, Hash160) {
(version, hashbytes)
}
pub struct RawRewardSetEntry {
pub reward_address: StacksAddress,
pub amount_stacked: u128,
pub stacker: Option<PrincipalData>,
}
impl StacksChainState {
fn eval_boot_code_read_only(
&mut self,
@@ -275,17 +281,23 @@ impl StacksChainState {
/// are summed.
pub fn make_reward_set(
threshold: u128,
mut addresses: Vec<(StacksAddress, u128)>,
mut addresses: Vec<RawRewardSetEntry>,
) -> Vec<StacksAddress> {
let mut reward_set = vec![];
// the way that we sum addresses relies on sorting.
addresses.sort_by_key(|k| k.0.bytes.0);
while let Some((address, mut stacked_amt)) = addresses.pop() {
addresses.sort_by_key(|k| k.reward_address.bytes.0);
while let Some(RawRewardSetEntry {
reward_address: address,
amount_stacked: mut stacked_amt,
..
}) = addresses.pop()
{
// peak at the next address in the set, and see if we need to sum
while addresses.last().map(|x| &x.0) == Some(&address) {
let (_, additional_amt) = addresses
while addresses.last().map(|x| &x.reward_address) == Some(&address) {
let additional_amt = addresses
.pop()
.expect("BUG: first() returned some, but pop() is none.");
.expect("BUG: first() returned some, but pop() is none.")
.amount_stacked;
stacked_amt = stacked_amt
.checked_add(additional_amt)
.expect("CORRUPTION: Stacker stacked > u128 max amount");
@@ -328,12 +340,12 @@ impl StacksChainState {
pub fn get_reward_threshold_and_participation(
pox_settings: &PoxConstants,
addresses: &[(StacksAddress, u128)],
addresses: &[RawRewardSetEntry],
liquid_ustx: u128,
) -> (u128, u128) {
let participation = addresses
.iter()
.fold(0, |agg, (_, stacked_amt)| agg + stacked_amt);
.fold(0, |agg, entry| agg + entry.amount_stacked);
assert!(
participation <= liquid_ustx,
@@ -359,25 +371,13 @@ impl StacksChainState {
(threshold, participation)
}
/// Each address will have at least (get-stacking-minimum) tokens.
pub fn get_reward_addresses(
fn get_reward_addresses_pox_1(
&mut self,
burnchain: &Burnchain,
sortdb: &SortitionDB,
current_burn_height: u64,
block_id: &StacksBlockId,
) -> Result<Vec<(StacksAddress, u128)>, Error> {
let reward_cycle = burnchain
.block_height_to_reward_cycle(current_burn_height)
.ok_or(Error::PoxNoRewardCycle)?;
let reward_cycle_start_height = burnchain.reward_cycle_to_block_height(reward_cycle);
let pox_contract_name = burnchain
.pox_constants
.active_pox_contract(reward_cycle_start_height);
if !self.is_pox_active(sortdb, block_id, reward_cycle as u128, pox_contract_name)? {
reward_cycle: u64,
) -> Result<Vec<RawRewardSetEntry>, Error> {
if !self.is_pox_active(sortdb, block_id, reward_cycle as u128, POX_1_NAME)? {
debug!(
"PoX was voted disabled in block {} (reward cycle {})",
block_id, reward_cycle
@@ -385,14 +385,12 @@ impl StacksChainState {
return Ok(vec![]);
}
debug!("Using pox_contract = {}", pox_contract_name);
// how many in this cycle?
let num_addrs = self
.eval_boot_code_read_only(
sortdb,
block_id,
pox_contract_name,
POX_1_NAME,
&format!("(get-reward-set-size u{})", reward_cycle),
)?
.expect_u128();
@@ -410,7 +408,7 @@ impl StacksChainState {
.eval_boot_code_read_only(
sortdb,
block_id,
pox_contract_name,
POX_1_NAME,
&format!("(get-reward-set-pox-address u{} u{})", reward_cycle, i),
)?
.expect_optional()
@@ -439,16 +437,138 @@ impl StacksChainState {
false => hash_mode.to_version_testnet(),
};
let reward_address = StacksAddress::new(version, hash);
debug!(
"PoX reward address (for {} ustx): {}",
total_ustx,
&StacksAddress::new(version, hash)
total_ustx, &reward_address,
);
ret.push((StacksAddress::new(version, hash), total_ustx));
ret.push(RawRewardSetEntry {
reward_address,
amount_stacked: total_ustx,
stacker: None,
})
}
Ok(ret)
}
fn get_reward_addresses_pox_2(
&mut self,
sortdb: &SortitionDB,
block_id: &StacksBlockId,
reward_cycle: u64,
) -> Result<Vec<RawRewardSetEntry>, Error> {
if !self.is_pox_active(sortdb, block_id, reward_cycle as u128, POX_2_NAME)? {
debug!(
"PoX was voted disabled in block {} (reward cycle {})",
block_id, reward_cycle
);
return Ok(vec![]);
}
// how many in this cycle?
let num_addrs = self
.eval_boot_code_read_only(
sortdb,
block_id,
POX_2_NAME,
&format!("(get-reward-set-size u{})", reward_cycle),
)?
.expect_u128();
debug!(
"At block {:?} (reward cycle {}): {} PoX reward addresses",
block_id, reward_cycle, num_addrs
);
let mut ret = vec![];
for i in 0..num_addrs {
// value should be (optional (tuple (pox-addr (tuple (...))) (total-ustx uint))).
// Get the tuple.
let tuple_data = self
.eval_boot_code_read_only(
sortdb,
block_id,
POX_2_NAME,
&format!("(get-reward-set-pox-address u{} u{})", reward_cycle, i),
)?
.expect_optional()
.expect(&format!(
"FATAL: missing PoX address in slot {} out of {} in reward cycle {}",
i, num_addrs, reward_cycle
))
.expect_tuple();
let pox_addr_tuple = tuple_data
.get("pox-addr")
.expect(&format!("FATAL: no 'pox-addr' in return value from (get-reward-set-pox-address u{} u{})", reward_cycle, i))
.to_owned()
.expect_tuple();
let (hash_mode, hash) = tuple_to_pox_addr(pox_addr_tuple);
let total_ustx = tuple_data
.get("total-ustx")
.expect(&format!("FATAL: no 'total-ustx' in return value from (get-reward-set-pox-address u{} u{})", reward_cycle, i))
.to_owned()
.expect_u128();
let stacker = tuple_data
.get("stacker")
.expect(&format!("FATAL: no 'total-ustx' in return value from (get-reward-set-pox-address u{} u{})", reward_cycle, i))
.to_owned()
.expect_optional()
.map(|value| value.expect_principal());
let version = match self.mainnet {
true => hash_mode.to_version_mainnet(),
false => hash_mode.to_version_testnet(),
};
let reward_address = StacksAddress::new(version, hash);
debug!(
"PoX reward address (for {} ustx): {}",
total_ustx, &reward_address,
);
ret.push(RawRewardSetEntry {
reward_address,
amount_stacked: total_ustx,
stacker,
})
}
Ok(ret)
}
/// Each address will have at least (get-stacking-minimum) tokens.
pub fn get_reward_addresses(
&mut self,
burnchain: &Burnchain,
sortdb: &SortitionDB,
current_burn_height: u64,
block_id: &StacksBlockId,
) -> Result<Vec<RawRewardSetEntry>, Error> {
let reward_cycle = burnchain
.block_height_to_reward_cycle(current_burn_height)
.ok_or(Error::PoxNoRewardCycle)?;
let reward_cycle_start_height = burnchain.reward_cycle_to_block_height(reward_cycle);
let pox_contract_name = burnchain
.pox_constants
.active_pox_contract(reward_cycle_start_height);
debug!("Using pox_contract = {}", pox_contract_name);
match pox_contract_name {
x if x == POX_1_NAME => self.get_reward_addresses_pox_1(sortdb, block_id, reward_cycle),
x if x == POX_2_NAME => self.get_reward_addresses_pox_2(sortdb, block_id, reward_cycle),
unknown_contract => {
panic!("Blockchain implementation failure: PoX contract name '{}' is unknown. Chainstate is corrupted.",
unknown_contract);
}
}
}
}
#[cfg(test)]
@@ -492,22 +612,38 @@ pub mod test {
fn make_reward_set_units() {
let threshold = 1_000;
let addresses = vec![
(
StacksAddress::from_string("STVK1K405H6SK9NKJAP32GHYHDJ98MMNP8Y6Z9N0").unwrap(),
1500,
),
(
StacksAddress::from_string("ST76D2FMXZ7D2719PNE4N71KPSX84XCCNCMYC940").unwrap(),
500,
),
(
StacksAddress::from_string("STVK1K405H6SK9NKJAP32GHYHDJ98MMNP8Y6Z9N0").unwrap(),
1500,
),
(
StacksAddress::from_string("ST76D2FMXZ7D2719PNE4N71KPSX84XCCNCMYC940").unwrap(),
400,
),
RawRewardSetEntry {
reward_address: StacksAddress::from_string(
"STVK1K405H6SK9NKJAP32GHYHDJ98MMNP8Y6Z9N0",
)
.unwrap(),
amount_stacked: 1500,
stacker: None,
},
RawRewardSetEntry {
reward_address: StacksAddress::from_string(
"ST76D2FMXZ7D2719PNE4N71KPSX84XCCNCMYC940",
)
.unwrap(),
amount_stacked: 500,
stacker: None,
},
RawRewardSetEntry {
reward_address: StacksAddress::from_string(
"STVK1K405H6SK9NKJAP32GHYHDJ98MMNP8Y6Z9N0",
)
.unwrap(),
amount_stacked: 1500,
stacker: None,
},
RawRewardSetEntry {
reward_address: StacksAddress::from_string(
"ST76D2FMXZ7D2719PNE4N71KPSX84XCCNCMYC940",
)
.unwrap(),
amount_stacked: 400,
stacker: None,
},
];
assert_eq!(
StacksChainState::make_reward_set(threshold, addresses).len(),
@@ -533,7 +669,11 @@ pub mod test {
assert_eq!(
StacksChainState::get_reward_threshold_and_participation(
&test_pox_constants,
&[(rand_addr(), liquid)],
&[RawRewardSetEntry {
reward_address: rand_addr(),
amount_stacked: liquid,
stacker: None
}],
liquid
)
.0,
@@ -555,7 +695,11 @@ pub mod test {
assert_eq!(
StacksChainState::get_reward_threshold_and_participation(
&test_pox_constants,
&[(rand_addr(), liquid / 4)],
&[RawRewardSetEntry {
reward_address: rand_addr(),
amount_stacked: liquid / 4,
stacker: None
}],
liquid
)
.0,
@@ -566,8 +710,16 @@ pub mod test {
StacksChainState::get_reward_threshold_and_participation(
&test_pox_constants,
&[
(rand_addr(), liquid / 4),
(rand_addr(), 10_000_000 * (MICROSTACKS_PER_STACKS as u128))
RawRewardSetEntry {
reward_address: rand_addr(),
amount_stacked: liquid / 4,
stacker: None
},
RawRewardSetEntry {
reward_address: rand_addr(),
amount_stacked: 10_000_000 * (MICROSTACKS_PER_STACKS as u128),
stacker: None
},
],
liquid
)
@@ -580,8 +732,16 @@ pub mod test {
StacksChainState::get_reward_threshold_and_participation(
&test_pox_constants,
&[
(rand_addr(), liquid / 4),
(rand_addr(), (MICROSTACKS_PER_STACKS as u128))
RawRewardSetEntry {
reward_address: rand_addr(),
amount_stacked: liquid / 4,
stacker: None
},
RawRewardSetEntry {
reward_address: rand_addr(),
amount_stacked: MICROSTACKS_PER_STACKS as u128,
stacker: None
},
],
liquid
)
@@ -593,7 +753,11 @@ pub mod test {
assert_eq!(
StacksChainState::get_reward_threshold_and_participation(
&test_pox_constants,
&[(rand_addr(), liquid)],
&[RawRewardSetEntry {
reward_address: rand_addr(),
amount_stacked: liquid,
stacker: None
}],
liquid
)
.0,
@@ -1139,8 +1303,11 @@ pub mod test {
state
.get_reward_addresses(burnchain, sortdb, burn_block_height, block_id)
.and_then(|mut addrs| {
addrs.sort_by_key(|k| k.0.bytes.0);
Ok(addrs)
addrs.sort_by_key(|k| k.reward_address.bytes.0);
Ok(addrs
.into_iter()
.map(|x| (x.reward_address, x.amount_stacked))
.collect())
})
}

View File

@@ -73,7 +73,9 @@
;; how long the uSTX are locked, in reward cycles.
lock-period: uint,
;; reward cycle when rewards begin
first-reward-cycle: uint
first-reward-cycle: uint,
;; indexes in each reward-set associated with this user
reward-set-indexes: (list 12 uint)
}
)
@@ -109,7 +111,8 @@
{ reward-cycle: uint, index: uint }
{
pox-addr: { version: (buff 1), hashbytes: (buff 20) },
total-ustx: uint
total-ustx: uint,
stacker: (optional principal)
}
)
@@ -234,20 +237,19 @@
;; Add a single PoX address to a single reward cycle.
;; Used to build up a set of per-reward-cycle PoX addresses.
;; No checking will be done -- don't call if this PoX address is already registered in this reward cycle!
;; Returns the index into the reward cycle that the PoX address is stored to
(define-private (append-reward-cycle-pox-addr (pox-addr (tuple (version (buff 1)) (hashbytes (buff 20))))
(reward-cycle uint)
(amount-ustx uint))
(let (
(sz (get-reward-set-size reward-cycle))
)
(map-set reward-cycle-pox-address-list
{ reward-cycle: reward-cycle, index: sz }
{ pox-addr: pox-addr, total-ustx: amount-ustx })
(map-set reward-cycle-pox-address-list-len
{ reward-cycle: reward-cycle }
{ len: (+ u1 sz) })
(+ u1 sz))
)
(amount-ustx uint)
(stacker (optional principal)))
(let ((sz (get-reward-set-size reward-cycle)))
(map-set reward-cycle-pox-address-list
{ reward-cycle: reward-cycle, index: sz }
{ pox-addr: pox-addr, total-ustx: amount-ustx, stacker: stacker })
(map-set reward-cycle-pox-address-list-len
{ reward-cycle: reward-cycle }
{ len: (+ u1 sz) })
sz))
;; How many uSTX are stacked?
(define-read-only (get-total-ustx-stacked (reward-cycle uint))
@@ -269,34 +271,42 @@
;; the pox-addr was added to the given cycle.
(define-private (add-pox-addr-to-ith-reward-cycle (cycle-index uint) (params (tuple
(pox-addr (tuple (version (buff 1)) (hashbytes (buff 20))))
(reward-set-indexes (list 12 uint))
(first-reward-cycle uint)
(num-cycles uint)
(stacker (optional principal))
(amount-ustx uint)
(i uint))))
(let ((reward-cycle (+ (get first-reward-cycle params) (get i params)))
(num-cycles (get num-cycles params))
(i (get i params)))
(i (get i params))
(reward-set-index (if (< i num-cycles)
(let ((total-ustx (get-total-ustx-stacked reward-cycle))
(reward-index
;; record how many uSTX this pox-addr will stack for in the given reward cycle
(append-reward-cycle-pox-addr
(get pox-addr params)
reward-cycle
(get amount-ustx params)
(get stacker params)
)))
;; update running total
(map-set reward-cycle-total-stacked
{ reward-cycle: reward-cycle }
{ total-ustx: (+ (get amount-ustx params) total-ustx) })
(some reward-index))
none))
(next-i (if (< i num-cycles) (+ i u1) (+ i u0))))
{
pox-addr: (get pox-addr params),
first-reward-cycle: (get first-reward-cycle params),
num-cycles: num-cycles,
amount-ustx: (get amount-ustx params),
i: (if (< i num-cycles)
(let ((total-ustx (get-total-ustx-stacked reward-cycle)))
;; record how many uSTX this pox-addr will stack for in the given reward cycle
(append-reward-cycle-pox-addr
(get pox-addr params)
reward-cycle
(get amount-ustx params))
;; update running total
(map-set reward-cycle-total-stacked
{ reward-cycle: reward-cycle }
{ total-ustx: (+ (get amount-ustx params) total-ustx) })
;; updated _this_ reward cycle
(+ i u1))
(+ i u0))
stacker: (get stacker params),
reward-set-indexes: (match
reward-set-index new (unwrap-panic (as-max-len? (append (get reward-set-indexes params) new) u12))
(get reward-set-indexes params)),
i: next-i
}))
;; Add a PoX address to a given sequence of reward cycle lists.
@@ -305,16 +315,17 @@
(define-private (add-pox-addr-to-reward-cycles (pox-addr (tuple (version (buff 1)) (hashbytes (buff 20))))
(first-reward-cycle uint)
(num-cycles uint)
(amount-ustx uint))
(let ((cycle-indexes (list u0 u1 u2 u3 u4 u5 u6 u7 u8 u9 u10 u11)))
(amount-ustx uint)
(stacker principal))
(let ((cycle-indexes (list u0 u1 u2 u3 u4 u5 u6 u7 u8 u9 u10 u11))
(results (fold add-pox-addr-to-ith-reward-cycle cycle-indexes
{ pox-addr: pox-addr, first-reward-cycle: first-reward-cycle, num-cycles: num-cycles,
reward-set-indexes: (list), amount-ustx: amount-ustx, i: u0, stacker: (some stacker) })))
;; For safety, add up the number of times (add-principal-to-ith-reward-cycle) returns 1.
;; It _should_ be equal to num-cycles.
(asserts!
(is-eq num-cycles
(get i (fold add-pox-addr-to-ith-reward-cycle cycle-indexes
{ pox-addr: pox-addr, first-reward-cycle: first-reward-cycle, num-cycles: num-cycles, amount-ustx: amount-ustx, i: u0 })))
(err ERR_STACKING_UNREACHABLE))
(ok true)))
(asserts! (is-eq num-cycles (get i results)) (err ERR_STACKING_UNREACHABLE))
(asserts! (is-eq num-cycles (len (get reward-set-indexes results))) (err ERR_STACKING_UNREACHABLE))
(ok (get reward-set-indexes results))))
(define-private (add-pox-partial-stacked-to-ith-cycle
(cycle-index uint)
@@ -479,19 +490,18 @@
(try! (can-stack-stx pox-addr amount-ustx first-reward-cycle lock-period))
;; register the PoX address with the amount stacked
(try! (add-pox-addr-to-reward-cycles pox-addr first-reward-cycle lock-period amount-ustx))
(let ((reward-set-indexes (try! (add-pox-addr-to-reward-cycles pox-addr first-reward-cycle lock-period amount-ustx tx-sender))))
;; add stacker record
(map-set stacking-state
{ stacker: tx-sender }
{ amount-ustx: amount-ustx,
pox-addr: pox-addr,
reward-set-indexes: reward-set-indexes,
first-reward-cycle: first-reward-cycle,
lock-period: lock-period })
;; add stacker record
(map-set stacking-state
{ stacker: tx-sender }
{ amount-ustx: amount-ustx,
pox-addr: pox-addr,
first-reward-cycle: first-reward-cycle,
lock-period: lock-period })
;; return the lock-up information, so the node can actually carry out the lock.
(ok { stacker: tx-sender, lock-amount: amount-ustx, unlock-burn-height: (reward-cycle-to-burn-height (+ first-reward-cycle lock-period)) }))
)
;; return the lock-up information, so the node can actually carry out the lock.
(ok { stacker: tx-sender, lock-amount: amount-ustx, unlock-burn-height: (reward-cycle-to-burn-height (+ first-reward-cycle lock-period)) }))))
(define-public (revoke-delegate-stx)
(begin
@@ -557,6 +567,8 @@
{ pox-addr: pox-addr,
first-reward-cycle: reward-cycle,
num-cycles: u1,
reward-set-indexes: (list),
stacker: none,
amount-ustx: amount-ustx,
i: u0 })
;; don't update the stacking-state map,
@@ -628,6 +640,7 @@
{ amount-ustx: amount-ustx,
pox-addr: pox-addr,
first-reward-cycle: first-reward-cycle,
reward-set-indexes: (list),
lock-period: lock-period })
;; return the lock-up information, so the node can actually carry out the lock.
@@ -728,18 +741,18 @@
;; register the PoX address with the amount stacked
;; for the new cycles
(try! (add-pox-addr-to-reward-cycles pox-addr first-extend-cycle extend-count amount-ustx))
(let ((reward-set-indexes (try! (add-pox-addr-to-reward-cycles pox-addr first-extend-cycle extend-count amount-ustx tx-sender))))
;; update stacker record
(map-set stacking-state
{ stacker: tx-sender }
{ amount-ustx: amount-ustx,
pox-addr: pox-addr,
reward-set-indexes: reward-set-indexes,
first-reward-cycle: cur-cycle,
lock-period: lock-period })
;; update stacker record
(map-set stacking-state
{ stacker: tx-sender }
{ amount-ustx: amount-ustx,
pox-addr: pox-addr,
first-reward-cycle: first-extend-cycle,
lock-period: lock-period })
;; return lock-up information
(ok { stacker: tx-sender, unlock-burn-height: new-unlock-ht }))))
;; return lock-up information
(ok { stacker: tx-sender, unlock-burn-height: new-unlock-ht })))))
;; As a delegator, extend an active stacking lock, issuing a "partial commitment" for the
;; extended-to cycles.
@@ -817,6 +830,7 @@
{ stacker: stacker }
{ amount-ustx: amount-ustx,
pox-addr: pox-addr,
reward-set-indexes: (list),
first-reward-cycle: first-extend-cycle,
lock-period: lock-period })