market_ticker market_history_api_impl::get_ticker() const { market_ticker result; auto db = app.chain_database(); const auto& bucket_idx = db->get_index_type< bucket_index >().indices().get< by_bucket >(); auto itr = bucket_idx.lower_bound( boost::make_tuple( 86400, db->head_block_time() - 86400 ) ); if( itr != bucket_idx.end() ) { auto open = ( asset( itr->open_sbd, SBD_SYMBOL ) / asset( itr->open_steem, STEEM_SYMBOL ) ).to_real(); result.latest = ( asset( itr->close_sbd, SBD_SYMBOL ) / asset( itr->close_steem, STEEM_SYMBOL ) ).to_real(); result.percent_change = ( ( result.latest - open ) / open ) * 100; } else { result.latest = 0; result.percent_change = 0; } auto orders = get_order_book( 1 ); if( orders.bids.size() ) result.highest_bid = orders.bids[0].price; if( orders.asks.size() ) result.lowest_ask = orders.asks[0].price; auto volume = get_volume(); result.steem_volume = volume.steem_volume; result.sbd_volume = volume.sbd_volume; return result; }
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) ) transaction_idx.remove(*dedupe_index.rbegin()); }
void database::pay_workers( share_type& budget ) { // ilog("Processing payroll! Available budget is ${b}", ("b", budget)); vector<std::reference_wrapper<const worker_object>> active_workers; get_index_type<worker_index>().inspect_all_objects([this, &active_workers](const object& o) { const worker_object& w = static_cast<const worker_object&>(o); auto now = head_block_time(); if( w.is_active(now) && w.approving_stake(_vote_tally_buffer) > 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(), [this](const worker_object& wa, const worker_object& wb) { share_type wa_vote = wa.approving_stake(_vote_tally_buffer); share_type wb_vote = wb.approving_stake(_vote_tally_buffer); if( wa_vote != wb_vote ) return wa_vote > wb_vote; return wa.id < wb.id; }); 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; if( head_block_time() - get_dynamic_global_properties().last_budget_time != fc::days(1) ) { fc::uint128 pay(requested_pay.value); pay *= (head_block_time() - get_dynamic_global_properties().last_budget_time).count(); pay /= fc::days(1).count(); requested_pay = pay.to_uint64(); } 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; } }
const witness_object& database::validate_block_header( uint32_t skip, const signed_block& next_block )const { FC_ASSERT( head_block_id() == next_block.previous, "", ("head_block_id",head_block_id())("next.prev",next_block.previous) ); FC_ASSERT( head_block_time() < next_block.timestamp, "", ("head_block_time",head_block_time())("next",next_block.timestamp)("blocknum",next_block.block_num()) ); const witness_object& witness = next_block.witness(*this); if( !(skip&skip_witness_signature) ) FC_ASSERT( next_block.validate_signee( witness.signing_key ) ); if( !(skip&skip_witness_schedule_check) ) { uint32_t slot_num = get_slot_at_time( next_block.timestamp ); FC_ASSERT( slot_num > 0 ); witness_id_type scheduled_witness = get_scheduled_witness( slot_num ); FC_ASSERT( next_block.witness == scheduled_witness, "Witness produced block at wrong time", ("block witness",next_block.witness)("scheduled",scheduled_witness)("slot_num",slot_num) ); } return witness; }
void database::update_expired_feeds() { auto& asset_idx = get_index_type<asset_index>().indices(); for( const asset_object& a : asset_idx ) { if( !a.is_market_issued() ) continue; const asset_bitasset_data_object& b = a.bitasset_data(*this); if( b.feed_is_expired(head_block_time()) ) { modify(b, [this](asset_bitasset_data_object& a) { 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; }); } }
bool database::apply_order(const limit_order_object& new_order_object, bool allow_black_swan) { auto order_id = new_order_object.id; const asset_object& sell_asset = get(new_order_object.amount_for_sale().asset_id); const asset_object& receive_asset = get(new_order_object.amount_to_receive().asset_id); // Possible optimization: We only need to check calls if both are true: // - The new order is at the front of the book // - The new order is below the call limit price bool called_some = check_call_orders(sell_asset, allow_black_swan); called_some |= check_call_orders(receive_asset, allow_black_swan); if( called_some && !find_object(order_id) ) // then we were filled by call order return true; const auto& limit_price_idx = get_index_type<limit_order_index>().indices().get<by_price>(); // TODO: it should be possible to simply check the NEXT/PREV iterator after new_order_object to // determine whether or not this order has "changed the book" in a way that requires us to // check orders. For now I just lookup the lower bound and check for equality... this is log(n) vs // constant time check. Potential optimization. auto max_price = ~new_order_object.sell_price; auto limit_itr = limit_price_idx.lower_bound(max_price.max()); auto limit_end = limit_price_idx.upper_bound(max_price); bool finished = false; while( !finished && limit_itr != limit_end ) { auto old_limit_itr = limit_itr; ++limit_itr; // match returns 2 when only the old order was fully filled. In this case, we keep matching; otherwise, we stop. finished = (match(new_order_object, *old_limit_itr, old_limit_itr->sell_price) != 2); } //Possible optimization: only check calls if the new order completely filled some old order //Do I need to check both assets? check_call_orders(sell_asset, allow_black_swan); check_call_orders(receive_asset, allow_black_swan); const limit_order_object* updated_order_object = find< limit_order_object >( order_id ); if( updated_order_object == nullptr ) return true; if( head_block_time() <= HARDFORK_555_TIME ) return false; // before #555 we would have done maybe_cull_small_order() logic as a result of fill_order() being called by match() above // however after #555 we need to get rid of small orders -- #555 hardfork defers logic that was done too eagerly before, and // this is the point it's deferred to. return maybe_cull_small_order( *this, *updated_order_object ); }
bool database::fill_order( const limit_order_object& order, const asset& pays, const asset& receives, bool cull_if_small ) { try { cull_if_small |= (head_block_time() < HARDFORK_555_TIME); FC_ASSERT( order.amount_for_sale().asset_id == pays.asset_id ); FC_ASSERT( pays.asset_id != receives.asset_id ); const account_object& seller = order.seller(*this); const asset_object& recv_asset = receives.asset_id(*this); auto issuer_fees = pay_market_fees( recv_asset, receives ); pay_order( seller, receives - issuer_fees, pays ); assert( pays.asset_id != receives.asset_id ); push_applied_operation( fill_order_operation( order.id, order.seller, pays, receives, issuer_fees ) ); // conditional because cheap integer comparison may allow us to avoid two expensive modify() and object lookups if( order.deferred_fee > 0 ) { modify( seller.statistics(*this), [&]( account_statistics_object& statistics ) { statistics.pay_fee( order.deferred_fee, get_global_properties().parameters.cashback_vesting_threshold ); } ); } if( pays == order.amount_for_sale() ) { remove( order ); return true; } else { modify( order, [&]( limit_order_object& b ) { b.for_sale -= pays.amount; b.deferred_fee = 0; }); if( cull_if_small ) return maybe_cull_small_order( *this, order ); return false; } } FC_CAPTURE_AND_RETHROW( (order)(pays)(receives) ) }
market_volume market_history_api_impl::get_volume() const { auto db = app.chain_database(); const auto& bucket_idx = db->get_index_type< bucket_index >().indices().get< by_bucket >(); auto itr = bucket_idx.lower_bound( boost::make_tuple( 0, db->head_block_time() - 86400 ) ); uint32_t bucket_size; market_volume result; if( itr != bucket_idx.end() ) bucket_size = itr->seconds; while( itr != bucket_idx.end() && itr->seconds == bucket_size ) { result.steem_volume.amount += itr->steem_volume; result.sbd_volume.amount += itr->sbd_volume; ++itr; } return result; }
processed_transaction database::push_proposal(const proposal_object& proposal) { try { transaction_evaluation_state eval_state(this); eval_state._is_proposed_trx = true; eval_state.operation_results.reserve(proposal.proposed_transaction.operations.size()); processed_transaction ptrx(proposal.proposed_transaction); eval_state._trx = &ptrx; size_t old_applied_ops_size = _applied_ops.size(); try { auto session = _undo_db.start_undo_session(true); for( auto& op : proposal.proposed_transaction.operations ) eval_state.operation_results.emplace_back(apply_operation(eval_state, op)); remove(proposal); session.merge(); } catch ( const fc::exception& e ) { if( head_block_time() <= HARDFORK_483_TIME ) { for( size_t i=old_applied_ops_size,n=_applied_ops.size(); i<n; i++ ) { ilog( "removing failed operation from applied_ops: ${op}", ("op", *(_applied_ops[i])) ); _applied_ops[i].reset(); } } else { _applied_ops.resize( old_applied_ops_size ); } elog( "e", ("e",e.to_detail_string() ) ); throw; } ptrx.operation_results = std::move(eval_state.operation_results); return ptrx; } FC_CAPTURE_AND_RETHROW( (proposal) ) }
processed_transaction database::_apply_transaction(const signed_transaction& trx) { try { uint32_t skip = get_node_properties().skip_flags; if( true || !(skip&skip_validate) ) /* issue #505 explains why this skip_flag is disabled */ trx.validate(); auto& trx_idx = get_mutable_index_type<transaction_index>(); const chain_id_type& chain_id = get_chain_id(); auto trx_id = trx.id(); FC_ASSERT( (skip & skip_transaction_dupe_check) || trx_idx.indices().get<by_trx_id>().find(trx_id) == trx_idx.indices().get<by_trx_id>().end() ); transaction_evaluation_state eval_state(this); const chain_parameters& chain_parameters = get_global_properties().parameters; eval_state._trx = &trx; if( !(skip & (skip_transaction_signatures | skip_authority_check) ) ) { auto get_active = [&]( account_id_type id ) { return &id(*this).active; }; auto get_owner = [&]( account_id_type id ) { return &id(*this).owner; }; trx.verify_authority( chain_id, get_active, get_owner, get_global_properties().parameters.max_authority_depth ); } //Skip all manner of expiration and TaPoS checking if we're on block 1; It's impossible that the transaction is //expired, and TaPoS makes no sense as no blocks exist. if( BOOST_LIKELY(head_block_num() > 0) ) { if( !(skip & skip_tapos_check) ) { const auto& tapos_block_summary = block_summary_id_type( trx.ref_block_num )(*this); //Verify TaPoS block summary has correct ID prefix, and that this block's time is not past the expiration FC_ASSERT( trx.ref_block_prefix == tapos_block_summary.block_id._hash[1] ); } fc::time_point_sec now = head_block_time(); FC_ASSERT( trx.expiration <= now + chain_parameters.maximum_time_until_expiration, "", ("trx.expiration",trx.expiration)("now",now)("max_til_exp",chain_parameters.maximum_time_until_expiration)); FC_ASSERT( now <= trx.expiration, "", ("now",now)("trx.exp",trx.expiration) ); } //Insert transaction into unique transactions database. if( !(skip & skip_transaction_dupe_check) ) { create<transaction_object>([&](transaction_object& transaction) { transaction.trx_id = trx_id; transaction.trx = trx; }); } eval_state.operation_results.reserve(trx.operations.size()); //Finally process the operations processed_transaction ptrx(trx); _current_op_in_trx = 0; for( const auto& op : ptrx.operations ) { eval_state.operation_results.emplace_back(apply_operation(eval_state, op)); ++_current_op_in_trx; } ptrx.operation_results = std::move(eval_state.operation_results); //Make sure the temp account has no non-zero balances const auto& index = get_index_type<account_balance_index>().indices().get<by_account_asset>(); auto range = index.equal_range( boost::make_tuple( GRAPHENE_TEMP_ACCOUNT ) ); std::for_each(range.first, range.second, [](const account_balance_object& b) { FC_ASSERT(b.balance == 0); }); return ptrx; } FC_CAPTURE_AND_RETHROW( (trx) ) }
/** * Starting with the least collateralized orders, fill them if their * call price is above the max(lowest bid,call_limit). * * This method will return true if it filled a short or limit * * @param mia - the market issued asset that should be called. * @param enable_black_swan - when adjusting collateral, triggering a black swan is invalid and will throw * if enable_black_swan is not set to true. * * @return true if a margin call was executed. */ bool database::check_call_orders(const asset_object& mia, bool enable_black_swan) { try { if( !mia.is_market_issued() ) return false; if( check_for_blackswan( mia, enable_black_swan ) ) return false; const asset_bitasset_data_object& bitasset = mia.bitasset_data(*this); if( bitasset.is_prediction_market ) return false; if( bitasset.current_feed.settlement_price.is_null() ) return false; 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 max_price = price::max( mia.id, bitasset.options.short_backing_asset ); // stop when limit orders are selling too little USD for too much CORE auto min_price = bitasset.current_feed.max_short_squeeze_price(); assert( max_price.base.asset_id == min_price.base.asset_id ); // NOTE limit_price_index is sorted from greatest to least auto limit_itr = limit_price_index.lower_bound( max_price ); auto limit_end = limit_price_index.upper_bound( min_price ); if( limit_itr == limit_end ) return false; 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 ); bool filled_limit = false; bool margin_called = false; while( !check_for_blackswan( mia, enable_black_swan ) && call_itr != call_end ) { bool filled_call = false; price match_price; asset usd_for_sale; if( limit_itr != limit_end ) { assert( limit_itr != limit_price_index.end() ); match_price = limit_itr->sell_price; usd_for_sale = limit_itr->amount_for_sale(); } else return margin_called; match_price.validate(); // would be margin called, but there is no matching order #436 bool feed_protected = ( bitasset.current_feed.settlement_price > ~call_itr->call_price ); if( feed_protected && (head_block_time() > HARDFORK_436_TIME) ) return margin_called; // would be margin called, but there is no matching order if( match_price > ~call_itr->call_price ) return margin_called; if( feed_protected ) { ilog( "Feed protected margin call executing (HARDFORK_436_TIME not here yet)" ); idump( (*call_itr) ); idump( (*limit_itr) ); } // idump((*call_itr)); // idump((*limit_itr)); // ilog( "match_price <= ~call_itr->call_price performing a margin call" ); margin_called = true; auto usd_to_buy = call_itr->get_debt(); if( usd_to_buy * match_price > call_itr->get_collateral() ) { elog( "black swan detected" ); edump((enable_black_swan)); FC_ASSERT( enable_black_swan ); globally_settle_asset(mia, bitasset.current_feed.settlement_price ); return true; } asset call_pays, call_receives, order_pays, order_receives; if( usd_to_buy >= usd_for_sale ) { // fill order call_receives = usd_for_sale; order_receives = usd_for_sale * match_price; call_pays = order_receives; order_pays = usd_for_sale; filled_limit = true; filled_call = (usd_to_buy == usd_for_sale); } else { // fill call call_receives = usd_to_buy; order_receives = usd_to_buy * match_price; call_pays = order_receives; order_pays = usd_to_buy; filled_call = true; } FC_ASSERT( filled_call || filled_limit ); auto old_call_itr = call_itr; if( filled_call ) ++call_itr; fill_order(*old_call_itr, call_pays, call_receives); auto old_limit_itr = filled_limit ? limit_itr++ : limit_itr; fill_order(*old_limit_itr, order_pays, order_receives, true); } // whlie call_itr != call_end return margin_called; } FC_CAPTURE_AND_RETHROW() }
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()); }
void database::clear_expired_orders() { detail::with_skip_flags( *this, get_node_properties().skip_flags | skip_authority_check, [&](){ transaction_evaluation_state cancel_context(this); //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); } }); //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 = [¤t_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); // 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 ) { /* 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)); */ 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; settled += match(*itr, order, settlement_price, max_settlement); } modify(mia, [settled](asset_bitasset_data_object& b) { b.force_settled_volume = settled.amount; }); } } }
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) ) { 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); } }