Skip to content

Commit

Permalink
Merge pull request #1926 from oxarbitrage/custom_operations
Browse files Browse the repository at this point in the history
Custom operations plugin
  • Loading branch information
oxarbitrage committed Sep 18, 2019
2 parents cc53975 + 8c24cdf commit 79b8b7a
Show file tree
Hide file tree
Showing 23 changed files with 1,118 additions and 6 deletions.
2 changes: 1 addition & 1 deletion libraries/app/CMakeLists.txt
Expand Up @@ -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"
Expand Down
31 changes: 31 additions & 0 deletions libraries/app/api.cpp
Expand Up @@ -48,6 +48,7 @@ template class fc::api<graphene::app::history_api>;
template class fc::api<graphene::app::crypto_api>;
template class fc::api<graphene::app::asset_api>;
template class fc::api<graphene::app::orders_api>;
template class fc::api<graphene::app::custom_operations_api>;
template class fc::api<graphene::debug_witness::debug_api>;
template class fc::api<graphene::app::login_api>;

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -296,6 +302,12 @@ namespace graphene { namespace app {
return *_debug_api;
}

fc::api<custom_operations_api> login_api::custom() const
{
FC_ASSERT(_custom_operations_api);
return *_custom_operations_api;
}

vector<order_history_object> history_api::get_fill_order_history( std::string asset_a, std::string asset_b, uint32_t limit )const
{
FC_ASSERT(_app.chain_database());
Expand Down Expand Up @@ -674,4 +686,23 @@ namespace graphene { namespace app {
return result;
}

// custom operations api
vector<account_storage_object> custom_operations_api::get_storage_info(std::string account_id_or_name,
std::string catalog)const
{
auto plugin = _app.get_plugin<graphene::custom_operations::custom_operations_plugin>("custom_operations");
FC_ASSERT( plugin );

const auto account_id = database_api.get_account_id_from_string(account_id_or_name);
vector<account_storage_object> results;
const auto& storage_index = _app.chain_database()->get_index_type<account_storage_index>();
const auto& by_account_catalog_idx = storage_index.indices().get<by_account_catalog>();
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;
}
return results;
}

} } // graphene::app
1 change: 1 addition & 0 deletions libraries/app/application.cpp
Expand Up @@ -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;
}

Expand Down
37 changes: 36 additions & 1 deletion libraries/app/include/graphene/app/api.hpp
Expand Up @@ -29,8 +29,8 @@
#include <graphene/protocol/confidential.hpp>

#include <graphene/market_history/market_history_plugin.hpp>

#include <graphene/grouped_orders/grouped_orders_plugin.hpp>
#include <graphene/custom_operations/custom_operations_plugin.hpp>

#include <graphene/elasticsearch/elasticsearch_plugin.hpp>

Expand All @@ -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;
Expand Down Expand Up @@ -518,6 +520,31 @@ 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( *app.chain_database(),
&(app.get_options()) ){}

/**
* @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.
*
* @return The vector of objects of the account or empty
*/
vector<account_storage_object> get_storage_info(std::string account, std::string catalog)const;

private:
application& _app;
graphene::app::database_api database_api;
};
} } // graphene::app

extern template class fc::api<graphene::app::block_api>;
Expand All @@ -528,6 +555,7 @@ extern template class fc::api<graphene::app::crypto_api>;
extern template class fc::api<graphene::app::asset_api>;
extern template class fc::api<graphene::app::orders_api>;
extern template class fc::api<graphene::debug_witness::debug_api>;
extern template class fc::api<graphene::app::custom_operations_api>;

namespace graphene { namespace app {
/**
Expand Down Expand Up @@ -569,6 +597,8 @@ namespace graphene { namespace app {
fc::api<orders_api> orders()const;
/// @brief Retrieve the debug API (if available)
fc::api<graphene::debug_witness::debug_api> debug()const;
/// @brief Retrieve the custom operations API
fc::api<custom_operations_api> custom()const;

/// @brief Called to enable an API, not reflected.
void enable_api( const string& api_name );
Expand All @@ -584,6 +614,7 @@ namespace graphene { namespace app {
optional< fc::api<asset_api> > _asset_api;
optional< fc::api<orders_api> > _orders_api;
optional< fc::api<graphene::debug_witness::debug_api> > _debug_api;
optional< fc::api<custom_operations_api> > _custom_operations_api;
};

}} // graphene::app
Expand Down Expand Up @@ -650,6 +681,9 @@ FC_API(graphene::app::orders_api,
(get_tracked_groups)
(get_grouped_limit_orders)
)
FC_API(graphene::app::custom_operations_api,
(get_storage_info)
)
FC_API(graphene::app::login_api,
(login)
(block)
Expand All @@ -661,4 +695,5 @@ FC_API(graphene::app::login_api,
(asset)
(orders)
(debug)
(custom)
)
1 change: 1 addition & 0 deletions libraries/plugins/CMakeLists.txt
Expand Up @@ -8,3 +8,4 @@ add_subdirectory( debug_witness )
add_subdirectory( snapshot )
add_subdirectory( es_objects )
add_subdirectory( api_helper_indexes )
add_subdirectory( custom_operations )
1 change: 1 addition & 0 deletions libraries/plugins/README.md
Expand Up @@ -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
Expand Down
25 changes: 25 additions & 0 deletions 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" )
94 changes: 94 additions & 0 deletions libraries/plugins/custom_operations/custom_evaluators.cpp
@@ -0,0 +1,94 @@
/*
* 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 <graphene/chain/database.hpp>

#include <graphene/custom_operations/custom_operations_plugin.hpp>
#include <graphene/custom_operations/custom_objects.hpp>
#include <graphene/custom_operations/custom_evaluators.hpp>

namespace graphene { namespace custom_operations {

custom_generic_evaluator::custom_generic_evaluator(database& db, const account_id_type account)
{
_db = &db;
_account = account;
}

vector<object_id_type> custom_generic_evaluator::do_apply(const account_storage_map& op)
{
auto &index = _db->get_index_type<account_storage_index>().indices().get<by_account_catalog_key>();
vector<object_id_type> results;

if (op.remove)
{
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);
}
}
}
else {
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.catalog, row.first));
if(itr == index.end())
{
try {
auto created = _db->create<account_storage_object>( [&op, this, &row]( account_storage_object& aso ) {
aso.catalog = op.catalog;
aso.account = _account;
aso.key = row.first;
if(row.second.valid())
aso.value = fc::json::from_string(*row.second);
});
results.push_back(created.id);
}
catch(const fc::parse_error_exception& e) { dlog(e.to_detail_string()); }
}
else
{
try {
_db->modify(*itr, [&op, this, &row](account_storage_object &aso) {
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);
}
catch(const fc::parse_error_exception& e) { dlog((e.to_detail_string())); }
}
}
}
return results;
}

} }
35 changes: 35 additions & 0 deletions libraries/plugins/custom_operations/custom_operations.cpp
@@ -0,0 +1,35 @@
/*
* 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 <graphene/custom_operations/custom_operations.hpp>

namespace graphene { namespace custom_operations {

void account_storage_map::validate()const
{
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 )

0 comments on commit 79b8b7a

Please sign in to comment.