/* * Copyright (c) 2015, Cryptonomex, Inc. * All rights reserved. * * This source code is provided for evaluation in private test networks only, until September 8, 2015. After this date, this license expires and * the code may not be used, modified or distributed for any purpose. Redistribution and use in source and binary forms, with or without modification, * are permitted until September 8, 2015, provided that the following conditions are met: * * 1. The code and/or derivative works are used only for private test networks consisting of no more than 10 P2P nodes. * * 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace graphene { namespace app { database_api::database_api(graphene::chain::database& db):_db(db) { wlog("creating database api ${x}", ("x",int64_t(this)) ); _change_connection = _db.changed_objects.connect([this](const vector& ids) { on_objects_changed(ids); }); _removed_connection = _db.removed_objects.connect([this](const vector& objs) { on_objects_removed(objs); }); _applied_block_connection = _db.applied_block.connect([this](const signed_block&){ on_applied_block(); }); } database_api::~database_api() { elog("freeing database api ${x}", ("x",int64_t(this)) ); } void database_api::set_subscribe_callback( std::function cb, bool clear_filter ) { edump((clear_filter)); _subscribe_callback = cb; if( clear_filter || !cb ) { static fc::bloom_parameters param; param.projected_element_count = 10000; param.false_positive_probability = 1.0/10000; param.maximum_size = 1024*8*8*2; _subscribe_filter = fc::bloom_filter(param); } } void database_api::subscribe_to_id( object_id_type id )const { idump((id)); if( _subscribe_callback ) _subscribe_filter.insert( (const unsigned char*)&id, sizeof(id) ); else elog( "unable to subscribe to id because there is no subscribe callback set" ); } fc::variants database_api::get_objects(const vector& ids)const { if( _subscribe_callback ) { for( auto id : ids ) { if( id.type() == operation_history_object_type && id.space() == protocol_ids ) continue; if( id.type() == impl_account_transaction_history_object_type && id.space() == implementation_ids ) continue; subscribe_to_id( id ); } } else { elog( "getObjects without subscribe callback??" ); } fc::variants result; result.reserve(ids.size()); std::transform(ids.begin(), ids.end(), std::back_inserter(result), [this](object_id_type id) -> fc::variant { if(auto obj = _db.find_object(id)) return obj->to_variant(); return {}; }); return result; } optional database_api::get_block_header(uint32_t block_num) const { auto result = _db.fetch_block_by_number(block_num); if(result) return *result; return {}; } optional database_api::get_block(uint32_t block_num)const { return _db.fetch_block_by_number(block_num); } processed_transaction database_api::get_transaction(uint32_t block_num, uint32_t trx_num)const { auto opt_block = _db.fetch_block_by_number(block_num); FC_ASSERT( opt_block ); FC_ASSERT( opt_block->transactions.size() > trx_num ); return opt_block->transactions[trx_num]; } vector> database_api::lookup_account_names(const vector& account_names)const { const auto& accounts_by_name = _db.get_index_type().indices().get(); vector > result; result.reserve(account_names.size()); std::transform(account_names.begin(), account_names.end(), std::back_inserter(result), [&accounts_by_name](const string& name) -> optional { auto itr = accounts_by_name.find(name); return itr == accounts_by_name.end()? optional() : *itr; }); return result; } vector> database_api::lookup_asset_symbols(const vector& symbols_or_ids)const { const auto& assets_by_symbol = _db.get_index_type().indices().get(); vector > result; result.reserve(symbols_or_ids.size()); std::transform(symbols_or_ids.begin(), symbols_or_ids.end(), std::back_inserter(result), [this, &assets_by_symbol](const string& symbol_or_id) -> optional { if( !symbol_or_id.empty() && std::isdigit(symbol_or_id[0]) ) { auto ptr = _db.find(variant(symbol_or_id).as()); return ptr == nullptr? optional() : *ptr; } auto itr = assets_by_symbol.find(symbol_or_id); return itr == assets_by_symbol.end()? optional() : *itr; }); return result; } chain_property_object database_api::get_chain_properties()const { return _db.get(chain_property_id_type()); } global_property_object database_api::get_global_properties()const { return _db.get(global_property_id_type()); } fc::variant_object database_api::get_config()const { return get_config(); } chain_id_type database_api::get_chain_id()const { return _db.get_chain_id(); } dynamic_global_property_object database_api::get_dynamic_global_properties()const { return _db.get(dynamic_global_property_id_type()); } vector> database_api::get_accounts(const vector& account_ids)const { vector> result; result.reserve(account_ids.size()); std::transform(account_ids.begin(), account_ids.end(), std::back_inserter(result), [this](account_id_type id) -> optional { if(auto o = _db.find(id)) { subscribe_to_id( id ); return *o; } return {}; }); return result; } vector> database_api::get_assets(const vector& asset_ids)const { vector> result; result.reserve(asset_ids.size()); std::transform(asset_ids.begin(), asset_ids.end(), std::back_inserter(result), [this](asset_id_type id) -> optional { if(auto o = _db.find(id)) { subscribe_to_id( id ); return *o; } return {}; }); return result; } uint64_t database_api::get_account_count()const { return _db.get_index_type().indices().size(); } map database_api::lookup_accounts(const string& lower_bound_name, uint32_t limit)const { FC_ASSERT( limit <= 1000 ); const auto& accounts_by_name = _db.get_index_type().indices().get(); map result; for( auto itr = accounts_by_name.lower_bound(lower_bound_name); limit-- && itr != accounts_by_name.end(); ++itr ) { result.insert(make_pair(itr->name, itr->get_id())); if( limit == 1 ) subscribe_to_id( itr->get_id() ); } return result; } std::map database_api::get_full_accounts( const vector& names_or_ids, bool subscribe) { std::map results; for (const std::string& account_name_or_id : names_or_ids) { const account_object* account = nullptr; if (std::isdigit(account_name_or_id[0])) account = _db.find(fc::variant(account_name_or_id).as()); else { const auto& idx = _db.get_index_type().indices().get(); auto itr = idx.find(account_name_or_id); if (itr != idx.end()) account = &*itr; } if (account == nullptr) continue; if( subscribe ) { ilog( "subscribe to ${id}", ("id",account->name) ); subscribe_to_id( account->id ); } // fc::mutable_variant_object full_account; full_account acnt; acnt.account = *account; acnt.statistics = account->statistics(_db); acnt.registrar_name = account->registrar(_db).name; acnt.referrer_name = account->referrer(_db).name; acnt.lifetime_referrer_name = account->lifetime_referrer(_db).name; acnt.votes = lookup_vote_ids( vector(account->options.votes.begin(),account->options.votes.end()) ); // Add the account itself, its statistics object, cashback balance, and referral account names /* full_account("account", *account)("statistics", account->statistics(_db)) ("registrar_name", account->registrar(_db).name)("referrer_name", account->referrer(_db).name) ("lifetime_referrer_name", account->lifetime_referrer(_db).name); */ if (account->cashback_vb) { acnt.cashback_balance = account->cashback_balance(_db); } // Add the account's proposals const auto& proposal_idx = _db.get_index_type(); const auto& pidx = dynamic_cast&>(proposal_idx); const auto& proposals_by_account = pidx.get_secondary_index(); auto required_approvals_itr = proposals_by_account._account_to_proposals.find( account->id ); if( required_approvals_itr != proposals_by_account._account_to_proposals.end() ) { acnt.proposals.reserve( required_approvals_itr->second.size() ); for( auto proposal_id : required_approvals_itr->second ) acnt.proposals.push_back( proposal_id(_db) ); } // Add the account's balances auto balance_range = _db.get_index_type().indices().get().equal_range(account->id); //vector balances; std::for_each(balance_range.first, balance_range.second, [&acnt](const account_balance_object& balance) { acnt.balances.emplace_back(balance); }); // Add the account's vesting balances auto vesting_range = _db.get_index_type().indices().get().equal_range(account->id); std::for_each(vesting_range.first, vesting_range.second, [&acnt](const vesting_balance_object& balance) { acnt.vesting_balances.emplace_back(balance); }); // Add the account's orders auto order_range = _db.get_index_type().indices().get().equal_range(account->id); std::for_each(order_range.first, order_range.second, [&acnt] (const limit_order_object& order) { acnt.limit_orders.emplace_back(order); }); auto call_range = _db.get_index_type().indices().get().equal_range(account->id); std::for_each(call_range.first, call_range.second, [&acnt] (const call_order_object& call) { acnt.call_orders.emplace_back(call); }); results[account_name_or_id] = acnt; } return results; } vector database_api::get_account_balances(account_id_type acnt, const flat_set& assets)const { vector result; if (assets.empty()) { // if the caller passes in an empty list of assets, return balances for all assets the account owns const account_balance_index& balance_index = _db.get_index_type(); auto range = balance_index.indices().get().equal_range(acnt); for (const account_balance_object& balance : boost::make_iterator_range(range.first, range.second)) result.push_back(asset(balance.get_balance())); } else { result.reserve(assets.size()); std::transform(assets.begin(), assets.end(), std::back_inserter(result), [this, acnt](asset_id_type id) { return _db.get_balance(acnt, id); }); } return result; } vector database_api::get_named_account_balances(const std::string& name, const flat_set& assets) const { const auto& accounts_by_name = _db.get_index_type().indices().get(); auto itr = accounts_by_name.find(name); FC_ASSERT( itr != accounts_by_name.end() ); return get_account_balances(itr->get_id(), assets); } /** * @return the limit orders for both sides of the book for the two assets specified up to limit number on each side. */ vector database_api::get_limit_orders(asset_id_type a, asset_id_type b, uint32_t limit)const { const auto& limit_order_idx = _db.get_index_type(); const auto& limit_price_idx = limit_order_idx.indices().get(); vector result; uint32_t count = 0; auto limit_itr = limit_price_idx.lower_bound(price::max(a,b)); auto limit_end = limit_price_idx.upper_bound(price::min(a,b)); while(limit_itr != limit_end && count < limit) { result.push_back(*limit_itr); ++limit_itr; ++count; } count = 0; limit_itr = limit_price_idx.lower_bound(price::max(b,a)); limit_end = limit_price_idx.upper_bound(price::min(b,a)); while(limit_itr != limit_end && count < limit) { result.push_back(*limit_itr); ++limit_itr; ++count; } return result; } vector database_api::get_call_orders(asset_id_type a, uint32_t limit)const { const auto& call_index = _db.get_index_type().indices().get(); const asset_object& mia = _db.get(a); price index_price = price::min(mia.bitasset_data(_db).options.short_backing_asset, mia.get_id()); return vector(call_index.lower_bound(index_price.min()), call_index.lower_bound(index_price.max())); } vector database_api::get_settle_orders(asset_id_type a, uint32_t limit)const { const auto& settle_index = _db.get_index_type().indices().get(); const asset_object& mia = _db.get(a); return vector(settle_index.lower_bound(mia.get_id()), settle_index.upper_bound(mia.get_id())); } vector database_api::list_assets(const string& lower_bound_symbol, uint32_t limit)const { FC_ASSERT( limit <= 100 ); const auto& assets_by_symbol = _db.get_index_type().indices().get(); vector result; result.reserve(limit); auto itr = assets_by_symbol.lower_bound(lower_bound_symbol); if( lower_bound_symbol == "" ) itr = assets_by_symbol.begin(); while(limit-- && itr != assets_by_symbol.end()) result.emplace_back(*itr++); return result; } fc::optional database_api::get_committee_member_by_account(account_id_type account) const { const auto& idx = _db.get_index_type().indices().get(); auto itr = idx.find(account); if( itr != idx.end() ) return *itr; return {}; } fc::optional database_api::get_witness_by_account(account_id_type account) const { const auto& idx = _db.get_index_type().indices().get(); auto itr = idx.find(account); if( itr != idx.end() ) return *itr; return {}; } vector database_api::lookup_vote_ids( const vector& votes )const { FC_ASSERT( votes.size() < 100, "Only 100 votes can be queried at a time" ); const auto& witness_idx = _db.get_index_type().indices().get(); const auto& committee_idx = _db.get_index_type().indices().get(); vector result; result.reserve( votes.size() ); for( auto id : votes ) { switch( id.type() ) { case vote_id_type::committee: { auto itr = committee_idx.find( id ); if( itr != committee_idx.end() ) result.emplace_back( variant( *itr ) ); else result.emplace_back( variant() ); break; } case vote_id_type::witness: { auto itr = witness_idx.find( id ); if( itr != witness_idx.end() ) result.emplace_back( variant( *itr ) ); else result.emplace_back( variant() ); break; } case vote_id_type::worker: break; case vote_id_type::VOTE_TYPE_COUNT: break; // supress unused enum value warnings } } return result; } uint64_t database_api::get_witness_count()const { return _db.get_index_type().indices().size(); } map database_api::lookup_witness_accounts(const string& lower_bound_name, uint32_t limit)const { FC_ASSERT( limit <= 1000 ); const auto& witnesses_by_id = _db.get_index_type().indices().get(); // we want to order witnesses by account name, but that name is in the account object // so the witness_index doesn't have a quick way to access it. // get all the names and look them all up, sort them, then figure out what // records to return. This could be optimized, but we expect the // number of witnesses to be few and the frequency of calls to be rare std::map witnesses_by_account_name; for (const witness_object& witness : witnesses_by_id) if (auto account_iter = _db.find(witness.witness_account)) if (account_iter->name >= lower_bound_name) // we can ignore anything below lower_bound_name witnesses_by_account_name.insert(std::make_pair(account_iter->name, witness.id)); auto end_iter = witnesses_by_account_name.begin(); while (end_iter != witnesses_by_account_name.end() && limit--) ++end_iter; witnesses_by_account_name.erase(end_iter, witnesses_by_account_name.end()); return witnesses_by_account_name; } map database_api::lookup_committee_member_accounts(const string& lower_bound_name, uint32_t limit)const { FC_ASSERT( limit <= 1000 ); const auto& committee_members_by_id = _db.get_index_type().indices().get(); // we want to order committee_members by account name, but that name is in the account object // so the committee_member_index doesn't have a quick way to access it. // get all the names and look them all up, sort them, then figure out what // records to return. This could be optimized, but we expect the // number of committee_members to be few and the frequency of calls to be rare std::map committee_members_by_account_name; for (const committee_member_object& committee_member : committee_members_by_id) if (auto account_iter = _db.find(committee_member.committee_member_account)) if (account_iter->name >= lower_bound_name) // we can ignore anything below lower_bound_name committee_members_by_account_name.insert(std::make_pair(account_iter->name, committee_member.id)); auto end_iter = committee_members_by_account_name.begin(); while (end_iter != committee_members_by_account_name.end() && limit--) ++end_iter; committee_members_by_account_name.erase(end_iter, committee_members_by_account_name.end()); return committee_members_by_account_name; } vector> database_api::get_witnesses(const vector& witness_ids)const { vector> result; result.reserve(witness_ids.size()); std::transform(witness_ids.begin(), witness_ids.end(), std::back_inserter(result), [this](witness_id_type id) -> optional { if(auto o = _db.find(id)) return *o; return {}; }); return result; } vector> database_api::get_committee_members(const vector& committee_member_ids)const { vector> result; result.reserve(committee_member_ids.size()); std::transform(committee_member_ids.begin(), committee_member_ids.end(), std::back_inserter(result), [this](committee_member_id_type id) -> optional { if(auto o = _db.find(id)) return *o; return {}; }); return result; } login_api::login_api(application& a) :_app(a) { } login_api::~login_api() { } bool login_api::login(const string& user, const string& password) { optional< api_access_info > acc = _app.get_api_access_info( user ); if( !acc.valid() ) return false; if( acc->password_hash_b64 != "*" ) { std::string password_salt = fc::base64_decode( acc->password_salt_b64 ); std::string acc_password_hash = fc::base64_decode( acc->password_hash_b64 ); fc::sha256 hash_obj = fc::sha256::hash( password + password_salt ); if( hash_obj.data_size() != acc_password_hash.length() ) return false; if( memcmp( hash_obj.data(), acc_password_hash.c_str(), hash_obj.data_size() ) != 0 ) return false; } for( const std::string& api_name : acc->allowed_apis ) enable_api( api_name ); return true; } void login_api::enable_api( const std::string& api_name ) { if( api_name == "database_api" ) { _database_api = std::make_shared< database_api >( std::ref( *_app.chain_database() ) ); } else if( api_name == "network_broadcast_api" ) { _network_broadcast_api = std::make_shared< network_broadcast_api >( std::ref( _app ) ); } else if( api_name == "history_api" ) { _history_api = std::make_shared< history_api >( _app ); } else if( api_name == "network_node_api" ) { _network_node_api = std::make_shared< network_node_api >( std::ref(_app) ); } return; } network_broadcast_api::network_broadcast_api(application& a):_app(a) { _applied_block_connection = _app.chain_database()->applied_block.connect([this](const signed_block& b){ on_applied_block(b); }); } void network_broadcast_api::on_applied_block( const signed_block& b ) { if( _callbacks.size() ) { /// we need to ensure the database_api is not deleted for the life of the async operation auto capture_this = shared_from_this(); for( uint32_t trx_num = 0; trx_num < b.transactions.size(); ++trx_num ) { const auto& trx = b.transactions[trx_num]; auto id = trx.id(); auto itr = _callbacks.find(id); if( itr != _callbacks.end() ) { auto block_num = b.block_num(); auto& callback = _callbacks.find(id)->second; fc::async( [capture_this,this,id,block_num,trx_num,trx,callback](){ callback( fc::variant(transaction_confirmation{ id, block_num, trx_num, trx}) ); } ); } } } } void network_broadcast_api::broadcast_transaction(const signed_transaction& trx) { trx.validate(); _app.chain_database()->push_transaction(trx); _app.p2p_node()->broadcast_transaction(trx); } void network_broadcast_api::broadcast_transaction_with_callback(confirmation_callback cb, const signed_transaction& trx) { trx.validate(); _callbacks[trx.id()] = cb; _app.chain_database()->push_transaction(trx); _app.p2p_node()->broadcast_transaction(trx); } network_node_api::network_node_api( application& a ) : _app( a ) { } void network_node_api::add_node(const fc::ip::endpoint& ep) { _app.p2p_node()->add_node(ep); } std::vector network_node_api::get_connected_peers() const { return _app.p2p_node()->get_connected_peers(); } fc::api login_api::network_broadcast()const { FC_ASSERT(_network_broadcast_api); return *_network_broadcast_api; } fc::api login_api::network_node()const { FC_ASSERT(_network_node_api); return *_network_node_api; } fc::api login_api::database()const { FC_ASSERT(_database_api); return *_database_api; } fc::api login_api::history() const { FC_ASSERT(_history_api); return *_history_api; } vector get_relevant_accounts( const object* obj ) { vector result; if( obj->id.space() == protocol_ids ) { switch( (object_type)obj->id.type() ) { case null_object_type: case base_object_type: case OBJECT_TYPE_COUNT: return result; case account_object_type:{ result.push_back( obj->id ); break; } case asset_object_type:{ const auto& aobj = dynamic_cast(obj); assert( aobj != nullptr ); result.push_back( aobj->issuer ); break; } case force_settlement_object_type:{ const auto& aobj = dynamic_cast(obj); assert( aobj != nullptr ); result.push_back( aobj->owner ); break; } case committee_member_object_type:{ const auto& aobj = dynamic_cast(obj); assert( aobj != nullptr ); result.push_back( aobj->committee_member_account ); break; } case witness_object_type:{ const auto& aobj = dynamic_cast(obj); assert( aobj != nullptr ); result.push_back( aobj->witness_account ); break; } case limit_order_object_type:{ const auto& aobj = dynamic_cast(obj); assert( aobj != nullptr ); result.push_back( aobj->seller ); break; } case call_order_object_type:{ const auto& aobj = dynamic_cast(obj); assert( aobj != nullptr ); result.push_back( aobj->borrower ); break; } case custom_object_type:{ } case proposal_object_type:{ const auto& aobj = dynamic_cast(obj); assert( aobj != nullptr ); flat_set impacted; transaction_get_impacted_accounts( aobj->proposed_transaction, impacted ); result.reserve( impacted.size() ); for( auto& item : impacted ) result.emplace_back(item); break; } case operation_history_object_type:{ const auto& aobj = dynamic_cast(obj); assert( aobj != nullptr ); flat_set impacted; operation_get_impacted_accounts( aobj->op, impacted ); result.reserve( impacted.size() ); for( auto& item : impacted ) result.emplace_back(item); break; } case withdraw_permission_object_type:{ const auto& aobj = dynamic_cast(obj); assert( aobj != nullptr ); result.push_back( aobj->withdraw_from_account ); result.push_back( aobj->authorized_account ); break; } case vesting_balance_object_type:{ const auto& aobj = dynamic_cast(obj); assert( aobj != nullptr ); result.push_back( aobj->owner ); break; } case worker_object_type:{ const auto& aobj = dynamic_cast(obj); assert( aobj != nullptr ); result.push_back( aobj->worker_account ); break; } case balance_object_type:{ /** these are free from any accounts */ } } } else if( obj->id.space() == implementation_ids ) { switch( (impl_object_type)obj->id.type() ) { case impl_global_property_object_type:{ } case impl_dynamic_global_property_object_type:{ } case impl_index_meta_object_type:{ } case impl_asset_dynamic_data_type:{ } case impl_asset_bitasset_data_type:{ break; } case impl_account_balance_object_type:{ const auto& aobj = dynamic_cast(obj); assert( aobj != nullptr ); result.push_back( aobj->owner ); break; } case impl_account_statistics_object_type:{ const auto& aobj = dynamic_cast(obj); assert( aobj != nullptr ); result.push_back( aobj->owner ); break; } case impl_transaction_object_type:{ const auto& aobj = dynamic_cast(obj); assert( aobj != nullptr ); flat_set impacted; transaction_get_impacted_accounts( aobj->trx, impacted ); result.reserve( impacted.size() ); for( auto& item : impacted ) result.emplace_back(item); break; } case impl_blinded_balance_object_type:{ const auto& aobj = dynamic_cast(obj); assert( aobj != nullptr ); result.reserve( aobj->owner.account_auths.size() ); for( const auto& a : aobj->owner.account_auths ) result.push_back( a.first ); break; } case impl_block_summary_object_type:{ } case impl_account_transaction_history_object_type:{ } case impl_witness_schedule_object_type: { } case impl_chain_property_object_type: { } } } return result; } // end get_relevant_accounts( obj ) void database_api::on_objects_removed( const vector& objs ) { /// we need to ensure the database_api is not deleted for the life of the async operation auto capture_this = shared_from_this(); if( _subscribe_callback ) { map > broadcast_queue; for( const auto& obj : objs ) { auto relevant = get_relevant_accounts( obj ); for( const auto& r : relevant ) { if( _subscribe_filter.contains(r) ) broadcast_queue[r].emplace_back(obj->to_variant()); } if( relevant.size() == 0 && _subscribe_filter.contains(obj->id) ) broadcast_queue[account_id_type()].emplace_back(obj->to_variant()); } if( broadcast_queue.size() ) { fc::async([capture_this,broadcast_queue,this](){ _subscribe_callback( fc::variant(broadcast_queue) ); }); } } if( _market_subscriptions.size() ) { map< pair, vector > broadcast_queue; for( const auto& obj : objs ) { const limit_order_object* order = dynamic_cast(obj); if( order ) { auto sub = _market_subscriptions.find( order->get_market() ); if( sub != _market_subscriptions.end() ) broadcast_queue[order->get_market()].emplace_back( order->id ); } } if( broadcast_queue.size() ) { fc::async([capture_this,this,broadcast_queue](){ for( const auto& item : broadcast_queue ) { auto sub = _market_subscriptions.find(item.first); if( sub != _market_subscriptions.end() ) sub->second( fc::variant(item.second ) ); } }); } } } void database_api::on_objects_changed(const vector& ids) { vector updates; map< pair, vector > market_broadcast_queue; idump((ids)); for(auto id : ids) { const object* obj = nullptr; if( _subscribe_callback ) { obj = _db.find_object( id ); if( obj ) { vector relevant = get_relevant_accounts( obj ); for( const auto& r : relevant ) { if( _subscribe_filter.contains(r) ) updates.emplace_back(obj->to_variant()); } if( relevant.size() == 0 && _subscribe_filter.contains(obj->id) ) updates.emplace_back(obj->to_variant()); } else { if( _subscribe_filter.contains(id) ) updates.emplace_back(id); // send just the id to indicate removal } } if( _market_subscriptions.size() ) { if( !_subscribe_callback ) obj = _db.find_object( id ); if( obj ) { const limit_order_object* order = dynamic_cast(obj); if( order ) { auto sub = _market_subscriptions.find( order->get_market() ); if( sub != _market_subscriptions.end() ) market_broadcast_queue[order->get_market()].emplace_back( order->id ); } } } } auto capture_this = shared_from_this(); /// pushing the future back / popping the prior future if it is complete. /// if a connection hangs then this could get backed up and result in /// a failure to exit cleanly. fc::async([capture_this,this,updates,market_broadcast_queue](){ if( _subscribe_callback ) _subscribe_callback( updates ); for( const auto& item : market_broadcast_queue ) { auto sub = _market_subscriptions.find(item.first); if( sub != _market_subscriptions.end() ) sub->second( fc::variant(item.second ) ); } }); } /** note: this method cannot yield because it is called in the middle of * apply a block. */ void database_api::on_applied_block() { if(_market_subscriptions.size() == 0) return; const auto& ops = _db.get_applied_operations(); map< std::pair, vector> > subscribed_markets_ops; for(const auto& op : ops) { std::pair market; switch(op.op.which()) { /* This is sent via the object_changed callback case operation::tag::value: market = op.op.get().get_market(); break; */ case operation::tag::value: market = op.op.get().get_market(); break; /* case operation::tag::value: */ default: break; } if(_market_subscriptions.count(market)) subscribed_markets_ops[market].push_back(std::make_pair(op.op, op.result)); } /// we need to ensure the database_api is not deleted for the life of the async operation auto capture_this = shared_from_this(); fc::async([this,capture_this,subscribed_markets_ops](){ for(auto item : subscribed_markets_ops) { auto itr = _market_subscriptions.find(item.first); if(itr != _market_subscriptions.end()) itr->second(fc::variant(item.second)); } }); } void database_api::subscribe_to_market(std::function callback, asset_id_type a, asset_id_type b) { if(a > b) std::swap(a,b); FC_ASSERT(a != b); _market_subscriptions[ std::make_pair(a,b) ] = callback; } void database_api::unsubscribe_from_market(asset_id_type a, asset_id_type b) { if(a > b) std::swap(a,b); FC_ASSERT(a != b); _market_subscriptions.erase(std::make_pair(a,b)); } std::string database_api::get_transaction_hex(const signed_transaction& trx)const { return fc::to_hex(fc::raw::pack(trx)); } vector history_api::get_account_history(account_id_type account, operation_history_id_type stop, unsigned limit, operation_history_id_type start) const { FC_ASSERT(_app.chain_database()); const auto& db = *_app.chain_database(); FC_ASSERT(limit <= 100); vector result; const auto& stats = account(db).statistics(db); if(stats.most_recent_op == account_transaction_history_id_type()) return result; const account_transaction_history_object* node = &stats.most_recent_op(db); if(start == operation_history_id_type()) start = node->id; while(node && node->operation_id.instance.value > stop.instance.value && result.size() < limit) { if(node->id.instance() <= start.instance.value) result.push_back(node->operation_id(db)); if(node->next == account_transaction_history_id_type()) node = nullptr; else node = db.find(node->next); } return result; } flat_set history_api::get_market_history_buckets()const { auto hist = _app.get_plugin( "market_history" ); FC_ASSERT( hist ); return hist->tracked_buckets(); } vector history_api::get_market_history( asset_id_type a, asset_id_type b, uint32_t bucket_seconds, fc::time_point_sec start, fc::time_point_sec end )const { try { FC_ASSERT(_app.chain_database()); const auto& db = *_app.chain_database(); vector result; result.reserve(100); if( a > b ) std::swap(a,b); const auto& bidx = db.get_index_type(); const auto& by_key_idx = bidx.indices().get(); auto itr = by_key_idx.lower_bound( bucket_key( a, b, bucket_seconds, start ) ); while( itr != by_key_idx.end() && itr->key.open <= end && result.size() < 100 ) { if( !(itr->key.base == a && itr->key.quote == b && itr->key.seconds == bucket_seconds) ) return result; result.push_back(*itr); ++itr; } return result; } FC_CAPTURE_AND_RETHROW( (a)(b)(bucket_seconds)(start)(end) ) } /** * @return all accounts that referr to the key or account id in their owner or active authorities. */ vector database_api::get_account_references( account_id_type account_id )const { const auto& idx = _db.get_index_type(); const auto& aidx = dynamic_cast&>(idx); const auto& refs = aidx.get_secondary_index(); auto itr = refs.account_to_account_memberships.find(account_id); vector result; if( itr != refs.account_to_account_memberships.end() ) { result.reserve( itr->second.size() ); for( auto item : itr->second ) result.push_back(item); } return result; } /** * @return all accounts that referr to the key or account id in their owner or active authorities. */ vector> database_api::get_key_references( vector keys )const { wdump( (keys) ); vector< vector > final_result; final_result.reserve(keys.size()); for( auto& key : keys ) { address a1( pts_address(key, false, 56) ); address a2( pts_address(key, true, 56) ); address a3( pts_address(key, false, 0) ); address a4( pts_address(key, true, 0) ); address a5( key ); const auto& idx = _db.get_index_type(); const auto& aidx = dynamic_cast&>(idx); const auto& refs = aidx.get_secondary_index(); auto itr = refs.account_to_key_memberships.find(key); vector result; for( auto& a : {a1,a2,a3,a4,a5} ) { auto itr = refs.account_to_address_memberships.find(a); if( itr != refs.account_to_address_memberships.end() ) { result.reserve( itr->second.size() ); for( auto item : itr->second ) { wdump((a)(item)(item(_db).name)); result.push_back(item); } } } if( itr != refs.account_to_key_memberships.end() ) { result.reserve( itr->second.size() ); for( auto item : itr->second ) result.push_back(item); } final_result.emplace_back( std::move(result) ); } return final_result; } /** TODO: add secondary index that will accelerate this process */ vector database_api::get_proposed_transactions( account_id_type id )const { const auto& idx = _db.get_index_type(); vector result; idx.inspect_all_objects( [&](const object& obj){ const proposal_object& p = static_cast(obj); if( p.required_active_approvals.find( id ) != p.required_active_approvals.end() ) result.push_back(p); else if ( p.required_owner_approvals.find( id ) != p.required_owner_approvals.end() ) result.push_back(p); else if ( p.available_active_approvals.find( id ) != p.available_active_approvals.end() ) result.push_back(p); }); return result; } vector database_api::get_margin_positions( const account_id_type& id )const { try { const auto& idx = _db.get_index_type(); const auto& aidx = idx.indices().get(); auto start = aidx.lower_bound( boost::make_tuple( id, 0 ) ); auto end = aidx.lower_bound( boost::make_tuple( id+1, 0 ) ); vector result; while( start != end ) { result.push_back(*start); ++start; } return result; } FC_CAPTURE_AND_RETHROW( (id) ) } vector database_api::get_vested_balances( const vector& objs )const { try { vector result; result.reserve( objs.size() ); auto now = _db.head_block_time(); for( auto obj : objs ) result.push_back( obj(_db).available( now ) ); return result; } FC_CAPTURE_AND_RETHROW( (objs) ) } vector database_api::get_balance_objects( const vector
& addrs )const { try { const auto& bal_idx = _db.get_index_type(); const auto& by_owner_idx = bal_idx.indices().get(); vector result; for( const auto& owner : addrs ) { auto itr = by_owner_idx.lower_bound( boost::make_tuple( owner, asset_id_type(0) ) ); while( itr != by_owner_idx.end() && itr->owner == owner ) { result.push_back( *itr ); ++itr; } } return result; } FC_CAPTURE_AND_RETHROW( (addrs) ) } set database_api::get_required_signatures( const signed_transaction& trx, const flat_set& available_keys )const { return trx.get_required_signatures( _db.get_chain_id(), available_keys, [&]( account_id_type id ){ return &id(_db).active; }, [&]( account_id_type id ){ return &id(_db).owner; }, _db.get_global_properties().parameters.max_authority_depth ); } set database_api::get_potential_signatures( const signed_transaction& trx )const { set result; trx.get_required_signatures( _db.get_chain_id(), flat_set(), [&]( account_id_type id ){ const auto& auth = id(_db).active; for( const auto& k : auth.get_keys() ) result.insert(k); return &auth; }, [&]( account_id_type id ){ const auto& auth = id(_db).owner; for( const auto& k : auth.get_keys() ) result.insert(k); return &auth; }, _db.get_global_properties().parameters.max_authority_depth ); return result; } bool database_api::verify_authority( const signed_transaction& trx )const { trx.verify_authority( _db.get_chain_id(), [&]( account_id_type id ){ return &id(_db).active; }, [&]( account_id_type id ){ return &id(_db).owner; }, _db.get_global_properties().parameters.max_authority_depth ); return true; } vector database_api::get_blinded_balances( const flat_set& commitments )const { vector result; result.reserve(commitments.size()); const auto& bal_idx = _db.get_index_type(); const auto& by_commitment_idx = bal_idx.indices().get(); for( const auto& c : commitments ) { auto itr = by_commitment_idx.find( c ); if( itr != by_commitment_idx.end() ) result.push_back( *itr ); } return result; } vector database_api::get_required_fees( const vector& ops, asset_id_type id )const { vector result; result.reserve(ops.size()); const asset_object& a = id(_db); for( const auto& op : ops ) result.push_back( _db.current_fee_schedule().calculate_fee( op, a.options.core_exchange_rate ) ); return result; } optional database_api::get_account_by_name( string name )const { const auto& idx = _db.get_index_type().indices().get(); auto itr = idx.find(name); if (itr != idx.end()) return *itr; return optional(); } } } // graphene::app