/* * 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 namespace graphene { namespace app { database_api::database_api(graphene::chain::database& db):_db(db) { _change_connection = _db.changed_objects.connect([this](const vector& ids) { on_objects_changed(ids); }); _applied_block_connection = _db.applied_block.connect([this](const signed_block&){ on_applied_block(); }); } fc::variants database_api::get_objects(const vector& ids)const { 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)const { const auto& assets_by_symbol = _db.get_index_type().indices().get(); vector > result; result.reserve(symbols.size()); std::transform(symbols.begin(), symbols.end(), std::back_inserter(result), [&assets_by_symbol](const string& symbol) -> optional { auto itr = assets_by_symbol.find(symbol); return itr == assets_by_symbol.end()? optional() : *itr; }); return result; } global_property_object database_api::get_global_properties()const { return _db.get(global_property_id_type()); } 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)) 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)) 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())); return result; } 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_delegate_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 {}; } 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_delegate_accounts(const string& lower_bound_name, uint32_t limit)const { FC_ASSERT( limit <= 1000 ); const auto& delegates_by_id = _db.get_index_type().indices().get(); // we want to order delegates by account name, but that name is in the account object // so the delegate_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 delegates to be few and the frequency of calls to be rare std::map delegates_by_account_name; for (const delegate_object& delegate : delegates_by_id) if (auto account_iter = _db.find(delegate.delegate_account)) if (account_iter->name >= lower_bound_name) // we can ignore anything below lower_bound_name delegates_by_account_name.insert(std::make_pair(account_iter->name, delegate.id)); auto end_iter = delegates_by_account_name.begin(); while (end_iter != delegates_by_account_name.end() && limit--) ++end_iter; delegates_by_account_name.erase(end_iter, delegates_by_account_name.end()); return delegates_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_delegates(const vector& delegate_ids)const { vector> result; result.reserve(delegate_ids.size()); std::transform(delegate_ids.begin(), delegate_ids.end(), std::back_inserter(result), [this](delegate_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) { auto db_api = std::make_shared(std::ref(*_app.chain_database())); auto net_api = std::make_shared(std::ref(_app)); auto hist_api = std::make_shared(_app); _database_api = db_api; _network_api = net_api; _history_api = hist_api; return true; } network_api::network_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_api::on_applied_block( const signed_block& b ) { if( _callbacks.size() ) { 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); auto block_num = b.block_num(); if( itr != _callbacks.end() ) { fc::async( [=](){ itr->second( fc::variant(transaction_confirmation{ id, block_num, trx_num, trx}) ); } ); } } } } void network_api::add_node(const fc::ip::endpoint& ep) { _app.p2p_node()->add_node(ep); } void network_api::broadcast_transaction(const signed_transaction& trx) { trx.validate(); _app.chain_database()->push_transaction(trx); _app.p2p_node()->broadcast_transaction(trx); } void network_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); } std::vector network_api::get_connected_peers() const { return _app.p2p_node()->get_connected_peers(); } fc::api login_api::network()const { FC_ASSERT(_network_api); return *_network_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; } void database_api::on_objects_changed(const vector& ids) { vector my_objects; for(auto id : ids) if(_subscriptions.find(id) != _subscriptions.end()) my_objects.push_back(id); _broadcast_changes_complete = fc::async([=](){ for(auto id : my_objects) { const object* obj = _db.find_object(id); if(obj) { _subscriptions[id](obj->to_variant()); } else { _subscriptions[id](fc::variant(id)); } } }); } /** 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()) { 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)); } fc::async([=](){ for(auto item : subscribed_markets_ops) { auto itr = _market_subscriptions.find(item.first); if(itr != _market_subscriptions.end()) itr->second(fc::variant(item.second)); } }); } database_api::~database_api() { try { if(_broadcast_changes_complete.valid()) { _broadcast_changes_complete.cancel(); _broadcast_changes_complete.wait(); } } catch (const fc::exception& e) { wlog("${e}", ("e",e.to_detail_string())); } } bool database_api::subscribe_to_objects( const std::function& callback, const vector& ids) { for(auto id : ids) _subscriptions[id] = callback; return true; } bool database_api::unsubscribe_from_objects(const vector& ids) { for(auto id : ids) _subscriptions.erase(id); return true; } 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( public_key_type key )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_key_memberships.find(key); vector result; if( itr != refs.account_to_key_memberships.end() ) { result.reserve( itr->second.size() ); for( auto item : itr->second ) result.push_back(item); } return 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_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) ) } } } // graphene::app