@@ -973,7 +973,7 @@ impl<'a> SortitionHandleTx<'a> {
973973 /// * The reward cycle had an anchor block, but it isn't known by this node.
974974 /// * The reward cycle did not have anchor block
975975 /// * The Stacking recipient set is empty (either because this reward cycle has already exhausted the set of addresses or because no one ever Stacked).
976- fn pick_recipient (
976+ fn pick_recipients (
977977 & mut self ,
978978 reward_set_vrf_seed : & SortitionHash ,
979979 next_pox_info : Option < & RewardCycleInfo > ,
@@ -986,20 +986,26 @@ impl<'a> SortitionHandleTx<'a> {
986986 return Ok ( None ) ;
987987 }
988988
989- let chosen_recipient = reward_set_vrf_seed. choose (
989+ if OUTPUTS_PER_COMMIT != 2 {
990+ unreachable ! ( "BUG: PoX reward address selection only implemented for OUTPUTS_PER_COMMIT = 2" ) ;
991+ }
992+
993+ let chosen_recipients = reward_set_vrf_seed. choose_two (
990994 reward_set
991995 . len ( )
992996 . try_into ( )
993997 . expect ( "BUG: u32 overflow in PoX outputs per commit" ) ,
994998 ) ;
995999
996- let recipient = (
997- reward_set[ chosen_recipient as usize ] ,
998- u16:: try_from ( chosen_recipient) . unwrap ( ) ,
999- ) ;
10001000 Ok ( Some ( RewardSetInfo {
10011001 anchor_block : anchor_block. clone ( ) ,
1002- recipient,
1002+ recipients : chosen_recipients
1003+ . into_iter ( )
1004+ . map ( |ix| {
1005+ let recipient = reward_set[ ix as usize ] . clone ( ) ;
1006+ ( recipient, u16:: try_from ( ix) . unwrap ( ) )
1007+ } )
1008+ . collect ( ) ,
10031009 } ) )
10041010 } else {
10051011 Ok ( None )
@@ -1013,12 +1019,16 @@ impl<'a> SortitionHandleTx<'a> {
10131019 if reward_set_size == 0 {
10141020 Ok ( None )
10151021 } else {
1016- let chosen_recipient = reward_set_vrf_seed. choose ( reward_set_size as u32 ) ;
1017- let ix = u16:: try_from ( chosen_recipient) . unwrap ( ) ;
1018- let recipient = ( self . get_reward_set_entry ( ix) ?, ix) ;
1022+ let chosen_recipients = reward_set_vrf_seed. choose_two ( reward_set_size as u32 ) ;
1023+ let mut recipients = vec ! [ ] ;
1024+ for ix in chosen_recipients. into_iter ( ) {
1025+ let ix = u16:: try_from ( ix) . unwrap ( ) ;
1026+ let recipient = self . get_reward_set_entry ( ix) ?;
1027+ recipients. push ( ( recipient, ix) ) ;
1028+ }
10191029 Ok ( Some ( RewardSetInfo {
10201030 anchor_block,
1021- recipient ,
1031+ recipients ,
10221032 } ) )
10231033 }
10241034 } else {
@@ -2336,7 +2346,7 @@ impl SortitionDB {
23362346 . mix_burn_header ( & parent_snapshot. burn_header_hash ) ;
23372347
23382348 let reward_set_info =
2339- sortition_db_handle. pick_recipient ( & reward_set_vrf_hash, next_pox_info. as_ref ( ) ) ?;
2349+ sortition_db_handle. pick_recipients ( & reward_set_vrf_hash, next_pox_info. as_ref ( ) ) ?;
23402350
23412351 let new_snapshot = sortition_db_handle. process_block_txs (
23422352 & parent_snapshot,
@@ -2375,7 +2385,7 @@ impl SortitionDB {
23752385
23762386 let mut sortition_db_handle =
23772387 SortitionHandleTx :: begin ( self , & parent_snapshot. sortition_id ) ?;
2378- sortition_db_handle. pick_recipient ( & reward_set_vrf_hash, next_pox_info)
2388+ sortition_db_handle. pick_recipients ( & reward_set_vrf_hash, next_pox_info)
23792389 }
23802390
23812391 pub fn is_stacks_block_in_sortition_set (
@@ -3229,9 +3239,18 @@ impl<'a> SortitionHandleTx<'a> {
32293239 if reward_set. len ( ) > 0 {
32303240 // if we have a reward set, then we must also have produced a recipient
32313241 // info for this block
3232- let ( addr, ix) = recipient_info. unwrap ( ) . recipient . clone ( ) ;
3233- assert_eq ! ( & reward_set. remove( ix as usize ) , & addr,
3234- "BUG: Attempted to remove used address from reward set, but failed to do so safely" ) ;
3242+ let mut recipients_to_remove: Vec < _ > = recipient_info
3243+ . unwrap ( )
3244+ . recipients
3245+ . iter ( )
3246+ . map ( |( addr, ix) | ( addr. clone ( ) , * ix) )
3247+ . collect ( ) ;
3248+ recipients_to_remove. sort_unstable_by ( |( _, a) , ( _, b) | b. cmp ( a) ) ;
3249+ // remove from the reward set any consumed addresses in this first reward block
3250+ for ( addr, ix) in recipients_to_remove. iter ( ) {
3251+ assert_eq ! ( & reward_set. remove( * ix as usize ) , addr,
3252+ "BUG: Attempted to remove used address from reward set, but failed to do so safely" ) ;
3253+ }
32353254 }
32363255
32373256 keys. push ( db_keys:: pox_reward_set_size ( ) . to_string ( ) ) ;
@@ -3254,22 +3273,43 @@ impl<'a> SortitionHandleTx<'a> {
32543273 // update the reward set
32553274 if let Some ( reward_info) = recipient_info {
32563275 let mut current_len = self . get_reward_set_size ( ) ?;
3257- let ( _, recipient_index) = reward_info. recipient ;
3258-
3259- if recipient_index >= current_len {
3260- unreachable ! (
3261- "Supplied index should never be greater than recipient set size"
3262- ) ;
3276+ let mut recipient_indexes: Vec < _ > =
3277+ reward_info. recipients . iter ( ) . map ( |( _, x) | * x) . collect ( ) ;
3278+ let mut remapped_entries = HashMap :: new ( ) ;
3279+ // sort in decrementing order
3280+ recipient_indexes. sort_unstable_by ( |a, b| b. cmp ( a) ) ;
3281+ for index in recipient_indexes. into_iter ( ) {
3282+ // sanity check
3283+ if index >= current_len {
3284+ unreachable ! (
3285+ "Supplied index should never be greater than recipient set size"
3286+ ) ;
3287+ } else if index + 1 == current_len {
3288+ // selected index is the last element: no need to swap, just decrement len
3289+ current_len -= 1 ;
3290+ } else {
3291+ let replacement = current_len - 1 ; // if current_len were 0, we would already have panicked.
3292+ let replace_with = if let Some ( ( _prior_ix, replace_with) ) =
3293+ remapped_entries. remove_entry ( & replacement)
3294+ {
3295+ // the entry to swap in was itself swapped, so let's use the new value instead
3296+ replace_with
3297+ } else {
3298+ self . get_reward_set_entry ( replacement) ?
3299+ } ;
3300+
3301+ // swap and decrement to remove from set
3302+ remapped_entries. insert ( index, replace_with) ;
3303+ current_len -= 1 ;
3304+ }
32633305 }
3264-
3265- current_len -= 1 ;
3266- let recipient = self . get_reward_set_entry ( current_len) ?;
3267-
32683306 // store the changes in the new trie
32693307 keys. push ( db_keys:: pox_reward_set_size ( ) . to_string ( ) ) ;
32703308 values. push ( db_keys:: reward_set_size_to_string ( current_len as usize ) ) ;
3271- keys. push ( db_keys:: pox_reward_set_entry ( recipient_index) ) ;
3272- values. push ( recipient. to_string ( ) )
3309+ for ( recipient_index, replace_with) in remapped_entries. into_iter ( ) {
3310+ keys. push ( db_keys:: pox_reward_set_entry ( recipient_index) ) ;
3311+ values. push ( replace_with. to_string ( ) )
3312+ }
32733313 }
32743314 }
32753315 } else {
0 commit comments