diff --git a/src/chainstate/stacks/boot/mod.rs b/src/chainstate/stacks/boot/mod.rs index 4e4239fd3..8876a7e8b 100644 --- a/src/chainstate/stacks/boot/mod.rs +++ b/src/chainstate/stacks/boot/mod.rs @@ -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, +} + 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, ) -> Vec { 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, 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, 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, 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, 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()) }) } diff --git a/src/chainstate/stacks/boot/pox-2.clar b/src/chainstate/stacks/boot/pox-2.clar index c9a51706c..41121767b 100644 --- a/src/chainstate/stacks/boot/pox-2.clar +++ b/src/chainstate/stacks/boot/pox-2.clar @@ -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 })