11use crate :: consensus:: aggsig:: DST_MOTION ;
22use crate :: { bcat, consensus} ;
33use num_bigint:: BigUint ;
4- use std:: collections:: HashSet ;
4+ use std:: collections:: { BTreeMap , HashSet } ;
55use std:: panic:: panic_any;
66
77use crate :: consensus:: consensus_apply:: ApplyEnv ;
@@ -10,6 +10,18 @@ use crate::consensus::consensus_kv::{kv_delete, kv_exists, kv_get, kv_get_next,
1010pub const EPOCH_EMISSION_BASE : i128 = 1_000_000_000_000_000 ;
1111pub const EPOCH_INTERVAL : i128 = 100_000 ;
1212
13+ pub const NETWORK_TAX_BPS : i128 = 2_500 ; //25%
14+
15+ pub const SOLVER_PARTICIPATION_TARGET : i128 = 100 ;
16+
17+ pub const PARTICIPATION_VAULT_EPOCH : u64 = 1150 ;
18+
19+ //per-epoch emission not disbursed carries over in these pools: the vault pool feeds
20+ //back into future vault APY budgets (and funds the network tax); the solver pool
21+ //just accumulates for now (disposition TBD).
22+ pub const VAULT_ACCRUED_POOL_KEY : & [ u8 ] = b"bic:epoch:vault_accrued_pool" ;
23+ pub const SOLVER_ACCRUED_POOL_KEY : & [ u8 ] = b"bic:epoch:solver_accrued_pool" ;
24+
1325pub const TREASURY_DONATION_ADDRESS : & [ u8 ; 48 ] = & [
1426 140 , 71 , 6 , 83 , 31 , 185 , 171 , 240 , 47 , 5 , 14 , 246 , 98 , 23 , 105 , 24 , 183 , 118 , 193 , 92 , 66 , 82 , 64 , 5 , 239 , 255 , 254 , 87 , 139 , 252 , 148 , 176 , 113 , 6 , 207 ,
1527 153 , 51 , 25 , 202 , 45 , 48 , 153 , 223 , 248 , 219 , 210 , 80 , 254 ,
@@ -611,11 +623,138 @@ pub fn next(env: &mut ApplyEnv) {
611623 clear_epoch_data ( env) ;
612624}
613625
614- fn distribute_emissions_to_trainers ( env : & mut ApplyEnv , trainers_to_recv : & Vec < ( Vec < u8 > , i128 ) > , total_emission : i128 , total_sols : i128 ) {
626+ //epoch boundary from VAULT_ACTIVATION_EPOCH on (and always on testnet): the vault
627+ //engine plus the reworked emission model. same backbone as next() — collect
628+ //leaders, set next epoch's validators, adjust difficulty, clear epoch data — with:
629+ // * queued vault validator changes post once, first thing (the only promotion site)
630+ // * emission split in half: vault APY vs solvers
631+ // * vault APY paid from (this half + carried pool); leftover carries on
632+ // * a 25% treasury tax on all payouts, FUNDED from the vault leftover (not minted
633+ // on top) so issuance stays within the curve
634+ // * emission curbed by participation (phash): below SOLVER_PARTICIPATION_TARGET only
635+ // phash% pays, the rest accrues. always applies to the solver half; applies to
636+ // vault APY too from PARTICIPATION_VAULT_EPOCH on
637+ // * validator set = peddlebike67 + every >=1m-stake vault validator + top 33 solvers
638+ // * no community-fund payout (peddlebike67 only enter the validator set)
639+ pub fn next2 ( env : & mut ApplyEnv ) {
640+ let epoch_cur = env. caller_env . entry_epoch ;
641+ let epoch_next = env. caller_env . entry_epoch + 1 ;
642+
643+ consensus:: bic:: lockup_vault:: promote_pending_validators ( env, epoch_next) ;
644+
645+ let peddlebike67_map: HashSet < Vec < u8 > > = if env. testnet {
646+ env. testnet_peddlebikes . iter ( ) . map ( |pk| pk. to_vec ( ) ) . collect ( )
647+ } else {
648+ PEDDLEBIKE67 . iter ( ) . map ( |pk| pk. to_vec ( ) ) . collect ( )
649+ } ;
650+
651+ let trainers = kv_get_trainers ( env, env. caller_env . entry_height ) ;
652+ let trainers_map: HashSet < Vec < u8 > > = trainers. into_iter ( ) . collect ( ) ;
653+ let trainers_removed = kv_get_trainers_removed ( env) ;
654+ let trainers_removed_map: HashSet < Vec < u8 > > = trainers_removed. into_iter ( ) . collect ( ) ;
655+
656+ //total_score_all sums EVERY sol submitter (matching the stats `score()` used by
657+ //pflops); leaders excludes slashed (removed) trainers, as in next().
658+ let mut leaders: Vec < ( Vec < u8 > , i128 ) > = Vec :: new ( ) ;
659+ let mut total_score_all: i128 = 0 ;
660+ let mut cursor: Vec < u8 > = Vec :: new ( ) ;
661+ while let Some ( ( pk, val) ) = kv_get_next ( env, b"bic:epoch:solutions_count:" , & cursor) {
662+ let count = std:: str:: from_utf8 ( & val) . ok ( ) . and_then ( |s| s. parse :: < i128 > ( ) . ok ( ) ) . unwrap_or_else ( || panic_any ( "invalid_solutions_count" ) ) ;
663+ total_score_all = total_score_all. checked_add ( count) . unwrap_or_else ( || panic_any ( "invalid_solutions_count" ) ) ;
664+ if !trainers_removed_map. contains ( & pk) {
665+ leaders. push ( ( pk. clone ( ) , count) ) ;
666+ }
667+ cursor = pk;
668+ }
669+ // sort descending; highest sol count first; tiebreak on PK
670+ leaders. sort_unstable_by ( |( ka, ca) , ( kb, cb) | match cb. cmp ( ca) {
671+ std:: cmp:: Ordering :: Equal => kb. cmp ( ka) ,
672+ other => other,
673+ } ) ;
674+
675+ //every sol-submitting solver in the set (not a peddlebike) is eligible — no cap
676+ let solvers: Vec < ( Vec < u8 > , i128 ) > =
677+ leaders. iter ( ) . cloned ( ) . filter ( |( pk, _) | trainers_map. contains ( pk) && !peddlebike67_map. contains ( pk) ) . collect ( ) ;
678+ let total_sols: i128 = solvers. iter ( ) . map ( |( _, count) | count) . sum ( ) ;
679+
680+ let epoch_total_emission = epoch_emission_active ( epoch_cur) ;
681+ let vault_half = epoch_total_emission / 2 ;
682+ let solver_half = epoch_total_emission - vault_half;
683+
684+ //--- participation (phash) — computed up front so it can also curb vault APY ---
685+ //at phash >= SOLVER_PARTICIPATION_TARGET (100 PFLOPS) full emission pays; below that
686+ //only phash% pays and the shortfall accrues, so a low-participation (bear) period
687+ //can't take the lion's share of easy emissions.
688+ let height_in_epoch = ( env. caller_env . entry_height % 100_000 ) as i128 ;
689+ let phash = net_phash ( env, total_score_all, height_in_epoch) ;
690+ let participation = phash. clamp ( 0 , SOLVER_PARTICIPATION_TARGET ) ; //0..=100
691+
692+ //participation curbs the solver half always; it curbs vault APY only from
693+ //PARTICIPATION_VAULT_EPOCH on. before that, vaults always pay full and only solvers
694+ //are curbed; after it, low solver participation heavily reduces vault APY too.
695+ let vault_reduction_pct: u64 = if epoch_cur >= PARTICIPATION_VAULT_EPOCH { participation as u64 } else { 100 } ;
696+
697+ //--- vault APY ---
698+ let vault_pool = kv_get ( env, VAULT_ACCRUED_POOL_KEY ) . and_then ( |v| std:: str:: from_utf8 ( & v) . ok ( ) . and_then ( |s| s. parse :: < i128 > ( ) . ok ( ) ) ) . unwrap_or ( 0 ) ;
699+ let vault_budget = vault_half. checked_add ( vault_pool) . unwrap_or_else ( || panic_any ( "vault_budget_overflow" ) ) ;
700+ let vault_paid = consensus:: bic:: lockup_vault:: pay_epoch_yield ( env, & trainers_map, vault_reduction_pct, vault_budget) ;
701+ let vault_leftover = vault_budget. checked_sub ( vault_paid) . unwrap_or_else ( || panic_any ( "vault_pool_underflow" ) ) ;
702+ let vault_stakes = consensus:: bic:: lockup_vault:: validator_stakes ( env) ;
703+
704+ //--- solver emission, curbed by participation ---
705+ let solver_budget = solver_half. checked_mul ( participation) . unwrap_or_else ( || panic_any ( "emission_overflow" ) ) / SOLVER_PARTICIPATION_TARGET ;
706+ let solver_paid = distribute_emissions_to_trainers ( env, & solvers, solver_budget, total_sols) ;
707+ //the part of the solver half not paid (participation shortfall + rounding) accrues
708+ let solver_accrued = solver_half. checked_sub ( solver_paid) . unwrap_or_else ( || panic_any ( "emission_overflow" ) ) ;
709+ if solver_accrued > 0 {
710+ let _ = kv_increment ( env, SOLVER_ACCRUED_POOL_KEY , solver_accrued) ;
711+ }
712+
713+ //--- 25% network tax, charged AFTER all participation reductions so it tracks what
714+ //was actually paid (vault_paid and solver_paid are already reduced). funded from the
715+ //vault leftover so it never exceeds emission; capped at the leftover — payouts are
716+ //never cut, the tax yields first. remaining leftover rolls into the vault pool. ---
717+ let taxable = vault_paid. checked_add ( solver_paid) . unwrap_or_else ( || panic_any ( "emission_overflow" ) ) ;
718+ let nominal_tax = taxable. checked_mul ( NETWORK_TAX_BPS ) . unwrap_or_else ( || panic_any ( "emission_overflow" ) ) / 10_000 ;
719+ let tax = nominal_tax. min ( vault_leftover) ;
720+ if tax > 0 {
721+ let _ = kv_increment ( env, & bcat ( & [ b"account:" , TREASURY_DONATION_ADDRESS . as_slice ( ) , b":balance:AMA" ] ) , tax) ;
722+ }
723+ let new_vault_pool = vault_leftover - tax;
724+ kv_put ( env, VAULT_ACCRUED_POOL_KEY , new_vault_pool. to_string ( ) . as_bytes ( ) ) ;
725+
726+ //--- validators for next epoch: peddlebike67 + >=1m vault validators + top 33 solvers ---
727+ let new_validators = build_and_shuffle_new_validators2 ( env, & leaders, & vault_stakes) ;
728+ let new_validators = consensus:: bic:: list_of_binaries_to_vecpak ( new_validators) ;
729+ let height_next = format ! ( "{:012}" , env. caller_env. entry_height. saturating_add( 1 ) ) . into_bytes ( ) ;
730+ let _ = kv_put ( env, & bcat ( & [ b"bic:epoch:validators:height:" , & height_next] ) , & new_validators) ;
731+
732+ update_difficulty_and_log_sols ( env, epoch_cur, epoch_next, total_sols) ;
733+ clear_epoch_data ( env) ;
734+ }
735+
736+ fn net_phash ( env : & mut ApplyEnv , total_sols : i128 , height_in_epoch : i128 ) -> i128 {
737+ const OPS : i128 = 16 * 16 * 50_240 * 2 ; //25_722_880 (MACs x2, per pflops)
738+
739+ let diff_bits = kv_get ( env, b"bic:epoch:diff_bits" )
740+ . and_then ( |v| std:: str:: from_utf8 ( & v) . ok ( ) . and_then ( |s| s. parse :: < u32 > ( ) . ok ( ) ) )
741+ . unwrap_or ( 24 ) ;
742+ let diff_multiplier: i128 = if diff_bits >= 127 { i128:: MAX } else { 1i128 << diff_bits } ;
743+
744+ let total_calcs = total_sols. checked_mul ( diff_multiplier) . unwrap_or ( i128:: MAX ) ;
745+ let numer = total_calcs. checked_mul ( OPS ) . unwrap_or ( i128:: MAX ) ;
746+ let denom = ( height_in_epoch + 2 ) . checked_mul ( 500_000_000_000_000 ) . unwrap_or_else ( || panic_any ( "phash_denom_overflow" ) ) ;
747+ numer / denom
748+ }
749+
750+ //distributes `total_emission` to solvers pro rata by sol count (paid in full);
751+ //returns the total actually paid, which the caller uses for tax accounting.
752+ fn distribute_emissions_to_trainers ( env : & mut ApplyEnv , trainers_to_recv : & Vec < ( Vec < u8 > , i128 ) > , total_emission : i128 , total_sols : i128 ) -> i128 {
615753 if total_sols == 0 {
616- return ;
754+ return 0 ;
617755 }
618756
757+ let mut paid: i128 = 0 ;
619758 for ( trainer, trainer_sols) in trainers_to_recv {
620759 let coins = trainer_sols
621760 . checked_mul ( total_emission)
@@ -627,9 +766,13 @@ fn distribute_emissions_to_trainers(env: &mut ApplyEnv, trainers_to_recv: &Vec<(
627766 if let Some ( addr) = emission_address { bcat ( & [ b"account:" , & addr, b":balance:AMA" ] ) } else { bcat ( & [ b"account:" , trainer, b":balance:AMA" ] ) } ;
628767
629768 let _ = kv_increment ( env, & balance_key, coins) ;
769+ paid = paid. checked_add ( coins) . unwrap_or_else ( || panic_any ( "emission_overflow" ) ) ;
630770 }
771+ paid
631772}
632773
774+ //legacy (pre-VAULT_ACTIVATION_EPOCH) community-fund split. from the fork on,
775+ //peddlebike67 receive no emission payout — they only enter the validator set.
633776fn distribute_peddlebike67_community_fund ( env : & mut ApplyEnv , total_emission : i128 ) {
634777 let n_count = PEDDLEBIKE67 . len ( ) as i128 ;
635778 let q = total_emission / n_count;
@@ -646,6 +789,10 @@ fn distribute_peddlebike67_community_fund(env: &mut ApplyEnv, total_emission: i1
646789 }
647790}
648791
792+ //number of top solvers admitted to the validator set from the fork on
793+ const SOLVER_VALIDATOR_SLOTS : usize = 33 ;
794+
795+ //legacy (pre-fork) validator set: peddlebike67 + all leaders, capped at 99.
649796fn build_and_shuffle_new_validators ( env : & ApplyEnv , leaders : & Vec < ( Vec < u8 > , i128 ) > ) -> Vec < Vec < u8 > > {
650797 let PEDDLEBIKE_LOCAL : Vec < [ u8 ; 48 ] > = if env. testnet {
651798 env. testnet_peddlebikes . iter ( ) . map ( |pk| pk. as_slice ( ) . try_into ( ) . expect ( "Testnet key was not 48 bytes long" ) ) . collect ( )
@@ -660,12 +807,50 @@ fn build_and_shuffle_new_validators(env: &ApplyEnv, leaders: &Vec<(Vec<u8>, i128
660807 new_validators. extend ( filtered_leaders) ;
661808 new_validators. truncate ( 99 ) ;
662809
810+ shuffle_validators ( env, & mut new_validators) ;
811+ new_validators
812+ }
813+
814+ //fork validator set: peddlebike67 + every >=1m-stake vault validator + top 33 solvers,
815+ //deduped, no fixed size cap.
816+ fn build_and_shuffle_new_validators2 ( env : & ApplyEnv , leaders : & Vec < ( Vec < u8 > , i128 ) > , vault_stakes : & BTreeMap < Vec < u8 > , i128 > ) -> Vec < Vec < u8 > > {
817+ let PEDDLEBIKE_LOCAL : Vec < [ u8 ; 48 ] > = if env. testnet {
818+ env. testnet_peddlebikes . iter ( ) . map ( |pk| pk. as_slice ( ) . try_into ( ) . expect ( "Testnet key was not 48 bytes long" ) ) . collect ( )
819+ } else {
820+ PEDDLEBIKE67 . to_vec ( )
821+ } ;
822+
823+ let mut new_validators: Vec < Vec < u8 > > = PEDDLEBIKE_LOCAL . iter ( ) . map ( |p| p. to_vec ( ) ) . collect ( ) ;
824+
825+ //vault-backed validators with at least VALIDATOR_MIN_STAKE (1m AMA amount+accrued)
826+ for ( validator, stake) in vault_stakes {
827+ if * stake >= consensus:: bic:: lockup_vault:: VALIDATOR_MIN_STAKE {
828+ new_validators. push ( validator. clone ( ) ) ;
829+ }
830+ }
831+
832+ //top SOLVER_VALIDATOR_SLOTS solvers (leaders are pre-sorted by sol count desc,
833+ //so a low-sol solver only enters if fewer than 33 out-sol it)
834+ let top_solvers = leaders
835+ . iter ( )
836+ . map ( |( pk, _) | pk. clone ( ) )
837+ . filter ( |pk| !PEDDLEBIKE_LOCAL . iter ( ) . any ( |p| p. as_slice ( ) == pk. as_slice ( ) ) )
838+ . take ( SOLVER_VALIDATOR_SLOTS ) ;
839+ new_validators. extend ( top_solvers) ;
840+
841+ //a vault validator may also be a peddlebike or a top solver; keep first occurrence
842+ let mut seen: HashSet < Vec < u8 > > = HashSet :: new ( ) ;
843+ new_validators. retain ( |pk| seen. insert ( pk. clone ( ) ) ) ;
844+
845+ shuffle_validators ( env, & mut new_validators) ;
846+ new_validators
847+ }
848+
849+ fn shuffle_validators ( env : & ApplyEnv , validators : & mut Vec < Vec < u8 > > ) {
663850 let seed_bytes = & env. caller_env . seed ;
664851 let seed_array: [ u8 ; 32 ] = seed_bytes. get ( ..32 ) . and_then ( |s| s. try_into ( ) . ok ( ) ) . unwrap_or ( [ 0u8 ; 32 ] ) ;
665852 let mut rng = crate :: consensus:: bic:: exsss:: Exsss :: from_seed ( & seed_array) ;
666- rng. shuffle ( & mut new_validators) ;
667-
668- new_validators
853+ rng. shuffle ( validators) ;
669854}
670855
671856fn update_difficulty_and_log_sols ( env : & mut ApplyEnv , epoch_cur : u64 , epoch_next : u64 , total_sols : i128 ) {
0 commit comments