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

BSIP 36 Candidate #832

Merged
merged 11 commits into from
May 7, 2018
Merged

BSIP 36 Candidate #832

merged 11 commits into from
May 7, 2018

Conversation

oxarbitrage
Copy link
Member

The fix in db_maint was already introduced in #598 but needed hardfork protection, also there was a small bug on it to delete the correct feeds that now is fixed.

Test cases reproduce the problem before and after hardfork. I created the test in a new file as i was not sure where it could fit. This can be changed.

Problem is present only with smartcoins(mpas that are feeded by the witnesses)

Had to add a bunch of witnesses in the testcases to be able to remove default and control them, there is probably a simpler way to do this(by modifying the genesis?)

Open to all kind of feedback and test cases suggestions.

Issue: #518
BSIP: https://github.com/bitshares/bsips/blob/master/bsip-0036.md
Replaces: #598

@abitmore
Copy link
Member

abitmore commented Apr 8, 2018

Had to add a bunch of witnesses in the testcases to be able to remove default and control them, there is probably a simpler way to do this(by modifying the genesis?)

IMHO using update_feed_producers( bitusd, {sam.id} ); in test cases would be enough. It doesn't harm to test with voted witnesses, it's more complicated.

@abitmore abitmore added this to the 201805 - Consensus Changing Release milestone Apr 8, 2018
@oxarbitrage
Copy link
Member Author

using update_feed_producers is different and the impact of the bug is minor(there is a way to remove feed).
when feed producers are removed by the issuer(by op) the feed for the account is removed.

still, if the operation is never called feed will stay forever, the after HF fix will delete not needed and expired feeds in this case.

added test case bsip36_update_feed_producers at 10c7e8f with this path.

