/** * This method dumps the state of the blockchain in a semi-human readable form for the * purpose of tracking down funds and mismatches in currency allocation */ void database::debug_dump() { const auto& db = *this; const asset_dynamic_data_object& core_asset_data = db.get_core_asset().dynamic_asset_data_id(db); const auto& balance_index = db.get_index_type<account_balance_index>().indices(); const simple_index<account_statistics_object>& statistics_index = db.get_index_type<simple_index<account_statistics_object>>(); map<asset_id_type,share_type> total_balances; map<asset_id_type,share_type> total_debts; share_type core_in_orders; share_type reported_core_in_orders; for( const account_balance_object& a : balance_index ) { // idump(("balance")(a)); total_balances[a.asset_type] += a.balance; } for( const account_statistics_object& s : statistics_index ) { // idump(("statistics")(s)); reported_core_in_orders += s.total_core_in_orders; } for( const limit_order_object& o : db.get_index_type<limit_order_index>().indices() ) { // idump(("limit_order")(o)); auto for_sale = o.amount_for_sale(); if( for_sale.asset_id == asset_id_type() ) core_in_orders += for_sale.amount; total_balances[for_sale.asset_id] += for_sale.amount; } for( const call_order_object& o : db.get_index_type<call_order_index>().indices() ) { // idump(("call_order")(o)); auto col = o.get_collateral(); if( col.asset_id == asset_id_type() ) core_in_orders += col.amount; total_balances[col.asset_id] += col.amount; total_debts[o.get_debt().asset_id] += o.get_debt().amount; } for( const asset_object& asset_obj : db.get_index_type<asset_index>().indices() ) { total_balances[asset_obj.id] += asset_obj.dynamic_asset_data_id(db).accumulated_fees; total_balances[asset_id_type()] += asset_obj.dynamic_asset_data_id(db).fee_pool; // edump((total_balances[asset_obj.id])(asset_obj.dynamic_asset_data_id(db).current_supply ) ); } if( total_balances[asset_id_type()].value != core_asset_data.current_supply.value ) { edump( (total_balances[asset_id_type()].value)(core_asset_data.current_supply.value )); } edump((core_in_orders)(reported_core_in_orders)); /* const auto& vbidx = db.get_index_type<simple_index<vesting_balance_object>>(); for( const auto& s : vbidx ) { // idump(("vesting_balance")(s)); } */ }
void cancel_current_short( market_transaction& mtrx, const asset_id_type& quote_asset_id ) { FC_ASSERT( _current_bid->type == short_order ); FC_ASSERT( mtrx.bid_type == short_order ); elog( "Canceling current short" ); edump( (mtrx) ); // Create automatic market cancel transaction mtrx.ask_paid = asset(); mtrx.ask_received = asset( 0, quote_asset_id ); mtrx.bid_received = _current_bid->get_balance(); mtrx.bid_paid = asset( 0, quote_asset_id ); mtrx.bid_collateral.reset(); // Fund refund balance record const balance_id_type id = withdraw_condition( withdraw_with_signature( mtrx.bid_owner ), 0 ).get_address(); obalance_record bid_payout = _pending_state->get_balance_record( id ); if( !bid_payout.valid() ) bid_payout = balance_record( mtrx.bid_owner, asset( 0, 0 ), 0 ); bid_payout->balance += mtrx.bid_received.amount; bid_payout->last_update = _pending_state->now(); bid_payout->deposit_date = _pending_state->now(); _pending_state->store_balance_record( *bid_payout ); // Remove short order _current_bid->state.balance = 0; _pending_state->store_short_record( _current_bid->market_index, _current_bid->state ); }
~witness_plugin() { try { if( _block_production_task.valid() ) _block_production_task.cancel_and_wait(__FUNCTION__); } catch(fc::canceled_exception&) { //Expected exception. Move along. } catch(fc::exception& e) { edump((e.to_detail_string())); } }
int main( int argc, char** argv, char** envp ) { try { //steemit::chain::database db; steemit::chain::block_log log; fc::temp_directory temp_dir( "." ); //db.open( temp_dir ); log.open( temp_dir.path() / "log" ); idump( (log.head() ) ); steemit::protocol::signed_block b1; b1.witness = "alice"; b1.previous = steemit::protocol::block_id_type(); log.append( b1 ); log.flush(); idump( (b1) ); idump( ( log.head() ) ); idump( (fc::raw::pack_size(b1)) ); steemit::protocol::signed_block b2; b2.witness = "bob"; b2.previous = b1.id(); log.append( b2 ); log.flush(); idump( (b2) ); idump( (log.head() ) ); idump( (fc::raw::pack_size(b2)) ); auto r1 = log.read_block( 0 ); idump( (r1) ); idump( (fc::raw::pack_size(r1.first)) ); auto r2 = log.read_block( r1.second ); idump( (r2) ); idump( (fc::raw::pack_size(r2.first)) ); idump( (log.read_head()) ); idump( (fc::raw::pack_size(log.read_head()))); auto r3 = log.read_block( r2.second ); idump( (r3) ); } catch ( const std::exception& e ) { edump( ( std::string( e.what() ) ) ); } return 0; }
std::vector<block_id_type> database::get_block_ids_on_fork(block_id_type head_of_fork) const { pair<fork_database::branch_type, fork_database::branch_type> branches = _fork_db.fetch_branch_from(head_block_id(), head_of_fork); if( !((branches.first.back()->previous_id() == branches.second.back()->previous_id())) ) { edump( (head_of_fork) (head_block_id()) (branches.first.size()) (branches.second.size()) ); assert(branches.first.back()->previous_id() == branches.second.back()->previous_id()); } std::vector<block_id_type> result; for (const item_ptr& fork_block : branches.second) result.emplace_back(fork_block->id); result.emplace_back(branches.first.back()->previous_id()); return result; }
read_only::get_actions_result read_only::get_actions( const read_only::get_actions_params& params )const { edump((params)); auto& chain = history->chain_plug->chain(); const auto& db = chain.db(); const auto& idx = db.get_index<account_history_index, by_account_action_seq>(); int32_t start = 0; int32_t pos = params.pos ? *params.pos : -1; int32_t end = 0; int32_t offset = params.offset ? *params.offset : -20; auto n = params.account_name; idump((pos)); if( pos == -1 ) { auto itr = idx.lower_bound( boost::make_tuple( name(n.value+1), 0 ) ); if( itr == idx.begin() ) { if( itr->account == n ) pos = itr->account_sequence_num+1; } else if( itr != idx.begin() ) --itr; if( itr->account == n ) pos = itr->account_sequence_num + 1; } if( pos== -1 ) pos = 0xfffffff; if( offset > 0 ) { start = pos; end = start + offset; } else { start = pos + offset; if( start > pos ) start = 0; end = pos; } FC_ASSERT( end >= start ); idump((start)(end)); auto start_itr = idx.lower_bound( boost::make_tuple( n, start ) ); auto end_itr = idx.lower_bound( boost::make_tuple( n, end+1) ); auto start_time = fc::time_point::now(); auto end_time = start_time; get_actions_result result; result.last_irreversible_block = chain.last_irreversible_block_num(); while( start_itr != end_itr ) { const auto& a = db.get<action_history_object, by_action_sequence_num>( start_itr->action_sequence_num ); fc::datastream<const char*> ds( a.packed_action_trace.data(), a.packed_action_trace.size() ); action_trace t; fc::raw::unpack( ds, t ); result.actions.emplace_back( ordered_action_result{ start_itr->action_sequence_num, start_itr->account_sequence_num, a.block_num, a.block_time, chain.to_variant_with_abi(t) }); end_time = fc::time_point::now(); if( end_time - start_time > fc::microseconds(100000) ) { result.time_limit_exceeded_error = true; break; } ++start_itr; } return result; }
/** * Returns a synopsis of the blockchain used for syncing. This consists of a list of * block hashes at intervals exponentially increasing towards the genesis block. * When syncing to a peer, the peer uses this data to determine if we're on the same * fork as they are, and if not, what blocks they need to send us to get us on their * fork. * * In the over-simplified case, this is a straighforward synopsis of our current * preferred blockchain; when we first connect up to a peer, this is what we will be sending. * It looks like this: * If the blockchain is empty, it will return the empty list. * If the blockchain has one block, it will return a list containing just that block. * If it contains more than one block: * the first element in the list will be the hash of the highest numbered block that * we cannot undo * the second element will be the hash of an item at the half way point in the undoable * segment of the blockchain * the third will be ~3/4 of the way through the undoable segment of the block chain * the fourth will be at ~7/8... * &c. * the last item in the list will be the hash of the most recent block on our preferred chain * so if the blockchain had 26 blocks labeled a - z, the synopsis would be: * a n u x z * the idea being that by sending a small (<30) number of block ids, we can summarize a huge * blockchain. The block ids are more dense near the end of the chain where because we are * more likely to be almost in sync when we first connect, and forks are likely to be short. * If the peer we're syncing with in our example is on a fork that started at block 'v', * then they will reply to our synopsis with a list of all blocks starting from block 'u', * the last block they know that we had in common. * * In the real code, there are several complications. * * First, as an optimization, we don't usually send a synopsis of the entire blockchain, we * send a synopsis of only the segment of the blockchain that we have undo data for. If their * fork doesn't build off of something in our undo history, we would be unable to switch, so there's * no reason to fetch the blocks. * * Second, when a peer replies to our initial synopsis and gives us a list of the blocks they think * we are missing, they only send a chunk of a few thousand blocks at once. After we get those * block ids, we need to request more blocks by sending another synopsis (we can't just say "send me * the next 2000 ids" because they may have switched forks themselves and they don't track what * they've sent us). For faster performance, we want to get a fairly long list of block ids first, * then start downloading the blocks. * The peer doesn't handle these follow-up block id requests any different from the initial request; * it treats the synopsis we send as our blockchain and bases its response entirely off that. So to * get the response we want (the next chunk of block ids following the last one they sent us, or, * failing that, the shortest fork off of the last list of block ids they sent), we need to construct * a synopsis as if our blockchain was made up of: * 1. the blocks in our block chain up to the fork point (if there is a fork) or the head block (if no fork) * 2. the blocks we've already pushed from their fork (if there's a fork) * 3. the block ids they've previously sent us * Segment 3 is handled in the p2p code, it just tells us the number of blocks it has (in * number_of_blocks_after_reference_point) so we can leave space in the synopsis for them. * We're responsible for constructing the synopsis of Segments 1 and 2 from our active blockchain and * fork database. The reference_point parameter is the last block from that peer that has been * successfully pushed to the blockchain, so that tells us whether the peer is on a fork or on * the main chain. */ virtual std::vector<item_hash_t> get_blockchain_synopsis(const item_hash_t& reference_point, uint32_t number_of_blocks_after_reference_point) override { try { std::vector<item_hash_t> synopsis; synopsis.reserve(30); uint32_t high_block_num; uint32_t non_fork_high_block_num; uint32_t low_block_num = _chain_db->last_non_undoable_block_num(); std::vector<block_id_type> fork_history; if (reference_point != item_hash_t()) { // the node is asking for a summary of the block chain up to a specified // block, which may or may not be on a fork // for now, assume it's not on a fork if (is_included_block(reference_point)) { // reference_point is a block we know about and is on the main chain uint32_t reference_point_block_num = block_header::num_from_id(reference_point); assert(reference_point_block_num > 0); high_block_num = reference_point_block_num; non_fork_high_block_num = high_block_num; if (reference_point_block_num < low_block_num) { // we're on the same fork (at least as far as reference_point) but we've passed // reference point and could no longer undo that far if we diverged after that // block. This should probably only happen due to a race condition where // the network thread calls this function, and then immediately pushes a bunch of blocks, // then the main thread finally processes this function. // with the current framework, there's not much we can do to tell the network // thread what our current head block is, so we'll just pretend that // our head is actually the reference point. // this *may* enable us to fetch blocks that we're unable to push, but that should // be a rare case (and correctly handled) low_block_num = reference_point_block_num; } } else { // block is a block we know about, but it is on a fork try { fork_history = _chain_db->get_block_ids_on_fork(reference_point); // returns a vector where the last element is the common ancestor with the preferred chain, // and the first element is the reference point you passed in assert(fork_history.size() >= 2); if( fork_history.front() != reference_point ) { edump( (fork_history)(reference_point) ); assert(fork_history.front() == reference_point); } block_id_type last_non_fork_block = fork_history.back(); fork_history.pop_back(); // remove the common ancestor boost::reverse(fork_history); if (last_non_fork_block == block_id_type()) // if the fork goes all the way back to genesis (does graphene's fork db allow this?) non_fork_high_block_num = 0; else non_fork_high_block_num = block_header::num_from_id(last_non_fork_block); high_block_num = non_fork_high_block_num + fork_history.size(); assert(high_block_num == block_header::num_from_id(fork_history.back())); } catch (const fc::exception& e) { // unable to get fork history for some reason. maybe not linked? // we can't return a synopsis of its chain elog("Unable to construct a blockchain synopsis for reference hash ${hash}: ${exception}", ("hash", reference_point)("exception", e)); throw; } if (non_fork_high_block_num < low_block_num) { wlog("Unable to generate a usable synopsis because the peer we're generating it for forked too long ago " "(our chains diverge after block #${non_fork_high_block_num} but only undoable to block #${low_block_num})", ("low_block_num", low_block_num) ("non_fork_high_block_num", non_fork_high_block_num)); FC_THROW_EXCEPTION(graphene::net::block_older_than_undo_history, "Peer is are on a fork I'm unable to switch to"); } } } else { // no reference point specified, summarize the whole block chain high_block_num = _chain_db->head_block_num(); non_fork_high_block_num = high_block_num; if (high_block_num == 0) return synopsis; // we have no blocks } if( low_block_num == 0) low_block_num = 1; // at this point: // low_block_num is the block before the first block we can undo, // non_fork_high_block_num is the block before the fork (if the peer is on a fork, or otherwise it is the same as high_block_num) // high_block_num is the block number of the reference block, or the end of the chain if no reference provided // true_high_block_num is the ending block number after the network code appends any item ids it // knows about that we don't uint32_t true_high_block_num = high_block_num + number_of_blocks_after_reference_point; do { // for each block in the synopsis, figure out where to pull the block id from. // if it's <= non_fork_high_block_num, we grab it from the main blockchain; // if it's not, we pull it from the fork history if (low_block_num <= non_fork_high_block_num) synopsis.push_back(_chain_db->get_block_id_for_num(low_block_num)); else synopsis.push_back(fork_history[low_block_num - non_fork_high_block_num - 1]); low_block_num += (true_high_block_num - low_block_num + 2) / 2; } while (low_block_num <= high_block_num); //idump((synopsis)); return synopsis; } FC_CAPTURE_AND_RETHROW() }
/** * 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 execute( asset_id_type quote_id, asset_id_type base_id, const fc::time_point_sec& timestamp ) { try { const uint32_t pending_block_num = _pending_state->get_head_block_num(); _quote_id = quote_id; _base_id = base_id; oasset_record quote_asset = _pending_state->get_asset_record( _quote_id ); oasset_record base_asset = _pending_state->get_asset_record( _base_id ); FC_ASSERT( quote_asset.valid() && base_asset.valid() ); // The order book is sorted from low to high price. So to get the last item (highest bid), // we need to go to the first item in the next market class and then back up one const price next_pair = (base_id+1 == quote_id) ? price( 0, quote_id+1, 0 ) : price( 0, quote_id, base_id+1 ); _bid_itr = _db_impl._bid_db.lower_bound( market_index_key( next_pair ) ); _ask_itr = _db_impl._ask_db.lower_bound( market_index_key( price( 0, quote_id, base_id) ) ); _short_itr = _db_impl._short_db.lower_bound( market_index_key( price( 0, quote_id, base_id) ) ); _collateral_itr = _db_impl._collateral_db.lower_bound( market_index_key( next_pair ) ); if( !_ask_itr.valid() ) { wlog( "ask iter invalid..." ); _ask_itr = _db_impl._ask_db.begin(); } if( _bid_itr.valid() ) --_bid_itr; else _bid_itr = _db_impl._bid_db.last(); if( _collateral_itr.valid() ) --_collateral_itr; else _collateral_itr = _db_impl._collateral_db.last(); asset trading_volume(0, base_id); // Set initial market status { omarket_status market_stat = _pending_state->get_market_status( _quote_id, _base_id ); if( !market_stat ) market_stat = market_status( quote_id, base_id, 0, 0 ); _market_stat = *market_stat; } price min_cover_ask; price opening_price; price closing_price; const oprice median_feed_price = _db_impl.self->get_median_delegate_price( quote_id, base_id ); if( quote_asset->is_market_issued() ) { // If bootstrapping market for the very first time if( _market_stat.center_price.ratio == fc::uint128_t() ) { if( median_feed_price.valid() ) _market_stat.center_price = *median_feed_price; else FC_CAPTURE_AND_THROW( insufficient_feeds, (quote_id) ); } min_cover_ask = _market_stat.minimum_ask(); } int last_orders_filled = -1; bool order_did_execute = false; // prime the pump, to make sure that margin calls (asks) have a bid to check against. get_next_bid(); get_next_ask(); idump( (_current_bid)(_current_ask) ); while( get_next_bid() && get_next_ask() ) { idump( (_current_bid)(_current_ask) ); // Make sure that at least one order was matched every time we enter the loop FC_ASSERT( _orders_filled != last_orders_filled, "We appear caught in an order matching loop" ); last_orders_filled = _orders_filled; const asset bid_quantity_xts = _current_bid->get_quantity(); const asset ask_quantity_xts = _current_ask->get_quantity(); asset current_bid_balance = _current_bid->get_balance(); // Initialize the market transaction market_transaction mtrx; mtrx.bid_owner = _current_bid->get_owner(); mtrx.ask_owner = _current_ask->get_owner(); // Always execute shorts at the center price mtrx.bid_price = (_current_bid->type != short_order) ? _current_bid->get_price() : _market_stat.center_price; mtrx.ask_price = _current_ask->get_price(); mtrx.bid_type = _current_bid->type; mtrx.ask_type = _current_ask->type; if( _current_ask->type == cover_order && _current_bid->type == short_order ) { FC_ASSERT( quote_asset->is_market_issued() ); /** don't allow new shorts to execute unless there is a feed, all other * trades are still valid. (we shouldn't stop the market) */ if( !median_feed_price.valid() ) { _current_bid.reset(); continue; } if( mtrx.ask_price < mtrx.bid_price ) // The call price has not been reached break; if( _current_bid->state.short_price_limit.valid() ) { if( *_current_bid->state.short_price_limit < mtrx.ask_price ) { _current_bid.reset(); continue; // skip shorts that are over the price limit. } mtrx.bid_price = *_current_bid->state.short_price_limit; } mtrx.ask_price = mtrx.bid_price; // Bound collateral ratio price collateral_rate = _current_bid->get_price(); if( collateral_rate > _market_stat.center_price ) collateral_rate = _market_stat.center_price; const asset ask_quantity_usd = _current_ask->get_quote_quantity(); const asset short_quantity_usd = _current_bid->get_balance() / collateral_rate; const asset trade_quantity_usd = std::min( short_quantity_usd, ask_quantity_usd ); mtrx.ask_received = trade_quantity_usd; mtrx.bid_paid = mtrx.ask_received; mtrx.ask_paid = mtrx.ask_received * mtrx.ask_price; mtrx.bid_received = mtrx.ask_paid; mtrx.bid_collateral = mtrx.bid_paid / collateral_rate; // Handle rounding errors if( (*mtrx.bid_collateral - _current_bid->get_balance()).amount < BTS_BLOCKCHAIN_PRECISION ) mtrx.bid_collateral = _current_bid->get_balance(); // If too little collateral at this price if( *mtrx.bid_collateral < mtrx.ask_paid ) { edump( (mtrx) ); _current_bid.reset(); continue; } pay_current_short( mtrx, *quote_asset ); pay_current_cover( mtrx, *quote_asset ); _market_stat.bid_depth -= mtrx.bid_collateral->amount; _market_stat.ask_depth += mtrx.bid_collateral->amount; order_did_execute = true; } else if( _current_ask->type == cover_order && _current_bid->type == bid_order ) { FC_ASSERT( quote_asset->is_market_issued() ); if( mtrx.ask_price < mtrx.bid_price ) // The call price has not been reached break; /** * Don't allow margin calls to be executed too far below * the minimum ask, this could lead to an attack where someone * walks the whole book to steal the collateral. */ if( mtrx.bid_price < _market_stat.minimum_ask() ) { _current_ask.reset(); continue; } mtrx.ask_price = mtrx.bid_price; const asset max_usd_purchase = asset( *_current_ask->collateral, _base_id ) * mtrx.bid_price; asset usd_exchanged = std::min( current_bid_balance, max_usd_purchase ); // Bound quote asset amount exchanged const asset required_usd_purchase = _current_ask->get_balance(); if( required_usd_purchase < usd_exchanged ) usd_exchanged = required_usd_purchase; mtrx.ask_received = usd_exchanged; mtrx.bid_paid = mtrx.ask_received; // Handle rounding errors if( usd_exchanged == max_usd_purchase ) mtrx.ask_paid = asset(*_current_ask->collateral,_base_id); else mtrx.ask_paid = usd_exchanged * mtrx.bid_price; mtrx.bid_received = mtrx.ask_paid; pay_current_bid( mtrx, *quote_asset ); pay_current_cover( mtrx, *quote_asset ); // TODO: Do we need to decrease bid depth as well? _market_stat.ask_depth -= mtrx.ask_paid.amount; order_did_execute = true; } else if( _current_ask->type == ask_order && _current_bid->type == short_order ) { FC_ASSERT( quote_asset->is_market_issued() ); /** don't allow new shorts to execute unless there is a feed, all other * trades are still valid. */ if( !median_feed_price.valid() ) { _current_bid.reset(); continue; } if( mtrx.bid_price < mtrx.ask_price ) // The ask price hasn't been reached break; if( _current_bid->state.short_price_limit.valid() ) { if( *_current_bid->state.short_price_limit < mtrx.ask_price ) { elog( "short price limit < bid price" ); _current_bid.reset(); continue; // skip shorts that are over the price limit. } mtrx.bid_price = *_current_bid->state.short_price_limit; } // Bound collateral ratio price collateral_rate = _current_bid->get_price(); if( collateral_rate > _market_stat.center_price ) collateral_rate = _market_stat.center_price; const asset ask_quantity_usd = _current_ask->get_quote_quantity(); const asset short_quantity_usd = _current_bid->get_balance() / collateral_rate; const asset trade_quantity_usd = std::min( short_quantity_usd, ask_quantity_usd ); mtrx.ask_received = trade_quantity_usd; mtrx.bid_paid = mtrx.ask_received; mtrx.ask_paid = mtrx.ask_received * mtrx.ask_price; mtrx.bid_received = mtrx.ask_paid; mtrx.bid_collateral = mtrx.bid_paid / collateral_rate; // Handle rounding errors if( (*mtrx.bid_collateral - _current_bid->get_balance()).amount < BTS_BLOCKCHAIN_PRECISION ) mtrx.bid_collateral = _current_bid->get_balance(); // If too little collateral at this price if( *mtrx.bid_collateral < mtrx.ask_paid ) { edump( (mtrx) ); _current_bid.reset(); continue; } pay_current_short( mtrx, *quote_asset ); pay_current_ask( mtrx, *quote_asset ); _market_stat.bid_depth -= mtrx.bid_collateral->amount; _market_stat.ask_depth += mtrx.bid_collateral->amount; order_did_execute = true; } else if( _current_ask->type == ask_order && _current_bid->type == bid_order ) { if( mtrx.bid_price < mtrx.ask_price ) // The ask price hasn't been reached break; const asset quantity_xts = std::min( bid_quantity_xts, ask_quantity_xts ); // Everyone gets the price they asked for mtrx.ask_received = quantity_xts * mtrx.ask_price; mtrx.bid_paid = quantity_xts * mtrx.bid_price; mtrx.ask_paid = quantity_xts; mtrx.bid_received = quantity_xts; // Handle rounding errors if( quantity_xts == bid_quantity_xts ) mtrx.bid_paid = current_bid_balance; mtrx.fees_collected = mtrx.bid_paid - mtrx.ask_received; pay_current_bid( mtrx, *quote_asset ); pay_current_ask( mtrx, *base_asset ); // TODO: Do we need to decrease bid depth as well? _market_stat.ask_depth -= mtrx.ask_paid.amount; order_did_execute = true; } push_market_transaction( mtrx ); if( mtrx.ask_received.asset_id == 0 ) trading_volume += mtrx.ask_received; else if( mtrx.bid_received.asset_id == 0 ) trading_volume += mtrx.bid_received; if( opening_price == price() ) opening_price = mtrx.bid_price; closing_price = mtrx.bid_price; if( mtrx.fees_collected.asset_id == base_asset->id ) base_asset->collected_fees += mtrx.fees_collected.amount; else if( mtrx.fees_collected.asset_id == quote_asset->id ) quote_asset->collected_fees += mtrx.fees_collected.amount; } // while( next bid && next ask ) // update any fees collected _pending_state->store_asset_record( *quote_asset ); _pending_state->store_asset_record( *base_asset ); _market_stat.last_error.reset(); // Force at least one center price update every 6 blocks order_did_execute |= (pending_block_num % 6) == 0; if( _current_bid && _current_ask && order_did_execute ) { if( median_feed_price.valid() ) { _market_stat.center_price = *median_feed_price; } else if( _current_bid->type != short_order ) // we cannot use short prices for this { _market_stat.center_price.ratio *= (BTS_BLOCKCHAIN_BLOCKS_PER_HOUR-1); const price max_bid = _market_stat.maximum_bid(); // limit the maximum movement rate of the price. if( _current_bid->get_price() < min_cover_ask ) _market_stat.center_price.ratio += min_cover_ask.ratio; else if( _current_bid->get_price() > max_bid ) _market_stat.center_price.ratio += max_bid.ratio; else _market_stat.center_price.ratio += _current_bid->get_price().ratio; if( _current_ask->get_price() < min_cover_ask ) _market_stat.center_price.ratio += min_cover_ask.ratio; else if( _current_ask->get_price() > max_bid ) _market_stat.center_price.ratio += max_bid.ratio; else _market_stat.center_price.ratio += _current_ask->get_price().ratio; _market_stat.center_price.ratio /= (BTS_BLOCKCHAIN_BLOCKS_PER_HOUR+1); } } // Update market status and market history _pending_state->store_market_status( _market_stat ); update_market_history( trading_volume, opening_price, closing_price, _market_stat, timestamp ); wlog( "done matching orders" ); idump( (_current_bid)(_current_ask) ); _pending_state->apply_changes(); } catch( const fc::exception& e ) { wlog( "error executing market ${quote} / ${base}\n ${e}", ("quote",quote_id)("base",base_id)("e",e.to_detail_string()) ); auto market_state = _prior_state->get_market_status( quote_id, base_id ); if( !market_state ) market_state = market_status( quote_id, base_id, 0, 0 ); market_state->last_error = e; _prior_state->store_market_status( *market_state ); } } // execute(...)
int main( int argc, char** argv ) { try { auto InB1 = fc::sha256::hash("InB1"); auto InB2 = fc::sha256::hash("InB2"); auto OutB1 = fc::sha256::hash("OutB1"); auto InC1 = fc::ecc::blind(InB1,25); auto InC2 = fc::ecc::blind(InB2,75); auto OutC1 = fc::ecc::blind(OutB1,40); auto OutB2 = fc::ecc::blind_sum( {InB1,InB2,OutB1}, 2 ); auto OutC2 = fc::ecc::blind( OutB2, 60 ); FC_ASSERT( fc::ecc::verify_sum( {InC1,InC2}, {OutC1,OutC2}, 0 ) ); auto nonce = fc::sha256::hash("nonce"); auto proof = fc::ecc::range_proof_sign( 0, OutC1, OutB1, nonce, 0, 0, 40 ); wdump( (proof.size())); auto result = fc::ecc::range_get_info( proof ); wdump((result)); FC_ASSERT( result.max_value >= 60 ); FC_ASSERT( result.min_value >= 0 ); auto B1 = fc::sha256::hash("B1"); auto B2 = fc::sha256::hash("B2"); auto b3 = fc::sha256::hash("b3"); auto B4 = fc::sha256::hash("B4"); auto C1 = fc::ecc::blind( B1, 1 ); auto C2 = fc::ecc::blind( B2, 2 ); auto c3 = fc::ecc::blind( b3, 3 ); auto C4 = fc::ecc::blind( B4, -1 ); auto B3 = fc::ecc::blind_sum( {B1,B2}, 2 ); auto C3 = fc::ecc::blind( B3, 3 ); auto B2m1 = fc::ecc::blind_sum( {B2,B1}, 1 ); auto C2m1 = fc::ecc::blind( B2m1, 1 ); FC_ASSERT( fc::ecc::verify_sum( {C1,C2}, {C3}, 0 ) ); FC_ASSERT( fc::ecc::verify_sum( {C3}, {C1,C2}, 0 ) ); FC_ASSERT( fc::ecc::verify_sum( {C3}, {C1,C2}, 0 ) ); { auto B1 = fc::sha256::hash("B1"); auto B2 = fc::sha256::hash("B2"); auto B3 = fc::sha256::hash("B3"); //secp256k1_scalar_get_b32((unsigned char*)&B1, (const secp256k1_scalar_t*)&B2); //B1 = fc::variant("b2e5da56ef9f2a34d3e22fd12634bc99261e95c87b9960bf94ed3d27b30").as<fc::sha256>(); auto C1 = fc::ecc::blind( B1, INT64_MAX ); auto C2 = fc::ecc::blind( B1, 0 ); auto C3 = fc::ecc::blind( B1, 1 ); FC_ASSERT( fc::ecc::verify_sum( {C2}, {C3}, -1 ) ); FC_ASSERT( fc::ecc::verify_sum( {C1}, {C1}, 0 ) ); FC_ASSERT( fc::ecc::verify_sum( {C2}, {C2}, 0 ) ); FC_ASSERT( fc::ecc::verify_sum( {C3}, {C2}, 1 ) ); FC_ASSERT( fc::ecc::verify_sum( {C1}, {C2}, INT64_MAX ) ); FC_ASSERT( fc::ecc::verify_sum( {C2}, {C1}, -INT64_MAX ) ); } } catch ( const fc::exception& e ) { edump((e.to_detail_string())); } return 0; }