peerplays_migrated/libraries/chain/db_update.cpp

413 lines
18 KiB
C++
Raw Normal View History

2015-06-08 15:50:35 +00:00
/*
2015-10-12 17:48:40 +00:00
* Copyright (c) 2015 Cryptonomex, Inc., and contributors.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
*
* 1. Any modified source or binaries are used only with the BitShares network.
*
* 2. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
*
* 3. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
2015-06-08 15:50:35 +00:00
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
2015-10-12 17:02:59 +00:00
*
2015-06-08 15:50:35 +00:00
*/
#include <graphene/chain/database.hpp>
#include <graphene/chain/db_with.hpp>
2015-06-08 15:50:35 +00:00
#include <graphene/chain/asset_object.hpp>
#include <graphene/chain/global_property_object.hpp>
#include <graphene/chain/proposal_object.hpp>
#include <graphene/chain/transaction_object.hpp>
#include <graphene/chain/market_evaluator.hpp>
2015-06-08 15:50:35 +00:00
#include <graphene/chain/withdraw_permission_object.hpp>
#include <graphene/chain/witness_object.hpp>
#include <graphene/chain/protocol/fee_schedule.hpp>
2015-06-08 15:50:35 +00:00
#include <fc/uint128.hpp>
namespace graphene { namespace chain {
void database::update_global_dynamic_data( const signed_block& b )
{
const dynamic_global_property_object& _dgp =
dynamic_global_property_id_type(0)(*this);
uint32_t missed_blocks = get_slot_at_time( b.timestamp );
assert( missed_blocks != 0 );
missed_blocks--;
2015-09-18 17:42:12 +00:00
for( uint32_t i = 0; i < missed_blocks; ++i ) {
const auto& witness_missed = get_scheduled_witness( i+1 )(*this);
if( witness_missed.id != b.witness ) {
/*
2015-09-28 14:16:48 +00:00
const auto& witness_account = witness_missed.witness_account(*this);
2015-09-18 17:42:12 +00:00
if( (fc::time_point::now() - b.timestamp) < fc::seconds(30) )
wlog( "Witness ${name} missed block ${n} around ${t}", ("name",witness_account.name)("n",b.block_num())("t",b.timestamp) );
*/
modify( witness_missed, [&]( witness_object& w ) {
w.total_missed++;
});
}
}
2015-06-08 15:50:35 +00:00
// dynamic global properties updating
modify( _dgp, [&]( dynamic_global_property_object& dgp ){
if( BOOST_UNLIKELY( b.block_num() == 1 ) )
dgp.recently_missed_count = 0;
else if( _checkpoints.size() && _checkpoints.rbegin()->first >= b.block_num() )
dgp.recently_missed_count = 0;
else if( missed_blocks )
dgp.recently_missed_count += GRAPHENE_RECENTLY_MISSED_COUNT_INCREMENT*missed_blocks;
else if( dgp.recently_missed_count > GRAPHENE_RECENTLY_MISSED_COUNT_INCREMENT )
dgp.recently_missed_count -= GRAPHENE_RECENTLY_MISSED_COUNT_DECREMENT;
else if( dgp.recently_missed_count > 0 )
dgp.recently_missed_count--;
2015-06-08 15:50:35 +00:00
dgp.head_block_number = b.block_num();
dgp.head_block_id = b.id();
dgp.time = b.timestamp;
dgp.current_witness = b.witness;
dgp.recent_slots_filled = (
(dgp.recent_slots_filled << 1)
+ 1) << missed_blocks;
dgp.current_aslot += missed_blocks+1;
2015-06-08 15:50:35 +00:00
});
if( !(get_node_properties().skip_flags & skip_undo_history_check) )
{
GRAPHENE_ASSERT( _dgp.head_block_number - _dgp.last_irreversible_block_num < GRAPHENE_MAX_UNDO_HISTORY, undo_database_exception,
"The database does not have enough undo history to support a blockchain with so many missed blocks. "
"Please add a checkpoint if you would like to continue applying blocks beyond this point.",
("last_irreversible_block_num",_dgp.last_irreversible_block_num)("head", _dgp.head_block_number)
("recently_missed",_dgp.recently_missed_count)("max_undo",GRAPHENE_MAX_UNDO_HISTORY) );
}
_undo_db.set_max_size( _dgp.head_block_number - _dgp.last_irreversible_block_num + 1 );
_fork_db.set_max_size( _dgp.head_block_number - _dgp.last_irreversible_block_num + 1 );
2015-06-08 15:50:35 +00:00
}
void database::update_signing_witness(const witness_object& signing_witness, const signed_block& new_block)
{
const global_property_object& gpo = get_global_properties();
const dynamic_global_property_object& dpo = get_dynamic_global_properties();
uint64_t new_block_aslot = dpo.current_aslot + get_slot_at_time( new_block.timestamp );
2015-06-08 15:50:35 +00:00
share_type witness_pay = std::min( gpo.parameters.witness_pay_per_block, dpo.witness_budget );
modify( dpo, [&]( dynamic_global_property_object& _dpo )
{
_dpo.witness_budget -= witness_pay;
} );
deposit_witness_pay( signing_witness, witness_pay );
2015-06-08 15:50:35 +00:00
modify( signing_witness, [&]( witness_object& _wit )
{
_wit.last_aslot = new_block_aslot;
2015-10-02 21:19:03 +00:00
_wit.last_confirmed_block_num = new_block.block_num();
2015-06-08 15:50:35 +00:00
} );
}
2015-10-02 21:19:03 +00:00
void database::update_last_irreversible_block()
{
const global_property_object& gpo = get_global_properties();
const dynamic_global_property_object& dpo = get_dynamic_global_properties();
vector< const witness_object* > wit_objs;
wit_objs.reserve( gpo.active_witnesses.size() );
for( const witness_id_type& wid : gpo.active_witnesses )
wit_objs.push_back( &(wid(*this)) );
static_assert( GRAPHENE_IRREVERSIBLE_THRESHOLD > 0, "irreversible threshold must be nonzero" );
// 1 1 1 2 2 2 2 2 2 2 -> 2 .7*10 = 7
// 1 1 1 1 1 1 1 2 2 2 -> 1
// 3 3 3 3 3 3 3 3 3 3 -> 3
size_t offset = ((GRAPHENE_100_PERCENT - GRAPHENE_IRREVERSIBLE_THRESHOLD) * wit_objs.size() / GRAPHENE_100_PERCENT);
std::nth_element( wit_objs.begin(), wit_objs.begin() + offset, wit_objs.end(),
[]( const witness_object* a, const witness_object* b )
{
return a->last_confirmed_block_num < b->last_confirmed_block_num;
} );
uint32_t new_last_irreversible_block_num = wit_objs[offset]->last_confirmed_block_num;
if( new_last_irreversible_block_num > dpo.last_irreversible_block_num )
{
modify( dpo, [&]( dynamic_global_property_object& _dpo )
{
_dpo.last_irreversible_block_num = new_last_irreversible_block_num;
} );
}
}
2015-06-08 15:50:35 +00:00
void database::clear_expired_transactions()
{
//Look for expired transactions in the deduplication list, and remove them.
//Transactions must have expired by at least two forking windows in order to be removed.
auto& transaction_idx = static_cast<transaction_index&>(get_mutable_index(implementation_ids, impl_transaction_object_type));
const auto& dedupe_index = transaction_idx.indices().get<by_expiration>();
while( (!dedupe_index.empty()) && (head_block_time() > dedupe_index.rbegin()->trx.expiration) )
2015-06-08 15:50:35 +00:00
transaction_idx.remove(*dedupe_index.rbegin());
}
void database::clear_expired_proposals()
{
const auto& proposal_expiration_index = get_index_type<proposal_index>().indices().get<by_expiration>();
while( !proposal_expiration_index.empty() && proposal_expiration_index.begin()->expiration_time <= head_block_time() )
{
const proposal_object& proposal = *proposal_expiration_index.begin();
processed_transaction result;
try {
if( proposal.is_authorized_to_execute(*this) )
2015-06-08 15:50:35 +00:00
{
result = push_proposal(proposal);
//TODO: Do something with result so plugins can process it.
continue;
}
} catch( const fc::exception& e ) {
elog("Failed to apply proposed transaction on its expiration. Deleting it.\n${proposal}\n${error}",
("proposal", proposal)("error", e.to_detail_string()));
}
remove(proposal);
}
}
/**
* let HB = the highest bid for the collateral (aka who will pay the most DEBT for the least collateral)
* let SP = current median feed's Settlement Price
* let LC = the least collateralized call order's swan price (debt/collateral)
*
* If there is no valid price feed or no bids then there is no black swan.
*
* A black swan occurs if MAX(HB,SP) <= LC
*/
bool database::check_for_blackswan( const asset_object& mia, bool enable_black_swan )
{
if( !mia.is_market_issued() ) return false;
const asset_bitasset_data_object& bitasset = mia.bitasset_data(*this);
if( bitasset.has_settlement() ) return true; // already force settled
auto settle_price = bitasset.current_feed.settlement_price;
if( settle_price.is_null() ) return false; // no feed
const call_order_index& call_index = get_index_type<call_order_index>();
const auto& call_price_index = call_index.indices().get<by_price>();
const limit_order_index& limit_index = get_index_type<limit_order_index>();
const auto& limit_price_index = limit_index.indices().get<by_price>();
// looking for limit orders selling the most USD for the least CORE
auto highest_possible_bid = price::max( mia.id, bitasset.options.short_backing_asset );
// stop when limit orders are selling too little USD for too much CORE
auto lowest_possible_bid = price::min( mia.id, bitasset.options.short_backing_asset );
assert( highest_possible_bid.base.asset_id == lowest_possible_bid.base.asset_id );
// NOTE limit_price_index is sorted from greatest to least
auto limit_itr = limit_price_index.lower_bound( highest_possible_bid );
auto limit_end = limit_price_index.upper_bound( lowest_possible_bid );
auto call_min = price::min( bitasset.options.short_backing_asset, mia.id );
auto call_max = price::max( bitasset.options.short_backing_asset, mia.id );
auto call_itr = call_price_index.lower_bound( call_min );
auto call_end = call_price_index.upper_bound( call_max );
if( call_itr == call_end ) return false; // no call orders
price highest = settle_price;
if( limit_itr != limit_end ) {
assert( settle_price.base.asset_id == limit_itr->sell_price.base.asset_id );
highest = std::max( limit_itr->sell_price, settle_price );
}
auto least_collateral = call_itr->collateralization();
if( ~least_collateral >= highest )
{
elog( "Black Swan detected: \n"
" Least collateralized call: ${lc} ${~lc}\n"
// " Highest Bid: ${hb} ${~hb}\n"
" Settle Price: ${sp} ${~sp}\n"
" Max: ${h} ${~h}\n",
("lc",least_collateral.to_real())("~lc",(~least_collateral).to_real())
// ("hb",limit_itr->sell_price.to_real())("~hb",(~limit_itr->sell_price).to_real())
("sp",settle_price.to_real())("~sp",(~settle_price).to_real())
("h",highest.to_real())("~h",(~highest).to_real()) );
FC_ASSERT( enable_black_swan, "Black swan was detected during a margin update which is not allowed to trigger a blackswan" );
globally_settle_asset(mia, ~least_collateral );
return true;
}
return false;
}
2015-06-08 15:50:35 +00:00
void database::clear_expired_orders()
{
detail::with_skip_flags( *this,
2015-06-18 19:31:30 +00:00
get_node_properties().skip_flags | skip_authority_check, [&](){
transaction_evaluation_state cancel_context(this);
2015-06-08 15:50:35 +00:00
2015-06-18 19:31:30 +00:00
//Cancel expired limit orders
auto& limit_index = get_index_type<limit_order_index>().indices().get<by_expiration>();
while( !limit_index.empty() && limit_index.begin()->expiration <= head_block_time() )
{
limit_order_cancel_operation canceler;
const limit_order_object& order = *limit_index.begin();
canceler.fee_paying_account = order.seller;
canceler.order = order.id;
apply_operation(cancel_context, canceler);
}
});
2015-06-08 15:50:35 +00:00
//Process expired force settlement orders
auto& settlement_index = get_index_type<force_settlement_index>().indices().get<by_expiration>();
if( !settlement_index.empty() )
{
asset_id_type current_asset = settlement_index.begin()->settlement_asset_id();
asset max_settlement_volume;
auto next_asset = [&current_asset, &settlement_index] {
auto bound = settlement_index.upper_bound(current_asset);
if( bound == settlement_index.end() )
return false;
current_asset = bound->settlement_asset_id();
return true;
};
// At each iteration, we either consume the current order and remove it, or we move to the next asset
for( auto itr = settlement_index.lower_bound(current_asset);
itr != settlement_index.end();
itr = settlement_index.lower_bound(current_asset) )
{
const force_settlement_object& order = *itr;
auto order_id = order.id;
current_asset = order.settlement_asset_id();
const asset_object& mia_object = get(current_asset);
const asset_bitasset_data_object mia = mia_object.bitasset_data(*this);
if( mia.has_settlement() )
{
ilog( "Canceling a force settlement because of black swan" );
cancel_order( order );
continue;
}
2015-06-08 15:50:35 +00:00
// Has this order not reached its settlement date?
if( order.settlement_date > head_block_time() )
{
if( next_asset() )
continue;
break;
}
// Can we still settle in this asset?
if( mia.current_feed.settlement_price.is_null() )
{
ilog("Canceling a force settlement in ${asset} because settlement price is null",
("asset", mia_object.symbol));
cancel_order(order);
continue;
}
if( max_settlement_volume.asset_id != current_asset )
max_settlement_volume = mia_object.amount(mia.max_force_settlement_volume(mia_object.dynamic_data(*this).current_supply));
if( mia.force_settled_volume >= max_settlement_volume.amount )
{
/*
2015-06-08 15:50:35 +00:00
ilog("Skipping force settlement in ${asset}; settled ${settled_volume} / ${max_volume}",
("asset", mia_object.symbol)("settlement_price_null",mia.current_feed.settlement_price.is_null())
("settled_volume", mia.force_settled_volume)("max_volume", max_settlement_volume));
*/
2015-06-08 15:50:35 +00:00
if( next_asset() )
continue;
break;
}
auto& pays = order.balance;
auto receives = (order.balance * mia.current_feed.settlement_price);
receives.amount = (fc::uint128_t(receives.amount.value) *
(GRAPHENE_100_PERCENT - mia.options.force_settlement_offset_percent) / GRAPHENE_100_PERCENT).to_uint64();
assert(receives <= order.balance * mia.current_feed.settlement_price);
price settlement_price = pays / receives;
auto& call_index = get_index_type<call_order_index>().indices().get<by_collateral>();
asset settled = mia_object.amount(mia.force_settled_volume);
// Match against the least collateralized short until the settlement is finished or we reach max settlements
while( settled < max_settlement_volume && find_object(order_id) )
{
auto itr = call_index.lower_bound(boost::make_tuple(price::min(mia_object.bitasset_data(*this).options.short_backing_asset,
mia_object.get_id())));
// There should always be a call order, since asset exists!
assert(itr != call_index.end() && itr->debt_type() == mia_object.get_id());
asset max_settlement = max_settlement_volume - settled;
try {
settled += match(*itr, order, settlement_price, max_settlement);
}
catch ( const black_swan_exception& e ) {
wlog( "black swan detected: ${e}", ("e", e.to_detail_string() ) );
cancel_order( order );
break;
}
2015-06-08 15:50:35 +00:00
}
modify(mia, [settled](asset_bitasset_data_object& b) {
b.force_settled_volume = settled.amount;
});
}
}
}
void database::update_expired_feeds()
{
auto& asset_idx = 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;
assert( a.is_market_issued() );
const asset_bitasset_data_object& b = a.bitasset_data(*this);
if( b.feed_is_expired(head_block_time()) )
2015-06-25 18:33:46 +00:00
{
modify(b, [this](asset_bitasset_data_object& a) {
2015-06-08 15:50:35 +00:00
a.update_median_feeds(head_block_time());
});
check_call_orders(b.current_feed.settlement_price.base.asset_id(*this));
}
if( !b.current_feed.core_exchange_rate.is_null() &&
a.options.core_exchange_rate != b.current_feed.core_exchange_rate )
modify(a, [&b](asset_object& a) {
a.options.core_exchange_rate = b.current_feed.core_exchange_rate;
});
}
2015-06-08 15:50:35 +00:00
}
void database::update_maintenance_flag( bool new_maintenance_flag )
{
modify( get_dynamic_global_properties(), [&]( dynamic_global_property_object& dpo )
{
auto maintenance_flag = dynamic_global_property_object::maintenance_flag;
dpo.dynamic_flags =
(dpo.dynamic_flags & ~maintenance_flag)
| (new_maintenance_flag ? maintenance_flag : 0);
} );
return;
}
2015-06-08 15:50:35 +00:00
void database::update_withdraw_permissions()
{
auto& permit_index = get_index_type<withdraw_permission_index>().indices().get<by_expiration>();
while( !permit_index.empty() && permit_index.begin()->expiration <= head_block_time() )
remove(*permit_index.begin());
}
} }