From f1518aa5e4be2e8c80bfa48228e86460a559d641 Mon Sep 17 00:00:00 2001 From: Alfredo Garcia Date: Fri, 16 Aug 2019 13:33:30 -0300 Subject: [PATCH 01/18] implement custom operations plugin --- libraries/app/CMakeLists.txt | 2 +- libraries/app/api.cpp | 64 +++ libraries/app/include/graphene/app/api.hpp | 70 ++- libraries/plugins/CMakeLists.txt | 1 + .../plugins/custom_operations/CMakeLists.txt | 25 + .../custom_operations/custom_evaluators.cpp | 117 +++++ .../custom_operations/custom_operations.cpp | 53 ++ .../custom_operations_plugin.cpp | 129 +++++ .../custom_operations/custom_evaluators.hpp | 44 ++ .../custom_operations/custom_objects.hpp | 137 +++++ .../custom_operations/custom_operations.hpp | 103 ++++ .../custom_operations_plugin.hpp | 91 ++++ .../wallet/include/graphene/wallet/wallet.hpp | 71 +++ libraries/wallet/wallet.cpp | 129 ++++- programs/witness_node/CMakeLists.txt | 2 +- programs/witness_node/main.cpp | 2 + tests/CMakeLists.txt | 15 +- tests/cli/main.cpp | 127 ++++- tests/common/database_fixture.cpp | 8 + tests/custom_operations/main.cpp | 469 ++++++++++++++++++ 20 files changed, 1652 insertions(+), 7 deletions(-) create mode 100644 libraries/plugins/custom_operations/CMakeLists.txt create mode 100644 libraries/plugins/custom_operations/custom_evaluators.cpp create mode 100644 libraries/plugins/custom_operations/custom_operations.cpp create mode 100644 libraries/plugins/custom_operations/custom_operations_plugin.cpp create mode 100644 libraries/plugins/custom_operations/include/graphene/custom_operations/custom_evaluators.hpp create mode 100644 libraries/plugins/custom_operations/include/graphene/custom_operations/custom_objects.hpp create mode 100644 libraries/plugins/custom_operations/include/graphene/custom_operations/custom_operations.hpp create mode 100644 libraries/plugins/custom_operations/include/graphene/custom_operations/custom_operations_plugin.hpp create mode 100644 tests/custom_operations/main.cpp diff --git a/libraries/app/CMakeLists.txt b/libraries/app/CMakeLists.txt index 71762b1d90..43a2a55254 100644 --- a/libraries/app/CMakeLists.txt +++ b/libraries/app/CMakeLists.txt @@ -16,7 +16,7 @@ add_library( graphene_app # need to link graphene_debug_witness because plugins aren't sufficiently isolated #246 target_link_libraries( graphene_app graphene_market_history graphene_account_history graphene_elasticsearch graphene_grouped_orders - graphene_api_helper_indexes + graphene_api_helper_indexes graphene_custom_operations graphene_chain fc graphene_db graphene_net graphene_utilities graphene_debug_witness ) target_include_directories( graphene_app PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include" diff --git a/libraries/app/api.cpp b/libraries/app/api.cpp index 2dc7d2eeb6..a6aa07f126 100644 --- a/libraries/app/api.cpp +++ b/libraries/app/api.cpp @@ -48,6 +48,7 @@ template class fc::api; template class fc::api; template class fc::api; template class fc::api; +template class fc::api; template class fc::api; template class fc::api; @@ -119,6 +120,11 @@ namespace graphene { namespace app { { _orders_api = std::make_shared< orders_api >( std::ref( _app ) ); } + else if( api_name == "custom_operations_api" ) + { + if( _app.get_plugin( "custom_operations" ) ) + _custom_operations_api = std::make_shared< custom_operations_api >( std::ref( _app ) ); + } else if( api_name == "debug_api" ) { // can only enable this API if the plugin was loaded @@ -296,6 +302,12 @@ namespace graphene { namespace app { return *_debug_api; } + fc::api login_api::custom() const + { + FC_ASSERT(_custom_operations_api); + return *_custom_operations_api; + } + vector history_api::get_fill_order_history( std::string asset_a, std::string asset_b, uint32_t limit )const { FC_ASSERT(_app.chain_database()); @@ -674,4 +686,56 @@ namespace graphene { namespace app { return result; } + // custom operations api + optional custom_operations_api::get_contact_info(std::string account_id_or_name)const + { + const auto account_id = database_api.get_account_id_from_string(account_id_or_name); + auto &index = _app.chain_database()->get_index_type().indices().get(); + auto itr = index.find(account_id); + if(itr != index.end()) + return *itr; + return optional(); + } + + vector custom_operations_api::get_account_htlc_offers(std::string account_id_or_name, + htlc_order_id_type start, uint32_t limit)const + { + FC_ASSERT(limit <= 101); + + const auto account_id = database_api.get_account_id_from_string(account_id_or_name); + vector results; + auto &index = _app.chain_database()->get_index_type().indices().get(); + + auto itr = index.lower_bound(boost::make_tuple(account_id, start)); + while(itr != index.end() && itr->bitshares_account == account_id && results.size() < limit) + { + results.push_back(*itr); + ++itr; + } + return results; + } + vector custom_operations_api::get_active_htlc_offers(htlc_order_id_type start, uint32_t limit)const + { + FC_ASSERT(limit <= 101); + + vector results; + auto db = _app.chain_database(); + auto &index = db->get_index_type().indices().get(); + auto itr = index.lower_bound(make_tuple(true, db->head_block_time(), start)); + while(itr != index.end() && itr->active && itr->expiration > db->head_block_time() && results.size() < limit) + { + results.push_back(*itr); + ++itr; + } + return results; + } + optional custom_operations_api::get_htlc_offer(htlc_order_id_type id)const + { + auto &index = _app.chain_database()->get_index_type().indices().get(); + auto itr = index.find(id); + if(itr != index.end()) + return *itr; + return optional(); + } + } } // graphene::app diff --git a/libraries/app/include/graphene/app/api.hpp b/libraries/app/include/graphene/app/api.hpp index e62c19a5de..f58582da4f 100644 --- a/libraries/app/include/graphene/app/api.hpp +++ b/libraries/app/include/graphene/app/api.hpp @@ -29,8 +29,8 @@ #include #include - #include +#include #include @@ -54,6 +54,8 @@ namespace graphene { namespace app { using namespace graphene::chain; using namespace graphene::market_history; using namespace graphene::grouped_orders; + using namespace graphene::custom_operations; + using namespace fc::ecc; using std::string; using std::vector; @@ -518,6 +520,61 @@ namespace graphene { namespace app { application& _app; graphene::app::database_api database_api; }; + + /** + * @brief The custom_operations_api class exposes access to standard custom objects parsed by the + * custom_operations_plugin. + */ + class custom_operations_api + { + public: + custom_operations_api(application& app):_app(app), database_api( std::ref(*app.chain_database()), + &(app.get_options()) ){} + + /** + * @breif Get contact information of an account + * + * @param account Account name to get info from + * + * @return The contact information of the account or empty + */ + optional get_contact_info(std::string account)const; + + /** + * @breif Get htlc offers from an account + * + * @param account Account name to get htlc offers from + * @param start ID of the most recent htlc offer to retrieve + * @param limit Maximum number of order objects to retrieve + * + * @return A vector of htlc offer objects from the account + */ + vector get_account_htlc_offers(std::string account, htlc_order_id_type start, + uint32_t limit)const; + + /** + * @breif Get all active and non expired htlc offers + * + * @param start ID of the most recent htlc offer to retrieve + * @param limit Maximum number of order objects to retrieve + * + * @return A vector of active and non expired htlc offers + */ + vector get_active_htlc_offers(htlc_order_id_type start, uint32_t limit)const; + + /** + * @breif Get htlc order offer by id + * + * @param id ID of the htlc order offer to retrieve + * + * @return A vector of active and non expired htlc offers + */ + optional get_htlc_offer(htlc_order_id_type id)const; + + private: + application& _app; + graphene::app::database_api database_api; + }; } } // graphene::app extern template class fc::api; @@ -528,6 +585,7 @@ extern template class fc::api; extern template class fc::api; extern template class fc::api; extern template class fc::api; +extern template class fc::api; namespace graphene { namespace app { /** @@ -569,6 +627,8 @@ namespace graphene { namespace app { fc::api orders()const; /// @brief Retrieve the debug API (if available) fc::api debug()const; + /// @brief Retrieve the custom operations API + fc::api custom()const; /// @brief Called to enable an API, not reflected. void enable_api( const string& api_name ); @@ -584,6 +644,7 @@ namespace graphene { namespace app { optional< fc::api > _asset_api; optional< fc::api > _orders_api; optional< fc::api > _debug_api; + optional< fc::api > _custom_operations_api; }; }} // graphene::app @@ -650,6 +711,12 @@ FC_API(graphene::app::orders_api, (get_tracked_groups) (get_grouped_limit_orders) ) +FC_API(graphene::app::custom_operations_api, + (get_contact_info) + (get_account_htlc_offers) + (get_active_htlc_offers) + (get_htlc_offer) + ) FC_API(graphene::app::login_api, (login) (block) @@ -661,4 +728,5 @@ FC_API(graphene::app::login_api, (asset) (orders) (debug) + (custom) ) diff --git a/libraries/plugins/CMakeLists.txt b/libraries/plugins/CMakeLists.txt index 412b185e08..25dfab19ed 100644 --- a/libraries/plugins/CMakeLists.txt +++ b/libraries/plugins/CMakeLists.txt @@ -8,3 +8,4 @@ add_subdirectory( debug_witness ) add_subdirectory( snapshot ) add_subdirectory( es_objects ) add_subdirectory( api_helper_indexes ) +add_subdirectory( custom_operations ) diff --git a/libraries/plugins/custom_operations/CMakeLists.txt b/libraries/plugins/custom_operations/CMakeLists.txt new file mode 100644 index 0000000000..4202f740c8 --- /dev/null +++ b/libraries/plugins/custom_operations/CMakeLists.txt @@ -0,0 +1,25 @@ +file(GLOB HEADERS "include/graphene/custom_operations/*.hpp") + +add_library( graphene_custom_operations + custom_operations_plugin.cpp + custom_operations.cpp + custom_evaluators.cpp + ) + +target_link_libraries( graphene_custom_operations graphene_chain graphene_app ) +target_include_directories( graphene_custom_operations + PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include" ) + +if(MSVC) + set_source_files_properties(custom_operations_plugin.cpp custom_operations.cpp custom_evaluators.cpp + PROPERTIES COMPILE_FLAGS "/bigobj" ) +endif(MSVC) + +install( TARGETS + graphene_custom_operations + + RUNTIME DESTINATION bin + LIBRARY DESTINATION lib + ARCHIVE DESTINATION lib +) +INSTALL( FILES ${HEADERS} DESTINATION "include/graphene/custom_operations" ) diff --git a/libraries/plugins/custom_operations/custom_evaluators.cpp b/libraries/plugins/custom_operations/custom_evaluators.cpp new file mode 100644 index 0000000000..1ce465c298 --- /dev/null +++ b/libraries/plugins/custom_operations/custom_evaluators.cpp @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2019 oxarbitrage and contributors. + * + * The MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include + +#include +#include +#include + +namespace graphene { namespace custom_operations { + +custom_generic_evaluator::custom_generic_evaluator(database& db, const account_id_type account) +{ + _db = &db; + _account = account; +} + +object_id_type custom_generic_evaluator::do_apply(const account_contact_operation& op) +{ + auto &index = _db->get_index_type().indices().get(); + + auto itr = index.find(_account); + if( itr != index.end() ) + { + _db->modify( *itr, [&op, this]( account_contact_object& aco ){ + aco.account = _account; + if(op.extensions.value.name.valid()) aco.name = *op.extensions.value.name; + if(op.extensions.value.email.valid()) aco.email = *op.extensions.value.email; + if(op.extensions.value.phone.valid()) aco.phone = *op.extensions.value.phone; + if(op.extensions.value.address.valid()) aco.address = *op.extensions.value.address; + if(op.extensions.value.company.valid()) aco.company = *op.extensions.value.company; + if(op.extensions.value.url.valid()) aco.url = *op.extensions.value.url; + }); + return itr->id; + } + else + { + auto created = _db->create( [&op, this]( account_contact_object& aco ) { + aco.account = _account; + if(op.extensions.value.name.valid()) aco.name = *op.extensions.value.name; + if(op.extensions.value.email.valid()) aco.email = *op.extensions.value.email; + if(op.extensions.value.phone.valid()) aco.phone = *op.extensions.value.phone; + if(op.extensions.value.address.valid()) aco.address = *op.extensions.value.address; + if(op.extensions.value.company.valid()) aco.company = *op.extensions.value.company; + if(op.extensions.value.url.valid()) aco.url = *op.extensions.value.url; + }); + return created.id; + } +} + +object_id_type custom_generic_evaluator::do_apply(const create_htlc_order_operation& op) +{ + FC_ASSERT(*op.extensions.value.expiration > _db->head_block_time() + fc::seconds(3600)); + + auto order_time = _db->head_block_time(); + auto created = _db->create( [&op, &order_time, this]( htlc_order_object& hoo ) { + hoo.bitshares_account = _account; + if(op.extensions.value.bitshares_amount.valid()) hoo.bitshares_amount = *op.extensions.value.bitshares_amount; + if(op.extensions.value.blockchain.valid()) hoo.blockchain = *op.extensions.value.blockchain; + if(op.extensions.value.blockchain_account.valid()) hoo.blockchain_account = *op.extensions.value.blockchain_account; + if(op.extensions.value.blockchain_asset.valid()) hoo.blockchain_asset = *op.extensions.value.blockchain_asset; + if(op.extensions.value.blockchain_asset_precision.valid()) hoo.blockchain_asset_precision = + *op.extensions.value.blockchain_asset_precision; + if(op.extensions.value.blockchain_amount.valid()) hoo.blockchain_amount = *op.extensions.value.blockchain_amount; + if(op.extensions.value.expiration.valid()) hoo.expiration = *op.extensions.value.expiration; + if(op.extensions.value.token_contract.valid()) hoo.token_contract = *op.extensions.value.token_contract; + if(op.extensions.value.tag.valid()) hoo.tag = *op.extensions.value.tag; + + hoo.order_time = order_time; + hoo.active = true; + }); + return created.id; +} + +object_id_type custom_generic_evaluator::do_apply(const take_htlc_order_operation& op) +{ + auto &index = _db->get_index_type().indices().get(); + htlc_order_id_type htlc_order_id; + + if(op.extensions.value.htlc_order_id.valid()) { + htlc_order_id = *op.extensions.value.htlc_order_id; + auto itr = index.find(htlc_order_id); + if (itr != index.end()) { + auto close_time = _db->head_block_time(); + _db->modify(*itr, [&op, &close_time, this](htlc_order_object &hoo) { + hoo.active = false; + hoo.taker_bitshares_account = _account; + if (op.extensions.value.blockchain_account.valid()) + hoo.taker_blockchain_account = op.extensions.value.blockchain_account; + hoo.close_time = close_time; + }); + } + } + return htlc_order_id; +} + +} } diff --git a/libraries/plugins/custom_operations/custom_operations.cpp b/libraries/plugins/custom_operations/custom_operations.cpp new file mode 100644 index 0000000000..673cb82d3b --- /dev/null +++ b/libraries/plugins/custom_operations/custom_operations.cpp @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2019 oxarbitrage and contributors. + * + * The MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include + +namespace graphene { namespace custom_operations { + +void account_contact_operation::validate()const +{ +} + +void create_htlc_order_operation::validate()const +{ + FC_ASSERT(extensions.value.bitshares_amount.valid()); + FC_ASSERT(extensions.value.bitshares_amount->amount.value > 0); + FC_ASSERT(extensions.value.blockchain.valid()); + FC_ASSERT(extensions.value.blockchain_account.valid()); + FC_ASSERT(extensions.value.blockchain_asset.valid()); + FC_ASSERT(extensions.value.blockchain_amount.valid()); + FC_ASSERT(extensions.value.expiration.valid()); +} + +void take_htlc_order_operation::validate()const +{ + FC_ASSERT(extensions.value.blockchain_account.valid()); + FC_ASSERT(extensions.value.htlc_order_id.valid()); +} + +} } //graphene::custom_operations + +GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::custom_operations::account_contact_operation ) +GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::custom_operations::create_htlc_order_operation ) +GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::custom_operations::take_htlc_order_operation ) diff --git a/libraries/plugins/custom_operations/custom_operations_plugin.cpp b/libraries/plugins/custom_operations/custom_operations_plugin.cpp new file mode 100644 index 0000000000..a8162d928d --- /dev/null +++ b/libraries/plugins/custom_operations/custom_operations_plugin.cpp @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2019 oxarbitrage and contributors. + * + * The MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include + +#include +#include +#include + +namespace graphene { namespace custom_operations { + +namespace detail +{ +class custom_operations_plugin_impl +{ + public: + custom_operations_plugin_impl(custom_operations_plugin& _plugin) + : _self( _plugin ) + { } + virtual ~custom_operations_plugin_impl(); + + void onBlock( const signed_block& b ); + + graphene::chain::database& database() + { + return _self.database(); + } + + custom_operations_plugin& _self; + + private: + +}; + +void custom_operations_plugin_impl::onBlock( const signed_block& b ) +{ + graphene::chain::database& db = database(); + const vector >& hist = db.get_applied_operations(); + for( const optional< operation_history_object >& o_operation : hist ) + { + if(!o_operation.valid() || !o_operation->op.is_type()) + continue; + + const custom_operation& custom_op = o_operation->op.get(); + + if(custom_op.data.size() == 0 || uint8_t(custom_op.data.data()[0]) != 0xFF) + continue; + + try { + auto unpacked = fc::raw::unpack(custom_op.data); + custom_op_visitor vtor(db, custom_op.fee_payer()); + unpacked.op.visit(vtor); + } + catch (fc::exception e) { // only api node will know if the unpack, validate or apply fails + wlog("Error: ${ex} in operation: ${op}", ("ex", e.to_detail_string())("op", fc::json::to_string(custom_op))); + continue; + } + } +} + +custom_operations_plugin_impl::~custom_operations_plugin_impl() +{ + return; +} + +} // end namespace detail + +custom_operations_plugin::custom_operations_plugin() : + my( new detail::custom_operations_plugin_impl(*this) ) +{ +} + +custom_operations_plugin::~custom_operations_plugin() +{ +} + +std::string custom_operations_plugin::plugin_name()const +{ + return "custom_operations"; +} +std::string custom_operations_plugin::plugin_description()const +{ + return "custom_operations description"; +} + +void custom_operations_plugin::plugin_set_program_options( + boost::program_options::options_description& cli, + boost::program_options::options_description& cfg + ) +{ +} + +void custom_operations_plugin::plugin_initialize(const boost::program_options::variables_map& options) +{ + database().add_index< primary_index< account_contact_index > >(); + database().add_index< primary_index< htlc_orderbook_index > >(); + + database().applied_block.connect( [this]( const signed_block& b) { + my->onBlock(b); + } ); +} + +void custom_operations_plugin::plugin_startup() +{ + ilog("custom_operations: plugin_startup() begin"); +} + +} } diff --git a/libraries/plugins/custom_operations/include/graphene/custom_operations/custom_evaluators.hpp b/libraries/plugins/custom_operations/include/graphene/custom_operations/custom_evaluators.hpp new file mode 100644 index 0000000000..926df77eb8 --- /dev/null +++ b/libraries/plugins/custom_operations/include/graphene/custom_operations/custom_evaluators.hpp @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2019 oxarbitrage and contributors. + * + * The MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#pragma once +#include +#include + +namespace graphene { namespace custom_operations { + +class custom_generic_evaluator +{ + public: + database* _db; + account_id_type _account; + custom_generic_evaluator(database& db, const account_id_type account); + + object_id_type do_apply(const account_contact_operation& o); + object_id_type do_apply(const create_htlc_order_operation& o); + object_id_type do_apply(const take_htlc_order_operation& o); +}; + +} } + +FC_REFLECT_TYPENAME( graphene::custom_operations::custom_generic_evaluator ) diff --git a/libraries/plugins/custom_operations/include/graphene/custom_operations/custom_objects.hpp b/libraries/plugins/custom_operations/include/graphene/custom_operations/custom_objects.hpp new file mode 100644 index 0000000000..602a18f1c2 --- /dev/null +++ b/libraries/plugins/custom_operations/include/graphene/custom_operations/custom_objects.hpp @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2019 oxarbitrage and contributors. + * + * The MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#pragma once + +#include +#include + +namespace graphene { namespace custom_operations { + +using namespace chain; + +#ifndef CUSTOM_OPERATIONS_SPACE_ID +#define CUSTOM_OPERATIONS_SPACE_ID 7 +#endif + +enum types { + account_contact = 0, + create_htlc = 1, + take_htlc = 2 +}; +enum blockchains { + eos = 0, + bitcoin = 1, + ripple = 2, + ethereum = 3 +}; + +struct account_contact_object : public abstract_object +{ + static const uint8_t space_id = CUSTOM_OPERATIONS_SPACE_ID; + static const uint8_t type_id = account_contact; + + account_id_type account; + optional name; + optional email; + optional phone; + optional address; + optional company; + optional url; +}; + +struct htlc_order_object : public abstract_object +{ + static const uint8_t space_id = CUSTOM_OPERATIONS_SPACE_ID; + static const uint8_t type_id = create_htlc; + + account_id_type bitshares_account; + asset bitshares_amount; + blockchains blockchain; + string blockchain_account; + string blockchain_asset; + string blockchain_amount; + fc::time_point_sec expiration; + fc::time_point_sec order_time; + bool active; + + optional blockchain_asset_precision; + optional token_contract; + optional tag; + optional taker_bitshares_account; + optional taker_blockchain_account; + optional close_time; +}; + +struct by_custom_id; +struct by_custom_account; +typedef multi_index_container< + account_contact_object, + indexed_by< + ordered_non_unique< tag, member< object, object_id_type, &object::id > >, + ordered_unique< tag, + member< account_contact_object, account_id_type, &account_contact_object::account > > + > +> account_contact_multi_index_type; + +typedef generic_index account_contact_index; + +struct by_bitshares_account; +struct by_active; +typedef multi_index_container< + htlc_order_object, + indexed_by< + ordered_non_unique< tag, member< object, object_id_type, &object::id > >, + ordered_unique< tag, + composite_key< htlc_order_object, + member< htlc_order_object, account_id_type, &htlc_order_object::bitshares_account >, + member< object, object_id_type, &object::id > + > + >, + ordered_unique< tag, + composite_key< htlc_order_object, + member< htlc_order_object, bool, &htlc_order_object::active >, + member< htlc_order_object, fc::time_point_sec, &htlc_order_object::expiration >, + member< object, object_id_type, &object::id > + > + > + > +> htlc_orderbook_multi_index_type; + +typedef generic_index htlc_orderbook_index; + +using account_contact_id_type = object_id; +using htlc_order_id_type = object_id; + +} } //graphene::custom_operations + + +FC_REFLECT_DERIVED( graphene::custom_operations::account_contact_object, (graphene::db::object), + (account)(name)(email)(phone)(address)(company)(url)) +FC_REFLECT_DERIVED( graphene::custom_operations::htlc_order_object, (graphene::db::object), + (bitshares_account)(bitshares_amount)(blockchain)(blockchain_account)(blockchain_asset) + (blockchain_amount)(expiration)(order_time)(active) + (blockchain_asset_precision)(token_contract)(tag)(taker_bitshares_account) + (taker_blockchain_account)(close_time)) +FC_REFLECT_ENUM( graphene::custom_operations::types, (account_contact)(create_htlc)(take_htlc) ) +FC_REFLECT_ENUM( graphene::custom_operations::blockchains, (eos)(bitcoin)(ripple)(ethereum) ) diff --git a/libraries/plugins/custom_operations/include/graphene/custom_operations/custom_operations.hpp b/libraries/plugins/custom_operations/include/graphene/custom_operations/custom_operations.hpp new file mode 100644 index 0000000000..c6c1a4916a --- /dev/null +++ b/libraries/plugins/custom_operations/include/graphene/custom_operations/custom_operations.hpp @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2019 oxarbitrage and contributors. + * + * The MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#pragma once + +#include + +#include "custom_objects.hpp" + +namespace graphene { namespace custom_operations { + +using namespace std; +using graphene::protocol::account_id_type; + +struct account_contact_operation : chain::base_operation +{ + struct ext + { + optional name; + optional email; + optional phone; + optional address; + optional company; + optional url; + }; + + graphene::protocol::extension extensions; + + void validate()const; +}; + +struct create_htlc_order_operation : chain::base_operation +{ + struct ext + { + optional bitshares_amount; + optional blockchain; + optional blockchain_account; + optional blockchain_asset; + optional blockchain_asset_precision; + optional blockchain_amount; + optional expiration; + optional token_contract; + optional tag; + }; + + graphene::protocol::extension extensions; + + void validate()const; +}; + +struct take_htlc_order_operation : chain::base_operation +{ + struct ext + { + optional htlc_order_id; + optional blockchain_account; + }; + + graphene::protocol::extension extensions; + + void validate()const; +}; + +} } //graphene::custom_operations + +FC_REFLECT( graphene::custom_operations::account_contact_operation::ext, (name)(email)(phone)(address)(company)(url) ) +FC_REFLECT_TYPENAME( graphene::protocol::extension ) +FC_REFLECT( graphene::custom_operations::account_contact_operation, (extensions) ) + +FC_REFLECT( graphene::custom_operations::create_htlc_order_operation::ext, (bitshares_amount)(blockchain) + (blockchain_account)(blockchain_asset)(blockchain_asset_precision)(blockchain_amount)(expiration) + (token_contract)(tag) ) +FC_REFLECT_TYPENAME( graphene::protocol::extension ) +FC_REFLECT( graphene::custom_operations::create_htlc_order_operation, (extensions) ) + +FC_REFLECT( graphene::custom_operations::take_htlc_order_operation::ext, (htlc_order_id)(blockchain_account) ) +FC_REFLECT_TYPENAME( graphene::protocol::extension ) +FC_REFLECT( graphene::custom_operations::take_htlc_order_operation, (extensions) ) + +GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::custom_operations::account_contact_operation ) +GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::custom_operations::create_htlc_order_operation ) +GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::custom_operations::take_htlc_order_operation ) diff --git a/libraries/plugins/custom_operations/include/graphene/custom_operations/custom_operations_plugin.hpp b/libraries/plugins/custom_operations/include/graphene/custom_operations/custom_operations_plugin.hpp new file mode 100644 index 0000000000..cec86e6611 --- /dev/null +++ b/libraries/plugins/custom_operations/include/graphene/custom_operations/custom_operations_plugin.hpp @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2019 oxarbitrage and contributors. + * + * The MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#pragma once + +#include +#include + +#include +#include +#include + +namespace graphene { namespace custom_operations { +using namespace chain; + +namespace detail +{ + class custom_operations_plugin_impl; +} + +class custom_operations_plugin : public graphene::app::plugin +{ + public: + custom_operations_plugin(); + virtual ~custom_operations_plugin(); + + std::string plugin_name()const override; + std::string plugin_description()const override; + virtual void plugin_set_program_options( + boost::program_options::options_description& cli, + boost::program_options::options_description& cfg) override; + virtual void plugin_initialize(const boost::program_options::variables_map& options) override; + virtual void plugin_startup() override; + + friend class detail::custom_operations_plugin_impl; + std::unique_ptr my; +}; + + +typedef fc::static_variant< + account_contact_operation, + create_htlc_order_operation, + take_htlc_order_operation +> custom_plugin_operation; + +struct custom_operation_wrapper { + uint8_t unused_data; // if first char of custom_op.data is 0xFF we unpack, this char is not used anymore then. + custom_plugin_operation op; +}; + +struct custom_op_visitor +{ + typedef void result_type; + account_id_type _fee_payer; + database* _db; + + custom_op_visitor(database& db, account_id_type fee_payer) { _db = &db; _fee_payer = fee_payer; }; + + template + void operator()(T &v) const { + v.validate(); + custom_generic_evaluator evaluator(*_db, _fee_payer); + evaluator.do_apply(v); + } +}; + + +} } //graphene::custom_operations + +FC_REFLECT_TYPENAME( graphene::custom_operations::custom_plugin_operation ) +FC_REFLECT( graphene::custom_operations::custom_operation_wrapper, (unused_data)(op) ) diff --git a/libraries/wallet/include/graphene/wallet/wallet.hpp b/libraries/wallet/include/graphene/wallet/wallet.hpp index c3624419bf..60196feb5b 100644 --- a/libraries/wallet/include/graphene/wallet/wallet.hpp +++ b/libraries/wallet/include/graphene/wallet/wallet.hpp @@ -2014,6 +2014,75 @@ class wallet_api fc::signal lock_changed; std::shared_ptr my; void encrypt_keys(); + + /** + * Add account contact data by using the custom operations plugin. + * + * Each account can optionally add personal information into the blockchain + * to be retrieved by any interested party. + * + * @param account The account ID or name that we are adding additional information to. + * @param data Contact data to be added. \c account_contact_operation::ext + * @param broadcast true if you wish to broadcast the transaction + * + * @return The signed transaction + */ + signed_transaction set_contact_information(string account, account_contact_operation::ext data, bool broadcast); + + /** + * Get contact data of an account by using the custom operations plugin. + * + * If the account added contact data with @ref set_contact_information an \c account_contact_object will be + * returned. + * + * @param account Account ID or name to get contact data from. + * + * @return An \c account_contact_object or empty if account had not provided contact info yet. + */ + optional get_contact_information(string account); + + /** + * Create an HTLC offer using the custom operations plugin. + * + * The \c custom_operations_plugin maintain a list of HTLC offers to facilitate the exchange + * of tokens between bitshares and another blockchain. + * Applications and individuals can create HTLC offers by the use of this command. + * + * @param account The account in the bitshares side. + * @param data Data to create an HTLC offer. \c create_htlc_order_operation::ext + * @param broadcast true if you wish to broadcast the transaction + * + * @return The signed transaction + */ + signed_transaction create_htlc_offer(string account, create_htlc_order_operation::ext data, bool broadcast); + + /** + * Take an HTLC offer using the custom operation plugin. + * + * The \c custom_operations_plugin maintain a list of HTLC offers to facilitate the exchange + * of tokens between bitshares and another blockchain. + * Applications and individuals can take existing HTLC offers by the use of this command. + * Object state will change from active to inactive after the order is taken and will not be displayed by + * @ref get_active_htlc_offers call anymore. + * + * @param account The account of the taker in the bitshares side. + * @param data Data to take an already created and active HTLC offer. \c take_htlc_order_operation::ext data + * @param broadcast true if you wish to broadcast the transaction + * + * @return The signed transaction + */ + signed_transaction take_htlc_offer(string account, take_htlc_order_operation::ext data, bool broadcast); + + /** + * Get active HTLC offers by using the custom operation plugin. + * + * Get the list of offers available at this time to initialize HTLC exchange with another blockchain. + * + * @param blockchain eos = 0 , bitcoin = 1, ripple = 2, ethereum = 3 + * + * @return A list of \c htlc_order_object that are active and non expired in the selected blockchain. + */ + vector get_active_htlc_offers(uint16_t blockchain); }; } } @@ -2221,5 +2290,7 @@ FC_API( graphene::wallet::wallet_api, (blind_history) (receive_blind_transfer) (get_order_book) + (set_contact_information) + (get_contact_information) (quit) ) diff --git a/libraries/wallet/wallet.cpp b/libraries/wallet/wallet.cpp index b96225702b..20eced8820 100644 --- a/libraries/wallet/wallet.cpp +++ b/libraries/wallet/wallet.cpp @@ -494,7 +494,8 @@ class wallet_api_impl _remote_api(rapi), _remote_db(rapi->database()), _remote_net_broadcast(rapi->network_broadcast()), - _remote_hist(rapi->history()) + _remote_hist(rapi->history()), + _custom_operations(rapi->custom()) { chain_id_type remote_chain_id = _remote_db->get_chain_id(); if( remote_chain_id != _chain_id ) @@ -1960,6 +1961,96 @@ class wallet_api_impl } FC_CAPTURE_AND_RETHROW( (htlc_id)(issuer)(seconds_to_add)(broadcast) ) } + signed_transaction set_contact_information(string account, account_contact_operation::ext data, bool broadcast) + { + try + { + FC_ASSERT( !self.is_locked() ); + + account_id_type account_id = get_account(account).id; + + custom_operation op; + account_contact_operation contact; + contact.extensions.value = data; + + auto packed = fc::raw::pack(contact); + packed.insert(packed.begin(), types::account_contact); + packed.insert(packed.begin(), 0xFF); + + op.payer = account_id; + op.data = packed; + + signed_transaction tx; + tx.operations.push_back(op); + set_operation_fees( tx, _remote_db->get_global_properties().parameters.get_current_fees()); + tx.validate(); + + return sign_transaction(tx, broadcast); + + } FC_CAPTURE_AND_RETHROW( (account)(data)(broadcast) ) + } + + signed_transaction create_htlc_offer(string account, create_htlc_order_operation::ext data, bool broadcast) + { + try + { + FC_ASSERT( !self.is_locked() ); + FC_ASSERT(data.bitshares_amount.valid()); + fc::optional asset_obj = get_asset(data.bitshares_amount->asset_id); + FC_ASSERT(asset_obj, "Could not find asset matching ${asset}", ("asset", data.bitshares_amount->asset_id)); + + account_id_type bitshares_account_id = get_account(account).id; + + custom_operation op; + create_htlc_order_operation htlc; + htlc.extensions.value = data; + + auto packed = fc::raw::pack(htlc); + packed.insert(packed.begin(), types::create_htlc); + packed.insert(packed.begin(), 0xFF); + + op.payer = bitshares_account_id; + op.data = packed; + + signed_transaction tx; + tx.operations.push_back(op); + set_operation_fees( tx, _remote_db->get_global_properties().parameters.get_current_fees()); + tx.validate(); + + return sign_transaction(tx, broadcast); + + } FC_CAPTURE_AND_RETHROW( (account)(data)(broadcast) ) + } + + signed_transaction take_htlc_offer(string account, take_htlc_order_operation::ext data, bool broadcast) + { + try + { + FC_ASSERT( !self.is_locked() ); + + account_id_type bitshares_account_id = get_account(account).id; + + custom_operation op; + take_htlc_order_operation htlc; + htlc.extensions.value = data; + + auto packed = fc::raw::pack(htlc); + packed.insert(packed.begin(), types::take_htlc); + packed.insert(packed.begin(), 0xFF); + + op.payer = bitshares_account_id; + op.data = packed; + + signed_transaction tx; + tx.operations.push_back(op); + set_operation_fees( tx, _remote_db->get_global_properties().parameters.get_current_fees()); + tx.validate(); + + return sign_transaction(tx, broadcast); + + } FC_CAPTURE_AND_RETHROW( (account)(data)(broadcast) ) + } + vector< vesting_balance_object_with_info > get_vesting_balances( string account_name ) { try { fc::optional vbid = maybe_id( account_name ); @@ -3087,6 +3178,7 @@ class wallet_api_impl fc::api _remote_db; fc::api _remote_net_broadcast; fc::api _remote_hist; + fc::api _custom_operations; optional< fc::api > _remote_net_node; optional< fc::api > _remote_debug; @@ -5186,6 +5278,41 @@ order_book wallet_api::get_order_book( const string& base, const string& quote, return( my->_remote_db->get_order_book( base, quote, limit ) ); } +signed_transaction wallet_api::set_contact_information(string account, account_contact_operation::ext data, + bool broadcast) +{ + return my->set_contact_information(account, data, broadcast); +} + +optional wallet_api::get_contact_information(string account) +{ + return my->_custom_operations->get_contact_info(account); +} + +signed_transaction wallet_api::create_htlc_offer(string account, create_htlc_order_operation::ext data, bool broadcast) +{ + return my->create_htlc_offer(account, data, broadcast); +} + +signed_transaction wallet_api::take_htlc_offer(string account, take_htlc_order_operation::ext data, bool broadcast) +{ + return my->take_htlc_offer(account, data, broadcast); +} + +vector wallet_api::get_active_htlc_offers(uint16_t blockchain) +{ + FC_ASSERT(blockchain <= blockchains::ethereum); + + vector results; + auto orders = my->_custom_operations->get_active_htlc_offers(htlc_order_id_type(0), 100); + for(const auto order : orders) + { + if(order.blockchain == static_cast(blockchain)) + results.push_back(order); + } + return results; +} + signed_block_with_info::signed_block_with_info( const signed_block& block ) : signed_block( block ) { diff --git a/programs/witness_node/CMakeLists.txt b/programs/witness_node/CMakeLists.txt index d671a93c17..674aa5a0a3 100644 --- a/programs/witness_node/CMakeLists.txt +++ b/programs/witness_node/CMakeLists.txt @@ -13,7 +13,7 @@ endif() target_link_libraries( witness_node PRIVATE graphene_app graphene_delayed_node graphene_account_history graphene_elasticsearch graphene_market_history graphene_grouped_orders graphene_witness graphene_chain graphene_debug_witness graphene_egenesis_full graphene_snapshot graphene_es_objects - graphene_api_helper_indexes + graphene_api_helper_indexes graphene_custom_operations fc ${CMAKE_DL_LIBS} ${PLATFORM_SPECIFIC_LIBS} ) install( TARGETS diff --git a/programs/witness_node/main.cpp b/programs/witness_node/main.cpp index 400d09f0ff..506cf9146b 100644 --- a/programs/witness_node/main.cpp +++ b/programs/witness_node/main.cpp @@ -34,6 +34,7 @@ #include #include #include +#include #include #include @@ -96,6 +97,7 @@ int main(int argc, char** argv) { auto es_objects_plug = node->register_plugin(); auto grouped_orders_plug = node->register_plugin(); auto api_helper_indexes_plug = node->register_plugin(); + auto custom_operations_plug = node->register_plugin(); // add plugin options to config try diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index d05bf13183..2934476a11 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -10,7 +10,7 @@ file(GLOB UNIT_TESTS "tests/*.cpp") add_executable( chain_test ${COMMON_SOURCES} ${UNIT_TESTS} ) target_link_libraries( chain_test graphene_chain graphene_app graphene_witness graphene_account_history graphene_elasticsearch - graphene_es_objects graphene_egenesis_none graphene_api_helper_indexes + graphene_es_objects graphene_egenesis_none graphene_api_helper_indexes graphene_custom_operations fc graphene_wallet ${PLATFORM_SPECIFIC_LIBS} ) if(MSVC) set_source_files_properties( tests/serialization_tests.cpp PROPERTIES COMPILE_FLAGS "/bigobj" ) @@ -22,6 +22,7 @@ add_executable( performance_test ${COMMON_SOURCES} ${PERFORMANCE_TESTS} ) target_link_libraries( performance_test graphene_chain graphene_app graphene_account_history graphene_elasticsearch graphene_es_objects graphene_egenesis_none graphene_api_helper_indexes + graphene_custom_operations fc ${PLATFORM_SPECIFIC_LIBS} ) file(GLOB BENCH_MARKS "benchmarks/*.cpp") @@ -29,6 +30,7 @@ add_executable( chain_bench ${COMMON_SOURCES} ${BENCH_MARKS} ) target_link_libraries( chain_bench graphene_chain graphene_app graphene_account_history graphene_elasticsearch graphene_es_objects graphene_egenesis_none graphene_api_helper_indexes + graphene_custom_operations fc ${PLATFORM_SPECIFIC_LIBS} ) file(GLOB APP_SOURCES "app/*.cpp") @@ -50,6 +52,15 @@ add_executable( es_test ${COMMON_SOURCES} ${ES_SOURCES} ) target_link_libraries( es_test graphene_chain graphene_app graphene_account_history graphene_elasticsearch graphene_es_objects graphene_egenesis_none graphene_api_helper_indexes + graphene_custom_operations fc ${PLATFORM_SPECIFIC_LIBS} ) - + +file(GLOB CUSTOM_SOURCES "custom_operations/*.cpp") +add_executable( custom_operations_test ${COMMON_SOURCES} ${CUSTOM_SOURCES} ) +target_link_libraries( custom_operations_test + graphene_chain graphene_app graphene_account_history graphene_elasticsearch + graphene_es_objects graphene_egenesis_none graphene_api_helper_indexes + graphene_custom_operations + fc ${PLATFORM_SPECIFIC_LIBS} ) + add_subdirectory( generate_empty_blocks ) diff --git a/tests/cli/main.cpp b/tests/cli/main.cpp index c2c0278a43..6b2e90d18f 100644 --- a/tests/cli/main.cpp +++ b/tests/cli/main.cpp @@ -30,6 +30,7 @@ #include #include #include +#include #include #include @@ -113,6 +114,30 @@ int get_available_port() return ntohs(sin.sin_port); } +boost::filesystem::path create_api_access_file(fc::temp_directory& directory) { + boost::filesystem::path apiaccess_path = boost::filesystem::path{directory.path().generic_string()} / "api-access.json"; + fc::path apiaccess_out = apiaccess_path; + + const string apiaccess_content = R"( + { + "permission_map" : + [ + [ + "*", + { + "password_hash_b64" : "*", + "password_salt_b64" : "*", + "allowed_apis" : ["database_api", "network_broadcast_api", "history_api", "custom_operations_api"] + } + ] + ] + } + )"; + + fc::json::save_to_file(fc::json::from_string(apiaccess_content), apiaccess_out); + return apiaccess_path; +} + /////////// /// @brief Start the application /// @param app_dir the temporary directory to use @@ -125,7 +150,7 @@ std::shared_ptr start_application(fc::temp_directory app1->register_plugin(true); app1->register_plugin< graphene::market_history::market_history_plugin >(true); app1->register_plugin< graphene::witness_plugin::witness_plugin >(true); - app1->register_plugin< graphene::grouped_orders::grouped_orders_plugin>(true); + app1->register_plugin< graphene::custom_operations::custom_operations_plugin>(true); app1->startup_plugins(); boost::program_options::variables_map cfg; #ifdef _WIN32 @@ -138,6 +163,7 @@ std::shared_ptr start_application(fc::temp_directory ); cfg.emplace("genesis-json", boost::program_options::variable_value(create_genesis_file(app_dir), false)); cfg.emplace("seed-nodes", boost::program_options::variable_value(string("[]"), false)); + cfg.emplace("api-access", boost::program_options::variable_value(create_api_access_file(app_dir), false)); app1->initialize(app_dir.path(), cfg); app1->initialize_plugins(cfg); @@ -145,6 +171,7 @@ std::shared_ptr start_application(fc::temp_directory app1->startup(); fc::usleep(fc::milliseconds(500)); + return app1; } @@ -1182,3 +1209,101 @@ BOOST_FIXTURE_TEST_CASE( cli_sign_message, cli_fixture ) BOOST_CHECK( con.wallet_api_ptr->verify_encapsulated_message( encapsulate( msg ) ) ); } FC_LOG_AND_RETHROW() } + +/////////////////// +// Test the contact information by custom operations plugin +/////////////////// +BOOST_FIXTURE_TEST_CASE( account_contact_information, cli_fixture ) +{ + try { + // just to fund nathan + INVOKE(upgrade_nathan_account); + + BOOST_TEST_MESSAGE("Check account information."); + auto account_contact_info = con.wallet_api_ptr->get_contact_information("nathan"); + BOOST_CHECK(!account_contact_info.valid()); // no info yet + + BOOST_TEST_MESSAGE("About to add contact information."); + + account_contact_operation::ext data; + data.name = "Nathan"; + data.email = "nathan@nathan.com"; + data.phone = "2121212121"; + data.address = "Bv DD 22"; + data.company = ""; + data.url = ""; + + signed_transaction custom_tx = con.wallet_api_ptr->set_contact_information("nathan", data, true); + + BOOST_TEST_MESSAGE("The system is generating a block."); + BOOST_CHECK(generate_block(app1)); + + BOOST_TEST_MESSAGE("Check account contact information."); + account_contact_info = con.wallet_api_ptr->get_contact_information("nathan"); + + BOOST_CHECK_EQUAL(account_contact_info->account.instance.value, 17 ); + BOOST_CHECK_EQUAL(*account_contact_info->name, "Nathan"); + BOOST_CHECK_EQUAL(*account_contact_info->email, "nathan@nathan.com"); + BOOST_CHECK_EQUAL(*account_contact_info->phone, "2121212121"); + BOOST_CHECK_EQUAL(*account_contact_info->address, "Bv DD 22"); + BOOST_CHECK_EQUAL(*account_contact_info->company, ""); + BOOST_CHECK_EQUAL(*account_contact_info->url, ""); + + } catch( fc::exception& e ) { + edump((e.to_detail_string())); + throw; + } +} + +/////////////////// +// Test the htlc offer orderbook by custom operations plugin +/////////////////// +BOOST_FIXTURE_TEST_CASE( htlc_orderbook, cli_fixture ) +{ + try { + // create the taker account + INVOKE(create_new_account); + + auto db = app1->chain_database(); + + BOOST_TEST_MESSAGE("Adding an offer."); + + create_htlc_order_operation::ext data_maker; + data_maker.blockchain = blockchains::bitcoin; + data_maker.blockchain_account = "nathan"; + data_maker.bitshares_amount = asset(100); + data_maker.blockchain_asset = "BTC"; + data_maker.blockchain_amount = "2000"; + data_maker.expiration = db->head_block_time() + 7200; + data_maker.tag = "Some text, can be a memo"; + + signed_transaction custom_tx = con.wallet_api_ptr->create_htlc_offer("nathan", data_maker, true); + + BOOST_TEST_MESSAGE("The system is generating a block."); + BOOST_CHECK(generate_block(app1)); + + BOOST_TEST_MESSAGE("Get active htlc offers."); + auto offers = con.wallet_api_ptr->get_active_htlc_offers(blockchains::bitcoin); + if(offers[0].blockchain == blockchains::bitcoin) { + BOOST_CHECK_EQUAL(offers[0].id.instance(), 0); + } + + BOOST_TEST_MESSAGE("Taking the offfer."); + take_htlc_order_operation::ext data_taker; + data_taker.htlc_order_id = offers[0].id; + data_taker.blockchain_account = "nathan"; + + custom_tx = con.wallet_api_ptr->take_htlc_offer("jmjatlanta", data_taker, true); + + BOOST_TEST_MESSAGE("The system is generating a block."); + BOOST_CHECK(generate_block(app1)); + + BOOST_TEST_MESSAGE("Get active htlc offers."); + offers = con.wallet_api_ptr->get_active_htlc_offers(blockchains::bitcoin); + BOOST_CHECK_EQUAL(offers.size(), 0); + + } catch( fc::exception& e ) { + edump((e.to_detail_string())); + throw; + } +} diff --git a/tests/common/database_fixture.cpp b/tests/common/database_fixture.cpp index e25b4f76e0..a4914d8bf7 100644 --- a/tests/common/database_fixture.cpp +++ b/tests/common/database_fixture.cpp @@ -31,6 +31,7 @@ #include #include #include +#include #include #include @@ -285,6 +286,13 @@ database_fixture::database_fixture(const fc::time_point_sec &initial_timestamp) ahiplugin->plugin_startup(); } + if(current_test_name == "custom_operations_account_contact_test" || current_test_name == "custom_operations_htlc_bitshares_eos_test") { + auto custom_operations_plugin = app.register_plugin(); + custom_operations_plugin->plugin_set_app(&app); + custom_operations_plugin->plugin_initialize(options); + custom_operations_plugin->plugin_startup(); + } + options.insert(std::make_pair("bucket-size", boost::program_options::variable_value(string("[15]"),false))); mhplugin->plugin_set_app(&app); mhplugin->plugin_initialize(options); diff --git a/tests/custom_operations/main.cpp b/tests/custom_operations/main.cpp new file mode 100644 index 0000000000..3e910705e2 --- /dev/null +++ b/tests/custom_operations/main.cpp @@ -0,0 +1,469 @@ +/* + * Copyright (c) 2019 oxarbitrage and contributors. + * + * The MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include +#include +#include + +#include + +#include "../common/database_fixture.hpp" + +#define BOOST_TEST_MODULE Custom operations plugin tests +#include + +using namespace graphene::chain; +using namespace graphene::chain::test; +using namespace graphene::app; +using namespace graphene::custom_operations; + +BOOST_FIXTURE_TEST_SUITE( custom_operation_tests, database_fixture ) + +BOOST_AUTO_TEST_CASE(custom_operations_account_contact_test) +{ +try { + ACTORS((nathan)(alice)); + + app.enable_plugin("custom_operations"); + custom_operations_api custom_operations_api(app); + + generate_block(); + fc::usleep(fc::milliseconds(200)); + + enable_fees(); + signed_transaction trx; + set_expiration(db, trx); + + int64_t init_balance(10000 * GRAPHENE_BLOCKCHAIN_PRECISION); + + transfer(committee_account, nathan_id, asset(init_balance)); + transfer(committee_account, alice_id, asset(init_balance)); + + // nathan adds account data via custom operation + { + custom_operation op; + account_contact_operation contact; + account_contact_operation::ext data; + + data.name = "Nathan"; + data.email = "nathan@nathan.com"; + data.phone = "+1 434343434343"; + data.address = ""; + data.company = "Bitshares"; + data.url = "http://nathan.com/"; + + contact.extensions.value = data; + + auto packed = fc::raw::pack(contact); + packed.insert(packed.begin(), types::account_contact); + packed.insert(packed.begin(), 0xFF); + + op.payer = nathan_id; + op.data = packed; + op.fee = db.get_global_properties().parameters.current_fees->calculate_fee(op); + trx.operations.push_back(op); + sign(trx, nathan_private_key); + PUSH_TX(db, trx, ~0); + trx.clear(); + } + + // alice adds account data via custom operation + { + custom_operation op; + account_contact_operation contact; + + account_contact_operation::ext data; + data.name = "Alice"; + data.email = "alice@alice.com"; + data.phone = ""; + data.address = "Some Street 456, Somewhere"; + data.company = ""; + data.url = "http://alice.com/"; + + contact.extensions.value = data; + + auto packed = fc::raw::pack(contact); + packed.insert(packed.begin(), types::account_contact); + packed.insert(packed.begin(), 0xFF); + + op.payer = alice_id; + op.data = packed; + op.fee = db.get_global_properties().parameters.current_fees->calculate_fee(op); + trx.operations.push_back(op); + sign(trx, alice_private_key); + PUSH_TX(db, trx, ~0); + trx.clear(); + } + + generate_block(); + fc::usleep(fc::milliseconds(200)); + + // check nathan account data with the api + account_contact_object contact_results_nathan = *custom_operations_api.get_contact_info("nathan"); + BOOST_CHECK_EQUAL(contact_results_nathan.account.instance.value, 16 ); + BOOST_CHECK_EQUAL(*contact_results_nathan.name, "Nathan"); + BOOST_CHECK_EQUAL(*contact_results_nathan.email, "nathan@nathan.com"); + BOOST_CHECK_EQUAL(*contact_results_nathan.phone, "+1 434343434343"); + BOOST_CHECK_EQUAL(*contact_results_nathan.address, ""); + BOOST_CHECK_EQUAL(*contact_results_nathan.company, "Bitshares"); + BOOST_CHECK_EQUAL(*contact_results_nathan.url, "http://nathan.com/"); + + // check alice account data with the api + account_contact_object contact_results_alice = *custom_operations_api.get_contact_info("alice"); + BOOST_CHECK_EQUAL(contact_results_alice.account.instance.value, 17 ); + BOOST_CHECK_EQUAL(*contact_results_alice.name, "Alice"); + BOOST_CHECK_EQUAL(*contact_results_alice.email, "alice@alice.com"); + BOOST_CHECK_EQUAL(*contact_results_alice.phone, ""); + BOOST_CHECK_EQUAL(*contact_results_alice.address, "Some Street 456, Somewhere"); + BOOST_CHECK_EQUAL(*contact_results_alice.company, ""); + BOOST_CHECK_EQUAL(*contact_results_alice.url, "http://alice.com/"); + + // alice update her data + { + custom_operation op; + account_contact_operation contact; + + account_contact_operation::ext data; + data.name = "Alice Smith"; + data.email = "alicesmith@alice.com"; + data.phone = "+1 1111 11 1111"; + data.address = "Some Street 456, Somewhere"; + data.company = ""; + data.url = "http://alice.com/"; + + contact.extensions.value = data; + + auto packed = fc::raw::pack(contact); + packed.insert(packed.begin(), types::account_contact); + packed.insert(packed.begin(), 0xFF); + + op.payer = alice_id; + op.data = packed; + op.fee = db.get_global_properties().parameters.current_fees->calculate_fee(op); + trx.operations.push_back(op); + sign(trx, alice_private_key); + PUSH_TX(db, trx, ~0); + trx.clear(); + } + + generate_block(); + fc::usleep(fc::milliseconds(200)); + + // check alice account updates with the api + contact_results_alice = *custom_operations_api.get_contact_info("alice"); + BOOST_CHECK_EQUAL(contact_results_alice.account.instance.value, 17 ); + BOOST_CHECK_EQUAL(*contact_results_alice.name, "Alice Smith"); + BOOST_CHECK_EQUAL(*contact_results_alice.email, "alicesmith@alice.com"); + BOOST_CHECK_EQUAL(*contact_results_alice.phone, "+1 1111 11 1111"); + BOOST_CHECK_EQUAL(*contact_results_alice.address, "Some Street 456, Somewhere"); + BOOST_CHECK_EQUAL(*contact_results_alice.company, ""); + BOOST_CHECK_EQUAL(*contact_results_alice.url, "http://alice.com/"); + + // alice try to update nathan data + { + custom_operation op; + account_contact_operation contact; + + account_contact_operation::ext data; + data.name = "Not my account"; + data.phone = "Fake phone"; + data.email = "Fake email"; + data.address = "Fake address"; + data.company = "Fake company"; + data.url = "http://fake.com"; + + contact.extensions.value = data; + + auto packed = fc::raw::pack(contact); + packed.insert(packed.begin(), types::account_contact); + packed.insert(packed.begin(), 0xFF); + + op.payer = alice_id; + op.data = packed; + op.fee = db.get_global_properties().parameters.current_fees->calculate_fee(op); + trx.operations.push_back(op); + sign(trx, alice_private_key); + PUSH_TX(db, trx, ~0); + trx.clear(); + } + generate_block(); + fc::usleep(fc::milliseconds(200)); + + // operation will pass but data will be unchanged, exception was produced in plug in + contact_results_nathan = *custom_operations_api.get_contact_info("nathan"); + BOOST_CHECK(contact_results_nathan.account.instance.value == 16 ); + BOOST_CHECK(*contact_results_nathan.name != "Not my account"); + BOOST_CHECK(*contact_results_nathan.phone != "Fake phone"); + BOOST_CHECK(*contact_results_nathan.email != "Fake email"); +} +catch (fc::exception &e) { + edump((e.to_detail_string())); + throw; +} } + +BOOST_AUTO_TEST_CASE(custom_operations_htlc_bitshares_eos_test) +{ try { + + ACTORS((nathan)(alice)(bob)(carol)); + + app.enable_plugin("custom_operations"); + custom_operations_api custom_operations_api(app); + + generate_block(); + fc::usleep(fc::milliseconds(200)); + + enable_fees(); + signed_transaction trx; + set_expiration(db, trx); + + int64_t init_balance(10000 * GRAPHENE_BLOCKCHAIN_PRECISION); + + transfer(committee_account, nathan_id, asset(init_balance)); + transfer(committee_account, alice_id, asset(init_balance)); + transfer(committee_account, bob_id, asset(init_balance)); + transfer(committee_account, carol_id, asset(init_balance)); + + enable_fees(); + + // alice creates an order + { + custom_operation op; + create_htlc_order_operation htlc; + + create_htlc_order_operation::ext data; + data.blockchain = blockchains::eos; + data.blockchain_account = "alice"; + data.bitshares_amount = asset(10); + data.blockchain_asset = "EOS"; + data.blockchain_amount = "10"; + data.expiration = db.head_block_time() + fc::seconds(7200); + + htlc.extensions.value = data; + + auto packed = fc::raw::pack(htlc); + packed.insert(packed.begin(), types::create_htlc); + packed.insert(packed.begin(), 0xFF); + + op.payer = alice_id; + op.data = packed; + op.fee = db.get_global_properties().parameters.current_fees->calculate_fee(op); + trx.operations.push_back(op); + sign(trx, alice_private_key); + PUSH_TX(db, trx, ~0); + trx.clear(); + } + + // bob creates an order + { + custom_operation op; + create_htlc_order_operation htlc; + + create_htlc_order_operation::ext data; + data.blockchain = blockchains::eos; + data.blockchain_account = "bob"; + data.bitshares_amount = asset(100); + data.blockchain_asset = "EOS"; + data.blockchain_amount = "100"; + data.expiration = db.head_block_time() + fc::seconds(7200); + data.tag = "Some text, can be a memo"; + + htlc.extensions.value = data; + + auto packed = fc::raw::pack(htlc); + packed.insert(packed.begin(), types::create_htlc); + packed.insert(packed.begin(), 0xFF); + + op.payer = bob_id; + op.data = packed; + op.fee = db.get_global_properties().parameters.current_fees->calculate_fee(op); + trx.operations.push_back(op); + sign(trx, bob_private_key); + PUSH_TX(db, trx, ~0); + trx.clear(); + } + + // carol creates an order with missing information (blockchain_amount), will fail in the validator + { + custom_operation op; + create_htlc_order_operation htlc; + + create_htlc_order_operation::ext data; + data.blockchain = blockchains::eos; + data.blockchain_account = "carol"; + data.bitshares_amount = asset(10); + data.blockchain_asset = "EOS"; + data.expiration = db.head_block_time() + fc::seconds(7200); + + htlc.extensions.value = data; + + auto packed = fc::raw::pack(htlc); + packed.insert(packed.begin(), types::create_htlc); + packed.insert(packed.begin(), 0xFF); + + op.payer = carol_id; + op.data = packed; + op.fee = db.get_global_properties().parameters.current_fees->calculate_fee(op); + trx.operations.push_back(op); + sign(trx, carol_private_key); + PUSH_TX(db, trx, ~0); + trx.clear(); + } + + generate_block(); + fc::usleep(fc::milliseconds(200)); + + // test the get_account_htlc_offers api call for alice + vector htlc_offers_results_alice = custom_operations_api.get_account_htlc_offers("alice", + htlc_order_id_type(0), 100); + BOOST_CHECK_EQUAL(htlc_offers_results_alice.size(), 1); + BOOST_CHECK_EQUAL(htlc_offers_results_alice[0].id.instance(), 0); + BOOST_CHECK_EQUAL(htlc_offers_results_alice[0].bitshares_account.instance.value, 17); + BOOST_CHECK_EQUAL(htlc_offers_results_alice[0].blockchain_account, "alice" ); + BOOST_CHECK_EQUAL(htlc_offers_results_alice[0].bitshares_amount.asset_id.instance.value, 0); + BOOST_CHECK_EQUAL(htlc_offers_results_alice[0].bitshares_amount.amount.value, 10); + BOOST_CHECK_EQUAL(htlc_offers_results_alice[0].blockchain_asset, "EOS"); + BOOST_CHECK_EQUAL(htlc_offers_results_alice[0].blockchain_amount, "10"); + BOOST_CHECK(htlc_offers_results_alice[0].active); + + // test the get_htlc_offer api call with alice order + auto htlc_offer = custom_operations_api.get_htlc_offer(htlc_order_id_type(0)); + BOOST_CHECK_EQUAL(htlc_offer->id.instance(), 0); + BOOST_CHECK_EQUAL(htlc_offer->bitshares_account.instance.value, 17); + BOOST_CHECK_EQUAL(htlc_offer->blockchain_account, "alice" ); + BOOST_CHECK_EQUAL(htlc_offer->bitshares_amount.asset_id.instance.value, 0); + BOOST_CHECK_EQUAL(htlc_offer->bitshares_amount.amount.value, 10); + BOOST_CHECK_EQUAL(htlc_offer->blockchain_asset, "EOS"); + BOOST_CHECK_EQUAL(htlc_offer->blockchain_amount, "10"); + BOOST_CHECK(htlc_offer->active); + + // test the get_account_htlc_offers api call for bob + vector htlc_offers_results_bob = custom_operations_api.get_account_htlc_offers("bob", + htlc_order_id_type(0), 100); + + BOOST_CHECK_EQUAL(htlc_offers_results_bob.size(), 1); + BOOST_CHECK_EQUAL(htlc_offers_results_bob[0].id.instance(), 1); + BOOST_CHECK_EQUAL(htlc_offers_results_bob[0].bitshares_account.instance.value, 18); + BOOST_CHECK_EQUAL(htlc_offers_results_bob[0].blockchain_account, "bob" ); + BOOST_CHECK_EQUAL(htlc_offers_results_bob[0].bitshares_amount.asset_id.instance.value, 0); + BOOST_CHECK_EQUAL(htlc_offers_results_bob[0].bitshares_amount.amount.value, 100); + BOOST_CHECK_EQUAL(htlc_offers_results_bob[0].blockchain_asset, "EOS"); + BOOST_CHECK_EQUAL(htlc_offers_results_bob[0].blockchain_amount, "100"); + BOOST_CHECK(htlc_offers_results_bob[0].active); + if(htlc_offers_results_bob[0].tag.valid()) + BOOST_CHECK_EQUAL(*htlc_offers_results_bob[0].tag, "Some text, can be a memo"); + + // get all active offers + vector htlc_offers_results_active = custom_operations_api.get_active_htlc_offers( + htlc_order_id_type(0), 100); + + BOOST_CHECK_EQUAL(htlc_offers_results_active.size(), 2); + BOOST_CHECK_EQUAL(htlc_offers_results_active[0].id.instance(), 0); + BOOST_CHECK_EQUAL(htlc_offers_results_active[0].bitshares_account.instance.value, 17); + BOOST_CHECK_EQUAL(htlc_offers_results_active[0].blockchain_account, "alice" ); + BOOST_CHECK_EQUAL(htlc_offers_results_active[0].bitshares_amount.asset_id.instance.value, 0); + BOOST_CHECK_EQUAL(htlc_offers_results_active[0].bitshares_amount.amount.value, 10); + BOOST_CHECK_EQUAL(htlc_offers_results_active[0].blockchain_asset, "EOS"); + BOOST_CHECK_EQUAL(htlc_offers_results_active[0].blockchain_amount, "10"); + BOOST_CHECK(htlc_offers_results_active[0].active); + + BOOST_CHECK_EQUAL(htlc_offers_results_active[1].id.instance(), 1); + BOOST_CHECK_EQUAL(htlc_offers_results_active[1].bitshares_account.instance.value, 18); + BOOST_CHECK_EQUAL(htlc_offers_results_active[1].blockchain_account, "bob" ); + BOOST_CHECK_EQUAL(htlc_offers_results_active[1].bitshares_amount.asset_id.instance.value, 0); + BOOST_CHECK_EQUAL(htlc_offers_results_active[1].bitshares_amount.amount.value, 100); + BOOST_CHECK_EQUAL(htlc_offers_results_active[1].blockchain_asset, "EOS"); + BOOST_CHECK_EQUAL(htlc_offers_results_active[1].blockchain_amount, "100"); + BOOST_CHECK(htlc_offers_results_active[1].active); + if(htlc_offers_results_active[0].tag.valid()) + BOOST_CHECK_EQUAL(*htlc_offers_results_active[0].tag, "Some text, can be a memo"); + + // nathan takes alice order + { + custom_operation op; + take_htlc_order_operation htlc; + + take_htlc_order_operation::ext data; + data.htlc_order_id = htlc_offers_results_alice[0].id; + data.blockchain_account = "nathan"; + + htlc.extensions.value = data; + + auto packed = fc::raw::pack(htlc); + packed.insert(packed.begin(), types::take_htlc); + packed.insert(packed.begin(), 0xFF); + + op.payer = nathan_id; + op.data = packed; + op.fee = db.get_global_properties().parameters.current_fees->calculate_fee(op); + trx.operations.push_back(op); + sign(trx, nathan_private_key); + PUSH_TX(db, trx, ~0); + trx.clear(); + } + generate_block(); + fc::usleep(fc::milliseconds(200)); + + // check the taken object + htlc_offer = custom_operations_api.get_htlc_offer(htlc_order_id_type(0)); + BOOST_CHECK_EQUAL(htlc_offer->id.instance(), 0); + BOOST_CHECK_EQUAL(htlc_offer->bitshares_account.instance.value, 17); + BOOST_CHECK_EQUAL(htlc_offer->blockchain_account, "alice" ); + BOOST_CHECK_EQUAL(htlc_offer->bitshares_amount.asset_id.instance.value, 0); + BOOST_CHECK_EQUAL(htlc_offer->bitshares_amount.amount.value, 10); + BOOST_CHECK_EQUAL(htlc_offer->blockchain_asset, "EOS"); + BOOST_CHECK_EQUAL(htlc_offer->blockchain_amount, "10"); + BOOST_CHECK(!htlc_offer->active); + BOOST_CHECK_EQUAL(htlc_offer->taker_bitshares_account->instance.value, 16); + BOOST_CHECK_EQUAL(*htlc_offer->taker_blockchain_account, "nathan"); + + // alice order was taken, bob order still up for get_active_htlc_offers + htlc_offers_results_active = custom_operations_api.get_active_htlc_offers(htlc_order_id_type(0), 100); + BOOST_CHECK_EQUAL(htlc_offers_results_active.size(), 1); + + BOOST_CHECK_EQUAL(htlc_offers_results_active[0].id.instance(), 1); + BOOST_CHECK_EQUAL(htlc_offers_results_active[0].bitshares_account.instance.value, 18); + BOOST_CHECK_EQUAL(htlc_offers_results_active[0].blockchain_account, "bob"); + BOOST_CHECK_EQUAL(htlc_offers_results_active[0].bitshares_amount.asset_id.instance.value, 0); + BOOST_CHECK_EQUAL(htlc_offers_results_active[0].bitshares_amount.amount.value, 100); + BOOST_CHECK_EQUAL(htlc_offers_results_active[0].blockchain_asset, "EOS"); + BOOST_CHECK_EQUAL(htlc_offers_results_active[0].blockchain_amount, "100"); + BOOST_CHECK(htlc_offers_results_active[0].active); + if(htlc_offers_results_active[0].tag.valid()) + BOOST_CHECK_EQUAL(*htlc_offers_results_active[0].tag, "Some text, can be a memo"); + + // make bob order expire + generate_blocks(7201); + fc::usleep(fc::milliseconds(200)); + + htlc_offers_results_active = custom_operations_api.get_active_htlc_offers(htlc_order_id_type(0), 100); + BOOST_CHECK_EQUAL(htlc_offers_results_active.size(), 0); +} + +catch (fc::exception &e) { + edump((e.to_detail_string())); + throw; +} } + + +BOOST_AUTO_TEST_SUITE_END() From 7e599107d8ee82c4f438a2aef2b349c6ed7507eb Mon Sep 17 00:00:00 2001 From: Alfredo Date: Fri, 23 Aug 2019 14:41:25 -0300 Subject: [PATCH 02/18] implement storage_map and account_list --- libraries/app/api.cpp | 10 + libraries/app/include/graphene/app/api.hpp | 9 + .../custom_operations/custom_evaluators.cpp | 97 +++- .../custom_operations/custom_operations.cpp | 13 + .../custom_operations_plugin.cpp | 1 + .../custom_operations/custom_evaluators.hpp | 2 + .../custom_operations/custom_objects.hpp | 32 +- .../custom_operations/custom_operations.hpp | 38 ++ .../custom_operations_plugin.hpp | 6 +- tests/common/database_fixture.cpp | 4 +- tests/custom_operations/main.cpp | 439 ++++++++++++++++++ 11 files changed, 630 insertions(+), 21 deletions(-) diff --git a/libraries/app/api.cpp b/libraries/app/api.cpp index a6aa07f126..83edac5c6f 100644 --- a/libraries/app/api.cpp +++ b/libraries/app/api.cpp @@ -738,4 +738,14 @@ namespace graphene { namespace app { return optional(); } + optional custom_operations_api::get_storage_info(std::string account_id_or_name)const + { + const auto account_id = database_api.get_account_id_from_string(account_id_or_name); + auto &index = _app.chain_database()->get_index_type().indices().get(); + auto itr = index.find(account_id); + if(itr != index.end()) + return *itr; + return optional(); + } + } } // graphene::app diff --git a/libraries/app/include/graphene/app/api.hpp b/libraries/app/include/graphene/app/api.hpp index f58582da4f..4650773d8d 100644 --- a/libraries/app/include/graphene/app/api.hpp +++ b/libraries/app/include/graphene/app/api.hpp @@ -571,6 +571,15 @@ namespace graphene { namespace app { */ optional get_htlc_offer(htlc_order_id_type id)const; + /** + * @breif Get storage information of an account + * + * @param account Account name to get info from + * + * @return The storage information of the account or empty + */ + optional get_storage_info(std::string account)const; + private: application& _app; graphene::app::database_api database_api; diff --git a/libraries/plugins/custom_operations/custom_evaluators.cpp b/libraries/plugins/custom_operations/custom_evaluators.cpp index 1ce465c298..e409aa2369 100644 --- a/libraries/plugins/custom_operations/custom_evaluators.cpp +++ b/libraries/plugins/custom_operations/custom_evaluators.cpp @@ -35,6 +35,17 @@ custom_generic_evaluator::custom_generic_evaluator(database& db, const account_i _account = account; } +void fill_contact_object(account_contact_object& aco, account_id_type account, const account_contact_operation& op) +{ + aco.account = account; + if(op.extensions.value.name.valid()) aco.name = *op.extensions.value.name; + if(op.extensions.value.email.valid()) aco.email = *op.extensions.value.email; + if(op.extensions.value.phone.valid()) aco.phone = *op.extensions.value.phone; + if(op.extensions.value.address.valid()) aco.address = *op.extensions.value.address; + if(op.extensions.value.company.valid()) aco.company = *op.extensions.value.company; + if(op.extensions.value.url.valid()) aco.url = *op.extensions.value.url; +} + object_id_type custom_generic_evaluator::do_apply(const account_contact_operation& op) { auto &index = _db->get_index_type().indices().get(); @@ -43,26 +54,14 @@ object_id_type custom_generic_evaluator::do_apply(const account_contact_operatio if( itr != index.end() ) { _db->modify( *itr, [&op, this]( account_contact_object& aco ){ - aco.account = _account; - if(op.extensions.value.name.valid()) aco.name = *op.extensions.value.name; - if(op.extensions.value.email.valid()) aco.email = *op.extensions.value.email; - if(op.extensions.value.phone.valid()) aco.phone = *op.extensions.value.phone; - if(op.extensions.value.address.valid()) aco.address = *op.extensions.value.address; - if(op.extensions.value.company.valid()) aco.company = *op.extensions.value.company; - if(op.extensions.value.url.valid()) aco.url = *op.extensions.value.url; + fill_contact_object(aco, _account, op); }); return itr->id; } else { auto created = _db->create( [&op, this]( account_contact_object& aco ) { - aco.account = _account; - if(op.extensions.value.name.valid()) aco.name = *op.extensions.value.name; - if(op.extensions.value.email.valid()) aco.email = *op.extensions.value.email; - if(op.extensions.value.phone.valid()) aco.phone = *op.extensions.value.phone; - if(op.extensions.value.address.valid()) aco.address = *op.extensions.value.address; - if(op.extensions.value.company.valid()) aco.company = *op.extensions.value.company; - if(op.extensions.value.url.valid()) aco.url = *op.extensions.value.url; + fill_contact_object(aco, _account, op); }); return created.id; } @@ -114,4 +113,74 @@ object_id_type custom_generic_evaluator::do_apply(const take_htlc_order_operatio return htlc_order_id; } +void fill_storage_map(account_storage_object& aso, account_id_type account, const account_store_data& op) +{ + aso.account = account; + if(op.extensions.value.pairs.valid()) + { + for(auto const& row: *op.extensions.value.pairs) { + if (op.extensions.value.remove.valid() && *op.extensions.value.remove) + aso.storage_map.erase(row.first); + else + aso.storage_map[row.first] = row.second; + } + } +} + +object_id_type custom_generic_evaluator::do_apply(const account_store_data& op) +{ + auto &index = _db->get_index_type().indices().get(); + + auto itr = index.find(_account); + if( itr != index.end() ) + { + _db->modify( *itr, [&op, this]( account_storage_object& aso ) { + fill_storage_map(aso, _account, op); + }); + return itr->id; + } + else + { + auto created = _db->create( [&op, this]( account_storage_object& aso ) { + fill_storage_map(aso, _account, op); + }); + return created.id; + } +} + +void fill_account_list(account_storage_object& aso, account_id_type account, const account_list_data& op) +{ + aso.account = account; + if(op.extensions.value.accounts.valid()) + { + for(auto const& account: *op.extensions.value.accounts) { + if (op.extensions.value.remove.valid() && *op.extensions.value.remove) + aso.account_list.erase(account); + else + aso.account_list.insert(account); + } + } +} + +object_id_type custom_generic_evaluator::do_apply(const account_list_data& op) +{ + auto &index = _db->get_index_type().indices().get(); + + auto itr = index.find(_account); + if( itr != index.end() ) + { + _db->modify( *itr, [&op, this]( account_storage_object& aso ) { + fill_account_list(aso, _account, op); + }); + return itr->id; + } + else + { + auto created = _db->create( [&op, this]( account_storage_object& aso ) { + fill_account_list(aso, _account, op); + }); + return created.id; + } +} + } } diff --git a/libraries/plugins/custom_operations/custom_operations.cpp b/libraries/plugins/custom_operations/custom_operations.cpp index 673cb82d3b..54176a295d 100644 --- a/libraries/plugins/custom_operations/custom_operations.cpp +++ b/libraries/plugins/custom_operations/custom_operations.cpp @@ -46,8 +46,21 @@ void take_htlc_order_operation::validate()const FC_ASSERT(extensions.value.htlc_order_id.valid()); } +void account_store_data::validate()const +{ + FC_ASSERT(extensions.value.pairs.valid()); + FC_ASSERT(extensions.value.pairs->size() <= 10); +} +void account_list_data::validate()const +{ + FC_ASSERT(extensions.value.accounts.valid()); + FC_ASSERT(extensions.value.accounts->size() <= 10); +} + } } //graphene::custom_operations GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::custom_operations::account_contact_operation ) GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::custom_operations::create_htlc_order_operation ) GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::custom_operations::take_htlc_order_operation ) +GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::custom_operations::account_store_data ) +GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::custom_operations::account_list_data ) diff --git a/libraries/plugins/custom_operations/custom_operations_plugin.cpp b/libraries/plugins/custom_operations/custom_operations_plugin.cpp index a8162d928d..9c6d3e169f 100644 --- a/libraries/plugins/custom_operations/custom_operations_plugin.cpp +++ b/libraries/plugins/custom_operations/custom_operations_plugin.cpp @@ -115,6 +115,7 @@ void custom_operations_plugin::plugin_initialize(const boost::program_options::v { database().add_index< primary_index< account_contact_index > >(); database().add_index< primary_index< htlc_orderbook_index > >(); + database().add_index< primary_index< account_storage_index > >(); database().applied_block.connect( [this]( const signed_block& b) { my->onBlock(b); diff --git a/libraries/plugins/custom_operations/include/graphene/custom_operations/custom_evaluators.hpp b/libraries/plugins/custom_operations/include/graphene/custom_operations/custom_evaluators.hpp index 926df77eb8..7f11a967c2 100644 --- a/libraries/plugins/custom_operations/include/graphene/custom_operations/custom_evaluators.hpp +++ b/libraries/plugins/custom_operations/include/graphene/custom_operations/custom_evaluators.hpp @@ -37,6 +37,8 @@ class custom_generic_evaluator object_id_type do_apply(const account_contact_operation& o); object_id_type do_apply(const create_htlc_order_operation& o); object_id_type do_apply(const take_htlc_order_operation& o); + object_id_type do_apply(const account_store_data& o); + object_id_type do_apply(const account_list_data& o); }; } } diff --git a/libraries/plugins/custom_operations/include/graphene/custom_operations/custom_objects.hpp b/libraries/plugins/custom_operations/include/graphene/custom_operations/custom_objects.hpp index 602a18f1c2..c92cda33d9 100644 --- a/libraries/plugins/custom_operations/include/graphene/custom_operations/custom_objects.hpp +++ b/libraries/plugins/custom_operations/include/graphene/custom_operations/custom_objects.hpp @@ -37,7 +37,9 @@ using namespace chain; enum types { account_contact = 0, create_htlc = 1, - take_htlc = 2 + take_htlc = 2, + account_store = 3, + account_list = 4 }; enum blockchains { eos = 0, @@ -83,6 +85,16 @@ struct htlc_order_object : public abstract_object optional close_time; }; +struct account_storage_object : public abstract_object +{ + static const uint8_t space_id = CUSTOM_OPERATIONS_SPACE_ID; + static const uint8_t type_id = account_store; + + account_id_type account; + flat_map storage_map; + flat_set account_list; +}; + struct by_custom_id; struct by_custom_account; typedef multi_index_container< @@ -120,12 +132,23 @@ typedef multi_index_container< typedef generic_index htlc_orderbook_index; +typedef multi_index_container< + account_storage_object, + indexed_by< + ordered_non_unique< tag, member< object, object_id_type, &object::id > >, + ordered_unique< tag, + member< account_storage_object, account_id_type, &account_storage_object::account > > + > +> account_storage_multi_index_type; + +typedef generic_index account_storage_index; + using account_contact_id_type = object_id; using htlc_order_id_type = object_id; +using account_storage_id_type = object_id; } } //graphene::custom_operations - FC_REFLECT_DERIVED( graphene::custom_operations::account_contact_object, (graphene::db::object), (account)(name)(email)(phone)(address)(company)(url)) FC_REFLECT_DERIVED( graphene::custom_operations::htlc_order_object, (graphene::db::object), @@ -133,5 +156,8 @@ FC_REFLECT_DERIVED( graphene::custom_operations::htlc_order_object, (graphene::d (blockchain_amount)(expiration)(order_time)(active) (blockchain_asset_precision)(token_contract)(tag)(taker_bitshares_account) (taker_blockchain_account)(close_time)) -FC_REFLECT_ENUM( graphene::custom_operations::types, (account_contact)(create_htlc)(take_htlc) ) +FC_REFLECT_DERIVED( graphene::custom_operations::account_storage_object, (graphene::db::object), + (account)(storage_map)(account_list)) +FC_REFLECT_ENUM( graphene::custom_operations::types, (account_contact)(create_htlc)(take_htlc)(account_store) + (account_list)) FC_REFLECT_ENUM( graphene::custom_operations::blockchains, (eos)(bitcoin)(ripple)(ethereum) ) diff --git a/libraries/plugins/custom_operations/include/graphene/custom_operations/custom_operations.hpp b/libraries/plugins/custom_operations/include/graphene/custom_operations/custom_operations.hpp index c6c1a4916a..1a9155ff7e 100644 --- a/libraries/plugins/custom_operations/include/graphene/custom_operations/custom_operations.hpp +++ b/libraries/plugins/custom_operations/include/graphene/custom_operations/custom_operations.hpp @@ -82,6 +82,33 @@ struct take_htlc_order_operation : chain::base_operation void validate()const; }; +struct account_store_data : chain::base_operation +{ + struct ext + { + optional remove; + optional> pairs; + }; + + graphene::protocol::extension extensions; + + void validate()const; +}; + +struct account_list_data : chain::base_operation +{ + struct ext + { + optional remove; + optional> accounts; + }; + + graphene::protocol::extension extensions; + + void validate()const; +}; + + } } //graphene::custom_operations FC_REFLECT( graphene::custom_operations::account_contact_operation::ext, (name)(email)(phone)(address)(company)(url) ) @@ -98,6 +125,17 @@ FC_REFLECT( graphene::custom_operations::take_htlc_order_operation::ext, (htlc_o FC_REFLECT_TYPENAME( graphene::protocol::extension ) FC_REFLECT( graphene::custom_operations::take_htlc_order_operation, (extensions) ) +FC_REFLECT( graphene::custom_operations::account_store_data::ext, (pairs)(remove) ) +FC_REFLECT_TYPENAME( graphene::protocol::extension ) +FC_REFLECT( graphene::custom_operations::account_store_data, (extensions) ) + +FC_REFLECT( graphene::custom_operations::account_list_data::ext, (accounts)(remove) ) +FC_REFLECT_TYPENAME( graphene::protocol::extension ) +FC_REFLECT( graphene::custom_operations::account_list_data, (extensions) ) + GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::custom_operations::account_contact_operation ) GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::custom_operations::create_htlc_order_operation ) GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::custom_operations::take_htlc_order_operation ) +GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::custom_operations::account_store_data ) +GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::custom_operations::account_list_data ) + diff --git a/libraries/plugins/custom_operations/include/graphene/custom_operations/custom_operations_plugin.hpp b/libraries/plugins/custom_operations/include/graphene/custom_operations/custom_operations_plugin.hpp index cec86e6611..b8612215b2 100644 --- a/libraries/plugins/custom_operations/include/graphene/custom_operations/custom_operations_plugin.hpp +++ b/libraries/plugins/custom_operations/include/graphene/custom_operations/custom_operations_plugin.hpp @@ -56,11 +56,12 @@ class custom_operations_plugin : public graphene::app::plugin std::unique_ptr my; }; - typedef fc::static_variant< account_contact_operation, create_htlc_order_operation, - take_htlc_order_operation + take_htlc_order_operation, + account_store_data, + account_list_data > custom_plugin_operation; struct custom_operation_wrapper { @@ -84,7 +85,6 @@ struct custom_op_visitor } }; - } } //graphene::custom_operations FC_REFLECT_TYPENAME( graphene::custom_operations::custom_plugin_operation ) diff --git a/tests/common/database_fixture.cpp b/tests/common/database_fixture.cpp index a4914d8bf7..f792a986f6 100644 --- a/tests/common/database_fixture.cpp +++ b/tests/common/database_fixture.cpp @@ -286,7 +286,9 @@ database_fixture::database_fixture(const fc::time_point_sec &initial_timestamp) ahiplugin->plugin_startup(); } - if(current_test_name == "custom_operations_account_contact_test" || current_test_name == "custom_operations_htlc_bitshares_eos_test") { + if(current_test_name == "custom_operations_account_contact_test" || + current_test_name == "custom_operations_htlc_bitshares_eos_test" || + current_test_name == "custom_operations_account_storage_test") { auto custom_operations_plugin = app.register_plugin(); custom_operations_plugin->plugin_set_app(&app); custom_operations_plugin->plugin_initialize(options); diff --git a/tests/custom_operations/main.cpp b/tests/custom_operations/main.cpp index 3e910705e2..e2b5606d8a 100644 --- a/tests/custom_operations/main.cpp +++ b/tests/custom_operations/main.cpp @@ -465,5 +465,444 @@ catch (fc::exception &e) { throw; } } +BOOST_AUTO_TEST_CASE(custom_operations_account_storage_test) +{ +try { + ACTORS((nathan)(alice)(robert)(patty)); + + app.enable_plugin("custom_operations"); + custom_operations_api custom_operations_api(app); + + generate_block(); + fc::usleep(fc::milliseconds(200)); + + enable_fees(); + signed_transaction trx; + set_expiration(db, trx); + + int64_t init_balance(10000 * GRAPHENE_BLOCKCHAIN_PRECISION); + + transfer(committee_account, nathan_id, asset(init_balance)); + transfer(committee_account, alice_id, asset(init_balance)); + + // nathan adds arbitrary account data via custom operation, simulating some dapp settings in this case + { + custom_operation op; + account_store_data store; + account_store_data::ext data; + + flat_map pairs; + pairs["language"] = "en"; + pairs["image_url"] = "http://some.image.url/img.jpg"; + + store.extensions.value.pairs = pairs; + + auto packed = fc::raw::pack(store); + packed.insert(packed.begin(), types::account_store); + packed.insert(packed.begin(), 0xFF); + + op.payer = nathan_id; + op.data = packed; + op.fee = db.get_global_properties().parameters.current_fees->calculate_fee(op); + trx.operations.push_back(op); + sign(trx, nathan_private_key); + PUSH_TX(db, trx, ~0); + trx.clear(); + } + + generate_block(); + fc::usleep(fc::milliseconds(200)); + + // check nathan stored data with the api + account_storage_object storage_results_nathan = *custom_operations_api.get_storage_info("nathan"); + + BOOST_CHECK_EQUAL(storage_results_nathan.account.instance.value, 16 ); + auto row1 = storage_results_nathan.storage_map.find("language"); + auto row2 = storage_results_nathan.storage_map.find("image_url"); + + BOOST_CHECK_EQUAL(row1->first, "language"); + BOOST_CHECK_EQUAL(row1->second, "en"); + BOOST_CHECK_EQUAL(row2->first, "image_url"); + BOOST_CHECK_EQUAL(row2->second, "http://some.image.url/img.jpg"); + + // add accounts to account list storage + { + custom_operation op; + account_list_data list; + account_list_data::ext data; + + flat_set accounts; + accounts.insert(alice_id); + accounts.insert(robert_id); + + list.extensions.value.accounts = accounts; + + auto packed = fc::raw::pack(list); + packed.insert(packed.begin(), types::account_list); + packed.insert(packed.begin(), 0xFF); + + op.payer = nathan_id; + op.data = packed; + op.fee = db.get_global_properties().parameters.current_fees->calculate_fee(op); + trx.operations.push_back(op); + sign(trx, nathan_private_key); + PUSH_TX(db, trx, ~0); + trx.clear(); + } + + generate_block(); + fc::usleep(fc::milliseconds(200)); + + // get the account list for nathan, check alice and robert are there + auto account_list_nathan = *custom_operations_api.get_storage_info("nathan"); + + BOOST_CHECK_EQUAL(account_list_nathan.account.instance.value, 16 ); + BOOST_CHECK_EQUAL(account_list_nathan.account_list.size(), 2 ); + auto itr = account_list_nathan.account_list.begin(); + BOOST_CHECK_EQUAL(itr->instance.value, alice_id.instance.value); + ++itr; + BOOST_CHECK_EQUAL(itr->instance.value, robert_id.instance.value); + + // add a value into account list already there + { + custom_operation op; + account_list_data list; + account_list_data::ext data; + + flat_set accounts; + accounts.insert(alice_id); + + list.extensions.value.accounts = accounts; + + auto packed = fc::raw::pack(list); + packed.insert(packed.begin(), types::account_list); + packed.insert(packed.begin(), 0xFF); + + op.payer = nathan_id; + op.data = packed; + op.fee = db.get_global_properties().parameters.current_fees->calculate_fee(op); + trx.operations.push_back(op); + sign(trx, nathan_private_key); + PUSH_TX(db, trx, ~0); + trx.clear(); + } + + generate_block(); + fc::usleep(fc::milliseconds(200)); + + // nothing changes + account_list_nathan = *custom_operations_api.get_storage_info("nathan"); + BOOST_CHECK_EQUAL(account_list_nathan.account.instance.value, 16 ); + BOOST_CHECK_EQUAL(account_list_nathan.account_list.size(), 2 ); + itr = account_list_nathan.account_list.begin(); + BOOST_CHECK_EQUAL(itr->instance.value, alice_id.instance.value); + ++itr; + BOOST_CHECK_EQUAL(itr->instance.value, robert_id.instance.value); + + // delete alice from the list + { + custom_operation op; + account_list_data list; + account_list_data::ext data; + + flat_set accounts; + accounts.insert(alice_id); + + list.extensions.value.accounts = accounts; + list.extensions.value.remove = true; + + auto packed = fc::raw::pack(list); + packed.insert(packed.begin(), types::account_list); + packed.insert(packed.begin(), 0xFF); + + op.payer = nathan_id; + op.data = packed; + op.fee = db.get_global_properties().parameters.current_fees->calculate_fee(op); + trx.operations.push_back(op); + sign(trx, nathan_private_key); + PUSH_TX(db, trx, ~0); + trx.clear(); + } + + generate_block(); + fc::usleep(fc::milliseconds(200)); + + // alice gone + account_list_nathan = *custom_operations_api.get_storage_info("nathan"); + BOOST_CHECK_EQUAL(account_list_nathan.account.instance.value, 16 ); + BOOST_CHECK_EQUAL(account_list_nathan.account_list.size(), 1 ); + itr = account_list_nathan.account_list.begin(); + BOOST_CHECK_EQUAL(itr->instance.value, robert_id.instance.value); + + // add and edit more stuff to the storage + { + custom_operation op; + account_store_data store; + account_store_data::ext data; + + flat_map pairs; + pairs["image_url"] = "http://new.image.url/newimg.jpg"; + pairs["theme"] = "dark"; + + store.extensions.value.pairs = pairs; + + auto packed = fc::raw::pack(store); + packed.insert(packed.begin(), types::account_store); + packed.insert(packed.begin(), 0xFF); + + op.payer = nathan_id; + op.data = packed; + op.fee = db.get_global_properties().parameters.current_fees->calculate_fee(op); + trx.operations.push_back(op); + sign(trx, nathan_private_key); + PUSH_TX(db, trx, ~0); + trx.clear(); + } + + generate_block(); + fc::usleep(fc::milliseconds(200)); + + // all good, image_url updated and theme added + storage_results_nathan = *custom_operations_api.get_storage_info("nathan"); + BOOST_CHECK_EQUAL(storage_results_nathan.account.instance.value, 16 ); + row1 = storage_results_nathan.storage_map.find("language"); + row2 = storage_results_nathan.storage_map.find("image_url"); + auto row3 = storage_results_nathan.storage_map.find("theme"); + + BOOST_CHECK_EQUAL(row1->first, "language"); + BOOST_CHECK_EQUAL(row1->second, "en"); + BOOST_CHECK_EQUAL(row2->first, "image_url"); + BOOST_CHECK_EQUAL(row2->second, "http://new.image.url/newimg.jpg"); + BOOST_CHECK_EQUAL(row3->first, "theme"); + BOOST_CHECK_EQUAL(row3->second, "dark"); + + // delete stuff from the storage + { + custom_operation op; + account_store_data store; + account_store_data::ext data; + + flat_map pairs; + pairs["theme"] = "dark"; + + store.extensions.value.pairs = pairs; + store.extensions.value.remove = true; + + auto packed = fc::raw::pack(store); + packed.insert(packed.begin(), types::account_store); + packed.insert(packed.begin(), 0xFF); + + op.payer = nathan_id; + op.data = packed; + op.fee = db.get_global_properties().parameters.current_fees->calculate_fee(op); + trx.operations.push_back(op); + sign(trx, nathan_private_key); + PUSH_TX(db, trx, ~0); + trx.clear(); + } + + generate_block(); + fc::usleep(fc::milliseconds(200)); + + // theme is removed from the storage + storage_results_nathan = *custom_operations_api.get_storage_info("nathan"); + BOOST_CHECK_EQUAL(storage_results_nathan.account.instance.value, 16 ); + row1 = storage_results_nathan.storage_map.find("language"); + row2 = storage_results_nathan.storage_map.find("image_url"); + + BOOST_CHECK_EQUAL(row1->first, "language"); + BOOST_CHECK_EQUAL(row1->second, "en"); + BOOST_CHECK_EQUAL(row2->first, "image_url"); + BOOST_CHECK_EQUAL(row2->second, "http://new.image.url/newimg.jpg"); + + // delete stuff from that it is not there + { + custom_operation op; + account_store_data store; + account_store_data::ext data; + + flat_map pairs; + pairs["nothere"] = "nothere"; + + store.extensions.value.pairs = pairs; + store.extensions.value.remove = true; + + auto packed = fc::raw::pack(store); + packed.insert(packed.begin(), types::account_store); + packed.insert(packed.begin(), 0xFF); + + op.payer = nathan_id; + op.data = packed; + op.fee = db.get_global_properties().parameters.current_fees->calculate_fee(op); + trx.operations.push_back(op); + sign(trx, nathan_private_key); + PUSH_TX(db, trx, ~0); + trx.clear(); + } + + generate_block(); + fc::usleep(fc::milliseconds(200)); + + // nothing changes + storage_results_nathan = *custom_operations_api.get_storage_info("nathan"); + BOOST_CHECK_EQUAL(storage_results_nathan.account.instance.value, 16 ); + row1 = storage_results_nathan.storage_map.find("language"); + row2 = storage_results_nathan.storage_map.find("image_url"); + + BOOST_CHECK_EQUAL(row1->first, "language"); + BOOST_CHECK_EQUAL(row1->second, "en"); + BOOST_CHECK_EQUAL(row2->first, "image_url"); + BOOST_CHECK_EQUAL(row2->second, "http://new.image.url/newimg.jpg"); + + // add more than 10 storage items in 1 operation is not allowed + { + custom_operation op; + account_store_data store; + account_store_data::ext data; + + flat_map pairs; + pairs["key1"] = "value1"; + pairs["key2"] = "value2"; + pairs["key3"] = "value3"; + pairs["key4"] = "value4"; + pairs["key5"] = "value5"; + pairs["key6"] = "value6"; + pairs["key7"] = "value7"; + pairs["key8"] = "value8"; + pairs["key9"] = "value9"; + pairs["key10"] = "value10"; + pairs["key11"] = "value11"; + + store.extensions.value.pairs = pairs; + + auto packed = fc::raw::pack(store); + packed.insert(packed.begin(), types::account_store); + packed.insert(packed.begin(), 0xFF); + + op.payer = nathan_id; + op.data = packed; + op.fee = db.get_global_properties().parameters.current_fees->calculate_fee(op); + trx.operations.push_back(op); + sign(trx, nathan_private_key); + PUSH_TX(db, trx, ~0); + trx.clear(); + } + + generate_block(); + fc::usleep(fc::milliseconds(200)); + + // add more than 10 accounts to the list in 1 operation is not allowed + { + custom_operation op; + account_list_data list; + account_list_data::ext data; + + flat_set accounts; + accounts.insert(account_id_type(0)); + accounts.insert(account_id_type(1)); + accounts.insert(account_id_type(2)); + accounts.insert(account_id_type(3)); + accounts.insert(account_id_type(4)); + accounts.insert(account_id_type(5)); + accounts.insert(account_id_type(6)); + accounts.insert(account_id_type(7)); + accounts.insert(account_id_type(8)); + accounts.insert(account_id_type(9)); + accounts.insert(account_id_type(10)); + + list.extensions.value.accounts = accounts; + list.extensions.value.remove = true; + + auto packed = fc::raw::pack(list); + packed.insert(packed.begin(), types::account_list); + packed.insert(packed.begin(), 0xFF); + + op.payer = nathan_id; + op.data = packed; + op.fee = db.get_global_properties().parameters.current_fees->calculate_fee(op); + trx.operations.push_back(op); + sign(trx, nathan_private_key); + PUSH_TX(db, trx, ~0); + trx.clear(); + } + + generate_block(); + fc::usleep(fc::milliseconds(200)); + + // alice, duplicated keys in storage, only second value will be added + { + custom_operation op; + account_store_data store; + account_store_data::ext data; + + flat_map pairs; + pairs["key1"] = "value1"; + pairs["key1"] = "value2"; + + store.extensions.value.pairs = pairs; + + auto packed = fc::raw::pack(store); + packed.insert(packed.begin(), types::account_store); + packed.insert(packed.begin(), 0xFF); + + op.payer = alice_id; + op.data = packed; + op.fee = db.get_global_properties().parameters.current_fees->calculate_fee(op); + trx.operations.push_back(op); + sign(trx, alice_private_key); + PUSH_TX(db, trx, ~0); + trx.clear(); + } + + generate_block(); + fc::usleep(fc::milliseconds(200)); + + auto storage_results_alice = *custom_operations_api.get_storage_info("alice"); + BOOST_CHECK_EQUAL(storage_results_alice.account.instance.value, 17 ); + row1 = storage_results_alice.storage_map.find("key1"); + + BOOST_CHECK_EQUAL(row1->first, "key1"); + BOOST_CHECK_EQUAL(row1->second, "value2"); + + // duplicated accounts in the list, only 1 will be inserted + { + custom_operation op; + account_list_data list; + account_list_data::ext data; + + flat_set accounts; + accounts.insert(robert_id); + accounts.insert(robert_id); + + list.extensions.value.accounts = accounts; + + auto packed = fc::raw::pack(list); + packed.insert(packed.begin(), types::account_list); + packed.insert(packed.begin(), 0xFF); + + op.payer = alice_id; + op.data = packed; + op.fee = db.get_global_properties().parameters.current_fees->calculate_fee(op); + trx.operations.push_back(op); + sign(trx, alice_private_key); + PUSH_TX(db, trx, ~0); + trx.clear(); + } + + generate_block(); + fc::usleep(fc::milliseconds(200)); + + auto account_list_alice = *custom_operations_api.get_storage_info("alice"); + BOOST_CHECK_EQUAL(account_list_alice.account.instance.value, 17 ); + BOOST_CHECK_EQUAL(account_list_alice.account_list.size(), 1 ); + itr = account_list_nathan.account_list.begin(); + BOOST_CHECK_EQUAL(itr->instance.value, robert_id.instance.value); + +} +catch (fc::exception &e) { + edump((e.to_detail_string())); + throw; +} } + BOOST_AUTO_TEST_SUITE_END() From 126016946ab6a22a5780677cde2d67df3ea8620e Mon Sep 17 00:00:00 2001 From: Alfredo Date: Sat, 24 Aug 2019 10:55:09 -0300 Subject: [PATCH 03/18] change underlying storage object and operations --- libraries/app/api.cpp | 16 +- libraries/app/include/graphene/app/api.hpp | 7 +- .../custom_operations/custom_evaluators.cpp | 102 +++---- .../custom_operations/custom_operations.cpp | 18 +- .../custom_operations/custom_evaluators.hpp | 4 +- .../custom_operations/custom_objects.hpp | 44 ++- .../custom_operations/custom_operations.hpp | 27 +- .../custom_operations_plugin.hpp | 4 +- tests/custom_operations/main.cpp | 267 +++++++++--------- 9 files changed, 261 insertions(+), 228 deletions(-) diff --git a/libraries/app/api.cpp b/libraries/app/api.cpp index 83edac5c6f..483179710c 100644 --- a/libraries/app/api.cpp +++ b/libraries/app/api.cpp @@ -738,14 +738,18 @@ namespace graphene { namespace app { return optional(); } - optional custom_operations_api::get_storage_info(std::string account_id_or_name)const + vector custom_operations_api::get_storage_info(std::string account_id_or_name, + std::string catalog)const { const auto account_id = database_api.get_account_id_from_string(account_id_or_name); - auto &index = _app.chain_database()->get_index_type().indices().get(); - auto itr = index.find(account_id); - if(itr != index.end()) - return *itr; - return optional(); + vector results; + auto &index = _app.chain_database()->get_index_type().indices().get(); + auto itr = index.lower_bound(make_tuple(account_id, catalog)); + while(itr != index.end() && itr->account == account_id && itr->catalog == catalog) { + results.push_back(*itr); + ++itr; + } + return results; } } } // graphene::app diff --git a/libraries/app/include/graphene/app/api.hpp b/libraries/app/include/graphene/app/api.hpp index 4650773d8d..7f39b8bfca 100644 --- a/libraries/app/include/graphene/app/api.hpp +++ b/libraries/app/include/graphene/app/api.hpp @@ -572,13 +572,14 @@ namespace graphene { namespace app { optional get_htlc_offer(htlc_order_id_type id)const; /** - * @breif Get storage information of an account + * @breif Get all stored objects of an account * * @param account Account name to get info from + * @param catalog Category classification * - * @return The storage information of the account or empty + * @return The vector of objects of the account or empty */ - optional get_storage_info(std::string account)const; + vector get_storage_info(std::string account, std::string catalog)const; private: application& _app; diff --git a/libraries/plugins/custom_operations/custom_evaluators.cpp b/libraries/plugins/custom_operations/custom_evaluators.cpp index e409aa2369..875e9019c7 100644 --- a/libraries/plugins/custom_operations/custom_evaluators.cpp +++ b/libraries/plugins/custom_operations/custom_evaluators.cpp @@ -113,73 +113,65 @@ object_id_type custom_generic_evaluator::do_apply(const take_htlc_order_operatio return htlc_order_id; } -void fill_storage_map(account_storage_object& aso, account_id_type account, const account_store_data& op) +object_id_type custom_generic_evaluator::do_apply(const account_storage_map& op) { - aso.account = account; - if(op.extensions.value.pairs.valid()) - { - for(auto const& row: *op.extensions.value.pairs) { - if (op.extensions.value.remove.valid() && *op.extensions.value.remove) - aso.storage_map.erase(row.first); - else - aso.storage_map[row.first] = row.second; - } - } -} + auto &index = _db->get_index_type().indices().get(); -object_id_type custom_generic_evaluator::do_apply(const account_store_data& op) -{ - auto &index = _db->get_index_type().indices().get(); - - auto itr = index.find(_account); - if( itr != index.end() ) + if (op.extensions.value.remove.valid() && *op.extensions.value.remove) { - _db->modify( *itr, [&op, this]( account_storage_object& aso ) { - fill_storage_map(aso, _account, op); - }); - return itr->id; - } - else - { - auto created = _db->create( [&op, this]( account_storage_object& aso ) { - fill_storage_map(aso, _account, op); - }); - return created.id; + for(auto const& row: *op.extensions.value.key_values) { + auto itr = index.find(make_tuple(_account, *op.extensions.value.catalog, row.first)); + if(itr != index.end()) + _db->remove(*itr); + } } -} - -void fill_account_list(account_storage_object& aso, account_id_type account, const account_list_data& op) -{ - aso.account = account; - if(op.extensions.value.accounts.valid()) - { - for(auto const& account: *op.extensions.value.accounts) { - if (op.extensions.value.remove.valid() && *op.extensions.value.remove) - aso.account_list.erase(account); + else { + for(auto const& row: *op.extensions.value.key_values) { + auto itr = index.find(make_tuple(_account, *op.extensions.value.catalog, row.first)); + if(itr == index.end()) + { + auto created = _db->create( [&op, this, &row]( account_storage_object& aso ) { + aso.catalog = *op.extensions.value.catalog; + aso.account = _account; + aso.key = row.first; + aso.value = row.second; + }); + } else - aso.account_list.insert(account); + { + _db->modify(*itr, [&op, this, &row](account_storage_object &aso) { + aso.value = row.second; + }); + } } } } - -object_id_type custom_generic_evaluator::do_apply(const account_list_data& op) +object_id_type custom_generic_evaluator::do_apply(const account_storage_list& op) { - auto &index = _db->get_index_type().indices().get(); + auto &index = _db->get_index_type().indices().get(); - auto itr = index.find(_account); - if( itr != index.end() ) + if (op.extensions.value.remove.valid() && *op.extensions.value.remove) { - _db->modify( *itr, [&op, this]( account_storage_object& aso ) { - fill_account_list(aso, _account, op); - }); - return itr->id; + for(auto const& list_value: *op.extensions.value.values) { + + auto itr = index.find(make_tuple(_account, *op.extensions.value.catalog, list_value)); + if(itr != index.end()) + _db->remove(*itr); + } } - else - { - auto created = _db->create( [&op, this]( account_storage_object& aso ) { - fill_account_list(aso, _account, op); - }); - return created.id; + else { + + for(auto const& list_value: *op.extensions.value.values) { + auto itr = index.find(make_tuple(_account, *op.extensions.value.catalog, list_value)); + if(itr == index.end()) + { + auto created = _db->create( [&op, this, &list_value]( account_storage_object& aso ) { + aso.catalog = *op.extensions.value.catalog; + aso.account = _account; + aso.value = list_value; + }); + } + } } } diff --git a/libraries/plugins/custom_operations/custom_operations.cpp b/libraries/plugins/custom_operations/custom_operations.cpp index 54176a295d..80ab429dab 100644 --- a/libraries/plugins/custom_operations/custom_operations.cpp +++ b/libraries/plugins/custom_operations/custom_operations.cpp @@ -46,15 +46,17 @@ void take_htlc_order_operation::validate()const FC_ASSERT(extensions.value.htlc_order_id.valid()); } -void account_store_data::validate()const +void account_storage_map::validate()const { - FC_ASSERT(extensions.value.pairs.valid()); - FC_ASSERT(extensions.value.pairs->size() <= 10); + FC_ASSERT(extensions.value.catalog.valid()); + FC_ASSERT(extensions.value.key_values.valid()); + FC_ASSERT(extensions.value.key_values->size() <= 10); } -void account_list_data::validate()const +void account_storage_list::validate()const { - FC_ASSERT(extensions.value.accounts.valid()); - FC_ASSERT(extensions.value.accounts->size() <= 10); + FC_ASSERT(extensions.value.catalog.valid()); + FC_ASSERT(extensions.value.values.valid()); + FC_ASSERT(extensions.value.values->size() <= 10); } } } //graphene::custom_operations @@ -62,5 +64,5 @@ void account_list_data::validate()const GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::custom_operations::account_contact_operation ) GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::custom_operations::create_htlc_order_operation ) GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::custom_operations::take_htlc_order_operation ) -GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::custom_operations::account_store_data ) -GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::custom_operations::account_list_data ) +GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::custom_operations::account_storage_map ) +GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::custom_operations::account_storage_list ) diff --git a/libraries/plugins/custom_operations/include/graphene/custom_operations/custom_evaluators.hpp b/libraries/plugins/custom_operations/include/graphene/custom_operations/custom_evaluators.hpp index 7f11a967c2..32a36fdae0 100644 --- a/libraries/plugins/custom_operations/include/graphene/custom_operations/custom_evaluators.hpp +++ b/libraries/plugins/custom_operations/include/graphene/custom_operations/custom_evaluators.hpp @@ -37,8 +37,8 @@ class custom_generic_evaluator object_id_type do_apply(const account_contact_operation& o); object_id_type do_apply(const create_htlc_order_operation& o); object_id_type do_apply(const take_htlc_order_operation& o); - object_id_type do_apply(const account_store_data& o); - object_id_type do_apply(const account_list_data& o); + object_id_type do_apply(const account_storage_map& o); + object_id_type do_apply(const account_storage_list& o); }; } } diff --git a/libraries/plugins/custom_operations/include/graphene/custom_operations/custom_objects.hpp b/libraries/plugins/custom_operations/include/graphene/custom_operations/custom_objects.hpp index c92cda33d9..a4ac2b5093 100644 --- a/libraries/plugins/custom_operations/include/graphene/custom_operations/custom_objects.hpp +++ b/libraries/plugins/custom_operations/include/graphene/custom_operations/custom_objects.hpp @@ -91,8 +91,9 @@ struct account_storage_object : public abstract_object static const uint8_t type_id = account_store; account_id_type account; - flat_map storage_map; - flat_set account_list; + string catalog; + optional key; + string value; }; struct by_custom_id; @@ -132,12 +133,45 @@ typedef multi_index_container< typedef generic_index htlc_orderbook_index; +struct by_account_catalog; +struct by_account_catalog_key; +struct by_account_catalog_value; +struct by_account_catalog_key_value; + typedef multi_index_container< account_storage_object, indexed_by< ordered_non_unique< tag, member< object, object_id_type, &object::id > >, - ordered_unique< tag, - member< account_storage_object, account_id_type, &account_storage_object::account > > + ordered_non_unique< tag, + member< account_storage_object, account_id_type, &account_storage_object::account > >, + ordered_non_unique< tag, + composite_key< account_storage_object, + member< account_storage_object, account_id_type, &account_storage_object::account >, + member< account_storage_object, string, &account_storage_object::catalog > + > + >, + ordered_non_unique< tag, + composite_key< account_storage_object, + member< account_storage_object, account_id_type, &account_storage_object::account >, + member< account_storage_object, string, &account_storage_object::catalog >, + member< account_storage_object, optional, &account_storage_object::key > + > + >, + ordered_unique< tag, + composite_key< account_storage_object, + member< account_storage_object, account_id_type, &account_storage_object::account >, + member< account_storage_object, string, &account_storage_object::catalog >, + member< account_storage_object, string, &account_storage_object::value > + > + >, + ordered_unique< tag, + composite_key< account_storage_object, + member< account_storage_object, account_id_type, &account_storage_object::account >, + member< account_storage_object, string, &account_storage_object::catalog >, + member< account_storage_object, optional, &account_storage_object::key >, + member< account_storage_object, string, &account_storage_object::value > + > + > > > account_storage_multi_index_type; @@ -157,7 +191,7 @@ FC_REFLECT_DERIVED( graphene::custom_operations::htlc_order_object, (graphene::d (blockchain_asset_precision)(token_contract)(tag)(taker_bitshares_account) (taker_blockchain_account)(close_time)) FC_REFLECT_DERIVED( graphene::custom_operations::account_storage_object, (graphene::db::object), - (account)(storage_map)(account_list)) + (account)(catalog)(key)(value)) FC_REFLECT_ENUM( graphene::custom_operations::types, (account_contact)(create_htlc)(take_htlc)(account_store) (account_list)) FC_REFLECT_ENUM( graphene::custom_operations::blockchains, (eos)(bitcoin)(ripple)(ethereum) ) diff --git a/libraries/plugins/custom_operations/include/graphene/custom_operations/custom_operations.hpp b/libraries/plugins/custom_operations/include/graphene/custom_operations/custom_operations.hpp index 1a9155ff7e..976302ce63 100644 --- a/libraries/plugins/custom_operations/include/graphene/custom_operations/custom_operations.hpp +++ b/libraries/plugins/custom_operations/include/graphene/custom_operations/custom_operations.hpp @@ -82,12 +82,13 @@ struct take_htlc_order_operation : chain::base_operation void validate()const; }; -struct account_store_data : chain::base_operation +struct account_storage_map : chain::base_operation { struct ext { optional remove; - optional> pairs; + optional catalog; + optional> key_values; }; graphene::protocol::extension extensions; @@ -95,12 +96,13 @@ struct account_store_data : chain::base_operation void validate()const; }; -struct account_list_data : chain::base_operation +struct account_storage_list : chain::base_operation { struct ext { optional remove; - optional> accounts; + optional catalog; + optional> values; }; graphene::protocol::extension extensions; @@ -125,17 +127,16 @@ FC_REFLECT( graphene::custom_operations::take_htlc_order_operation::ext, (htlc_o FC_REFLECT_TYPENAME( graphene::protocol::extension ) FC_REFLECT( graphene::custom_operations::take_htlc_order_operation, (extensions) ) -FC_REFLECT( graphene::custom_operations::account_store_data::ext, (pairs)(remove) ) -FC_REFLECT_TYPENAME( graphene::protocol::extension ) -FC_REFLECT( graphene::custom_operations::account_store_data, (extensions) ) +FC_REFLECT( graphene::custom_operations::account_storage_map::ext, (remove)(catalog)(key_values) ) +FC_REFLECT_TYPENAME( graphene::protocol::extension ) +FC_REFLECT( graphene::custom_operations::account_storage_map, (extensions) ) -FC_REFLECT( graphene::custom_operations::account_list_data::ext, (accounts)(remove) ) -FC_REFLECT_TYPENAME( graphene::protocol::extension ) -FC_REFLECT( graphene::custom_operations::account_list_data, (extensions) ) +FC_REFLECT( graphene::custom_operations::account_storage_list::ext, (catalog)(values)(remove) ) +FC_REFLECT_TYPENAME( graphene::protocol::extension ) +FC_REFLECT( graphene::custom_operations::account_storage_list, (extensions) ) GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::custom_operations::account_contact_operation ) GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::custom_operations::create_htlc_order_operation ) GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::custom_operations::take_htlc_order_operation ) -GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::custom_operations::account_store_data ) -GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::custom_operations::account_list_data ) - +GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::custom_operations::account_storage_map ) +GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::custom_operations::account_storage_list ) diff --git a/libraries/plugins/custom_operations/include/graphene/custom_operations/custom_operations_plugin.hpp b/libraries/plugins/custom_operations/include/graphene/custom_operations/custom_operations_plugin.hpp index b8612215b2..d9ee4cdf61 100644 --- a/libraries/plugins/custom_operations/include/graphene/custom_operations/custom_operations_plugin.hpp +++ b/libraries/plugins/custom_operations/include/graphene/custom_operations/custom_operations_plugin.hpp @@ -60,8 +60,8 @@ typedef fc::static_variant< account_contact_operation, create_htlc_order_operation, take_htlc_order_operation, - account_store_data, - account_list_data + account_storage_map, + account_storage_list > custom_plugin_operation; struct custom_operation_wrapper { diff --git a/tests/custom_operations/main.cpp b/tests/custom_operations/main.cpp index e2b5606d8a..3916b3af57 100644 --- a/tests/custom_operations/main.cpp +++ b/tests/custom_operations/main.cpp @@ -485,17 +485,18 @@ try { transfer(committee_account, nathan_id, asset(init_balance)); transfer(committee_account, alice_id, asset(init_balance)); - // nathan adds arbitrary account data via custom operation, simulating some dapp settings in this case + // nathan adds key-value data via custom operation to a settings catalog { custom_operation op; - account_store_data store; - account_store_data::ext data; + account_storage_map store; + account_storage_map::ext data; flat_map pairs; pairs["language"] = "en"; pairs["image_url"] = "http://some.image.url/img.jpg"; - store.extensions.value.pairs = pairs; + store.extensions.value.key_values = pairs; + store.extensions.value.catalog = "settings"; auto packed = fc::raw::pack(store); packed.insert(packed.begin(), types::account_store); @@ -514,28 +515,27 @@ try { fc::usleep(fc::milliseconds(200)); // check nathan stored data with the api - account_storage_object storage_results_nathan = *custom_operations_api.get_storage_info("nathan"); - - BOOST_CHECK_EQUAL(storage_results_nathan.account.instance.value, 16 ); - auto row1 = storage_results_nathan.storage_map.find("language"); - auto row2 = storage_results_nathan.storage_map.find("image_url"); - - BOOST_CHECK_EQUAL(row1->first, "language"); - BOOST_CHECK_EQUAL(row1->second, "en"); - BOOST_CHECK_EQUAL(row2->first, "image_url"); - BOOST_CHECK_EQUAL(row2->second, "http://some.image.url/img.jpg"); - - // add accounts to account list storage + vector storage_results_nathan = custom_operations_api.get_storage_info("nathan", "settings"); + BOOST_CHECK_EQUAL(storage_results_nathan.size(), 2 ); + BOOST_CHECK_EQUAL(storage_results_nathan[0].account.instance.value, 16 ); + BOOST_CHECK_EQUAL(*storage_results_nathan[0].key, "image_url"); + BOOST_CHECK_EQUAL(storage_results_nathan[0].value, "http://some.image.url/img.jpg"); + BOOST_CHECK_EQUAL(storage_results_nathan[1].account.instance.value, 16 ); + BOOST_CHECK_EQUAL(*storage_results_nathan[1].key, "language"); + BOOST_CHECK_EQUAL(storage_results_nathan[1].value, "en"); + + // nathan add a list of accounts to storage { custom_operation op; - account_list_data list; - account_list_data::ext data; + account_storage_list list; + account_storage_list::ext data; - flat_set accounts; - accounts.insert(alice_id); - accounts.insert(robert_id); + flat_set accounts; + accounts.insert(alice.name); + accounts.insert(robert.name); - list.extensions.value.accounts = accounts; + list.extensions.value.values = accounts; + list.extensions.value.catalog = "contact_list"; auto packed = fc::raw::pack(list); packed.insert(packed.begin(), types::account_list); @@ -554,25 +554,24 @@ try { fc::usleep(fc::milliseconds(200)); // get the account list for nathan, check alice and robert are there - auto account_list_nathan = *custom_operations_api.get_storage_info("nathan"); - - BOOST_CHECK_EQUAL(account_list_nathan.account.instance.value, 16 ); - BOOST_CHECK_EQUAL(account_list_nathan.account_list.size(), 2 ); - auto itr = account_list_nathan.account_list.begin(); - BOOST_CHECK_EQUAL(itr->instance.value, alice_id.instance.value); - ++itr; - BOOST_CHECK_EQUAL(itr->instance.value, robert_id.instance.value); + storage_results_nathan = custom_operations_api.get_storage_info("nathan", "contact_list"); + BOOST_CHECK_EQUAL(storage_results_nathan.size(), 2 ); + BOOST_CHECK_EQUAL(storage_results_nathan[0].account.instance.value, 16 ); + BOOST_CHECK_EQUAL(storage_results_nathan[0].value, alice.name); + BOOST_CHECK_EQUAL(storage_results_nathan[1].account.instance.value, 16 ); + BOOST_CHECK_EQUAL(storage_results_nathan[1].value, robert.name); // add a value into account list already there { custom_operation op; - account_list_data list; - account_list_data::ext data; + account_storage_list list; + account_storage_list::ext data; - flat_set accounts; - accounts.insert(alice_id); + flat_set accounts; + accounts.insert(alice.name); - list.extensions.value.accounts = accounts; + list.extensions.value.values = accounts; + list.extensions.value.catalog = "contact_list"; auto packed = fc::raw::pack(list); packed.insert(packed.begin(), types::account_list); @@ -591,25 +590,25 @@ try { fc::usleep(fc::milliseconds(200)); // nothing changes - account_list_nathan = *custom_operations_api.get_storage_info("nathan"); - BOOST_CHECK_EQUAL(account_list_nathan.account.instance.value, 16 ); - BOOST_CHECK_EQUAL(account_list_nathan.account_list.size(), 2 ); - itr = account_list_nathan.account_list.begin(); - BOOST_CHECK_EQUAL(itr->instance.value, alice_id.instance.value); - ++itr; - BOOST_CHECK_EQUAL(itr->instance.value, robert_id.instance.value); + storage_results_nathan = custom_operations_api.get_storage_info("nathan", "contact_list"); + BOOST_CHECK_EQUAL(storage_results_nathan.size(), 2 ); + BOOST_CHECK_EQUAL(storage_results_nathan[0].account.instance.value, 16 ); + BOOST_CHECK_EQUAL(storage_results_nathan[0].value, alice.name); + BOOST_CHECK_EQUAL(storage_results_nathan[1].account.instance.value, 16 ); + BOOST_CHECK_EQUAL(storage_results_nathan[1].value, robert.name); // delete alice from the list { custom_operation op; - account_list_data list; - account_list_data::ext data; + account_storage_list list; + account_storage_list::ext data; - flat_set accounts; - accounts.insert(alice_id); + flat_set accounts; + accounts.insert(alice.name); - list.extensions.value.accounts = accounts; + list.extensions.value.values = accounts; list.extensions.value.remove = true; + list.extensions.value.catalog = "contact_list"; auto packed = fc::raw::pack(list); packed.insert(packed.begin(), types::account_list); @@ -628,23 +627,23 @@ try { fc::usleep(fc::milliseconds(200)); // alice gone - account_list_nathan = *custom_operations_api.get_storage_info("nathan"); - BOOST_CHECK_EQUAL(account_list_nathan.account.instance.value, 16 ); - BOOST_CHECK_EQUAL(account_list_nathan.account_list.size(), 1 ); - itr = account_list_nathan.account_list.begin(); - BOOST_CHECK_EQUAL(itr->instance.value, robert_id.instance.value); + storage_results_nathan = custom_operations_api.get_storage_info("nathan", "contact_list"); + BOOST_CHECK_EQUAL(storage_results_nathan.size(), 1 ); + BOOST_CHECK_EQUAL(storage_results_nathan[0].account.instance.value, 16 ); + BOOST_CHECK_EQUAL(storage_results_nathan[0].value, robert.name); // add and edit more stuff to the storage { custom_operation op; - account_store_data store; - account_store_data::ext data; + account_storage_map store; + account_storage_map::ext data; flat_map pairs; pairs["image_url"] = "http://new.image.url/newimg.jpg"; pairs["theme"] = "dark"; - store.extensions.value.pairs = pairs; + store.extensions.value.key_values = pairs; + store.extensions.value.catalog = "settings"; auto packed = fc::raw::pack(store); packed.insert(packed.begin(), types::account_store); @@ -662,31 +661,30 @@ try { generate_block(); fc::usleep(fc::milliseconds(200)); - // all good, image_url updated and theme added - storage_results_nathan = *custom_operations_api.get_storage_info("nathan"); - BOOST_CHECK_EQUAL(storage_results_nathan.account.instance.value, 16 ); - row1 = storage_results_nathan.storage_map.find("language"); - row2 = storage_results_nathan.storage_map.find("image_url"); - auto row3 = storage_results_nathan.storage_map.find("theme"); - - BOOST_CHECK_EQUAL(row1->first, "language"); - BOOST_CHECK_EQUAL(row1->second, "en"); - BOOST_CHECK_EQUAL(row2->first, "image_url"); - BOOST_CHECK_EQUAL(row2->second, "http://new.image.url/newimg.jpg"); - BOOST_CHECK_EQUAL(row3->first, "theme"); - BOOST_CHECK_EQUAL(row3->second, "dark"); + // check old and new stuff + storage_results_nathan = custom_operations_api.get_storage_info("nathan", "settings"); + BOOST_CHECK_EQUAL(storage_results_nathan.size(), 3 ); + BOOST_CHECK_EQUAL(storage_results_nathan[0].account.instance.value, 16 ); + BOOST_CHECK_EQUAL(*storage_results_nathan[0].key, "image_url"); + BOOST_CHECK_EQUAL(storage_results_nathan[0].value, "http://new.image.url/newimg.jpg"); + BOOST_CHECK_EQUAL(storage_results_nathan[1].account.instance.value, 16 ); + BOOST_CHECK_EQUAL(*storage_results_nathan[1].key, "language"); + BOOST_CHECK_EQUAL(storage_results_nathan[1].value, "en"); + BOOST_CHECK_EQUAL(*storage_results_nathan[2].key, "theme"); + BOOST_CHECK_EQUAL(storage_results_nathan[2].value, "dark"); // delete stuff from the storage { custom_operation op; - account_store_data store; - account_store_data::ext data; + account_storage_map store; + account_storage_map::ext data; flat_map pairs; pairs["theme"] = "dark"; - store.extensions.value.pairs = pairs; + store.extensions.value.key_values = pairs; store.extensions.value.remove = true; + store.extensions.value.catalog = "settings"; auto packed = fc::raw::pack(store); packed.insert(packed.begin(), types::account_store); @@ -705,27 +703,27 @@ try { fc::usleep(fc::milliseconds(200)); // theme is removed from the storage - storage_results_nathan = *custom_operations_api.get_storage_info("nathan"); - BOOST_CHECK_EQUAL(storage_results_nathan.account.instance.value, 16 ); - row1 = storage_results_nathan.storage_map.find("language"); - row2 = storage_results_nathan.storage_map.find("image_url"); - - BOOST_CHECK_EQUAL(row1->first, "language"); - BOOST_CHECK_EQUAL(row1->second, "en"); - BOOST_CHECK_EQUAL(row2->first, "image_url"); - BOOST_CHECK_EQUAL(row2->second, "http://new.image.url/newimg.jpg"); - - // delete stuff from that it is not there + storage_results_nathan = custom_operations_api.get_storage_info("nathan", "settings"); + BOOST_CHECK_EQUAL(storage_results_nathan.size(), 2 ); + BOOST_CHECK_EQUAL(storage_results_nathan[0].account.instance.value, 16 ); + BOOST_CHECK_EQUAL(*storage_results_nathan[0].key, "image_url"); + BOOST_CHECK_EQUAL(storage_results_nathan[0].value, "http://new.image.url/newimg.jpg"); + BOOST_CHECK_EQUAL(storage_results_nathan[1].account.instance.value, 16 ); + BOOST_CHECK_EQUAL(*storage_results_nathan[1].key, "language"); + BOOST_CHECK_EQUAL(storage_results_nathan[1].value, "en"); + + // delete stuff that it is not there { custom_operation op; - account_store_data store; - account_store_data::ext data; + account_storage_map store; + account_storage_map::ext data; flat_map pairs; pairs["nothere"] = "nothere"; - store.extensions.value.pairs = pairs; + store.extensions.value.key_values = pairs; store.extensions.value.remove = true; + store.extensions.value.catalog = "settings"; auto packed = fc::raw::pack(store); packed.insert(packed.begin(), types::account_store); @@ -744,21 +742,20 @@ try { fc::usleep(fc::milliseconds(200)); // nothing changes - storage_results_nathan = *custom_operations_api.get_storage_info("nathan"); - BOOST_CHECK_EQUAL(storage_results_nathan.account.instance.value, 16 ); - row1 = storage_results_nathan.storage_map.find("language"); - row2 = storage_results_nathan.storage_map.find("image_url"); - - BOOST_CHECK_EQUAL(row1->first, "language"); - BOOST_CHECK_EQUAL(row1->second, "en"); - BOOST_CHECK_EQUAL(row2->first, "image_url"); - BOOST_CHECK_EQUAL(row2->second, "http://new.image.url/newimg.jpg"); + storage_results_nathan = custom_operations_api.get_storage_info("nathan", "settings"); + BOOST_CHECK_EQUAL(storage_results_nathan.size(), 2 ); + BOOST_CHECK_EQUAL(storage_results_nathan[0].account.instance.value, 16 ); + BOOST_CHECK_EQUAL(*storage_results_nathan[0].key, "image_url"); + BOOST_CHECK_EQUAL(storage_results_nathan[0].value, "http://new.image.url/newimg.jpg"); + BOOST_CHECK_EQUAL(storage_results_nathan[1].account.instance.value, 16 ); + BOOST_CHECK_EQUAL(*storage_results_nathan[1].key, "language"); + BOOST_CHECK_EQUAL(storage_results_nathan[1].value, "en"); // add more than 10 storage items in 1 operation is not allowed { custom_operation op; - account_store_data store; - account_store_data::ext data; + account_storage_map store; + account_storage_map::ext data; flat_map pairs; pairs["key1"] = "value1"; @@ -773,7 +770,8 @@ try { pairs["key10"] = "value10"; pairs["key11"] = "value11"; - store.extensions.value.pairs = pairs; + store.extensions.value.key_values = pairs; + store.extensions.value.catalog = "settings"; auto packed = fc::raw::pack(store); packed.insert(packed.begin(), types::account_store); @@ -794,23 +792,24 @@ try { // add more than 10 accounts to the list in 1 operation is not allowed { custom_operation op; - account_list_data list; - account_list_data::ext data; - - flat_set accounts; - accounts.insert(account_id_type(0)); - accounts.insert(account_id_type(1)); - accounts.insert(account_id_type(2)); - accounts.insert(account_id_type(3)); - accounts.insert(account_id_type(4)); - accounts.insert(account_id_type(5)); - accounts.insert(account_id_type(6)); - accounts.insert(account_id_type(7)); - accounts.insert(account_id_type(8)); - accounts.insert(account_id_type(9)); - accounts.insert(account_id_type(10)); - - list.extensions.value.accounts = accounts; + account_storage_list list; + account_storage_list::ext data; + + flat_set accounts; + accounts.insert("init0"); + accounts.insert("init1"); + accounts.insert("init2"); + accounts.insert("init3"); + accounts.insert("init4"); + accounts.insert("init5"); + accounts.insert("init6"); + accounts.insert("init7"); + accounts.insert("init8"); + accounts.insert("init9"); + accounts.insert("init10"); + + list.extensions.value.values = accounts; + list.extensions.value.catalog = "contact_list"; list.extensions.value.remove = true; auto packed = fc::raw::pack(list); @@ -832,14 +831,15 @@ try { // alice, duplicated keys in storage, only second value will be added { custom_operation op; - account_store_data store; - account_store_data::ext data; + account_storage_map store; + account_storage_map::ext data; flat_map pairs; pairs["key1"] = "value1"; pairs["key1"] = "value2"; - store.extensions.value.pairs = pairs; + store.extensions.value.key_values = pairs; + store.extensions.value.catalog = "random"; auto packed = fc::raw::pack(store); packed.insert(packed.begin(), types::account_store); @@ -857,24 +857,24 @@ try { generate_block(); fc::usleep(fc::milliseconds(200)); - auto storage_results_alice = *custom_operations_api.get_storage_info("alice"); - BOOST_CHECK_EQUAL(storage_results_alice.account.instance.value, 17 ); - row1 = storage_results_alice.storage_map.find("key1"); - - BOOST_CHECK_EQUAL(row1->first, "key1"); - BOOST_CHECK_EQUAL(row1->second, "value2"); + vector storage_results_alice = custom_operations_api.get_storage_info("alice", "random"); + BOOST_CHECK_EQUAL(storage_results_alice.size(), 1 ); + BOOST_CHECK_EQUAL(storage_results_alice[0].account.instance.value, 17 ); + BOOST_CHECK_EQUAL(*storage_results_alice[0].key, "key1"); + BOOST_CHECK_EQUAL(storage_results_alice[0].value, "value2"); // duplicated accounts in the list, only 1 will be inserted { custom_operation op; - account_list_data list; - account_list_data::ext data; + account_storage_list list; + account_storage_list::ext data; - flat_set accounts; - accounts.insert(robert_id); - accounts.insert(robert_id); + flat_set accounts; + accounts.insert(robert.name); + accounts.insert(robert.name); - list.extensions.value.accounts = accounts; + list.extensions.value.values = accounts; + list.extensions.value.catalog = "contact_list"; auto packed = fc::raw::pack(list); packed.insert(packed.begin(), types::account_list); @@ -892,11 +892,10 @@ try { generate_block(); fc::usleep(fc::milliseconds(200)); - auto account_list_alice = *custom_operations_api.get_storage_info("alice"); - BOOST_CHECK_EQUAL(account_list_alice.account.instance.value, 17 ); - BOOST_CHECK_EQUAL(account_list_alice.account_list.size(), 1 ); - itr = account_list_nathan.account_list.begin(); - BOOST_CHECK_EQUAL(itr->instance.value, robert_id.instance.value); + storage_results_alice = custom_operations_api.get_storage_info("alice", "contact_list"); + BOOST_CHECK_EQUAL(storage_results_alice.size(), 1 ); + BOOST_CHECK_EQUAL(storage_results_alice[0].account.instance.value, 17 ); + BOOST_CHECK_EQUAL(storage_results_alice[0].value, robert.name); } catch (fc::exception &e) { From 9c23a2bdb297e7ba39d27dfc0b84636cd3e594c6 Mon Sep 17 00:00:00 2001 From: Alfredo Date: Wed, 28 Aug 2019 19:30:45 -0300 Subject: [PATCH 04/18] add general storage wallet calls --- libraries/app/include/graphene/app/api.hpp | 3 +- .../custom_operations/custom_objects.hpp | 8 +- .../wallet/include/graphene/wallet/wallet.hpp | 43 ++++++++++ libraries/wallet/wallet.cpp | 74 +++++++++++++++++ tests/cli/main.cpp | 80 +++++++++++++++++++ tests/custom_operations/main.cpp | 12 +-- 6 files changed, 209 insertions(+), 11 deletions(-) diff --git a/libraries/app/include/graphene/app/api.hpp b/libraries/app/include/graphene/app/api.hpp index 7f39b8bfca..d0e97f76a0 100644 --- a/libraries/app/include/graphene/app/api.hpp +++ b/libraries/app/include/graphene/app/api.hpp @@ -572,7 +572,7 @@ namespace graphene { namespace app { optional get_htlc_offer(htlc_order_id_type id)const; /** - * @breif Get all stored objects of an account + * @breif Get all stored objects of an account in a particular catalog * * @param account Account name to get info from * @param catalog Category classification @@ -726,6 +726,7 @@ FC_API(graphene::app::custom_operations_api, (get_account_htlc_offers) (get_active_htlc_offers) (get_htlc_offer) + (get_storage_info) ) FC_API(graphene::app::login_api, (login) diff --git a/libraries/plugins/custom_operations/include/graphene/custom_operations/custom_objects.hpp b/libraries/plugins/custom_operations/include/graphene/custom_operations/custom_objects.hpp index a4ac2b5093..fb24f3f9ae 100644 --- a/libraries/plugins/custom_operations/include/graphene/custom_operations/custom_objects.hpp +++ b/libraries/plugins/custom_operations/include/graphene/custom_operations/custom_objects.hpp @@ -38,7 +38,7 @@ enum types { account_contact = 0, create_htlc = 1, take_htlc = 2, - account_store = 3, + account_map = 3, account_list = 4 }; enum blockchains { @@ -88,7 +88,7 @@ struct htlc_order_object : public abstract_object struct account_storage_object : public abstract_object { static const uint8_t space_id = CUSTOM_OPERATIONS_SPACE_ID; - static const uint8_t type_id = account_store; + static const uint8_t type_id = account_map; account_id_type account; string catalog; @@ -179,7 +179,7 @@ typedef generic_index using account_contact_id_type = object_id; using htlc_order_id_type = object_id; -using account_storage_id_type = object_id; +using account_storage_id_type = object_id; } } //graphene::custom_operations @@ -192,6 +192,6 @@ FC_REFLECT_DERIVED( graphene::custom_operations::htlc_order_object, (graphene::d (taker_blockchain_account)(close_time)) FC_REFLECT_DERIVED( graphene::custom_operations::account_storage_object, (graphene::db::object), (account)(catalog)(key)(value)) -FC_REFLECT_ENUM( graphene::custom_operations::types, (account_contact)(create_htlc)(take_htlc)(account_store) +FC_REFLECT_ENUM( graphene::custom_operations::types, (account_contact)(create_htlc)(take_htlc)(account_map) (account_list)) FC_REFLECT_ENUM( graphene::custom_operations::blockchains, (eos)(bitcoin)(ripple)(ethereum) ) diff --git a/libraries/wallet/include/graphene/wallet/wallet.hpp b/libraries/wallet/include/graphene/wallet/wallet.hpp index 60196feb5b..229a5df7cf 100644 --- a/libraries/wallet/include/graphene/wallet/wallet.hpp +++ b/libraries/wallet/include/graphene/wallet/wallet.hpp @@ -2071,6 +2071,7 @@ class wallet_api * * @return The signed transaction */ + signed_transaction take_htlc_offer(string account, take_htlc_order_operation::ext data, bool broadcast); /** @@ -2083,6 +2084,48 @@ class wallet_api * @return A list of \c htlc_order_object that are active and non expired in the selected blockchain. */ vector get_active_htlc_offers(uint16_t blockchain); + + /** + * Manage account storage map(key->value) by using the custom operations plugin. + * + * Each account can optionally add random information in the form of a key-value map + * to be retrieved by any interested party. + * + * @param account The account ID or name that we are adding additional information to. + * @param data Storage data to be added. \c account_storage_map::ext + * @param broadcast true if you wish to broadcast the transaction + * + * @return The signed transaction + */ + signed_transaction account_store_map(string account, account_storage_map::ext map, bool broadcast); + + /** + * Manage a accounts list(values)by using the custom operations plugin. + * + * Each account can optionally add and delete data from a dedicated list + * to be retrieved by any interested party. Can be used as a whitelist, address book, etc. + * + * @param account The account ID or name that we are adding additional information to. + * @param data List data to be added. \c account_storage_list::ext + * @param broadcast true if you wish to broadcast the transaction + * + * @return The signed transaction + */ + signed_transaction account_store_list(string account, account_storage_list::ext data, bool broadcast); + + /** + * Get \c account_storage_object of an account by using the custom operations plugin. + * + * Storage data added to the map with @ref account_store_map and list data added by + * @ref account_list_accounts will be returned. + * + * @param account Account ID or name to get contact data from. + * @param catalog The catalog to retrieve. + * + * @return An \c account_storage_object or empty. + */ + vector get_account_storage(string account, string catalog); + }; } } diff --git a/libraries/wallet/wallet.cpp b/libraries/wallet/wallet.cpp index 20eced8820..f534c1806a 100644 --- a/libraries/wallet/wallet.cpp +++ b/libraries/wallet/wallet.cpp @@ -2051,6 +2051,64 @@ class wallet_api_impl } FC_CAPTURE_AND_RETHROW( (account)(data)(broadcast) ) } + signed_transaction account_store_map(string account, account_storage_map::ext map, bool broadcast) + { + try + { + FC_ASSERT( !self.is_locked() ); + + account_id_type account_id = get_account(account).id; + + custom_operation op; + account_storage_map store; + store.extensions.value = map; + + auto packed = fc::raw::pack(store); + packed.insert(packed.begin(), types::account_map); + packed.insert(packed.begin(), 0xFF); + + op.payer = account_id; + op.data = packed; + + signed_transaction tx; + tx.operations.push_back(op); + set_operation_fees( tx, _remote_db->get_global_properties().parameters.get_current_fees()); + tx.validate(); + + return sign_transaction(tx, broadcast); + + } FC_CAPTURE_AND_RETHROW( (account)(map)(broadcast) ) + } + + signed_transaction account_store_list(string account, account_storage_list::ext list, bool broadcast) + { + try + { + FC_ASSERT( !self.is_locked() ); + + account_id_type account_id = get_account(account).id; + + custom_operation op; + account_storage_list store; + store.extensions.value = list; + + auto packed = fc::raw::pack(store); + packed.insert(packed.begin(), types::account_list); + packed.insert(packed.begin(), 0xFF); + + op.payer = account_id; + op.data = packed; + + signed_transaction tx; + tx.operations.push_back(op); + set_operation_fees( tx, _remote_db->get_global_properties().parameters.get_current_fees()); + tx.validate(); + + return sign_transaction(tx, broadcast); + + } FC_CAPTURE_AND_RETHROW( (account)(list)(broadcast) ) + } + vector< vesting_balance_object_with_info > get_vesting_balances( string account_name ) { try { fc::optional vbid = maybe_id( account_name ); @@ -5278,6 +5336,7 @@ order_book wallet_api::get_order_book( const string& base, const string& quote, return( my->_remote_db->get_order_book( base, quote, limit ) ); } +// custom operations signed_transaction wallet_api::set_contact_information(string account, account_contact_operation::ext data, bool broadcast) { @@ -5313,6 +5372,21 @@ vector wallet_api::get_active_htlc_offers(uint16_t blockchain return results; } +signed_transaction wallet_api::account_store_map(string account, account_storage_map::ext map, bool broadcast) +{ + return my->account_store_map(account, map, broadcast); +} + +signed_transaction wallet_api::account_store_list(string account, account_storage_list::ext list, bool broadcast) +{ + return my->account_store_list(account, list, broadcast); +} + +vector wallet_api::get_account_storage(string account, string catalog) +{ + return my->_custom_operations->get_storage_info(account, catalog); +} + signed_block_with_info::signed_block_with_info( const signed_block& block ) : signed_block( block ) { diff --git a/tests/cli/main.cpp b/tests/cli/main.cpp index 6b2e90d18f..226d9e1661 100644 --- a/tests/cli/main.cpp +++ b/tests/cli/main.cpp @@ -1307,3 +1307,83 @@ BOOST_FIXTURE_TEST_CASE( htlc_orderbook, cli_fixture ) throw; } } + +/////////////////// +// Test the general storage by custom operations plugin +/////////////////// +BOOST_FIXTURE_TEST_CASE( general_storage, cli_fixture ) +{ + try { + // create the taker account + INVOKE(create_new_account); + + auto db = app1->chain_database(); + + BOOST_TEST_MESSAGE("Storing in a map."); + + flat_map pairs; + pairs["key1"] = "value1"; + pairs["key2"] = "value2"; + + account_storage_map::ext map; + + map.key_values = pairs; + map.catalog = "any"; + + con.wallet_api_ptr->account_store_map("nathan", map, true); + + BOOST_TEST_MESSAGE("The system is generating a block."); + BOOST_CHECK(generate_block(app1)); + + BOOST_TEST_MESSAGE("Get current map for nathan."); + auto nathan_map = con.wallet_api_ptr->get_account_storage("nathan", "any"); + + BOOST_CHECK_EQUAL(nathan_map[0].id.instance(), 0); + BOOST_CHECK_EQUAL(nathan_map[0].account.instance.value, 17); + BOOST_CHECK_EQUAL(nathan_map[0].catalog, "any"); + BOOST_CHECK_EQUAL(*nathan_map[0].key, "key1"); + BOOST_CHECK_EQUAL(nathan_map[0].value, "value1"); + BOOST_CHECK_EQUAL(nathan_map[1].id.instance(), 1); + BOOST_CHECK_EQUAL(nathan_map[1].account.instance.value, 17); + BOOST_CHECK_EQUAL(nathan_map[1].catalog, "any"); + BOOST_CHECK_EQUAL(*nathan_map[1].key, "key2"); + BOOST_CHECK_EQUAL(nathan_map[1].value, "value2"); + + BOOST_TEST_MESSAGE("Storing in a list."); + + flat_set favs; + favs.insert("chocolate"); + favs.insert("milk"); + favs.insert("banana"); + + account_storage_list::ext list; + + list.values = favs; + list.catalog = "favourites"; + + con.wallet_api_ptr->account_store_list("nathan", list, true); + + BOOST_TEST_MESSAGE("The system is generating a block."); + BOOST_CHECK(generate_block(app1)); + + BOOST_TEST_MESSAGE("Get current list for nathan."); + auto nathan_list = con.wallet_api_ptr->get_account_storage("nathan", "favourites"); + + BOOST_CHECK_EQUAL(nathan_list[0].id.instance(), 2); + BOOST_CHECK_EQUAL(nathan_list[0].account.instance.value, 17); + BOOST_CHECK_EQUAL(nathan_list[0].catalog, "favourites"); + BOOST_CHECK_EQUAL(nathan_list[0].value, "banana"); + BOOST_CHECK_EQUAL(nathan_list[1].id.instance(), 3); + BOOST_CHECK_EQUAL(nathan_list[1].account.instance.value, 17); + BOOST_CHECK_EQUAL(nathan_list[1].catalog, "favourites"); + BOOST_CHECK_EQUAL(nathan_list[1].value, "chocolate"); + BOOST_CHECK_EQUAL(nathan_list[2].id.instance(), 4); + BOOST_CHECK_EQUAL(nathan_list[2].account.instance.value, 17); + BOOST_CHECK_EQUAL(nathan_list[2].catalog, "favourites"); + BOOST_CHECK_EQUAL(nathan_list[2].value, "milk"); + + } catch( fc::exception& e ) { + edump((e.to_detail_string())); + throw; + } +} diff --git a/tests/custom_operations/main.cpp b/tests/custom_operations/main.cpp index 3916b3af57..b671dcf291 100644 --- a/tests/custom_operations/main.cpp +++ b/tests/custom_operations/main.cpp @@ -499,7 +499,7 @@ try { store.extensions.value.catalog = "settings"; auto packed = fc::raw::pack(store); - packed.insert(packed.begin(), types::account_store); + packed.insert(packed.begin(), types::account_map); packed.insert(packed.begin(), 0xFF); op.payer = nathan_id; @@ -646,7 +646,7 @@ try { store.extensions.value.catalog = "settings"; auto packed = fc::raw::pack(store); - packed.insert(packed.begin(), types::account_store); + packed.insert(packed.begin(), types::account_map); packed.insert(packed.begin(), 0xFF); op.payer = nathan_id; @@ -687,7 +687,7 @@ try { store.extensions.value.catalog = "settings"; auto packed = fc::raw::pack(store); - packed.insert(packed.begin(), types::account_store); + packed.insert(packed.begin(), types::account_map); packed.insert(packed.begin(), 0xFF); op.payer = nathan_id; @@ -726,7 +726,7 @@ try { store.extensions.value.catalog = "settings"; auto packed = fc::raw::pack(store); - packed.insert(packed.begin(), types::account_store); + packed.insert(packed.begin(), types::account_map); packed.insert(packed.begin(), 0xFF); op.payer = nathan_id; @@ -774,7 +774,7 @@ try { store.extensions.value.catalog = "settings"; auto packed = fc::raw::pack(store); - packed.insert(packed.begin(), types::account_store); + packed.insert(packed.begin(), types::account_map); packed.insert(packed.begin(), 0xFF); op.payer = nathan_id; @@ -842,7 +842,7 @@ try { store.extensions.value.catalog = "random"; auto packed = fc::raw::pack(store); - packed.insert(packed.begin(), types::account_store); + packed.insert(packed.begin(), types::account_map); packed.insert(packed.begin(), 0xFF); op.payer = alice_id; From e4f0d762339ebd01216e2cca17a2af31bd59822a Mon Sep 17 00:00:00 2001 From: Alfredo Garcia Date: Tue, 10 Sep 2019 18:51:46 -0300 Subject: [PATCH 05/18] leave only general storage code --- libraries/app/api.cpp | 51 - libraries/app/include/graphene/app/api.hpp | 46 +- .../custom_operations/custom_evaluators.cpp | 150 +-- .../custom_operations/custom_operations.cpp | 26 +- .../custom_operations_plugin.cpp | 2 - .../custom_operations/custom_evaluators.hpp | 7 +- .../custom_operations/custom_objects.hpp | 120 +- .../custom_operations/custom_operations.hpp | 67 -- .../custom_operations_plugin.hpp | 8 +- .../wallet/include/graphene/wallet/wallet.hpp | 75 +- libraries/wallet/wallet.cpp | 125 -- tests/cli/main.cpp | 112 +- tests/common/database_fixture.cpp | 5 +- tests/custom_operations/main.cpp | 1012 ++++------------- 14 files changed, 325 insertions(+), 1481 deletions(-) diff --git a/libraries/app/api.cpp b/libraries/app/api.cpp index 483179710c..97d0b455a6 100644 --- a/libraries/app/api.cpp +++ b/libraries/app/api.cpp @@ -687,57 +687,6 @@ namespace graphene { namespace app { } // custom operations api - optional custom_operations_api::get_contact_info(std::string account_id_or_name)const - { - const auto account_id = database_api.get_account_id_from_string(account_id_or_name); - auto &index = _app.chain_database()->get_index_type().indices().get(); - auto itr = index.find(account_id); - if(itr != index.end()) - return *itr; - return optional(); - } - - vector custom_operations_api::get_account_htlc_offers(std::string account_id_or_name, - htlc_order_id_type start, uint32_t limit)const - { - FC_ASSERT(limit <= 101); - - const auto account_id = database_api.get_account_id_from_string(account_id_or_name); - vector results; - auto &index = _app.chain_database()->get_index_type().indices().get(); - - auto itr = index.lower_bound(boost::make_tuple(account_id, start)); - while(itr != index.end() && itr->bitshares_account == account_id && results.size() < limit) - { - results.push_back(*itr); - ++itr; - } - return results; - } - vector custom_operations_api::get_active_htlc_offers(htlc_order_id_type start, uint32_t limit)const - { - FC_ASSERT(limit <= 101); - - vector results; - auto db = _app.chain_database(); - auto &index = db->get_index_type().indices().get(); - auto itr = index.lower_bound(make_tuple(true, db->head_block_time(), start)); - while(itr != index.end() && itr->active && itr->expiration > db->head_block_time() && results.size() < limit) - { - results.push_back(*itr); - ++itr; - } - return results; - } - optional custom_operations_api::get_htlc_offer(htlc_order_id_type id)const - { - auto &index = _app.chain_database()->get_index_type().indices().get(); - auto itr = index.find(id); - if(itr != index.end()) - return *itr; - return optional(); - } - vector custom_operations_api::get_storage_info(std::string account_id_or_name, std::string catalog)const { diff --git a/libraries/app/include/graphene/app/api.hpp b/libraries/app/include/graphene/app/api.hpp index d0e97f76a0..b9480b0932 100644 --- a/libraries/app/include/graphene/app/api.hpp +++ b/libraries/app/include/graphene/app/api.hpp @@ -531,51 +531,11 @@ namespace graphene { namespace app { custom_operations_api(application& app):_app(app), database_api( std::ref(*app.chain_database()), &(app.get_options()) ){} - /** - * @breif Get contact information of an account - * - * @param account Account name to get info from - * - * @return The contact information of the account or empty - */ - optional get_contact_info(std::string account)const; - - /** - * @breif Get htlc offers from an account - * - * @param account Account name to get htlc offers from - * @param start ID of the most recent htlc offer to retrieve - * @param limit Maximum number of order objects to retrieve - * - * @return A vector of htlc offer objects from the account - */ - vector get_account_htlc_offers(std::string account, htlc_order_id_type start, - uint32_t limit)const; - - /** - * @breif Get all active and non expired htlc offers - * - * @param start ID of the most recent htlc offer to retrieve - * @param limit Maximum number of order objects to retrieve - * - * @return A vector of active and non expired htlc offers - */ - vector get_active_htlc_offers(htlc_order_id_type start, uint32_t limit)const; - - /** - * @breif Get htlc order offer by id - * - * @param id ID of the htlc order offer to retrieve - * - * @return A vector of active and non expired htlc offers - */ - optional get_htlc_offer(htlc_order_id_type id)const; - /** * @breif Get all stored objects of an account in a particular catalog * * @param account Account name to get info from - * @param catalog Category classification + * @param catalog Category classification. Each account can store multiple catalogs. * * @return The vector of objects of the account or empty */ @@ -722,10 +682,6 @@ FC_API(graphene::app::orders_api, (get_grouped_limit_orders) ) FC_API(graphene::app::custom_operations_api, - (get_contact_info) - (get_account_htlc_offers) - (get_active_htlc_offers) - (get_htlc_offer) (get_storage_info) ) FC_API(graphene::app::login_api, diff --git a/libraries/plugins/custom_operations/custom_evaluators.cpp b/libraries/plugins/custom_operations/custom_evaluators.cpp index 875e9019c7..9cc9ee0d5e 100644 --- a/libraries/plugins/custom_operations/custom_evaluators.cpp +++ b/libraries/plugins/custom_operations/custom_evaluators.cpp @@ -35,144 +35,98 @@ custom_generic_evaluator::custom_generic_evaluator(database& db, const account_i _account = account; } -void fill_contact_object(account_contact_object& aco, account_id_type account, const account_contact_operation& op) -{ - aco.account = account; - if(op.extensions.value.name.valid()) aco.name = *op.extensions.value.name; - if(op.extensions.value.email.valid()) aco.email = *op.extensions.value.email; - if(op.extensions.value.phone.valid()) aco.phone = *op.extensions.value.phone; - if(op.extensions.value.address.valid()) aco.address = *op.extensions.value.address; - if(op.extensions.value.company.valid()) aco.company = *op.extensions.value.company; - if(op.extensions.value.url.valid()) aco.url = *op.extensions.value.url; -} - -object_id_type custom_generic_evaluator::do_apply(const account_contact_operation& op) -{ - auto &index = _db->get_index_type().indices().get(); - - auto itr = index.find(_account); - if( itr != index.end() ) - { - _db->modify( *itr, [&op, this]( account_contact_object& aco ){ - fill_contact_object(aco, _account, op); - }); - return itr->id; - } - else - { - auto created = _db->create( [&op, this]( account_contact_object& aco ) { - fill_contact_object(aco, _account, op); - }); - return created.id; - } -} - -object_id_type custom_generic_evaluator::do_apply(const create_htlc_order_operation& op) -{ - FC_ASSERT(*op.extensions.value.expiration > _db->head_block_time() + fc::seconds(3600)); - - auto order_time = _db->head_block_time(); - auto created = _db->create( [&op, &order_time, this]( htlc_order_object& hoo ) { - hoo.bitshares_account = _account; - if(op.extensions.value.bitshares_amount.valid()) hoo.bitshares_amount = *op.extensions.value.bitshares_amount; - if(op.extensions.value.blockchain.valid()) hoo.blockchain = *op.extensions.value.blockchain; - if(op.extensions.value.blockchain_account.valid()) hoo.blockchain_account = *op.extensions.value.blockchain_account; - if(op.extensions.value.blockchain_asset.valid()) hoo.blockchain_asset = *op.extensions.value.blockchain_asset; - if(op.extensions.value.blockchain_asset_precision.valid()) hoo.blockchain_asset_precision = - *op.extensions.value.blockchain_asset_precision; - if(op.extensions.value.blockchain_amount.valid()) hoo.blockchain_amount = *op.extensions.value.blockchain_amount; - if(op.extensions.value.expiration.valid()) hoo.expiration = *op.extensions.value.expiration; - if(op.extensions.value.token_contract.valid()) hoo.token_contract = *op.extensions.value.token_contract; - if(op.extensions.value.tag.valid()) hoo.tag = *op.extensions.value.tag; - - hoo.order_time = order_time; - hoo.active = true; - }); - return created.id; -} - -object_id_type custom_generic_evaluator::do_apply(const take_htlc_order_operation& op) -{ - auto &index = _db->get_index_type().indices().get(); - htlc_order_id_type htlc_order_id; - - if(op.extensions.value.htlc_order_id.valid()) { - htlc_order_id = *op.extensions.value.htlc_order_id; - auto itr = index.find(htlc_order_id); - if (itr != index.end()) { - auto close_time = _db->head_block_time(); - _db->modify(*itr, [&op, &close_time, this](htlc_order_object &hoo) { - hoo.active = false; - hoo.taker_bitshares_account = _account; - if (op.extensions.value.blockchain_account.valid()) - hoo.taker_blockchain_account = op.extensions.value.blockchain_account; - hoo.close_time = close_time; - }); - } - } - return htlc_order_id; -} - -object_id_type custom_generic_evaluator::do_apply(const account_storage_map& op) +vector custom_generic_evaluator::do_apply(const account_storage_map& op) { auto &index = _db->get_index_type().indices().get(); + vector results; if (op.extensions.value.remove.valid() && *op.extensions.value.remove) { for(auto const& row: *op.extensions.value.key_values) { auto itr = index.find(make_tuple(_account, *op.extensions.value.catalog, row.first)); - if(itr != index.end()) + if(itr != index.end()) { + results.push_back(itr->id); _db->remove(*itr); + } } } else { for(auto const& row: *op.extensions.value.key_values) { + if(row.first.length() > CUSTOM_OPERATIONS_MAX_KEY_SIZE) + { + wlog("Key can't be bigger than ${max} characters", ("max", CUSTOM_OPERATIONS_MAX_KEY_SIZE)); + continue; + } auto itr = index.find(make_tuple(_account, *op.extensions.value.catalog, row.first)); if(itr == index.end()) { - auto created = _db->create( [&op, this, &row]( account_storage_object& aso ) { - aso.catalog = *op.extensions.value.catalog; - aso.account = _account; - aso.key = row.first; - aso.value = row.second; - }); + try { + auto created = _db->create( [&op, this, &row]( account_storage_object& aso ) { + aso.catalog = *op.extensions.value.catalog; + aso.account = _account; + aso.key = row.first; + aso.value = fc::json::from_string(row.second); + }); + results.push_back(created.id); + } + catch(const fc::parse_error_exception& e) { wdump((e.to_detail_string())); } + catch(const fc::assert_exception& e) { wdump((e.to_detail_string())); } } else { - _db->modify(*itr, [&op, this, &row](account_storage_object &aso) { - aso.value = row.second; - }); + try { + _db->modify(*itr, [&op, this, &row](account_storage_object &aso) { + aso.value = fc::json::from_string(row.second); + }); + results.push_back(itr->id); + } + catch(const fc::parse_error_exception& e) { wdump((e.to_detail_string())); } + catch(const fc::assert_exception& e) { wdump((e.to_detail_string())); } } } } + return results; } -object_id_type custom_generic_evaluator::do_apply(const account_storage_list& op) +vector custom_generic_evaluator::do_apply(const account_storage_list& op) { - auto &index = _db->get_index_type().indices().get(); + auto &index = _db->get_index_type().indices().get(); + vector results; if (op.extensions.value.remove.valid() && *op.extensions.value.remove) { for(auto const& list_value: *op.extensions.value.values) { auto itr = index.find(make_tuple(_account, *op.extensions.value.catalog, list_value)); - if(itr != index.end()) + if(itr != index.end()) { + results.push_back(itr->id); _db->remove(*itr); + } } } else { - for(auto const& list_value: *op.extensions.value.values) { + if(list_value.length() > 200) + { + wlog("List value can't be bigger than ${max} characters", ("max", CUSTOM_OPERATIONS_MAX_KEY_SIZE)); + continue; + } auto itr = index.find(make_tuple(_account, *op.extensions.value.catalog, list_value)); if(itr == index.end()) { - auto created = _db->create( [&op, this, &list_value]( account_storage_object& aso ) { - aso.catalog = *op.extensions.value.catalog; - aso.account = _account; - aso.value = list_value; - }); + try { + auto created = _db->create( + [&op, this, &list_value](account_storage_object &aso) { + aso.catalog = *op.extensions.value.catalog; + aso.account = _account; + aso.subkey = list_value; + }); + results.push_back(itr->id); + } + catch(const fc::assert_exception& e) { wdump((e.to_detail_string())); } } } } + return results; } } } diff --git a/libraries/plugins/custom_operations/custom_operations.cpp b/libraries/plugins/custom_operations/custom_operations.cpp index 80ab429dab..89ff76e59c 100644 --- a/libraries/plugins/custom_operations/custom_operations.cpp +++ b/libraries/plugins/custom_operations/custom_operations.cpp @@ -25,44 +25,22 @@ namespace graphene { namespace custom_operations { -void account_contact_operation::validate()const -{ -} - -void create_htlc_order_operation::validate()const -{ - FC_ASSERT(extensions.value.bitshares_amount.valid()); - FC_ASSERT(extensions.value.bitshares_amount->amount.value > 0); - FC_ASSERT(extensions.value.blockchain.valid()); - FC_ASSERT(extensions.value.blockchain_account.valid()); - FC_ASSERT(extensions.value.blockchain_asset.valid()); - FC_ASSERT(extensions.value.blockchain_amount.valid()); - FC_ASSERT(extensions.value.expiration.valid()); -} - -void take_htlc_order_operation::validate()const -{ - FC_ASSERT(extensions.value.blockchain_account.valid()); - FC_ASSERT(extensions.value.htlc_order_id.valid()); -} - void account_storage_map::validate()const { FC_ASSERT(extensions.value.catalog.valid()); FC_ASSERT(extensions.value.key_values.valid()); FC_ASSERT(extensions.value.key_values->size() <= 10); + FC_ASSERT(extensions.value.catalog->length() <= CUSTOM_OPERATIONS_MAX_KEY_SIZE); } void account_storage_list::validate()const { FC_ASSERT(extensions.value.catalog.valid()); FC_ASSERT(extensions.value.values.valid()); FC_ASSERT(extensions.value.values->size() <= 10); + FC_ASSERT(extensions.value.catalog->length() <= CUSTOM_OPERATIONS_MAX_KEY_SIZE); } } } //graphene::custom_operations -GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::custom_operations::account_contact_operation ) -GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::custom_operations::create_htlc_order_operation ) -GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::custom_operations::take_htlc_order_operation ) GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::custom_operations::account_storage_map ) GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::custom_operations::account_storage_list ) diff --git a/libraries/plugins/custom_operations/custom_operations_plugin.cpp b/libraries/plugins/custom_operations/custom_operations_plugin.cpp index 9c6d3e169f..e5249c52b3 100644 --- a/libraries/plugins/custom_operations/custom_operations_plugin.cpp +++ b/libraries/plugins/custom_operations/custom_operations_plugin.cpp @@ -113,8 +113,6 @@ void custom_operations_plugin::plugin_set_program_options( void custom_operations_plugin::plugin_initialize(const boost::program_options::variables_map& options) { - database().add_index< primary_index< account_contact_index > >(); - database().add_index< primary_index< htlc_orderbook_index > >(); database().add_index< primary_index< account_storage_index > >(); database().applied_block.connect( [this]( const signed_block& b) { diff --git a/libraries/plugins/custom_operations/include/graphene/custom_operations/custom_evaluators.hpp b/libraries/plugins/custom_operations/include/graphene/custom_operations/custom_evaluators.hpp index 32a36fdae0..50d8644105 100644 --- a/libraries/plugins/custom_operations/include/graphene/custom_operations/custom_evaluators.hpp +++ b/libraries/plugins/custom_operations/include/graphene/custom_operations/custom_evaluators.hpp @@ -34,11 +34,8 @@ class custom_generic_evaluator account_id_type _account; custom_generic_evaluator(database& db, const account_id_type account); - object_id_type do_apply(const account_contact_operation& o); - object_id_type do_apply(const create_htlc_order_operation& o); - object_id_type do_apply(const take_htlc_order_operation& o); - object_id_type do_apply(const account_storage_map& o); - object_id_type do_apply(const account_storage_list& o); + vector do_apply(const account_storage_map& o); + vector do_apply(const account_storage_list& o); }; } } diff --git a/libraries/plugins/custom_operations/include/graphene/custom_operations/custom_objects.hpp b/libraries/plugins/custom_operations/include/graphene/custom_operations/custom_objects.hpp index fb24f3f9ae..bba04c807d 100644 --- a/libraries/plugins/custom_operations/include/graphene/custom_operations/custom_objects.hpp +++ b/libraries/plugins/custom_operations/include/graphene/custom_operations/custom_objects.hpp @@ -34,55 +34,11 @@ using namespace chain; #define CUSTOM_OPERATIONS_SPACE_ID 7 #endif -enum types { - account_contact = 0, - create_htlc = 1, - take_htlc = 2, - account_map = 3, - account_list = 4 -}; -enum blockchains { - eos = 0, - bitcoin = 1, - ripple = 2, - ethereum = 3 -}; - -struct account_contact_object : public abstract_object -{ - static const uint8_t space_id = CUSTOM_OPERATIONS_SPACE_ID; - static const uint8_t type_id = account_contact; - - account_id_type account; - optional name; - optional email; - optional phone; - optional address; - optional company; - optional url; -}; +#define CUSTOM_OPERATIONS_MAX_KEY_SIZE (200) -struct htlc_order_object : public abstract_object -{ - static const uint8_t space_id = CUSTOM_OPERATIONS_SPACE_ID; - static const uint8_t type_id = create_htlc; - - account_id_type bitshares_account; - asset bitshares_amount; - blockchains blockchain; - string blockchain_account; - string blockchain_asset; - string blockchain_amount; - fc::time_point_sec expiration; - fc::time_point_sec order_time; - bool active; - - optional blockchain_asset_precision; - optional token_contract; - optional tag; - optional taker_bitshares_account; - optional taker_blockchain_account; - optional close_time; +enum types { + account_map = 0, + account_list = 1 }; struct account_storage_object : public abstract_object @@ -93,50 +49,15 @@ struct account_storage_object : public abstract_object account_id_type account; string catalog; optional key; - string value; + optional subkey; + optional value; }; struct by_custom_id; struct by_custom_account; -typedef multi_index_container< - account_contact_object, - indexed_by< - ordered_non_unique< tag, member< object, object_id_type, &object::id > >, - ordered_unique< tag, - member< account_contact_object, account_id_type, &account_contact_object::account > > - > -> account_contact_multi_index_type; - -typedef generic_index account_contact_index; - -struct by_bitshares_account; -struct by_active; -typedef multi_index_container< - htlc_order_object, - indexed_by< - ordered_non_unique< tag, member< object, object_id_type, &object::id > >, - ordered_unique< tag, - composite_key< htlc_order_object, - member< htlc_order_object, account_id_type, &htlc_order_object::bitshares_account >, - member< object, object_id_type, &object::id > - > - >, - ordered_unique< tag, - composite_key< htlc_order_object, - member< htlc_order_object, bool, &htlc_order_object::active >, - member< htlc_order_object, fc::time_point_sec, &htlc_order_object::expiration >, - member< object, object_id_type, &object::id > - > - > - > -> htlc_orderbook_multi_index_type; - -typedef generic_index htlc_orderbook_index; - struct by_account_catalog; struct by_account_catalog_key; -struct by_account_catalog_value; -struct by_account_catalog_key_value; +struct by_account_catalog_subkey; typedef multi_index_container< account_storage_object, @@ -157,19 +78,11 @@ typedef multi_index_container< member< account_storage_object, optional, &account_storage_object::key > > >, - ordered_unique< tag, - composite_key< account_storage_object, - member< account_storage_object, account_id_type, &account_storage_object::account >, - member< account_storage_object, string, &account_storage_object::catalog >, - member< account_storage_object, string, &account_storage_object::value > - > - >, - ordered_unique< tag, + ordered_non_unique< tag, composite_key< account_storage_object, member< account_storage_object, account_id_type, &account_storage_object::account >, member< account_storage_object, string, &account_storage_object::catalog >, - member< account_storage_object, optional, &account_storage_object::key >, - member< account_storage_object, string, &account_storage_object::value > + member< account_storage_object, optional, &account_storage_object::subkey > > > > @@ -177,21 +90,10 @@ typedef multi_index_container< typedef generic_index account_storage_index; -using account_contact_id_type = object_id; -using htlc_order_id_type = object_id; using account_storage_id_type = object_id; } } //graphene::custom_operations -FC_REFLECT_DERIVED( graphene::custom_operations::account_contact_object, (graphene::db::object), - (account)(name)(email)(phone)(address)(company)(url)) -FC_REFLECT_DERIVED( graphene::custom_operations::htlc_order_object, (graphene::db::object), - (bitshares_account)(bitshares_amount)(blockchain)(blockchain_account)(blockchain_asset) - (blockchain_amount)(expiration)(order_time)(active) - (blockchain_asset_precision)(token_contract)(tag)(taker_bitshares_account) - (taker_blockchain_account)(close_time)) FC_REFLECT_DERIVED( graphene::custom_operations::account_storage_object, (graphene::db::object), - (account)(catalog)(key)(value)) -FC_REFLECT_ENUM( graphene::custom_operations::types, (account_contact)(create_htlc)(take_htlc)(account_map) - (account_list)) -FC_REFLECT_ENUM( graphene::custom_operations::blockchains, (eos)(bitcoin)(ripple)(ethereum) ) + (account)(catalog)(key)(subkey)(value)) +FC_REFLECT_ENUM( graphene::custom_operations::types, (account_map)(account_list)) diff --git a/libraries/plugins/custom_operations/include/graphene/custom_operations/custom_operations.hpp b/libraries/plugins/custom_operations/include/graphene/custom_operations/custom_operations.hpp index 976302ce63..3c28dc2bbd 100644 --- a/libraries/plugins/custom_operations/include/graphene/custom_operations/custom_operations.hpp +++ b/libraries/plugins/custom_operations/include/graphene/custom_operations/custom_operations.hpp @@ -32,56 +32,6 @@ namespace graphene { namespace custom_operations { using namespace std; using graphene::protocol::account_id_type; -struct account_contact_operation : chain::base_operation -{ - struct ext - { - optional name; - optional email; - optional phone; - optional address; - optional company; - optional url; - }; - - graphene::protocol::extension extensions; - - void validate()const; -}; - -struct create_htlc_order_operation : chain::base_operation -{ - struct ext - { - optional bitshares_amount; - optional blockchain; - optional blockchain_account; - optional blockchain_asset; - optional blockchain_asset_precision; - optional blockchain_amount; - optional expiration; - optional token_contract; - optional tag; - }; - - graphene::protocol::extension extensions; - - void validate()const; -}; - -struct take_htlc_order_operation : chain::base_operation -{ - struct ext - { - optional htlc_order_id; - optional blockchain_account; - }; - - graphene::protocol::extension extensions; - - void validate()const; -}; - struct account_storage_map : chain::base_operation { struct ext @@ -113,20 +63,6 @@ struct account_storage_list : chain::base_operation } } //graphene::custom_operations -FC_REFLECT( graphene::custom_operations::account_contact_operation::ext, (name)(email)(phone)(address)(company)(url) ) -FC_REFLECT_TYPENAME( graphene::protocol::extension ) -FC_REFLECT( graphene::custom_operations::account_contact_operation, (extensions) ) - -FC_REFLECT( graphene::custom_operations::create_htlc_order_operation::ext, (bitshares_amount)(blockchain) - (blockchain_account)(blockchain_asset)(blockchain_asset_precision)(blockchain_amount)(expiration) - (token_contract)(tag) ) -FC_REFLECT_TYPENAME( graphene::protocol::extension ) -FC_REFLECT( graphene::custom_operations::create_htlc_order_operation, (extensions) ) - -FC_REFLECT( graphene::custom_operations::take_htlc_order_operation::ext, (htlc_order_id)(blockchain_account) ) -FC_REFLECT_TYPENAME( graphene::protocol::extension ) -FC_REFLECT( graphene::custom_operations::take_htlc_order_operation, (extensions) ) - FC_REFLECT( graphene::custom_operations::account_storage_map::ext, (remove)(catalog)(key_values) ) FC_REFLECT_TYPENAME( graphene::protocol::extension ) FC_REFLECT( graphene::custom_operations::account_storage_map, (extensions) ) @@ -135,8 +71,5 @@ FC_REFLECT( graphene::custom_operations::account_storage_list::ext, (catalog)(va FC_REFLECT_TYPENAME( graphene::protocol::extension ) FC_REFLECT( graphene::custom_operations::account_storage_list, (extensions) ) -GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::custom_operations::account_contact_operation ) -GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::custom_operations::create_htlc_order_operation ) -GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::custom_operations::take_htlc_order_operation ) GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::custom_operations::account_storage_map ) GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::custom_operations::account_storage_list ) diff --git a/libraries/plugins/custom_operations/include/graphene/custom_operations/custom_operations_plugin.hpp b/libraries/plugins/custom_operations/include/graphene/custom_operations/custom_operations_plugin.hpp index d9ee4cdf61..caf3b11cb8 100644 --- a/libraries/plugins/custom_operations/include/graphene/custom_operations/custom_operations_plugin.hpp +++ b/libraries/plugins/custom_operations/include/graphene/custom_operations/custom_operations_plugin.hpp @@ -56,13 +56,7 @@ class custom_operations_plugin : public graphene::app::plugin std::unique_ptr my; }; -typedef fc::static_variant< - account_contact_operation, - create_htlc_order_operation, - take_htlc_order_operation, - account_storage_map, - account_storage_list -> custom_plugin_operation; +typedef fc::static_variant custom_plugin_operation; struct custom_operation_wrapper { uint8_t unused_data; // if first char of custom_op.data is 0xFF we unpack, this char is not used anymore then. diff --git a/libraries/wallet/include/graphene/wallet/wallet.hpp b/libraries/wallet/include/graphene/wallet/wallet.hpp index 229a5df7cf..412fc4998e 100644 --- a/libraries/wallet/include/graphene/wallet/wallet.hpp +++ b/libraries/wallet/include/graphene/wallet/wallet.hpp @@ -2015,76 +2015,6 @@ class wallet_api std::shared_ptr my; void encrypt_keys(); - /** - * Add account contact data by using the custom operations plugin. - * - * Each account can optionally add personal information into the blockchain - * to be retrieved by any interested party. - * - * @param account The account ID or name that we are adding additional information to. - * @param data Contact data to be added. \c account_contact_operation::ext - * @param broadcast true if you wish to broadcast the transaction - * - * @return The signed transaction - */ - signed_transaction set_contact_information(string account, account_contact_operation::ext data, bool broadcast); - - /** - * Get contact data of an account by using the custom operations plugin. - * - * If the account added contact data with @ref set_contact_information an \c account_contact_object will be - * returned. - * - * @param account Account ID or name to get contact data from. - * - * @return An \c account_contact_object or empty if account had not provided contact info yet. - */ - optional get_contact_information(string account); - - /** - * Create an HTLC offer using the custom operations plugin. - * - * The \c custom_operations_plugin maintain a list of HTLC offers to facilitate the exchange - * of tokens between bitshares and another blockchain. - * Applications and individuals can create HTLC offers by the use of this command. - * - * @param account The account in the bitshares side. - * @param data Data to create an HTLC offer. \c create_htlc_order_operation::ext - * @param broadcast true if you wish to broadcast the transaction - * - * @return The signed transaction - */ - signed_transaction create_htlc_offer(string account, create_htlc_order_operation::ext data, bool broadcast); - - /** - * Take an HTLC offer using the custom operation plugin. - * - * The \c custom_operations_plugin maintain a list of HTLC offers to facilitate the exchange - * of tokens between bitshares and another blockchain. - * Applications and individuals can take existing HTLC offers by the use of this command. - * Object state will change from active to inactive after the order is taken and will not be displayed by - * @ref get_active_htlc_offers call anymore. - * - * @param account The account of the taker in the bitshares side. - * @param data Data to take an already created and active HTLC offer. \c take_htlc_order_operation::ext data - * @param broadcast true if you wish to broadcast the transaction - * - * @return The signed transaction - */ - - signed_transaction take_htlc_offer(string account, take_htlc_order_operation::ext data, bool broadcast); - - /** - * Get active HTLC offers by using the custom operation plugin. - * - * Get the list of offers available at this time to initialize HTLC exchange with another blockchain. - * - * @param blockchain eos = 0 , bitcoin = 1, ripple = 2, ethereum = 3 - * - * @return A list of \c htlc_order_object that are active and non expired in the selected blockchain. - */ - vector get_active_htlc_offers(uint16_t blockchain); - /** * Manage account storage map(key->value) by using the custom operations plugin. * @@ -2333,7 +2263,8 @@ FC_API( graphene::wallet::wallet_api, (blind_history) (receive_blind_transfer) (get_order_book) - (set_contact_information) - (get_contact_information) + (account_store_map) + (account_store_list) + (get_account_storage) (quit) ) diff --git a/libraries/wallet/wallet.cpp b/libraries/wallet/wallet.cpp index f534c1806a..075398a3ac 100644 --- a/libraries/wallet/wallet.cpp +++ b/libraries/wallet/wallet.cpp @@ -1961,96 +1961,6 @@ class wallet_api_impl } FC_CAPTURE_AND_RETHROW( (htlc_id)(issuer)(seconds_to_add)(broadcast) ) } - signed_transaction set_contact_information(string account, account_contact_operation::ext data, bool broadcast) - { - try - { - FC_ASSERT( !self.is_locked() ); - - account_id_type account_id = get_account(account).id; - - custom_operation op; - account_contact_operation contact; - contact.extensions.value = data; - - auto packed = fc::raw::pack(contact); - packed.insert(packed.begin(), types::account_contact); - packed.insert(packed.begin(), 0xFF); - - op.payer = account_id; - op.data = packed; - - signed_transaction tx; - tx.operations.push_back(op); - set_operation_fees( tx, _remote_db->get_global_properties().parameters.get_current_fees()); - tx.validate(); - - return sign_transaction(tx, broadcast); - - } FC_CAPTURE_AND_RETHROW( (account)(data)(broadcast) ) - } - - signed_transaction create_htlc_offer(string account, create_htlc_order_operation::ext data, bool broadcast) - { - try - { - FC_ASSERT( !self.is_locked() ); - FC_ASSERT(data.bitshares_amount.valid()); - fc::optional asset_obj = get_asset(data.bitshares_amount->asset_id); - FC_ASSERT(asset_obj, "Could not find asset matching ${asset}", ("asset", data.bitshares_amount->asset_id)); - - account_id_type bitshares_account_id = get_account(account).id; - - custom_operation op; - create_htlc_order_operation htlc; - htlc.extensions.value = data; - - auto packed = fc::raw::pack(htlc); - packed.insert(packed.begin(), types::create_htlc); - packed.insert(packed.begin(), 0xFF); - - op.payer = bitshares_account_id; - op.data = packed; - - signed_transaction tx; - tx.operations.push_back(op); - set_operation_fees( tx, _remote_db->get_global_properties().parameters.get_current_fees()); - tx.validate(); - - return sign_transaction(tx, broadcast); - - } FC_CAPTURE_AND_RETHROW( (account)(data)(broadcast) ) - } - - signed_transaction take_htlc_offer(string account, take_htlc_order_operation::ext data, bool broadcast) - { - try - { - FC_ASSERT( !self.is_locked() ); - - account_id_type bitshares_account_id = get_account(account).id; - - custom_operation op; - take_htlc_order_operation htlc; - htlc.extensions.value = data; - - auto packed = fc::raw::pack(htlc); - packed.insert(packed.begin(), types::take_htlc); - packed.insert(packed.begin(), 0xFF); - - op.payer = bitshares_account_id; - op.data = packed; - - signed_transaction tx; - tx.operations.push_back(op); - set_operation_fees( tx, _remote_db->get_global_properties().parameters.get_current_fees()); - tx.validate(); - - return sign_transaction(tx, broadcast); - - } FC_CAPTURE_AND_RETHROW( (account)(data)(broadcast) ) - } - signed_transaction account_store_map(string account, account_storage_map::ext map, bool broadcast) { try @@ -5337,41 +5247,6 @@ order_book wallet_api::get_order_book( const string& base, const string& quote, } // custom operations -signed_transaction wallet_api::set_contact_information(string account, account_contact_operation::ext data, - bool broadcast) -{ - return my->set_contact_information(account, data, broadcast); -} - -optional wallet_api::get_contact_information(string account) -{ - return my->_custom_operations->get_contact_info(account); -} - -signed_transaction wallet_api::create_htlc_offer(string account, create_htlc_order_operation::ext data, bool broadcast) -{ - return my->create_htlc_offer(account, data, broadcast); -} - -signed_transaction wallet_api::take_htlc_offer(string account, take_htlc_order_operation::ext data, bool broadcast) -{ - return my->take_htlc_offer(account, data, broadcast); -} - -vector wallet_api::get_active_htlc_offers(uint16_t blockchain) -{ - FC_ASSERT(blockchain <= blockchains::ethereum); - - vector results; - auto orders = my->_custom_operations->get_active_htlc_offers(htlc_order_id_type(0), 100); - for(const auto order : orders) - { - if(order.blockchain == static_cast(blockchain)) - results.push_back(order); - } - return results; -} - signed_transaction wallet_api::account_store_map(string account, account_storage_map::ext map, bool broadcast) { return my->account_store_map(account, map, broadcast); diff --git a/tests/cli/main.cpp b/tests/cli/main.cpp index 9a35a34d03..f051f8606b 100644 --- a/tests/cli/main.cpp +++ b/tests/cli/main.cpp @@ -1211,104 +1211,6 @@ BOOST_FIXTURE_TEST_CASE( cli_sign_message, cli_fixture ) } FC_LOG_AND_RETHROW() } -/////////////////// -// Test the contact information by custom operations plugin -/////////////////// -BOOST_FIXTURE_TEST_CASE( account_contact_information, cli_fixture ) -{ - try { - // just to fund nathan - INVOKE(upgrade_nathan_account); - - BOOST_TEST_MESSAGE("Check account information."); - auto account_contact_info = con.wallet_api_ptr->get_contact_information("nathan"); - BOOST_CHECK(!account_contact_info.valid()); // no info yet - - BOOST_TEST_MESSAGE("About to add contact information."); - - account_contact_operation::ext data; - data.name = "Nathan"; - data.email = "nathan@nathan.com"; - data.phone = "2121212121"; - data.address = "Bv DD 22"; - data.company = ""; - data.url = ""; - - signed_transaction custom_tx = con.wallet_api_ptr->set_contact_information("nathan", data, true); - - BOOST_TEST_MESSAGE("The system is generating a block."); - BOOST_CHECK(generate_block(app1)); - - BOOST_TEST_MESSAGE("Check account contact information."); - account_contact_info = con.wallet_api_ptr->get_contact_information("nathan"); - - BOOST_CHECK_EQUAL(account_contact_info->account.instance.value, 17 ); - BOOST_CHECK_EQUAL(*account_contact_info->name, "Nathan"); - BOOST_CHECK_EQUAL(*account_contact_info->email, "nathan@nathan.com"); - BOOST_CHECK_EQUAL(*account_contact_info->phone, "2121212121"); - BOOST_CHECK_EQUAL(*account_contact_info->address, "Bv DD 22"); - BOOST_CHECK_EQUAL(*account_contact_info->company, ""); - BOOST_CHECK_EQUAL(*account_contact_info->url, ""); - - } catch( fc::exception& e ) { - edump((e.to_detail_string())); - throw; - } -} - -/////////////////// -// Test the htlc offer orderbook by custom operations plugin -/////////////////// -BOOST_FIXTURE_TEST_CASE( htlc_orderbook, cli_fixture ) -{ - try { - // create the taker account - INVOKE(create_new_account); - - auto db = app1->chain_database(); - - BOOST_TEST_MESSAGE("Adding an offer."); - - create_htlc_order_operation::ext data_maker; - data_maker.blockchain = blockchains::bitcoin; - data_maker.blockchain_account = "nathan"; - data_maker.bitshares_amount = asset(100); - data_maker.blockchain_asset = "BTC"; - data_maker.blockchain_amount = "2000"; - data_maker.expiration = db->head_block_time() + 7200; - data_maker.tag = "Some text, can be a memo"; - - signed_transaction custom_tx = con.wallet_api_ptr->create_htlc_offer("nathan", data_maker, true); - - BOOST_TEST_MESSAGE("The system is generating a block."); - BOOST_CHECK(generate_block(app1)); - - BOOST_TEST_MESSAGE("Get active htlc offers."); - auto offers = con.wallet_api_ptr->get_active_htlc_offers(blockchains::bitcoin); - if(offers[0].blockchain == blockchains::bitcoin) { - BOOST_CHECK_EQUAL(offers[0].id.instance(), 0); - } - - BOOST_TEST_MESSAGE("Taking the offfer."); - take_htlc_order_operation::ext data_taker; - data_taker.htlc_order_id = offers[0].id; - data_taker.blockchain_account = "nathan"; - - custom_tx = con.wallet_api_ptr->take_htlc_offer("jmjatlanta", data_taker, true); - - BOOST_TEST_MESSAGE("The system is generating a block."); - BOOST_CHECK(generate_block(app1)); - - BOOST_TEST_MESSAGE("Get active htlc offers."); - offers = con.wallet_api_ptr->get_active_htlc_offers(blockchains::bitcoin); - BOOST_CHECK_EQUAL(offers.size(), 0); - - } catch( fc::exception& e ) { - edump((e.to_detail_string())); - throw; - } -} - /////////////////// // Test the general storage by custom operations plugin /////////////////// @@ -1323,8 +1225,8 @@ BOOST_FIXTURE_TEST_CASE( general_storage, cli_fixture ) BOOST_TEST_MESSAGE("Storing in a map."); flat_map pairs; - pairs["key1"] = "value1"; - pairs["key2"] = "value2"; + pairs["key1"] = fc::json::to_string("value1"); + pairs["key2"] = fc::json::to_string("value2"); account_storage_map::ext map; @@ -1343,12 +1245,12 @@ BOOST_FIXTURE_TEST_CASE( general_storage, cli_fixture ) BOOST_CHECK_EQUAL(nathan_map[0].account.instance.value, 17); BOOST_CHECK_EQUAL(nathan_map[0].catalog, "any"); BOOST_CHECK_EQUAL(*nathan_map[0].key, "key1"); - BOOST_CHECK_EQUAL(nathan_map[0].value, "value1"); + BOOST_CHECK_EQUAL(nathan_map[0].value->as_string(), "value1"); BOOST_CHECK_EQUAL(nathan_map[1].id.instance(), 1); BOOST_CHECK_EQUAL(nathan_map[1].account.instance.value, 17); BOOST_CHECK_EQUAL(nathan_map[1].catalog, "any"); BOOST_CHECK_EQUAL(*nathan_map[1].key, "key2"); - BOOST_CHECK_EQUAL(nathan_map[1].value, "value2"); + BOOST_CHECK_EQUAL(nathan_map[1].value->as_string(), "value2"); BOOST_TEST_MESSAGE("Storing in a list."); @@ -1373,15 +1275,15 @@ BOOST_FIXTURE_TEST_CASE( general_storage, cli_fixture ) BOOST_CHECK_EQUAL(nathan_list[0].id.instance(), 2); BOOST_CHECK_EQUAL(nathan_list[0].account.instance.value, 17); BOOST_CHECK_EQUAL(nathan_list[0].catalog, "favourites"); - BOOST_CHECK_EQUAL(nathan_list[0].value, "banana"); + BOOST_CHECK_EQUAL(*nathan_list[0].subkey, "banana"); BOOST_CHECK_EQUAL(nathan_list[1].id.instance(), 3); BOOST_CHECK_EQUAL(nathan_list[1].account.instance.value, 17); BOOST_CHECK_EQUAL(nathan_list[1].catalog, "favourites"); - BOOST_CHECK_EQUAL(nathan_list[1].value, "chocolate"); + BOOST_CHECK_EQUAL(*nathan_list[1].subkey, "chocolate"); BOOST_CHECK_EQUAL(nathan_list[2].id.instance(), 4); BOOST_CHECK_EQUAL(nathan_list[2].account.instance.value, 17); BOOST_CHECK_EQUAL(nathan_list[2].catalog, "favourites"); - BOOST_CHECK_EQUAL(nathan_list[2].value, "milk"); + BOOST_CHECK_EQUAL(*nathan_list[2].subkey, "milk"); } catch( fc::exception& e ) { edump((e.to_detail_string())); diff --git a/tests/common/database_fixture.cpp b/tests/common/database_fixture.cpp index a1e1f60961..87fce373b3 100644 --- a/tests/common/database_fixture.cpp +++ b/tests/common/database_fixture.cpp @@ -332,9 +332,8 @@ database_fixture::database_fixture(const fc::time_point_sec &initial_timestamp) ahiplugin->plugin_startup(); } - if(current_test_name == "custom_operations_account_contact_test" || - current_test_name == "custom_operations_htlc_bitshares_eos_test" || - current_test_name == "custom_operations_account_storage_test") { + if(current_test_name == "custom_operations_account_storage_map_test" || + current_test_name == "custom_operations_account_storage_list_test") { auto custom_operations_plugin = app.register_plugin(); custom_operations_plugin->plugin_set_app(&app); custom_operations_plugin->plugin_initialize(options); diff --git a/tests/custom_operations/main.cpp b/tests/custom_operations/main.cpp index b671dcf291..22ec469f82 100644 --- a/tests/custom_operations/main.cpp +++ b/tests/custom_operations/main.cpp @@ -40,432 +40,235 @@ using namespace graphene::custom_operations; BOOST_FIXTURE_TEST_SUITE( custom_operation_tests, database_fixture ) -BOOST_AUTO_TEST_CASE(custom_operations_account_contact_test) +void map_operation(flat_map& pairs, bool remove, string& catalog, account_id_type& account, + private_key& pk, database& db) +{ + signed_transaction trx; + set_expiration(db, trx); + + custom_operation op; + account_storage_map store; + account_storage_map::ext data; + + store.extensions.value.key_values = pairs; + store.extensions.value.remove = remove; + store.extensions.value.catalog = catalog; + + auto packed = fc::raw::pack(store); + packed.insert(packed.begin(), types::account_map); + packed.insert(packed.begin(), 0xFF); + + op.payer = account; + op.data = packed; + op.fee = db.get_global_properties().parameters.current_fees->calculate_fee(op); + trx.operations.push_back(op); + trx.sign(pk, db.get_chain_id()); + PUSH_TX(db, trx, ~0); + trx.clear(); +} + +void list_operation(flat_set& list, bool remove, string& catalog, account_id_type& account, + private_key& pk, database& db) +{ + signed_transaction trx; + set_expiration(db, trx); + + custom_operation op; + account_storage_list storage_list; + account_storage_list::ext data; + + storage_list.extensions.value.values = list; + storage_list.extensions.value.remove = remove; + storage_list.extensions.value.catalog = "contact_list"; + + auto packed = fc::raw::pack(storage_list); + packed.insert(packed.begin(), types::account_list); + packed.insert(packed.begin(), 0xFF); + + op.payer = account; + op.data = packed; + op.fee = db.get_global_properties().parameters.current_fees->calculate_fee(op); + trx.operations.push_back(op); + trx.sign(pk, db.get_chain_id()); + PUSH_TX(db, trx, ~0); + trx.clear(); +} + +BOOST_AUTO_TEST_CASE(custom_operations_account_storage_map_test) { try { - ACTORS((nathan)(alice)); + ACTORS((nathan)(alice)(robert)(patty)); app.enable_plugin("custom_operations"); custom_operations_api custom_operations_api(app); generate_block(); - fc::usleep(fc::milliseconds(200)); - enable_fees(); - signed_transaction trx; - set_expiration(db, trx); int64_t init_balance(10000 * GRAPHENE_BLOCKCHAIN_PRECISION); transfer(committee_account, nathan_id, asset(init_balance)); transfer(committee_account, alice_id, asset(init_balance)); - // nathan adds account data via custom operation - { - custom_operation op; - account_contact_operation contact; - account_contact_operation::ext data; - - data.name = "Nathan"; - data.email = "nathan@nathan.com"; - data.phone = "+1 434343434343"; - data.address = ""; - data.company = "Bitshares"; - data.url = "http://nathan.com/"; - - contact.extensions.value = data; - - auto packed = fc::raw::pack(contact); - packed.insert(packed.begin(), types::account_contact); - packed.insert(packed.begin(), 0xFF); - - op.payer = nathan_id; - op.data = packed; - op.fee = db.get_global_properties().parameters.current_fees->calculate_fee(op); - trx.operations.push_back(op); - sign(trx, nathan_private_key); - PUSH_TX(db, trx, ~0); - trx.clear(); - } - - // alice adds account data via custom operation - { - custom_operation op; - account_contact_operation contact; - - account_contact_operation::ext data; - data.name = "Alice"; - data.email = "alice@alice.com"; - data.phone = ""; - data.address = "Some Street 456, Somewhere"; - data.company = ""; - data.url = "http://alice.com/"; - - contact.extensions.value = data; - - auto packed = fc::raw::pack(contact); - packed.insert(packed.begin(), types::account_contact); - packed.insert(packed.begin(), 0xFF); - - op.payer = alice_id; - op.data = packed; - op.fee = db.get_global_properties().parameters.current_fees->calculate_fee(op); - trx.operations.push_back(op); - sign(trx, alice_private_key); - PUSH_TX(db, trx, ~0); - trx.clear(); - } - + // catalog is indexed so cant be too big(greater than CUSTOM_OPERATIONS_MAX_KEY_SIZE(200) is not allowed) + std::string catalog(201, 'a'); + flat_map pairs; + pairs["key"] = fc::json::to_string("value"); + map_operation(pairs, false, catalog, nathan_id, nathan_private_key, db); generate_block(); - fc::usleep(fc::milliseconds(200)); - - // check nathan account data with the api - account_contact_object contact_results_nathan = *custom_operations_api.get_contact_info("nathan"); - BOOST_CHECK_EQUAL(contact_results_nathan.account.instance.value, 16 ); - BOOST_CHECK_EQUAL(*contact_results_nathan.name, "Nathan"); - BOOST_CHECK_EQUAL(*contact_results_nathan.email, "nathan@nathan.com"); - BOOST_CHECK_EQUAL(*contact_results_nathan.phone, "+1 434343434343"); - BOOST_CHECK_EQUAL(*contact_results_nathan.address, ""); - BOOST_CHECK_EQUAL(*contact_results_nathan.company, "Bitshares"); - BOOST_CHECK_EQUAL(*contact_results_nathan.url, "http://nathan.com/"); - - // check alice account data with the api - account_contact_object contact_results_alice = *custom_operations_api.get_contact_info("alice"); - BOOST_CHECK_EQUAL(contact_results_alice.account.instance.value, 17 ); - BOOST_CHECK_EQUAL(*contact_results_alice.name, "Alice"); - BOOST_CHECK_EQUAL(*contact_results_alice.email, "alice@alice.com"); - BOOST_CHECK_EQUAL(*contact_results_alice.phone, ""); - BOOST_CHECK_EQUAL(*contact_results_alice.address, "Some Street 456, Somewhere"); - BOOST_CHECK_EQUAL(*contact_results_alice.company, ""); - BOOST_CHECK_EQUAL(*contact_results_alice.url, "http://alice.com/"); - - // alice update her data - { - custom_operation op; - account_contact_operation contact; - - account_contact_operation::ext data; - data.name = "Alice Smith"; - data.email = "alicesmith@alice.com"; - data.phone = "+1 1111 11 1111"; - data.address = "Some Street 456, Somewhere"; - data.company = ""; - data.url = "http://alice.com/"; - - contact.extensions.value = data; - - auto packed = fc::raw::pack(contact); - packed.insert(packed.begin(), types::account_contact); - packed.insert(packed.begin(), 0xFF); - - op.payer = alice_id; - op.data = packed; - op.fee = db.get_global_properties().parameters.current_fees->calculate_fee(op); - trx.operations.push_back(op); - sign(trx, alice_private_key); - PUSH_TX(db, trx, ~0); - trx.clear(); - } + auto storage_results_nathan = custom_operations_api.get_storage_info("nathan", catalog); + BOOST_CHECK_EQUAL(storage_results_nathan.size(), 0 ); + + // keys are indexed so they cant be too big(greater than CUSTOM_OPERATIONS_MAX_KEY_SIZE(200) is not allowed) + catalog = "whatever"; + std::string key(201, 'a'); + pairs.clear(); + pairs[key] = fc::json::to_string("value"); + map_operation(pairs, false, catalog, nathan_id, nathan_private_key, db); generate_block(); - fc::usleep(fc::milliseconds(200)); - - // check alice account updates with the api - contact_results_alice = *custom_operations_api.get_contact_info("alice"); - BOOST_CHECK_EQUAL(contact_results_alice.account.instance.value, 17 ); - BOOST_CHECK_EQUAL(*contact_results_alice.name, "Alice Smith"); - BOOST_CHECK_EQUAL(*contact_results_alice.email, "alicesmith@alice.com"); - BOOST_CHECK_EQUAL(*contact_results_alice.phone, "+1 1111 11 1111"); - BOOST_CHECK_EQUAL(*contact_results_alice.address, "Some Street 456, Somewhere"); - BOOST_CHECK_EQUAL(*contact_results_alice.company, ""); - BOOST_CHECK_EQUAL(*contact_results_alice.url, "http://alice.com/"); - - // alice try to update nathan data - { - custom_operation op; - account_contact_operation contact; - - account_contact_operation::ext data; - data.name = "Not my account"; - data.phone = "Fake phone"; - data.email = "Fake email"; - data.address = "Fake address"; - data.company = "Fake company"; - data.url = "http://fake.com"; - - contact.extensions.value = data; - - auto packed = fc::raw::pack(contact); - packed.insert(packed.begin(), types::account_contact); - packed.insert(packed.begin(), 0xFF); - - op.payer = alice_id; - op.data = packed; - op.fee = db.get_global_properties().parameters.current_fees->calculate_fee(op); - trx.operations.push_back(op); - sign(trx, alice_private_key); - PUSH_TX(db, trx, ~0); - trx.clear(); - } + + storage_results_nathan = custom_operations_api.get_storage_info("nathan", catalog); + BOOST_CHECK_EQUAL(storage_results_nathan.size(), 0 ); + + // nathan adds key-value data via custom operation to a settings catalog + catalog = "settings"; + pairs.clear(); + pairs["language"] = fc::json::to_string("en"); + pairs["image_url"] = fc::json::to_string("http://some.image.url/img.jpg"); + map_operation(pairs, false, catalog, nathan_id, nathan_private_key, db); generate_block(); - fc::usleep(fc::milliseconds(200)); - - // operation will pass but data will be unchanged, exception was produced in plug in - contact_results_nathan = *custom_operations_api.get_contact_info("nathan"); - BOOST_CHECK(contact_results_nathan.account.instance.value == 16 ); - BOOST_CHECK(*contact_results_nathan.name != "Not my account"); - BOOST_CHECK(*contact_results_nathan.phone != "Fake phone"); - BOOST_CHECK(*contact_results_nathan.email != "Fake email"); -} -catch (fc::exception &e) { - edump((e.to_detail_string())); - throw; -} } -BOOST_AUTO_TEST_CASE(custom_operations_htlc_bitshares_eos_test) -{ try { + // check nathan stored data with the api + storage_results_nathan = custom_operations_api.get_storage_info("nathan", "settings"); + BOOST_CHECK_EQUAL(storage_results_nathan.size(), 2 ); + BOOST_CHECK_EQUAL(storage_results_nathan[0].account.instance.value, 16 ); + BOOST_CHECK_EQUAL(*storage_results_nathan[0].key, "image_url"); + BOOST_CHECK_EQUAL(storage_results_nathan[0].value->as_string(), "http://some.image.url/img.jpg"); + BOOST_CHECK_EQUAL(storage_results_nathan[1].account.instance.value, 16 ); + BOOST_CHECK_EQUAL(*storage_results_nathan[1].key, "language"); + BOOST_CHECK_EQUAL(storage_results_nathan[1].value->as_string(), "en"); - ACTORS((nathan)(alice)(bob)(carol)); + // edit some stuff and add new stuff + pairs.clear(); + pairs["image_url"] = fc::json::to_string("http://new.image.url/newimg.jpg"); + pairs["theme"] = fc::json::to_string("dark"); + map_operation(pairs, false, catalog, nathan_id, nathan_private_key, db); + generate_block(); - app.enable_plugin("custom_operations"); - custom_operations_api custom_operations_api(app); + // check old and new stuff + storage_results_nathan = custom_operations_api.get_storage_info("nathan", "settings"); + BOOST_CHECK_EQUAL(storage_results_nathan.size(), 3 ); + BOOST_CHECK_EQUAL(storage_results_nathan[0].account.instance.value, 16 ); + BOOST_CHECK_EQUAL(*storage_results_nathan[0].key, "image_url"); + BOOST_CHECK_EQUAL(storage_results_nathan[0].value->as_string(), "http://new.image.url/newimg.jpg"); + BOOST_CHECK_EQUAL(storage_results_nathan[1].account.instance.value, 16 ); + BOOST_CHECK_EQUAL(*storage_results_nathan[1].key, "language"); + BOOST_CHECK_EQUAL(storage_results_nathan[1].value->as_string(), "en"); + BOOST_CHECK_EQUAL(*storage_results_nathan[2].key, "theme"); + BOOST_CHECK_EQUAL(storage_results_nathan[2].value->as_string(), "dark"); + // delete stuff from the storage + pairs.clear(); + pairs["theme"] = fc::json::to_string("dark"); + map_operation(pairs, true, catalog, nathan_id, nathan_private_key, db); generate_block(); - fc::usleep(fc::milliseconds(200)); - enable_fees(); - signed_transaction trx; - set_expiration(db, trx); + // theme is removed from the storage + storage_results_nathan = custom_operations_api.get_storage_info("nathan", "settings"); + BOOST_CHECK_EQUAL(storage_results_nathan.size(), 2 ); + BOOST_CHECK_EQUAL(storage_results_nathan[0].account.instance.value, 16 ); + BOOST_CHECK_EQUAL(*storage_results_nathan[0].key, "image_url"); + BOOST_CHECK_EQUAL(storage_results_nathan[0].value->as_string(), "http://new.image.url/newimg.jpg"); + BOOST_CHECK_EQUAL(storage_results_nathan[1].account.instance.value, 16 ); + BOOST_CHECK_EQUAL(*storage_results_nathan[1].key, "language"); + BOOST_CHECK_EQUAL(storage_results_nathan[1].value->as_string(), "en"); - int64_t init_balance(10000 * GRAPHENE_BLOCKCHAIN_PRECISION); + // delete stuff that it is not there + pairs.clear(); + pairs["nothere"] = fc::json::to_string("nothere"); + map_operation(pairs, true, catalog, nathan_id, nathan_private_key, db); + generate_block(); - transfer(committee_account, nathan_id, asset(init_balance)); - transfer(committee_account, alice_id, asset(init_balance)); - transfer(committee_account, bob_id, asset(init_balance)); - transfer(committee_account, carol_id, asset(init_balance)); + // nothing changes + storage_results_nathan = custom_operations_api.get_storage_info("nathan", "settings"); + BOOST_CHECK_EQUAL(storage_results_nathan.size(), 2 ); + BOOST_CHECK_EQUAL(storage_results_nathan[0].account.instance.value, 16 ); + BOOST_CHECK_EQUAL(*storage_results_nathan[0].key, "image_url"); + BOOST_CHECK_EQUAL(storage_results_nathan[0].value->as_string(), "http://new.image.url/newimg.jpg"); + BOOST_CHECK_EQUAL(storage_results_nathan[1].account.instance.value, 16 ); + BOOST_CHECK_EQUAL(*storage_results_nathan[1].key, "language"); + BOOST_CHECK_EQUAL(storage_results_nathan[1].value->as_string(), "en"); - enable_fees(); + // add more than 10 storage items in 1 operation is not allowed + pairs.clear(); + pairs["key1"] = fc::json::to_string("value1"); + pairs["key2"] = fc::json::to_string("value2"); + pairs["key3"] = fc::json::to_string("value3"); + pairs["key4"] = fc::json::to_string("value4"); + pairs["key5"] = fc::json::to_string("value5"); + pairs["key6"] = fc::json::to_string("value6"); + pairs["key7"] = fc::json::to_string("value7"); + pairs["key8"] = fc::json::to_string("value8"); + pairs["key9"] = fc::json::to_string("value9"); + pairs["key10"] = fc::json::to_string("value10"); + pairs["key11"] = fc::json::to_string("value11"); + + map_operation(pairs, false, catalog, nathan_id, nathan_private_key, db); + generate_block(); - // alice creates an order - { - custom_operation op; - create_htlc_order_operation htlc; - - create_htlc_order_operation::ext data; - data.blockchain = blockchains::eos; - data.blockchain_account = "alice"; - data.bitshares_amount = asset(10); - data.blockchain_asset = "EOS"; - data.blockchain_amount = "10"; - data.expiration = db.head_block_time() + fc::seconds(7200); - - htlc.extensions.value = data; - - auto packed = fc::raw::pack(htlc); - packed.insert(packed.begin(), types::create_htlc); - packed.insert(packed.begin(), 0xFF); - - op.payer = alice_id; - op.data = packed; - op.fee = db.get_global_properties().parameters.current_fees->calculate_fee(op); - trx.operations.push_back(op); - sign(trx, alice_private_key); - PUSH_TX(db, trx, ~0); - trx.clear(); - } - - // bob creates an order - { - custom_operation op; - create_htlc_order_operation htlc; - - create_htlc_order_operation::ext data; - data.blockchain = blockchains::eos; - data.blockchain_account = "bob"; - data.bitshares_amount = asset(100); - data.blockchain_asset = "EOS"; - data.blockchain_amount = "100"; - data.expiration = db.head_block_time() + fc::seconds(7200); - data.tag = "Some text, can be a memo"; - - htlc.extensions.value = data; - - auto packed = fc::raw::pack(htlc); - packed.insert(packed.begin(), types::create_htlc); - packed.insert(packed.begin(), 0xFF); - - op.payer = bob_id; - op.data = packed; - op.fee = db.get_global_properties().parameters.current_fees->calculate_fee(op); - trx.operations.push_back(op); - sign(trx, bob_private_key); - PUSH_TX(db, trx, ~0); - trx.clear(); - } - - // carol creates an order with missing information (blockchain_amount), will fail in the validator - { - custom_operation op; - create_htlc_order_operation htlc; - - create_htlc_order_operation::ext data; - data.blockchain = blockchains::eos; - data.blockchain_account = "carol"; - data.bitshares_amount = asset(10); - data.blockchain_asset = "EOS"; - data.expiration = db.head_block_time() + fc::seconds(7200); - - htlc.extensions.value = data; - - auto packed = fc::raw::pack(htlc); - packed.insert(packed.begin(), types::create_htlc); - packed.insert(packed.begin(), 0xFF); - - op.payer = carol_id; - op.data = packed; - op.fee = db.get_global_properties().parameters.current_fees->calculate_fee(op); - trx.operations.push_back(op); - sign(trx, carol_private_key); - PUSH_TX(db, trx, ~0); - trx.clear(); - } + // alice, duplicated keys in storage, only second value will be added + pairs.clear(); + catalog = "random"; + pairs["key1"] = fc::json::to_string("value1"); + pairs["key1"] = fc::json::to_string("value2"); + map_operation(pairs, false, catalog, alice_id, alice_private_key, db); + generate_block(); + vector storage_results_alice = custom_operations_api.get_storage_info("alice", "random"); + BOOST_CHECK_EQUAL(storage_results_alice.size(), 1 ); + BOOST_CHECK_EQUAL(storage_results_alice[0].account.instance.value, 17 ); + BOOST_CHECK_EQUAL(*storage_results_alice[0].key, "key1"); + BOOST_CHECK_EQUAL(storage_results_alice[0].value->as_string(), "value2"); + + // add an object + pairs.clear(); + catalog = "account_object"; + pairs["nathan"] = fc::json::to_string(nathan); + map_operation(pairs, false, catalog, alice_id, alice_private_key, db); generate_block(); - fc::usleep(fc::milliseconds(200)); - - // test the get_account_htlc_offers api call for alice - vector htlc_offers_results_alice = custom_operations_api.get_account_htlc_offers("alice", - htlc_order_id_type(0), 100); - BOOST_CHECK_EQUAL(htlc_offers_results_alice.size(), 1); - BOOST_CHECK_EQUAL(htlc_offers_results_alice[0].id.instance(), 0); - BOOST_CHECK_EQUAL(htlc_offers_results_alice[0].bitshares_account.instance.value, 17); - BOOST_CHECK_EQUAL(htlc_offers_results_alice[0].blockchain_account, "alice" ); - BOOST_CHECK_EQUAL(htlc_offers_results_alice[0].bitshares_amount.asset_id.instance.value, 0); - BOOST_CHECK_EQUAL(htlc_offers_results_alice[0].bitshares_amount.amount.value, 10); - BOOST_CHECK_EQUAL(htlc_offers_results_alice[0].blockchain_asset, "EOS"); - BOOST_CHECK_EQUAL(htlc_offers_results_alice[0].blockchain_amount, "10"); - BOOST_CHECK(htlc_offers_results_alice[0].active); - - // test the get_htlc_offer api call with alice order - auto htlc_offer = custom_operations_api.get_htlc_offer(htlc_order_id_type(0)); - BOOST_CHECK_EQUAL(htlc_offer->id.instance(), 0); - BOOST_CHECK_EQUAL(htlc_offer->bitshares_account.instance.value, 17); - BOOST_CHECK_EQUAL(htlc_offer->blockchain_account, "alice" ); - BOOST_CHECK_EQUAL(htlc_offer->bitshares_amount.asset_id.instance.value, 0); - BOOST_CHECK_EQUAL(htlc_offer->bitshares_amount.amount.value, 10); - BOOST_CHECK_EQUAL(htlc_offer->blockchain_asset, "EOS"); - BOOST_CHECK_EQUAL(htlc_offer->blockchain_amount, "10"); - BOOST_CHECK(htlc_offer->active); - - // test the get_account_htlc_offers api call for bob - vector htlc_offers_results_bob = custom_operations_api.get_account_htlc_offers("bob", - htlc_order_id_type(0), 100); - - BOOST_CHECK_EQUAL(htlc_offers_results_bob.size(), 1); - BOOST_CHECK_EQUAL(htlc_offers_results_bob[0].id.instance(), 1); - BOOST_CHECK_EQUAL(htlc_offers_results_bob[0].bitshares_account.instance.value, 18); - BOOST_CHECK_EQUAL(htlc_offers_results_bob[0].blockchain_account, "bob" ); - BOOST_CHECK_EQUAL(htlc_offers_results_bob[0].bitshares_amount.asset_id.instance.value, 0); - BOOST_CHECK_EQUAL(htlc_offers_results_bob[0].bitshares_amount.amount.value, 100); - BOOST_CHECK_EQUAL(htlc_offers_results_bob[0].blockchain_asset, "EOS"); - BOOST_CHECK_EQUAL(htlc_offers_results_bob[0].blockchain_amount, "100"); - BOOST_CHECK(htlc_offers_results_bob[0].active); - if(htlc_offers_results_bob[0].tag.valid()) - BOOST_CHECK_EQUAL(*htlc_offers_results_bob[0].tag, "Some text, can be a memo"); - - // get all active offers - vector htlc_offers_results_active = custom_operations_api.get_active_htlc_offers( - htlc_order_id_type(0), 100); - - BOOST_CHECK_EQUAL(htlc_offers_results_active.size(), 2); - BOOST_CHECK_EQUAL(htlc_offers_results_active[0].id.instance(), 0); - BOOST_CHECK_EQUAL(htlc_offers_results_active[0].bitshares_account.instance.value, 17); - BOOST_CHECK_EQUAL(htlc_offers_results_active[0].blockchain_account, "alice" ); - BOOST_CHECK_EQUAL(htlc_offers_results_active[0].bitshares_amount.asset_id.instance.value, 0); - BOOST_CHECK_EQUAL(htlc_offers_results_active[0].bitshares_amount.amount.value, 10); - BOOST_CHECK_EQUAL(htlc_offers_results_active[0].blockchain_asset, "EOS"); - BOOST_CHECK_EQUAL(htlc_offers_results_active[0].blockchain_amount, "10"); - BOOST_CHECK(htlc_offers_results_active[0].active); - - BOOST_CHECK_EQUAL(htlc_offers_results_active[1].id.instance(), 1); - BOOST_CHECK_EQUAL(htlc_offers_results_active[1].bitshares_account.instance.value, 18); - BOOST_CHECK_EQUAL(htlc_offers_results_active[1].blockchain_account, "bob" ); - BOOST_CHECK_EQUAL(htlc_offers_results_active[1].bitshares_amount.asset_id.instance.value, 0); - BOOST_CHECK_EQUAL(htlc_offers_results_active[1].bitshares_amount.amount.value, 100); - BOOST_CHECK_EQUAL(htlc_offers_results_active[1].blockchain_asset, "EOS"); - BOOST_CHECK_EQUAL(htlc_offers_results_active[1].blockchain_amount, "100"); - BOOST_CHECK(htlc_offers_results_active[1].active); - if(htlc_offers_results_active[0].tag.valid()) - BOOST_CHECK_EQUAL(*htlc_offers_results_active[0].tag, "Some text, can be a memo"); - - // nathan takes alice order - { - custom_operation op; - take_htlc_order_operation htlc; - - take_htlc_order_operation::ext data; - data.htlc_order_id = htlc_offers_results_alice[0].id; - data.blockchain_account = "nathan"; - - htlc.extensions.value = data; - - auto packed = fc::raw::pack(htlc); - packed.insert(packed.begin(), types::take_htlc); - packed.insert(packed.begin(), 0xFF); - - op.payer = nathan_id; - op.data = packed; - op.fee = db.get_global_properties().parameters.current_fees->calculate_fee(op); - trx.operations.push_back(op); - sign(trx, nathan_private_key); - PUSH_TX(db, trx, ~0); - trx.clear(); - } + + storage_results_alice = custom_operations_api.get_storage_info("alice", "account_object"); + BOOST_CHECK_EQUAL(storage_results_alice.size(), 1); + BOOST_CHECK_EQUAL(storage_results_alice[0].account.instance.value, 17); + BOOST_CHECK_EQUAL(*storage_results_alice[0].key, "nathan"); + BOOST_CHECK_EQUAL(storage_results_alice[0].value->as(20).name, "nathan"); + + // add 2 more objects + pairs.clear(); + catalog = "account_object"; + pairs["robert"] = fc::json::to_string(robert); + pairs["patty"] = fc::json::to_string(patty); + map_operation(pairs, false, catalog, alice_id, alice_private_key, db); generate_block(); - fc::usleep(fc::milliseconds(200)); - - // check the taken object - htlc_offer = custom_operations_api.get_htlc_offer(htlc_order_id_type(0)); - BOOST_CHECK_EQUAL(htlc_offer->id.instance(), 0); - BOOST_CHECK_EQUAL(htlc_offer->bitshares_account.instance.value, 17); - BOOST_CHECK_EQUAL(htlc_offer->blockchain_account, "alice" ); - BOOST_CHECK_EQUAL(htlc_offer->bitshares_amount.asset_id.instance.value, 0); - BOOST_CHECK_EQUAL(htlc_offer->bitshares_amount.amount.value, 10); - BOOST_CHECK_EQUAL(htlc_offer->blockchain_asset, "EOS"); - BOOST_CHECK_EQUAL(htlc_offer->blockchain_amount, "10"); - BOOST_CHECK(!htlc_offer->active); - BOOST_CHECK_EQUAL(htlc_offer->taker_bitshares_account->instance.value, 16); - BOOST_CHECK_EQUAL(*htlc_offer->taker_blockchain_account, "nathan"); - - // alice order was taken, bob order still up for get_active_htlc_offers - htlc_offers_results_active = custom_operations_api.get_active_htlc_offers(htlc_order_id_type(0), 100); - BOOST_CHECK_EQUAL(htlc_offers_results_active.size(), 1); - - BOOST_CHECK_EQUAL(htlc_offers_results_active[0].id.instance(), 1); - BOOST_CHECK_EQUAL(htlc_offers_results_active[0].bitshares_account.instance.value, 18); - BOOST_CHECK_EQUAL(htlc_offers_results_active[0].blockchain_account, "bob"); - BOOST_CHECK_EQUAL(htlc_offers_results_active[0].bitshares_amount.asset_id.instance.value, 0); - BOOST_CHECK_EQUAL(htlc_offers_results_active[0].bitshares_amount.amount.value, 100); - BOOST_CHECK_EQUAL(htlc_offers_results_active[0].blockchain_asset, "EOS"); - BOOST_CHECK_EQUAL(htlc_offers_results_active[0].blockchain_amount, "100"); - BOOST_CHECK(htlc_offers_results_active[0].active); - if(htlc_offers_results_active[0].tag.valid()) - BOOST_CHECK_EQUAL(*htlc_offers_results_active[0].tag, "Some text, can be a memo"); - - // make bob order expire - generate_blocks(7201); - fc::usleep(fc::milliseconds(200)); - - htlc_offers_results_active = custom_operations_api.get_active_htlc_offers(htlc_order_id_type(0), 100); - BOOST_CHECK_EQUAL(htlc_offers_results_active.size(), 0); -} + storage_results_alice = custom_operations_api.get_storage_info("alice", "account_object"); + BOOST_CHECK_EQUAL(storage_results_alice.size(), 3); + BOOST_CHECK_EQUAL(storage_results_alice[0].account.instance.value, 17); + BOOST_CHECK_EQUAL(*storage_results_alice[0].key, "nathan"); + BOOST_CHECK_EQUAL(storage_results_alice[0].value->as(20).name, "nathan"); + BOOST_CHECK_EQUAL(storage_results_alice[1].account.instance.value, 17); + BOOST_CHECK_EQUAL(*storage_results_alice[1].key, "patty"); + BOOST_CHECK_EQUAL(storage_results_alice[1].value->as(20).name, "patty"); + BOOST_CHECK_EQUAL(*storage_results_alice[2].key, "robert"); + BOOST_CHECK_EQUAL(storage_results_alice[2].value->as(20).name, "robert"); +} catch (fc::exception &e) { edump((e.to_detail_string())); throw; } } -BOOST_AUTO_TEST_CASE(custom_operations_account_storage_test) +BOOST_AUTO_TEST_CASE(custom_operations_account_storage_list_test) { try { ACTORS((nathan)(alice)(robert)(patty)); @@ -474,434 +277,107 @@ try { custom_operations_api custom_operations_api(app); generate_block(); - fc::usleep(fc::milliseconds(200)); - enable_fees(); - signed_transaction trx; - set_expiration(db, trx); int64_t init_balance(10000 * GRAPHENE_BLOCKCHAIN_PRECISION); transfer(committee_account, nathan_id, asset(init_balance)); transfer(committee_account, alice_id, asset(init_balance)); - // nathan adds key-value data via custom operation to a settings catalog - { - custom_operation op; - account_storage_map store; - account_storage_map::ext data; - - flat_map pairs; - pairs["language"] = "en"; - pairs["image_url"] = "http://some.image.url/img.jpg"; - - store.extensions.value.key_values = pairs; - store.extensions.value.catalog = "settings"; - - auto packed = fc::raw::pack(store); - packed.insert(packed.begin(), types::account_map); - packed.insert(packed.begin(), 0xFF); - - op.payer = nathan_id; - op.data = packed; - op.fee = db.get_global_properties().parameters.current_fees->calculate_fee(op); - trx.operations.push_back(op); - sign(trx, nathan_private_key); - PUSH_TX(db, trx, ~0); - trx.clear(); - } + // catalog is indexed so cant be too big(greater than CUSTOM_OPERATIONS_MAX_KEY_SIZE(200) is not allowed) + std::string catalog(201, 'a'); + flat_set accounts; + accounts.insert(robert.name); + list_operation(accounts, false, catalog, nathan_id, nathan_private_key, db); + generate_block(); + + auto storage_results_nathan = custom_operations_api.get_storage_info("nathan", catalog); + BOOST_CHECK_EQUAL(storage_results_nathan.size(), 0 ); + // keys are indexed so they cant be too big(greater than CUSTOM_OPERATIONS_MAX_KEY_SIZE(200) is not allowed) + catalog = "whatever"; + std::string value(201, 'a'); + accounts.clear(); + accounts.insert(value); + list_operation(accounts, false, catalog, nathan_id, nathan_private_key, db); generate_block(); - fc::usleep(fc::milliseconds(200)); - // check nathan stored data with the api - vector storage_results_nathan = custom_operations_api.get_storage_info("nathan", "settings"); - BOOST_CHECK_EQUAL(storage_results_nathan.size(), 2 ); - BOOST_CHECK_EQUAL(storage_results_nathan[0].account.instance.value, 16 ); - BOOST_CHECK_EQUAL(*storage_results_nathan[0].key, "image_url"); - BOOST_CHECK_EQUAL(storage_results_nathan[0].value, "http://some.image.url/img.jpg"); - BOOST_CHECK_EQUAL(storage_results_nathan[1].account.instance.value, 16 ); - BOOST_CHECK_EQUAL(*storage_results_nathan[1].key, "language"); - BOOST_CHECK_EQUAL(storage_results_nathan[1].value, "en"); + storage_results_nathan = custom_operations_api.get_storage_info("nathan", catalog); + BOOST_CHECK_EQUAL(storage_results_nathan.size(), 0 ); // nathan add a list of accounts to storage - { - custom_operation op; - account_storage_list list; - account_storage_list::ext data; - - flat_set accounts; - accounts.insert(alice.name); - accounts.insert(robert.name); - - list.extensions.value.values = accounts; - list.extensions.value.catalog = "contact_list"; - - auto packed = fc::raw::pack(list); - packed.insert(packed.begin(), types::account_list); - packed.insert(packed.begin(), 0xFF); - - op.payer = nathan_id; - op.data = packed; - op.fee = db.get_global_properties().parameters.current_fees->calculate_fee(op); - trx.operations.push_back(op); - sign(trx, nathan_private_key); - PUSH_TX(db, trx, ~0); - trx.clear(); - } - + accounts.clear(); + accounts.insert(alice.name); + accounts.insert(robert.name); + catalog = "contact_list"; + list_operation(accounts, false, catalog, nathan_id, nathan_private_key, db); generate_block(); - fc::usleep(fc::milliseconds(200)); // get the account list for nathan, check alice and robert are there storage_results_nathan = custom_operations_api.get_storage_info("nathan", "contact_list"); BOOST_CHECK_EQUAL(storage_results_nathan.size(), 2 ); BOOST_CHECK_EQUAL(storage_results_nathan[0].account.instance.value, 16 ); - BOOST_CHECK_EQUAL(storage_results_nathan[0].value, alice.name); + BOOST_CHECK_EQUAL(*storage_results_nathan[0].subkey, robert.name); BOOST_CHECK_EQUAL(storage_results_nathan[1].account.instance.value, 16 ); - BOOST_CHECK_EQUAL(storage_results_nathan[1].value, robert.name); + BOOST_CHECK_EQUAL(*storage_results_nathan[1].subkey, alice.name); // add a value into account list already there - { - custom_operation op; - account_storage_list list; - account_storage_list::ext data; - - flat_set accounts; - accounts.insert(alice.name); - - list.extensions.value.values = accounts; - list.extensions.value.catalog = "contact_list"; - - auto packed = fc::raw::pack(list); - packed.insert(packed.begin(), types::account_list); - packed.insert(packed.begin(), 0xFF); - - op.payer = nathan_id; - op.data = packed; - op.fee = db.get_global_properties().parameters.current_fees->calculate_fee(op); - trx.operations.push_back(op); - sign(trx, nathan_private_key); - PUSH_TX(db, trx, ~0); - trx.clear(); - } - + accounts.clear(); + accounts.insert(alice.name); + list_operation(accounts, false, catalog, nathan_id, nathan_private_key, db); generate_block(); - fc::usleep(fc::milliseconds(200)); // nothing changes storage_results_nathan = custom_operations_api.get_storage_info("nathan", "contact_list"); BOOST_CHECK_EQUAL(storage_results_nathan.size(), 2 ); BOOST_CHECK_EQUAL(storage_results_nathan[0].account.instance.value, 16 ); - BOOST_CHECK_EQUAL(storage_results_nathan[0].value, alice.name); + BOOST_CHECK_EQUAL(*storage_results_nathan[0].subkey, robert.name); BOOST_CHECK_EQUAL(storage_results_nathan[1].account.instance.value, 16 ); - BOOST_CHECK_EQUAL(storage_results_nathan[1].value, robert.name); + BOOST_CHECK_EQUAL(*storage_results_nathan[1].subkey, alice.name); // delete alice from the list - { - custom_operation op; - account_storage_list list; - account_storage_list::ext data; - - flat_set accounts; - accounts.insert(alice.name); - - list.extensions.value.values = accounts; - list.extensions.value.remove = true; - list.extensions.value.catalog = "contact_list"; - - auto packed = fc::raw::pack(list); - packed.insert(packed.begin(), types::account_list); - packed.insert(packed.begin(), 0xFF); - - op.payer = nathan_id; - op.data = packed; - op.fee = db.get_global_properties().parameters.current_fees->calculate_fee(op); - trx.operations.push_back(op); - sign(trx, nathan_private_key); - PUSH_TX(db, trx, ~0); - trx.clear(); - } - + accounts.clear(); + accounts.insert(alice.name); + list_operation(accounts, true, catalog, nathan_id, nathan_private_key, db); generate_block(); - fc::usleep(fc::milliseconds(200)); // alice gone storage_results_nathan = custom_operations_api.get_storage_info("nathan", "contact_list"); BOOST_CHECK_EQUAL(storage_results_nathan.size(), 1 ); BOOST_CHECK_EQUAL(storage_results_nathan[0].account.instance.value, 16 ); - BOOST_CHECK_EQUAL(storage_results_nathan[0].value, robert.name); - - // add and edit more stuff to the storage - { - custom_operation op; - account_storage_map store; - account_storage_map::ext data; - - flat_map pairs; - pairs["image_url"] = "http://new.image.url/newimg.jpg"; - pairs["theme"] = "dark"; - - store.extensions.value.key_values = pairs; - store.extensions.value.catalog = "settings"; - - auto packed = fc::raw::pack(store); - packed.insert(packed.begin(), types::account_map); - packed.insert(packed.begin(), 0xFF); - - op.payer = nathan_id; - op.data = packed; - op.fee = db.get_global_properties().parameters.current_fees->calculate_fee(op); - trx.operations.push_back(op); - sign(trx, nathan_private_key); - PUSH_TX(db, trx, ~0); - trx.clear(); - } - - generate_block(); - fc::usleep(fc::milliseconds(200)); - - // check old and new stuff - storage_results_nathan = custom_operations_api.get_storage_info("nathan", "settings"); - BOOST_CHECK_EQUAL(storage_results_nathan.size(), 3 ); - BOOST_CHECK_EQUAL(storage_results_nathan[0].account.instance.value, 16 ); - BOOST_CHECK_EQUAL(*storage_results_nathan[0].key, "image_url"); - BOOST_CHECK_EQUAL(storage_results_nathan[0].value, "http://new.image.url/newimg.jpg"); - BOOST_CHECK_EQUAL(storage_results_nathan[1].account.instance.value, 16 ); - BOOST_CHECK_EQUAL(*storage_results_nathan[1].key, "language"); - BOOST_CHECK_EQUAL(storage_results_nathan[1].value, "en"); - BOOST_CHECK_EQUAL(*storage_results_nathan[2].key, "theme"); - BOOST_CHECK_EQUAL(storage_results_nathan[2].value, "dark"); - - // delete stuff from the storage - { - custom_operation op; - account_storage_map store; - account_storage_map::ext data; - - flat_map pairs; - pairs["theme"] = "dark"; - - store.extensions.value.key_values = pairs; - store.extensions.value.remove = true; - store.extensions.value.catalog = "settings"; - - auto packed = fc::raw::pack(store); - packed.insert(packed.begin(), types::account_map); - packed.insert(packed.begin(), 0xFF); - - op.payer = nathan_id; - op.data = packed; - op.fee = db.get_global_properties().parameters.current_fees->calculate_fee(op); - trx.operations.push_back(op); - sign(trx, nathan_private_key); - PUSH_TX(db, trx, ~0); - trx.clear(); - } - - generate_block(); - fc::usleep(fc::milliseconds(200)); - - // theme is removed from the storage - storage_results_nathan = custom_operations_api.get_storage_info("nathan", "settings"); - BOOST_CHECK_EQUAL(storage_results_nathan.size(), 2 ); - BOOST_CHECK_EQUAL(storage_results_nathan[0].account.instance.value, 16 ); - BOOST_CHECK_EQUAL(*storage_results_nathan[0].key, "image_url"); - BOOST_CHECK_EQUAL(storage_results_nathan[0].value, "http://new.image.url/newimg.jpg"); - BOOST_CHECK_EQUAL(storage_results_nathan[1].account.instance.value, 16 ); - BOOST_CHECK_EQUAL(*storage_results_nathan[1].key, "language"); - BOOST_CHECK_EQUAL(storage_results_nathan[1].value, "en"); - - // delete stuff that it is not there - { - custom_operation op; - account_storage_map store; - account_storage_map::ext data; - - flat_map pairs; - pairs["nothere"] = "nothere"; - - store.extensions.value.key_values = pairs; - store.extensions.value.remove = true; - store.extensions.value.catalog = "settings"; - - auto packed = fc::raw::pack(store); - packed.insert(packed.begin(), types::account_map); - packed.insert(packed.begin(), 0xFF); - - op.payer = nathan_id; - op.data = packed; - op.fee = db.get_global_properties().parameters.current_fees->calculate_fee(op); - trx.operations.push_back(op); - sign(trx, nathan_private_key); - PUSH_TX(db, trx, ~0); - trx.clear(); - } - - generate_block(); - fc::usleep(fc::milliseconds(200)); - - // nothing changes - storage_results_nathan = custom_operations_api.get_storage_info("nathan", "settings"); - BOOST_CHECK_EQUAL(storage_results_nathan.size(), 2 ); - BOOST_CHECK_EQUAL(storage_results_nathan[0].account.instance.value, 16 ); - BOOST_CHECK_EQUAL(*storage_results_nathan[0].key, "image_url"); - BOOST_CHECK_EQUAL(storage_results_nathan[0].value, "http://new.image.url/newimg.jpg"); - BOOST_CHECK_EQUAL(storage_results_nathan[1].account.instance.value, 16 ); - BOOST_CHECK_EQUAL(*storage_results_nathan[1].key, "language"); - BOOST_CHECK_EQUAL(storage_results_nathan[1].value, "en"); - - // add more than 10 storage items in 1 operation is not allowed - { - custom_operation op; - account_storage_map store; - account_storage_map::ext data; - - flat_map pairs; - pairs["key1"] = "value1"; - pairs["key2"] = "value2"; - pairs["key3"] = "value3"; - pairs["key4"] = "value4"; - pairs["key5"] = "value5"; - pairs["key6"] = "value6"; - pairs["key7"] = "value7"; - pairs["key8"] = "value8"; - pairs["key9"] = "value9"; - pairs["key10"] = "value10"; - pairs["key11"] = "value11"; - - store.extensions.value.key_values = pairs; - store.extensions.value.catalog = "settings"; - - auto packed = fc::raw::pack(store); - packed.insert(packed.begin(), types::account_map); - packed.insert(packed.begin(), 0xFF); - - op.payer = nathan_id; - op.data = packed; - op.fee = db.get_global_properties().parameters.current_fees->calculate_fee(op); - trx.operations.push_back(op); - sign(trx, nathan_private_key); - PUSH_TX(db, trx, ~0); - trx.clear(); - } - - generate_block(); - fc::usleep(fc::milliseconds(200)); + BOOST_CHECK_EQUAL(*storage_results_nathan[0].subkey, robert.name); // add more than 10 accounts to the list in 1 operation is not allowed - { - custom_operation op; - account_storage_list list; - account_storage_list::ext data; - - flat_set accounts; - accounts.insert("init0"); - accounts.insert("init1"); - accounts.insert("init2"); - accounts.insert("init3"); - accounts.insert("init4"); - accounts.insert("init5"); - accounts.insert("init6"); - accounts.insert("init7"); - accounts.insert("init8"); - accounts.insert("init9"); - accounts.insert("init10"); - - list.extensions.value.values = accounts; - list.extensions.value.catalog = "contact_list"; - list.extensions.value.remove = true; - - auto packed = fc::raw::pack(list); - packed.insert(packed.begin(), types::account_list); - packed.insert(packed.begin(), 0xFF); - - op.payer = nathan_id; - op.data = packed; - op.fee = db.get_global_properties().parameters.current_fees->calculate_fee(op); - trx.operations.push_back(op); - sign(trx, nathan_private_key); - PUSH_TX(db, trx, ~0); - trx.clear(); - } - - generate_block(); - fc::usleep(fc::milliseconds(200)); - - // alice, duplicated keys in storage, only second value will be added - { - custom_operation op; - account_storage_map store; - account_storage_map::ext data; - - flat_map pairs; - pairs["key1"] = "value1"; - pairs["key1"] = "value2"; - - store.extensions.value.key_values = pairs; - store.extensions.value.catalog = "random"; - - auto packed = fc::raw::pack(store); - packed.insert(packed.begin(), types::account_map); - packed.insert(packed.begin(), 0xFF); - - op.payer = alice_id; - op.data = packed; - op.fee = db.get_global_properties().parameters.current_fees->calculate_fee(op); - trx.operations.push_back(op); - sign(trx, alice_private_key); - PUSH_TX(db, trx, ~0); - trx.clear(); - } - + accounts.clear(); + accounts.insert("init0"); + accounts.insert("init1"); + accounts.insert("init2"); + accounts.insert("init3"); + accounts.insert("init4"); + accounts.insert("init5"); + accounts.insert("init6"); + accounts.insert("init7"); + accounts.insert("init8"); + accounts.insert("init9"); + accounts.insert("init10"); + list_operation(accounts, false, catalog, nathan_id, nathan_private_key, db); generate_block(); - fc::usleep(fc::milliseconds(200)); - - vector storage_results_alice = custom_operations_api.get_storage_info("alice", "random"); - BOOST_CHECK_EQUAL(storage_results_alice.size(), 1 ); - BOOST_CHECK_EQUAL(storage_results_alice[0].account.instance.value, 17 ); - BOOST_CHECK_EQUAL(*storage_results_alice[0].key, "key1"); - BOOST_CHECK_EQUAL(storage_results_alice[0].value, "value2"); // duplicated accounts in the list, only 1 will be inserted - { - custom_operation op; - account_storage_list list; - account_storage_list::ext data; - - flat_set accounts; - accounts.insert(robert.name); - accounts.insert(robert.name); - - list.extensions.value.values = accounts; - list.extensions.value.catalog = "contact_list"; - - auto packed = fc::raw::pack(list); - packed.insert(packed.begin(), types::account_list); - packed.insert(packed.begin(), 0xFF); - - op.payer = alice_id; - op.data = packed; - op.fee = db.get_global_properties().parameters.current_fees->calculate_fee(op); - trx.operations.push_back(op); - sign(trx, alice_private_key); - PUSH_TX(db, trx, ~0); - trx.clear(); - } - + accounts.clear(); + accounts.insert(robert.name); + accounts.insert(robert.name); + list_operation(accounts, false, catalog, alice_id, alice_private_key, db); generate_block(); - fc::usleep(fc::milliseconds(200)); - storage_results_alice = custom_operations_api.get_storage_info("alice", "contact_list"); + auto storage_results_alice = custom_operations_api.get_storage_info("alice", "contact_list"); BOOST_CHECK_EQUAL(storage_results_alice.size(), 1 ); BOOST_CHECK_EQUAL(storage_results_alice[0].account.instance.value, 17 ); - BOOST_CHECK_EQUAL(storage_results_alice[0].value, robert.name); - + BOOST_CHECK_EQUAL(*storage_results_alice[0].subkey, robert.name); } catch (fc::exception &e) { edump((e.to_detail_string())); throw; } } - BOOST_AUTO_TEST_SUITE_END() From 0f8560be0186f0323bc456683fdc3ec3d06e86b3 Mon Sep 17 00:00:00 2001 From: Alfredo Garcia Date: Wed, 11 Sep 2019 12:44:36 -0300 Subject: [PATCH 06/18] change logs to dlog, remove not needed catch --- .../custom_operations/custom_evaluators.cpp | 26 +++++++------------ .../custom_operations_plugin.cpp | 2 +- tests/custom_operations/main.cpp | 10 +++++++ 3 files changed, 21 insertions(+), 17 deletions(-) diff --git a/libraries/plugins/custom_operations/custom_evaluators.cpp b/libraries/plugins/custom_operations/custom_evaluators.cpp index 9cc9ee0d5e..372ecb630c 100644 --- a/libraries/plugins/custom_operations/custom_evaluators.cpp +++ b/libraries/plugins/custom_operations/custom_evaluators.cpp @@ -54,7 +54,7 @@ vector custom_generic_evaluator::do_apply(const account_storage_ for(auto const& row: *op.extensions.value.key_values) { if(row.first.length() > CUSTOM_OPERATIONS_MAX_KEY_SIZE) { - wlog("Key can't be bigger than ${max} characters", ("max", CUSTOM_OPERATIONS_MAX_KEY_SIZE)); + dlog("Key can't be bigger than ${max} characters", ("max", CUSTOM_OPERATIONS_MAX_KEY_SIZE)); continue; } auto itr = index.find(make_tuple(_account, *op.extensions.value.catalog, row.first)); @@ -69,8 +69,7 @@ vector custom_generic_evaluator::do_apply(const account_storage_ }); results.push_back(created.id); } - catch(const fc::parse_error_exception& e) { wdump((e.to_detail_string())); } - catch(const fc::assert_exception& e) { wdump((e.to_detail_string())); } + catch(const fc::parse_error_exception& e) { dlog(e.to_detail_string()); } } else { @@ -80,8 +79,7 @@ vector custom_generic_evaluator::do_apply(const account_storage_ }); results.push_back(itr->id); } - catch(const fc::parse_error_exception& e) { wdump((e.to_detail_string())); } - catch(const fc::assert_exception& e) { wdump((e.to_detail_string())); } + catch(const fc::parse_error_exception& e) { dlog((e.to_detail_string())); } } } } @@ -107,22 +105,18 @@ vector custom_generic_evaluator::do_apply(const account_storage_ for(auto const& list_value: *op.extensions.value.values) { if(list_value.length() > 200) { - wlog("List value can't be bigger than ${max} characters", ("max", CUSTOM_OPERATIONS_MAX_KEY_SIZE)); + dlog("List value can't be bigger than ${max} characters", ("max", CUSTOM_OPERATIONS_MAX_KEY_SIZE)); continue; } auto itr = index.find(make_tuple(_account, *op.extensions.value.catalog, list_value)); if(itr == index.end()) { - try { - auto created = _db->create( - [&op, this, &list_value](account_storage_object &aso) { - aso.catalog = *op.extensions.value.catalog; - aso.account = _account; - aso.subkey = list_value; - }); - results.push_back(itr->id); - } - catch(const fc::assert_exception& e) { wdump((e.to_detail_string())); } + auto created = _db->create([&op, this, &list_value](account_storage_object &aso) { + aso.catalog = *op.extensions.value.catalog; + aso.account = _account; + aso.subkey = list_value; + }); + results.push_back(itr->id); } } } diff --git a/libraries/plugins/custom_operations/custom_operations_plugin.cpp b/libraries/plugins/custom_operations/custom_operations_plugin.cpp index e5249c52b3..caf2f750ba 100644 --- a/libraries/plugins/custom_operations/custom_operations_plugin.cpp +++ b/libraries/plugins/custom_operations/custom_operations_plugin.cpp @@ -73,7 +73,7 @@ void custom_operations_plugin_impl::onBlock( const signed_block& b ) unpacked.op.visit(vtor); } catch (fc::exception e) { // only api node will know if the unpack, validate or apply fails - wlog("Error: ${ex} in operation: ${op}", ("ex", e.to_detail_string())("op", fc::json::to_string(custom_op))); + dlog("Error: ${ex} in operation: ${op}", ("ex", e.to_detail_string())("op", fc::json::to_string(custom_op))); continue; } } diff --git a/tests/custom_operations/main.cpp b/tests/custom_operations/main.cpp index 22ec469f82..783de79489 100644 --- a/tests/custom_operations/main.cpp +++ b/tests/custom_operations/main.cpp @@ -131,6 +131,16 @@ try { storage_results_nathan = custom_operations_api.get_storage_info("nathan", catalog); BOOST_CHECK_EQUAL(storage_results_nathan.size(), 0 ); + // creating a map with bad json as value is not allowed + catalog = "whatever"; + pairs.clear(); + pairs["key"] = "value"; + map_operation(pairs, false, catalog, nathan_id, nathan_private_key, db); + generate_block(); + + storage_results_nathan = custom_operations_api.get_storage_info("nathan", catalog); + BOOST_CHECK_EQUAL(storage_results_nathan.size(), 0 ); + // nathan adds key-value data via custom operation to a settings catalog catalog = "settings"; pairs.clear(); From ff11b33193aac157a8837fd9d1e7d7555eaa875f Mon Sep 17 00:00:00 2001 From: Alfredo Garcia Date: Wed, 11 Sep 2019 15:37:29 -0300 Subject: [PATCH 07/18] remove subkey --- .../plugins/custom_operations/custom_evaluators.cpp | 4 ++-- .../graphene/custom_operations/custom_objects.hpp | 11 +---------- tests/cli/main.cpp | 6 +++--- tests/custom_operations/main.cpp | 12 ++++++------ 4 files changed, 12 insertions(+), 21 deletions(-) diff --git a/libraries/plugins/custom_operations/custom_evaluators.cpp b/libraries/plugins/custom_operations/custom_evaluators.cpp index 372ecb630c..6a97619e19 100644 --- a/libraries/plugins/custom_operations/custom_evaluators.cpp +++ b/libraries/plugins/custom_operations/custom_evaluators.cpp @@ -87,7 +87,7 @@ vector custom_generic_evaluator::do_apply(const account_storage_ } vector custom_generic_evaluator::do_apply(const account_storage_list& op) { - auto &index = _db->get_index_type().indices().get(); + auto &index = _db->get_index_type().indices().get(); vector results; if (op.extensions.value.remove.valid() && *op.extensions.value.remove) @@ -114,7 +114,7 @@ vector custom_generic_evaluator::do_apply(const account_storage_ auto created = _db->create([&op, this, &list_value](account_storage_object &aso) { aso.catalog = *op.extensions.value.catalog; aso.account = _account; - aso.subkey = list_value; + aso.key = list_value; }); results.push_back(itr->id); } diff --git a/libraries/plugins/custom_operations/include/graphene/custom_operations/custom_objects.hpp b/libraries/plugins/custom_operations/include/graphene/custom_operations/custom_objects.hpp index bba04c807d..4488538af0 100644 --- a/libraries/plugins/custom_operations/include/graphene/custom_operations/custom_objects.hpp +++ b/libraries/plugins/custom_operations/include/graphene/custom_operations/custom_objects.hpp @@ -49,7 +49,6 @@ struct account_storage_object : public abstract_object account_id_type account; string catalog; optional key; - optional subkey; optional value; }; @@ -57,7 +56,6 @@ struct by_custom_id; struct by_custom_account; struct by_account_catalog; struct by_account_catalog_key; -struct by_account_catalog_subkey; typedef multi_index_container< account_storage_object, @@ -77,13 +75,6 @@ typedef multi_index_container< member< account_storage_object, string, &account_storage_object::catalog >, member< account_storage_object, optional, &account_storage_object::key > > - >, - ordered_non_unique< tag, - composite_key< account_storage_object, - member< account_storage_object, account_id_type, &account_storage_object::account >, - member< account_storage_object, string, &account_storage_object::catalog >, - member< account_storage_object, optional, &account_storage_object::subkey > - > > > > account_storage_multi_index_type; @@ -95,5 +86,5 @@ using account_storage_id_type = object_id Date: Wed, 11 Sep 2019 20:07:01 -0300 Subject: [PATCH 08/18] remove complexity from operations by replacing extension by normal fields --- .../custom_operations/custom_evaluators.cpp | 24 ++++----- .../custom_operations/custom_operations.cpp | 12 ++--- .../custom_operations/custom_objects.hpp | 4 +- .../custom_operations/custom_operations.hpp | 32 +++-------- .../wallet/include/graphene/wallet/wallet.hpp | 14 +++-- libraries/wallet/wallet.cpp | 28 ++++++---- tests/cli/main.cpp | 24 +++------ tests/custom_operations/main.cpp | 54 +++++++++---------- 8 files changed, 87 insertions(+), 105 deletions(-) diff --git a/libraries/plugins/custom_operations/custom_evaluators.cpp b/libraries/plugins/custom_operations/custom_evaluators.cpp index 6a97619e19..50eec94c96 100644 --- a/libraries/plugins/custom_operations/custom_evaluators.cpp +++ b/libraries/plugins/custom_operations/custom_evaluators.cpp @@ -40,10 +40,10 @@ vector custom_generic_evaluator::do_apply(const account_storage_ auto &index = _db->get_index_type().indices().get(); vector results; - if (op.extensions.value.remove.valid() && *op.extensions.value.remove) + if (op.remove) { - for(auto const& row: *op.extensions.value.key_values) { - auto itr = index.find(make_tuple(_account, *op.extensions.value.catalog, row.first)); + for(auto const& row: op.key_values) { + auto itr = index.find(make_tuple(_account, op.catalog, row.first)); if(itr != index.end()) { results.push_back(itr->id); _db->remove(*itr); @@ -51,18 +51,18 @@ vector custom_generic_evaluator::do_apply(const account_storage_ } } else { - for(auto const& row: *op.extensions.value.key_values) { + for(auto const& row: op.key_values) { if(row.first.length() > CUSTOM_OPERATIONS_MAX_KEY_SIZE) { dlog("Key can't be bigger than ${max} characters", ("max", CUSTOM_OPERATIONS_MAX_KEY_SIZE)); continue; } - auto itr = index.find(make_tuple(_account, *op.extensions.value.catalog, row.first)); + auto itr = index.find(make_tuple(_account, op.catalog, row.first)); if(itr == index.end()) { try { auto created = _db->create( [&op, this, &row]( account_storage_object& aso ) { - aso.catalog = *op.extensions.value.catalog; + aso.catalog = op.catalog; aso.account = _account; aso.key = row.first; aso.value = fc::json::from_string(row.second); @@ -90,11 +90,11 @@ vector custom_generic_evaluator::do_apply(const account_storage_ auto &index = _db->get_index_type().indices().get(); vector results; - if (op.extensions.value.remove.valid() && *op.extensions.value.remove) + if (op.remove) { - for(auto const& list_value: *op.extensions.value.values) { + for(auto const& list_value: op.values) { - auto itr = index.find(make_tuple(_account, *op.extensions.value.catalog, list_value)); + auto itr = index.find(make_tuple(_account, op.catalog, list_value)); if(itr != index.end()) { results.push_back(itr->id); _db->remove(*itr); @@ -102,17 +102,17 @@ vector custom_generic_evaluator::do_apply(const account_storage_ } } else { - for(auto const& list_value: *op.extensions.value.values) { + for(auto const& list_value: op.values) { if(list_value.length() > 200) { dlog("List value can't be bigger than ${max} characters", ("max", CUSTOM_OPERATIONS_MAX_KEY_SIZE)); continue; } - auto itr = index.find(make_tuple(_account, *op.extensions.value.catalog, list_value)); + auto itr = index.find(make_tuple(_account, op.catalog, list_value)); if(itr == index.end()) { auto created = _db->create([&op, this, &list_value](account_storage_object &aso) { - aso.catalog = *op.extensions.value.catalog; + aso.catalog = op.catalog; aso.account = _account; aso.key = list_value; }); diff --git a/libraries/plugins/custom_operations/custom_operations.cpp b/libraries/plugins/custom_operations/custom_operations.cpp index 89ff76e59c..2e5bfd1c4d 100644 --- a/libraries/plugins/custom_operations/custom_operations.cpp +++ b/libraries/plugins/custom_operations/custom_operations.cpp @@ -27,17 +27,13 @@ namespace graphene { namespace custom_operations { void account_storage_map::validate()const { - FC_ASSERT(extensions.value.catalog.valid()); - FC_ASSERT(extensions.value.key_values.valid()); - FC_ASSERT(extensions.value.key_values->size() <= 10); - FC_ASSERT(extensions.value.catalog->length() <= CUSTOM_OPERATIONS_MAX_KEY_SIZE); + FC_ASSERT(key_values.size() <= 10); + FC_ASSERT(catalog.length() <= CUSTOM_OPERATIONS_MAX_KEY_SIZE && catalog.length() > 0); } void account_storage_list::validate()const { - FC_ASSERT(extensions.value.catalog.valid()); - FC_ASSERT(extensions.value.values.valid()); - FC_ASSERT(extensions.value.values->size() <= 10); - FC_ASSERT(extensions.value.catalog->length() <= CUSTOM_OPERATIONS_MAX_KEY_SIZE); + FC_ASSERT(values.size() <= 10); + FC_ASSERT(catalog.length() <= CUSTOM_OPERATIONS_MAX_KEY_SIZE && catalog.length() > 0); } } } //graphene::custom_operations diff --git a/libraries/plugins/custom_operations/include/graphene/custom_operations/custom_objects.hpp b/libraries/plugins/custom_operations/include/graphene/custom_operations/custom_objects.hpp index 4488538af0..9b910f99d1 100644 --- a/libraries/plugins/custom_operations/include/graphene/custom_operations/custom_objects.hpp +++ b/libraries/plugins/custom_operations/include/graphene/custom_operations/custom_objects.hpp @@ -48,7 +48,7 @@ struct account_storage_object : public abstract_object account_id_type account; string catalog; - optional key; + string key; optional value; }; @@ -73,7 +73,7 @@ typedef multi_index_container< composite_key< account_storage_object, member< account_storage_object, account_id_type, &account_storage_object::account >, member< account_storage_object, string, &account_storage_object::catalog >, - member< account_storage_object, optional, &account_storage_object::key > + member< account_storage_object, string, &account_storage_object::key > > > > diff --git a/libraries/plugins/custom_operations/include/graphene/custom_operations/custom_operations.hpp b/libraries/plugins/custom_operations/include/graphene/custom_operations/custom_operations.hpp index 3c28dc2bbd..0c5a61de9e 100644 --- a/libraries/plugins/custom_operations/include/graphene/custom_operations/custom_operations.hpp +++ b/libraries/plugins/custom_operations/include/graphene/custom_operations/custom_operations.hpp @@ -34,42 +34,26 @@ using graphene::protocol::account_id_type; struct account_storage_map : chain::base_operation { - struct ext - { - optional remove; - optional catalog; - optional> key_values; - }; - - graphene::protocol::extension extensions; + bool remove; + string catalog; + flat_map key_values; void validate()const; }; struct account_storage_list : chain::base_operation { - struct ext - { - optional remove; - optional catalog; - optional> values; - }; - - graphene::protocol::extension extensions; + bool remove; + string catalog; + flat_set values; void validate()const; }; - } } //graphene::custom_operations -FC_REFLECT( graphene::custom_operations::account_storage_map::ext, (remove)(catalog)(key_values) ) -FC_REFLECT_TYPENAME( graphene::protocol::extension ) -FC_REFLECT( graphene::custom_operations::account_storage_map, (extensions) ) - -FC_REFLECT( graphene::custom_operations::account_storage_list::ext, (catalog)(values)(remove) ) -FC_REFLECT_TYPENAME( graphene::protocol::extension ) -FC_REFLECT( graphene::custom_operations::account_storage_list, (extensions) ) +FC_REFLECT( graphene::custom_operations::account_storage_map, (remove)(catalog)(key_values) ) +FC_REFLECT( graphene::custom_operations::account_storage_list, (remove)(catalog)(values) ) GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::custom_operations::account_storage_map ) GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::custom_operations::account_storage_list ) diff --git a/libraries/wallet/include/graphene/wallet/wallet.hpp b/libraries/wallet/include/graphene/wallet/wallet.hpp index 412fc4998e..6942733432 100644 --- a/libraries/wallet/include/graphene/wallet/wallet.hpp +++ b/libraries/wallet/include/graphene/wallet/wallet.hpp @@ -2022,12 +2022,15 @@ class wallet_api * to be retrieved by any interested party. * * @param account The account ID or name that we are adding additional information to. - * @param data Storage data to be added. \c account_storage_map::ext + * @param catalog The name of the catalog the operation will insert data to. + * @param remove true if you want to remove stuff from a catalog. + * @param key_values The map to be inserted/removed to/from the catalog * @param broadcast true if you wish to broadcast the transaction * * @return The signed transaction */ - signed_transaction account_store_map(string account, account_storage_map::ext map, bool broadcast); + signed_transaction account_store_map(string account, string catalog, bool remove, + flat_map key_values, bool broadcast); /** * Manage a accounts list(values)by using the custom operations plugin. @@ -2036,12 +2039,15 @@ class wallet_api * to be retrieved by any interested party. Can be used as a whitelist, address book, etc. * * @param account The account ID or name that we are adding additional information to. - * @param data List data to be added. \c account_storage_list::ext + * @param catalog The name of the catalog the operation will insert data to. + * @param remove true if you want to remove stuff from a catalog. + * @param values The list of strings to be inserted/removed to/from the catalog. * @param broadcast true if you wish to broadcast the transaction * * @return The signed transaction */ - signed_transaction account_store_list(string account, account_storage_list::ext data, bool broadcast); + signed_transaction account_store_list(string account, string catalog, bool remove, flat_set values, + bool broadcast); /** * Get \c account_storage_object of an account by using the custom operations plugin. diff --git a/libraries/wallet/wallet.cpp b/libraries/wallet/wallet.cpp index 075398a3ac..f1f2c67709 100644 --- a/libraries/wallet/wallet.cpp +++ b/libraries/wallet/wallet.cpp @@ -1961,7 +1961,8 @@ class wallet_api_impl } FC_CAPTURE_AND_RETHROW( (htlc_id)(issuer)(seconds_to_add)(broadcast) ) } - signed_transaction account_store_map(string account, account_storage_map::ext map, bool broadcast) + signed_transaction account_store_map(string account, string catalog, bool remove, flat_map key_values, + bool broadcast) { try { @@ -1971,7 +1972,9 @@ class wallet_api_impl custom_operation op; account_storage_map store; - store.extensions.value = map; + store.catalog = catalog; + store.remove = remove; + store.key_values = key_values; auto packed = fc::raw::pack(store); packed.insert(packed.begin(), types::account_map); @@ -1987,10 +1990,11 @@ class wallet_api_impl return sign_transaction(tx, broadcast); - } FC_CAPTURE_AND_RETHROW( (account)(map)(broadcast) ) + } FC_CAPTURE_AND_RETHROW( (account)(remove)(catalog)(key_values)(broadcast) ) } - signed_transaction account_store_list(string account, account_storage_list::ext list, bool broadcast) + signed_transaction account_store_list(string account, string catalog, bool remove, flat_set values, + bool broadcast) { try { @@ -2000,7 +2004,9 @@ class wallet_api_impl custom_operation op; account_storage_list store; - store.extensions.value = list; + store.remove = remove; + store.catalog = catalog; + store.values = values; auto packed = fc::raw::pack(store); packed.insert(packed.begin(), types::account_list); @@ -2016,7 +2022,7 @@ class wallet_api_impl return sign_transaction(tx, broadcast); - } FC_CAPTURE_AND_RETHROW( (account)(list)(broadcast) ) + } FC_CAPTURE_AND_RETHROW( (account)(remove)(catalog)(values)(broadcast) ) } vector< vesting_balance_object_with_info > get_vesting_balances( string account_name ) @@ -5247,14 +5253,16 @@ order_book wallet_api::get_order_book( const string& base, const string& quote, } // custom operations -signed_transaction wallet_api::account_store_map(string account, account_storage_map::ext map, bool broadcast) +signed_transaction wallet_api::account_store_map(string account, string catalog, bool remove, + flat_map key_values, bool broadcast) { - return my->account_store_map(account, map, broadcast); + return my->account_store_map(account, catalog, remove, key_values, broadcast); } -signed_transaction wallet_api::account_store_list(string account, account_storage_list::ext list, bool broadcast) +signed_transaction wallet_api::account_store_list(string account, string catalog, bool remove, flat_set values, + bool broadcast) { - return my->account_store_list(account, list, broadcast); + return my->account_store_list(account, catalog, remove, values, broadcast); } vector wallet_api::get_account_storage(string account, string catalog) diff --git a/tests/cli/main.cpp b/tests/cli/main.cpp index f13b8f64c1..8f8c82cd91 100644 --- a/tests/cli/main.cpp +++ b/tests/cli/main.cpp @@ -1228,12 +1228,7 @@ BOOST_FIXTURE_TEST_CASE( general_storage, cli_fixture ) pairs["key1"] = fc::json::to_string("value1"); pairs["key2"] = fc::json::to_string("value2"); - account_storage_map::ext map; - - map.key_values = pairs; - map.catalog = "any"; - - con.wallet_api_ptr->account_store_map("nathan", map, true); + con.wallet_api_ptr->account_store_map("nathan", "any", false, pairs, true); BOOST_TEST_MESSAGE("The system is generating a block."); BOOST_CHECK(generate_block(app1)); @@ -1244,12 +1239,12 @@ BOOST_FIXTURE_TEST_CASE( general_storage, cli_fixture ) BOOST_CHECK_EQUAL(nathan_map[0].id.instance(), 0); BOOST_CHECK_EQUAL(nathan_map[0].account.instance.value, 17); BOOST_CHECK_EQUAL(nathan_map[0].catalog, "any"); - BOOST_CHECK_EQUAL(*nathan_map[0].key, "key1"); + BOOST_CHECK_EQUAL(nathan_map[0].key, "key1"); BOOST_CHECK_EQUAL(nathan_map[0].value->as_string(), "value1"); BOOST_CHECK_EQUAL(nathan_map[1].id.instance(), 1); BOOST_CHECK_EQUAL(nathan_map[1].account.instance.value, 17); BOOST_CHECK_EQUAL(nathan_map[1].catalog, "any"); - BOOST_CHECK_EQUAL(*nathan_map[1].key, "key2"); + BOOST_CHECK_EQUAL(nathan_map[1].key, "key2"); BOOST_CHECK_EQUAL(nathan_map[1].value->as_string(), "value2"); BOOST_TEST_MESSAGE("Storing in a list."); @@ -1259,12 +1254,7 @@ BOOST_FIXTURE_TEST_CASE( general_storage, cli_fixture ) favs.insert("milk"); favs.insert("banana"); - account_storage_list::ext list; - - list.values = favs; - list.catalog = "favourites"; - - con.wallet_api_ptr->account_store_list("nathan", list, true); + con.wallet_api_ptr->account_store_list("nathan", "favourites", false, favs, true); BOOST_TEST_MESSAGE("The system is generating a block."); BOOST_CHECK(generate_block(app1)); @@ -1275,15 +1265,15 @@ BOOST_FIXTURE_TEST_CASE( general_storage, cli_fixture ) BOOST_CHECK_EQUAL(nathan_list[0].id.instance(), 2); BOOST_CHECK_EQUAL(nathan_list[0].account.instance.value, 17); BOOST_CHECK_EQUAL(nathan_list[0].catalog, "favourites"); - BOOST_CHECK_EQUAL(*nathan_list[0].key, "banana"); + BOOST_CHECK_EQUAL(nathan_list[0].key, "banana"); BOOST_CHECK_EQUAL(nathan_list[1].id.instance(), 3); BOOST_CHECK_EQUAL(nathan_list[1].account.instance.value, 17); BOOST_CHECK_EQUAL(nathan_list[1].catalog, "favourites"); - BOOST_CHECK_EQUAL(*nathan_list[1].key, "chocolate"); + BOOST_CHECK_EQUAL(nathan_list[1].key, "chocolate"); BOOST_CHECK_EQUAL(nathan_list[2].id.instance(), 4); BOOST_CHECK_EQUAL(nathan_list[2].account.instance.value, 17); BOOST_CHECK_EQUAL(nathan_list[2].catalog, "favourites"); - BOOST_CHECK_EQUAL(*nathan_list[2].key, "milk"); + BOOST_CHECK_EQUAL(nathan_list[2].key, "milk"); } catch( fc::exception& e ) { edump((e.to_detail_string())); diff --git a/tests/custom_operations/main.cpp b/tests/custom_operations/main.cpp index 5539c69d56..8eeabbefbd 100644 --- a/tests/custom_operations/main.cpp +++ b/tests/custom_operations/main.cpp @@ -48,11 +48,10 @@ void map_operation(flat_map& pairs, bool remove, string& catalog custom_operation op; account_storage_map store; - account_storage_map::ext data; - store.extensions.value.key_values = pairs; - store.extensions.value.remove = remove; - store.extensions.value.catalog = catalog; + store.key_values = pairs; + store.remove = remove; + store.catalog = catalog; auto packed = fc::raw::pack(store); packed.insert(packed.begin(), types::account_map); @@ -75,11 +74,10 @@ void list_operation(flat_set& list, bool remove, string& catalog, accoun custom_operation op; account_storage_list storage_list; - account_storage_list::ext data; - storage_list.extensions.value.values = list; - storage_list.extensions.value.remove = remove; - storage_list.extensions.value.catalog = "contact_list"; + storage_list.values = list; + storage_list.remove = remove; + storage_list.catalog = "contact_list"; auto packed = fc::raw::pack(storage_list); packed.insert(packed.begin(), types::account_list); @@ -153,10 +151,10 @@ try { storage_results_nathan = custom_operations_api.get_storage_info("nathan", "settings"); BOOST_CHECK_EQUAL(storage_results_nathan.size(), 2 ); BOOST_CHECK_EQUAL(storage_results_nathan[0].account.instance.value, 16 ); - BOOST_CHECK_EQUAL(*storage_results_nathan[0].key, "image_url"); + BOOST_CHECK_EQUAL(storage_results_nathan[0].key, "image_url"); BOOST_CHECK_EQUAL(storage_results_nathan[0].value->as_string(), "http://some.image.url/img.jpg"); BOOST_CHECK_EQUAL(storage_results_nathan[1].account.instance.value, 16 ); - BOOST_CHECK_EQUAL(*storage_results_nathan[1].key, "language"); + BOOST_CHECK_EQUAL(storage_results_nathan[1].key, "language"); BOOST_CHECK_EQUAL(storage_results_nathan[1].value->as_string(), "en"); // edit some stuff and add new stuff @@ -170,12 +168,12 @@ try { storage_results_nathan = custom_operations_api.get_storage_info("nathan", "settings"); BOOST_CHECK_EQUAL(storage_results_nathan.size(), 3 ); BOOST_CHECK_EQUAL(storage_results_nathan[0].account.instance.value, 16 ); - BOOST_CHECK_EQUAL(*storage_results_nathan[0].key, "image_url"); + BOOST_CHECK_EQUAL(storage_results_nathan[0].key, "image_url"); BOOST_CHECK_EQUAL(storage_results_nathan[0].value->as_string(), "http://new.image.url/newimg.jpg"); BOOST_CHECK_EQUAL(storage_results_nathan[1].account.instance.value, 16 ); - BOOST_CHECK_EQUAL(*storage_results_nathan[1].key, "language"); + BOOST_CHECK_EQUAL(storage_results_nathan[1].key, "language"); BOOST_CHECK_EQUAL(storage_results_nathan[1].value->as_string(), "en"); - BOOST_CHECK_EQUAL(*storage_results_nathan[2].key, "theme"); + BOOST_CHECK_EQUAL(storage_results_nathan[2].key, "theme"); BOOST_CHECK_EQUAL(storage_results_nathan[2].value->as_string(), "dark"); // delete stuff from the storage @@ -188,10 +186,10 @@ try { storage_results_nathan = custom_operations_api.get_storage_info("nathan", "settings"); BOOST_CHECK_EQUAL(storage_results_nathan.size(), 2 ); BOOST_CHECK_EQUAL(storage_results_nathan[0].account.instance.value, 16 ); - BOOST_CHECK_EQUAL(*storage_results_nathan[0].key, "image_url"); + BOOST_CHECK_EQUAL(storage_results_nathan[0].key, "image_url"); BOOST_CHECK_EQUAL(storage_results_nathan[0].value->as_string(), "http://new.image.url/newimg.jpg"); BOOST_CHECK_EQUAL(storage_results_nathan[1].account.instance.value, 16 ); - BOOST_CHECK_EQUAL(*storage_results_nathan[1].key, "language"); + BOOST_CHECK_EQUAL(storage_results_nathan[1].key, "language"); BOOST_CHECK_EQUAL(storage_results_nathan[1].value->as_string(), "en"); // delete stuff that it is not there @@ -204,10 +202,10 @@ try { storage_results_nathan = custom_operations_api.get_storage_info("nathan", "settings"); BOOST_CHECK_EQUAL(storage_results_nathan.size(), 2 ); BOOST_CHECK_EQUAL(storage_results_nathan[0].account.instance.value, 16 ); - BOOST_CHECK_EQUAL(*storage_results_nathan[0].key, "image_url"); + BOOST_CHECK_EQUAL(storage_results_nathan[0].key, "image_url"); BOOST_CHECK_EQUAL(storage_results_nathan[0].value->as_string(), "http://new.image.url/newimg.jpg"); BOOST_CHECK_EQUAL(storage_results_nathan[1].account.instance.value, 16 ); - BOOST_CHECK_EQUAL(*storage_results_nathan[1].key, "language"); + BOOST_CHECK_EQUAL(storage_results_nathan[1].key, "language"); BOOST_CHECK_EQUAL(storage_results_nathan[1].value->as_string(), "en"); // add more than 10 storage items in 1 operation is not allowed @@ -238,7 +236,7 @@ try { vector storage_results_alice = custom_operations_api.get_storage_info("alice", "random"); BOOST_CHECK_EQUAL(storage_results_alice.size(), 1 ); BOOST_CHECK_EQUAL(storage_results_alice[0].account.instance.value, 17 ); - BOOST_CHECK_EQUAL(*storage_results_alice[0].key, "key1"); + BOOST_CHECK_EQUAL(storage_results_alice[0].key, "key1"); BOOST_CHECK_EQUAL(storage_results_alice[0].value->as_string(), "value2"); // add an object @@ -251,7 +249,7 @@ try { storage_results_alice = custom_operations_api.get_storage_info("alice", "account_object"); BOOST_CHECK_EQUAL(storage_results_alice.size(), 1); BOOST_CHECK_EQUAL(storage_results_alice[0].account.instance.value, 17); - BOOST_CHECK_EQUAL(*storage_results_alice[0].key, "nathan"); + BOOST_CHECK_EQUAL(storage_results_alice[0].key, "nathan"); BOOST_CHECK_EQUAL(storage_results_alice[0].value->as(20).name, "nathan"); // add 2 more objects @@ -265,12 +263,12 @@ try { storage_results_alice = custom_operations_api.get_storage_info("alice", "account_object"); BOOST_CHECK_EQUAL(storage_results_alice.size(), 3); BOOST_CHECK_EQUAL(storage_results_alice[0].account.instance.value, 17); - BOOST_CHECK_EQUAL(*storage_results_alice[0].key, "nathan"); + BOOST_CHECK_EQUAL(storage_results_alice[0].key, "nathan"); BOOST_CHECK_EQUAL(storage_results_alice[0].value->as(20).name, "nathan"); BOOST_CHECK_EQUAL(storage_results_alice[1].account.instance.value, 17); - BOOST_CHECK_EQUAL(*storage_results_alice[1].key, "patty"); + BOOST_CHECK_EQUAL(storage_results_alice[1].key, "patty"); BOOST_CHECK_EQUAL(storage_results_alice[1].value->as(20).name, "patty"); - BOOST_CHECK_EQUAL(*storage_results_alice[2].key, "robert"); + BOOST_CHECK_EQUAL(storage_results_alice[2].key, "robert"); BOOST_CHECK_EQUAL(storage_results_alice[2].value->as(20).name, "robert"); } catch (fc::exception &e) { @@ -327,9 +325,9 @@ try { storage_results_nathan = custom_operations_api.get_storage_info("nathan", "contact_list"); BOOST_CHECK_EQUAL(storage_results_nathan.size(), 2 ); BOOST_CHECK_EQUAL(storage_results_nathan[0].account.instance.value, 16 ); - BOOST_CHECK_EQUAL(*storage_results_nathan[0].key, robert.name); + BOOST_CHECK_EQUAL(storage_results_nathan[0].key, robert.name); BOOST_CHECK_EQUAL(storage_results_nathan[1].account.instance.value, 16 ); - BOOST_CHECK_EQUAL(*storage_results_nathan[1].key, alice.name); + BOOST_CHECK_EQUAL(storage_results_nathan[1].key, alice.name); // add a value into account list already there accounts.clear(); @@ -341,9 +339,9 @@ try { storage_results_nathan = custom_operations_api.get_storage_info("nathan", "contact_list"); BOOST_CHECK_EQUAL(storage_results_nathan.size(), 2 ); BOOST_CHECK_EQUAL(storage_results_nathan[0].account.instance.value, 16 ); - BOOST_CHECK_EQUAL(*storage_results_nathan[0].key, robert.name); + BOOST_CHECK_EQUAL(storage_results_nathan[0].key, robert.name); BOOST_CHECK_EQUAL(storage_results_nathan[1].account.instance.value, 16 ); - BOOST_CHECK_EQUAL(*storage_results_nathan[1].key, alice.name); + BOOST_CHECK_EQUAL(storage_results_nathan[1].key, alice.name); // delete alice from the list accounts.clear(); @@ -355,7 +353,7 @@ try { storage_results_nathan = custom_operations_api.get_storage_info("nathan", "contact_list"); BOOST_CHECK_EQUAL(storage_results_nathan.size(), 1 ); BOOST_CHECK_EQUAL(storage_results_nathan[0].account.instance.value, 16 ); - BOOST_CHECK_EQUAL(*storage_results_nathan[0].key, robert.name); + BOOST_CHECK_EQUAL(storage_results_nathan[0].key, robert.name); // add more than 10 accounts to the list in 1 operation is not allowed accounts.clear(); @@ -383,7 +381,7 @@ try { auto storage_results_alice = custom_operations_api.get_storage_info("alice", "contact_list"); BOOST_CHECK_EQUAL(storage_results_alice.size(), 1 ); BOOST_CHECK_EQUAL(storage_results_alice[0].account.instance.value, 17 ); - BOOST_CHECK_EQUAL(*storage_results_alice[0].key, robert.name); + BOOST_CHECK_EQUAL(storage_results_alice[0].key, robert.name); } catch (fc::exception &e) { edump((e.to_detail_string())); From 4637af12a75ed683fba202d0c8a810c99c5b6537 Mon Sep 17 00:00:00 2001 From: Alfredo Garcia Date: Thu, 12 Sep 2019 10:56:17 -0300 Subject: [PATCH 09/18] remove the 0xFF byte --- .../plugins/custom_operations/custom_operations_plugin.cpp | 6 +++--- .../graphene/custom_operations/custom_operations_plugin.hpp | 6 ------ libraries/wallet/wallet.cpp | 2 -- tests/custom_operations/main.cpp | 2 -- 4 files changed, 3 insertions(+), 13 deletions(-) diff --git a/libraries/plugins/custom_operations/custom_operations_plugin.cpp b/libraries/plugins/custom_operations/custom_operations_plugin.cpp index caf2f750ba..cfdf145d9b 100644 --- a/libraries/plugins/custom_operations/custom_operations_plugin.cpp +++ b/libraries/plugins/custom_operations/custom_operations_plugin.cpp @@ -64,13 +64,13 @@ void custom_operations_plugin_impl::onBlock( const signed_block& b ) const custom_operation& custom_op = o_operation->op.get(); - if(custom_op.data.size() == 0 || uint8_t(custom_op.data.data()[0]) != 0xFF) + if(custom_op.data.size() == 0) continue; try { - auto unpacked = fc::raw::unpack(custom_op.data); + auto unpacked = fc::raw::unpack(custom_op.data); custom_op_visitor vtor(db, custom_op.fee_payer()); - unpacked.op.visit(vtor); + unpacked.visit(vtor); } catch (fc::exception e) { // only api node will know if the unpack, validate or apply fails dlog("Error: ${ex} in operation: ${op}", ("ex", e.to_detail_string())("op", fc::json::to_string(custom_op))); diff --git a/libraries/plugins/custom_operations/include/graphene/custom_operations/custom_operations_plugin.hpp b/libraries/plugins/custom_operations/include/graphene/custom_operations/custom_operations_plugin.hpp index caf3b11cb8..cd75e565d6 100644 --- a/libraries/plugins/custom_operations/include/graphene/custom_operations/custom_operations_plugin.hpp +++ b/libraries/plugins/custom_operations/include/graphene/custom_operations/custom_operations_plugin.hpp @@ -58,11 +58,6 @@ class custom_operations_plugin : public graphene::app::plugin typedef fc::static_variant custom_plugin_operation; -struct custom_operation_wrapper { - uint8_t unused_data; // if first char of custom_op.data is 0xFF we unpack, this char is not used anymore then. - custom_plugin_operation op; -}; - struct custom_op_visitor { typedef void result_type; @@ -82,4 +77,3 @@ struct custom_op_visitor } } //graphene::custom_operations FC_REFLECT_TYPENAME( graphene::custom_operations::custom_plugin_operation ) -FC_REFLECT( graphene::custom_operations::custom_operation_wrapper, (unused_data)(op) ) diff --git a/libraries/wallet/wallet.cpp b/libraries/wallet/wallet.cpp index f1f2c67709..7c4ddfe232 100644 --- a/libraries/wallet/wallet.cpp +++ b/libraries/wallet/wallet.cpp @@ -1978,7 +1978,6 @@ class wallet_api_impl auto packed = fc::raw::pack(store); packed.insert(packed.begin(), types::account_map); - packed.insert(packed.begin(), 0xFF); op.payer = account_id; op.data = packed; @@ -2010,7 +2009,6 @@ class wallet_api_impl auto packed = fc::raw::pack(store); packed.insert(packed.begin(), types::account_list); - packed.insert(packed.begin(), 0xFF); op.payer = account_id; op.data = packed; diff --git a/tests/custom_operations/main.cpp b/tests/custom_operations/main.cpp index 8eeabbefbd..574263a087 100644 --- a/tests/custom_operations/main.cpp +++ b/tests/custom_operations/main.cpp @@ -55,7 +55,6 @@ void map_operation(flat_map& pairs, bool remove, string& catalog auto packed = fc::raw::pack(store); packed.insert(packed.begin(), types::account_map); - packed.insert(packed.begin(), 0xFF); op.payer = account; op.data = packed; @@ -81,7 +80,6 @@ void list_operation(flat_set& list, bool remove, string& catalog, accoun auto packed = fc::raw::pack(storage_list); packed.insert(packed.begin(), types::account_list); - packed.insert(packed.begin(), 0xFF); op.payer = account; op.data = packed; From 085f4d6e1e477c48e6022bd7aa32e533f0d10411 Mon Sep 17 00:00:00 2001 From: Alfredo Garcia Date: Thu, 12 Sep 2019 12:37:14 -0300 Subject: [PATCH 10/18] remove list operation --- .../custom_operations/custom_evaluators.cpp | 44 +-------- .../custom_operations/custom_operations.cpp | 6 -- .../custom_operations/custom_evaluators.hpp | 1 - .../custom_operations/custom_objects.hpp | 5 +- .../custom_operations/custom_operations.hpp | 13 +-- .../custom_operations_plugin.hpp | 2 +- .../wallet/include/graphene/wallet/wallet.hpp | 20 +--- libraries/wallet/wallet.cpp | 43 +-------- tests/cli/main.cpp | 12 +-- tests/custom_operations/main.cpp | 91 +++++++------------ 10 files changed, 52 insertions(+), 185 deletions(-) diff --git a/libraries/plugins/custom_operations/custom_evaluators.cpp b/libraries/plugins/custom_operations/custom_evaluators.cpp index 50eec94c96..f30e923145 100644 --- a/libraries/plugins/custom_operations/custom_evaluators.cpp +++ b/libraries/plugins/custom_operations/custom_evaluators.cpp @@ -65,7 +65,8 @@ vector custom_generic_evaluator::do_apply(const account_storage_ aso.catalog = op.catalog; aso.account = _account; aso.key = row.first; - aso.value = fc::json::from_string(row.second); + if(row.second.valid()) + aso.value = fc::json::from_string(*row.second); }); results.push_back(created.id); } @@ -75,7 +76,9 @@ vector custom_generic_evaluator::do_apply(const account_storage_ { try { _db->modify(*itr, [&op, this, &row](account_storage_object &aso) { - aso.value = fc::json::from_string(row.second); + aso.key = row.first; + if(row.second.valid()) + aso.value = fc::json::from_string(*row.second); }); results.push_back(itr->id); } @@ -85,42 +88,5 @@ vector custom_generic_evaluator::do_apply(const account_storage_ } return results; } -vector custom_generic_evaluator::do_apply(const account_storage_list& op) -{ - auto &index = _db->get_index_type().indices().get(); - vector results; - - if (op.remove) - { - for(auto const& list_value: op.values) { - - auto itr = index.find(make_tuple(_account, op.catalog, list_value)); - if(itr != index.end()) { - results.push_back(itr->id); - _db->remove(*itr); - } - } - } - else { - for(auto const& list_value: op.values) { - if(list_value.length() > 200) - { - dlog("List value can't be bigger than ${max} characters", ("max", CUSTOM_OPERATIONS_MAX_KEY_SIZE)); - continue; - } - auto itr = index.find(make_tuple(_account, op.catalog, list_value)); - if(itr == index.end()) - { - auto created = _db->create([&op, this, &list_value](account_storage_object &aso) { - aso.catalog = op.catalog; - aso.account = _account; - aso.key = list_value; - }); - results.push_back(itr->id); - } - } - } - return results; -} } } diff --git a/libraries/plugins/custom_operations/custom_operations.cpp b/libraries/plugins/custom_operations/custom_operations.cpp index 2e5bfd1c4d..5a351892ed 100644 --- a/libraries/plugins/custom_operations/custom_operations.cpp +++ b/libraries/plugins/custom_operations/custom_operations.cpp @@ -30,13 +30,7 @@ void account_storage_map::validate()const FC_ASSERT(key_values.size() <= 10); FC_ASSERT(catalog.length() <= CUSTOM_OPERATIONS_MAX_KEY_SIZE && catalog.length() > 0); } -void account_storage_list::validate()const -{ - FC_ASSERT(values.size() <= 10); - FC_ASSERT(catalog.length() <= CUSTOM_OPERATIONS_MAX_KEY_SIZE && catalog.length() > 0); -} } } //graphene::custom_operations GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::custom_operations::account_storage_map ) -GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::custom_operations::account_storage_list ) diff --git a/libraries/plugins/custom_operations/include/graphene/custom_operations/custom_evaluators.hpp b/libraries/plugins/custom_operations/include/graphene/custom_operations/custom_evaluators.hpp index 50d8644105..fe70545762 100644 --- a/libraries/plugins/custom_operations/include/graphene/custom_operations/custom_evaluators.hpp +++ b/libraries/plugins/custom_operations/include/graphene/custom_operations/custom_evaluators.hpp @@ -35,7 +35,6 @@ class custom_generic_evaluator custom_generic_evaluator(database& db, const account_id_type account); vector do_apply(const account_storage_map& o); - vector do_apply(const account_storage_list& o); }; } } diff --git a/libraries/plugins/custom_operations/include/graphene/custom_operations/custom_objects.hpp b/libraries/plugins/custom_operations/include/graphene/custom_operations/custom_objects.hpp index 9b910f99d1..8586dc6e44 100644 --- a/libraries/plugins/custom_operations/include/graphene/custom_operations/custom_objects.hpp +++ b/libraries/plugins/custom_operations/include/graphene/custom_operations/custom_objects.hpp @@ -37,8 +37,7 @@ using namespace chain; #define CUSTOM_OPERATIONS_MAX_KEY_SIZE (200) enum types { - account_map = 0, - account_list = 1 + account_map = 0 }; struct account_storage_object : public abstract_object @@ -87,4 +86,4 @@ using account_storage_id_type = object_id key_values; - - void validate()const; -}; - -struct account_storage_list : chain::base_operation -{ - bool remove; - string catalog; - flat_set values; + flat_map> key_values; void validate()const; }; @@ -53,7 +44,5 @@ struct account_storage_list : chain::base_operation } } //graphene::custom_operations FC_REFLECT( graphene::custom_operations::account_storage_map, (remove)(catalog)(key_values) ) -FC_REFLECT( graphene::custom_operations::account_storage_list, (remove)(catalog)(values) ) GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::custom_operations::account_storage_map ) -GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::custom_operations::account_storage_list ) diff --git a/libraries/plugins/custom_operations/include/graphene/custom_operations/custom_operations_plugin.hpp b/libraries/plugins/custom_operations/include/graphene/custom_operations/custom_operations_plugin.hpp index cd75e565d6..39aec8ab07 100644 --- a/libraries/plugins/custom_operations/include/graphene/custom_operations/custom_operations_plugin.hpp +++ b/libraries/plugins/custom_operations/include/graphene/custom_operations/custom_operations_plugin.hpp @@ -56,7 +56,7 @@ class custom_operations_plugin : public graphene::app::plugin std::unique_ptr my; }; -typedef fc::static_variant custom_plugin_operation; +typedef fc::static_variant custom_plugin_operation; struct custom_op_visitor { diff --git a/libraries/wallet/include/graphene/wallet/wallet.hpp b/libraries/wallet/include/graphene/wallet/wallet.hpp index 6942733432..f8d622fbda 100644 --- a/libraries/wallet/include/graphene/wallet/wallet.hpp +++ b/libraries/wallet/include/graphene/wallet/wallet.hpp @@ -2030,24 +2030,7 @@ class wallet_api * @return The signed transaction */ signed_transaction account_store_map(string account, string catalog, bool remove, - flat_map key_values, bool broadcast); - - /** - * Manage a accounts list(values)by using the custom operations plugin. - * - * Each account can optionally add and delete data from a dedicated list - * to be retrieved by any interested party. Can be used as a whitelist, address book, etc. - * - * @param account The account ID or name that we are adding additional information to. - * @param catalog The name of the catalog the operation will insert data to. - * @param remove true if you want to remove stuff from a catalog. - * @param values The list of strings to be inserted/removed to/from the catalog. - * @param broadcast true if you wish to broadcast the transaction - * - * @return The signed transaction - */ - signed_transaction account_store_list(string account, string catalog, bool remove, flat_set values, - bool broadcast); + flat_map> key_values, bool broadcast); /** * Get \c account_storage_object of an account by using the custom operations plugin. @@ -2270,7 +2253,6 @@ FC_API( graphene::wallet::wallet_api, (receive_blind_transfer) (get_order_book) (account_store_map) - (account_store_list) (get_account_storage) (quit) ) diff --git a/libraries/wallet/wallet.cpp b/libraries/wallet/wallet.cpp index 7c4ddfe232..01210f62ca 100644 --- a/libraries/wallet/wallet.cpp +++ b/libraries/wallet/wallet.cpp @@ -1961,8 +1961,8 @@ class wallet_api_impl } FC_CAPTURE_AND_RETHROW( (htlc_id)(issuer)(seconds_to_add)(broadcast) ) } - signed_transaction account_store_map(string account, string catalog, bool remove, flat_map key_values, - bool broadcast) + signed_transaction account_store_map(string account, string catalog, bool remove, + flat_map> key_values, bool broadcast) { try { @@ -1992,37 +1992,6 @@ class wallet_api_impl } FC_CAPTURE_AND_RETHROW( (account)(remove)(catalog)(key_values)(broadcast) ) } - signed_transaction account_store_list(string account, string catalog, bool remove, flat_set values, - bool broadcast) - { - try - { - FC_ASSERT( !self.is_locked() ); - - account_id_type account_id = get_account(account).id; - - custom_operation op; - account_storage_list store; - store.remove = remove; - store.catalog = catalog; - store.values = values; - - auto packed = fc::raw::pack(store); - packed.insert(packed.begin(), types::account_list); - - op.payer = account_id; - op.data = packed; - - signed_transaction tx; - tx.operations.push_back(op); - set_operation_fees( tx, _remote_db->get_global_properties().parameters.get_current_fees()); - tx.validate(); - - return sign_transaction(tx, broadcast); - - } FC_CAPTURE_AND_RETHROW( (account)(remove)(catalog)(values)(broadcast) ) - } - vector< vesting_balance_object_with_info > get_vesting_balances( string account_name ) { try { fc::optional vbid = maybe_id( account_name ); @@ -5252,17 +5221,11 @@ order_book wallet_api::get_order_book( const string& base, const string& quote, // custom operations signed_transaction wallet_api::account_store_map(string account, string catalog, bool remove, - flat_map key_values, bool broadcast) + flat_map> key_values, bool broadcast) { return my->account_store_map(account, catalog, remove, key_values, broadcast); } -signed_transaction wallet_api::account_store_list(string account, string catalog, bool remove, flat_set values, - bool broadcast) -{ - return my->account_store_list(account, catalog, remove, values, broadcast); -} - vector wallet_api::get_account_storage(string account, string catalog) { return my->_custom_operations->get_storage_info(account, catalog); diff --git a/tests/cli/main.cpp b/tests/cli/main.cpp index 8f8c82cd91..fc2703c932 100644 --- a/tests/cli/main.cpp +++ b/tests/cli/main.cpp @@ -1224,7 +1224,7 @@ BOOST_FIXTURE_TEST_CASE( general_storage, cli_fixture ) BOOST_TEST_MESSAGE("Storing in a map."); - flat_map pairs; + flat_map> pairs; pairs["key1"] = fc::json::to_string("value1"); pairs["key2"] = fc::json::to_string("value2"); @@ -1249,12 +1249,12 @@ BOOST_FIXTURE_TEST_CASE( general_storage, cli_fixture ) BOOST_TEST_MESSAGE("Storing in a list."); - flat_set favs; - favs.insert("chocolate"); - favs.insert("milk"); - favs.insert("banana"); + flat_map> favs; + favs["chocolate"]; + favs["milk"]; + favs["banana"]; - con.wallet_api_ptr->account_store_list("nathan", "favourites", false, favs, true); + con.wallet_api_ptr->account_store_map("nathan", "favourites", false, favs, true); BOOST_TEST_MESSAGE("The system is generating a block."); BOOST_CHECK(generate_block(app1)); diff --git a/tests/custom_operations/main.cpp b/tests/custom_operations/main.cpp index 574263a087..9457e166e8 100644 --- a/tests/custom_operations/main.cpp +++ b/tests/custom_operations/main.cpp @@ -40,7 +40,7 @@ using namespace graphene::custom_operations; BOOST_FIXTURE_TEST_SUITE( custom_operation_tests, database_fixture ) -void map_operation(flat_map& pairs, bool remove, string& catalog, account_id_type& account, +void map_operation(flat_map>& pairs, bool remove, string& catalog, account_id_type& account, private_key& pk, database& db) { signed_transaction trx; @@ -65,31 +65,6 @@ void map_operation(flat_map& pairs, bool remove, string& catalog trx.clear(); } -void list_operation(flat_set& list, bool remove, string& catalog, account_id_type& account, - private_key& pk, database& db) -{ - signed_transaction trx; - set_expiration(db, trx); - - custom_operation op; - account_storage_list storage_list; - - storage_list.values = list; - storage_list.remove = remove; - storage_list.catalog = "contact_list"; - - auto packed = fc::raw::pack(storage_list); - packed.insert(packed.begin(), types::account_list); - - op.payer = account; - op.data = packed; - op.fee = db.get_global_properties().parameters.current_fees->calculate_fee(op); - trx.operations.push_back(op); - trx.sign(pk, db.get_chain_id()); - PUSH_TX(db, trx, ~0); - trx.clear(); -} - BOOST_AUTO_TEST_CASE(custom_operations_account_storage_map_test) { try { @@ -108,7 +83,7 @@ try { // catalog is indexed so cant be too big(greater than CUSTOM_OPERATIONS_MAX_KEY_SIZE(200) is not allowed) std::string catalog(201, 'a'); - flat_map pairs; + flat_map> pairs; pairs["key"] = fc::json::to_string("value"); map_operation(pairs, false, catalog, nathan_id, nathan_private_key, db); generate_block(); @@ -292,9 +267,9 @@ try { // catalog is indexed so cant be too big(greater than CUSTOM_OPERATIONS_MAX_KEY_SIZE(200) is not allowed) std::string catalog(201, 'a'); - flat_set accounts; - accounts.insert(robert.name); - list_operation(accounts, false, catalog, nathan_id, nathan_private_key, db); + flat_map> accounts; + accounts[robert.name]; + map_operation(accounts, false, catalog, nathan_id, nathan_private_key, db); generate_block(); auto storage_results_nathan = custom_operations_api.get_storage_info("nathan", catalog); @@ -304,8 +279,8 @@ try { catalog = "whatever"; std::string value(201, 'a'); accounts.clear(); - accounts.insert(value); - list_operation(accounts, false, catalog, nathan_id, nathan_private_key, db); + accounts[value]; + map_operation(accounts, false, catalog, nathan_id, nathan_private_key, db); generate_block(); storage_results_nathan = custom_operations_api.get_storage_info("nathan", catalog); @@ -313,38 +288,38 @@ try { // nathan add a list of accounts to storage accounts.clear(); - accounts.insert(alice.name); - accounts.insert(robert.name); + accounts[alice.name]; + accounts[robert.name]; catalog = "contact_list"; - list_operation(accounts, false, catalog, nathan_id, nathan_private_key, db); + map_operation(accounts, false, catalog, nathan_id, nathan_private_key, db); generate_block(); // get the account list for nathan, check alice and robert are there storage_results_nathan = custom_operations_api.get_storage_info("nathan", "contact_list"); BOOST_CHECK_EQUAL(storage_results_nathan.size(), 2 ); BOOST_CHECK_EQUAL(storage_results_nathan[0].account.instance.value, 16 ); - BOOST_CHECK_EQUAL(storage_results_nathan[0].key, robert.name); + BOOST_CHECK_EQUAL(storage_results_nathan[0].key, alice.name); BOOST_CHECK_EQUAL(storage_results_nathan[1].account.instance.value, 16 ); - BOOST_CHECK_EQUAL(storage_results_nathan[1].key, alice.name); + BOOST_CHECK_EQUAL(storage_results_nathan[1].key, robert.name); // add a value into account list already there accounts.clear(); - accounts.insert(alice.name); - list_operation(accounts, false, catalog, nathan_id, nathan_private_key, db); + accounts[alice.name]; + map_operation(accounts, false, catalog, nathan_id, nathan_private_key, db); generate_block(); // nothing changes storage_results_nathan = custom_operations_api.get_storage_info("nathan", "contact_list"); BOOST_CHECK_EQUAL(storage_results_nathan.size(), 2 ); BOOST_CHECK_EQUAL(storage_results_nathan[0].account.instance.value, 16 ); - BOOST_CHECK_EQUAL(storage_results_nathan[0].key, robert.name); + BOOST_CHECK_EQUAL(storage_results_nathan[0].key, alice.name); BOOST_CHECK_EQUAL(storage_results_nathan[1].account.instance.value, 16 ); - BOOST_CHECK_EQUAL(storage_results_nathan[1].key, alice.name); + BOOST_CHECK_EQUAL(storage_results_nathan[1].key, robert.name); // delete alice from the list accounts.clear(); - accounts.insert(alice.name); - list_operation(accounts, true, catalog, nathan_id, nathan_private_key, db); + accounts[alice.name]; + map_operation(accounts, true, catalog, nathan_id, nathan_private_key, db); generate_block(); // alice gone @@ -355,25 +330,25 @@ try { // add more than 10 accounts to the list in 1 operation is not allowed accounts.clear(); - accounts.insert("init0"); - accounts.insert("init1"); - accounts.insert("init2"); - accounts.insert("init3"); - accounts.insert("init4"); - accounts.insert("init5"); - accounts.insert("init6"); - accounts.insert("init7"); - accounts.insert("init8"); - accounts.insert("init9"); - accounts.insert("init10"); - list_operation(accounts, false, catalog, nathan_id, nathan_private_key, db); + accounts["init0"]; + accounts["init1"]; + accounts["init2"]; + accounts["init3"]; + accounts["init4"]; + accounts["init5"]; + accounts["init6"]; + accounts["init7"]; + accounts["init8"]; + accounts["init9"]; + accounts["init10"]; + map_operation(accounts, false, catalog, nathan_id, nathan_private_key, db); generate_block(); // duplicated accounts in the list, only 1 will be inserted accounts.clear(); - accounts.insert(robert.name); - accounts.insert(robert.name); - list_operation(accounts, false, catalog, alice_id, alice_private_key, db); + accounts[robert.name]; + accounts[robert.name]; + map_operation(accounts, false, catalog, alice_id, alice_private_key, db); generate_block(); auto storage_results_alice = custom_operations_api.get_storage_info("alice", "contact_list"); From 0ca990d087327489fca62e1bad51cd986fe9611d Mon Sep 17 00:00:00 2001 From: Alfredo Garcia Date: Fri, 13 Sep 2019 07:48:11 -0300 Subject: [PATCH 11/18] wrap store in custom_plugin_operation in wallet command --- libraries/wallet/wallet.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/wallet/wallet.cpp b/libraries/wallet/wallet.cpp index 01210f62ca..68778361bc 100644 --- a/libraries/wallet/wallet.cpp +++ b/libraries/wallet/wallet.cpp @@ -1976,8 +1976,8 @@ class wallet_api_impl store.remove = remove; store.key_values = key_values; - auto packed = fc::raw::pack(store); - packed.insert(packed.begin(), types::account_map); + custom_plugin_operation custom_plugin_op(store); + auto packed = fc::raw::pack(custom_plugin_op); op.payer = account_id; op.data = packed; From 19a78861adbe3326190b13554a47e091cbd7e4f6 Mon Sep 17 00:00:00 2001 From: Alfredo Garcia Date: Fri, 13 Sep 2019 08:28:54 -0300 Subject: [PATCH 12/18] add account_storage_map operation to js_serializer --- programs/js_operation_serializer/main.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/programs/js_operation_serializer/main.cpp b/programs/js_operation_serializer/main.cpp index 4effe0efa3..e2c3513589 100644 --- a/programs/js_operation_serializer/main.cpp +++ b/programs/js_operation_serializer/main.cpp @@ -37,10 +37,12 @@ #include #include #include +#include #include using namespace graphene::chain; +using namespace graphene::custom_operations; namespace detail_ns { @@ -392,6 +394,8 @@ int main( int argc, char** argv ) detail_ns::serializer::init(); detail_ns::serializer::init(); detail_ns::serializer::init(); + detail_ns::serializer::init(); + for( const auto& gen : detail_ns::serializers ) gen(); From 739bc780d8ba210141bfde1c1335db76ffd40ed9 Mon Sep 17 00:00:00 2001 From: Alfredo Garcia Date: Fri, 13 Sep 2019 09:18:47 -0300 Subject: [PATCH 13/18] make custom_operations_api enabled by default --- libraries/app/api.cpp | 3 +++ libraries/app/application.cpp | 1 + tests/cli/main.cpp | 25 ------------------------- 3 files changed, 4 insertions(+), 25 deletions(-) diff --git a/libraries/app/api.cpp b/libraries/app/api.cpp index 97d0b455a6..8b513e7ffc 100644 --- a/libraries/app/api.cpp +++ b/libraries/app/api.cpp @@ -690,6 +690,9 @@ namespace graphene { namespace app { vector custom_operations_api::get_storage_info(std::string account_id_or_name, std::string catalog)const { + auto plugin = _app.get_plugin("custom_operations"); + FC_ASSERT( plugin ); + const auto account_id = database_api.get_account_id_from_string(account_id_or_name); vector results; auto &index = _app.chain_database()->get_index_type().indices().get(); diff --git a/libraries/app/application.cpp b/libraries/app/application.cpp index d1f972ac0d..de5bf8a334 100644 --- a/libraries/app/application.cpp +++ b/libraries/app/application.cpp @@ -533,6 +533,7 @@ void application_impl::startup() wild_access.allowed_apis.push_back( "network_broadcast_api" ); wild_access.allowed_apis.push_back( "history_api" ); wild_access.allowed_apis.push_back( "orders_api" ); + wild_access.allowed_apis.push_back( "custom_operations_api" ); _apiaccess.permission_map["*"] = wild_access; } diff --git a/tests/cli/main.cpp b/tests/cli/main.cpp index fc2703c932..a112f224fa 100644 --- a/tests/cli/main.cpp +++ b/tests/cli/main.cpp @@ -114,30 +114,6 @@ int get_available_port() return ntohs(sin.sin_port); } -boost::filesystem::path create_api_access_file(fc::temp_directory& directory) { - boost::filesystem::path apiaccess_path = boost::filesystem::path{directory.path().generic_string()} / "api-access.json"; - fc::path apiaccess_out = apiaccess_path; - - const string apiaccess_content = R"( - { - "permission_map" : - [ - [ - "*", - { - "password_hash_b64" : "*", - "password_salt_b64" : "*", - "allowed_apis" : ["database_api", "network_broadcast_api", "history_api", "custom_operations_api"] - } - ] - ] - } - )"; - - fc::json::save_to_file(fc::json::from_string(apiaccess_content), apiaccess_out); - return apiaccess_path; -} - /////////// /// @brief Start the application /// @param app_dir the temporary directory to use @@ -164,7 +140,6 @@ std::shared_ptr start_application(fc::temp_directory ); cfg.emplace("genesis-json", boost::program_options::variable_value(create_genesis_file(app_dir), false)); cfg.emplace("seed-nodes", boost::program_options::variable_value(string("[]"), false)); - cfg.emplace("api-access", boost::program_options::variable_value(create_api_access_file(app_dir), false)); app1->initialize(app_dir.path(), cfg); app1->initialize_plugins(cfg); From 2a6cd6166fabb625a36942308a72ea25ba5433b3 Mon Sep 17 00:00:00 2001 From: Alfredo Garcia Date: Mon, 16 Sep 2019 12:24:38 -0300 Subject: [PATCH 14/18] add description, line wraps, remove restriction, typo --- libraries/app/api.cpp | 7 ++-- libraries/app/include/graphene/app/api.hpp | 2 +- .../custom_operations/custom_evaluators.cpp | 2 ++ .../custom_operations/custom_operations.cpp | 1 - .../custom_operations_plugin.cpp | 5 +-- tests/custom_operations/main.cpp | 33 ------------------- 6 files changed, 10 insertions(+), 40 deletions(-) diff --git a/libraries/app/api.cpp b/libraries/app/api.cpp index 8b513e7ffc..99564106a2 100644 --- a/libraries/app/api.cpp +++ b/libraries/app/api.cpp @@ -695,9 +695,10 @@ namespace graphene { namespace app { const auto account_id = database_api.get_account_id_from_string(account_id_or_name); vector results; - auto &index = _app.chain_database()->get_index_type().indices().get(); - auto itr = index.lower_bound(make_tuple(account_id, catalog)); - while(itr != index.end() && itr->account == account_id && itr->catalog == catalog) { + const auto& storage_index = _app.chain_database()->get_index_type(); + const auto& by_account_catalog_idx = storage_index.indices().get(); + auto itr = by_account_catalog_idx.lower_bound(make_tuple(account_id, catalog)); + while(itr != by_account_catalog_idx.end() && itr->account == account_id && itr->catalog == catalog) { results.push_back(*itr); ++itr; } diff --git a/libraries/app/include/graphene/app/api.hpp b/libraries/app/include/graphene/app/api.hpp index b9480b0932..64b33d83c8 100644 --- a/libraries/app/include/graphene/app/api.hpp +++ b/libraries/app/include/graphene/app/api.hpp @@ -532,7 +532,7 @@ namespace graphene { namespace app { &(app.get_options()) ){} /** - * @breif Get all stored objects of an account in a particular catalog + * @brief Get all stored objects of an account in a particular catalog * * @param account Account name to get info from * @param catalog Category classification. Each account can store multiple catalogs. diff --git a/libraries/plugins/custom_operations/custom_evaluators.cpp b/libraries/plugins/custom_operations/custom_evaluators.cpp index f30e923145..f8d0d6a827 100644 --- a/libraries/plugins/custom_operations/custom_evaluators.cpp +++ b/libraries/plugins/custom_operations/custom_evaluators.cpp @@ -67,6 +67,8 @@ vector custom_generic_evaluator::do_apply(const account_storage_ aso.key = row.first; if(row.second.valid()) aso.value = fc::json::from_string(*row.second); + else + aso.value.reset(); }); results.push_back(created.id); } diff --git a/libraries/plugins/custom_operations/custom_operations.cpp b/libraries/plugins/custom_operations/custom_operations.cpp index 5a351892ed..18faee64d8 100644 --- a/libraries/plugins/custom_operations/custom_operations.cpp +++ b/libraries/plugins/custom_operations/custom_operations.cpp @@ -27,7 +27,6 @@ namespace graphene { namespace custom_operations { void account_storage_map::validate()const { - FC_ASSERT(key_values.size() <= 10); FC_ASSERT(catalog.length() <= CUSTOM_OPERATIONS_MAX_KEY_SIZE && catalog.length() > 0); } diff --git a/libraries/plugins/custom_operations/custom_operations_plugin.cpp b/libraries/plugins/custom_operations/custom_operations_plugin.cpp index cfdf145d9b..b938af9024 100644 --- a/libraries/plugins/custom_operations/custom_operations_plugin.cpp +++ b/libraries/plugins/custom_operations/custom_operations_plugin.cpp @@ -73,7 +73,8 @@ void custom_operations_plugin_impl::onBlock( const signed_block& b ) unpacked.visit(vtor); } catch (fc::exception e) { // only api node will know if the unpack, validate or apply fails - dlog("Error: ${ex} in operation: ${op}", ("ex", e.to_detail_string())("op", fc::json::to_string(custom_op))); + dlog("Custom operations plugin serializing error: ${ex} in operation: ${op}", + ("ex", e.to_detail_string())("op", fc::json::to_string(custom_op))); continue; } } @@ -101,7 +102,7 @@ std::string custom_operations_plugin::plugin_name()const } std::string custom_operations_plugin::plugin_description()const { - return "custom_operations description"; + return "Stores arbitrary data for accounts by creating specially crafted custom operations."; } void custom_operations_plugin::plugin_set_program_options( diff --git a/tests/custom_operations/main.cpp b/tests/custom_operations/main.cpp index 9457e166e8..87ef326ed8 100644 --- a/tests/custom_operations/main.cpp +++ b/tests/custom_operations/main.cpp @@ -181,23 +181,6 @@ try { BOOST_CHECK_EQUAL(storage_results_nathan[1].key, "language"); BOOST_CHECK_EQUAL(storage_results_nathan[1].value->as_string(), "en"); - // add more than 10 storage items in 1 operation is not allowed - pairs.clear(); - pairs["key1"] = fc::json::to_string("value1"); - pairs["key2"] = fc::json::to_string("value2"); - pairs["key3"] = fc::json::to_string("value3"); - pairs["key4"] = fc::json::to_string("value4"); - pairs["key5"] = fc::json::to_string("value5"); - pairs["key6"] = fc::json::to_string("value6"); - pairs["key7"] = fc::json::to_string("value7"); - pairs["key8"] = fc::json::to_string("value8"); - pairs["key9"] = fc::json::to_string("value9"); - pairs["key10"] = fc::json::to_string("value10"); - pairs["key11"] = fc::json::to_string("value11"); - - map_operation(pairs, false, catalog, nathan_id, nathan_private_key, db); - generate_block(); - // alice, duplicated keys in storage, only second value will be added pairs.clear(); catalog = "random"; @@ -328,22 +311,6 @@ try { BOOST_CHECK_EQUAL(storage_results_nathan[0].account.instance.value, 16 ); BOOST_CHECK_EQUAL(storage_results_nathan[0].key, robert.name); - // add more than 10 accounts to the list in 1 operation is not allowed - accounts.clear(); - accounts["init0"]; - accounts["init1"]; - accounts["init2"]; - accounts["init3"]; - accounts["init4"]; - accounts["init5"]; - accounts["init6"]; - accounts["init7"]; - accounts["init8"]; - accounts["init9"]; - accounts["init10"]; - map_operation(accounts, false, catalog, nathan_id, nathan_private_key, db); - generate_block(); - // duplicated accounts in the list, only 1 will be inserted accounts.clear(); accounts[robert.name]; From d381a7e5f8e39a7e3749576b09204485dca65789 Mon Sep 17 00:00:00 2001 From: Alfredo Garcia Date: Mon, 16 Sep 2019 13:15:54 -0300 Subject: [PATCH 15/18] move custom_op_visitor to cpp file --- .../custom_operations_plugin.cpp | 16 ++++++++++++++++ .../custom_operations_plugin.hpp | 16 ---------------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/libraries/plugins/custom_operations/custom_operations_plugin.cpp b/libraries/plugins/custom_operations/custom_operations_plugin.cpp index b938af9024..562bcaf217 100644 --- a/libraries/plugins/custom_operations/custom_operations_plugin.cpp +++ b/libraries/plugins/custom_operations/custom_operations_plugin.cpp @@ -53,6 +53,22 @@ class custom_operations_plugin_impl }; +struct custom_op_visitor +{ + typedef void result_type; + account_id_type _fee_payer; + database* _db; + + custom_op_visitor(database& db, account_id_type fee_payer) { _db = &db; _fee_payer = fee_payer; }; + + template + void operator()(T &v) const { + v.validate(); + custom_generic_evaluator evaluator(*_db, _fee_payer); + evaluator.do_apply(v); + } +}; + void custom_operations_plugin_impl::onBlock( const signed_block& b ) { graphene::chain::database& db = database(); diff --git a/libraries/plugins/custom_operations/include/graphene/custom_operations/custom_operations_plugin.hpp b/libraries/plugins/custom_operations/include/graphene/custom_operations/custom_operations_plugin.hpp index 39aec8ab07..f4baacd573 100644 --- a/libraries/plugins/custom_operations/include/graphene/custom_operations/custom_operations_plugin.hpp +++ b/libraries/plugins/custom_operations/include/graphene/custom_operations/custom_operations_plugin.hpp @@ -58,22 +58,6 @@ class custom_operations_plugin : public graphene::app::plugin typedef fc::static_variant custom_plugin_operation; -struct custom_op_visitor -{ - typedef void result_type; - account_id_type _fee_payer; - database* _db; - - custom_op_visitor(database& db, account_id_type fee_payer) { _db = &db; _fee_payer = fee_payer; }; - - template - void operator()(T &v) const { - v.validate(); - custom_generic_evaluator evaluator(*_db, _fee_payer); - evaluator.do_apply(v); - } -}; - } } //graphene::custom_operations FC_REFLECT_TYPENAME( graphene::custom_operations::custom_plugin_operation ) From 05a2cd20464037719c5a8b256741a64aef1486e3 Mon Sep 17 00:00:00 2001 From: Alfredo Garcia Date: Tue, 17 Sep 2019 11:38:27 -0300 Subject: [PATCH 16/18] move reset to modify, remove ref --- libraries/app/include/graphene/app/api.hpp | 2 +- libraries/plugins/custom_operations/custom_evaluators.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/libraries/app/include/graphene/app/api.hpp b/libraries/app/include/graphene/app/api.hpp index 64b33d83c8..191380091c 100644 --- a/libraries/app/include/graphene/app/api.hpp +++ b/libraries/app/include/graphene/app/api.hpp @@ -528,7 +528,7 @@ namespace graphene { namespace app { class custom_operations_api { public: - custom_operations_api(application& app):_app(app), database_api( std::ref(*app.chain_database()), + custom_operations_api(application& app):_app(app), database_api( *app.chain_database(), &(app.get_options()) ){} /** diff --git a/libraries/plugins/custom_operations/custom_evaluators.cpp b/libraries/plugins/custom_operations/custom_evaluators.cpp index f8d0d6a827..6270f011e0 100644 --- a/libraries/plugins/custom_operations/custom_evaluators.cpp +++ b/libraries/plugins/custom_operations/custom_evaluators.cpp @@ -67,8 +67,6 @@ vector custom_generic_evaluator::do_apply(const account_storage_ aso.key = row.first; if(row.second.valid()) aso.value = fc::json::from_string(*row.second); - else - aso.value.reset(); }); results.push_back(created.id); } @@ -81,6 +79,8 @@ vector custom_generic_evaluator::do_apply(const account_storage_ aso.key = row.first; if(row.second.valid()) aso.value = fc::json::from_string(*row.second); + else + aso.value.reset(); }); results.push_back(itr->id); } From 53fba25f4bd08b85c8e6295b332cd9e174e08a83 Mon Sep 17 00:00:00 2001 From: Alfredo Garcia Date: Tue, 17 Sep 2019 14:28:59 -0300 Subject: [PATCH 17/18] move custom operation tests to chain_tests --- tests/CMakeLists.txt | 8 -------- .../main.cpp => tests/custom_operations.cpp} | 3 ++- 2 files changed, 2 insertions(+), 9 deletions(-) rename tests/{custom_operations/main.cpp => tests/custom_operations.cpp} (99%) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 2934476a11..001c817300 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -55,12 +55,4 @@ target_link_libraries( es_test graphene_custom_operations fc ${PLATFORM_SPECIFIC_LIBS} ) -file(GLOB CUSTOM_SOURCES "custom_operations/*.cpp") -add_executable( custom_operations_test ${COMMON_SOURCES} ${CUSTOM_SOURCES} ) -target_link_libraries( custom_operations_test - graphene_chain graphene_app graphene_account_history graphene_elasticsearch - graphene_es_objects graphene_egenesis_none graphene_api_helper_indexes - graphene_custom_operations - fc ${PLATFORM_SPECIFIC_LIBS} ) - add_subdirectory( generate_empty_blocks ) diff --git a/tests/custom_operations/main.cpp b/tests/tests/custom_operations.cpp similarity index 99% rename from tests/custom_operations/main.cpp rename to tests/tests/custom_operations.cpp index 87ef326ed8..9829a8b55f 100644 --- a/tests/custom_operations/main.cpp +++ b/tests/tests/custom_operations.cpp @@ -22,6 +22,8 @@ * THE SOFTWARE. */ +#include + #include #include #include @@ -31,7 +33,6 @@ #include "../common/database_fixture.hpp" #define BOOST_TEST_MODULE Custom operations plugin tests -#include using namespace graphene::chain; using namespace graphene::chain::test; From 8c24cdff19dec4b2cc318fe9d91e8a1be47b627e Mon Sep 17 00:00:00 2001 From: Alfredo Garcia Date: Tue, 17 Sep 2019 14:37:35 -0300 Subject: [PATCH 18/18] add custom_operations to plugins readme --- libraries/plugins/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/libraries/plugins/README.md b/libraries/plugins/README.md index 411bab9b13..68414c32cd 100644 --- a/libraries/plugins/README.md +++ b/libraries/plugins/README.md @@ -12,6 +12,7 @@ Folder | Name | Description -----------------------------------|--------------------------|-----------------------------------------------------------------------------|----------------|---------------|--------------| [account_history](account_history) | Account History | Save account history data | History | Stable | 4 [api_helper_indexes](api_helper_indexes) | API Helper Indexes | Provides some helper indexes used by various API calls | Database API | Stable | +[custom_operations](custom_operations) | Custom Operations | Store and retrieve account catalogs of key=>value data using custom operations | Additional data | Experimental | 7 [debug_witness](debug_witness) | Debug Witness | Run "what-if" tests | Debug | Stable | [delayed_node](delayed_node) | Delayed Node | Avoid forks by running a several times confirmed and delayed blockchain | Business | Stable | [elasticsearch](elasticsearch) | ElasticSearch Operations | Save account history data into elasticsearch database | History | Experimental | 6