Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Defer MCR update to next maintenance interval #941

Closed
wants to merge 14 commits into from
Closed
50 changes: 39 additions & 11 deletions libraries/chain/asset_evaluator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -624,7 +624,7 @@ static bool update_bitasset_object_options(
const asset_update_bitasset_operation& op, database& db,
asset_bitasset_data_object& bdo, const asset_object& asset_to_update )
{
const fc::time_point_sec& next_maint_time = db.get_dynamic_global_properties().next_maintenance_time;
fc::time_point_sec next_maint_time = db.get_dynamic_global_properties().next_maintenance_time;
bool after_hf_core_868_890 = ( next_maint_time > HARDFORK_CORE_868_890_TIME );

// If the minimum number of feeds to calculate a median has changed, we need to recalculate the median
Expand Down Expand Up @@ -674,11 +674,30 @@ static bool update_bitasset_object_options(

if( should_update_feeds )
{
const auto old_feed_price = bdo.current_feed.settlement_price;
bdo.update_median_feeds( db.head_block_time() );
const auto old_feed = bdo.current_feed;
bool defer_mcr_update = ( next_maint_time > HARDFORK_CORE_935_TIME
&& asset_to_update.dynamic_data(db).current_supply > 0 );
bdo.update_median_feeds( db.head_block_time(), defer_mcr_update );

// TODO review and refactor / cleanup after hard fork:
// 1. if hf_core_868_890 and core-935 occurred at same time
// 2. if wlog did not actually get called

// We need to call check_call_orders if the price feed changes after hardfork core-935
if( next_maint_time > HARDFORK_CORE_935_TIME )
return ( !( old_feed == bdo.current_feed ) );

// We need to call check_call_orders if the settlement price changes after hardfork core-868-890
return after_hf_core_868_890 && old_feed_price != bdo.current_feed.settlement_price;
if( after_hf_core_868_890 )
{
if( old_feed.settlement_price != bdo.current_feed.settlement_price )
return true;
else
{
if( !( old_feed == bdo.current_feed ) )
wlog( "Settlement price did not change but current_feed changed at block ${b}", ("b",db.head_block_num()) );
}
}
}

return false;
Expand Down Expand Up @@ -722,13 +741,17 @@ void_result asset_update_feed_producers_evaluator::do_evaluate(const asset_updat

const asset_bitasset_data_object& b = a.bitasset_data(d);
bitasset_to_update = &b;
asset_to_update = &a;
FC_ASSERT( a.issuer == o.issuer );
return void_result();
} FC_CAPTURE_AND_RETHROW( (o) ) }

void_result asset_update_feed_producers_evaluator::do_apply(const asset_update_feed_producers_evaluator::operation_type& o)
{ try {
db().modify(*bitasset_to_update, [&](asset_bitasset_data_object& a) {
database& d = db();
bool defer_mcr_update = ( d.get_dynamic_global_properties().next_maintenance_time > HARDFORK_CORE_935_TIME
&& asset_to_update->dynamic_data(d).current_supply > 0 );
d.modify(*bitasset_to_update, [&d, &o, defer_mcr_update](asset_bitasset_data_object& a) {
//This is tricky because I have a set of publishers coming in, but a map of publisher to feed is stored.
//I need to update the map such that the keys match the new publishers, but not munge the old price feeds from
//publishers who are being kept.
Expand All @@ -744,9 +767,9 @@ void_result asset_update_feed_producers_evaluator::do_apply(const asset_update_f
for( auto itr = o.new_feed_producers.begin(); itr != o.new_feed_producers.end(); ++itr )
if( !a.feeds.count(*itr) )
a.feeds[*itr];
a.update_median_feeds(db().head_block_time());
a.update_median_feeds( d.head_block_time(), defer_mcr_update );
});
db().check_call_orders( o.asset_to_update(db()) );
d.check_call_orders( *asset_to_update );

return void_result();
} FC_CAPTURE_AND_RETHROW( (o) ) }
Expand Down Expand Up @@ -906,6 +929,9 @@ void_result asset_publish_feeds_evaluator::do_evaluate(const asset_publish_feed_
FC_ASSERT(bitasset.feeds.count(o.publisher));
}

asset_to_update = &base;
bitasset_to_update = &bitasset;

return void_result();
} FC_CAPTURE_AND_RETHROW((o)) }

Expand All @@ -914,14 +940,16 @@ void_result asset_publish_feeds_evaluator::do_apply(const asset_publish_feed_ope

database& d = db();

const asset_object& base = o.asset_id(d);
const asset_bitasset_data_object& bad = base.bitasset_data(d);
const asset_object& base = *asset_to_update;
const asset_bitasset_data_object& bad = *bitasset_to_update;
bool defer_mcr_update = ( d.get_dynamic_global_properties().next_maintenance_time > HARDFORK_CORE_935_TIME
&& base.dynamic_data(d).current_supply > 0 );

auto old_feed = bad.current_feed;
// Store medians for this asset
d.modify(bad , [&o,&d](asset_bitasset_data_object& a) {
d.modify(bad , [&o, &d, defer_mcr_update](asset_bitasset_data_object& a) {
a.feeds[o.publisher] = make_pair(d.head_block_time(), o.feed);
a.update_median_feeds(d.head_block_time());
a.update_median_feeds( d.head_block_time(), defer_mcr_update );
});

if( !(old_feed == bad.current_feed) )
Expand Down
32 changes: 20 additions & 12 deletions libraries/chain/asset_object.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,15 +43,7 @@ share_type asset_bitasset_data_object::max_force_settlement_volume(share_type cu
return volume.to_uint64();
}

/******
* @brief calculate the median feed
*
* This calculates the median feed. It sets the current_feed_publication_time
* and current_feed member variables
*
* @param current_time the time to use in the calculations
*/
void graphene::chain::asset_bitasset_data_object::update_median_feeds(time_point_sec current_time)
void graphene::chain::asset_bitasset_data_object::update_median_feeds( time_point_sec current_time, bool defer_mcr_update )
{
current_feed_publication_time = current_time;
vector<std::reference_wrapper<const price_feed>> current_feeds;
Expand All @@ -71,12 +63,15 @@ void graphene::chain::asset_bitasset_data_object::update_median_feeds(time_point
{
//... don't calculate a median, and set a null feed
current_feed_publication_time = current_time;
current_feed = price_feed();
price_feed null_feed;
null_feed.maintenance_collateral_ratio = current_feed.maintenance_collateral_ratio; // keep old MCR
update_current_feed( null_feed, defer_mcr_update );
return;
}
if( current_feeds.size() == 1 )
{
current_feed = std::move(current_feeds.front());
price_feed only_feed = std::move( current_feeds.front() );
update_current_feed( only_feed, defer_mcr_update );
return;
}

Expand All @@ -94,7 +89,20 @@ void graphene::chain::asset_bitasset_data_object::update_median_feeds(time_point
#undef CALCULATE_MEDIAN_VALUE
// *** End Median Calculations ***

current_feed = median_feed;
update_current_feed( median_feed, defer_mcr_update );
}

void graphene::chain::asset_bitasset_data_object::update_current_feed( price_feed& new_feed, bool defer_mcr_update )
{
if( defer_mcr_update )
{
if( current_feed.maintenance_collateral_ratio == new_feed.maintenance_collateral_ratio )
pending_maintenance_collateral_ratio.reset();
else
pending_maintenance_collateral_ratio = new_feed.maintenance_collateral_ratio;
new_feed.maintenance_collateral_ratio = current_feed.maintenance_collateral_ratio;
}
current_feed = std::move( new_feed );
}


Expand Down
139 changes: 111 additions & 28 deletions libraries/chain/db_maint.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -766,38 +766,99 @@ void database::process_bids( const asset_bitasset_data_object& bad )
_cancel_bids_and_revive_mpa( to_revive, bad );
}

void update_and_match_call_orders( database& db )
/**
* @brief Update and match call orders
* @param db The database
* @param bitasset set to `nullptr` to update call orders of all assets, otherwise only update call orders of this asset
*/
void update_and_match_call_orders( database& db, const asset_bitasset_data_object* bitasset = nullptr )
{
const auto head_num = db.head_block_num();
const asset_object* asset_obj = nullptr;
if( !bitasset )
wlog( "Updating all call orders for hardfork core-343/935 at block ${n}", ("n",head_num) );
else
{
asset_obj = &bitasset->asset_id(db);
wlog( "Updating all call orders for asset ${sym} (${aid}) due to MCR change at block ${n}",
("aid",asset_obj->get_id())("sym",asset_obj->symbol)("n",head_num) );
}

// 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;

// Save variables outside of the iteration for better performance
asset_id_type current_asset = bitasset ? bitasset->asset_id : asset_id_type();
const asset_bitasset_data_object* abd = bitasset;

// 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>() )
const auto& call_idx = db.get_index_type<call_order_index>().indices().get<by_collateral>();
auto citr = abd ? call_idx.lower_bound( price::min( abd->options.short_backing_asset, current_asset ) )
: call_idx.begin();
auto cend = abd ? call_idx.upper_bound( price::max( abd->options.short_backing_asset, current_asset ) )
: call_idx.end();
while( citr != cend )
{
if( current_asset != call_obj.debt_type() ) // debt type won't be asset_id_type(), abd will always get initialized
{
const call_order_object& call_obj = *citr;
++citr;
if( !bitasset && current_asset != call_obj.debt_type() ) // only do this when updating call orders of all assets
{ // debt type won't be asset_id_type(), so abd will always get initialized
current_asset = call_obj.debt_type();
abd = &current_asset(db).bitasset_data(db);
}
if( !abd || abd->is_prediction_market ) // nothing to do with PM's; check !abd just to be safe
continue;
// note: to be consistent with `call_order_update_evaluator::do_apply()`, process prediction markets as well
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 );
});
}

// Match call orders
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() )
if( bitasset )
{
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
// Note: asset_obj should have been updated earlier, so will be valid
db.check_call_orders( *asset_obj, true, false ); // allow black swan, and call orders are taker
wlog( "Done updating all call orders for asset ${sym} (${aid}) due to MCR change at block ${n}",
("aid",asset_obj->get_id())("sym",asset_obj->symbol)("n",head_num) );
}
else
{
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 updating all call orders for hardfork core-343/935 at block ${n}", ("n",head_num) );
}
}

/**
* @brief Process pending_maintenance_collateral_ratio data for all MPA's
* @param db The database
*/
void process_pending_mcr( database& db )
{
const auto head_num = db.head_block_num();
const auto& bitasset_idx = db.get_index_type<asset_bitasset_data_index>().indices().get<by_pending_mcr>();
auto ba_itr = bitasset_idx.upper_bound( optional<uint16_t>() ); // has something pending
while( ba_itr != bitasset_idx.end() )
{
const asset_bitasset_data_object& abd = *ba_itr;
wlog( "Updating MCR from ${old} to ${new} for bitasset ${bid} / asset ${aid} at block ${n}",
("old",abd.current_feed.maintenance_collateral_ratio)
("new",abd.pending_maintenance_collateral_ratio)
("bid",ba_itr->id)("aid",abd.asset_id)("n",head_num) );
++ba_itr;
db.modify( abd, []( asset_bitasset_data_object& abdo )
{
abdo.current_feed.maintenance_collateral_ratio = *abdo.pending_maintenance_collateral_ratio;
abdo.pending_maintenance_collateral_ratio.reset();
});
update_and_match_call_orders( db, &abd );
}
wlog( "Done updating all call orders for hardfork core-343 at block ${n}", ("n",db.head_block_num()) );
}

void database::process_bitassets()
Expand Down Expand Up @@ -853,13 +914,16 @@ void database::process_bitassets()
*
* @param db the database
* @param skip_check_call_orders true if check_call_orders() should not be called
* @param next_maintenance_time the next maintenance time
*/
// TODO: for better performance, this function can be removed if it actually updated nothing at hf time.
// * Also need to update related test cases
// * NOTE: perhaps the removal can't be applied to testnet
void process_hf_868_890( database& db, bool skip_check_call_orders )
// * NOTE: the removal can't be applied to testnet
void process_hf_868_890( database& db, bool skip_check_call_orders, time_point_sec next_maintenance_time )
{
auto head_time = db.head_block_time();
const auto head_time = db.head_block_time();
const auto head_num = db.head_block_num();
wlog( "Processing hard fork core-868-890 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 )
Expand All @@ -875,8 +939,8 @@ void process_hf_868_890( database& db, bool skip_check_call_orders )

// for each feed
const asset_bitasset_data_object& bitasset_data = current_asset.bitasset_data(db);
// NOTE: We'll only need old_price if HF343 hasn't rolled out yet
auto old_price = bitasset_data.current_feed.settlement_price;
// NOTE: We'll only need old_feed if HF343 hasn't rolled out yet
auto old_feed = bitasset_data.current_feed;
bool feeds_changed = false; // did any feed change
auto itr = bitasset_data.feeds.begin();
while( itr != bitasset_data.feeds.end() )
Expand Down Expand Up @@ -916,22 +980,36 @@ void process_hf_868_890( database& db, bool skip_check_call_orders )
}

// always update the median feed due to https://github.com/bitshares/bitshares-core/issues/890
db.modify( bitasset_data, [&head_time]( asset_bitasset_data_object &obj ) {
obj.update_median_feeds( head_time );
bool defer_mcr_update = ( next_maintenance_time > HARDFORK_CORE_935_TIME
&& current_asset.dynamic_data(db).current_supply > 0 );
db.modify( bitasset_data, [head_time,defer_mcr_update]( asset_bitasset_data_object &obj ) {
obj.update_median_feeds( head_time, defer_mcr_update );
});

bool median_changed = ( old_price != bitasset_data.current_feed.settlement_price );
if( median_changed )
bool median_changed = ( old_feed.settlement_price != bitasset_data.current_feed.settlement_price );
bool median_feed_changed = ( !( old_feed == bitasset_data.current_feed ) );
if( median_feed_changed )
{
wlog( "Median feed for asset ${asset_sym} (${asset_id}) changed during hardfork core-868-890",
("asset_sym", current_asset.symbol)("asset_id", current_asset.id) );
}

// Note: due to bitshares-core issue #935, the check below (using median_changed) is incorrect.
// However, `skip_check_call_orders` will likely be true in both testnet and mainnet,
// so effectively the incorrect code won't make a difference.
// Additionally, we have code to update all call orders again during hardfork core-935
// TODO cleanup after hard fork
if( !skip_check_call_orders && median_changed ) // check_call_orders should be called
{
db.check_call_orders( current_asset );
}
else if( !skip_check_call_orders && median_feed_changed )
{
wlog( "Incorrectly skipped check_call_orders for asset ${asset_sym} (${asset_id}) during hardfork core-868-890",
("asset_sym", current_asset.symbol)("asset_id", current_asset.id) );
}
} // for each market issued asset
wlog( "Done processing hard fork core-868-890 at block ${n}", ("n",head_num) );
}

void database::perform_chain_maintenance(const signed_block& next_block, const global_property_object& global_props)
Expand Down Expand Up @@ -1087,12 +1165,17 @@ void database::perform_chain_maintenance(const signed_block& next_block, const g

// To reset call_price of all call orders, then match by new rule
bool to_update_and_match_call_orders = false;
if( (dgpo.next_maintenance_time <= HARDFORK_CORE_343_TIME) && (next_maintenance_time > HARDFORK_CORE_343_TIME) )
if( ( dgpo.next_maintenance_time <= HARDFORK_CORE_343_TIME && next_maintenance_time > HARDFORK_CORE_343_TIME )
|| ( dgpo.next_maintenance_time <= HARDFORK_CORE_935_TIME && next_maintenance_time > HARDFORK_CORE_935_TIME ) )
to_update_and_match_call_orders = true;

// Process inconsistent price feeds
if( (dgpo.next_maintenance_time <= HARDFORK_CORE_868_890_TIME) && (next_maintenance_time > HARDFORK_CORE_868_890_TIME) )
process_hf_868_890( *this, to_update_and_match_call_orders );
process_hf_868_890( *this, to_update_and_match_call_orders, next_maintenance_time );

// Process pending_maitenance_collateral_ratio data for all MPA's
if( dgpo.next_maintenance_time > HARDFORK_CORE_935_TIME )
process_pending_mcr( *this );

modify(dgpo, [next_maintenance_time](dynamic_global_property_object& d) {
d.next_maintenance_time = next_maintenance_time;
Expand Down
Loading