for( auto itr = o.feeds.begin(); itr != o.feeds.end();)
{
auto feed_time = itr[0].second.first;
if( feed_time + (o.options.feed_lifetime_sec) < head_block_time() && o.feeds.size() > o.options.minimum_feeds)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Why leave minimum_feeds in? They will be ignored anyway.
  2. No need to cycle through the rest when the minimum has been reached.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why leave minimum_feeds in? They will be ignored anyway.

are you sure this is safe? i guess the only thing that matters if all feeds are expired is current_feed.

just confirming before making the change.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assets start out with no feeds at all and go through a time with less than minimum. I don't see why it shouldn't be safe.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@oxarbitrage please remove && o.feeds.size() > o.options.minimum_feeds check. It's not relevant here.

No need to cycle through the rest when the minimum has been reached.

@pmconrad what did this mean?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Once o.feeds.size() > o.options.minimum_feeds becomes false it will remain false, so once we reach that point we can as well break out of the loop.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Anyway I think this doesn't belong to here, since we want to remove ALL expired price feed entries.

@oxarbitrage
Copy link
Member Author

i had the intention initially to remove expired non needed feeds from MPAs that are fed by witnesses or by feed producers as well. However, it seems that this will only be possible on witness fed MPAs(aka smartcoins).

the problem is in non smartcoins the feed producers are actually entries in the feed field of the bitasset options, if removed at expiration the producer will not be valid anymore to post a new one unless the issuer update the producers again by asset_update_feed_producers_operation.

the related code is here:
https://github.com/bitshares/bitshares-core/blob/master/libraries/chain/asset_evaluator.cpp#L546-L558

as you can see witness feed producer are validated different and it doesn't matter if they are already in the feed fields or not while in the last case the feed producer need to be inside the feed field to work.

@pmconrad @abitmore please let me know what do you think because i think we need to change code to only go after smartcoins and leave the other mpas untouched.

Copy link
Member

@abitmore abitmore left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As mentioned in last comment, please only remove feeds for assets enabled witness_fed_asset or committee_fed_asset.

// Remove all expired feeds
for( auto itr = o.feeds.begin(); itr != o.feeds.end();)
{
auto feed_time = itr[0].second.first;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

itr[0] here looks strange. Why not use itr->?

for( auto itr = o.feeds.begin(); itr != o.feeds.end();)
{
auto feed_time = itr[0].second.first;
if( feed_time + (o.options.feed_lifetime_sec) < head_block_time() && o.feeds.size() > o.options.minimum_feeds)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@oxarbitrage please remove && o.feeds.size() > o.options.minimum_feeds check. It's not relevant here.

No need to cycle through the rest when the minimum has been reached.

@pmconrad what did this mean?

modify( d, [this](asset_bitasset_data_object& o) {
o.force_settled_volume = 0;
// Remove all expired feeds
for( auto itr = o.feeds.begin(); itr != o.feeds.end();)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use a reverse iterator for better performance while erasing multiple entries.

@oxarbitrage
Copy link
Member Author

In the last commit(101a02c) the bsip code is changed to only affect smartcoins.

A few considerations:

  • A new index tag by_bitasset_id was added to the asset asset_index. This can be of use in other parts of code, specifically if you have the bitasset_id and want to get what asset it belongs to.
  • In db_maint.cpp: const auto& idx = get_index_type<asset_index>().indices().get<by_bitasset_id>(); is done inside lambda and by that inside a for loop. It will be more efficient to add this line before the loop but that will not compile as the indexes are protected. I am not sure how bad in performance is doing it like it is now. Suggestions are appreciated.
  • We can't exit the feeds loop before all feeds are checked, this is because the feeds are not sorted by date but by witness account id. this means, expired feed can be at any position of the map. Can be seen in bsip36_additional test case.
  • @abitmore please explain some more about how reverse_itr will increase performance in this scenario as i was not able to get it.
  • TODO: Add test for asset fed by committee.

thanks.

@pmconrad
Copy link
Contributor

A new index tag by_bitasset_id was added to the asset asset_index.

Instead of this, you can find the original asset_id from o.feeds.begin()->settlement_price.base.asset_id . (If o.feeds is empty there's nothing to do anyway.)

@oxarbitrage
Copy link
Member Author

Instead of this, you can find the original asset_id from o.feeds.begin()->settlement_price.base.asset_id . (If o.feeds is empty there's nothing to do anyway.)

thanks, i had this idea in a telegram chat and then i forgot about it. implemented now in 63199d0

@abitmore
Copy link
Member

please explain some more about how reverse_itr will increase performance in this scenario as i was not able to get it.

@oxarbitrage you're removing elements from a flat_map, every removal will cost linear time depends on how many elements are after it since need to move them all. By using a reverse iterator, less elements will need to be moved when there are more than 1 element to be removed.

o.force_settled_volume = 0;

// Check if asset is smartcoin
const auto& settlement_price = o.feeds.begin()->second.second.settlement_price;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not safe. Before dereferencing begin(), please check whether feeds is empty.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

silly me, fixed here f60a953 thank you.

@@ -925,7 +925,29 @@ void database::perform_chain_maintenance(const signed_block& next_block, const g
// Reset all BitAsset force settlement volumes to zero
for( const auto& d : get_index_type<asset_bitasset_data_index>().indices() )
{
modify( d, [](asset_bitasset_data_object& o) { o.force_settled_volume = 0; });
if(head_block_time() < HARDFORK_CORE_518_TIME)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For better performance, please move this check out of the for loop.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.


// Check if asset is smartcoin
const auto& settlement_price = o.feeds.begin()->second.second.settlement_price;
if(!settlement_price.is_null()) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This check is incorrect. The first feed is null doesn't mean all feeds are null.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#832 (comment)
you think that is wrong ? makes sense to me.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

waiting for more comments from @pmconrad maybe.

if ((flags & witness_fed_asset) || (flags & committee_fed_asset)) { // if smartcoin
for (auto itr = o.feeds.begin(); itr != o.feeds.end();) { // loop feeds
auto feed_time = itr->second.first;
if (feed_time + (o.options.feed_lifetime_sec) < head_block_time())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For better performance, please calculate head_block_time()-feed_lifetime_sec out of the for loop (and be careful about Integer overflow or underflow) and only check feed_time while looping.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done this one, checked for overflow only as we are dealing with all unsigned ints so underflow will not be possible. overflow could occur with a feed_lifetime_sec is too big. hard to happen in practice as when we are here we are in a smartcoin but check was added anyways.

// Reset all BitAsset force settlement volumes to zero
for( const auto& d : get_index_type<asset_bitasset_data_index>().indices() )
{
modify( d, [](asset_bitasset_data_object& o) { o.force_settled_volume = 0; });
if(!hf518)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer this structure:

if ( hf )
 for
else
 for

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changed to it.

}
});
if (d.has_settlement())
process_bids(d);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, this is a bit ugly (duplicate code). But I guess it's better to live with it at this moment.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd rather avoid the duplicate code. Why not pull the HF check into the lambda?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@pmconrad it's a side-effect of one of my earlier requests, since hard fork time check is independent, for slightly better performance, moved it to out of for.

Copy link
Member

@abitmore abitmore left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually I think it's better to move the whole code to a new function since it's getting a bit too long.

}
else {
for( const auto& d : get_index_type<asset_bitasset_data_index>().indices() ) {
modify(d, [](asset_bitasset_data_object &o) { o.force_settled_volume = 0; });
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better leave the "Reset all BitAsset force settlement volumes to zero" comment here.

@oxarbitrage
Copy link
Member Author

moved code to new function.

@oxarbitrage
Copy link
Member Author

as discussed in telegram i now added asset_id to the bitasset object in this commit: 09b7ffb

will need replay of course as this is added on object creation.

Copy link
Member

@abitmore abitmore left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. Looks good to me, except the coding style (most code elsewhere has { in a new line). Approving anyway.

@pmconrad what's your opinion?

}
});
if (d.has_settlement())
process_bids(d);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd rather avoid the duplicate code. Why not pull the HF check into the lambda?

if ((flags & witness_fed_asset) || (flags & committee_fed_asset)) // if smartcoin
{
// check overflow
if (std::numeric_limits<uint32_t>::max() - o.options.feed_lifetime_sec > head_block_time().sec_since_epoch())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is wrong. Two lines below you calculate (head_block_time - feed_lifetime), so what you want to avoid is feed_lifetime > head_block_time.
Also, please move this check into the if two lines above.

(Good thinking to put in the check though.)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@pmconrad i think you are right and making sure feed_lifetime < head is enough. changed here 960bfe9 and moved check to the previous if. thanks.

// bitshares-core issue #518 Clean up bitasset_data during maintenance
#ifndef HARDFORK_CORE_518_TIME
#define HARDFORK_CORE_518_TIME (fc::time_point_sec( 1600000000 ))
#endif
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a newline.

{
try
{
/* Bug affects only smartcoions(market pegged assets feeded by active witnesses or committee members).
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not a bug, it's an issue. :-)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BTW smartcoions is typo.

@@ -778,20 +778,19 @@ void database::process_bitassets()

const auto &asset = get(o.asset_id);
auto flags = asset.options.flags;
if ((flags & witness_fed_asset) || (flags & committee_fed_asset)) // if smartcoin
if ( (flags & witness_fed_asset || flags & committee_fed_asset) &&
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To make sure & will be done before ||, it's not harm to leave the ()s.
BTW as @jmjatlanta mentioned, perhaps better to use ( flag & ( witness_fed_asset | committee_fed_asset ) )

@abitmore
Copy link
Member

abitmore commented May 3, 2018

Please resolve conflicts.

@abitmore abitmore changed the base branch from hardfork to bsip36 May 7, 2018 08:49
@abitmore abitmore merged commit 960bfe9 into bitshares:bsip36 May 7, 2018
@abitmore
Copy link
Member

abitmore commented May 7, 2018

Resolved conflicts and merged into a new bsip36 branch. Will develop in that branch.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants