-
Notifications
You must be signed in to change notification settings - Fork 650
/
Copy pathdb_maint.cpp
1338 lines (1175 loc) · 52.4 KB
/
db_maint.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*
* Copyright (c) 2015 Cryptonomex, Inc., and contributors.
*
* The MIT License
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include <fc/uint128.hpp>
#include <graphene/protocol/market.hpp>
#include <graphene/chain/database.hpp>
#include <graphene/chain/fba_accumulator_id.hpp>
#include <graphene/chain/hardfork.hpp>
#include <graphene/chain/account_object.hpp>
#include <graphene/chain/asset_object.hpp>
#include <graphene/chain/budget_record_object.hpp>
#include <graphene/chain/buyback_object.hpp>
#include <graphene/chain/chain_property_object.hpp>
#include <graphene/chain/committee_member_object.hpp>
#include <graphene/chain/fba_object.hpp>
#include <graphene/chain/global_property_object.hpp>
#include <graphene/chain/market_object.hpp>
#include <graphene/chain/special_authority_object.hpp>
#include <graphene/chain/vesting_balance_object.hpp>
#include <graphene/chain/vote_count.hpp>
#include <graphene/chain/witness_object.hpp>
#include <graphene/chain/worker_object.hpp>
namespace graphene { namespace chain {
template<class Index>
vector<std::reference_wrapper<const typename Index::object_type>> database::sort_votable_objects(size_t count) const
{
using ObjectType = typename Index::object_type;
const auto& all_objects = get_index_type<Index>().indices();
count = std::min(count, all_objects.size());
vector<std::reference_wrapper<const ObjectType>> refs;
refs.reserve(all_objects.size());
std::transform(all_objects.begin(), all_objects.end(),
std::back_inserter(refs),
[](const ObjectType& o) { return std::cref(o); });
std::partial_sort(refs.begin(), refs.begin() + count, refs.end(),
[this](const ObjectType& a, const ObjectType& b)->bool {
share_type oa_vote = _vote_tally_buffer[a.vote_id];
share_type ob_vote = _vote_tally_buffer[b.vote_id];
if( oa_vote != ob_vote )
return oa_vote > ob_vote;
return a.vote_id < b.vote_id;
});
refs.resize(count, refs.front());
return refs;
}
template<class Type>
void database::perform_account_maintenance(Type tally_helper)
{
const auto& bal_idx = get_index_type< account_balance_index >().indices().get< by_maintenance_flag >();
if( bal_idx.begin() != bal_idx.end() )
{
auto bal_itr = bal_idx.rbegin();
while( bal_itr->maintenance_flag )
{
const account_balance_object& bal_obj = *bal_itr;
modify( get_account_stats_by_owner( bal_obj.owner ), [&bal_obj](account_statistics_object& aso) {
aso.core_in_balance = bal_obj.balance;
});
modify( bal_obj, []( account_balance_object& abo ) {
abo.maintenance_flag = false;
});
bal_itr = bal_idx.rbegin();
}
}
const auto& stats_idx = get_index_type< account_stats_index >().indices().get< by_maintenance_seq >();
auto stats_itr = stats_idx.lower_bound( true );
while( stats_itr != stats_idx.end() )
{
const account_statistics_object& acc_stat = *stats_itr;
const account_object& acc_obj = acc_stat.owner( *this );
++stats_itr;
if( acc_stat.has_some_core_voting() )
tally_helper( acc_obj, acc_stat );
if( acc_stat.has_pending_fees() )
acc_stat.process_fees( acc_obj, *this );
}
}
/// @brief A visitor for @ref worker_type which calls pay_worker on the worker within
struct worker_pay_visitor
{
private:
share_type pay;
database& db;
public:
worker_pay_visitor(share_type pay, database& db)
: pay(pay), db(db) {}
typedef void result_type;
template<typename W>
void operator()(W& worker)const
{
worker.pay_worker(pay, db);
}
};
void database::update_worker_votes()
{
const auto& idx = get_index_type<worker_index>().indices().get<by_account>();
auto itr = idx.begin();
auto itr_end = idx.end();
bool allow_negative_votes = (head_block_time() < HARDFORK_607_TIME);
while( itr != itr_end )
{
modify( *itr, [this,allow_negative_votes]( worker_object& obj )
{
obj.total_votes_for = _vote_tally_buffer[obj.vote_for];
obj.total_votes_against = allow_negative_votes ? _vote_tally_buffer[obj.vote_against] : 0;
});
++itr;
}
}
void database::pay_workers( share_type& budget )
{
const auto head_time = head_block_time();
// ilog("Processing payroll! Available budget is ${b}", ("b", budget));
vector<std::reference_wrapper<const worker_object>> active_workers;
// TODO optimization: add by_expiration index to avoid iterating through all objects
get_index_type<worker_index>().inspect_all_objects([head_time, &active_workers](const object& o) {
const worker_object& w = static_cast<const worker_object&>(o);
if( w.is_active(head_time) && w.approving_stake() > 0 )
active_workers.emplace_back(w);
});
// worker with more votes is preferred
// if two workers exactly tie for votes, worker with lower ID is preferred
std::sort(active_workers.begin(), active_workers.end(), [](const worker_object& wa, const worker_object& wb) {
share_type wa_vote = wa.approving_stake();
share_type wb_vote = wb.approving_stake();
if( wa_vote != wb_vote )
return wa_vote > wb_vote;
return wa.id < wb.id;
});
const auto last_budget_time = get_dynamic_global_properties().last_budget_time;
const auto passed_time_ms = head_time - last_budget_time;
const auto passed_time_count = passed_time_ms.count();
const auto day_count = fc::days(1).count();
for( uint32_t i = 0; i < active_workers.size() && budget > 0; ++i )
{
const worker_object& active_worker = active_workers[i];
share_type requested_pay = active_worker.daily_pay;
// Note: if there is a good chance that passed_time_count == day_count,
// for better performance, can avoid the 128 bit calculation by adding a check.
// Since it's not the case on BitShares mainnet, we're not using a check here.
fc::uint128_t pay = requested_pay.value;
pay *= passed_time_count;
pay /= day_count;
requested_pay = static_cast<uint64_t>(pay);
share_type actual_pay = std::min(budget, requested_pay);
//ilog(" ==> Paying ${a} to worker ${w}", ("w", active_worker.id)("a", actual_pay));
modify(active_worker, [&](worker_object& w) {
w.worker.visit(worker_pay_visitor(actual_pay, *this));
});
budget -= actual_pay;
}
}
void database::update_active_witnesses()
{ try {
assert( _witness_count_histogram_buffer.size() > 0 );
share_type stake_target = (_total_voting_stake-_witness_count_histogram_buffer[0]) / 2;
/// accounts that vote for 0 or 1 witness do not get to express an opinion on
/// the number of witnesses to have (they abstain and are non-voting accounts)
share_type stake_tally = 0;
size_t witness_count = 0;
if( stake_target > 0 )
{
while( (witness_count < _witness_count_histogram_buffer.size() - 1)
&& (stake_tally <= stake_target) )
{
stake_tally += _witness_count_histogram_buffer[++witness_count];
}
}
const chain_property_object& cpo = get_chain_properties();
witness_count = std::max( witness_count*2+1, (size_t)cpo.immutable_parameters.min_witness_count );
auto wits = sort_votable_objects<witness_index>( witness_count );
const global_property_object& gpo = get_global_properties();
auto update_witness_total_votes = [this]( const witness_object& wit ) {
modify( wit, [this]( witness_object& obj )
{
obj.total_votes = _vote_tally_buffer[obj.vote_id];
});
};
if( _track_standby_votes )
{
const auto& all_witnesses = get_index_type<witness_index>().indices();
for( const witness_object& wit : all_witnesses )
{
update_witness_total_votes( wit );
}
}
else
{
for( const witness_object& wit : wits )
{
update_witness_total_votes( wit );
}
}
// Update witness authority
modify( get(GRAPHENE_WITNESS_ACCOUNT), [this,&wits]( account_object& a )
{
if( head_block_time() < HARDFORK_533_TIME )
{
uint64_t total_votes = 0;
map<account_id_type, uint64_t> weights;
a.active.weight_threshold = 0;
a.active.clear();
for( const witness_object& wit : wits )
{
weights.emplace(wit.witness_account, _vote_tally_buffer[wit.vote_id]);
total_votes += _vote_tally_buffer[wit.vote_id];
}
// total_votes is 64 bits. Subtract the number of leading low bits from 64 to get the number of useful bits,
// then I want to keep the most significant 16 bits of what's left.
int8_t bits_to_drop = std::max(int(boost::multiprecision::detail::find_msb(total_votes)) - 15, 0);
for( const auto& weight : weights )
{
// Ensure that everyone has at least one vote. Zero weights aren't allowed.
uint16_t votes = std::max((weight.second >> bits_to_drop), uint64_t(1) );
a.active.account_auths[weight.first] += votes;
a.active.weight_threshold += votes;
}
a.active.weight_threshold /= 2;
a.active.weight_threshold += 1;
}
else
{
vote_counter vc;
for( const witness_object& wit : wits )
vc.add( wit.witness_account, _vote_tally_buffer[wit.vote_id] );
vc.finish( a.active );
}
} );
modify( gpo, [&wits]( global_property_object& gp )
{
gp.active_witnesses.clear();
gp.active_witnesses.reserve(wits.size());
std::transform(wits.begin(), wits.end(),
std::inserter(gp.active_witnesses, gp.active_witnesses.end()),
[](const witness_object& w) {
return w.id;
});
});
} FC_CAPTURE_AND_RETHROW() }
void database::update_active_committee_members()
{ try {
assert( _committee_count_histogram_buffer.size() > 0 );
share_type stake_target = (_total_voting_stake-_committee_count_histogram_buffer[0]) / 2;
/// accounts that vote for 0 or 1 committee member do not get to express an opinion on
/// the number of committee members to have (they abstain and are non-voting accounts)
share_type stake_tally = 0;
size_t committee_member_count = 0;
if( stake_target > 0 )
{
while( (committee_member_count < _committee_count_histogram_buffer.size() - 1)
&& (stake_tally <= stake_target.value) )
{
stake_tally += _committee_count_histogram_buffer[++committee_member_count];
}
}
const chain_property_object& cpo = get_chain_properties();
committee_member_count = std::max( committee_member_count*2+1, (size_t)cpo.immutable_parameters.min_committee_member_count );
auto committee_members = sort_votable_objects<committee_member_index>( committee_member_count );
auto update_committee_member_total_votes = [this]( const committee_member_object& cm ) {
modify( cm, [this]( committee_member_object& obj )
{
obj.total_votes = _vote_tally_buffer[obj.vote_id];
});
};
if( _track_standby_votes )
{
const auto& all_committee_members = get_index_type<committee_member_index>().indices();
for( const committee_member_object& cm : all_committee_members )
{
update_committee_member_total_votes( cm );
}
}
else
{
for( const committee_member_object& cm : committee_members )
{
update_committee_member_total_votes( cm );
}
}
// Update committee authorities
if( !committee_members.empty() )
{
const account_object& committee_account = get(GRAPHENE_COMMITTEE_ACCOUNT);
modify( committee_account, [this,&committee_members](account_object& a)
{
if( head_block_time() < HARDFORK_533_TIME )
{
uint64_t total_votes = 0;
map<account_id_type, uint64_t> weights;
a.active.weight_threshold = 0;
a.active.clear();
for( const committee_member_object& cm : committee_members )
{
weights.emplace( cm.committee_member_account, _vote_tally_buffer[cm.vote_id] );
total_votes += _vote_tally_buffer[cm.vote_id];
}
// total_votes is 64 bits. Subtract the number of leading low bits from 64 to get the number of useful bits,
// then I want to keep the most significant 16 bits of what's left.
int8_t bits_to_drop = std::max(int(boost::multiprecision::detail::find_msb(total_votes)) - 15, 0);
for( const auto& weight : weights )
{
// Ensure that everyone has at least one vote. Zero weights aren't allowed.
uint16_t votes = std::max((weight.second >> bits_to_drop), uint64_t(1) );
a.active.account_auths[weight.first] += votes;
a.active.weight_threshold += votes;
}
a.active.weight_threshold /= 2;
a.active.weight_threshold += 1;
}
else
{
vote_counter vc;
for( const committee_member_object& cm : committee_members )
vc.add( cm.committee_member_account, _vote_tally_buffer[cm.vote_id] );
vc.finish( a.active );
}
});
modify( get(GRAPHENE_RELAXED_COMMITTEE_ACCOUNT), [&committee_account](account_object& a)
{
a.active = committee_account.active;
});
}
modify( get_global_properties(), [&committee_members](global_property_object& gp)
{
gp.active_committee_members.clear();
std::transform(committee_members.begin(), committee_members.end(),
std::inserter(gp.active_committee_members, gp.active_committee_members.begin()),
[](const committee_member_object& d) { return d.id; });
});
} FC_CAPTURE_AND_RETHROW() }
void database::initialize_budget_record( fc::time_point_sec now, budget_record& rec )const
{
const dynamic_global_property_object& dpo = get_dynamic_global_properties();
const asset_object& core = get_core_asset();
const asset_dynamic_data_object& core_dd = get_core_dynamic_data();
rec.from_initial_reserve = core.reserved(*this);
rec.from_accumulated_fees = core_dd.accumulated_fees;
rec.from_unused_witness_budget = dpo.witness_budget;
if( (dpo.last_budget_time == fc::time_point_sec())
|| (now <= dpo.last_budget_time) )
{
rec.time_since_last_budget = 0;
return;
}
int64_t dt = (now - dpo.last_budget_time).to_seconds();
rec.time_since_last_budget = uint64_t( dt );
// We'll consider accumulated_fees to be reserved at the BEGINNING
// of the maintenance interval. However, for speed we only
// call modify() on the asset_dynamic_data_object once at the
// end of the maintenance interval. Thus the accumulated_fees
// are available for the budget at this point, but not included
// in core.reserved().
share_type reserve = rec.from_initial_reserve + core_dd.accumulated_fees;
// Similarly, we consider leftover witness_budget to be burned
// at the BEGINNING of the maintenance interval.
reserve += dpo.witness_budget;
fc::uint128_t budget_u128 = reserve.value;
budget_u128 *= uint64_t(dt);
budget_u128 *= GRAPHENE_CORE_ASSET_CYCLE_RATE;
//round up to the nearest satoshi -- this is necessary to ensure
// there isn't an "untouchable" reserve, and we will eventually
// be able to use the entire reserve
budget_u128 += ((uint64_t(1) << GRAPHENE_CORE_ASSET_CYCLE_RATE_BITS) - 1);
budget_u128 >>= GRAPHENE_CORE_ASSET_CYCLE_RATE_BITS;
if( budget_u128 < static_cast<fc::uint128_t>(reserve.value) )
rec.total_budget = share_type(static_cast<uint64_t>(budget_u128));
else
rec.total_budget = reserve;
return;
}
/**
* Update the budget for witnesses and workers.
*/
void database::process_budget()
{
try
{
const global_property_object& gpo = get_global_properties();
const dynamic_global_property_object& dpo = get_dynamic_global_properties();
const asset_dynamic_data_object& core = get_core_dynamic_data();
fc::time_point_sec now = head_block_time();
int64_t time_to_maint = (dpo.next_maintenance_time - now).to_seconds();
//
// The code that generates the next maintenance time should
// only produce a result in the future. If this assert
// fails, then the next maintenance time algorithm is buggy.
//
assert( time_to_maint > 0 );
//
// Code for setting chain parameters should validate
// block_interval > 0 (as well as the humans proposing /
// voting on changes to block interval).
//
assert( gpo.parameters.block_interval > 0 );
uint64_t blocks_to_maint = (uint64_t(time_to_maint) + gpo.parameters.block_interval - 1) / gpo.parameters.block_interval;
// blocks_to_maint > 0 because time_to_maint > 0,
// which means numerator is at least equal to block_interval
budget_record rec;
initialize_budget_record( now, rec );
share_type available_funds = rec.total_budget;
share_type witness_budget = gpo.parameters.witness_pay_per_block.value * blocks_to_maint;
rec.requested_witness_budget = witness_budget;
witness_budget = std::min(witness_budget, available_funds);
rec.witness_budget = witness_budget;
available_funds -= witness_budget;
fc::uint128_t worker_budget_u128 = gpo.parameters.worker_budget_per_day.value;
worker_budget_u128 *= uint64_t(time_to_maint);
worker_budget_u128 /= 60*60*24;
share_type worker_budget;
if( worker_budget_u128 >= static_cast<fc::uint128_t>(available_funds.value) )
worker_budget = available_funds;
else
worker_budget = static_cast<uint64_t>(worker_budget_u128);
rec.worker_budget = worker_budget;
available_funds -= worker_budget;
share_type leftover_worker_funds = worker_budget;
pay_workers(leftover_worker_funds);
rec.leftover_worker_funds = leftover_worker_funds;
available_funds += leftover_worker_funds;
rec.supply_delta = rec.witness_budget
+ rec.worker_budget
- rec.leftover_worker_funds
- rec.from_accumulated_fees
- rec.from_unused_witness_budget;
modify(core, [&]( asset_dynamic_data_object& _core )
{
_core.current_supply = (_core.current_supply + rec.supply_delta );
assert( rec.supply_delta ==
witness_budget
+ worker_budget
- leftover_worker_funds
- _core.accumulated_fees
- dpo.witness_budget
);
_core.accumulated_fees = 0;
});
modify(dpo, [&]( dynamic_global_property_object& _dpo )
{
// Since initial witness_budget was rolled into
// available_funds, we replace it with witness_budget
// instead of adding it.
_dpo.witness_budget = witness_budget;
_dpo.last_budget_time = now;
});
create< budget_record_object >( [&]( budget_record_object& _rec )
{
_rec.time = head_block_time();
_rec.record = rec;
});
// available_funds is money we could spend, but don't want to.
// we simply let it evaporate back into the reserve.
}
FC_CAPTURE_AND_RETHROW()
}
template< typename Visitor >
void visit_special_authorities( const database& db, Visitor visit )
{
const auto& sa_idx = db.get_index_type< special_authority_index >().indices().get<by_id>();
for( const special_authority_object& sao : sa_idx )
{
const account_object& acct = sao.account(db);
if( !acct.owner_special_authority.is_type< no_special_authority >() )
{
visit( acct, true, acct.owner_special_authority );
}
if( !acct.active_special_authority.is_type< no_special_authority >() )
{
visit( acct, false, acct.active_special_authority );
}
}
}
void update_top_n_authorities( database& db )
{
visit_special_authorities( db,
[&]( const account_object& acct, bool is_owner, const special_authority& auth )
{
if( auth.is_type< top_holders_special_authority >() )
{
// use index to grab the top N holders of the asset and vote_counter to obtain the weights
const top_holders_special_authority& tha = auth.get< top_holders_special_authority >();
vote_counter vc;
const auto& bal_idx = db.get_index_type< account_balance_index >().indices().get< by_asset_balance >();
uint8_t num_needed = tha.num_top_holders;
if( num_needed == 0 )
return;
// find accounts
const auto range = bal_idx.equal_range( boost::make_tuple( tha.asset ) );
for( const account_balance_object& bal : boost::make_iterator_range( range.first, range.second ) )
{
assert( bal.asset_type == tha.asset );
if( bal.owner == acct.id )
continue;
vc.add( bal.owner, bal.balance.value );
--num_needed;
if( num_needed == 0 )
break;
}
db.modify( acct, [&]( account_object& a )
{
vc.finish( is_owner ? a.owner : a.active );
if( !vc.is_empty() )
a.top_n_control_flags |= (is_owner ? account_object::top_n_control_owner : account_object::top_n_control_active);
} );
}
} );
}
void split_fba_balance(
database& db,
uint64_t fba_id,
uint16_t network_pct,
uint16_t designated_asset_buyback_pct,
uint16_t designated_asset_issuer_pct
)
{
FC_ASSERT( uint32_t(network_pct) + uint32_t(designated_asset_buyback_pct) + uint32_t(designated_asset_issuer_pct) == GRAPHENE_100_PERCENT );
const fba_accumulator_object& fba = fba_accumulator_id_type( fba_id )(db);
if( fba.accumulated_fba_fees == 0 )
return;
const asset_dynamic_data_object& core_dd = db.get_core_dynamic_data();
if( !fba.is_configured(db) )
{
ilog( "${n} core given to network at block ${b} due to non-configured FBA", ("n", fba.accumulated_fba_fees)("b", db.head_block_time()) );
db.modify( core_dd, [&]( asset_dynamic_data_object& _core_dd )
{
_core_dd.current_supply -= fba.accumulated_fba_fees;
} );
db.modify( fba, [&]( fba_accumulator_object& _fba )
{
_fba.accumulated_fba_fees = 0;
} );
return;
}
fc::uint128_t buyback_amount_128 = fba.accumulated_fba_fees.value;
buyback_amount_128 *= designated_asset_buyback_pct;
buyback_amount_128 /= GRAPHENE_100_PERCENT;
share_type buyback_amount = static_cast<uint64_t>(buyback_amount_128);
fc::uint128_t issuer_amount_128 = fba.accumulated_fba_fees.value;
issuer_amount_128 *= designated_asset_issuer_pct;
issuer_amount_128 /= GRAPHENE_100_PERCENT;
share_type issuer_amount = static_cast<uint64_t>(issuer_amount_128);
// this assert should never fail
FC_ASSERT( buyback_amount + issuer_amount <= fba.accumulated_fba_fees );
share_type network_amount = fba.accumulated_fba_fees - (buyback_amount + issuer_amount);
const asset_object& designated_asset = (*fba.designated_asset)(db);
if( network_amount != 0 )
{
db.modify( core_dd, [&]( asset_dynamic_data_object& _core_dd )
{
_core_dd.current_supply -= network_amount;
} );
}
fba_distribute_operation vop;
vop.account_id = *designated_asset.buyback_account;
vop.fba_id = fba.id;
vop.amount = buyback_amount;
if( vop.amount != 0 )
{
db.adjust_balance( *designated_asset.buyback_account, asset(buyback_amount) );
db.push_applied_operation(vop);
}
vop.account_id = designated_asset.issuer;
vop.fba_id = fba.id;
vop.amount = issuer_amount;
if( vop.amount != 0 )
{
db.adjust_balance( designated_asset.issuer, asset(issuer_amount) );
db.push_applied_operation(vop);
}
db.modify( fba, [&]( fba_accumulator_object& _fba )
{
_fba.accumulated_fba_fees = 0;
} );
}
void distribute_fba_balances( database& db )
{
split_fba_balance( db, fba_accumulator_id_transfer_to_blind , 20*GRAPHENE_1_PERCENT, 60*GRAPHENE_1_PERCENT, 20*GRAPHENE_1_PERCENT );
split_fba_balance( db, fba_accumulator_id_blind_transfer , 20*GRAPHENE_1_PERCENT, 60*GRAPHENE_1_PERCENT, 20*GRAPHENE_1_PERCENT );
split_fba_balance( db, fba_accumulator_id_transfer_from_blind, 20*GRAPHENE_1_PERCENT, 60*GRAPHENE_1_PERCENT, 20*GRAPHENE_1_PERCENT );
}
void create_buyback_orders( database& db )
{
const auto& bbo_idx = db.get_index_type< buyback_index >().indices().get<by_id>();
const auto& bal_idx = db.get_index_type< primary_index< account_balance_index > >().get_secondary_index< balances_by_account_index >();
for( const buyback_object& bbo : bbo_idx )
{
const asset_object& asset_to_buy = bbo.asset_to_buy(db);
assert( asset_to_buy.buyback_account.valid() );
const account_object& buyback_account = (*(asset_to_buy.buyback_account))(db);
if( !buyback_account.allowed_assets.valid() )
{
wlog( "skipping buyback account ${b} at block ${n} because allowed_assets does not exist", ("b", buyback_account)("n", db.head_block_num()) );
continue;
}
for( const auto& entry : bal_idx.get_account_balances( buyback_account.id ) )
{
const auto* it = entry.second;
asset_id_type asset_to_sell = it->asset_type;
share_type amount_to_sell = it->balance;
if( asset_to_sell == asset_to_buy.id )
continue;
if( amount_to_sell == 0 )
continue;
if( buyback_account.allowed_assets->find( asset_to_sell ) == buyback_account.allowed_assets->end() )
{
wlog( "buyback account ${b} not selling disallowed holdings of asset ${a} at block ${n}", ("b", buyback_account)("a", asset_to_sell)("n", db.head_block_num()) );
continue;
}
try
{
transaction_evaluation_state buyback_context(&db);
buyback_context.skip_fee_schedule_check = true;
limit_order_create_operation create_vop;
create_vop.fee = asset( 0, asset_id_type() );
create_vop.seller = buyback_account.id;
create_vop.amount_to_sell = asset( amount_to_sell, asset_to_sell );
create_vop.min_to_receive = asset( 1, asset_to_buy.id );
create_vop.expiration = time_point_sec::maximum();
create_vop.fill_or_kill = false;
limit_order_id_type order_id = db.apply_operation( buyback_context, create_vop ).get< object_id_type >();
if( db.find( order_id ) != nullptr )
{
limit_order_cancel_operation cancel_vop;
cancel_vop.fee = asset( 0, asset_id_type() );
cancel_vop.order = order_id;
cancel_vop.fee_paying_account = buyback_account.id;
db.apply_operation( buyback_context, cancel_vop );
}
}
catch( const fc::exception& e )
{
// we can in fact get here, e.g. if asset issuer of buy/sell asset blacklists/whitelists the buyback account
wlog( "Skipping buyback processing selling ${as} for ${ab} for buyback account ${b} at block ${n}; exception was ${e}",
("as", asset_to_sell)("ab", asset_to_buy)("b", buyback_account)("n", db.head_block_num())("e", e.to_detail_string()) );
continue;
}
}
}
return;
}
void deprecate_annual_members( database& db )
{
const auto& account_idx = db.get_index_type<account_index>().indices().get<by_id>();
fc::time_point_sec now = db.head_block_time();
for( const account_object& acct : account_idx )
{
try
{
transaction_evaluation_state upgrade_context(&db);
upgrade_context.skip_fee_schedule_check = true;
if( acct.is_annual_member( now ) )
{
account_upgrade_operation upgrade_vop;
upgrade_vop.fee = asset( 0, asset_id_type() );
upgrade_vop.account_to_upgrade = acct.id;
upgrade_vop.upgrade_to_lifetime_member = true;
db.apply_operation( upgrade_context, upgrade_vop );
}
}
catch( const fc::exception& e )
{
// we can in fact get here, e.g. if asset issuer of buy/sell asset blacklists/whitelists the buyback account
wlog( "Skipping annual member deprecate processing for account ${a} (${an}) at block ${n}; exception was ${e}",
("a", acct.id)("an", acct.name)("n", db.head_block_num())("e", e.to_detail_string()) );
continue;
}
}
return;
}
void database::process_bids( const asset_bitasset_data_object& bad )
{
if( bad.is_prediction_market ) return;
if( bad.current_feed.settlement_price.is_null() ) return;
asset_id_type to_revive_id = (asset( 0, bad.options.short_backing_asset ) * bad.settlement_price).asset_id;
const asset_object& to_revive = to_revive_id( *this );
const asset_dynamic_data_object& bdd = to_revive.dynamic_data( *this );
const auto& bid_idx = get_index_type< collateral_bid_index >().indices().get<by_price>();
const auto start = bid_idx.lower_bound( boost::make_tuple( to_revive_id, price::max( bad.options.short_backing_asset, to_revive_id ), collateral_bid_id_type() ) );
share_type covered = 0;
auto itr = start;
while( covered < bdd.current_supply && itr != bid_idx.end() && itr->inv_swan_price.quote.asset_id == to_revive_id )
{
const collateral_bid_object& bid = *itr;
asset debt_in_bid = bid.inv_swan_price.quote;
if( debt_in_bid.amount > bdd.current_supply )
debt_in_bid.amount = bdd.current_supply;
asset total_collateral = debt_in_bid * bad.settlement_price;
total_collateral += bid.inv_swan_price.base;
price call_price = price::call_price( debt_in_bid, total_collateral, bad.current_feed.maintenance_collateral_ratio );
if( ~call_price >= bad.current_feed.settlement_price ) break;
covered += debt_in_bid.amount;
++itr;
}
if( covered < bdd.current_supply ) return;
const auto end = itr;
share_type to_cover = bdd.current_supply;
share_type remaining_fund = bad.settlement_fund;
for( itr = start; itr != end; )
{
const collateral_bid_object& bid = *itr;
++itr;
asset debt_in_bid = bid.inv_swan_price.quote;
if( debt_in_bid.amount > bdd.current_supply )
debt_in_bid.amount = bdd.current_supply;
share_type debt = debt_in_bid.amount;
share_type collateral = (debt_in_bid * bad.settlement_price).amount;
if( debt >= to_cover )
{
debt = to_cover;
collateral = remaining_fund;
}
to_cover -= debt;
remaining_fund -= collateral;
execute_bid( bid, debt, collateral, bad.current_feed );
}
FC_ASSERT( remaining_fund == 0 );
FC_ASSERT( to_cover == 0 );
_cancel_bids_and_revive_mpa( to_revive, bad );
}
/// Reset call_price of all call orders according to their remaining collateral and debt.
/// Do not update orders of prediction markets because we're sure they're up to date.
void update_call_orders_hf_343( database& db )
{
// Update call_price
wlog( "Updating all call orders for hardfork core-343 at block ${n}", ("n",db.head_block_num()) );
asset_id_type current_asset;
const asset_bitasset_data_object* abd = nullptr;
// by_collateral index won't change after call_price updated, so it's safe to iterate
for( const auto& call_obj : db.get_index_type<call_order_index>().indices().get<by_collateral>() )
{
if( current_asset != call_obj.debt_type() ) // debt type won't be asset_id_type(), abd will always get initialized
{
current_asset = call_obj.debt_type();
abd = ¤t_asset(db).bitasset_data(db);
}
if( !abd || abd->is_prediction_market ) // nothing to do with PM's; check !abd just to be safe
continue;
db.modify( call_obj, [abd]( call_order_object& call ) {
call.call_price = price::call_price( call.get_debt(), call.get_collateral(),
abd->current_feed.maintenance_collateral_ratio );
});
}
wlog( "Done updating all call orders for hardfork core-343 at block ${n}", ("n",db.head_block_num()) );
}
/// Reset call_price of all call orders to (1,1) since it won't be used in the future.
/// Update PMs as well.
void update_call_orders_hf_1270( database& db )
{
// Update call_price
wlog( "Updating all call orders for hardfork core-1270 at block ${n}", ("n",db.head_block_num()) );
for( const auto& call_obj : db.get_index_type<call_order_index>().indices().get<by_id>() )
{
db.modify( call_obj, []( call_order_object& call ) {
call.call_price.base.amount = 1;
call.call_price.quote.amount = 1;
});
}
wlog( "Done updating all call orders for hardfork core-1270 at block ${n}", ("n",db.head_block_num()) );
}
/// Match call orders for all bitAssets, including PMs.
void match_call_orders( database& db )
{
// Match call orders
wlog( "Matching call orders at block ${n}", ("n",db.head_block_num()) );
const auto& asset_idx = db.get_index_type<asset_index>().indices().get<by_type>();
auto itr = asset_idx.lower_bound( true /** market issued */ );
while( itr != asset_idx.end() )
{
const asset_object& a = *itr;
++itr;
// be here, next_maintenance_time should have been updated already
db.check_call_orders( a, true, false ); // allow black swan, and call orders are taker
}
wlog( "Done matching call orders at block ${n}", ("n",db.head_block_num()) );
}
void database::process_bitassets()
{
time_point_sec head_time = head_block_time();
uint32_t head_epoch_seconds = head_time.sec_since_epoch();
bool after_hf_core_518 = ( head_time >= HARDFORK_CORE_518_TIME ); // clear expired feeds
const auto update_bitasset = [this,head_time,head_epoch_seconds,after_hf_core_518]( asset_bitasset_data_object &o )
{
o.force_settled_volume = 0; // Reset all BitAsset force settlement volumes to zero
// clear expired feeds
if( after_hf_core_518 )
{
const auto &asset = get( o.asset_id );
auto flags = asset.options.flags;
if ( ( flags & ( witness_fed_asset | committee_fed_asset ) ) &&
o.options.feed_lifetime_sec < head_epoch_seconds ) // if smartcoin && check overflow
{
fc::time_point_sec calculated = head_time - o.options.feed_lifetime_sec;
for( auto itr = o.feeds.rbegin(); itr != o.feeds.rend(); ) // loop feeds
{
auto feed_time = itr->second.first;
std::advance( itr, 1 );
if( feed_time < calculated )
o.feeds.erase( itr.base() ); // delete expired feed
}
}
}
};
for( const auto& d : get_index_type<asset_bitasset_data_index>().indices() )
{
modify( d, update_bitasset );
if( d.has_settlement() )
process_bids(d);
}
}
/****
* @brief a one-time data process to correct max_supply
*/
void process_hf_1465( database& db )
{
const auto head_num = db.head_block_num();
wlog( "Processing hard fork core-1465 at block ${n}", ("n",head_num) );
// for each market issued asset
const auto& asset_idx = db.get_index_type<asset_index>().indices().get<by_type>();
for( auto asset_itr = asset_idx.lower_bound(true); asset_itr != asset_idx.end(); ++asset_itr )
{
const auto& current_asset = *asset_itr;
graphene::chain::share_type current_supply = current_asset.dynamic_data(db).current_supply;
graphene::chain::share_type max_supply = current_asset.options.max_supply;
if (current_supply > max_supply && max_supply != GRAPHENE_MAX_SHARE_SUPPLY)
{
wlog( "Adjusting max_supply of ${asset} because current_supply (${current_supply}) is greater than ${old}.",
("asset", current_asset.symbol)
("current_supply", current_supply.value)
("old", max_supply));
db.modify<asset_object>( current_asset, [current_supply](asset_object& obj) {
obj.options.max_supply = graphene::chain::share_type(std::min(current_supply.value, GRAPHENE_MAX_SHARE_SUPPLY));
});
}
}
}
void update_median_feeds(database& db)
{
time_point_sec head_time = db.head_block_time();
time_point_sec next_maint_time = db.get_dynamic_global_properties().next_maintenance_time;
const auto update_bitasset = [head_time, next_maint_time]( asset_bitasset_data_object &o )
{
o.update_median_feeds( head_time, next_maint_time );
};
for( const auto& d : db.get_index_type<asset_bitasset_data_index>().indices() )
{
db.modify( d, update_bitasset );
}
}
/******
* @brief one-time data process for hard fork core-868-890
*
* Prior to hardfork 868, switching a bitasset's shorting asset would not reset its
* feeds. This method will run at the hardfork time, and erase (or nullify) feeds
* that have incorrect backing assets.
* https://github.com/bitshares/bitshares-core/issues/868
*
* Prior to hardfork 890, changing a bitasset's feed expiration time would not
* trigger a median feed update. This method will run at the hardfork time, and
* correct all median feed data.
* https://github.com/bitshares/bitshares-core/issues/890