From 4e70e8700102c138951999cf7099387995f780c9 Mon Sep 17 00:00:00 2001 From: Nathan Hourt Date: Wed, 17 Jul 2019 17:01:10 -0500 Subject: [PATCH 01/41] BSIP 40: Core implementation This commit adds the very beginnings of BSIP 40/Custom Active Authorities, including a working implementation of the core logic which evaluates whether an operation complies with a list of restrictions. --- libraries/chain/db_notify.cpp | 17 +- libraries/fc | 2 +- libraries/protocol/CMakeLists.txt | 3 + libraries/protocol/custom_authority.cpp | 122 ++++ .../graphene/protocol/custom_authority.hpp | 119 ++++ .../include/graphene/protocol/operations.hpp | 6 +- .../include/graphene/protocol/restriction.hpp | 136 ++++ .../protocol/restriction_predicate.hpp | 45 ++ libraries/protocol/restriction.cpp | 104 ++++ libraries/protocol/restriction_predicate.cpp | 583 ++++++++++++++++++ tests/tests/custom_authority_tests.cpp | 77 +++ 11 files changed, 1210 insertions(+), 4 deletions(-) create mode 100644 libraries/protocol/custom_authority.cpp create mode 100644 libraries/protocol/include/graphene/protocol/custom_authority.hpp create mode 100644 libraries/protocol/include/graphene/protocol/restriction.hpp create mode 100644 libraries/protocol/include/graphene/protocol/restriction_predicate.hpp create mode 100644 libraries/protocol/restriction.cpp create mode 100644 libraries/protocol/restriction_predicate.cpp create mode 100644 tests/tests/custom_authority_tests.cpp diff --git a/libraries/chain/db_notify.cpp b/libraries/chain/db_notify.cpp index b3e9933c6d..c2cb2f1e5a 100644 --- a/libraries/chain/db_notify.cpp +++ b/libraries/chain/db_notify.cpp @@ -289,6 +289,19 @@ struct get_impacted_account_visitor { _impacted.insert( op.fee_payer() ); } + void operator()( const custom_authority_create_operation& op ) + { + _impacted.insert( op.fee_payer() ); // account + add_authority_accounts( _impacted, op.auth ); + } + void operator()( const custom_authority_update_operation& op ) + { + _impacted.insert( op.fee_payer() ); // account + } + void operator()( const custom_authority_delete_operation& op ) + { + _impacted.insert( op.fee_payer() ); // account + } }; void graphene::chain::operation_get_impacted_accounts( const operation& op, flat_set& result, bool ignore_custom_operation_required_auths ) { @@ -364,7 +377,7 @@ void get_relevant_accounts( const object* obj, flat_set& accoun break; } case vesting_balance_object_type:{ const auto& aobj = dynamic_cast(obj); - FC_ASSERT( aobj != nullptr ); + FC_ASSERT(aobj != nullptr ); accounts.insert( aobj->owner ); break; } case worker_object_type:{ @@ -443,7 +456,7 @@ void get_relevant_accounts( const object* obj, flat_set& accoun const auto& aobj = dynamic_cast(obj); FC_ASSERT( aobj != nullptr ); accounts.insert( aobj->bidder ); - break; + break; } } } diff --git a/libraries/fc b/libraries/fc index 9db1417b25..344a948d9e 160000 --- a/libraries/fc +++ b/libraries/fc @@ -1 +1 @@ -Subproject commit 9db1417b2529e7456226676f0c0ad1f09e2dd449 +Subproject commit 344a948d9e0fe364e4cefdc1fe0a21df8ea46c63 diff --git a/libraries/protocol/CMakeLists.txt b/libraries/protocol/CMakeLists.txt index 11a3b66016..b623745b37 100644 --- a/libraries/protocol/CMakeLists.txt +++ b/libraries/protocol/CMakeLists.txt @@ -16,6 +16,9 @@ list(APPEND SOURCES account.cpp asset.cpp authority.cpp special_authority.cpp + custom_authority.cpp + restriction.cpp + restriction_predicate.cpp committee_member.cpp custom.cpp market.cpp diff --git a/libraries/protocol/custom_authority.cpp b/libraries/protocol/custom_authority.cpp new file mode 100644 index 0000000000..c75c2216cb --- /dev/null +++ b/libraries/protocol/custom_authority.cpp @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2018 Abit More, 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 + +namespace graphene { namespace protocol { + +share_type custom_authority_create_operation::calculate_fee( const fee_parameters_type& k )const +{ + share_type core_fee_required = k.basic_fee; + + if( enabled ) + { + share_type unit_fee = k.price_per_k_unit; + unit_fee *= (valid_to - valid_from).to_seconds(); + unit_fee *= auth.num_auths(); + uint64_t restriction_units = 0; + for( const auto& r : restrictions ) + { + restriction_units += r.get_units(); + } + unit_fee *= restriction_units; + unit_fee /= 1000; + core_fee_required += unit_fee; + } + + return core_fee_required; +} + +void custom_authority_create_operation::validate()const +{ + FC_ASSERT( fee.amount >= 0, "Fee amount can not be negative" ); + + FC_ASSERT( account != GRAPHENE_TEMP_ACCOUNT + && account != GRAPHENE_COMMITTEE_ACCOUNT + && account != GRAPHENE_WITNESS_ACCOUNT + && account != GRAPHENE_RELAXED_COMMITTEE_ACCOUNT, + "Can not create custom authority for special accounts" ); + + FC_ASSERT( valid_from < valid_to, "valid_from must be earlier than valid_to" ); + + // Note: when adding new operation with hard fork, need to check more strictly in evaluator + // TODO add code in evaluator + FC_ASSERT( operation_type.value < operation::count(), "operation_type is too large" ); + + // Note: allow auths to be empty + //FC_ASSERT( auth.num_auths() > 0, "Can not set empty auth" ); + FC_ASSERT( auth.address_auths.size() == 0, "Address auth is not supported" ); + // Note: allow auths to be impossible + //FC_ASSERT( !auth.is_impossible(), "cannot use an imposible authority threshold" ); + + // Note: allow restrictions to be empty + for( const auto& r: restrictions ) + { + // recursively validate member index and argument type + r.validate( operation_type ); + } +} + +share_type custom_authority_update_operation::calculate_fee( const fee_parameters_type& k )const +{ + share_type core_fee_required = k.basic_fee; + + share_type unit_fee = k.price_per_k_unit; + unit_fee *= delta_units; + unit_fee /= 1000; + + return core_fee_required + unit_fee; +} + +void custom_authority_update_operation::validate()const +{ + FC_ASSERT( fee.amount >= 0, "Fee amount can not be negative" ); + + FC_ASSERT( account != GRAPHENE_TEMP_ACCOUNT + && account != GRAPHENE_COMMITTEE_ACCOUNT + && account != GRAPHENE_WITNESS_ACCOUNT + && account != GRAPHENE_RELAXED_COMMITTEE_ACCOUNT, + "Can not create custom authority for special accounts" ); +/* + FC_ASSERT( valid_from < valid_to, "valid_from must be earlier than valid_to" ); + // Note: when adding new operation with hard fork, need to check more strictly in evaluator + // TODO add code in evaluator + FC_ASSERT( operation_type < operation::count(), "operation type too large" ); + FC_ASSERT( auth.num_auths() > 0, "Can not set empty auth" ); + FC_ASSERT( auth.address_auths.size() == 0, "Address auth is not supported" ); + //FC_ASSERT( !auth.is_impossible(), "cannot use an imposible authority threshold" ); + // Note: allow restrictions to be empty + for( const auto& r : restrictions ) + { + // TODO recursively validate member index and argument type + r.validate( operation_type ); + } +*/ +} + +void custom_authority_delete_operation::validate()const +{ +} + +} } // graphene::protocol diff --git a/libraries/protocol/include/graphene/protocol/custom_authority.hpp b/libraries/protocol/include/graphene/protocol/custom_authority.hpp new file mode 100644 index 0000000000..5e2a3b42c6 --- /dev/null +++ b/libraries/protocol/include/graphene/protocol/custom_authority.hpp @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2018 Abit More, 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 + +namespace graphene { namespace protocol { + + /** + * @brief Create a new custom authority + * @ingroup operations + */ + struct custom_authority_create_operation : public base_operation + { + struct fee_parameters_type + { + uint64_t basic_fee = GRAPHENE_BLOCKCHAIN_PRECISION; + uint32_t price_per_k_unit = 100; ///< units = valid seconds * items in auth * items in restrictions + }; + + asset fee; // TODO: defer fee to expiration / update / removal ? + account_id_type account; + uint32_t custom_id; + bool enabled; + time_point_sec valid_from; + time_point_sec valid_to; + unsigned_int operation_type; + authority auth; + vector restrictions; + + extensions_type extensions; + + account_id_type fee_payer()const { return account; } + void validate()const; + share_type calculate_fee(const fee_parameters_type& k)const; + }; + + /** + * @brief Update a custom authority + * @ingroup operations + */ + struct custom_authority_update_operation : public base_operation + { + struct fee_parameters_type + { + uint64_t basic_fee = GRAPHENE_BLOCKCHAIN_PRECISION; + uint32_t price_per_k_unit = 100; ///< units = valid seconds * items in auth * items in restrictions + }; + + asset fee; + account_id_type account; + uint64_t delta_units; // to calculate fee, it will be validated in evaluator + // Note: if start was in the past, when updating, used fee should be deducted + + account_id_type fee_payer()const { return account; } + void validate()const; + share_type calculate_fee(const fee_parameters_type& k)const; + }; + + + /** + * @brief Delete a custom authority + * @ingroup operations + */ + struct custom_authority_delete_operation : public base_operation + { + struct fee_parameters_type { uint64_t fee = GRAPHENE_BLOCKCHAIN_PRECISION; }; + + asset fee; + account_id_type account; + + account_id_type fee_payer()const { return account; } + void validate()const; + }; + +} } // graphene::protocol + +FC_REFLECT( graphene::protocol::custom_authority_create_operation::fee_parameters_type, (basic_fee)(price_per_k_unit) ) +FC_REFLECT( graphene::protocol::custom_authority_update_operation::fee_parameters_type, (basic_fee)(price_per_k_unit) ) +FC_REFLECT( graphene::protocol::custom_authority_delete_operation::fee_parameters_type, (fee) ) + +FC_REFLECT( graphene::protocol::custom_authority_create_operation, + (fee) + (account) + (custom_id) + (enabled) + (valid_from) + (valid_to) + (operation_type) + (auth) + (restrictions) + (extensions) + ) + +FC_REFLECT( graphene::protocol::custom_authority_update_operation, (fee)(account) ) +FC_REFLECT( graphene::protocol::custom_authority_delete_operation, (fee)(account) ) diff --git a/libraries/protocol/include/graphene/protocol/operations.hpp b/libraries/protocol/include/graphene/protocol/operations.hpp index f7ddc01a02..9506628699 100644 --- a/libraries/protocol/include/graphene/protocol/operations.hpp +++ b/libraries/protocol/include/graphene/protocol/operations.hpp @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -101,7 +102,10 @@ namespace graphene { namespace protocol { htlc_redeem_operation, htlc_redeemed_operation, // VIRTUAL htlc_extend_operation, - htlc_refund_operation // VIRTUAL + htlc_refund_operation, // VIRTUAL + custom_authority_create_operation, + custom_authority_update_operation, + custom_authority_delete_operation > operation; /// @} // operations group diff --git a/libraries/protocol/include/graphene/protocol/restriction.hpp b/libraries/protocol/include/graphene/protocol/restriction.hpp new file mode 100644 index 0000000000..1218322bfc --- /dev/null +++ b/libraries/protocol/include/graphene/protocol/restriction.hpp @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2018 Abit More, 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 protocol { + +/** + * Defines the set of valid operation restritions as a discriminated union type. + */ +struct restriction { + enum function_type { + func_eq, + func_ne, + func_lt, + func_le, + func_gt, + func_ge, + func_in, + func_not_in, + func_has_all, + func_has_none, + func_attr, + func_logical_or, + FUNCTION_TYPE_COUNT ///< Sentry value which contains the number of different types + }; + +#define GRAPHENE_OP_RESTRICTION_ARGUMENTS_VARIADIC \ + /* 0 */ void_t, \ + /* 1 */ bool, \ + /* 2 */ int64_t, \ + /* 3 */ string, \ + /* 4 */ time_point_sec, \ + /* 5 */ public_key_type, \ + /* 6 */ fc::sha256, \ + /* 7 */ account_id_type, \ + /* 8 */ asset_id_type, \ + /* 9 */ force_settlement_id_type, \ + /* 10 */ committee_member_id_type, \ + /* 11 */ witness_id_type, \ + /* 12 */ limit_order_id_type, \ + /* 13 */ call_order_id_type, \ + /* 14 */ custom_id_type, \ + /* 15 */ proposal_id_type, \ + /* 16 */ withdraw_permission_id_type, \ + /* 17 */ vesting_balance_id_type, \ + /* 18 */ worker_id_type, \ + /* 19 */ balance_id_type, \ + /* 20 */ flat_set, \ + /* 21 */ flat_set, \ + /* 22 */ flat_set, \ + /* 23 */ flat_set, \ + /* 24 */ flat_set, \ + /* 25 */ flat_set, \ + /* 26 */ flat_set, \ + /* 27 */ flat_set, \ + /* 28 */ flat_set, \ + /* 29 */ flat_set, \ + /* 30 */ flat_set, \ + /* 31 */ flat_set, \ + /* 32 */ flat_set, \ + /* 33 */ flat_set, \ + /* 34 */ flat_set, \ + /* 35 */ flat_set, \ + /* 36 */ flat_set, \ + /* 37 */ flat_set, \ + /* 38 */ flat_set, \ + /* 39 */ vector, \ + /* 40 */ vector> + + using argument_type = fc::static_variant; + + string member_name; + unsigned_int restriction_type; + argument_type argument; + + extensions_type extensions; + + restriction() = default; + restriction(const string& member_name, function_type type, const argument_type& argument) + : member_name(member_name), restriction_type(type), argument(argument) {} + + uint64_t get_units()const; + + /// Validates the restriction with given operation type, to be called by an operation validator + void validate( unsigned_int op_type )const; +}; + +} } // graphene::protocol + +FC_REFLECT_ENUM( graphene::protocol::restriction::function_type, + (func_eq) + (func_ne) + (func_lt) + (func_le) + (func_gt) + (func_ge) + (func_in) + (func_not_in) + (func_has_all) + (func_has_none) + (func_attr) + (func_logical_or) + (FUNCTION_TYPE_COUNT) + ) + +FC_REFLECT( graphene::protocol::restriction, + (member_name) + (restriction_type) + (argument) + (extensions) + ) + + diff --git a/libraries/protocol/include/graphene/protocol/restriction_predicate.hpp b/libraries/protocol/include/graphene/protocol/restriction_predicate.hpp new file mode 100644 index 0000000000..16662faef9 --- /dev/null +++ b/libraries/protocol/include/graphene/protocol/restriction_predicate.hpp @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2019 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 + +namespace graphene { namespace protocol { + +/// A restriction predicate is a function accepting an operation and returning a boolean which indicates whether the +/// operation complies with the restrictions or not +using restriction_predicate_function = std::function; + +/** + * @brief get_restriction_predicate Get a predicate function for the supplied restriction + * @param r The restrictions to evaluate operations against + * @param op_type The tag specifying which operation type the restrictions apply to + * @return A predicate function which evaluates an operation to determine whether it complies with the restriction + */ +restriction_predicate_function get_restriction_predicate(const vector& r, operation::tag_type op_type); + +} } // namespace graphene::protocol diff --git a/libraries/protocol/restriction.cpp b/libraries/protocol/restriction.cpp new file mode 100644 index 0000000000..f7d2f242e5 --- /dev/null +++ b/libraries/protocol/restriction.cpp @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2018 Abit More, 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 +#include + +#include + +namespace graphene { namespace protocol { + +template +using bool_const = std::integral_constant; + +struct restriction_validation_visitor; + +struct argument_get_units_visitor +{ + typedef uint64_t result_type; + + template + inline result_type operator()( const T& t ) + { + return 1; + } + + inline result_type operator()( const fc::sha256& t ) + { + return 4; + } + + inline result_type operator()( const public_key_type& t ) + { + return 4; + } + + inline result_type operator()( const string& t ) + { + return ( t.size() + 7 ) / 8; + } + + template + inline result_type operator()( const flat_set& t ) + { + return t.size() * (*this)( *((const T*)nullptr) ); + } + + template + inline result_type operator()( const flat_set& t ) + { + result_type result = 0; + for( const auto& s : t ) + { + result += ( s.size() + 7 ) / 8; + } + return result; + } + + result_type operator()( const vector& t ) + { + result_type result = 0; + for( const auto& r : t ) + { + result += r.argument.visit(*this); + } + return result; + } +}; + +uint64_t restriction::get_units()const +{ + argument_get_units_visitor vtor; + return argument.visit( vtor ); +} + +void restriction::validate( unsigned_int op_type )const +{ +} + +} } // graphene::protocol diff --git a/libraries/protocol/restriction_predicate.cpp b/libraries/protocol/restriction_predicate.cpp new file mode 100644 index 0000000000..6e71cbac67 --- /dev/null +++ b/libraries/protocol/restriction_predicate.cpp @@ -0,0 +1,583 @@ +/* + * Copyright (c) 2019 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 + +namespace graphene { namespace protocol { +using std::declval; +using std::size_t; +using restriction_function = restriction::function_type; +using restriction_argument = restriction::argument_type; + +// Make our own std::void_t since the real one isn't available in C++14 +template using make_void = void; + +// We use our own is_integral which does not consider bools integral (to disallow comparison between bool and ints) +template constexpr static bool is_integral = + std::conditional_t::value, std::false_type, std::is_integral>::value; + +// Metafunction to check if two types are comparable, which means not void_t, and either the same or both integral +template +constexpr static bool comparable_types = !std::is_same::value && + (std::is_same::value || (is_integral && is_integral)); + +// Metafunction to check if type is an optional type +template constexpr static bool is_optional = false; +template constexpr static bool is_optional> = true; + +// Metafunction to check if type is a container +template constexpr static bool is_container = false; +template constexpr static bool is_container().size())> = true; + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// *** Restriction Predicate Logic *** + +// This file is where the magic happens for BSIP 40, aka Custom Active Authorities. This gets fairly complicated, so +// let me break it down for you: we're given a restriction object and an operation type. The restriction contains one +// or more assertions that will eventually be made on operation instances, and this file creates a predicate function +// that takes those operation instances, evaluates all of the assertions, and returns whether they all pass or not. +// +// So this file works on restriction instances, but only operation *types* -- the actual operation instance won't +// show up until the predicate function this file returns is eventually called! But in here, we need to dig into the +// operation types based on the specifications in the restrictions, make sure all the assertions are valid, then +// create a predicate that runs very quickly, digging into the operation and checking all the assertions. +// +// It kicks off at the bottom of this file, in get_restriction_predicate(), which gets a vector of restrictions and +// an operation type tag. So first, we've got to enter into a template context that knows the actual operation type, +// not just the type tag. And we do a lot of entering into deeper and deeper template contexts, resolving tags to +// actual types so that we know all the type information when we create the predicate, and it can execute quickly +// once it has an operation instance, knowing exactly what types and fields it needs, how to cast things, how to +// execute the assertions, etc. +// +// So to give an overview of the logic, the layers stack up like so, from beginning (bottom of file) to end: +// - get_restriction_predicate() -- gets vector and operation::tag_type, visits operation with the tag +// type to get an actual operation type as a template parameter +// - predicate_creator_visitor -- gets vector and particular operation type, creates predicate that +// wraps predicate from deeper layers, converting the generic operation to a specific operation. This is the last +// layer that can assume the type is an operation; all subsequent layers work on any object or field +// - restrictions_to_predicate() -- converts vector to vector then wraps that in a +// single predicate that returns the logical and of all predicates +// - restriction_to_predicate() -- switches on restriction type to determine legal argument types, creates a +// predicate for that argument type +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +// These typelists contain the argument types legal for various function types: + +// Valid for magnitude comparisons and equality comparisons +using comparable_types_list = fc::typelist; +// Valid for list functions (in, not_in, has_all, has_none) +struct make_flat_set { template struct transform { using type = flat_set; }; }; +using list_types_list = fc::typelist::concat::type + ::transform; +// Valid for equality comparisons but not necessarily magnitude comparisons +using equality_types_list = + typename fc::typelist::concat::type + ::concat::type; +// Valid for attritube assertions +using attr_types_list = fc::typelist>; +// Valid for logical or assertions +using or_types_list = fc::typelist>>; + +// Final implementations of predicate functors +template struct predicate_eq { + Argument a; + constexpr predicate_eq(const Argument& a) : a(a) {} + template + struct can_evaluate_helper : std::false_type {}; template + struct can_evaluate_helper()(declval()))>> { + static constexpr bool value = equality_types_list::contains; + }; + template static constexpr bool can_evaluate = can_evaluate_helper::value; + + // Simple comparison + template>> + constexpr bool operator()(const Field& f) const { return f == a; } + // Compare container size against int + template && is_integral>> + bool operator()(const Field& f) const { return f.size() == a; } + // Compare optional value against same type + template>> + bool operator()(const fc::optional& f) const { return f.valid() && *f == a; } + // Compare optional value against void_t (checks that optional is null) + template::value>> + bool operator()(const fc::optional& f) const { return !f.valid(); } + // Compare containers of different types +// template && is_container && !std::is_same::value +// && comparable_types>> +// bool operator()(const Field& f) const { +// flat_set fs(f.begin(), f.end()); +// return (*this)(fs); +// } +}; +template struct predicate_ne : predicate_eq { + using base = predicate_eq; + predicate_ne(const Argument& a) : base(a) {} + template auto operator()(const Field& f) const { return !base::operator()(f); } +}; +template struct predicate_compare { + Argument a; + constexpr predicate_compare(const Argument& a) : a(a) {} + template struct can_evaluate_helper : std::false_type {}; + template + struct can_evaluate_helper()(declval()))>> { + static constexpr bool value = comparable_types_list::contains; + }; + template static constexpr bool can_evaluate = can_evaluate_helper::value; + + // Simple comparison + template>> + constexpr int8_t operator()(const Field& f) const { return fa? 1 : 0); } + // Compare optional value against same type + template>> + constexpr int8_t operator()(const fc::optional& f) const { + FC_ASSERT(f.valid(), "Cannot compute inequality comparison against a null optional"); + return (*this)(*f); + } +}; +template struct predicate_lt : predicate_compare { + using base = predicate_compare; + constexpr predicate_lt(const Argument& a) : base(a) {} + template constexpr bool operator()(const Field& f) const { return base::operator()(f) < 0; } +}; +template struct predicate_le : predicate_compare { + using base = predicate_compare; + constexpr predicate_le(const Argument& a) : base(a) {} + template constexpr bool operator()(const Field& f) const { return base::operator()(f) <= 0; } +}; +template struct predicate_gt : predicate_compare { + using base = predicate_compare; + constexpr predicate_gt(const Argument& a) : base(a) {} + template constexpr bool operator()(const Field& f) const { return base::operator()(f) > 0; } +}; +template struct predicate_ge : predicate_compare { + using base = predicate_compare; + constexpr predicate_ge(const Argument& a) : base(a) {} + template constexpr bool operator()(const Field& f) const { return base::operator()(f) >= 0; } +}; +template struct predicate_in { template static constexpr bool can_evaluate = false; }; +template struct predicate_in> { + flat_set a; + predicate_in(const flat_set& a) : a(a) {} + template struct can_evaluate_helper : std::false_type {}; + template + struct can_evaluate_helper()(declval()))>> { + static constexpr bool value = equality_types_list::contains; + }; + template static constexpr bool can_evaluate = can_evaluate_helper::value; + + // Simple inclusion check + template>> + auto operator()(const Field& f) const { return a.count(f) != 0; } + // Check for optional value + template>> + auto operator()(const fc::optional& f) const { + FC_ASSERT(f.valid(), "Cannot compute whether null optional is in list"); + return (*this)(*f); + } +}; +template struct predicate_not_in : predicate_in{ + using base = predicate_in; + constexpr predicate_not_in(const Argument& a) : base(a) {} + template constexpr bool operator()(const Field& f) const { return !base::operator()(f); } +}; +template struct predicate_has_all { template static constexpr bool can_evaluate = false; }; +template struct predicate_has_all> { + flat_set a; + predicate_has_all(const flat_set& a) : a(a) {} + template struct can_evaluate_helper : std::false_type {}; + template + struct can_evaluate_helper()(declval()))>> { + static constexpr bool value = equality_types_list::contains; + }; + template static constexpr bool can_evaluate = can_evaluate_helper::value; + + // Field is already flat_set + template>> + auto operator()(const flat_set& f) const { + if (f.size() < a.size()) return false; + return std::includes(f.begin(), f.end(), a.begin(), a.end()); + } + // Field is other container; convert to flat_set + template && + comparable_types>> + auto operator()(const Field& f) const { + if (f.size() < a.size()) return false; + flat_set fs(f.begin(), f.end()); + return (*this)(fs); + } + // Field is optional container + template && + comparable_types>> + auto operator()(const fc::optional& f) const { + FC_ASSERT(f.valid(), "Cannot compute whether all elements of null optional container are in other container"); + return (*this)(*f); + } +}; +template struct predicate_has_none { template static constexpr bool can_evaluate = false; }; +template struct predicate_has_none> { + flat_set a; + predicate_has_none(const flat_set& a) : a(a) {} + template struct can_evaluate_helper : std::false_type {}; + template + struct can_evaluate_helper()(declval()))>> { + static constexpr bool value = equality_types_list::contains; + }; + template static constexpr bool can_evaluate = can_evaluate_helper::value; + + // Field is already flat_set + template>> + auto operator()(const flat_set& f) const { + flat_set intersection; + std::set_intersection(f.begin(), f.end(), a.begin(), a.end(), + std::inserter(intersection, intersection.begin())); + return intersection.empty(); + } + // Field is other container; convert to flat_set + template && + comparable_types>> + auto operator()(const Field& f) const { + flat_set fs(f.begin(), f.end()); + return (*this)(fs); + } + // Field is optional container + template && + comparable_types>> + auto operator()(const fc::optional& f) const { + FC_ASSERT(f.valid(), "Cannot compute whether no elements of null optional container are in other container"); + return (*this)(*f); + } +}; + +// Type alias for a predicate on a particular field type +template +using object_restriction_predicate = std::function; + +// Template to visit the restriction argument, resolving its type, and create the appropriate predicate functor, or +// throw if the types are not compatible for the predicate assertion +template class Predicate, typename Field> +struct restriction_argument_visitor { + using result_type = object_restriction_predicate; + + template::template can_evaluate>> + result_type make_predicate(const Argument& a, short) { + return Predicate(a); + } + template + result_type make_predicate(const Argument&, long) { + FC_THROW_EXCEPTION(fc::assert_exception, "Invalid argument types for predicate: ${Field}, ${Argument}", + ("Field", fc::get_typename::name())("Argument", fc::get_typename::name())); + } + template + result_type operator()(const Argument& a) { return make_predicate(a, short()); } +}; + +// Forward declarations of predicate making functions so we can recurse into them +template object_restriction_predicate restrictions_to_predicate(vector); +template +object_restriction_predicate create_predicate_function(restriction_function, restriction_argument); + +template +object_restriction_predicate create_logical_or_predicate(vector> rs) { + vector> predicates; + std::transform(std::make_move_iterator(rs.begin()), std::make_move_iterator(rs.end()), + std::back_inserter(predicates), restrictions_to_predicate); + + return [predicates=std::move(predicates)](const Field& field) { + return std::any_of(predicates.begin(), predicates.end(), [&f=field](const auto& p) { return p(f); }); + }; +} + +template +struct attribute_assertion { + static object_restriction_predicate create(vector&& rs) { + return restrictions_to_predicate(std::move(rs)); + } +}; +template +struct attribute_assertion> { + static object_restriction_predicate> create(vector&& rs) { + return [p=restrictions_to_predicate(std::move(rs))](const fc::optional& f) { + FC_ASSERT(f.valid(), "Cannot evaluate attribute assertion on null optional field"); + return p(*f); + }; + } +}; +template +struct attribute_assertion> { + static object_restriction_predicate> create(vector&& rs) { + return [p=restrictions_to_predicate(std::move(rs))](const extension& x) { + return p(x.value); + }; + } +}; + +template +object_restriction_predicate create_predicate_function(restriction_function func, + restriction_argument arg) { + try { + switch(func) { + case restriction::func_eq: { + restriction_argument_visitor visitor; + return arg.visit(visitor); + } + case restriction::func_ne: { + restriction_argument_visitor visitor; + return arg.visit(visitor); + } + case restriction::func_lt: { + restriction_argument_visitor visitor; + return arg.visit(visitor); + } + case restriction::func_le: { + restriction_argument_visitor visitor; + return arg.visit(visitor); + } + case restriction::func_gt: { + restriction_argument_visitor visitor; + return arg.visit(visitor); + } + case restriction::func_ge: { + restriction_argument_visitor visitor; + return arg.visit(visitor); + } + case restriction::func_in: { + restriction_argument_visitor visitor; + return arg.visit(visitor); + } + case restriction::func_not_in: { + restriction_argument_visitor visitor; + return arg.visit(visitor); + } + case restriction::func_has_all: { + restriction_argument_visitor visitor; + return arg.visit(visitor); + } + case restriction::func_has_none: { + restriction_argument_visitor visitor; + return arg.visit(visitor); + } + case restriction::func_attr: + FC_ASSERT(arg.which() == restriction_argument::tag>::value, + "Argument type for attribute assertion must be restriction list"); + return attribute_assertion::create(std::move(arg.get>())); + case restriction::func_logical_or: + FC_ASSERT(arg.which() == restriction_argument::tag>>::value, + "Argument type for logical or restriction must be list of restriction lists"); + return create_logical_or_predicate(std::move(arg.get>>())); + default: + FC_THROW_EXCEPTION(fc::assert_exception, "Invalid function type on restriction"); + } + } FC_CAPTURE_AND_RETHROW( (func) ) +} + +/** + * @brief Create a predicate asserting on the field of an object a restriction is referencing + * + * @tparam Object The type the restriction restricts + * + * A restriction specifies requirements about a field of an object. This struct shifts the focus from the object type + * the restriction references to the particular field type, creates a predicate on that field, and wraps that + * predicate to accept the object type and invoke the inner predicate on the specified field. + */ +template +struct object_field_predicator { + restriction r; + object_field_predicator(restriction&& r) : r(std::move(r)) {} + mutable fc::optional> predicate; + + template + void operator()(const char* member_name) const { + if (r.member_name == member_name) { + auto function = static_cast(r.restriction_type.value); + auto p = create_predicate_function(function, std::move(r.argument)); + predicate = [p=std::move(p)](const Object& obj) { return p(obj.*field); }; + } + } +}; +/// Helper function to invoke object_field_predicator +template::is_defined::value>> +object_restriction_predicate create_field_predicate(restriction&& r, short) { + object_field_predicator visitor(std::move(r)); + fc::reflector::visit(visitor); + FC_ASSERT(visitor.predicate.valid(), "Invalid member name ${O}::${M} in restriction", + ("O", fc::get_typename::name())("M", visitor.r.member_name)); + return std::move(*visitor.predicate); +} +template +object_restriction_predicate create_field_predicate(restriction&& r, long) { + FC_THROW_EXCEPTION(fc::assert_exception, "Invalid restriction references member of non-object type: ${O}::${M}", + ("O", fc::get_typename::name())("M", r.member_name)); +} + +template +object_restriction_predicate restrictions_to_predicate(vector rs) { + vector> predicates; + std::transform(std::make_move_iterator(rs.begin()), std::make_move_iterator(rs.end()), + std::back_inserter(predicates), + [](auto&& r) { return create_field_predicate(std::move(r), short()); }); + + return [predicates=std::move(predicates)](const Object& field) { + return std::all_of(predicates.begin(), predicates.end(), [&f=field](const auto& p) { return p(f); }); + }; +} + +/** + * @brief Visitor on the generic operation to determine the actual operation type and make a predicate for it + * + * This struct is used as a visitor to the operation static_variant. It has a visit method which is called with the + * particular operation type as a template argument, and this method constructs a restriction predicate which accepts + * an operation wrapping the visited type and returns whether the operation complies with all restrictions or not. + */ +struct predicate_creator_visitor { + using result_type = restriction_predicate_function; + + const vector& restrictions; + + predicate_creator_visitor(const vector& restrictions) : restrictions(restrictions) {} + + template + result_type operator()(const Op&) { + auto predicate = restrictions_to_predicate(restrictions); + return [predicate=std::move(predicate)](const operation& op) { + FC_ASSERT(op.which() == operation::tag::value, + "Supplied operation is incorrect type for restriction predicate"); + return predicate(op.get()); + }; + } +}; + +restriction_predicate_function get_restriction_predicate(const vector &r, operation::tag_type op_type) { + predicate_creator_visitor visitor(r); + return operation::visit(op_type, visitor, static_cast(nullptr)); +} + +// Now for some compile-time tests of the metafunctions we use in here... +#ifndef NDEBUG +static_assert(!is_optional, ""); +static_assert(is_optional>, ""); + +static_assert(!is_container, ""); +static_assert(is_container>, ""); + +static_assert(predicate_eq(10)(20) == false, ""); +static_assert(predicate_eq(10)(5) == false, ""); +static_assert(predicate_eq(10)(10) == true, ""); + +static_assert(predicate_eq::can_evaluate == false, ""); +static_assert(predicate_eq::can_evaluate == false, ""); +static_assert(predicate_eq::can_evaluate == false, ""); +static_assert(predicate_eq::can_evaluate == true, ""); +static_assert(predicate_eq::can_evaluate == true, ""); +static_assert(predicate_eq::can_evaluate> == true, ""); +static_assert(predicate_eq::can_evaluate> == true, ""); +static_assert(predicate_eq::can_evaluate == true, ""); +static_assert(predicate_eq::can_evaluate == false, ""); +static_assert(predicate_eq::can_evaluate == false, ""); +static_assert(predicate_eq::can_evaluate> == true, ""); +static_assert(predicate_eq::can_evaluate> == true, ""); +static_assert(predicate_eq::can_evaluate> == true, ""); +static_assert(predicate_eq>::can_evaluate> == true, ""); +//static_assert(predicate_eq>::can_evaluate> == true, ""); +static_assert(predicate_eq::can_evaluate> == false, ""); +static_assert(predicate_eq::can_evaluate == true, ""); +static_assert(predicate_ne::can_evaluate == false, ""); +static_assert(predicate_ne::can_evaluate == false, ""); +static_assert(predicate_ne::can_evaluate == true, ""); +static_assert(predicate_ne::can_evaluate == true, ""); +static_assert(predicate_ne::can_evaluate> == true, ""); +static_assert(predicate_ne::can_evaluate> == true, ""); +static_assert(predicate_ne::can_evaluate == true, ""); +static_assert(predicate_ne::can_evaluate == false, ""); +static_assert(predicate_ne::can_evaluate == false, ""); +static_assert(predicate_ne::can_evaluate> == true, ""); +static_assert(predicate_ne::can_evaluate> == true, ""); +static_assert(predicate_ne::can_evaluate> == true, ""); +static_assert(predicate_ne::can_evaluate == true, ""); + +static_assert(predicate_compare(10)(20) == 1, ""); +static_assert(predicate_compare(10)(5) == -1, ""); +static_assert(predicate_compare(10)(10) == 0, ""); +static_assert(predicate_lt(10)(20) == false, ""); +static_assert(predicate_lt(10)(5) == true, ""); +static_assert(predicate_lt(10)(10) == false, ""); +static_assert(predicate_le(10)(20) == false, ""); +static_assert(predicate_le(10)(5) == true, ""); +static_assert(predicate_le(10)(10) == true, ""); +static_assert(predicate_gt(10)(20) == true, ""); +static_assert(predicate_gt(10)(5) == false, ""); +static_assert(predicate_gt(10)(10) == false, ""); +static_assert(predicate_ge(10)(20) == true, ""); +static_assert(predicate_ge(10)(5) == false, ""); +static_assert(predicate_ge(10)(10) == true, ""); + +static_assert(predicate_compare::can_evaluate == true, ""); +static_assert(predicate_compare::can_evaluate == true, ""); +static_assert(predicate_compare::can_evaluate == true, ""); +static_assert(predicate_compare::can_evaluate> == false, ""); +static_assert(predicate_compare::can_evaluate> == true, ""); +static_assert(predicate_compare::can_evaluate> == true, ""); +static_assert(predicate_compare::can_evaluate> == true, ""); +static_assert(predicate_lt::can_evaluate == true, ""); +static_assert(predicate_lt::can_evaluate == true, ""); +static_assert(predicate_lt::can_evaluate == true, ""); +static_assert(predicate_lt::can_evaluate> == false, ""); +static_assert(predicate_lt::can_evaluate> == true, ""); +static_assert(predicate_lt::can_evaluate> == true, ""); +static_assert(predicate_lt::can_evaluate> == true, ""); + +static_assert(predicate_in::can_evaluate == false, ""); +static_assert(predicate_in>::can_evaluate == false, ""); +static_assert(predicate_in>::can_evaluate == true, ""); +static_assert(predicate_in>::can_evaluate> == false, ""); +static_assert(predicate_in>::can_evaluate> == true, ""); +static_assert(predicate_not_in::can_evaluate == false, ""); +static_assert(predicate_not_in>::can_evaluate == false, ""); +static_assert(predicate_not_in>::can_evaluate == true, ""); +static_assert(predicate_not_in>::can_evaluate> == false, ""); +static_assert(predicate_not_in>::can_evaluate> == true, ""); + +static_assert(predicate_has_all::can_evaluate == false, ""); +static_assert(predicate_has_all>::can_evaluate == false, ""); +static_assert(predicate_has_all>::can_evaluate == false, ""); +static_assert(predicate_has_all>::can_evaluate> == true, ""); +static_assert(predicate_has_all>::can_evaluate> == false, ""); +static_assert(predicate_has_all>::can_evaluate>> == true, ""); +static_assert(predicate_has_none::can_evaluate == false, ""); +static_assert(predicate_has_none>::can_evaluate == false, ""); +static_assert(predicate_has_none>::can_evaluate == false, ""); +static_assert(predicate_has_none>::can_evaluate> == true, ""); +static_assert(predicate_has_none>::can_evaluate> == false, ""); +static_assert(predicate_has_none>::can_evaluate>> == true, ""); + +#endif + +} } // namespace graphene::protocol diff --git a/tests/tests/custom_authority_tests.cpp b/tests/tests/custom_authority_tests.cpp new file mode 100644 index 0000000000..69cb917de3 --- /dev/null +++ b/tests/tests/custom_authority_tests.cpp @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2019 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 + +BOOST_AUTO_TEST_SUITE(custom_authority_tests) + +#define FUNC(TYPE) BOOST_PP_CAT(restriction::func_, TYPE) + +BOOST_AUTO_TEST_CASE(restriction_predicate_tests) { try { + using namespace graphene::protocol; + vector restrictions; + transfer_operation transfer; + + restrictions.emplace_back("to", FUNC(eq), account_id_type(12)); + BOOST_CHECK(get_restriction_predicate(restrictions, operation::tag::value)(transfer) == false); + transfer.to = account_id_type(12); + BOOST_CHECK(get_restriction_predicate(restrictions, operation::tag::value)(transfer) == true); + + restrictions.front() = restriction("foo", FUNC(eq), account_id_type(12)); + BOOST_CHECK_THROW(get_restriction_predicate(restrictions, operation::tag::value), + fc::assert_exception); + restrictions.front() = restriction("to", FUNC(eq), asset_id_type(12)); + BOOST_CHECK_THROW(get_restriction_predicate(restrictions, operation::tag::value), + fc::assert_exception); + + restrictions.front() = restriction("fee", FUNC(attr), + vector{restriction("asset_id", FUNC(eq), asset_id_type(0))}); + BOOST_CHECK(get_restriction_predicate(restrictions, operation::tag::value)(transfer) == true); + restrictions.front().argument.get>().front().argument = asset_id_type(1); + BOOST_CHECK(get_restriction_predicate(restrictions, operation::tag::value)(transfer) == false); + restrictions.emplace_back("to", FUNC(eq), account_id_type(12)); + transfer.to = account_id_type(12); + transfer.fee.asset_id = asset_id_type(1); + BOOST_CHECK(get_restriction_predicate(restrictions, operation::tag::value)(transfer) == true); + transfer.to = account_id_type(10); + BOOST_CHECK(get_restriction_predicate(restrictions, operation::tag::value)(transfer) == false); + + account_update_operation update; + restrictions.clear(); + restrictions.emplace_back("extensions", FUNC(attr), + vector{restriction("owner_special_authority", FUNC(eq), void_t())}); + auto predicate = get_restriction_predicate(restrictions, operation::tag::value); + BOOST_CHECK_THROW(predicate(transfer), fc::assert_exception); + BOOST_CHECK(predicate(update) == true); + update.extensions.value.owner_special_authority = special_authority(); + BOOST_CHECK(predicate(update) == false); + restrictions.front().argument.get>().front().restriction_type = FUNC(ne); + predicate = get_restriction_predicate(restrictions, operation::tag::value); + BOOST_CHECK(predicate(update) == true); +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_SUITE_END() From 794f1ab9ab28938f2c021514bd5981f5b434f6a8 Mon Sep 17 00:00:00 2001 From: Nathan Hourt Date: Thu, 18 Jul 2019 13:04:41 -0500 Subject: [PATCH 02/41] BSIP 40: Improve build time, update docs --- libraries/protocol/restriction_predicate.cpp | 148 +++++++++++-------- 1 file changed, 90 insertions(+), 58 deletions(-) diff --git a/libraries/protocol/restriction_predicate.cpp b/libraries/protocol/restriction_predicate.cpp index 6e71cbac67..16e84666b8 100644 --- a/libraries/protocol/restriction_predicate.cpp +++ b/libraries/protocol/restriction_predicate.cpp @@ -44,10 +44,6 @@ template constexpr static bool comparable_types = !std::is_same::value && (std::is_same::value || (is_integral && is_integral)); -// Metafunction to check if type is an optional type -template constexpr static bool is_optional = false; -template constexpr static bool is_optional> = true; - // Metafunction to check if type is a container template constexpr static bool is_container = false; template constexpr static bool is_container().size())> = true; @@ -56,32 +52,42 @@ template constexpr static bool is_container() // *** Restriction Predicate Logic *** // This file is where the magic happens for BSIP 40, aka Custom Active Authorities. This gets fairly complicated, so -// let me break it down for you: we're given a restriction object and an operation type. The restriction contains one -// or more assertions that will eventually be made on operation instances, and this file creates a predicate function -// that takes those operation instances, evaluates all of the assertions, and returns whether they all pass or not. +// let me break it down: we're given a restriction object and an operation type. The restriction contains one or more +// assertions that will eventually be made on operation instances, and this file creates a predicate function that +// takes such an operation instance, evaluates all of the assertions, and returns whether they all pass or not. // // So this file works on restriction instances, but only operation *types* -- the actual operation instance won't -// show up until the predicate function this file returns is eventually called! But in here, we need to dig into the -// operation types based on the specifications in the restrictions, make sure all the assertions are valid, then -// create a predicate that runs very quickly, digging into the operation and checking all the assertions. +// show up until the predicate function this file returns is eventually called. But first, we need to dig into the +// operation/fields based on the specifications in the restrictions, make sure all the assertions are valid, then +// create a predicate that runs quickly, looking through the operation and checking all the assertions. // // It kicks off at the bottom of this file, in get_restriction_predicate(), which gets a vector of restrictions and // an operation type tag. So first, we've got to enter into a template context that knows the actual operation type, // not just the type tag. And we do a lot of entering into deeper and deeper template contexts, resolving tags to // actual types so that we know all the type information when we create the predicate, and it can execute quickly -// once it has an operation instance, knowing exactly what types and fields it needs, how to cast things, how to -// execute the assertions, etc. +// on an operation instance, knowing exactly what types and fields it needs, how to cast things, how to execute the +// assertions, etc. // -// So to give an overview of the logic, the layers stack up like so, from beginning (bottom of file) to end: +// To give an overview of the logic, the layers stack up like so, from beginning (bottom of file) to end: // - get_restriction_predicate() -- gets vector and operation::tag_type, visits operation with the tag // type to get an actual operation type as a template parameter -// - predicate_creator_visitor -- gets vector and particular operation type, creates predicate that -// wraps predicate from deeper layers, converting the generic operation to a specific operation. This is the last -// layer that can assume the type is an operation; all subsequent layers work on any object or field -// - restrictions_to_predicate() -- converts vector to vector then wraps that in a -// single predicate that returns the logical and of all predicates -// - restriction_to_predicate() -- switches on restriction type to determine legal argument types, creates a -// predicate for that argument type +// - operation_type_resolver -- gets vector and operation type tag, resolves tag to operation type +// template argument. This is the last layer that can assume the type being restricted is an operation; all +// subsequent layers work on any object or field +// - restrictions_to_predicate() -- takes a vector and creates a predicate for each of them, +// but returns a single predicate that returns true only if all sub-predicates return true +// - object_field_predicator -- visits the object being restricted to resolve which specific field is the +// subject of the restriction +// - create_logical_or_predicate() -- If the predicate is a logical OR function, instead of using the +// object_field_predicator, we recurse into restrictions_to_predicate for each branch of the OR, returning a +// predicate which returns true if any branch of the OR passes +// - create_predicate_function() -- switches on restriction type to determine which predicate template to use +// going forward +// - restriction_argument_visitor -- Determines what type the restriction argument is and creates a +// predicate functor for that type +// - attribute_assertion -- If the restriction is an attribute assertion, instead of using the +// restriction_argument_visitor, we recurse into restrictions_to_predicate with the current Field as the Object +// - predicate_xyz -- These are functors implementing the various predicate function types ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // These typelists contain the argument types legal for various function types: @@ -105,6 +111,35 @@ using attr_types_list = fc::typelist>; // Valid for logical or assertions using or_types_list = fc::typelist>>; +// slimmer_visitor and static_variant_slimmer accelerate build times by reducing the number of elements in the +// argument static variant to only those supported for a given function type. This reduces build time because it +// eliminates many options the compiler has to explore when visiting the argument variant to create a predicate +template +struct slimmer_visitor { + using result_type = typename List::template apply; + template constexpr static bool type_in_list = List::template contains; + + template>> + result_type do_cast(const T& content, short) { return result_type(content); } + template + result_type do_cast(const T&, long) { + FC_THROW_EXCEPTION(fc::assert_exception, "Invalid argument type for restriction function type"); + } + + template result_type operator()(const T& content) { + return do_cast(content, short()); + } +}; +template +struct static_variant_slimmer { + using result_type = typename slimmer_visitor::result_type; + template + static result_type slim(SV& variant) { + slimmer_visitor visitor; + return variant.visit(visitor); + } +}; + // Final implementations of predicate functors template struct predicate_eq { Argument a; @@ -130,6 +165,7 @@ template struct predicate_eq { template::value>> bool operator()(const fc::optional& f) const { return !f.valid(); } // Compare containers of different types +// TODO: Figure out how to actually make this compile... x( // template && is_container && !std::is_same::value // && comparable_types>> @@ -300,21 +336,8 @@ struct restriction_argument_visitor { result_type operator()(const Argument& a) { return make_predicate(a, short()); } }; -// Forward declarations of predicate making functions so we can recurse into them +// Forward declaration of restrictions_to_predicate, because attribute assertions and logical ORs recurse into it template object_restriction_predicate restrictions_to_predicate(vector); -template -object_restriction_predicate create_predicate_function(restriction_function, restriction_argument); - -template -object_restriction_predicate create_logical_or_predicate(vector> rs) { - vector> predicates; - std::transform(std::make_move_iterator(rs.begin()), std::make_move_iterator(rs.end()), - std::back_inserter(predicates), restrictions_to_predicate); - - return [predicates=std::move(predicates)](const Field& field) { - return std::any_of(predicates.begin(), predicates.end(), [&f=field](const auto& p) { return p(f); }); - }; -} template struct attribute_assertion { @@ -341,58 +364,53 @@ struct attribute_assertion> { }; template -object_restriction_predicate create_predicate_function(restriction_function func, - restriction_argument arg) { +object_restriction_predicate create_predicate_function(restriction_function func, restriction_argument arg) { try { switch(func) { case restriction::func_eq: { restriction_argument_visitor visitor; - return arg.visit(visitor); + return static_variant_slimmer::slim(arg).visit(visitor); } case restriction::func_ne: { restriction_argument_visitor visitor; - return arg.visit(visitor); + return static_variant_slimmer::slim(arg).visit(visitor); } case restriction::func_lt: { restriction_argument_visitor visitor; - return arg.visit(visitor); + return static_variant_slimmer::slim(arg).visit(visitor); } case restriction::func_le: { restriction_argument_visitor visitor; - return arg.visit(visitor); + return static_variant_slimmer::slim(arg).visit(visitor); } case restriction::func_gt: { restriction_argument_visitor visitor; - return arg.visit(visitor); + return static_variant_slimmer::slim(arg).visit(visitor); } case restriction::func_ge: { restriction_argument_visitor visitor; - return arg.visit(visitor); + return static_variant_slimmer::slim(arg).visit(visitor); } case restriction::func_in: { restriction_argument_visitor visitor; - return arg.visit(visitor); + return static_variant_slimmer::slim(arg).visit(visitor); } case restriction::func_not_in: { restriction_argument_visitor visitor; - return arg.visit(visitor); + return static_variant_slimmer::slim(arg).visit(visitor); } case restriction::func_has_all: { restriction_argument_visitor visitor; - return arg.visit(visitor); + return static_variant_slimmer::slim(arg).visit(visitor); } case restriction::func_has_none: { restriction_argument_visitor visitor; - return arg.visit(visitor); + return static_variant_slimmer::slim(arg).visit(visitor); } case restriction::func_attr: FC_ASSERT(arg.which() == restriction_argument::tag>::value, "Argument type for attribute assertion must be restriction list"); return attribute_assertion::create(std::move(arg.get>())); - case restriction::func_logical_or: - FC_ASSERT(arg.which() == restriction_argument::tag>>::value, - "Argument type for logical or restriction must be list of restriction lists"); - return create_logical_or_predicate(std::move(arg.get>>())); default: FC_THROW_EXCEPTION(fc::assert_exception, "Invalid function type on restriction"); } @@ -438,12 +456,29 @@ object_restriction_predicate create_field_predicate(restriction&& r, lon ("O", fc::get_typename::name())("M", r.member_name)); } +template +object_restriction_predicate create_logical_or_predicate(vector> rs) { + vector> predicates; + std::transform(std::make_move_iterator(rs.begin()), std::make_move_iterator(rs.end()), + std::back_inserter(predicates), restrictions_to_predicate); + + return [predicates=std::move(predicates)](const Object& object) { + return std::any_of(predicates.begin(), predicates.end(), [&o=object](const auto& p) { return p(o); }); + }; +} + template object_restriction_predicate restrictions_to_predicate(vector rs) { vector> predicates; std::transform(std::make_move_iterator(rs.begin()), std::make_move_iterator(rs.end()), - std::back_inserter(predicates), - [](auto&& r) { return create_field_predicate(std::move(r), short()); }); + std::back_inserter(predicates), [](restriction&& r) { + if (r.restriction_type.value == restriction::func_logical_or) { + FC_ASSERT(r.argument.which() == restriction_argument::tag>>::value, + "Restriction argument for logical OR function type must be list of restriction lists."); + return create_logical_or_predicate(std::move(r.argument.get>>())); + } + return create_field_predicate(std::move(r), short()); + }); return [predicates=std::move(predicates)](const Object& field) { return std::all_of(predicates.begin(), predicates.end(), [&f=field](const auto& p) { return p(f); }); @@ -457,12 +492,12 @@ object_restriction_predicate restrictions_to_predicate(vector& restrictions; - predicate_creator_visitor(const vector& restrictions) : restrictions(restrictions) {} + operation_type_resolver(const vector& restrictions) : restrictions(restrictions) {} template result_type operator()(const Op&) { @@ -476,15 +511,12 @@ struct predicate_creator_visitor { }; restriction_predicate_function get_restriction_predicate(const vector &r, operation::tag_type op_type) { - predicate_creator_visitor visitor(r); + operation_type_resolver visitor(r); return operation::visit(op_type, visitor, static_cast(nullptr)); } // Now for some compile-time tests of the metafunctions we use in here... #ifndef NDEBUG -static_assert(!is_optional, ""); -static_assert(is_optional>, ""); - static_assert(!is_container, ""); static_assert(is_container>, ""); From 0a588c1f2c57a20eb813d72d5978bfc5a8fae6ef Mon Sep 17 00:00:00 2001 From: Nathan Hourt Date: Fri, 19 Jul 2019 14:09:01 -0500 Subject: [PATCH 03/41] BSIP 40: Finish writing operations Notably, I made an overhaul to the fee logic here. The old logic was incredibly complex and had weird multiply-by-zero edge cases to get a free fee... I dropped the whole thing and replaced it with a simple price per byte, which should have a fairly similar effect without segfaulting anyone's brain. --- libraries/protocol/CMakeLists.txt | 1 - libraries/protocol/custom_authority.cpp | 111 +++++----------- .../include/graphene/protocol/authority.hpp | 2 +- .../graphene/protocol/custom_authority.hpp | 123 ++++++++++-------- .../include/graphene/protocol/restriction.hpp | 5 - .../include/graphene/protocol/types.hpp | 3 +- libraries/protocol/restriction.cpp | 104 --------------- libraries/protocol/restriction_predicate.cpp | 36 ++--- 8 files changed, 129 insertions(+), 256 deletions(-) delete mode 100644 libraries/protocol/restriction.cpp diff --git a/libraries/protocol/CMakeLists.txt b/libraries/protocol/CMakeLists.txt index b623745b37..21955919cb 100644 --- a/libraries/protocol/CMakeLists.txt +++ b/libraries/protocol/CMakeLists.txt @@ -17,7 +17,6 @@ list(APPEND SOURCES account.cpp authority.cpp special_authority.cpp custom_authority.cpp - restriction.cpp restriction_predicate.cpp committee_member.cpp custom.cpp diff --git a/libraries/protocol/custom_authority.cpp b/libraries/protocol/custom_authority.cpp index c75c2216cb..2229052217 100644 --- a/libraries/protocol/custom_authority.cpp +++ b/libraries/protocol/custom_authority.cpp @@ -23,100 +23,59 @@ */ #include #include +#include namespace graphene { namespace protocol { -share_type custom_authority_create_operation::calculate_fee( const fee_parameters_type& k )const -{ +share_type custom_authority_create_operation::calculate_fee(const fee_parameters_type& k)const { share_type core_fee_required = k.basic_fee; - - if( enabled ) - { - share_type unit_fee = k.price_per_k_unit; - unit_fee *= (valid_to - valid_from).to_seconds(); - unit_fee *= auth.num_auths(); - uint64_t restriction_units = 0; - for( const auto& r : restrictions ) - { - restriction_units += r.get_units(); - } - unit_fee *= restriction_units; - unit_fee /= 1000; - core_fee_required += unit_fee; - } - + core_fee_required += k.price_per_byte * (fc::raw::pack_size(restrictions) + fc::raw::pack_size(auth)); return core_fee_required; } -void custom_authority_create_operation::validate()const -{ - FC_ASSERT( fee.amount >= 0, "Fee amount can not be negative" ); +void custom_authority_create_operation::validate()const { + FC_ASSERT(fee.amount >= 0, "Fee amount can not be negative"); - FC_ASSERT( account != GRAPHENE_TEMP_ACCOUNT - && account != GRAPHENE_COMMITTEE_ACCOUNT - && account != GRAPHENE_WITNESS_ACCOUNT - && account != GRAPHENE_RELAXED_COMMITTEE_ACCOUNT, - "Can not create custom authority for special accounts" ); + FC_ASSERT(account != GRAPHENE_TEMP_ACCOUNT + && account != GRAPHENE_COMMITTEE_ACCOUNT + && account != GRAPHENE_WITNESS_ACCOUNT + && account != GRAPHENE_RELAXED_COMMITTEE_ACCOUNT, + "Can not create custom authority for special accounts"); - FC_ASSERT( valid_from < valid_to, "valid_from must be earlier than valid_to" ); + FC_ASSERT(valid_from < valid_to, "valid_from must be earlier than valid_to"); - // Note: when adding new operation with hard fork, need to check more strictly in evaluator - // TODO add code in evaluator - FC_ASSERT( operation_type.value < operation::count(), "operation_type is too large" ); + // Note: The authentication authority can be empty, but it cannot be impossible to satisify. Disable the authority + // using the `enabled` boolean rather than setting an impossible authority. - // Note: allow auths to be empty - //FC_ASSERT( auth.num_auths() > 0, "Can not set empty auth" ); - FC_ASSERT( auth.address_auths.size() == 0, "Address auth is not supported" ); - // Note: allow auths to be impossible - //FC_ASSERT( !auth.is_impossible(), "cannot use an imposible authority threshold" ); + FC_ASSERT(auth.address_auths.size() == 0, "Address authorities are not supported"); + FC_ASSERT(!auth.is_impossible(), "Cannot use an imposible authority threshold"); - // Note: allow restrictions to be empty - for( const auto& r: restrictions ) - { - // recursively validate member index and argument type - r.validate( operation_type ); - } + // Validate restrictions by constructing a predicate for them; this throws if restrictions aren't valid + get_restriction_predicate(restrictions, operation_type); } -share_type custom_authority_update_operation::calculate_fee( const fee_parameters_type& k )const -{ +share_type custom_authority_update_operation::calculate_fee(const fee_parameters_type& k)const { share_type core_fee_required = k.basic_fee; - - share_type unit_fee = k.price_per_k_unit; - unit_fee *= delta_units; - unit_fee /= 1000; - - return core_fee_required + unit_fee; + core_fee_required += k.price_per_byte * fc::raw::pack_size(restrictions_to_add); + if (new_auth) + core_fee_required += k.price_per_byte * fc::raw::pack_size(*new_auth); + return core_fee_required; } -void custom_authority_update_operation::validate()const -{ - FC_ASSERT( fee.amount >= 0, "Fee amount can not be negative" ); - - FC_ASSERT( account != GRAPHENE_TEMP_ACCOUNT - && account != GRAPHENE_COMMITTEE_ACCOUNT - && account != GRAPHENE_WITNESS_ACCOUNT - && account != GRAPHENE_RELAXED_COMMITTEE_ACCOUNT, - "Can not create custom authority for special accounts" ); -/* - FC_ASSERT( valid_from < valid_to, "valid_from must be earlier than valid_to" ); - // Note: when adding new operation with hard fork, need to check more strictly in evaluator - // TODO add code in evaluator - FC_ASSERT( operation_type < operation::count(), "operation type too large" ); - FC_ASSERT( auth.num_auths() > 0, "Can not set empty auth" ); - FC_ASSERT( auth.address_auths.size() == 0, "Address auth is not supported" ); - //FC_ASSERT( !auth.is_impossible(), "cannot use an imposible authority threshold" ); - // Note: allow restrictions to be empty - for( const auto& r : restrictions ) - { - // TODO recursively validate member index and argument type - r.validate( operation_type ); +void custom_authority_update_operation::validate()const { + FC_ASSERT(fee.amount >= 0, "Fee amount can not be negative"); + + FC_ASSERT(account != GRAPHENE_TEMP_ACCOUNT + && account != GRAPHENE_COMMITTEE_ACCOUNT + && account != GRAPHENE_WITNESS_ACCOUNT + && account != GRAPHENE_RELAXED_COMMITTEE_ACCOUNT, + "Can not create custom authority for special accounts"); + if (new_valid_from && new_valid_to) + FC_ASSERT(*new_valid_from < new_valid_to, "valid_from must be earlier than valid_to"); + if (new_auth) { + FC_ASSERT(!new_auth->is_impossible(), "Cannot use an impossible authority threshold"); + FC_ASSERT(new_auth->address_auths.size() == 0, "Address auth is not supported"); } -*/ -} - -void custom_authority_delete_operation::validate()const -{ } } } // graphene::protocol diff --git a/libraries/protocol/include/graphene/protocol/authority.hpp b/libraries/protocol/include/graphene/protocol/authority.hpp index ba309ef9e8..b03cca2ac4 100644 --- a/libraries/protocol/include/graphene/protocol/authority.hpp +++ b/libraries/protocol/include/graphene/protocol/authority.hpp @@ -108,7 +108,7 @@ namespace graphene { namespace protocol { (a.address_auths == b.address_auths); } uint32_t num_auths()const { return account_auths.size() + key_auths.size() + address_auths.size(); } - void clear() { account_auths.clear(); key_auths.clear(); } + void clear() { account_auths.clear(); key_auths.clear(); address_auths.clear(); weight_threshold = 0; } static authority null_authority() { diff --git a/libraries/protocol/include/graphene/protocol/custom_authority.hpp b/libraries/protocol/include/graphene/protocol/custom_authority.hpp index 5e2a3b42c6..3ac345f93d 100644 --- a/libraries/protocol/include/graphene/protocol/custom_authority.hpp +++ b/libraries/protocol/include/graphene/protocol/custom_authority.hpp @@ -33,51 +33,70 @@ namespace graphene { namespace protocol { * @brief Create a new custom authority * @ingroup operations */ - struct custom_authority_create_operation : public base_operation - { - struct fee_parameters_type - { - uint64_t basic_fee = GRAPHENE_BLOCKCHAIN_PRECISION; - uint32_t price_per_k_unit = 100; ///< units = valid seconds * items in auth * items in restrictions + struct custom_authority_create_operation : public base_operation { + struct fee_parameters_type { + uint64_t basic_fee = GRAPHENE_BLOCKCHAIN_PRECISION; + uint32_t price_per_byte = GRAPHENE_BLOCKCHAIN_PRECISION / 10; }; - asset fee; // TODO: defer fee to expiration / update / removal ? - account_id_type account; - uint32_t custom_id; - bool enabled; - time_point_sec valid_from; - time_point_sec valid_to; - unsigned_int operation_type; - authority auth; - vector restrictions; + /// Operation fee + asset fee; + /// Account which is setting the custom authority; also pays the fee + account_id_type account; + /// Whether the custom authority is enabled or not + bool enabled; + /// Date when custom authority becomes active + time_point_sec valid_from; + /// Expiration date for custom authority + time_point_sec valid_to; + /// Tag of the operation this custom authority can authorize + unsigned_int operation_type; + /// Authentication requirements for the custom authority + authority auth; + /// Restrictions on operations this custom authority can authenticate + vector restrictions; extensions_type extensions; account_id_type fee_payer()const { return account; } - void validate()const; - share_type calculate_fee(const fee_parameters_type& k)const; + void validate()const; + share_type calculate_fee(const fee_parameters_type& k)const; }; /** * @brief Update a custom authority * @ingroup operations */ - struct custom_authority_update_operation : public base_operation - { - struct fee_parameters_type - { - uint64_t basic_fee = GRAPHENE_BLOCKCHAIN_PRECISION; - uint32_t price_per_k_unit = 100; ///< units = valid seconds * items in auth * items in restrictions + struct custom_authority_update_operation : public base_operation { + struct fee_parameters_type { + uint64_t basic_fee = GRAPHENE_BLOCKCHAIN_PRECISION; + uint32_t price_per_byte = GRAPHENE_BLOCKCHAIN_PRECISION / 10; }; - asset fee; - account_id_type account; - uint64_t delta_units; // to calculate fee, it will be validated in evaluator - // Note: if start was in the past, when updating, used fee should be deducted + /// Operation fee + asset fee; + /// Account which owns the custom authority to update; also pays the fee + account_id_type account; + /// ID of the custom authority to update + custom_authority_id_type authority_to_update; + /// Change to whether the custom authority is enabled or not + optional new_enabled; + /// Change to the custom authority begin date + optional new_valid_from; + /// Change to the custom authority expiration date + optional new_valid_to; + /// Change to the authentication for the custom authority + optional new_auth; + /// Set of indexes of restrictions to remove + flat_set restrictions_to_remove; + /// Vector of new restrictions; will be appended to the old list + vector restrictions_to_add; + + extensions_type extensions; account_id_type fee_payer()const { return account; } - void validate()const; - share_type calculate_fee(const fee_parameters_type& k)const; + void validate()const; + share_type calculate_fee(const fee_parameters_type& k)const; }; @@ -85,35 +104,33 @@ namespace graphene { namespace protocol { * @brief Delete a custom authority * @ingroup operations */ - struct custom_authority_delete_operation : public base_operation - { + struct custom_authority_delete_operation : public base_operation { struct fee_parameters_type { uint64_t fee = GRAPHENE_BLOCKCHAIN_PRECISION; }; - asset fee; - account_id_type account; + /// Operation fee + asset fee; + /// Account which owns the custom authority to update; also pays the fee + account_id_type account; + /// ID of the custom authority to delete + custom_authority_id_type authority_to_delete; + + extensions_type extensions; account_id_type fee_payer()const { return account; } - void validate()const; + void validate()const {} + share_type calculate_fee(const fee_parameters_type& k)const { return k.fee; } }; } } // graphene::protocol -FC_REFLECT( graphene::protocol::custom_authority_create_operation::fee_parameters_type, (basic_fee)(price_per_k_unit) ) -FC_REFLECT( graphene::protocol::custom_authority_update_operation::fee_parameters_type, (basic_fee)(price_per_k_unit) ) -FC_REFLECT( graphene::protocol::custom_authority_delete_operation::fee_parameters_type, (fee) ) - -FC_REFLECT( graphene::protocol::custom_authority_create_operation, - (fee) - (account) - (custom_id) - (enabled) - (valid_from) - (valid_to) - (operation_type) - (auth) - (restrictions) - (extensions) - ) - -FC_REFLECT( graphene::protocol::custom_authority_update_operation, (fee)(account) ) -FC_REFLECT( graphene::protocol::custom_authority_delete_operation, (fee)(account) ) +FC_REFLECT(graphene::protocol::custom_authority_create_operation::fee_parameters_type, (basic_fee)(price_per_byte)) +FC_REFLECT(graphene::protocol::custom_authority_update_operation::fee_parameters_type, (basic_fee)(price_per_byte)) +FC_REFLECT(graphene::protocol::custom_authority_delete_operation::fee_parameters_type, (fee)) + +FC_REFLECT(graphene::protocol::custom_authority_create_operation, + (fee)(account)(enabled)(valid_from)(valid_to)(operation_type)(auth)(restrictions)(extensions)) + +FC_REFLECT(graphene::protocol::custom_authority_update_operation, + (fee)(account)(authority_to_update)(new_enabled)(new_valid_from) + (new_valid_to)(new_auth)(restrictions_to_remove)(restrictions_to_add)(extensions)) +FC_REFLECT(graphene::protocol::custom_authority_delete_operation, (fee)(account)(authority_to_delete)(extensions)) diff --git a/libraries/protocol/include/graphene/protocol/restriction.hpp b/libraries/protocol/include/graphene/protocol/restriction.hpp index 1218322bfc..4f3bc24c21 100644 --- a/libraries/protocol/include/graphene/protocol/restriction.hpp +++ b/libraries/protocol/include/graphene/protocol/restriction.hpp @@ -101,11 +101,6 @@ struct restriction { restriction() = default; restriction(const string& member_name, function_type type, const argument_type& argument) : member_name(member_name), restriction_type(type), argument(argument) {} - - uint64_t get_units()const; - - /// Validates the restriction with given operation type, to be called by an operation validator - void validate( unsigned_int op_type )const; }; } } // graphene::protocol diff --git a/libraries/protocol/include/graphene/protocol/types.hpp b/libraries/protocol/include/graphene/protocol/types.hpp index b861efc78a..2134f3b772 100644 --- a/libraries/protocol/include/graphene/protocol/types.hpp +++ b/libraries/protocol/include/graphene/protocol/types.hpp @@ -239,7 +239,8 @@ GRAPHENE_DEFINE_IDS(protocol, protocol_ids, /*protocol objects are not prefixed* (vesting_balance) (worker) (balance) - (htlc)) + (htlc) + (custom_authority)) FC_REFLECT(graphene::protocol::public_key_type, (key_data)) FC_REFLECT(graphene::protocol::public_key_type::binary_key, (data)(check)) diff --git a/libraries/protocol/restriction.cpp b/libraries/protocol/restriction.cpp deleted file mode 100644 index f7d2f242e5..0000000000 --- a/libraries/protocol/restriction.cpp +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright (c) 2018 Abit More, 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 -#include - -#include - -namespace graphene { namespace protocol { - -template -using bool_const = std::integral_constant; - -struct restriction_validation_visitor; - -struct argument_get_units_visitor -{ - typedef uint64_t result_type; - - template - inline result_type operator()( const T& t ) - { - return 1; - } - - inline result_type operator()( const fc::sha256& t ) - { - return 4; - } - - inline result_type operator()( const public_key_type& t ) - { - return 4; - } - - inline result_type operator()( const string& t ) - { - return ( t.size() + 7 ) / 8; - } - - template - inline result_type operator()( const flat_set& t ) - { - return t.size() * (*this)( *((const T*)nullptr) ); - } - - template - inline result_type operator()( const flat_set& t ) - { - result_type result = 0; - for( const auto& s : t ) - { - result += ( s.size() + 7 ) / 8; - } - return result; - } - - result_type operator()( const vector& t ) - { - result_type result = 0; - for( const auto& r : t ) - { - result += r.argument.visit(*this); - } - return result; - } -}; - -uint64_t restriction::get_units()const -{ - argument_get_units_visitor vtor; - return argument.visit( vtor ); -} - -void restriction::validate( unsigned_int op_type )const -{ -} - -} } // graphene::protocol diff --git a/libraries/protocol/restriction_predicate.cpp b/libraries/protocol/restriction_predicate.cpp index 16e84666b8..e8d635e706 100644 --- a/libraries/protocol/restriction_predicate.cpp +++ b/libraries/protocol/restriction_predicate.cpp @@ -232,10 +232,10 @@ template struct predicate_in> { // Simple inclusion check template>> - auto operator()(const Field& f) const { return a.count(f) != 0; } + bool operator()(const Field& f) const { return a.count(f) != 0; } // Check for optional value template>> - auto operator()(const fc::optional& f) const { + bool operator()(const fc::optional& f) const { FC_ASSERT(f.valid(), "Cannot compute whether null optional is in list"); return (*this)(*f); } @@ -258,14 +258,14 @@ template struct predicate_has_all> { // Field is already flat_set template>> - auto operator()(const flat_set& f) const { + bool operator()(const flat_set& f) const { if (f.size() < a.size()) return false; return std::includes(f.begin(), f.end(), a.begin(), a.end()); } // Field is other container; convert to flat_set template && comparable_types>> - auto operator()(const Field& f) const { + bool operator()(const Field& f) const { if (f.size() < a.size()) return false; flat_set fs(f.begin(), f.end()); return (*this)(fs); @@ -273,7 +273,7 @@ template struct predicate_has_all> { // Field is optional container template && comparable_types>> - auto operator()(const fc::optional& f) const { + bool operator()(const fc::optional& f) const { FC_ASSERT(f.valid(), "Cannot compute whether all elements of null optional container are in other container"); return (*this)(*f); } @@ -291,7 +291,7 @@ template struct predicate_has_none> { // Field is already flat_set template>> - auto operator()(const flat_set& f) const { + bool operator()(const flat_set& f) const { flat_set intersection; std::set_intersection(f.begin(), f.end(), a.begin(), a.end(), std::inserter(intersection, intersection.begin())); @@ -300,14 +300,14 @@ template struct predicate_has_none> { // Field is other container; convert to flat_set template && comparable_types>> - auto operator()(const Field& f) const { + bool operator()(const Field& f) const { flat_set fs(f.begin(), f.end()); return (*this)(fs); } // Field is optional container template && comparable_types>> - auto operator()(const fc::optional& f) const { + bool operator()(const fc::optional& f) const { FC_ASSERT(f.valid(), "Cannot compute whether no elements of null optional container are in other container"); return (*this)(*f); } @@ -337,18 +337,18 @@ struct restriction_argument_visitor { }; // Forward declaration of restrictions_to_predicate, because attribute assertions and logical ORs recurse into it -template object_restriction_predicate restrictions_to_predicate(vector); +template object_restriction_predicate restrictions_to_predicate(vector, bool); template struct attribute_assertion { static object_restriction_predicate create(vector&& rs) { - return restrictions_to_predicate(std::move(rs)); + return restrictions_to_predicate(std::move(rs), false); } }; template struct attribute_assertion> { static object_restriction_predicate> create(vector&& rs) { - return [p=restrictions_to_predicate(std::move(rs))](const fc::optional& f) { + return [p=restrictions_to_predicate(std::move(rs), false)](const fc::optional& f) { FC_ASSERT(f.valid(), "Cannot evaluate attribute assertion on null optional field"); return p(*f); }; @@ -357,7 +357,7 @@ struct attribute_assertion> { template struct attribute_assertion> { static object_restriction_predicate> create(vector&& rs) { - return [p=restrictions_to_predicate(std::move(rs))](const extension& x) { + return [p=restrictions_to_predicate(std::move(rs), false)](const extension& x) { return p(x.value); }; } @@ -458,9 +458,12 @@ object_restriction_predicate create_field_predicate(restriction&& r, lon template object_restriction_predicate create_logical_or_predicate(vector> rs) { + FC_ASSERT(rs.size() > 1, "Logical OR must have at least two branches"); + auto to_predicate = std::bind(restrictions_to_predicate, std::placeholders::_1, false); + vector> predicates; std::transform(std::make_move_iterator(rs.begin()), std::make_move_iterator(rs.end()), - std::back_inserter(predicates), restrictions_to_predicate); + std::back_inserter(predicates), to_predicate); return [predicates=std::move(predicates)](const Object& object) { return std::any_of(predicates.begin(), predicates.end(), [&o=object](const auto& p) { return p(o); }); @@ -468,7 +471,10 @@ object_restriction_predicate create_logical_or_predicate(vector -object_restriction_predicate restrictions_to_predicate(vector rs) { +object_restriction_predicate restrictions_to_predicate(vector rs, bool allow_empty) { + if (!allow_empty) + FC_ASSERT(!rs.empty(), "Empty attribute assertions and logical OR branches are not permitted"); + vector> predicates; std::transform(std::make_move_iterator(rs.begin()), std::make_move_iterator(rs.end()), std::back_inserter(predicates), [](restriction&& r) { @@ -501,7 +507,7 @@ struct operation_type_resolver { template result_type operator()(const Op&) { - auto predicate = restrictions_to_predicate(restrictions); + auto predicate = restrictions_to_predicate(restrictions, true); return [predicate=std::move(predicate)](const operation& op) { FC_ASSERT(op.which() == operation::tag::value, "Supplied operation is incorrect type for restriction predicate"); From 32cdad84d8e93b0b460b0934ee0845d9ab45bbf3 Mon Sep 17 00:00:00 2001 From: Nathan Hourt Date: Fri, 19 Jul 2019 16:17:57 -0500 Subject: [PATCH 04/41] BSIP 40: Add database object --- libraries/chain/db_init.cpp | 4 + libraries/chain/db_notify.cpp | 6 ++ .../chain/custom_authority_object.hpp | 86 +++++++++++++++++++ libraries/chain/small_objects.cpp | 5 ++ .../graphene/protocol/custom_authority.hpp | 2 +- .../include/graphene/protocol/restriction.hpp | 40 ++++----- 6 files changed, 121 insertions(+), 22 deletions(-) create mode 100644 libraries/chain/include/graphene/chain/custom_authority_object.hpp diff --git a/libraries/chain/db_init.cpp b/libraries/chain/db_init.cpp index 124cede1eb..d570ab1a79 100644 --- a/libraries/chain/db_init.cpp +++ b/libraries/chain/db_init.cpp @@ -47,6 +47,7 @@ #include #include #include +#include #include #include @@ -127,6 +128,8 @@ const uint8_t worker_object::type_id; const uint8_t htlc_object::space_id; const uint8_t htlc_object::type_id; +const uint8_t custom_authority_object::space_id; +const uint8_t custom_authority_object::type_id; void database::initialize_evaluators() { @@ -207,6 +210,7 @@ void database::initialize_indexes() add_index< primary_index >(); add_index< primary_index >(); add_index< primary_index< htlc_index> >(); + add_index< primary_index< custom_authority_index> >(); //Implementation object indexes add_index< primary_index >(); diff --git a/libraries/chain/db_notify.cpp b/libraries/chain/db_notify.cpp index c2cb2f1e5a..92e235f244 100644 --- a/libraries/chain/db_notify.cpp +++ b/libraries/chain/db_notify.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include #include @@ -394,6 +395,11 @@ void get_relevant_accounts( const object* obj, flat_set& accoun accounts.insert( htlc_obj->transfer.from ); accounts.insert( htlc_obj->transfer.to ); break; + } case custom_authority_object_type:{ + const auto* cust_auth_obj = dynamic_cast( obj ); + FC_ASSERT( cust_auth_obj != nullptr ); + accounts.insert( cust_auth_obj->account ); + add_authority_accounts( accounts, cust_auth_obj->auth ); } } } diff --git a/libraries/chain/include/graphene/chain/custom_authority_object.hpp b/libraries/chain/include/graphene/chain/custom_authority_object.hpp new file mode 100644 index 0000000000..5dcabba44f --- /dev/null +++ b/libraries/chain/include/graphene/chain/custom_authority_object.hpp @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2019 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 +#include + +namespace graphene { namespace chain { + + /** + * @brief Tracks account custom authorities + * @ingroup object + * + */ + class custom_authority_object : public abstract_object { + public: + static const uint8_t space_id = protocol_ids; + static const uint8_t type_id = custom_authority_object_type; + + account_id_type account; + bool enabled; + time_point_sec valid_from; + time_point_sec valid_to; + unsigned_int operation_type; + authority auth; + vector restrictions; + + /// Unreflected field to store a cache of the predicate function + optional predicate_cache; + }; + + struct by_account_custom; + + /** + * @ingroup object_index + */ + typedef multi_index_container< + custom_authority_object, + indexed_by< + ordered_unique, member>, + ordered_unique, + composite_key< + custom_authority_object, + member, + member + > + > + > + > custom_authority_multi_index_type; + + /** + * @ingroup object_index + */ + using custom_authority_index = generic_index; + +} } // graphene::chain + +MAP_OBJECT_ID_TO_TYPE(graphene::chain::custom_authority_object) + +FC_REFLECT_TYPENAME(graphene::chain::custom_authority_object) + +GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION(graphene::chain::custom_authority_object) diff --git a/libraries/chain/small_objects.cpp b/libraries/chain/small_objects.cpp index ed68a4eaef..7b4e023180 100644 --- a/libraries/chain/small_objects.cpp +++ b/libraries/chain/small_objects.cpp @@ -41,6 +41,7 @@ #include #include #include +#include #include @@ -189,6 +190,9 @@ FC_REFLECT_DERIVED_NO_TYPENAME( graphene::chain::worker_object, (graphene::db::o (url) ) +FC_REFLECT_DERIVED_NO_TYPENAME( graphene::chain::custom_authority_object, (graphene::db::object), + (account)(enabled)(valid_from)(valid_to)(operation_type)(auth)(restrictions) ) + GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::chain::balance_object ) GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::chain::block_summary_object ) GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::chain::budget_record ) @@ -210,3 +214,4 @@ GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::chain::withdraw_permission_ GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::chain::witness_object ) GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::chain::witness_schedule_object ) GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::chain::worker_object ) +GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::chain::custom_authority_object ) diff --git a/libraries/protocol/include/graphene/protocol/custom_authority.hpp b/libraries/protocol/include/graphene/protocol/custom_authority.hpp index 3ac345f93d..59d1b7845e 100644 --- a/libraries/protocol/include/graphene/protocol/custom_authority.hpp +++ b/libraries/protocol/include/graphene/protocol/custom_authority.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018 Abit More, and contributors. + * Copyright (c) 2019 Contributors. * * The MIT License * diff --git a/libraries/protocol/include/graphene/protocol/restriction.hpp b/libraries/protocol/include/graphene/protocol/restriction.hpp index 4f3bc24c21..652c246887 100644 --- a/libraries/protocol/include/graphene/protocol/restriction.hpp +++ b/libraries/protocol/include/graphene/protocol/restriction.hpp @@ -105,27 +105,25 @@ struct restriction { } } // graphene::protocol -FC_REFLECT_ENUM( graphene::protocol::restriction::function_type, - (func_eq) - (func_ne) - (func_lt) - (func_le) - (func_gt) - (func_ge) - (func_in) - (func_not_in) - (func_has_all) - (func_has_none) - (func_attr) - (func_logical_or) - (FUNCTION_TYPE_COUNT) - ) +FC_REFLECT_ENUM(graphene::protocol::restriction::function_type, + (func_eq) + (func_ne) + (func_lt) + (func_le) + (func_gt) + (func_ge) + (func_in) + (func_not_in) + (func_has_all) + (func_has_none) + (func_attr) + (func_logical_or) + (FUNCTION_TYPE_COUNT)) -FC_REFLECT( graphene::protocol::restriction, - (member_name) - (restriction_type) - (argument) - (extensions) - ) +FC_REFLECT(graphene::protocol::restriction, + (member_name) + (restriction_type) + (argument) + (extensions)) From 18394fe3fafa04b594956daa6db3a52e7ded07db Mon Sep 17 00:00:00 2001 From: Nathan Hourt Date: Fri, 19 Jul 2019 23:20:57 -0500 Subject: [PATCH 05/41] BSIP 40: Add evaluators, transaction eval code, testing Implementation passes cursory tests. --- libraries/app/database_api.cpp | 8 +- libraries/chain/CMakeLists.txt | 1 + .../chain/custom_authority_evaluator.cpp | 166 ++++++++++++++++++ libraries/chain/db_block.cpp | 5 +- libraries/chain/db_getter.cpp | 16 ++ libraries/chain/db_init.cpp | 4 + libraries/chain/hardfork.d/BSIP_40.hf | 6 + .../chain/custom_authority_evaluator.hpp | 58 ++++++ .../chain/custom_authority_object.hpp | 13 +- .../chain/include/graphene/chain/database.hpp | 1 + libraries/chain/proposal_object.cpp | 2 + .../include/graphene/protocol/authority.hpp | 1 + .../graphene/protocol/chain_parameters.hpp | 14 ++ .../include/graphene/protocol/config.hpp | 9 + .../include/graphene/protocol/transaction.hpp | 6 + libraries/protocol/restriction_predicate.cpp | 10 +- libraries/protocol/transaction.cpp | 42 +++-- .../performance/market_fee_sharing_tests.cpp | 1 + tests/tests/authority_tests.cpp | 129 ++++++++------ tests/tests/custom_authority_tests.cpp | 104 ++++++++++- 20 files changed, 528 insertions(+), 68 deletions(-) create mode 100644 libraries/chain/custom_authority_evaluator.cpp create mode 100644 libraries/chain/hardfork.d/BSIP_40.hf create mode 100644 libraries/chain/include/graphene/chain/custom_authority_evaluator.hpp diff --git a/libraries/app/database_api.cpp b/libraries/app/database_api.cpp index ce77752697..c9a2fcfd38 100644 --- a/libraries/app/database_api.cpp +++ b/libraries/app/database_api.cpp @@ -510,7 +510,7 @@ std::map database_api_impl::get_full_accounts( const // Add the account's balances const auto& balances = _db.get_index_type< primary_index< account_balance_index > >(). get_secondary_index< balances_by_account_index >().get_account_balances( account->id ); - for( const auto balance : balances ) + for( const auto& balance : balances ) { if(acnt.balances.size() >= api_limit_get_full_accounts_lists) { acnt.more_data_available.balances = true; @@ -738,7 +738,7 @@ vector database_api_impl::get_account_balances( const std::string& accoun const auto& balance_index = _db.get_index_type< primary_index< account_balance_index > >(); const auto& balances = balance_index.get_secondary_index< balances_by_account_index >() .get_account_balances( acnt ); - for( const auto balance : balances ) + for( const auto& balance : balances ) result.push_back( balance.second->get_balance() ); } else @@ -2002,6 +2002,8 @@ bool database_api_impl::verify_authority( const signed_transaction& trx )const trx.verify_authority( _db.get_chain_id(), [this]( account_id_type id ){ return &id(_db).active; }, [this]( account_id_type id ){ return &id(_db).owner; }, + [this]( account_id_type id, const operation& op ) { + return _db.get_viable_custom_authorities(id, op); }, allow_non_immediate_owner, _db.get_global_properties().parameters.max_authority_depth ); return true; @@ -2027,6 +2029,8 @@ bool database_api_impl::verify_account_authority( const string& account_name_or_ graphene::chain::verify_authority(ops, keys, [this]( account_id_type id ){ return &id(_db).active; }, [this]( account_id_type id ){ return &id(_db).owner; }, + // Use a no-op lookup for custom authorities; we don't want it even if one does apply for our dummy op + [](auto, auto) { return vector(); }, true, MUST_IGNORE_CUSTOM_OP_REQD_AUTHS(_db.head_block_time()) ); } catch (fc::exception& ex) diff --git a/libraries/chain/CMakeLists.txt b/libraries/chain/CMakeLists.txt index fbcd6ab866..9e3b8f1be9 100644 --- a/libraries/chain/CMakeLists.txt +++ b/libraries/chain/CMakeLists.txt @@ -55,6 +55,7 @@ add_library( graphene_chain htlc_evaluator.cpp confidential_evaluator.cpp special_authority_evaluation.cpp + custom_authority_evaluator.cpp buyback.cpp account_object.cpp diff --git a/libraries/chain/custom_authority_evaluator.cpp b/libraries/chain/custom_authority_evaluator.cpp new file mode 100644 index 0000000000..375091b7dc --- /dev/null +++ b/libraries/chain/custom_authority_evaluator.cpp @@ -0,0 +1,166 @@ +/* + * Copyright (c) 2019 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 +#include + +namespace graphene { namespace chain { + +void_result custom_authority_create_evaluator::do_evaluate(const custom_authority_create_operation& op) +{ try { + const database& d = db(); + auto now = d.head_block_time(); + FC_ASSERT(HARDFORK_BSIP_40_PASSED(now), "Custom active authorities are not yet enabled"); + + op.account(d); + + const auto& config = global_property_id_type()(d).parameters.extensions.value.custom_authority_options; + FC_ASSERT(config.valid(), "Cannot use custom authorities yet: global configuration not set"); + FC_ASSERT(op.valid_to > now, "Custom authority expiration must be in the future"); + FC_ASSERT((op.valid_to - now).to_seconds() <= config->max_custom_authority_lifetime_seconds, + "Custom authority lifetime exceeds maximum limit"); + + FC_ASSERT(op.operation_type.value <= config->max_operation_tag, + "Cannot create custom authority for operation type which is not yet active"); + + for (const auto& account_weight_pair : op.auth.account_auths) + account_weight_pair.first(d); + + const auto& index = d.get_index_type().indices().get(); + auto range = index.equal_range(op.account); + FC_ASSERT(std::distance(range.first, range.second) < config->max_custom_authorities_per_account, + "Cannot create custom authority for account: account already has maximum number"); + + predicate = get_restriction_predicate(op.restrictions, op.operation_type); + return void_result(); +} FC_CAPTURE_AND_RETHROW((op)) } + +object_id_type custom_authority_create_evaluator::do_apply(const custom_authority_create_operation& op) +{ try { + database& d = db(); + + return d.create([&op, p=std::move(predicate)] (custom_authority_object& obj) mutable { + obj.account = op.account; + obj.enabled = op.enabled; + obj.valid_from = op.valid_from; + obj.valid_to = op.valid_to; + obj.operation_type = op.operation_type; + obj.auth = op.auth; + obj.restrictions = op.restrictions; + + obj.predicate_cache = std::move(p); + }).id; +} FC_CAPTURE_AND_RETHROW((op)) } + +void_result custom_authority_update_evaluator::do_evaluate(const custom_authority_update_operation& op) +{ try { + const database& d = db(); + auto now = d.head_block_time(); + FC_ASSERT(HARDFORK_BSIP_40_PASSED(now), "Custom active authorities are not yet enabled"); + const auto& old_object = op.authority_to_update(d); + + op.account(d); + if (op.new_enabled) + FC_ASSERT(*op.new_enabled != old_object.enabled, + "Custom authority update specifies an enabled flag, but flag is not changed"); + + const auto& config = global_property_id_type()(d).parameters.extensions.value.custom_authority_options; + if (op.new_valid_from) + FC_ASSERT(*op.new_valid_from != old_object.valid_from, + "Custom authority update specifies a new valid from date, but date is not changed"); + if (op.new_valid_to) { + FC_ASSERT(*op.new_valid_to != old_object.valid_to, + "Custom authority update specifies a new valid to date, but date is not changed"); + FC_ASSERT(*op.new_valid_to > now, "Custom authority expiration must be in the future"); + FC_ASSERT((*op.new_valid_to - now).to_seconds() <= config->max_custom_authority_lifetime_seconds, + "Custom authority lifetime exceeds maximum limit"); + } + + if (op.new_auth) { + FC_ASSERT(*op.new_auth != old_object.auth, + "Custom authority update specifies a new authentication authority, but authority is not changed"); + for (const auto& account_weight_pair : op.new_auth->account_auths) + account_weight_pair.first(d); + } + + auto largest_index = *(--op.restrictions_to_remove.end()); + FC_ASSERT(largest_index < old_object.restrictions.size(), + "Index of custom authority restriction to remove is out of bounds"); + + predicate = get_restriction_predicate(op.restrictions_to_add, old_object.operation_type); + return void_result(); +} FC_CAPTURE_AND_RETHROW((op)) } + +void_result custom_authority_update_evaluator::do_apply(const custom_authority_update_operation& op) +{ try { + database& d = db(); + + d.modify(op.authority_to_update(d), [&op, p=std::move(predicate)](custom_authority_object& obj) { + if (op.new_enabled) obj.enabled = *op.new_enabled; + if (op.new_valid_from) obj.valid_from = *op.new_valid_from; + if (op.new_valid_to) obj.valid_to = *op.new_valid_to; + if (op.new_auth) obj.auth = *op.new_auth; + + // Move restrictions at indexes to be removed to the end, then truncate them. + // Note: we could use partition instead of stable_partition, which would be slightly faster, but would also + // reorder the restrictions. I opted to preserve order as a courtesy to the user, who obviously does care about + // what items are at what indexes (removed items are specified by index) + std::stable_partition(obj.restrictions.begin(), obj.restrictions.end(), [&op, index=0](const auto&) mutable { + return op.restrictions_to_remove.count(index++) == 0; + }); + obj.restrictions.resize(obj.restrictions.size() - op.restrictions_to_remove.size()); + + obj.restrictions.insert(obj.restrictions.end(), op.restrictions_to_add.begin(), op.restrictions_to_add.end()); + + obj.predicate_cache = std::move(p); + }); + + return void_result(); +} FC_CAPTURE_AND_RETHROW((op)) } + +void_result custom_authority_delete_evaluator::do_evaluate(const custom_authority_delete_operation& op) +{ try { + const database& d = db(); + FC_ASSERT(HARDFORK_BSIP_40_PASSED(d.head_block_time()), "Custom active authorities are not yet enabled"); + + op.account(d); + op.authority_to_delete(d); + + return void_result(); +} FC_CAPTURE_AND_RETHROW((op)) } + +void_result custom_authority_delete_evaluator::do_apply(const custom_authority_delete_operation& op) +{ try { + database& d = db(); + + d.remove(op.authority_to_delete(d)); + + return void_result(); +} FC_CAPTURE_AND_RETHROW((op)) } + +} } // graphene::chain diff --git a/libraries/chain/db_block.cpp b/libraries/chain/db_block.cpp index ad61974d51..f9fa0cec35 100644 --- a/libraries/chain/db_block.cpp +++ b/libraries/chain/db_block.cpp @@ -651,8 +651,11 @@ processed_transaction database::_apply_transaction(const signed_transaction& trx bool allow_non_immediate_owner = ( head_block_time() >= HARDFORK_CORE_584_TIME ); auto get_active = [this]( account_id_type id ) { return &id(*this).active; }; auto get_owner = [this]( account_id_type id ) { return &id(*this).owner; }; + auto get_custom = [this]( account_id_type id, const operation& op ) { + return get_viable_custom_authorities(id, op); + }; - trx.verify_authority(chain_id, get_active, get_owner, allow_non_immediate_owner, + trx.verify_authority(chain_id, get_active, get_owner, get_custom, allow_non_immediate_owner, MUST_IGNORE_CUSTOM_OP_REQD_AUTHS(head_block_time()), get_global_properties().parameters.max_authority_depth); } diff --git a/libraries/chain/db_getter.cpp b/libraries/chain/db_getter.cpp index 3a4ff98c18..85d30aff36 100644 --- a/libraries/chain/db_getter.cpp +++ b/libraries/chain/db_getter.cpp @@ -27,6 +27,7 @@ #include #include #include +#include namespace graphene { namespace chain { @@ -95,6 +96,21 @@ node_property_object& database::node_properties() return _node_property_object; } +vector database::get_viable_custom_authorities(account_id_type account, const operation &op) const +{ + const auto& index = get_index_type().indices().get(); + auto range = index.equal_range(boost::make_tuple(account, unsigned_int(op.which()), true)); + vector results; + + std::for_each(range.first, range.second, + [&results, &op, now=head_block_time()](const custom_authority_object& cust_auth) { + if (cust_auth.is_valid(now) && cust_auth.get_predicate()(op)) + results.insert(results.end(), cust_auth.auth); + }); + + return results; +} + uint32_t database::last_non_undoable_block_num() const { //see https://github.com/bitshares/bitshares-core/issues/377 diff --git a/libraries/chain/db_init.cpp b/libraries/chain/db_init.cpp index d570ab1a79..ae720226b8 100644 --- a/libraries/chain/db_init.cpp +++ b/libraries/chain/db_init.cpp @@ -64,6 +64,7 @@ #include #include #include +#include #include @@ -181,6 +182,9 @@ void database::initialize_evaluators() register_evaluator(); register_evaluator(); register_evaluator(); + register_evaluator(); + register_evaluator(); + register_evaluator(); } void database::initialize_indexes() diff --git a/libraries/chain/hardfork.d/BSIP_40.hf b/libraries/chain/hardfork.d/BSIP_40.hf new file mode 100644 index 0000000000..db05db0cc7 --- /dev/null +++ b/libraries/chain/hardfork.d/BSIP_40.hf @@ -0,0 +1,6 @@ +// BSIP 40 (Custom Active Authorities) hardfork check +#ifndef HARDFORK_BSIP_40_TIME +// Jan 1 2030, midnight; this is a dummy date until a hardfork date is scheduled +#define HARDFORK_BSIP_40_TIME (fc::time_point_sec( 1893456000 )) +#define HARDFORK_BSIP_40_PASSED(now) (now >= HARDFORK_BSIP_40_TIME) +#endif diff --git a/libraries/chain/include/graphene/chain/custom_authority_evaluator.hpp b/libraries/chain/include/graphene/chain/custom_authority_evaluator.hpp new file mode 100644 index 0000000000..a2e1398d46 --- /dev/null +++ b/libraries/chain/include/graphene/chain/custom_authority_evaluator.hpp @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2019 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 chain { + +class custom_authority_create_evaluator : public evaluator { +public: + using operation_type = custom_authority_create_operation; + restriction_predicate_function predicate; + + void_result do_evaluate(const operation_type& op); + object_id_type do_apply(const operation_type& op); +}; + +class custom_authority_update_evaluator : public evaluator { +public: + using operation_type = custom_authority_update_operation; + restriction_predicate_function predicate; + + void_result do_evaluate(const operation_type& op); + void_result do_apply(const operation_type& op); +}; + +class custom_authority_delete_evaluator : public evaluator { +public: + using operation_type = custom_authority_delete_operation; + + void_result do_evaluate(const operation_type& op); + void_result do_apply(const operation_type& op); +}; + +} } // graphene::chain diff --git a/libraries/chain/include/graphene/chain/custom_authority_object.hpp b/libraries/chain/include/graphene/chain/custom_authority_object.hpp index 5dcabba44f..3a2fa8f619 100644 --- a/libraries/chain/include/graphene/chain/custom_authority_object.hpp +++ b/libraries/chain/include/graphene/chain/custom_authority_object.hpp @@ -49,8 +49,17 @@ namespace graphene { namespace chain { authority auth; vector restrictions; + /// Check whether the custom authority is valid + bool is_valid(time_point_sec now) const { return enabled && now >= valid_from && now < valid_to; } + /// Unreflected field to store a cache of the predicate function - optional predicate_cache; + mutable optional predicate_cache; + + /// Get predicate, from cache if possible, and update cache if not (modifies const object!) + restriction_predicate_function get_predicate() const { + if (!predicate_cache.valid()) predicate_cache = get_restriction_predicate(restrictions, operation_type); + return *predicate_cache; + } }; struct by_account_custom; @@ -66,6 +75,8 @@ namespace graphene { namespace chain { composite_key< custom_authority_object, member, + member, + member, member > > diff --git a/libraries/chain/include/graphene/chain/database.hpp b/libraries/chain/include/graphene/chain/database.hpp index a769d6e950..04882b796b 100644 --- a/libraries/chain/include/graphene/chain/database.hpp +++ b/libraries/chain/include/graphene/chain/database.hpp @@ -279,6 +279,7 @@ namespace graphene { namespace chain { node_property_object& node_properties(); + vector get_viable_custom_authorities( account_id_type account, const operation& op )const; uint32_t last_non_undoable_block_num() const; //////////////////// db_init.cpp //////////////////// diff --git a/libraries/chain/proposal_object.cpp b/libraries/chain/proposal_object.cpp index 1b192d8475..4359826048 100644 --- a/libraries/chain/proposal_object.cpp +++ b/libraries/chain/proposal_object.cpp @@ -39,6 +39,8 @@ bool proposal_object::is_authorized_to_execute( database& db ) const available_key_approvals, [&db]( account_id_type id ){ return &id( db ).active; }, [&db]( account_id_type id ){ return &id( db ).owner; }, + [&]( account_id_type id, const operation& op ){ + return db.get_viable_custom_authorities(id, op); }, allow_non_immediate_owner, MUST_IGNORE_CUSTOM_OP_REQD_AUTHS( db.head_block_time() ), db.get_global_properties().parameters.max_authority_depth, diff --git a/libraries/protocol/include/graphene/protocol/authority.hpp b/libraries/protocol/include/graphene/protocol/authority.hpp index b03cca2ac4..292734a6cb 100644 --- a/libraries/protocol/include/graphene/protocol/authority.hpp +++ b/libraries/protocol/include/graphene/protocol/authority.hpp @@ -107,6 +107,7 @@ namespace graphene { namespace protocol { (a.key_auths == b.key_auths) && (a.address_auths == b.address_auths); } + friend bool operator!= ( const authority& a, const authority& b ) { return !(a==b); } uint32_t num_auths()const { return account_auths.size() + key_auths.size() + address_auths.size(); } void clear() { account_auths.clear(); key_auths.clear(); address_auths.clear(); weight_threshold = 0; } diff --git a/libraries/protocol/include/graphene/protocol/chain_parameters.hpp b/libraries/protocol/include/graphene/protocol/chain_parameters.hpp index 3ccf499b58..937640ddb8 100644 --- a/libraries/protocol/include/graphene/protocol/chain_parameters.hpp +++ b/libraries/protocol/include/graphene/protocol/chain_parameters.hpp @@ -35,6 +35,13 @@ namespace graphene { namespace protocol { uint32_t max_preimage_size; }; + struct custom_authority_options_type + { + uint32_t max_custom_authority_lifetime_seconds = GRAPHENE_DEFAULT_MAX_CUSTOM_AUTHORITY_LIFETIME_SECONDS; + uint32_t max_custom_authorities_per_account = GRAPHENE_DEFAULT_MAX_CUSTOM_AUTHORITIES_PER_ACCOUNT; + int64_t max_operation_tag = GRAPHENE_DEFAULT_MAX_OPERATION_TAG; + }; + struct chain_parameters { /** using a shared_ptr breaks the circular dependency created between operations and the fee schedule */ @@ -74,6 +81,7 @@ namespace graphene { namespace protocol { struct ext { optional< htlc_options > updatable_htlc_options; + optional< custom_authority_options_type > custom_authority_options; }; extension extensions; @@ -98,8 +106,14 @@ FC_REFLECT( graphene::protocol::htlc_options, (max_preimage_size) ) +FC_REFLECT( graphene::protocol::custom_authority_options_type, + (max_custom_authority_lifetime_seconds) + (max_operation_tag) +) + FC_REFLECT( graphene::protocol::chain_parameters::ext, (updatable_htlc_options) + (custom_authority_options) ) FC_REFLECT( graphene::protocol::chain_parameters, diff --git a/libraries/protocol/include/graphene/protocol/config.hpp b/libraries/protocol/include/graphene/protocol/config.hpp index 8ea4bcc56b..a73edbd4a6 100644 --- a/libraries/protocol/include/graphene/protocol/config.hpp +++ b/libraries/protocol/include/graphene/protocol/config.hpp @@ -138,3 +138,12 @@ ///@} #define GRAPHENE_FBA_STEALTH_DESIGNATED_ASSET (asset_id_type(743)) + +/// Maximum duration before a custom authority can expire +#define GRAPHENE_DEFAULT_MAX_CUSTOM_AUTHORITY_LIFETIME_SECONDS (60*60*24*365) +/// Maximum number of custom authorities a particular account can set +#define GRAPHENE_DEFAULT_MAX_CUSTOM_AUTHORITIES_PER_ACCOUNT 10 + +/// Maximum operation tag which has been forked in so far. Defaults to 56 because that's what it was when we first +/// began keeping track +#define GRAPHENE_DEFAULT_MAX_OPERATION_TAG 56 diff --git a/libraries/protocol/include/graphene/protocol/transaction.hpp b/libraries/protocol/include/graphene/protocol/transaction.hpp index 505c3bebef..bc00638175 100644 --- a/libraries/protocol/include/graphene/protocol/transaction.hpp +++ b/libraries/protocol/include/graphene/protocol/transaction.hpp @@ -26,6 +26,8 @@ namespace graphene { namespace protocol { + using custom_authority_lookup = std::function(account_id_type, const operation&)>; + /** * @defgroup transactions Transactions * @@ -161,6 +163,7 @@ namespace graphene { namespace protocol { * @param chain_id the ID of a block chain * @param get_active callback function to retrieve active authorities of a given account * @param get_owner callback function to retrieve owner authorities of a given account + * @param get_custom callback function to retrieve viable custom authorities for a given account and operation * @param allow_non_immediate_owner whether to allow owner authority of non-immediately * required accounts to authorize operations in the transaction * @param ignore_custom_operation_required_auths See issue #210; whether to ignore the @@ -172,6 +175,7 @@ namespace graphene { namespace protocol { const chain_id_type& chain_id, const std::function& get_active, const std::function& get_owner, + custom_authority_lookup get_custom, bool allow_non_immediate_owner, bool ignore_custom_operation_required_auths, uint32_t max_recursion = GRAPHENE_MAX_SIG_CHECK_DEPTH )const; @@ -187,6 +191,7 @@ namespace graphene { namespace protocol { const flat_set& available_keys, const std::function& get_active, const std::function& get_owner, + custom_authority_lookup get_custom, bool allow_non_immediate_owner, bool ignore_custom_operation_required_auths, uint32_t max_recursion = GRAPHENE_MAX_SIG_CHECK_DEPTH) const; @@ -258,6 +263,7 @@ namespace graphene { namespace protocol { void verify_authority( const vector& ops, const flat_set& sigs, const std::function& get_active, const std::function& get_owner, + custom_authority_lookup get_custom, bool allow_non_immediate_owner, bool ignore_custom_operation_required_auths, uint32_t max_recursion = GRAPHENE_MAX_SIG_CHECK_DEPTH, diff --git a/libraries/protocol/restriction_predicate.cpp b/libraries/protocol/restriction_predicate.cpp index e8d635e706..3b9ed4ca74 100644 --- a/libraries/protocol/restriction_predicate.cpp +++ b/libraries/protocol/restriction_predicate.cpp @@ -39,10 +39,15 @@ template using make_void = void; template constexpr static bool is_integral = std::conditional_t::value, std::false_type, std::is_integral>::value; +// Metafunction to check if type is some instantiation of fc::safe +template constexpr static bool is_safe = false; +template constexpr static bool is_safe> = true; + // Metafunction to check if two types are comparable, which means not void_t, and either the same or both integral template constexpr static bool comparable_types = !std::is_same::value && - (std::is_same::value || (is_integral && is_integral)); + (std::is_same::value || ((is_integral || is_safe) && + (is_integral || is_safe))); // Metafunction to check if type is a container template constexpr static bool is_container = false; @@ -233,6 +238,9 @@ template struct predicate_in> { // Simple inclusion check template>> bool operator()(const Field& f) const { return a.count(f) != 0; } + // Check for safe value + template>> + bool operator()(const fc::safe& f) const { return a.count(f.value) != 0; } // Check for optional value template>> bool operator()(const fc::optional& f) const { diff --git a/libraries/protocol/transaction.cpp b/libraries/protocol/transaction.cpp index 153bed7ea5..95874b45de 100644 --- a/libraries/protocol/transaction.cpp +++ b/libraries/protocol/transaction.cpp @@ -268,6 +268,7 @@ struct sign_state void verify_authority( const vector& ops, const flat_set& sigs, const std::function& get_active, const std::function& get_owner, + custom_authority_lookup get_custom, bool allow_non_immediate_owner, bool ignore_custom_operation_required_auths, uint32_t max_recursion_depth, @@ -279,21 +280,40 @@ void verify_authority( const vector& ops, const flat_set required_owner; vector other; + sign_state s( sigs, get_active, get_owner, allow_non_immediate_owner, max_recursion_depth ); + for( auto& id : active_aprovals ) + s.approved_by.insert( id ); + for( auto& id : owner_approvals ) + s.approved_by.insert( id ); + + auto approved_by_custom_authority = [&s, get_custom = std::move(get_custom)]( account_id_type account, + operation op ) mutable { + auto viable_custom_auths = get_custom(account, op); + for (const auto& auth : viable_custom_auths) + if (s.check_authority(&auth)) return true; + return false; + }; + for( const auto& op : ops ) { - operation_get_required_authorities( op, required_active, required_owner, other, + flat_set operation_required_active; + operation_get_required_authorities( op, operation_required_active, required_owner, other, ignore_custom_operation_required_auths ); + + auto itr = operation_required_active.begin(); + while ( itr != operation_required_active.end() ) { + if ( approved_by_custom_authority( *itr, op ) ) + itr = operation_required_active.erase( itr ); + else + ++itr; + } + + required_active.insert( operation_required_active.begin(), operation_required_active.end() ); } if( !allow_committee ) GRAPHENE_ASSERT( required_active.find(GRAPHENE_COMMITTEE_ACCOUNT) == required_active.end(), invalid_committee_approval, "Committee account may only propose transactions" ); - sign_state s( sigs, get_active, get_owner, allow_non_immediate_owner, max_recursion_depth ); - for( auto& id : active_aprovals ) - s.approved_by.insert( id ); - for( auto& id : owner_approvals ) - s.approved_by.insert( id ); - for( const auto& auth : other ) { GRAPHENE_ASSERT( s.check_authority(&auth), tx_missing_other_auth, "Missing Authority", ("auth",auth)("sigs",sigs) ); @@ -379,6 +399,7 @@ set signed_transaction::minimize_required_signatures( const flat_set& available_keys, const std::function& get_active, const std::function& get_owner, + custom_authority_lookup get_custom, bool allow_non_immediate_owner, bool ignore_custom_operation_required_auths, uint32_t max_recursion )const @@ -392,7 +413,7 @@ set signed_transaction::minimize_required_signatures( result.erase( k ); try { - graphene::protocol::verify_authority( operations, result, get_active, get_owner, + graphene::protocol::verify_authority( operations, result, get_active, get_owner, get_custom, allow_non_immediate_owner,ignore_custom_operation_required_auths, max_recursion ); continue; // element stays erased if verify_authority is ok @@ -438,13 +459,14 @@ const flat_set& precomputable_transaction::get_signature_keys( void signed_transaction::verify_authority( const chain_id_type& chain_id, const std::function& get_active, const std::function& get_owner, + custom_authority_lookup get_custom, bool allow_non_immediate_owner, bool ignore_custom_operation_required_auths, uint32_t max_recursion )const { try { graphene::protocol::verify_authority( operations, get_signature_keys( chain_id ), get_active, get_owner, - allow_non_immediate_owner, ignore_custom_operation_required_auths, - max_recursion ); + get_custom, allow_non_immediate_owner, + ignore_custom_operation_required_auths, max_recursion ); } FC_CAPTURE_AND_RETHROW( (*this) ) } } } // graphene::protocol diff --git a/tests/performance/market_fee_sharing_tests.cpp b/tests/performance/market_fee_sharing_tests.cpp index 5c431595ea..0140f837e7 100644 --- a/tests/performance/market_fee_sharing_tests.cpp +++ b/tests/performance/market_fee_sharing_tests.cpp @@ -25,6 +25,7 @@ #include #include +#include #include "../common/database_fixture.hpp" using namespace graphene::chain; diff --git a/tests/tests/authority_tests.cpp b/tests/tests/authority_tests.cpp index c5e4031fe3..c9f578c14b 100644 --- a/tests/tests/authority_tests.cpp +++ b/tests/tests/authority_tests.cpp @@ -43,6 +43,10 @@ using namespace graphene::chain::test; BOOST_FIXTURE_TEST_SUITE( authority_tests, database_fixture ) +auto make_get_custom(const database& db) { + return [&db](account_id_type id, const operation& op) { return db.get_viable_custom_authorities(id, op); }; +} + BOOST_AUTO_TEST_CASE( simple_single_signature ) { try { try { @@ -1324,10 +1328,15 @@ BOOST_FIXTURE_TEST_CASE( nonminimal_sig_test, database_fixture ) ) -> bool { //wdump( (tx)(available_keys) ); + auto get_custom = [&db=db](account_id_type id, const operation& op) { + return db.get_viable_custom_authorities(id, op); + }; set result_set = tx.minimize_required_signatures(db.get_chain_id(), available_keys, - get_active, get_owner, false, false); + get_active, get_owner, get_custom, + false, false); set result_set2 = tx.minimize_required_signatures(db.get_chain_id(), available_keys, - get_active, get_owner, true, false); + get_active, get_owner, get_custom, + true, false); //wdump( (result_set)(result_set2)(ref_set) ); return result_set == ref_set && result_set2 == ref_set; } ; @@ -1346,11 +1355,13 @@ BOOST_FIXTURE_TEST_CASE( nonminimal_sig_test, database_fixture ) BOOST_CHECK( chk( tx, { alice_public_key, bob_public_key }, { alice_public_key, bob_public_key } ) ); BOOST_CHECK( chk_min( tx, { alice_public_key, bob_public_key }, { alice_public_key } ) ); - GRAPHENE_REQUIRE_THROW( tx.verify_authority( db.get_chain_id(), get_active, get_owner, false, false ), fc::exception ); - GRAPHENE_REQUIRE_THROW( tx.verify_authority( db.get_chain_id(), get_active, get_owner, true, false ), fc::exception ); + GRAPHENE_REQUIRE_THROW( tx.verify_authority( db.get_chain_id(), get_active, get_owner, make_get_custom(db), + false, false ), fc::exception ); + GRAPHENE_REQUIRE_THROW( tx.verify_authority( db.get_chain_id(), get_active, get_owner, make_get_custom(db), + true, false ), fc::exception ); sign( tx, alice_private_key ); - tx.verify_authority( db.get_chain_id(), get_active, get_owner, false, false ); - tx.verify_authority( db.get_chain_id(), get_active, get_owner, true, false ); + tx.verify_authority( db.get_chain_id(), get_active, get_owner, make_get_custom(db), false, false ); + tx.verify_authority( db.get_chain_id(), get_active, get_owner, make_get_custom(db), true, false ); } catch(fc::exception& e) { @@ -1549,87 +1560,96 @@ BOOST_FIXTURE_TEST_CASE( parent_owner_test, database_fixture ) BOOST_CHECK( chk( tx, true, { gavin_active_pub, gavin_owner_pub }, { gavin_active_pub } ) ); sign( tx, alice_owner_key ); - GRAPHENE_REQUIRE_THROW( tx.verify_authority( db.get_chain_id(), get_active, get_owner, false, false ), fc::exception ); + GRAPHENE_REQUIRE_THROW( tx.verify_authority( db.get_chain_id(), get_active, get_owner, make_get_custom(db), + false, false ), fc::exception ); GRAPHENE_REQUIRE_THROW( PUSH_TX( db, tx, database::skip_transaction_dupe_check ), fc::exception ); - tx.verify_authority( db.get_chain_id(), get_active, get_owner, true, false ); + tx.verify_authority( db.get_chain_id(), get_active, get_owner, make_get_custom(db), + true, false ); tx.clear_signatures(); sign( tx, alice_active_key ); - tx.verify_authority( db.get_chain_id(), get_active, get_owner, false, false ); + tx.verify_authority( db.get_chain_id(), get_active, get_owner, make_get_custom(db), false, false ); PUSH_TX( db, tx, database::skip_transaction_dupe_check ); - tx.verify_authority( db.get_chain_id(), get_active, get_owner, true, false ); + tx.verify_authority( db.get_chain_id(), get_active, get_owner, make_get_custom(db), true, false ); tx.clear_signatures(); sign( tx, bob_owner_key ); - tx.verify_authority( db.get_chain_id(), get_active, get_owner, false, false ); + tx.verify_authority( db.get_chain_id(), get_active, get_owner, make_get_custom(db), false, false ); PUSH_TX( db, tx, database::skip_transaction_dupe_check ); - tx.verify_authority( db.get_chain_id(), get_active, get_owner, true, false ); + tx.verify_authority( db.get_chain_id(), get_active, get_owner, make_get_custom(db), true, false ); tx.clear_signatures(); sign( tx, bob_active_key ); - tx.verify_authority( db.get_chain_id(), get_active, get_owner, false, false ); + tx.verify_authority( db.get_chain_id(), get_active, get_owner, make_get_custom(db), false, false ); PUSH_TX( db, tx, database::skip_transaction_dupe_check ); - tx.verify_authority( db.get_chain_id(), get_active, get_owner, true, false ); + tx.verify_authority( db.get_chain_id(), get_active, get_owner, make_get_custom(db), true, false ); tx.clear_signatures(); sign( tx, cindy_owner_key ); - GRAPHENE_REQUIRE_THROW( tx.verify_authority( db.get_chain_id(), get_active, get_owner, false, false ), fc::exception ); + GRAPHENE_REQUIRE_THROW( tx.verify_authority( db.get_chain_id(), get_active, get_owner, make_get_custom(db), + false, false ), fc::exception ); GRAPHENE_REQUIRE_THROW( PUSH_TX( db, tx, database::skip_transaction_dupe_check ), fc::exception ); - tx.verify_authority( db.get_chain_id(), get_active, get_owner, true, false ); + tx.verify_authority( db.get_chain_id(), get_active, get_owner, make_get_custom(db), true, false ); tx.clear_signatures(); sign( tx, cindy_active_key ); - GRAPHENE_REQUIRE_THROW( tx.verify_authority( db.get_chain_id(), get_active, get_owner, false, false ), fc::exception ); + GRAPHENE_REQUIRE_THROW( tx.verify_authority( db.get_chain_id(), get_active, get_owner, make_get_custom(db), + false, false ), fc::exception ); GRAPHENE_REQUIRE_THROW( PUSH_TX( db, tx, database::skip_transaction_dupe_check ), fc::exception ); - tx.verify_authority( db.get_chain_id(), get_active, get_owner, true, false ); + tx.verify_authority( db.get_chain_id(), get_active, get_owner, make_get_custom(db), true, false ); tx.clear_signatures(); sign( tx, daisy_owner_key ); - GRAPHENE_REQUIRE_THROW( tx.verify_authority( db.get_chain_id(), get_active, get_owner, false, false ), fc::exception ); + GRAPHENE_REQUIRE_THROW( tx.verify_authority( db.get_chain_id(), get_active, get_owner, make_get_custom(db), + false, false ), fc::exception ); GRAPHENE_REQUIRE_THROW( PUSH_TX( db, tx, database::skip_transaction_dupe_check ), fc::exception ); - tx.verify_authority( db.get_chain_id(), get_active, get_owner, true, false ); + tx.verify_authority( db.get_chain_id(), get_active, get_owner, make_get_custom(db), true, false ); tx.clear_signatures(); sign( tx, daisy_active_key ); - tx.verify_authority( db.get_chain_id(), get_active, get_owner, false, false ); + tx.verify_authority( db.get_chain_id(), get_active, get_owner, make_get_custom(db), false, false ); PUSH_TX( db, tx, database::skip_transaction_dupe_check ); - tx.verify_authority( db.get_chain_id(), get_active, get_owner, true, false ); + tx.verify_authority( db.get_chain_id(), get_active, get_owner, make_get_custom(db), true, false ); tx.clear_signatures(); sign( tx, edwin_owner_key ); - GRAPHENE_REQUIRE_THROW( tx.verify_authority( db.get_chain_id(), get_active, get_owner, false, false ), fc::exception ); + GRAPHENE_REQUIRE_THROW( tx.verify_authority( db.get_chain_id(), get_active, get_owner, make_get_custom(db), + false, false ), fc::exception ); GRAPHENE_REQUIRE_THROW( PUSH_TX( db, tx, database::skip_transaction_dupe_check ), fc::exception ); - tx.verify_authority( db.get_chain_id(), get_active, get_owner, true, false ); + tx.verify_authority( db.get_chain_id(), get_active, get_owner, make_get_custom(db), true, false ); tx.clear_signatures(); sign( tx, edwin_active_key ); - tx.verify_authority( db.get_chain_id(), get_active, get_owner, false, false ); + tx.verify_authority( db.get_chain_id(), get_active, get_owner, make_get_custom(db), false, false ); PUSH_TX( db, tx, database::skip_transaction_dupe_check ); - tx.verify_authority( db.get_chain_id(), get_active, get_owner, true, false ); + tx.verify_authority( db.get_chain_id(), get_active, get_owner, make_get_custom(db), true, false ); tx.clear_signatures(); sign( tx, frank_owner_key ); - GRAPHENE_REQUIRE_THROW( tx.verify_authority( db.get_chain_id(), get_active, get_owner, false, false ), fc::exception ); + GRAPHENE_REQUIRE_THROW( tx.verify_authority( db.get_chain_id(), get_active, get_owner, make_get_custom(db), + false, false ), fc::exception ); GRAPHENE_REQUIRE_THROW( PUSH_TX( db, tx, database::skip_transaction_dupe_check ), fc::exception ); - tx.verify_authority( db.get_chain_id(), get_active, get_owner, true, false ); + tx.verify_authority( db.get_chain_id(), get_active, get_owner, make_get_custom(db), true, false ); tx.clear_signatures(); sign( tx, frank_active_key ); - GRAPHENE_REQUIRE_THROW( tx.verify_authority( db.get_chain_id(), get_active, get_owner, false, false ), fc::exception ); + GRAPHENE_REQUIRE_THROW( tx.verify_authority( db.get_chain_id(), get_active, get_owner, make_get_custom(db), + false, false ), fc::exception ); GRAPHENE_REQUIRE_THROW( PUSH_TX( db, tx, database::skip_transaction_dupe_check ), fc::exception ); - tx.verify_authority( db.get_chain_id(), get_active, get_owner, true, false ); + tx.verify_authority( db.get_chain_id(), get_active, get_owner, make_get_custom(db), true, false ); tx.clear_signatures(); sign( tx, gavin_owner_key ); - GRAPHENE_REQUIRE_THROW( tx.verify_authority( db.get_chain_id(), get_active, get_owner, false, false ), fc::exception ); + GRAPHENE_REQUIRE_THROW( tx.verify_authority( db.get_chain_id(), get_active, get_owner, make_get_custom(db), + false, false ), fc::exception ); GRAPHENE_REQUIRE_THROW( PUSH_TX( db, tx, database::skip_transaction_dupe_check ), fc::exception ); - tx.verify_authority( db.get_chain_id(), get_active, get_owner, true, false ); + tx.verify_authority( db.get_chain_id(), get_active, get_owner, make_get_custom(db), true, false ); tx.clear_signatures(); sign( tx, gavin_active_key ); - tx.verify_authority( db.get_chain_id(), get_active, get_owner, false, false ); + tx.verify_authority( db.get_chain_id(), get_active, get_owner, make_get_custom(db), false, false ); PUSH_TX( db, tx, database::skip_transaction_dupe_check ); - tx.verify_authority( db.get_chain_id(), get_active, get_owner, true, false ); + tx.verify_authority( db.get_chain_id(), get_active, get_owner, make_get_custom(db), true, false ); tx.clear_signatures(); // proposal tests @@ -2036,29 +2056,33 @@ BOOST_FIXTURE_TEST_CASE( missing_owner_auth_test, database_fixture ) tx.operations.push_back( op ); // not signed, should throw tx_missing_owner_auth - GRAPHENE_REQUIRE_THROW( tx.verify_authority( db.get_chain_id(), get_active, get_owner, false, false ), + GRAPHENE_REQUIRE_THROW( tx.verify_authority( db.get_chain_id(), get_active, get_owner, make_get_custom(db), + false, false ), graphene::chain::tx_missing_owner_auth ); - GRAPHENE_REQUIRE_THROW( tx.verify_authority( db.get_chain_id(), get_active, get_owner, true, false ), + GRAPHENE_REQUIRE_THROW( tx.verify_authority( db.get_chain_id(), get_active, get_owner, make_get_custom(db), + true, false ), graphene::chain::tx_missing_owner_auth ); // signed with alice's active key, should throw tx_missing_owner_auth sign( tx, alice_active_key ); - GRAPHENE_REQUIRE_THROW( tx.verify_authority( db.get_chain_id(), get_active, get_owner, false, false ), + GRAPHENE_REQUIRE_THROW( tx.verify_authority( db.get_chain_id(), get_active, get_owner, make_get_custom(db), + false, false ), graphene::chain::tx_missing_owner_auth ); - GRAPHENE_REQUIRE_THROW( tx.verify_authority( db.get_chain_id(), get_active, get_owner, true, false ), + GRAPHENE_REQUIRE_THROW( tx.verify_authority( db.get_chain_id(), get_active, get_owner, make_get_custom(db), + true, false ), graphene::chain::tx_missing_owner_auth ); // signed with alice's owner key, should not throw tx.clear_signatures(); sign( tx, alice_owner_key ); - tx.verify_authority( db.get_chain_id(), get_active, get_owner, false, false ); - tx.verify_authority( db.get_chain_id(), get_active, get_owner, true, false ); + tx.verify_authority( db.get_chain_id(), get_active, get_owner, make_get_custom(db), false, false ); + tx.verify_authority( db.get_chain_id(), get_active, get_owner, make_get_custom(db), true, false ); // signed with both alice's owner key and active key, // it does not throw due to https://github.com/bitshares/bitshares-core/issues/580 sign( tx, alice_active_key ); - tx.verify_authority( db.get_chain_id(), get_active, get_owner, false, false ); - tx.verify_authority( db.get_chain_id(), get_active, get_owner, true, false ); + tx.verify_authority( db.get_chain_id(), get_active, get_owner, make_get_custom(db), false, false ); + tx.verify_authority( db.get_chain_id(), get_active, get_owner, make_get_custom(db), true, false ); // creating a transaction that needs active permission tx.clear(); @@ -2067,29 +2091,32 @@ BOOST_FIXTURE_TEST_CASE( missing_owner_auth_test, database_fixture ) tx.operations.push_back( op ); // not signed, should throw tx_missing_active_auth - GRAPHENE_REQUIRE_THROW( tx.verify_authority( db.get_chain_id(), get_active, get_owner, false, false ), + GRAPHENE_REQUIRE_THROW( tx.verify_authority( db.get_chain_id(), get_active, get_owner, make_get_custom(db), + false, false ), graphene::chain::tx_missing_active_auth ); - GRAPHENE_REQUIRE_THROW( tx.verify_authority( db.get_chain_id(), get_active, get_owner, true, false ), + GRAPHENE_REQUIRE_THROW( tx.verify_authority( db.get_chain_id(), get_active, get_owner, make_get_custom(db), + true, false ), graphene::chain::tx_missing_active_auth ); // signed with alice's active key, should not throw sign( tx, alice_active_key ); - tx.verify_authority( db.get_chain_id(), get_active, get_owner, false, false ); - tx.verify_authority( db.get_chain_id(), get_active, get_owner, true, false ); + tx.verify_authority( db.get_chain_id(), get_active, get_owner, make_get_custom(db), false, false ); + tx.verify_authority( db.get_chain_id(), get_active, get_owner, make_get_custom(db), true, false ); // signed with alice's owner key, should not throw tx.clear_signatures(); sign( tx, alice_owner_key ); - tx.verify_authority( db.get_chain_id(), get_active, get_owner, false, false ); - tx.verify_authority( db.get_chain_id(), get_active, get_owner, true, false ); + tx.verify_authority( db.get_chain_id(), get_active, get_owner, make_get_custom(db), false, false ); + tx.verify_authority( db.get_chain_id(), get_active, get_owner, make_get_custom(db), true, false ); // signed with both alice's owner key and active key, should throw tx_irrelevant_sig sign( tx, alice_active_key ); - GRAPHENE_REQUIRE_THROW( tx.verify_authority( db.get_chain_id(), get_active, get_owner, false, false ), + GRAPHENE_REQUIRE_THROW( tx.verify_authority( db.get_chain_id(), get_active, get_owner, make_get_custom(db), + false, false ), graphene::chain::tx_irrelevant_sig ); - GRAPHENE_REQUIRE_THROW( tx.verify_authority( db.get_chain_id(), get_active, get_owner, true, false ), + GRAPHENE_REQUIRE_THROW( tx.verify_authority( db.get_chain_id(), get_active, get_owner, make_get_custom(db), + true, false ), graphene::chain::tx_irrelevant_sig ); - } catch(fc::exception& e) { diff --git a/tests/tests/custom_authority_tests.cpp b/tests/tests/custom_authority_tests.cpp index 69cb917de3..a76f4967df 100644 --- a/tests/tests/custom_authority_tests.cpp +++ b/tests/tests/custom_authority_tests.cpp @@ -22,12 +22,19 @@ * THE SOFTWARE. */ -#include #include +#include #include #include +#include +#include + +#include "../common/database_fixture.hpp" -BOOST_AUTO_TEST_SUITE(custom_authority_tests) +using namespace graphene::chain; +using namespace graphene::chain::test; + +BOOST_FIXTURE_TEST_SUITE(custom_authority_tests, database_fixture) #define FUNC(TYPE) BOOST_PP_CAT(restriction::func_, TYPE) @@ -74,4 +81,97 @@ BOOST_AUTO_TEST_CASE(restriction_predicate_tests) { try { BOOST_CHECK(predicate(update) == true); } FC_LOG_AND_RETHROW() } +BOOST_AUTO_TEST_CASE(custom_auths) { try { + generate_blocks(HARDFORK_BSIP_40_TIME); + generate_blocks(5); + db.modify(global_property_id_type()(db), [](global_property_object& gpo) { + gpo.parameters.extensions.value.custom_authority_options = custom_authority_options_type(); + }); + set_expiration(db, trx); + ACTORS((alice)(bob)) + fund(alice, asset(1000*GRAPHENE_BLOCKCHAIN_PRECISION)); + fund(bob, asset(1000*GRAPHENE_BLOCKCHAIN_PRECISION)); + + + custom_authority_create_operation op; + op.account = alice.get_id(); + op.auth.add_authority(bob.get_id(), 1); + op.auth.weight_threshold = 1; + op.enabled = true; + op.valid_to = db.head_block_time() + 1000; + op.operation_type = operation::tag::value; + op.restrictions = {restriction("amount", restriction::func_attr, vector{ + restriction("amount", restriction::func_lt, int64_t(100*GRAPHENE_BLOCKCHAIN_PRECISION)), + restriction("asset_id", restriction::func_eq, asset_id_type(0))})}; + + transfer_operation top; + top.to = bob.get_id(); + top.from = alice.get_id(); + top.amount.amount = 99 * GRAPHENE_BLOCKCHAIN_PRECISION; + trx.operations = {top}; + sign(trx, bob_private_key); + BOOST_CHECK_THROW(PUSH_TX(db, trx), tx_missing_active_auth); + + trx.clear(); + trx.operations = {op}; + sign(trx, alice_private_key); + PUSH_TX(db, trx); + + const auto& auth = *db.get_index_type().indices().get().find(alice_id); + + trx.clear(); + trx.operations = {top}; + sign(trx, bob_private_key); + PUSH_TX(db, trx); + + trx.operations.front().get().amount.amount = 100*GRAPHENE_BLOCKCHAIN_PRECISION; + trx.clear_signatures(); + sign(trx, bob_private_key); + BOOST_CHECK_THROW(PUSH_TX(db, trx), tx_missing_active_auth); + + op.restrictions.front().argument.get>().front().restriction_type = restriction::func_eq; + custom_authority_update_operation uop; + uop.account = alice.get_id(); + uop.authority_to_update = auth.id; + uop.restrictions_to_remove = {0}; + uop.restrictions_to_add = {op.restrictions.front()}; + trx.clear(); + trx.operations = {uop}; + sign(trx, alice_private_key); + PUSH_TX(db, trx); + + trx.clear(); + trx.operations = {top}; + trx.expiration += 5; + sign(trx, bob_private_key); + BOOST_CHECK_THROW(PUSH_TX(db, trx), tx_missing_active_auth); + + trx.operations.front().get().amount.amount = 100*GRAPHENE_BLOCKCHAIN_PRECISION; + trx.clear_signatures(); + sign(trx, bob_private_key); + PUSH_TX(db, trx); + auto transfer = trx; + + generate_block(); + + trx.expiration += 5; + trx.clear_signatures(); + sign(trx, bob_private_key); + PUSH_TX(db, trx); + + custom_authority_delete_operation dop; + auto id = db.get_index_type().indices().get().find(alice_id)->id; + dop.account = alice.get_id(); + dop.authority_to_delete = id; + trx.clear(); + trx.operations = {dop}; + sign(trx, alice_private_key); + PUSH_TX(db, trx); + + transfer.expiration += 10; + transfer.clear_signatures(); + sign(transfer, bob_private_key); + BOOST_CHECK_THROW(PUSH_TX(db, transfer), tx_missing_active_auth); +} FC_LOG_AND_RETHROW() } + BOOST_AUTO_TEST_SUITE_END() From 1c9d74e43e99eb243d3de445fc99ce32a3a544ac Mon Sep 17 00:00:00 2001 From: Nathan Hourt Date: Sat, 20 Jul 2019 16:12:26 -0500 Subject: [PATCH 06/41] BSIP 40: Fixes from code review --- .../chain/committee_member_evaluator.cpp | 19 +++++++-- .../chain/custom_authority_evaluator.cpp | 40 ++++++++++++------- libraries/chain/db_notify.cpp | 6 ++- .../chain/custom_authority_evaluator.hpp | 4 +- .../chain/custom_authority_object.hpp | 40 ++++++++++--------- libraries/chain/proposal_evaluator.cpp | 22 +++++++++- libraries/protocol/custom_authority.cpp | 2 +- .../graphene/protocol/chain_parameters.hpp | 1 + .../include/graphene/protocol/restriction.hpp | 5 +++ tests/tests/custom_authority_tests.cpp | 21 +++++++--- 10 files changed, 113 insertions(+), 47 deletions(-) diff --git a/libraries/chain/committee_member_evaluator.cpp b/libraries/chain/committee_member_evaluator.cpp index 90620fc010..94054edb49 100644 --- a/libraries/chain/committee_member_evaluator.cpp +++ b/libraries/chain/committee_member_evaluator.cpp @@ -72,17 +72,30 @@ void_result committee_member_update_evaluator::do_apply( const committee_member_ return void_result(); } FC_CAPTURE_AND_RETHROW( (op) ) } -void_result committee_member_update_global_parameters_evaluator::do_evaluate(const committee_member_update_global_parameters_operation& o) +void_result committee_member_update_global_parameters_evaluator::do_evaluate( + const committee_member_update_global_parameters_operation& o) { try { FC_ASSERT(trx_state->_is_proposed_trx); - FC_ASSERT( db().head_block_time() > HARDFORK_CORE_1468_TIME || !o.new_parameters.extensions.value.updatable_htlc_options.valid(), + auto now = db().head_block_time(); + FC_ASSERT( now > HARDFORK_CORE_1468_TIME || !o.new_parameters.extensions.value.updatable_htlc_options.valid(), "Unable to set HTLC parameters until hardfork." ); + if (!HARDFORK_BSIP_40_PASSED( now )) { + FC_ASSERT( !o.new_parameters.extensions.value.custom_authority_options.valid(), + "Unable to set Custom Authority Options until hardfork BSIP 40." ); + FC_ASSERT( !o.new_parameters.current_fees->exists(), + "Unable to set Custom Authority operation fees until hardfork BSIP 40." ); + FC_ASSERT( !o.new_parameters.current_fees->exists(), + "Unable to set Custom Authority operation fees until hardfork BSIP 40." ); + FC_ASSERT( !o.new_parameters.current_fees->exists(), + "Unable to set Custom Authority operation fees until hardfork BSIP 40." ); + } return void_result(); } FC_CAPTURE_AND_RETHROW( (o) ) } -void_result committee_member_update_global_parameters_evaluator::do_apply(const committee_member_update_global_parameters_operation& o) +void_result committee_member_update_global_parameters_evaluator::do_apply( + const committee_member_update_global_parameters_operation& o) { try { db().modify(db().get_global_properties(), [&o](global_property_object& p) { p.pending_parameters = o.new_parameters; diff --git a/libraries/chain/custom_authority_evaluator.cpp b/libraries/chain/custom_authority_evaluator.cpp index 375091b7dc..9901d7f688 100644 --- a/libraries/chain/custom_authority_evaluator.cpp +++ b/libraries/chain/custom_authority_evaluator.cpp @@ -39,7 +39,7 @@ void_result custom_authority_create_evaluator::do_evaluate(const custom_authorit op.account(d); - const auto& config = global_property_id_type()(d).parameters.extensions.value.custom_authority_options; + const auto& config = d.get_global_properties().parameters.extensions.value.custom_authority_options; FC_ASSERT(config.valid(), "Cannot use custom authorities yet: global configuration not set"); FC_ASSERT(op.valid_to > now, "Custom authority expiration must be in the future"); FC_ASSERT((op.valid_to - now).to_seconds() <= config->max_custom_authority_lifetime_seconds, @@ -73,7 +73,8 @@ object_id_type custom_authority_create_evaluator::do_apply(const custom_authorit obj.auth = op.auth; obj.restrictions = op.restrictions; - obj.predicate_cache = std::move(p); + // Update the predicate cache + obj.update_predicate_cache(); }).id; } FC_CAPTURE_AND_RETHROW((op)) } @@ -82,37 +83,44 @@ void_result custom_authority_update_evaluator::do_evaluate(const custom_authorit const database& d = db(); auto now = d.head_block_time(); FC_ASSERT(HARDFORK_BSIP_40_PASSED(now), "Custom active authorities are not yet enabled"); - const auto& old_object = op.authority_to_update(d); + old_object = &op.authority_to_update(d); + FC_ASSERT(old_object->account == op.account, "Cannot update a different account's custom authority"); op.account(d); if (op.new_enabled) - FC_ASSERT(*op.new_enabled != old_object.enabled, + FC_ASSERT(*op.new_enabled != old_object->enabled, "Custom authority update specifies an enabled flag, but flag is not changed"); - const auto& config = global_property_id_type()(d).parameters.extensions.value.custom_authority_options; - if (op.new_valid_from) - FC_ASSERT(*op.new_valid_from != old_object.valid_from, + const auto& config = d.get_global_properties().parameters.extensions.value.custom_authority_options; + auto valid_from = old_object->valid_from; + auto valid_to = old_object->valid_to; + if (op.new_valid_from) { + FC_ASSERT(*op.new_valid_from != old_object->valid_from, "Custom authority update specifies a new valid from date, but date is not changed"); + valid_from = *op.new_valid_from; + } if (op.new_valid_to) { - FC_ASSERT(*op.new_valid_to != old_object.valid_to, + FC_ASSERT(*op.new_valid_to != old_object->valid_to, "Custom authority update specifies a new valid to date, but date is not changed"); FC_ASSERT(*op.new_valid_to > now, "Custom authority expiration must be in the future"); FC_ASSERT((*op.new_valid_to - now).to_seconds() <= config->max_custom_authority_lifetime_seconds, "Custom authority lifetime exceeds maximum limit"); + valid_to = *op.new_valid_to; } + FC_ASSERT(valid_from < valid_to, "Custom authority validity begin date must be before expiration date"); if (op.new_auth) { - FC_ASSERT(*op.new_auth != old_object.auth, + FC_ASSERT(*op.new_auth != old_object->auth, "Custom authority update specifies a new authentication authority, but authority is not changed"); for (const auto& account_weight_pair : op.new_auth->account_auths) account_weight_pair.first(d); } auto largest_index = *(--op.restrictions_to_remove.end()); - FC_ASSERT(largest_index < old_object.restrictions.size(), + FC_ASSERT(largest_index < old_object->restrictions.size(), "Index of custom authority restriction to remove is out of bounds"); - predicate = get_restriction_predicate(op.restrictions_to_add, old_object.operation_type); + get_restriction_predicate(op.restrictions_to_add, old_object->operation_type); return void_result(); } FC_CAPTURE_AND_RETHROW((op)) } @@ -120,7 +128,7 @@ void_result custom_authority_update_evaluator::do_apply(const custom_authority_u { try { database& d = db(); - d.modify(op.authority_to_update(d), [&op, p=std::move(predicate)](custom_authority_object& obj) { + d.modify(*old_object, [&op](custom_authority_object& obj) { if (op.new_enabled) obj.enabled = *op.new_enabled; if (op.new_valid_from) obj.valid_from = *op.new_valid_from; if (op.new_valid_to) obj.valid_to = *op.new_valid_to; @@ -137,7 +145,8 @@ void_result custom_authority_update_evaluator::do_apply(const custom_authority_u obj.restrictions.insert(obj.restrictions.end(), op.restrictions_to_add.begin(), op.restrictions_to_add.end()); - obj.predicate_cache = std::move(p); + // Update the predicate cache + obj.update_predicate_cache(); }); return void_result(); @@ -149,7 +158,8 @@ void_result custom_authority_delete_evaluator::do_evaluate(const custom_authorit FC_ASSERT(HARDFORK_BSIP_40_PASSED(d.head_block_time()), "Custom active authorities are not yet enabled"); op.account(d); - op.authority_to_delete(d); + old_object = &op.authority_to_delete(d); + FC_ASSERT(old_object->account == op.account, "Cannot delete a different account's custom authority"); return void_result(); } FC_CAPTURE_AND_RETHROW((op)) } @@ -158,7 +168,7 @@ void_result custom_authority_delete_evaluator::do_apply(const custom_authority_d { try { database& d = db(); - d.remove(op.authority_to_delete(d)); + d.remove(*old_object); return void_result(); } FC_CAPTURE_AND_RETHROW((op)) } diff --git a/libraries/chain/db_notify.cpp b/libraries/chain/db_notify.cpp index 92e235f244..83bd84577e 100644 --- a/libraries/chain/db_notify.cpp +++ b/libraries/chain/db_notify.cpp @@ -298,6 +298,8 @@ struct get_impacted_account_visitor void operator()( const custom_authority_update_operation& op ) { _impacted.insert( op.fee_payer() ); // account + if ( op.new_auth ) + add_authority_accounts(_impacted, *op.new_auth); } void operator()( const custom_authority_delete_operation& op ) { @@ -378,7 +380,7 @@ void get_relevant_accounts( const object* obj, flat_set& accoun break; } case vesting_balance_object_type:{ const auto& aobj = dynamic_cast(obj); - FC_ASSERT(aobj != nullptr ); + FC_ASSERT( aobj != nullptr ); accounts.insert( aobj->owner ); break; } case worker_object_type:{ @@ -462,7 +464,7 @@ void get_relevant_accounts( const object* obj, flat_set& accoun const auto& aobj = dynamic_cast(obj); FC_ASSERT( aobj != nullptr ); accounts.insert( aobj->bidder ); - break; + break; } } } diff --git a/libraries/chain/include/graphene/chain/custom_authority_evaluator.hpp b/libraries/chain/include/graphene/chain/custom_authority_evaluator.hpp index a2e1398d46..4eebf70a59 100644 --- a/libraries/chain/include/graphene/chain/custom_authority_evaluator.hpp +++ b/libraries/chain/include/graphene/chain/custom_authority_evaluator.hpp @@ -28,6 +28,7 @@ #include namespace graphene { namespace chain { +class custom_authority_object; class custom_authority_create_evaluator : public evaluator { public: @@ -41,7 +42,7 @@ class custom_authority_create_evaluator : public evaluator { public: using operation_type = custom_authority_update_operation; - restriction_predicate_function predicate; + const custom_authority_object* old_object = nullptr; void_result do_evaluate(const operation_type& op); void_result do_apply(const operation_type& op); @@ -50,6 +51,7 @@ class custom_authority_update_evaluator : public evaluator { public: using operation_type = custom_authority_delete_operation; + const custom_authority_object* old_object = nullptr; void_result do_evaluate(const operation_type& op); void_result do_apply(const operation_type& op); diff --git a/libraries/chain/include/graphene/chain/custom_authority_object.hpp b/libraries/chain/include/graphene/chain/custom_authority_object.hpp index 3a2fa8f619..b3f731a2e2 100644 --- a/libraries/chain/include/graphene/chain/custom_authority_object.hpp +++ b/libraries/chain/include/graphene/chain/custom_authority_object.hpp @@ -37,29 +37,31 @@ namespace graphene { namespace chain { * */ class custom_authority_object : public abstract_object { - public: - static const uint8_t space_id = protocol_ids; - static const uint8_t type_id = custom_authority_object_type; + /// Unreflected field to store a cache of the predicate function + mutable optional predicate_cache; - account_id_type account; - bool enabled; - time_point_sec valid_from; - time_point_sec valid_to; - unsigned_int operation_type; - authority auth; - vector restrictions; + public: + static const uint8_t space_id = protocol_ids; + static const uint8_t type_id = custom_authority_object_type; - /// Check whether the custom authority is valid - bool is_valid(time_point_sec now) const { return enabled && now >= valid_from && now < valid_to; } + account_id_type account; + bool enabled; + time_point_sec valid_from; + time_point_sec valid_to; + unsigned_int operation_type; + authority auth; + vector restrictions; - /// Unreflected field to store a cache of the predicate function - mutable optional predicate_cache; + /// Check whether the custom authority is valid + bool is_valid(time_point_sec now) const { return enabled && now >= valid_from && now < valid_to; } - /// Get predicate, from cache if possible, and update cache if not (modifies const object!) - restriction_predicate_function get_predicate() const { - if (!predicate_cache.valid()) predicate_cache = get_restriction_predicate(restrictions, operation_type); - return *predicate_cache; - } + /// Get predicate, from cache if possible, and update cache if not (modifies const object!) + restriction_predicate_function get_predicate() const { + if (!predicate_cache.valid()) predicate_cache = get_restriction_predicate(restrictions, operation_type); + return *predicate_cache; + } + /// Regenerate predicate function and update predicate cache + void update_predicate_cache() { predicate_cache = get_restriction_predicate(restrictions, operation_type); } }; struct by_account_custom; diff --git a/libraries/chain/proposal_evaluator.cpp b/libraries/chain/proposal_evaluator.cpp index 39f838183a..f783aed96e 100644 --- a/libraries/chain/proposal_evaluator.cpp +++ b/libraries/chain/proposal_evaluator.cpp @@ -95,11 +95,22 @@ struct proposal_operation_hardfork_visitor } void operator()(const graphene::chain::committee_member_update_global_parameters_operation &op) const { if (block_time < HARDFORK_CORE_1468_TIME) { - FC_ASSERT(!op.new_parameters.extensions.value.updatable_htlc_options.valid(), "Unable to set HTLC options before hardfork 1468"); + FC_ASSERT(!op.new_parameters.extensions.value.updatable_htlc_options.valid(), + "Unable to set HTLC options before hardfork 1468"); FC_ASSERT(!op.new_parameters.current_fees->exists()); FC_ASSERT(!op.new_parameters.current_fees->exists()); FC_ASSERT(!op.new_parameters.current_fees->exists()); } + if (!HARDFORK_BSIP_40_PASSED(block_time)) { + FC_ASSERT(!op.new_parameters.extensions.value.custom_authority_options.valid(), + "Unable to set Custom Authority Options before hardfork BSIP 40"); + FC_ASSERT(!op.new_parameters.current_fees->exists(), + "Unable to define fees for custom authority operations prior to hardfork BSIP 40"); + FC_ASSERT(!op.new_parameters.current_fees->exists(), + "Unable to define fees for custom authority operations prior to hardfork BSIP 40"); + FC_ASSERT(!op.new_parameters.current_fees->exists(), + "Unable to define fees for custom authority operations prior to hardfork BSIP 40"); + } } void operator()(const graphene::chain::htlc_create_operation &op) const { FC_ASSERT( block_time >= HARDFORK_CORE_1468_TIME, "Not allowed until hardfork 1468" ); @@ -110,6 +121,15 @@ struct proposal_operation_hardfork_visitor void operator()(const graphene::chain::htlc_extend_operation &op) const { FC_ASSERT( block_time >= HARDFORK_CORE_1468_TIME, "Not allowed until hardfork 1468" ); } + void operator()(const graphene::chain::custom_authority_create_operation&) const { + FC_ASSERT( HARDFORK_BSIP_40_PASSED(block_time), "Not allowed until hardfork BSIP 40" ); + } + void operator()(const graphene::chain::custom_authority_update_operation&) const { + FC_ASSERT( HARDFORK_BSIP_40_PASSED(block_time), "Not allowed until hardfork BSIP 40" ); + } + void operator()(const graphene::chain::custom_authority_delete_operation&) const { + FC_ASSERT( HARDFORK_BSIP_40_PASSED(block_time), "Not allowed until hardfork BSIP 40" ); + } // loop and self visit in proposals void operator()(const graphene::chain::proposal_create_operation &v) const { bool already_contains_proposal_update = false; diff --git a/libraries/protocol/custom_authority.cpp b/libraries/protocol/custom_authority.cpp index 2229052217..b91233ec64 100644 --- a/libraries/protocol/custom_authority.cpp +++ b/libraries/protocol/custom_authority.cpp @@ -71,7 +71,7 @@ void custom_authority_update_operation::validate()const { && account != GRAPHENE_RELAXED_COMMITTEE_ACCOUNT, "Can not create custom authority for special accounts"); if (new_valid_from && new_valid_to) - FC_ASSERT(*new_valid_from < new_valid_to, "valid_from must be earlier than valid_to"); + FC_ASSERT(*new_valid_from < *new_valid_to, "valid_from must be earlier than valid_to"); if (new_auth) { FC_ASSERT(!new_auth->is_impossible(), "Cannot use an impossible authority threshold"); FC_ASSERT(new_auth->address_auths.size() == 0, "Address auth is not supported"); diff --git a/libraries/protocol/include/graphene/protocol/chain_parameters.hpp b/libraries/protocol/include/graphene/protocol/chain_parameters.hpp index 937640ddb8..549b65aca8 100644 --- a/libraries/protocol/include/graphene/protocol/chain_parameters.hpp +++ b/libraries/protocol/include/graphene/protocol/chain_parameters.hpp @@ -108,6 +108,7 @@ FC_REFLECT( graphene::protocol::htlc_options, FC_REFLECT( graphene::protocol::custom_authority_options_type, (max_custom_authority_lifetime_seconds) + (max_custom_authorities_per_account) (max_operation_tag) ) diff --git a/libraries/protocol/include/graphene/protocol/restriction.hpp b/libraries/protocol/include/graphene/protocol/restriction.hpp index 652c246887..0aebec99c7 100644 --- a/libraries/protocol/include/graphene/protocol/restriction.hpp +++ b/libraries/protocol/include/graphene/protocol/restriction.hpp @@ -101,6 +101,11 @@ struct restriction { restriction() = default; restriction(const string& member_name, function_type type, const argument_type& argument) : member_name(member_name), restriction_type(type), argument(argument) {} + + friend bool operator==(const restriction& a, const restriction& b) { + return std::tie(a.member_name, a.restriction_type, a.argument, a.extensions) == + std::tie(b.member_name, b.restriction_type, b.argument, b.extensions); + } }; } } // graphene::protocol diff --git a/tests/tests/custom_authority_tests.cpp b/tests/tests/custom_authority_tests.cpp index a76f4967df..54df3d33ba 100644 --- a/tests/tests/custom_authority_tests.cpp +++ b/tests/tests/custom_authority_tests.cpp @@ -92,7 +92,6 @@ BOOST_AUTO_TEST_CASE(custom_auths) { try { fund(alice, asset(1000*GRAPHENE_BLOCKCHAIN_PRECISION)); fund(bob, asset(1000*GRAPHENE_BLOCKCHAIN_PRECISION)); - custom_authority_create_operation op; op.account = alice.get_id(); op.auth.add_authority(bob.get_id(), 1); @@ -110,45 +109,55 @@ BOOST_AUTO_TEST_CASE(custom_auths) { try { top.amount.amount = 99 * GRAPHENE_BLOCKCHAIN_PRECISION; trx.operations = {top}; sign(trx, bob_private_key); + // No custom auth yet; bob's transfer should fail BOOST_CHECK_THROW(PUSH_TX(db, trx), tx_missing_active_auth); trx.clear(); trx.operations = {op}; sign(trx, alice_private_key); + // Alice publishes the custom authority PUSH_TX(db, trx); - const auto& auth = *db.get_index_type().indices().get().find(alice_id); + custom_authority_id_type auth_id = + db.get_index_type().indices().get().find(alice_id)->id; trx.clear(); trx.operations = {top}; sign(trx, bob_private_key); + // Now bob's transfer should succeed due to the custom authority PUSH_TX(db, trx); trx.operations.front().get().amount.amount = 100*GRAPHENE_BLOCKCHAIN_PRECISION; trx.clear_signatures(); sign(trx, bob_private_key); + // If bob tries to transfer 100, it fails because the restriction is strictly less than 100 BOOST_CHECK_THROW(PUSH_TX(db, trx), tx_missing_active_auth); op.restrictions.front().argument.get>().front().restriction_type = restriction::func_eq; custom_authority_update_operation uop; uop.account = alice.get_id(); - uop.authority_to_update = auth.id; + uop.authority_to_update = auth_id; uop.restrictions_to_remove = {0}; uop.restrictions_to_add = {op.restrictions.front()}; trx.clear(); trx.operations = {uop}; sign(trx, alice_private_key); + // Alice publishes an update to the custom authority, making the restriction require exactly 100 PUSH_TX(db, trx); + BOOST_CHECK(auth_id(db).restrictions == uop.restrictions_to_add); + trx.clear(); trx.operations = {top}; trx.expiration += 5; sign(trx, bob_private_key); + // The transfer of 99 should fail now becaues the requirement is for exactly 100 BOOST_CHECK_THROW(PUSH_TX(db, trx), tx_missing_active_auth); trx.operations.front().get().amount.amount = 100*GRAPHENE_BLOCKCHAIN_PRECISION; trx.clear_signatures(); sign(trx, bob_private_key); + // A transfer of 100 should succeed PUSH_TX(db, trx); auto transfer = trx; @@ -157,20 +166,22 @@ BOOST_AUTO_TEST_CASE(custom_auths) { try { trx.expiration += 5; trx.clear_signatures(); sign(trx, bob_private_key); + // Another one should succeed PUSH_TX(db, trx); custom_authority_delete_operation dop; - auto id = db.get_index_type().indices().get().find(alice_id)->id; dop.account = alice.get_id(); - dop.authority_to_delete = id; + dop.authority_to_delete = auth_id; trx.clear(); trx.operations = {dop}; sign(trx, alice_private_key); + // Alice deletes the custom authority PUSH_TX(db, trx); transfer.expiration += 10; transfer.clear_signatures(); sign(transfer, bob_private_key); + // The transfer should no longer work BOOST_CHECK_THROW(PUSH_TX(db, transfer), tx_missing_active_auth); } FC_LOG_AND_RETHROW() } From 969999bc640cec9dc5fe6e2bcec72c07d4d41cf7 Mon Sep 17 00:00:00 2001 From: Nathan Hourt Date: Mon, 22 Jul 2019 16:41:46 -0500 Subject: [PATCH 07/41] BSIP 40: Restrictions use member index rather than name Restrictions now specify the field they restrict by member index rather than member name. Although this change makes restriction objects more opaque to human readers and increases the compile-time complexity of restriction_predicate.cpp, using indexes is faster at runtime, smaller when serialized, and allows renaming of members in future code updates. --- .../include/graphene/protocol/object_id.hpp | 8 +++ .../include/graphene/protocol/restriction.hpp | 12 ++--- libraries/protocol/restriction_predicate.cpp | 49 +++++++------------ tests/tests/custom_authority_tests.cpp | 42 +++++++++++----- 4 files changed, 64 insertions(+), 47 deletions(-) diff --git a/libraries/protocol/include/graphene/protocol/object_id.hpp b/libraries/protocol/include/graphene/protocol/object_id.hpp index f4f02ab914..ff23959085 100644 --- a/libraries/protocol/include/graphene/protocol/object_id.hpp +++ b/libraries/protocol/include/graphene/protocol/object_id.hpp @@ -169,6 +169,10 @@ struct reflector > { typedef graphene::db::object_id type; typedef std::true_type is_defined; + using native_members = typelist>; + using inherited_members = typelist<>; + using members = native_members; + using base_classes = typelist<>; enum member_count_enum { local_member_count = 1, total_member_count = 1 @@ -180,6 +184,10 @@ struct reflector > visitor.TEMPLATE operator()( "instance" ); } }; +namespace member_names { +template +struct member_name, 0> { static constexpr const char* value = "instance"; }; +} inline void to_variant( const graphene::db::object_id_type& var, fc::variant& vo, uint32_t max_depth = 1 ) diff --git a/libraries/protocol/include/graphene/protocol/restriction.hpp b/libraries/protocol/include/graphene/protocol/restriction.hpp index 0aebec99c7..6aa8457a8d 100644 --- a/libraries/protocol/include/graphene/protocol/restriction.hpp +++ b/libraries/protocol/include/graphene/protocol/restriction.hpp @@ -92,19 +92,19 @@ struct restriction { using argument_type = fc::static_variant; - string member_name; + unsigned_int member_index; unsigned_int restriction_type; argument_type argument; extensions_type extensions; restriction() = default; - restriction(const string& member_name, function_type type, const argument_type& argument) - : member_name(member_name), restriction_type(type), argument(argument) {} + restriction(const unsigned_int& member_index, function_type type, const argument_type& argument) + : member_index(member_index), restriction_type(type), argument(argument) {} friend bool operator==(const restriction& a, const restriction& b) { - return std::tie(a.member_name, a.restriction_type, a.argument, a.extensions) == - std::tie(b.member_name, b.restriction_type, b.argument, b.extensions); + return std::tie(a.member_index, a.restriction_type, a.argument, a.extensions) == + std::tie(b.member_index, b.restriction_type, b.argument, b.extensions); } }; @@ -126,7 +126,7 @@ FC_REFLECT_ENUM(graphene::protocol::restriction::function_type, (FUNCTION_TYPE_COUNT)) FC_REFLECT(graphene::protocol::restriction, - (member_name) + (member_index) (restriction_type) (argument) (extensions)) diff --git a/libraries/protocol/restriction_predicate.cpp b/libraries/protocol/restriction_predicate.cpp index 3b9ed4ca74..b2571b4d6b 100644 --- a/libraries/protocol/restriction_predicate.cpp +++ b/libraries/protocol/restriction_predicate.cpp @@ -81,10 +81,10 @@ template constexpr static bool is_container() // subsequent layers work on any object or field // - restrictions_to_predicate() -- takes a vector and creates a predicate for each of them, // but returns a single predicate that returns true only if all sub-predicates return true -// - object_field_predicator -- visits the object being restricted to resolve which specific field is the -// subject of the restriction -// - create_logical_or_predicate() -- If the predicate is a logical OR function, instead of using the -// object_field_predicator, we recurse into restrictions_to_predicate for each branch of the OR, returning a +// - create_field_predicate() -- Resolves which field of Object the restriction is referencing by indexing +// into the object's reflected fields with the predicates member_index +// - create_logical_or_predicate() -- If the predicate is a logical OR function, instead of using +// create_field_predicate, we recurse into restrictions_to_predicate for each branch of the OR, returning a // predicate which returns true if any branch of the OR passes // - create_predicate_function() -- switches on restriction type to determine which predicate template to use // going forward @@ -426,7 +426,7 @@ object_restriction_predicate create_predicate_function(restriction_functi } /** - * @brief Create a predicate asserting on the field of an object a restriction is referencing + * @brief Create a predicate asserting on the field of the object a restriction is referencing * * @tparam Object The type the restriction restricts * @@ -434,34 +434,23 @@ object_restriction_predicate create_predicate_function(restriction_functi * the restriction references to the particular field type, creates a predicate on that field, and wraps that * predicate to accept the object type and invoke the inner predicate on the specified field. */ -template -struct object_field_predicator { - restriction r; - object_field_predicator(restriction&& r) : r(std::move(r)) {} - mutable fc::optional> predicate; - - template - void operator()(const char* member_name) const { - if (r.member_name == member_name) { - auto function = static_cast(r.restriction_type.value); - auto p = create_predicate_function(function, std::move(r.argument)); - predicate = [p=std::move(p)](const Object& obj) { return p(obj.*field); }; - } - } -}; -/// Helper function to invoke object_field_predicator -template::is_defined::value>> +template::native_members::length != 0>> object_restriction_predicate create_field_predicate(restriction&& r, short) { - object_field_predicator visitor(std::move(r)); - fc::reflector::visit(visitor); - FC_ASSERT(visitor.predicate.valid(), "Invalid member name ${O}::${M} in restriction", - ("O", fc::get_typename::name())("M", visitor.r.member_name)); - return std::move(*visitor.predicate); + using member_list = typename fc::reflector::native_members; + FC_ASSERT(r.member_index < member_list::length, "Invalid member index ${I} for object ${O}", + ("I", r.member_index)("O", fc::get_typename::name())); + auto predicator = [f=r.restriction_type, a=std::move(r.argument)](auto t) -> object_restriction_predicate { + using FieldReflection = typename decltype(t)::type; + using Field = typename FieldReflection::type; + auto p = create_predicate_function(static_cast(f), std::move(a)); + return [p=std::move(p)](const Object& o) { return p(FieldReflection::get(o)); }; + }; + return fc::typelist_utils::dispatch(member_list(), r.member_index, predicator); } template -object_restriction_predicate create_field_predicate(restriction&& r, long) { - FC_THROW_EXCEPTION(fc::assert_exception, "Invalid restriction references member of non-object type: ${O}::${M}", - ("O", fc::get_typename::name())("M", r.member_name)); +object_restriction_predicate create_field_predicate(restriction&&, long) { + FC_THROW_EXCEPTION(fc::assert_exception, "Invalid restriction references member of non-object type: ${O}", + ("O", fc::get_typename::name())); } template diff --git a/tests/tests/custom_authority_tests.cpp b/tests/tests/custom_authority_tests.cpp index 54df3d33ba..a1fc7c1e4e 100644 --- a/tests/tests/custom_authority_tests.cpp +++ b/tests/tests/custom_authority_tests.cpp @@ -38,29 +38,43 @@ BOOST_FIXTURE_TEST_SUITE(custom_authority_tests, database_fixture) #define FUNC(TYPE) BOOST_PP_CAT(restriction::func_, TYPE) +template +unsigned_int member_index(string name) { + unsigned_int index; + fc::typelist_utils::for_each(typename fc::reflector::native_members(), [&name, &index](auto t) mutable { + if (name == decltype(t)::type::get_name()) + index = decltype(t)::type::index; + }); + return index; +} + BOOST_AUTO_TEST_CASE(restriction_predicate_tests) { try { using namespace graphene::protocol; vector restrictions; transfer_operation transfer; - restrictions.emplace_back("to", FUNC(eq), account_id_type(12)); + auto to_index = member_index("to"); + restrictions.emplace_back(to_index, FUNC(eq), account_id_type(12)); BOOST_CHECK(get_restriction_predicate(restrictions, operation::tag::value)(transfer) == false); transfer.to = account_id_type(12); BOOST_CHECK(get_restriction_predicate(restrictions, operation::tag::value)(transfer) == true); - restrictions.front() = restriction("foo", FUNC(eq), account_id_type(12)); + restrictions.front() = restriction(fc::reflector::native_members::length, + FUNC(eq), account_id_type(12)); BOOST_CHECK_THROW(get_restriction_predicate(restrictions, operation::tag::value), fc::assert_exception); - restrictions.front() = restriction("to", FUNC(eq), asset_id_type(12)); + restrictions.front() = restriction(to_index, FUNC(eq), asset_id_type(12)); BOOST_CHECK_THROW(get_restriction_predicate(restrictions, operation::tag::value), fc::assert_exception); - restrictions.front() = restriction("fee", FUNC(attr), - vector{restriction("asset_id", FUNC(eq), asset_id_type(0))}); + auto fee_index = member_index("fee"); + auto asset_id_index = member_index("asset_id"); + restrictions.front() = restriction(fee_index, FUNC(attr), + vector{restriction(asset_id_index, FUNC(eq), asset_id_type(0))}); BOOST_CHECK(get_restriction_predicate(restrictions, operation::tag::value)(transfer) == true); restrictions.front().argument.get>().front().argument = asset_id_type(1); BOOST_CHECK(get_restriction_predicate(restrictions, operation::tag::value)(transfer) == false); - restrictions.emplace_back("to", FUNC(eq), account_id_type(12)); + restrictions.emplace_back(to_index, FUNC(eq), account_id_type(12)); transfer.to = account_id_type(12); transfer.fee.asset_id = asset_id_type(1); BOOST_CHECK(get_restriction_predicate(restrictions, operation::tag::value)(transfer) == true); @@ -69,8 +83,10 @@ BOOST_AUTO_TEST_CASE(restriction_predicate_tests) { try { account_update_operation update; restrictions.clear(); - restrictions.emplace_back("extensions", FUNC(attr), - vector{restriction("owner_special_authority", FUNC(eq), void_t())}); + auto extensions_index = member_index("extensions"); + auto authority_index = member_index("owner_special_authority"); + restrictions.emplace_back(extensions_index, FUNC(attr), + vector{restriction(authority_index, FUNC(eq), void_t())}); auto predicate = get_restriction_predicate(restrictions, operation::tag::value); BOOST_CHECK_THROW(predicate(transfer), fc::assert_exception); BOOST_CHECK(predicate(update) == true); @@ -99,9 +115,13 @@ BOOST_AUTO_TEST_CASE(custom_auths) { try { op.enabled = true; op.valid_to = db.head_block_time() + 1000; op.operation_type = operation::tag::value; - op.restrictions = {restriction("amount", restriction::func_attr, vector{ - restriction("amount", restriction::func_lt, int64_t(100*GRAPHENE_BLOCKCHAIN_PRECISION)), - restriction("asset_id", restriction::func_eq, asset_id_type(0))})}; + auto transfer_amount_index = member_index("amount"); + auto asset_amount_index = member_index("amount"); + auto assed_id_index = member_index("asset_id"); + op.restrictions = {restriction(transfer_amount_index, restriction::func_attr, vector{ + restriction(asset_amount_index, restriction::func_lt, + int64_t(100*GRAPHENE_BLOCKCHAIN_PRECISION)), + restriction(assed_id_index, restriction::func_eq, asset_id_type(0))})}; transfer_operation top; top.to = bob.get_id(); From ab4a262ef3f6d8eb9282382694c60bd2b50eb09d Mon Sep 17 00:00:00 2001 From: Nathan Hourt Date: Wed, 24 Jul 2019 00:02:59 -0500 Subject: [PATCH 08/41] BSIP 40: Fix build on old G++ This also improves the build time on all compilers --- .../include/graphene/protocol/object_id.hpp | 6 +- libraries/protocol/restriction_predicate.cpp | 137 +++++++++--------- tests/tests/custom_authority_tests.cpp | 4 +- 3 files changed, 74 insertions(+), 73 deletions(-) diff --git a/libraries/protocol/include/graphene/protocol/object_id.hpp b/libraries/protocol/include/graphene/protocol/object_id.hpp index ff23959085..7f627e4b19 100644 --- a/libraries/protocol/include/graphene/protocol/object_id.hpp +++ b/libraries/protocol/include/graphene/protocol/object_id.hpp @@ -169,10 +169,10 @@ struct reflector > { typedef graphene::db::object_id type; typedef std::true_type is_defined; - using native_members = typelist>; - using inherited_members = typelist<>; + using native_members = typelist::list>; + using inherited_members = typelist::list<>; using members = native_members; - using base_classes = typelist<>; + using base_classes = typelist::list<>; enum member_count_enum { local_member_count = 1, total_member_count = 1 diff --git a/libraries/protocol/restriction_predicate.cpp b/libraries/protocol/restriction_predicate.cpp index b2571b4d6b..fbfcf506ec 100644 --- a/libraries/protocol/restriction_predicate.cpp +++ b/libraries/protocol/restriction_predicate.cpp @@ -27,6 +27,7 @@ #include namespace graphene { namespace protocol { +namespace typelist = fc::typelist; using std::declval; using std::size_t; using restriction_function = restriction::function_type; @@ -50,10 +51,13 @@ constexpr static bool comparable_types = !std::is_same::value && (is_integral || is_safe))); // Metafunction to check if type is a container -template constexpr static bool is_container = false; -template constexpr static bool is_container().size())> = true; +template +struct is_container_impl { constexpr static bool value = false; }; +template +struct is_container_impl().size())> { constexpr static bool value = true; }; +template constexpr static bool is_container = is_container_impl::value; -////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // *** Restriction Predicate Logic *** // This file is where the magic happens for BSIP 40, aka Custom Active Authorities. This gets fairly complicated, so @@ -93,57 +97,28 @@ template constexpr static bool is_container() // - attribute_assertion -- If the restriction is an attribute assertion, instead of using the // restriction_argument_visitor, we recurse into restrictions_to_predicate with the current Field as the Object // - predicate_xyz -- These are functors implementing the various predicate function types -////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // These typelists contain the argument types legal for various function types: // Valid for magnitude comparisons and equality comparisons -using comparable_types_list = fc::typelist; +using comparable_types_list = typelist::list; // Valid for list functions (in, not_in, has_all, has_none) struct make_flat_set { template struct transform { using type = flat_set; }; }; -using list_types_list = fc::typelist::concat::type - ::transform; +using list_types_list = typelist::transform, + comparable_types_list>, + make_flat_set>; // Valid for equality comparisons but not necessarily magnitude comparisons -using equality_types_list = - typename fc::typelist::concat::type - ::concat::type; +using equality_types_list = typename typelist::concat, + comparable_types_list, list_types_list>; // Valid for attritube assertions -using attr_types_list = fc::typelist>; +using attr_types_list = typelist::list>; // Valid for logical or assertions -using or_types_list = fc::typelist>>; - -// slimmer_visitor and static_variant_slimmer accelerate build times by reducing the number of elements in the -// argument static variant to only those supported for a given function type. This reduces build time because it -// eliminates many options the compiler has to explore when visiting the argument variant to create a predicate -template -struct slimmer_visitor { - using result_type = typename List::template apply; - template constexpr static bool type_in_list = List::template contains; - - template>> - result_type do_cast(const T& content, short) { return result_type(content); } - template - result_type do_cast(const T&, long) { - FC_THROW_EXCEPTION(fc::assert_exception, "Invalid argument type for restriction function type"); - } - - template result_type operator()(const T& content) { - return do_cast(content, short()); - } -}; -template -struct static_variant_slimmer { - using result_type = typename slimmer_visitor::result_type; - template - static result_type slim(SV& variant) { - slimmer_visitor visitor; - return variant.visit(visitor); - } -}; +using or_types_list = typelist::list>>; // Final implementations of predicate functors template struct predicate_eq { @@ -152,7 +127,7 @@ template struct predicate_eq { template struct can_evaluate_helper : std::false_type {}; template struct can_evaluate_helper()(declval()))>> { - static constexpr bool value = equality_types_list::contains; + static constexpr bool value = typelist::contains(); }; template static constexpr bool can_evaluate = can_evaluate_helper::value; @@ -190,7 +165,7 @@ template struct predicate_compare { template struct can_evaluate_helper : std::false_type {}; template struct can_evaluate_helper()(declval()))>> { - static constexpr bool value = comparable_types_list::contains; + static constexpr bool value = typelist::contains(); }; template static constexpr bool can_evaluate = can_evaluate_helper::value; @@ -231,7 +206,7 @@ template struct predicate_in> { template struct can_evaluate_helper : std::false_type {}; template struct can_evaluate_helper()(declval()))>> { - static constexpr bool value = equality_types_list::contains; + static constexpr bool value = typelist::contains(); }; template static constexpr bool can_evaluate = can_evaluate_helper::value; @@ -260,7 +235,7 @@ template struct predicate_has_all> { template struct can_evaluate_helper : std::false_type {}; template struct can_evaluate_helper()(declval()))>> { - static constexpr bool value = equality_types_list::contains; + static constexpr bool value = typelist::contains(); }; template static constexpr bool can_evaluate = can_evaluate_helper::value; @@ -293,7 +268,7 @@ template struct predicate_has_none> { template struct can_evaluate_helper : std::false_type {}; template struct can_evaluate_helper()(declval()))>> { - static constexpr bool value = equality_types_list::contains; + static constexpr bool value = typelist::contains(); }; template static constexpr bool can_evaluate = can_evaluate_helper::value; @@ -331,7 +306,8 @@ template class Predicate, typename Field> struct restriction_argument_visitor { using result_type = object_restriction_predicate; - template::template can_evaluate>> + template::template can_evaluate_helper::value>> result_type make_predicate(const Argument& a, short) { return Predicate(a); } @@ -371,49 +347,73 @@ struct attribute_assertion> { } }; +// slimmer accelerates build times by reducing the number of elements in the argument static variant to only those +// supported for a given function type. This reduces build time because it eliminates many options the compiler has +// to explore when visiting the argument variant to create a predicate +template +struct slimmer { + restriction_argument arg; + slimmer(restriction_argument&& arg) : arg(std::move(arg)) {} + using result_type = typelist::apply; + + template()>> + result_type do_cast(T&& content, short) { return result_type(content); } + template + result_type do_cast(T&&, long) { + FC_THROW_EXCEPTION(fc::assert_exception, "Invalid argument type for restriction function type"); + } + + template result_type operator()(T) { + return do_cast(std::move(arg.get()), short()); + } +}; + template object_restriction_predicate create_predicate_function(restriction_function func, restriction_argument arg) { try { + using typelist::runtime::dispatch; + using std::move; + using Arg = decltype(arg); switch(func) { case restriction::func_eq: { restriction_argument_visitor visitor; - return static_variant_slimmer::slim(arg).visit(visitor); + return dispatch(Arg::list(), arg.which(), slimmer(move(arg))).visit(visitor); } case restriction::func_ne: { restriction_argument_visitor visitor; - return static_variant_slimmer::slim(arg).visit(visitor); + return dispatch(Arg::list(), arg.which(), slimmer(move(arg))).visit(visitor); } case restriction::func_lt: { restriction_argument_visitor visitor; - return static_variant_slimmer::slim(arg).visit(visitor); + return dispatch(Arg::list(), arg.which(), slimmer(move(arg))).visit(visitor); } case restriction::func_le: { restriction_argument_visitor visitor; - return static_variant_slimmer::slim(arg).visit(visitor); + return dispatch(Arg::list(), arg.which(), slimmer(move(arg))).visit(visitor); } case restriction::func_gt: { restriction_argument_visitor visitor; - return static_variant_slimmer::slim(arg).visit(visitor); + return dispatch(Arg::list(), arg.which(), slimmer(move(arg))).visit(visitor); } case restriction::func_ge: { restriction_argument_visitor visitor; - return static_variant_slimmer::slim(arg).visit(visitor); + return dispatch(Arg::list(), arg.which(), slimmer(move(arg))).visit(visitor); } case restriction::func_in: { restriction_argument_visitor visitor; - return static_variant_slimmer::slim(arg).visit(visitor); + return dispatch(Arg::list(), arg.which(), slimmer(move(arg))).visit(visitor); } case restriction::func_not_in: { restriction_argument_visitor visitor; - return static_variant_slimmer::slim(arg).visit(visitor); + return dispatch(Arg::list(), arg.which(), slimmer(move(arg))).visit(visitor); } case restriction::func_has_all: { restriction_argument_visitor visitor; - return static_variant_slimmer::slim(arg).visit(visitor); + return dispatch(Arg::list(), arg.which(), slimmer(move(arg))).visit(visitor); } case restriction::func_has_none: { restriction_argument_visitor visitor; - return static_variant_slimmer::slim(arg).visit(visitor); + return dispatch(Arg::list(), arg.which(), slimmer(move(arg))).visit(visitor); } case restriction::func_attr: FC_ASSERT(arg.which() == restriction_argument::tag>::value, @@ -434,10 +434,11 @@ object_restriction_predicate create_predicate_function(restriction_functi * the restriction references to the particular field type, creates a predicate on that field, and wraps that * predicate to accept the object type and invoke the inner predicate on the specified field. */ -template::native_members::length != 0>> +template::native_members>() != 0>> object_restriction_predicate create_field_predicate(restriction&& r, short) { using member_list = typename fc::reflector::native_members; - FC_ASSERT(r.member_index < member_list::length, "Invalid member index ${I} for object ${O}", + FC_ASSERT(r.member_index < typelist::length(), "Invalid member index ${I} for object ${O}", ("I", r.member_index)("O", fc::get_typename::name())); auto predicator = [f=r.restriction_type, a=std::move(r.argument)](auto t) -> object_restriction_predicate { using FieldReflection = typename decltype(t)::type; @@ -445,7 +446,7 @@ object_restriction_predicate create_field_predicate(restriction&& r, sho auto p = create_predicate_function(static_cast(f), std::move(a)); return [p=std::move(p)](const Object& o) { return p(FieldReflection::get(o)); }; }; - return fc::typelist_utils::dispatch(member_list(), r.member_index, predicator); + return typelist::runtime::dispatch(member_list(), r.member_index, predicator); } template object_restriction_predicate create_field_predicate(restriction&&, long) { @@ -462,8 +463,8 @@ object_restriction_predicate create_logical_or_predicate(vector restrictions_to_predicate(vector(std::move(r), short()); }); - return [predicates=std::move(predicates)](const Object& field) { - return std::all_of(predicates.begin(), predicates.end(), [&f=field](const auto& p) { return p(f); }); + return [predicates=std::move(predicates)](const Object& obj) { + return std::all_of(predicates.begin(), predicates.end(), [o=std::cref(obj)](const auto& p) { return p(o); }); }; } diff --git a/tests/tests/custom_authority_tests.cpp b/tests/tests/custom_authority_tests.cpp index a1fc7c1e4e..ce67c8b716 100644 --- a/tests/tests/custom_authority_tests.cpp +++ b/tests/tests/custom_authority_tests.cpp @@ -41,7 +41,7 @@ BOOST_FIXTURE_TEST_SUITE(custom_authority_tests, database_fixture) template unsigned_int member_index(string name) { unsigned_int index; - fc::typelist_utils::for_each(typename fc::reflector::native_members(), [&name, &index](auto t) mutable { + fc::typelist::runtime::for_each(typename fc::reflector::native_members(), [&name, &index](auto t) mutable { if (name == decltype(t)::type::get_name()) index = decltype(t)::type::index; }); @@ -59,7 +59,7 @@ BOOST_AUTO_TEST_CASE(restriction_predicate_tests) { try { transfer.to = account_id_type(12); BOOST_CHECK(get_restriction_predicate(restrictions, operation::tag::value)(transfer) == true); - restrictions.front() = restriction(fc::reflector::native_members::length, + restrictions.front() = restriction(fc::typelist::length::native_members>(), FUNC(eq), account_id_type(12)); BOOST_CHECK_THROW(get_restriction_predicate(restrictions, operation::tag::value), fc::assert_exception); From a2a5801806e3251fdef88647ad9c2d9a392fb891 Mon Sep 17 00:00:00 2001 From: Nathan Hourt Date: Wed, 31 Jul 2019 18:54:15 -0500 Subject: [PATCH 09/41] BSIP 40: Best build --- libraries/protocol/restriction_predicate.cpp | 44 +++++--------------- 1 file changed, 10 insertions(+), 34 deletions(-) diff --git a/libraries/protocol/restriction_predicate.cpp b/libraries/protocol/restriction_predicate.cpp index fbfcf506ec..b1a529bfcc 100644 --- a/libraries/protocol/restriction_predicate.cpp +++ b/libraries/protocol/restriction_predicate.cpp @@ -347,73 +347,49 @@ struct attribute_assertion> { } }; -// slimmer accelerates build times by reducing the number of elements in the argument static variant to only those -// supported for a given function type. This reduces build time because it eliminates many options the compiler has -// to explore when visiting the argument variant to create a predicate -template -struct slimmer { - restriction_argument arg; - slimmer(restriction_argument&& arg) : arg(std::move(arg)) {} - using result_type = typelist::apply; - - template()>> - result_type do_cast(T&& content, short) { return result_type(content); } - template - result_type do_cast(T&&, long) { - FC_THROW_EXCEPTION(fc::assert_exception, "Invalid argument type for restriction function type"); - } - - template result_type operator()(T) { - return do_cast(std::move(arg.get()), short()); - } -}; - template object_restriction_predicate create_predicate_function(restriction_function func, restriction_argument arg) { try { - using typelist::runtime::dispatch; - using std::move; - using Arg = decltype(arg); switch(func) { case restriction::func_eq: { restriction_argument_visitor visitor; - return dispatch(Arg::list(), arg.which(), slimmer(move(arg))).visit(visitor); + return typelist::apply::import_from(std::move(arg)).visit(visitor); } case restriction::func_ne: { restriction_argument_visitor visitor; - return dispatch(Arg::list(), arg.which(), slimmer(move(arg))).visit(visitor); + return typelist::apply::import_from(std::move(arg)).visit(visitor); } case restriction::func_lt: { restriction_argument_visitor visitor; - return dispatch(Arg::list(), arg.which(), slimmer(move(arg))).visit(visitor); + return typelist::apply::import_from(std::move(arg)).visit(visitor); } case restriction::func_le: { restriction_argument_visitor visitor; - return dispatch(Arg::list(), arg.which(), slimmer(move(arg))).visit(visitor); + return typelist::apply::import_from(std::move(arg)).visit(visitor); } case restriction::func_gt: { restriction_argument_visitor visitor; - return dispatch(Arg::list(), arg.which(), slimmer(move(arg))).visit(visitor); + return typelist::apply::import_from(std::move(arg)).visit(visitor); } case restriction::func_ge: { restriction_argument_visitor visitor; - return dispatch(Arg::list(), arg.which(), slimmer(move(arg))).visit(visitor); + return typelist::apply::import_from(std::move(arg)).visit(visitor); } case restriction::func_in: { restriction_argument_visitor visitor; - return dispatch(Arg::list(), arg.which(), slimmer(move(arg))).visit(visitor); + return typelist::apply::import_from(std::move(arg)).visit(visitor); } case restriction::func_not_in: { restriction_argument_visitor visitor; - return dispatch(Arg::list(), arg.which(), slimmer(move(arg))).visit(visitor); + return typelist::apply::import_from(std::move(arg)).visit(visitor); } case restriction::func_has_all: { restriction_argument_visitor visitor; - return dispatch(Arg::list(), arg.which(), slimmer(move(arg))).visit(visitor); + return typelist::apply::import_from(std::move(arg)).visit(visitor); } case restriction::func_has_none: { restriction_argument_visitor visitor; - return dispatch(Arg::list(), arg.which(), slimmer(move(arg))).visit(visitor); + return typelist::apply::import_from(std::move(arg)).visit(visitor); } case restriction::func_attr: FC_ASSERT(arg.which() == restriction_argument::tag>::value, From 636abb0fc4236e8a24576dc29afc2108c28d4e1b Mon Sep 17 00:00:00 2001 From: Nathan Hourt Date: Fri, 2 Aug 2019 09:59:34 -0500 Subject: [PATCH 10/41] BSIP 40: Delete expired authorities in maintenance --- libraries/chain/db_maint.cpp | 13 +++++++++++++ .../graphene/chain/custom_authority_object.hpp | 12 +++++++++--- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/libraries/chain/db_maint.cpp b/libraries/chain/db_maint.cpp index 7618fda50b..5ff519eab1 100644 --- a/libraries/chain/db_maint.cpp +++ b/libraries/chain/db_maint.cpp @@ -44,6 +44,7 @@ #include #include #include +#include namespace graphene { namespace chain { @@ -1143,6 +1144,17 @@ void process_hf_935( database& db ) } } +/** + * @brief Remove any custom active authorities whose expiration dates are in the past + * @param db A mutable database reference + */ +void delete_expired_custom_authorities( database& db ) +{ + const auto& index = db.get_index_type().indices().get(); + while (!index.empty() && index.begin()->valid_to < db.head_block_time()) + db.remove(*index.begin()); +} + void database::perform_chain_maintenance(const signed_block& next_block, const global_property_object& global_props) { const auto& gpo = get_global_properties(); @@ -1323,6 +1335,7 @@ void database::perform_chain_maintenance(const signed_block& next_block, const g } process_bitassets(); + delete_expired_custom_authorities(*this); // process_budget needs to run at the bottom because // it needs to know the next_maintenance_time diff --git a/libraries/chain/include/graphene/chain/custom_authority_object.hpp b/libraries/chain/include/graphene/chain/custom_authority_object.hpp index b3f731a2e2..15b61cb9b9 100644 --- a/libraries/chain/include/graphene/chain/custom_authority_object.hpp +++ b/libraries/chain/include/graphene/chain/custom_authority_object.hpp @@ -65,6 +65,7 @@ namespace graphene { namespace chain { }; struct by_account_custom; + struct by_expiration; /** * @ingroup object_index @@ -74,13 +75,18 @@ namespace graphene { namespace chain { indexed_by< ordered_unique, member>, ordered_unique, - composite_key< - custom_authority_object, + composite_key, member, member, member - > + >>, + ordered_unique, + composite_key, + member + >, + composite_key_compare, std::less> > > > custom_authority_multi_index_type; From d28f68ce2647c7aa1ba80c674926ecfed03e68cb Mon Sep 17 00:00:00 2001 From: Nathan Hourt Date: Sun, 4 Aug 2019 19:51:11 -0500 Subject: [PATCH 11/41] BSIP 40: Refactor predicates --- libraries/protocol/restriction_predicate.cpp | 563 ++++++++++--------- 1 file changed, 288 insertions(+), 275 deletions(-) diff --git a/libraries/protocol/restriction_predicate.cpp b/libraries/protocol/restriction_predicate.cpp index b1a529bfcc..f23c19019c 100644 --- a/libraries/protocol/restriction_predicate.cpp +++ b/libraries/protocol/restriction_predicate.cpp @@ -36,27 +36,35 @@ using restriction_argument = restriction::argument_type; // Make our own std::void_t since the real one isn't available in C++14 template using make_void = void; -// We use our own is_integral which does not consider bools integral (to disallow comparison between bool and ints) -template constexpr static bool is_integral = - std::conditional_t::value, std::false_type, std::is_integral>::value; - // Metafunction to check if type is some instantiation of fc::safe template constexpr static bool is_safe = false; template constexpr static bool is_safe> = true; +// Metafunction to check if type is a flat_set of any element type +template constexpr static bool is_flat_set = false; +template constexpr static bool is_flat_set> = true; + +// We use our own is_integral which does not consider bools integral (to disallow comparison between bool and ints) +template constexpr static bool is_integral = !std::is_same::value && + !std::is_same>::value && + (is_safe || std::is_integral::value); + // Metafunction to check if two types are comparable, which means not void_t, and either the same or both integral template constexpr static bool comparable_types = !std::is_same::value && - (std::is_same::value || ((is_integral || is_safe) && - (is_integral || is_safe))); + (std::is_same::value || (is_integral && is_integral)); // Metafunction to check if type is a container -template -struct is_container_impl { constexpr static bool value = false; }; +template +struct is_container_impl : std::false_type {}; template -struct is_container_impl().size())> { constexpr static bool value = true; }; +struct is_container_impl().size())>> : std::true_type {}; template constexpr static bool is_container = is_container_impl::value; +// Type alias for a predicate on a particular field type +template +using object_restriction_predicate = std::function; + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // *** Restriction Predicate Logic *** @@ -120,185 +128,181 @@ using attr_types_list = typelist::list>; // Valid for logical or assertions using or_types_list = typelist::list>>; -// Final implementations of predicate functors -template struct predicate_eq { - Argument a; - constexpr predicate_eq(const Argument& a) : a(a) {} - template - struct can_evaluate_helper : std::false_type {}; template - struct can_evaluate_helper()(declval()))>> { - static constexpr bool value = typelist::contains(); - }; - template static constexpr bool can_evaluate = can_evaluate_helper::value; - +//////////////////////////////////////////////// PREDICATE FUNCTORS //////////////////////////////////////////////// +// An invalid predicate which throws upon construction. Inherited by other predicates when arg types are incompatible +template +struct predicate_invalid { + constexpr static bool valid = false; + predicate_invalid() { FC_THROW_EXCEPTION(fc::assert_exception, "Invalid types for predicate"); } + bool operator()(const A&, const B&) const { return false; } +}; +// Equality comparison +template struct predicate_eq : predicate_invalid {}; +template +struct predicate_eq>> { // Simple comparison - template>> - constexpr bool operator()(const Field& f) const { return f == a; } + constexpr static bool valid = true; + constexpr bool operator()(const Field& f, const Argument& a) const { return f == a; } +}; +template +struct predicate_eq && is_integral>> { // Compare container size against int - template && is_integral>> - bool operator()(const Field& f) const { return f.size() == a; } + constexpr static bool valid = true; + bool operator()(const Field& f, const Argument& a) const { return f.size() == a; } +}; +template +struct predicate_eq, Argument, std::enable_if_t>> { // Compare optional value against same type - template>> - bool operator()(const fc::optional& f) const { return f.valid() && *f == a; } + constexpr static bool valid = true; + bool operator()(const fc::optional& f, const Argument& a) const { return f.valid() && *f == a; } +}; +template +struct predicate_eq, void_t, void> { // Compare optional value against void_t (checks that optional is null) - template::value>> - bool operator()(const fc::optional& f) const { return !f.valid(); } - // Compare containers of different types -// TODO: Figure out how to actually make this compile... x( -// template && is_container && !std::is_same::value -// && comparable_types>> -// bool operator()(const Field& f) const { -// flat_set fs(f.begin(), f.end()); -// return (*this)(fs); -// } + constexpr static bool valid = true; + bool operator()(const fc::optional& f, const void_t&) const { return !f.valid(); } }; -template struct predicate_ne : predicate_eq { - using base = predicate_eq; - predicate_ne(const Argument& a) : base(a) {} - template auto operator()(const Field& f) const { return !base::operator()(f); } +// Not-equal is just an equality comparison wrapped in a negator +template struct predicate_ne : predicate_eq { + using equal = predicate_eq; + bool operator()(const Field& f, const Argument& a) const { return !equal::operator()(f, a); } }; -template struct predicate_compare { - Argument a; - constexpr predicate_compare(const Argument& a) : a(a) {} - template struct can_evaluate_helper : std::false_type {}; - template - struct can_evaluate_helper()(declval()))>> { - static constexpr bool value = typelist::contains(); - }; - template static constexpr bool can_evaluate = can_evaluate_helper::value; - // Simple comparison - template>> - constexpr int8_t operator()(const Field& f) const { return fa? 1 : 0); } +// Shared implementation for all inequality comparisons +template struct predicate_compare : predicate_invalid {}; +template +struct predicate_compare>> { + // Simple comparison, integral types + constexpr static bool valid = true; + constexpr int8_t operator()(const Field& f, const Argument& a) const { + return fa? 1 : 0); + } +}; +template +struct predicate_compare, Argument, void> : predicate_compare { // Compare optional value against same type - template>> - constexpr int8_t operator()(const fc::optional& f) const { + constexpr static bool valid = true; + constexpr int8_t operator()(const fc::optional& f, const Argument& a) const { FC_ASSERT(f.valid(), "Cannot compute inequality comparison against a null optional"); - return (*this)(*f); + return (*this)(*f, a); } }; -template struct predicate_lt : predicate_compare { - using base = predicate_compare; - constexpr predicate_lt(const Argument& a) : base(a) {} - template constexpr bool operator()(const Field& f) const { return base::operator()(f) < 0; } +// The actual inequality predicates +template struct predicate_lt : predicate_compare { + using base = predicate_compare; + constexpr bool operator()(const Field& f, const Argument& a) const { return base::operator()(f, a) < 0; } }; -template struct predicate_le : predicate_compare { - using base = predicate_compare; - constexpr predicate_le(const Argument& a) : base(a) {} - template constexpr bool operator()(const Field& f) const { return base::operator()(f) <= 0; } +template struct predicate_le : predicate_compare { + using base = predicate_compare; + constexpr bool operator()(const Field& f, const Argument& a) const { return base::operator()(f, a) <= 0; } }; -template struct predicate_gt : predicate_compare { - using base = predicate_compare; - constexpr predicate_gt(const Argument& a) : base(a) {} - template constexpr bool operator()(const Field& f) const { return base::operator()(f) > 0; } +template struct predicate_gt : predicate_compare { + using base = predicate_compare; + constexpr bool operator()(const Field& f, const Argument& a) const { return base::operator()(f, a) > 0; } }; -template struct predicate_ge : predicate_compare { - using base = predicate_compare; - constexpr predicate_ge(const Argument& a) : base(a) {} - template constexpr bool operator()(const Field& f) const { return base::operator()(f) >= 0; } +template struct predicate_ge : predicate_compare { + using base = predicate_compare; + constexpr bool operator()(const Field& f, const Argument& a) const { return base::operator()(f, a) >= 0; } }; -template struct predicate_in { template static constexpr bool can_evaluate = false; }; -template struct predicate_in> { - flat_set a; - predicate_in(const flat_set& a) : a(a) {} - template struct can_evaluate_helper : std::false_type {}; - template - struct can_evaluate_helper()(declval()))>> { - static constexpr bool value = typelist::contains(); - }; - template static constexpr bool can_evaluate = can_evaluate_helper::value; +// Field-in-list predicate +template struct predicate_in : predicate_invalid {}; +template +struct predicate_in, std::enable_if_t && !is_safe>> { // Simple inclusion check - template>> - bool operator()(const Field& f) const { return a.count(f) != 0; } + constexpr static bool valid = true; + bool operator()(const Field& f, const flat_set& c) const { return c.count(f) != 0; } +}; +template +struct predicate_in, flat_set, std::enable_if_t>> { // Check for safe value - template>> - bool operator()(const fc::safe& f) const { return a.count(f.value) != 0; } + constexpr static bool valid = true; + bool operator()(const fc::safe& f, const flat_set& c) const { return c.count(f.value) != 0; } +}; +template +struct predicate_in, flat_set, std::enable_if_t>> { // Check for optional value - template>> - bool operator()(const fc::optional& f) const { + constexpr static bool valid = true; + bool operator()(const fc::optional& f, const flat_set& c) const { FC_ASSERT(f.valid(), "Cannot compute whether null optional is in list"); - return (*this)(*f); + return c.count(*f) != 0; } }; -template struct predicate_not_in : predicate_in{ - using base = predicate_in; - constexpr predicate_not_in(const Argument& a) : base(a) {} - template constexpr bool operator()(const Field& f) const { return !base::operator()(f); } +// Field-not-in-list is just field-in-list wrapped in a negator +template struct predicate_not_in : predicate_in { + using base = predicate_in; + bool operator()(const Field& f, const Container& c) const { return !base::operator()(f, c); } }; -template struct predicate_has_all { template static constexpr bool can_evaluate = false; }; -template struct predicate_has_all> { - flat_set a; - predicate_has_all(const flat_set& a) : a(a) {} - template struct can_evaluate_helper : std::false_type {}; - template - struct can_evaluate_helper()(declval()))>> { - static constexpr bool value = typelist::contains(); - }; - template static constexpr bool can_evaluate = can_evaluate_helper::value; +// List-contains-list predicate +template struct predicate_has_all : predicate_invalid {}; +template +struct predicate_has_all, flat_set, + std::enable_if_t>> { // Field is already flat_set - template>> - bool operator()(const flat_set& f) const { + constexpr static bool valid = true; + bool operator()(const flat_set& f, const flat_set& a) const { if (f.size() < a.size()) return false; return std::includes(f.begin(), f.end(), a.begin(), a.end()); } +}; +template +struct predicate_has_all, + std::enable_if_t && !is_flat_set && + comparable_types>> { + predicate_has_all, flat_set> inner; // Field is other container; convert to flat_set - template && - comparable_types>> - bool operator()(const Field& f) const { + constexpr static bool valid = true; + bool operator()(const FieldContainer& f, const flat_set& a) const { if (f.size() < a.size()) return false; - flat_set fs(f.begin(), f.end()); - return (*this)(fs); + flat_set fs(f.begin(), f.end()); + return inner(fs, a); } +}; +template +struct predicate_has_all, Argument, void> : predicate_has_all { // Field is optional container - template && - comparable_types>> - bool operator()(const fc::optional& f) const { + bool operator()(const fc::optional& f, const Argument& a) const { FC_ASSERT(f.valid(), "Cannot compute whether all elements of null optional container are in other container"); - return (*this)(*f); + return (*this)(*f, a); } }; -template struct predicate_has_none { template static constexpr bool can_evaluate = false; }; -template struct predicate_has_none> { - flat_set a; - predicate_has_none(const flat_set& a) : a(a) {} - template struct can_evaluate_helper : std::false_type {}; - template - struct can_evaluate_helper()(declval()))>> { - static constexpr bool value = typelist::contains(); - }; - template static constexpr bool can_evaluate = can_evaluate_helper::value; +// List contains none of list predicate +template struct predicate_has_none : predicate_invalid {}; +template +struct predicate_has_none, flat_set, + std::enable_if_t>> { // Field is already flat_set - template>> - bool operator()(const flat_set& f) const { - flat_set intersection; + constexpr static bool valid = true; + bool operator()(const flat_set& f, const flat_set& a) const { + flat_set intersection; std::set_intersection(f.begin(), f.end(), a.begin(), a.end(), std::inserter(intersection, intersection.begin())); return intersection.empty(); } +}; +template +struct predicate_has_none, + std::enable_if_t && !is_flat_set && + comparable_types>> { + predicate_has_none, flat_set> inner; // Field is other container; convert to flat_set - template && - comparable_types>> - bool operator()(const Field& f) const { - flat_set fs(f.begin(), f.end()); - return (*this)(fs); + constexpr static bool valid = true; + bool operator()(const FieldContainer& f, const flat_set& a) const { + flat_set fs(f.begin(), f.end()); + return inner(fs, a); } +}; +template +struct predicate_has_none, Argument, void> : predicate_has_all { // Field is optional container - template && - comparable_types>> - bool operator()(const fc::optional& f) const { - FC_ASSERT(f.valid(), "Cannot compute whether no elements of null optional container are in other container"); - return (*this)(*f); + bool operator()(const fc::optional& f, const Argument& a) const { + FC_ASSERT(f.valid(), "Cannot evaluate list has-none-of list on null optional list"); + return (*this)(*f, a); } }; - -// Type alias for a predicate on a particular field type -template -using object_restriction_predicate = std::function; +////////////////////////////////////////////// END PREDICATE FUNCTORS ////////////////////////////////////////////// // Template to visit the restriction argument, resolving its type, and create the appropriate predicate functor, or // throw if the types are not compatible for the predicate assertion @@ -347,50 +351,55 @@ struct attribute_assertion> { } }; +template> +object_restriction_predicate mkpred(P p, A a, short) { + return std::bind(p, std::placeholders::_1, std::move(a)); +} +template +object_restriction_predicate mkpred(P, A, long) { + FC_THROW_EXCEPTION(fc::assert_exception, "Invalid types for predicated"); +} + +template class Predicate, typename Field, typename ArgVariant> +object_restriction_predicate make_predicate(ArgVariant arg) { + return typelist::runtime::dispatch(typename ArgVariant::list(), arg.which(), + [&arg](auto t) mutable -> object_restriction_predicate { + using Arg = typename decltype(t)::type; + return mkpred(Predicate(), std::move(arg.template get()), short()); + }); +} + template object_restriction_predicate create_predicate_function(restriction_function func, restriction_argument arg) { try { switch(func) { - case restriction::func_eq: { - restriction_argument_visitor visitor; - return typelist::apply::import_from(std::move(arg)).visit(visitor); - } - case restriction::func_ne: { - restriction_argument_visitor visitor; - return typelist::apply::import_from(std::move(arg)).visit(visitor); - } - case restriction::func_lt: { - restriction_argument_visitor visitor; - return typelist::apply::import_from(std::move(arg)).visit(visitor); - } - case restriction::func_le: { - restriction_argument_visitor visitor; - return typelist::apply::import_from(std::move(arg)).visit(visitor); - } - case restriction::func_gt: { - restriction_argument_visitor visitor; - return typelist::apply::import_from(std::move(arg)).visit(visitor); - } - case restriction::func_ge: { - restriction_argument_visitor visitor; - return typelist::apply::import_from(std::move(arg)).visit(visitor); - } - case restriction::func_in: { - restriction_argument_visitor visitor; - return typelist::apply::import_from(std::move(arg)).visit(visitor); - } - case restriction::func_not_in: { - restriction_argument_visitor visitor; - return typelist::apply::import_from(std::move(arg)).visit(visitor); - } - case restriction::func_has_all: { - restriction_argument_visitor visitor; - return typelist::apply::import_from(std::move(arg)).visit(visitor); - } - case restriction::func_has_none: { - restriction_argument_visitor visitor; - return typelist::apply::import_from(std::move(arg)).visit(visitor); - } + case restriction::func_eq: + return make_predicate(static_variant::import_from(std::move(arg))); + case restriction::func_ne: + return make_predicate(static_variant::import_from(std::move(arg))); + case restriction::func_lt: + return make_predicate(static_variant + ::import_from(std::move(arg))); + case restriction::func_le: + return make_predicate(static_variant + ::import_from(std::move(arg))); + case restriction::func_gt: + return make_predicate(static_variant + ::import_from(std::move(arg))); + case restriction::func_ge: + return make_predicate(static_variant + ::import_from(std::move(arg))); + case restriction::func_in: + return make_predicate(static_variant::import_from(std::move(arg))); + case restriction::func_not_in: + return make_predicate(static_variant + ::import_from(std::move(arg))); + case restriction::func_has_all: + return make_predicate(static_variant + ::import_from(std::move(arg))); + case restriction::func_has_none: + return make_predicate(static_variant + ::import_from(std::move(arg))); case restriction::func_attr: FC_ASSERT(arg.which() == restriction_argument::tag>::value, "Argument type for attribute assertion must be restriction list"); @@ -398,7 +407,7 @@ object_restriction_predicate create_predicate_function(restriction_functi default: FC_THROW_EXCEPTION(fc::assert_exception, "Invalid function type on restriction"); } - } FC_CAPTURE_AND_RETHROW( (func) ) + } FC_CAPTURE_AND_RETHROW( (fc::get_typename::name())(func)(arg) ) } /** @@ -495,100 +504,104 @@ restriction_predicate_function get_restriction_predicate(const vector(nullptr)); } -// Now for some compile-time tests of the metafunctions we use in here... -#ifndef NDEBUG +// These are some compile-time tests of the metafunctions and predicate type analysis. They are turned off to make +// building faster; they only need to be enabled when making changes in this file +#if false static_assert(!is_container, ""); static_assert(is_container>, ""); - -static_assert(predicate_eq(10)(20) == false, ""); -static_assert(predicate_eq(10)(5) == false, ""); -static_assert(predicate_eq(10)(10) == true, ""); - -static_assert(predicate_eq::can_evaluate == false, ""); -static_assert(predicate_eq::can_evaluate == false, ""); -static_assert(predicate_eq::can_evaluate == false, ""); -static_assert(predicate_eq::can_evaluate == true, ""); -static_assert(predicate_eq::can_evaluate == true, ""); -static_assert(predicate_eq::can_evaluate> == true, ""); -static_assert(predicate_eq::can_evaluate> == true, ""); -static_assert(predicate_eq::can_evaluate == true, ""); -static_assert(predicate_eq::can_evaluate == false, ""); -static_assert(predicate_eq::can_evaluate == false, ""); -static_assert(predicate_eq::can_evaluate> == true, ""); -static_assert(predicate_eq::can_evaluate> == true, ""); -static_assert(predicate_eq::can_evaluate> == true, ""); -static_assert(predicate_eq>::can_evaluate> == true, ""); -//static_assert(predicate_eq>::can_evaluate> == true, ""); -static_assert(predicate_eq::can_evaluate> == false, ""); -static_assert(predicate_eq::can_evaluate == true, ""); -static_assert(predicate_ne::can_evaluate == false, ""); -static_assert(predicate_ne::can_evaluate == false, ""); -static_assert(predicate_ne::can_evaluate == true, ""); -static_assert(predicate_ne::can_evaluate == true, ""); -static_assert(predicate_ne::can_evaluate> == true, ""); -static_assert(predicate_ne::can_evaluate> == true, ""); -static_assert(predicate_ne::can_evaluate == true, ""); -static_assert(predicate_ne::can_evaluate == false, ""); -static_assert(predicate_ne::can_evaluate == false, ""); -static_assert(predicate_ne::can_evaluate> == true, ""); -static_assert(predicate_ne::can_evaluate> == true, ""); -static_assert(predicate_ne::can_evaluate> == true, ""); -static_assert(predicate_ne::can_evaluate == true, ""); - -static_assert(predicate_compare(10)(20) == 1, ""); -static_assert(predicate_compare(10)(5) == -1, ""); -static_assert(predicate_compare(10)(10) == 0, ""); -static_assert(predicate_lt(10)(20) == false, ""); -static_assert(predicate_lt(10)(5) == true, ""); -static_assert(predicate_lt(10)(10) == false, ""); -static_assert(predicate_le(10)(20) == false, ""); -static_assert(predicate_le(10)(5) == true, ""); -static_assert(predicate_le(10)(10) == true, ""); -static_assert(predicate_gt(10)(20) == true, ""); -static_assert(predicate_gt(10)(5) == false, ""); -static_assert(predicate_gt(10)(10) == false, ""); -static_assert(predicate_ge(10)(20) == true, ""); -static_assert(predicate_ge(10)(5) == false, ""); -static_assert(predicate_ge(10)(10) == true, ""); - -static_assert(predicate_compare::can_evaluate == true, ""); -static_assert(predicate_compare::can_evaluate == true, ""); -static_assert(predicate_compare::can_evaluate == true, ""); -static_assert(predicate_compare::can_evaluate> == false, ""); -static_assert(predicate_compare::can_evaluate> == true, ""); -static_assert(predicate_compare::can_evaluate> == true, ""); -static_assert(predicate_compare::can_evaluate> == true, ""); -static_assert(predicate_lt::can_evaluate == true, ""); -static_assert(predicate_lt::can_evaluate == true, ""); -static_assert(predicate_lt::can_evaluate == true, ""); -static_assert(predicate_lt::can_evaluate> == false, ""); -static_assert(predicate_lt::can_evaluate> == true, ""); -static_assert(predicate_lt::can_evaluate> == true, ""); -static_assert(predicate_lt::can_evaluate> == true, ""); - -static_assert(predicate_in::can_evaluate == false, ""); -static_assert(predicate_in>::can_evaluate == false, ""); -static_assert(predicate_in>::can_evaluate == true, ""); -static_assert(predicate_in>::can_evaluate> == false, ""); -static_assert(predicate_in>::can_evaluate> == true, ""); -static_assert(predicate_not_in::can_evaluate == false, ""); -static_assert(predicate_not_in>::can_evaluate == false, ""); -static_assert(predicate_not_in>::can_evaluate == true, ""); -static_assert(predicate_not_in>::can_evaluate> == false, ""); -static_assert(predicate_not_in>::can_evaluate> == true, ""); - -static_assert(predicate_has_all::can_evaluate == false, ""); -static_assert(predicate_has_all>::can_evaluate == false, ""); -static_assert(predicate_has_all>::can_evaluate == false, ""); -static_assert(predicate_has_all>::can_evaluate> == true, ""); -static_assert(predicate_has_all>::can_evaluate> == false, ""); -static_assert(predicate_has_all>::can_evaluate>> == true, ""); -static_assert(predicate_has_none::can_evaluate == false, ""); -static_assert(predicate_has_none>::can_evaluate == false, ""); -static_assert(predicate_has_none>::can_evaluate == false, ""); -static_assert(predicate_has_none>::can_evaluate> == true, ""); -static_assert(predicate_has_none>::can_evaluate> == false, ""); -static_assert(predicate_has_none>::can_evaluate>> == true, ""); +static_assert(is_container>, ""); +static_assert(is_container, ""); +static_assert(is_flat_set>, ""); +static_assert(!is_flat_set>, ""); + +static_assert(predicate_eq()(10, 20) == false, ""); +static_assert(predicate_eq()(10, 5) == false, ""); +static_assert(predicate_eq()(10, 10) == true, ""); + +static_assert(predicate_eq::valid == false, ""); +static_assert(predicate_eq::valid == false, ""); +static_assert(predicate_eq::valid == false, ""); +static_assert(predicate_eq::valid == true, ""); +static_assert(predicate_eq::valid == true, ""); +static_assert(predicate_eq, int64_t>::valid == true, ""); +static_assert(predicate_eq, int64_t>::valid == true, ""); +static_assert(predicate_eq::valid == true, ""); +static_assert(predicate_eq::valid == false, ""); +static_assert(predicate_eq::valid == false, ""); +static_assert(predicate_eq, int64_t>::valid == true, ""); +static_assert(predicate_eq, int64_t>::valid == true, ""); +static_assert(predicate_eq, void_t>::valid == true, ""); +static_assert(predicate_eq, flat_set>::valid == true, ""); +static_assert(predicate_eq, string>::valid == false, ""); +static_assert(predicate_eq::valid == true, ""); +static_assert(predicate_ne::valid == false, ""); +static_assert(predicate_ne::valid == false, ""); +static_assert(predicate_ne::valid == true, ""); +static_assert(predicate_ne::valid == true, ""); +static_assert(predicate_ne, int64_t>::valid == true, ""); +static_assert(predicate_ne, int64_t>::valid == true, ""); +static_assert(predicate_ne::valid == true, ""); +static_assert(predicate_ne::valid == false, ""); +static_assert(predicate_ne::valid == false, ""); +static_assert(predicate_ne, int64_t>::valid == true, ""); +static_assert(predicate_ne, int64_t>::valid == true, ""); +static_assert(predicate_ne, void_t>::valid == true, ""); +static_assert(predicate_ne::valid == true, ""); + +static_assert(predicate_compare()(20, 10) == 1, ""); +static_assert(predicate_compare()(5, 10) == -1, ""); +static_assert(predicate_compare()(10, 10) == 0, ""); +static_assert(predicate_lt()(20, 10) == false, ""); +static_assert(predicate_lt()(5, 10) == true, ""); +static_assert(predicate_lt()(10, 10) == false, ""); +static_assert(predicate_le()(20, 10) == false, ""); +static_assert(predicate_le()(5, 10) == true, ""); +static_assert(predicate_le()(10, 10) == true, ""); +static_assert(predicate_gt()(20, 10) == true, ""); +static_assert(predicate_gt()(5, 10) == false, ""); +static_assert(predicate_gt()(10, 10) == false, ""); +static_assert(predicate_ge()(20, 10) == true, ""); +static_assert(predicate_ge()(5, 10) == false, ""); +static_assert(predicate_ge()(10, 10) == true, ""); + +static_assert(predicate_compare::valid == true, ""); +static_assert(predicate_compare::valid == true, ""); +static_assert(predicate_compare::valid == true, ""); +static_assert(predicate_compare, int64_t>::valid == false, ""); +static_assert(predicate_compare, int64_t>::valid == true, ""); +static_assert(predicate_compare, int64_t>::valid == true, ""); +static_assert(predicate_compare, string>::valid == true, ""); +static_assert(predicate_lt::valid == true, ""); +static_assert(predicate_lt::valid == true, ""); +static_assert(predicate_lt::valid == true, ""); +static_assert(predicate_lt, int64_t>::valid == false, ""); +static_assert(predicate_lt, int64_t>::valid == true, ""); +static_assert(predicate_lt, int64_t>::valid == true, ""); +static_assert(predicate_lt, string>::valid == true, ""); + +static_assert(predicate_in::valid == false, ""); +static_assert(predicate_in>::valid == false, ""); +static_assert(predicate_in>::valid == true, ""); +static_assert(predicate_in, flat_set>::valid == false, ""); +static_assert(predicate_in, flat_set>::valid == true, ""); +static_assert(predicate_not_in::valid == false, ""); +static_assert(predicate_not_in>::valid == false, ""); +static_assert(predicate_not_in>::valid == true, ""); +static_assert(predicate_not_in, flat_set>::valid == false, ""); +static_assert(predicate_not_in, flat_set>::valid == true, ""); + +static_assert(predicate_has_all::valid == false, ""); +static_assert(predicate_has_all>::valid == false, ""); +static_assert(predicate_has_all>::valid == false, ""); +static_assert(predicate_has_all, flat_set>::valid == true, ""); +static_assert(predicate_has_all, flat_set>::valid == false, ""); +static_assert(predicate_has_all>, flat_set>::valid == true, ""); +static_assert(predicate_has_none::valid == false, ""); +static_assert(predicate_has_none>::valid == false, ""); +static_assert(predicate_has_none>::valid == false, ""); +static_assert(predicate_has_none, flat_set>::valid == true, ""); +static_assert(predicate_has_none, flat_set>::valid == false, ""); +static_assert(predicate_has_none>, flat_set>::valid == true, ""); #endif From ef87594ada384ce9168b72b258f72274b74ae1ce Mon Sep 17 00:00:00 2001 From: Nathan Hourt Date: Sun, 4 Aug 2019 22:31:25 -0500 Subject: [PATCH 12/41] BSIP 40: Reduce build RAM usage Reduce peak RAM usage during build by splitting the build up over several different .cpp files, each of which should compile in under 3 minutes and 5GB or less RAM. --- libraries/protocol/CMakeLists.txt | 9 +- .../protocol/custom_authorities/list_1.cpp | 41 +++++ .../protocol/custom_authorities/list_2.cpp | 41 +++++ .../protocol/custom_authorities/list_3.cpp | 41 +++++ .../protocol/custom_authorities/list_4.cpp | 41 +++++ .../protocol/custom_authorities/list_5.cpp | 41 +++++ .../protocol/custom_authorities/list_6.cpp | 41 +++++ .../protocol/custom_authorities/list_7.cpp | 41 +++++ .../restriction_predicate.cpp | 161 ++++++++++++++++++ .../restriction_predicate.hxx} | 154 +++-------------- .../protocol/restriction_predicate.hpp | 4 +- 11 files changed, 479 insertions(+), 136 deletions(-) create mode 100644 libraries/protocol/custom_authorities/list_1.cpp create mode 100644 libraries/protocol/custom_authorities/list_2.cpp create mode 100644 libraries/protocol/custom_authorities/list_3.cpp create mode 100644 libraries/protocol/custom_authorities/list_4.cpp create mode 100644 libraries/protocol/custom_authorities/list_5.cpp create mode 100644 libraries/protocol/custom_authorities/list_6.cpp create mode 100644 libraries/protocol/custom_authorities/list_7.cpp create mode 100644 libraries/protocol/custom_authorities/restriction_predicate.cpp rename libraries/protocol/{restriction_predicate.cpp => custom_authorities/restriction_predicate.hxx} (77%) diff --git a/libraries/protocol/CMakeLists.txt b/libraries/protocol/CMakeLists.txt index 21955919cb..3785a8d992 100644 --- a/libraries/protocol/CMakeLists.txt +++ b/libraries/protocol/CMakeLists.txt @@ -17,7 +17,14 @@ list(APPEND SOURCES account.cpp authority.cpp special_authority.cpp custom_authority.cpp - restriction_predicate.cpp + custom_authorities/restriction_predicate.cpp + custom_authorities/list_1.cpp + custom_authorities/list_2.cpp + custom_authorities/list_3.cpp + custom_authorities/list_4.cpp + custom_authorities/list_5.cpp + custom_authorities/list_6.cpp + custom_authorities/list_7.cpp committee_member.cpp custom.cpp market.cpp diff --git a/libraries/protocol/custom_authorities/list_1.cpp b/libraries/protocol/custom_authorities/list_1.cpp new file mode 100644 index 0000000000..2093dafdeb --- /dev/null +++ b/libraries/protocol/custom_authorities/list_1.cpp @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2019 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 "restriction_predicate.hxx" + +namespace graphene { namespace protocol { + +using result_type = object_restriction_predicate; + +result_type get_restriction_predicate_list_1(size_t idx, vector rs) { + return typelist::runtime::dispatch(operation_list_1::list(), idx, [&rs] (auto t) -> result_type { + using Op = typename decltype(t)::type; + return [p=restrictions_to_predicate(std::move(rs), true)] (const operation& op) { + FC_ASSERT(op.which() == operation::tag::value, + "Supplied operation is incorrect type for restriction predicate"); + return p(op.get()); + }; + }); +} +} } diff --git a/libraries/protocol/custom_authorities/list_2.cpp b/libraries/protocol/custom_authorities/list_2.cpp new file mode 100644 index 0000000000..a1027358ba --- /dev/null +++ b/libraries/protocol/custom_authorities/list_2.cpp @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2019 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 "restriction_predicate.hxx" + +namespace graphene { namespace protocol { + +using result_type = object_restriction_predicate; + +result_type get_restriction_predicate_list_2(size_t idx, vector rs) { + return typelist::runtime::dispatch(operation_list_2::list(), idx, [&rs] (auto t) -> result_type { + using Op = typename decltype(t)::type; + return [p=restrictions_to_predicate(std::move(rs), true)] (const operation& op) { + FC_ASSERT(op.which() == operation::tag::value, + "Supplied operation is incorrect type for restriction predicate"); + return p(op.get()); + }; + }); +} +} } diff --git a/libraries/protocol/custom_authorities/list_3.cpp b/libraries/protocol/custom_authorities/list_3.cpp new file mode 100644 index 0000000000..81f5504029 --- /dev/null +++ b/libraries/protocol/custom_authorities/list_3.cpp @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2019 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 "restriction_predicate.hxx" + +namespace graphene { namespace protocol { + +using result_type = object_restriction_predicate; + +result_type get_restriction_predicate_list_3(size_t idx, vector rs) { + return typelist::runtime::dispatch(operation_list_3::list(), idx, [&rs] (auto t) -> result_type { + using Op = typename decltype(t)::type; + return [p=restrictions_to_predicate(std::move(rs), true)] (const operation& op) { + FC_ASSERT(op.which() == operation::tag::value, + "Supplied operation is incorrect type for restriction predicate"); + return p(op.get()); + }; + }); +} +} } diff --git a/libraries/protocol/custom_authorities/list_4.cpp b/libraries/protocol/custom_authorities/list_4.cpp new file mode 100644 index 0000000000..5ec0f63638 --- /dev/null +++ b/libraries/protocol/custom_authorities/list_4.cpp @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2019 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 "restriction_predicate.hxx" + +namespace graphene { namespace protocol { + +using result_type = object_restriction_predicate; + +result_type get_restriction_predicate_list_4(size_t idx, vector rs) { + return typelist::runtime::dispatch(operation_list_4::list(), idx, [&rs] (auto t) -> result_type { + using Op = typename decltype(t)::type; + return [p=restrictions_to_predicate(std::move(rs), true)] (const operation& op) { + FC_ASSERT(op.which() == operation::tag::value, + "Supplied operation is incorrect type for restriction predicate"); + return p(op.get()); + }; + }); +} +} } diff --git a/libraries/protocol/custom_authorities/list_5.cpp b/libraries/protocol/custom_authorities/list_5.cpp new file mode 100644 index 0000000000..72734400b1 --- /dev/null +++ b/libraries/protocol/custom_authorities/list_5.cpp @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2019 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 "restriction_predicate.hxx" + +namespace graphene { namespace protocol { + +using result_type = object_restriction_predicate; + +result_type get_restriction_predicate_list_5(size_t idx, vector rs) { + return typelist::runtime::dispatch(operation_list_5::list(), idx, [&rs] (auto t) -> result_type { + using Op = typename decltype(t)::type; + return [p=restrictions_to_predicate(std::move(rs), true)] (const operation& op) { + FC_ASSERT(op.which() == operation::tag::value, + "Supplied operation is incorrect type for restriction predicate"); + return p(op.get()); + }; + }); +} +} } diff --git a/libraries/protocol/custom_authorities/list_6.cpp b/libraries/protocol/custom_authorities/list_6.cpp new file mode 100644 index 0000000000..cbbc351140 --- /dev/null +++ b/libraries/protocol/custom_authorities/list_6.cpp @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2019 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 "restriction_predicate.hxx" + +namespace graphene { namespace protocol { + +using result_type = object_restriction_predicate; + +result_type get_restriction_predicate_list_6(size_t idx, vector rs) { + return typelist::runtime::dispatch(operation_list_6::list(), idx, [&rs] (auto t) -> result_type { + using Op = typename decltype(t)::type; + return [p=restrictions_to_predicate(std::move(rs), true)] (const operation& op) { + FC_ASSERT(op.which() == operation::tag::value, + "Supplied operation is incorrect type for restriction predicate"); + return p(op.get()); + }; + }); +} +} } diff --git a/libraries/protocol/custom_authorities/list_7.cpp b/libraries/protocol/custom_authorities/list_7.cpp new file mode 100644 index 0000000000..21bb50f41b --- /dev/null +++ b/libraries/protocol/custom_authorities/list_7.cpp @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2019 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 "restriction_predicate.hxx" + +namespace graphene { namespace protocol { + +using result_type = object_restriction_predicate; + +result_type get_restriction_predicate_list_7(size_t idx, vector rs) { + return typelist::runtime::dispatch(operation_list_7::list(), idx, [&rs] (auto t) -> result_type { + using Op = typename decltype(t)::type; + return [p=restrictions_to_predicate(std::move(rs), true)] (const operation& op) { + FC_ASSERT(op.which() == operation::tag::value, + "Supplied operation is incorrect type for restriction predicate"); + return p(op.get()); + }; + }); +} +} } diff --git a/libraries/protocol/custom_authorities/restriction_predicate.cpp b/libraries/protocol/custom_authorities/restriction_predicate.cpp new file mode 100644 index 0000000000..2465422188 --- /dev/null +++ b/libraries/protocol/custom_authorities/restriction_predicate.cpp @@ -0,0 +1,161 @@ +/* + * Copyright (c) 2019 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 "restriction_predicate.hxx" + +namespace graphene { namespace protocol { + +restriction_predicate_function get_restriction_predicate(vector rs, operation::tag_type op_type) { + return typelist::runtime::dispatch(operation::list(), op_type, [&rs](auto t) -> restriction_predicate_function { + using Op = typename decltype(t)::type; + if (typelist::contains()) + return get_restriction_predicate_list_1(typelist::index_of(), std::move(rs)); + if (typelist::contains()) + return get_restriction_predicate_list_2(typelist::index_of(), std::move(rs)); + if (typelist::contains()) + return get_restriction_predicate_list_3(typelist::index_of(), std::move(rs)); + if (typelist::contains()) + return get_restriction_predicate_list_4(typelist::index_of(), std::move(rs)); + if (typelist::contains()) + return get_restriction_predicate_list_5(typelist::index_of(), std::move(rs)); + if (typelist::contains()) + return get_restriction_predicate_list_6(typelist::index_of(), std::move(rs)); + if (typelist::contains()) + return get_restriction_predicate_list_7(typelist::index_of(), std::move(rs)); + + // Compile time check that we'll never get to the exception below + static_assert(typelist::contains, Op>(), ""); + FC_THROW_EXCEPTION(fc::assert_exception, + "LOGIC ERROR: Operation type not handled by custom authorities implementation. " + "Please report this error."); + }); +} + +// These are some compile-time tests of the metafunctions and predicate type analysis. They are turned off to make +// building faster; they only need to be enabled when making changes in restriction_predicate.hxx +#if false +static_assert(!is_container, ""); +static_assert(is_container>, ""); +static_assert(is_container>, ""); +static_assert(is_container, ""); +static_assert(is_flat_set>, ""); +static_assert(!is_flat_set>, ""); + +static_assert(predicate_eq()(10, 20) == false, ""); +static_assert(predicate_eq()(10, 5) == false, ""); +static_assert(predicate_eq()(10, 10) == true, ""); + +static_assert(predicate_eq::valid == false, ""); +static_assert(predicate_eq::valid == false, ""); +static_assert(predicate_eq::valid == false, ""); +static_assert(predicate_eq::valid == true, ""); +static_assert(predicate_eq::valid == true, ""); +static_assert(predicate_eq, int64_t>::valid == true, ""); +static_assert(predicate_eq, int64_t>::valid == true, ""); +static_assert(predicate_eq::valid == true, ""); +static_assert(predicate_eq::valid == false, ""); +static_assert(predicate_eq::valid == false, ""); +static_assert(predicate_eq, int64_t>::valid == true, ""); +static_assert(predicate_eq, int64_t>::valid == true, ""); +static_assert(predicate_eq, void_t>::valid == true, ""); +static_assert(predicate_eq, flat_set>::valid == true, ""); +static_assert(predicate_eq, string>::valid == false, ""); +static_assert(predicate_eq::valid == true, ""); +static_assert(predicate_ne::valid == false, ""); +static_assert(predicate_ne::valid == false, ""); +static_assert(predicate_ne::valid == true, ""); +static_assert(predicate_ne::valid == true, ""); +static_assert(predicate_ne, int64_t>::valid == true, ""); +static_assert(predicate_ne, int64_t>::valid == true, ""); +static_assert(predicate_ne::valid == true, ""); +static_assert(predicate_ne::valid == false, ""); +static_assert(predicate_ne::valid == false, ""); +static_assert(predicate_ne, int64_t>::valid == true, ""); +static_assert(predicate_ne, int64_t>::valid == true, ""); +static_assert(predicate_ne, void_t>::valid == true, ""); +static_assert(predicate_ne::valid == true, ""); + +static_assert(predicate_compare()(20, 10) == 1, ""); +static_assert(predicate_compare()(5, 10) == -1, ""); +static_assert(predicate_compare()(10, 10) == 0, ""); +static_assert(predicate_lt()(20, 10) == false, ""); +static_assert(predicate_lt()(5, 10) == true, ""); +static_assert(predicate_lt()(10, 10) == false, ""); +static_assert(predicate_le()(20, 10) == false, ""); +static_assert(predicate_le()(5, 10) == true, ""); +static_assert(predicate_le()(10, 10) == true, ""); +static_assert(predicate_gt()(20, 10) == true, ""); +static_assert(predicate_gt()(5, 10) == false, ""); +static_assert(predicate_gt()(10, 10) == false, ""); +static_assert(predicate_ge()(20, 10) == true, ""); +static_assert(predicate_ge()(5, 10) == false, ""); +static_assert(predicate_ge()(10, 10) == true, ""); + +static_assert(predicate_compare::valid == true, ""); +static_assert(predicate_compare::valid == true, ""); +static_assert(predicate_compare::valid == true, ""); +static_assert(predicate_compare, int64_t>::valid == false, ""); +static_assert(predicate_compare, int64_t>::valid == true, ""); +static_assert(predicate_compare, int64_t>::valid == true, ""); +static_assert(predicate_compare, string>::valid == true, ""); +static_assert(predicate_lt::valid == true, ""); +static_assert(predicate_lt::valid == true, ""); +static_assert(predicate_lt::valid == true, ""); +static_assert(predicate_lt, int64_t>::valid == false, ""); +static_assert(predicate_lt, int64_t>::valid == true, ""); +static_assert(predicate_lt, int64_t>::valid == true, ""); +static_assert(predicate_lt, string>::valid == true, ""); + +static_assert(predicate_in::valid == false, ""); +static_assert(predicate_in>::valid == false, ""); +static_assert(predicate_in>::valid == true, ""); +static_assert(predicate_in, flat_set>::valid == false, ""); +static_assert(predicate_in, flat_set>::valid == true, ""); +static_assert(predicate_not_in::valid == false, ""); +static_assert(predicate_not_in>::valid == false, ""); +static_assert(predicate_not_in>::valid == true, ""); +static_assert(predicate_not_in, flat_set>::valid == false, ""); +static_assert(predicate_not_in, flat_set>::valid == true, ""); + +static_assert(predicate_has_all::valid == false, ""); +static_assert(predicate_has_all>::valid == false, ""); +static_assert(predicate_has_all>::valid == false, ""); +static_assert(predicate_has_all, flat_set>::valid == true, ""); +static_assert(predicate_has_all, flat_set>::valid == false, ""); +static_assert(predicate_has_all>, flat_set>::valid == true, ""); +static_assert(predicate_has_none::valid == false, ""); +static_assert(predicate_has_none>::valid == false, ""); +static_assert(predicate_has_none>::valid == false, ""); +static_assert(predicate_has_none, flat_set>::valid == true, ""); +static_assert(predicate_has_none, flat_set>::valid == false, ""); +static_assert(predicate_has_none>, flat_set>::valid == true, ""); + +#endif + +} } // namespace graphene::protocol diff --git a/libraries/protocol/restriction_predicate.cpp b/libraries/protocol/custom_authorities/restriction_predicate.hxx similarity index 77% rename from libraries/protocol/restriction_predicate.cpp rename to libraries/protocol/custom_authorities/restriction_predicate.hxx index f23c19019c..d5a4a6f709 100644 --- a/libraries/protocol/restriction_predicate.cpp +++ b/libraries/protocol/custom_authorities/restriction_predicate.hxx @@ -22,7 +22,8 @@ * THE SOFTWARE. */ -#include +#include +#include #include @@ -41,8 +42,9 @@ template constexpr static bool is_safe = false; template constexpr static bool is_safe> = true; // Metafunction to check if type is a flat_set of any element type -template constexpr static bool is_flat_set = false; -template constexpr static bool is_flat_set> = true; +template struct is_flat_set_impl : std::false_type {}; +template struct is_flat_set_impl> : std::true_type {}; +template constexpr static bool is_flat_set = is_flat_set_impl::value; // We use our own is_integral which does not consider bools integral (to disallow comparison between bool and ints) template constexpr static bool is_integral = !std::is_same::value && @@ -474,135 +476,21 @@ object_restriction_predicate restrictions_to_predicate(vector& restrictions; - - operation_type_resolver(const vector& restrictions) : restrictions(restrictions) {} - - template - result_type operator()(const Op&) { - auto predicate = restrictions_to_predicate(restrictions, true); - return [predicate=std::move(predicate)](const operation& op) { - FC_ASSERT(op.which() == operation::tag::value, - "Supplied operation is incorrect type for restriction predicate"); - return predicate(op.get()); - }; - } -}; - -restriction_predicate_function get_restriction_predicate(const vector &r, operation::tag_type op_type) { - operation_type_resolver visitor(r); - return operation::visit(op_type, visitor, static_cast(nullptr)); -} - -// These are some compile-time tests of the metafunctions and predicate type analysis. They are turned off to make -// building faster; they only need to be enabled when making changes in this file -#if false -static_assert(!is_container, ""); -static_assert(is_container>, ""); -static_assert(is_container>, ""); -static_assert(is_container, ""); -static_assert(is_flat_set>, ""); -static_assert(!is_flat_set>, ""); - -static_assert(predicate_eq()(10, 20) == false, ""); -static_assert(predicate_eq()(10, 5) == false, ""); -static_assert(predicate_eq()(10, 10) == true, ""); - -static_assert(predicate_eq::valid == false, ""); -static_assert(predicate_eq::valid == false, ""); -static_assert(predicate_eq::valid == false, ""); -static_assert(predicate_eq::valid == true, ""); -static_assert(predicate_eq::valid == true, ""); -static_assert(predicate_eq, int64_t>::valid == true, ""); -static_assert(predicate_eq, int64_t>::valid == true, ""); -static_assert(predicate_eq::valid == true, ""); -static_assert(predicate_eq::valid == false, ""); -static_assert(predicate_eq::valid == false, ""); -static_assert(predicate_eq, int64_t>::valid == true, ""); -static_assert(predicate_eq, int64_t>::valid == true, ""); -static_assert(predicate_eq, void_t>::valid == true, ""); -static_assert(predicate_eq, flat_set>::valid == true, ""); -static_assert(predicate_eq, string>::valid == false, ""); -static_assert(predicate_eq::valid == true, ""); -static_assert(predicate_ne::valid == false, ""); -static_assert(predicate_ne::valid == false, ""); -static_assert(predicate_ne::valid == true, ""); -static_assert(predicate_ne::valid == true, ""); -static_assert(predicate_ne, int64_t>::valid == true, ""); -static_assert(predicate_ne, int64_t>::valid == true, ""); -static_assert(predicate_ne::valid == true, ""); -static_assert(predicate_ne::valid == false, ""); -static_assert(predicate_ne::valid == false, ""); -static_assert(predicate_ne, int64_t>::valid == true, ""); -static_assert(predicate_ne, int64_t>::valid == true, ""); -static_assert(predicate_ne, void_t>::valid == true, ""); -static_assert(predicate_ne::valid == true, ""); - -static_assert(predicate_compare()(20, 10) == 1, ""); -static_assert(predicate_compare()(5, 10) == -1, ""); -static_assert(predicate_compare()(10, 10) == 0, ""); -static_assert(predicate_lt()(20, 10) == false, ""); -static_assert(predicate_lt()(5, 10) == true, ""); -static_assert(predicate_lt()(10, 10) == false, ""); -static_assert(predicate_le()(20, 10) == false, ""); -static_assert(predicate_le()(5, 10) == true, ""); -static_assert(predicate_le()(10, 10) == true, ""); -static_assert(predicate_gt()(20, 10) == true, ""); -static_assert(predicate_gt()(5, 10) == false, ""); -static_assert(predicate_gt()(10, 10) == false, ""); -static_assert(predicate_ge()(20, 10) == true, ""); -static_assert(predicate_ge()(5, 10) == false, ""); -static_assert(predicate_ge()(10, 10) == true, ""); - -static_assert(predicate_compare::valid == true, ""); -static_assert(predicate_compare::valid == true, ""); -static_assert(predicate_compare::valid == true, ""); -static_assert(predicate_compare, int64_t>::valid == false, ""); -static_assert(predicate_compare, int64_t>::valid == true, ""); -static_assert(predicate_compare, int64_t>::valid == true, ""); -static_assert(predicate_compare, string>::valid == true, ""); -static_assert(predicate_lt::valid == true, ""); -static_assert(predicate_lt::valid == true, ""); -static_assert(predicate_lt::valid == true, ""); -static_assert(predicate_lt, int64_t>::valid == false, ""); -static_assert(predicate_lt, int64_t>::valid == true, ""); -static_assert(predicate_lt, int64_t>::valid == true, ""); -static_assert(predicate_lt, string>::valid == true, ""); - -static_assert(predicate_in::valid == false, ""); -static_assert(predicate_in>::valid == false, ""); -static_assert(predicate_in>::valid == true, ""); -static_assert(predicate_in, flat_set>::valid == false, ""); -static_assert(predicate_in, flat_set>::valid == true, ""); -static_assert(predicate_not_in::valid == false, ""); -static_assert(predicate_not_in>::valid == false, ""); -static_assert(predicate_not_in>::valid == true, ""); -static_assert(predicate_not_in, flat_set>::valid == false, ""); -static_assert(predicate_not_in, flat_set>::valid == true, ""); - -static_assert(predicate_has_all::valid == false, ""); -static_assert(predicate_has_all>::valid == false, ""); -static_assert(predicate_has_all>::valid == false, ""); -static_assert(predicate_has_all, flat_set>::valid == true, ""); -static_assert(predicate_has_all, flat_set>::valid == false, ""); -static_assert(predicate_has_all>, flat_set>::valid == true, ""); -static_assert(predicate_has_none::valid == false, ""); -static_assert(predicate_has_none>::valid == false, ""); -static_assert(predicate_has_none>::valid == false, ""); -static_assert(predicate_has_none, flat_set>::valid == true, ""); -static_assert(predicate_has_none, flat_set>::valid == false, ""); -static_assert(predicate_has_none>, flat_set>::valid == true, ""); - -#endif +// To make the build gentler on RAM, break the operation list into several pieces to build over several files +using operation_list_1 = static_variant>; +using operation_list_2 = static_variant>; +using operation_list_3 = static_variant>; +using operation_list_4 = static_variant>; +using operation_list_5 = static_variant>; +using operation_list_6 = static_variant>; +using operation_list_7 = static_variant>; + +object_restriction_predicate get_restriction_predicate_list_1(size_t idx, vector rs); +object_restriction_predicate get_restriction_predicate_list_2(size_t idx, vector rs); +object_restriction_predicate get_restriction_predicate_list_3(size_t idx, vector rs); +object_restriction_predicate get_restriction_predicate_list_4(size_t idx, vector rs); +object_restriction_predicate get_restriction_predicate_list_5(size_t idx, vector rs); +object_restriction_predicate get_restriction_predicate_list_6(size_t idx, vector rs); +object_restriction_predicate get_restriction_predicate_list_7(size_t idx, vector rs); } } // namespace graphene::protocol diff --git a/libraries/protocol/include/graphene/protocol/restriction_predicate.hpp b/libraries/protocol/include/graphene/protocol/restriction_predicate.hpp index 16662faef9..ade2b7dd18 100644 --- a/libraries/protocol/include/graphene/protocol/restriction_predicate.hpp +++ b/libraries/protocol/include/graphene/protocol/restriction_predicate.hpp @@ -36,10 +36,10 @@ using restriction_predicate_function = std::function; /** * @brief get_restriction_predicate Get a predicate function for the supplied restriction - * @param r The restrictions to evaluate operations against + * @param rs The restrictions to evaluate operations against * @param op_type The tag specifying which operation type the restrictions apply to * @return A predicate function which evaluates an operation to determine whether it complies with the restriction */ -restriction_predicate_function get_restriction_predicate(const vector& r, operation::tag_type op_type); +restriction_predicate_function get_restriction_predicate(vector rs, operation::tag_type op_type); } } // namespace graphene::protocol From 2e0615af0b45ca4e918366753e8a8e8b51ba9eb6 Mon Sep 17 00:00:00 2001 From: Nathan Hourt Date: Sun, 4 Aug 2019 23:43:23 -0500 Subject: [PATCH 13/41] BSIP 40: Cleanup, disable sign-compare warnings on g++ Set travis to build single-threaded and disable signed-unsigned comparison warnings on g++. If anyone has a real solution to signed comparisons, I'd love to hear it, but until then, the warnings are actually so awful that travis breaks over them. --- .travis.yml | 1 + .../chain/custom_authority_evaluator.cpp | 2 +- libraries/protocol/CMakeLists.txt | 28 ++++++++++----- .../protocol/custom_authorities/list_1.cpp | 1 - .../restriction_predicate.hxx | 35 ++++++------------- programs/build_helpers/build_protocol | 3 +- 6 files changed, 33 insertions(+), 37 deletions(-) diff --git a/.travis.yml b/.travis.yml index bff35d3e29..3b46fc9e18 100644 --- a/.travis.yml +++ b/.travis.yml @@ -30,3 +30,4 @@ jobs: script: ./programs/build_helpers/build_protocol - stage: build and test script: ./programs/build_helpers/build_and_test + diff --git a/libraries/chain/custom_authority_evaluator.cpp b/libraries/chain/custom_authority_evaluator.cpp index 9901d7f688..783abf7c88 100644 --- a/libraries/chain/custom_authority_evaluator.cpp +++ b/libraries/chain/custom_authority_evaluator.cpp @@ -45,7 +45,7 @@ void_result custom_authority_create_evaluator::do_evaluate(const custom_authorit FC_ASSERT((op.valid_to - now).to_seconds() <= config->max_custom_authority_lifetime_seconds, "Custom authority lifetime exceeds maximum limit"); - FC_ASSERT(op.operation_type.value <= config->max_operation_tag, + FC_ASSERT(op.operation_type.value <= (size_t)config->max_operation_tag, "Cannot create custom authority for operation type which is not yet active"); for (const auto& account_weight_pair : op.auth.account_auths) diff --git a/libraries/protocol/CMakeLists.txt b/libraries/protocol/CMakeLists.txt index 3785a8d992..872313c48d 100644 --- a/libraries/protocol/CMakeLists.txt +++ b/libraries/protocol/CMakeLists.txt @@ -17,14 +17,6 @@ list(APPEND SOURCES account.cpp authority.cpp special_authority.cpp custom_authority.cpp - custom_authorities/restriction_predicate.cpp - custom_authorities/list_1.cpp - custom_authorities/list_2.cpp - custom_authorities/list_3.cpp - custom_authorities/list_4.cpp - custom_authorities/list_5.cpp - custom_authorities/list_6.cpp - custom_authorities/list_7.cpp committee_member.cpp custom.cpp market.cpp @@ -38,8 +30,26 @@ list(APPEND SOURCES account.cpp htlc.cpp) +list(APPEND CUSTOM_AUTHS_FILES + custom_authorities/restriction_predicate.cpp + custom_authorities/list_1.cpp + custom_authorities/list_2.cpp + custom_authorities/list_3.cpp + custom_authorities/list_4.cpp + custom_authorities/list_5.cpp + custom_authorities/list_6.cpp + custom_authorities/list_7.cpp) + +if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") + set_source_files_properties(${CUSTOM_AUTHS_FILES} PROPERTIES COMPILE_FLAGS -Wno-sign-compare) +endif() + +add_library( graphene_protocol_custom_auths ${CUSTOM_AUTHS_FILES} ) +target_link_libraries( graphene_protocol_custom_auths fc ) +target_include_directories( graphene_protocol_custom_auths PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include" ) + add_library( graphene_protocol ${SOURCES} ${HEADERS} ) -target_link_libraries( graphene_protocol fc ) +target_link_libraries( graphene_protocol fc graphene_protocol_custom_auths ) target_include_directories( graphene_protocol PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include" ) install( TARGETS diff --git a/libraries/protocol/custom_authorities/list_1.cpp b/libraries/protocol/custom_authorities/list_1.cpp index 2093dafdeb..ba01e0ac44 100644 --- a/libraries/protocol/custom_authorities/list_1.cpp +++ b/libraries/protocol/custom_authorities/list_1.cpp @@ -25,7 +25,6 @@ #include "restriction_predicate.hxx" namespace graphene { namespace protocol { - using result_type = object_restriction_predicate; result_type get_restriction_predicate_list_1(size_t idx, vector rs) { diff --git a/libraries/protocol/custom_authorities/restriction_predicate.hxx b/libraries/protocol/custom_authorities/restriction_predicate.hxx index d5a4a6f709..219d9365c7 100644 --- a/libraries/protocol/custom_authorities/restriction_predicate.hxx +++ b/libraries/protocol/custom_authorities/restriction_predicate.hxx @@ -102,10 +102,12 @@ using object_restriction_predicate = std::function; // predicate which returns true if any branch of the OR passes // - create_predicate_function() -- switches on restriction type to determine which predicate template to use // going forward -// - restriction_argument_visitor -- Determines what type the restriction argument is and creates a -// predicate functor for that type +// - make_predicate -- Determines what type the restriction argument is and creates +// a predicate functor for that type // - attribute_assertion -- If the restriction is an attribute assertion, instead of using the // restriction_argument_visitor, we recurse into restrictions_to_predicate with the current Field as the Object +// - embed_argument() -- Embeds the argument into the predicate if it is a valid type +// for the predicate, and throws otherwise. // - predicate_xyz -- These are functors implementing the various predicate function types ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -306,26 +308,6 @@ struct predicate_has_none, Argument, void> : predicat }; ////////////////////////////////////////////// END PREDICATE FUNCTORS ////////////////////////////////////////////// -// Template to visit the restriction argument, resolving its type, and create the appropriate predicate functor, or -// throw if the types are not compatible for the predicate assertion -template class Predicate, typename Field> -struct restriction_argument_visitor { - using result_type = object_restriction_predicate; - - template::template can_evaluate_helper::value>> - result_type make_predicate(const Argument& a, short) { - return Predicate(a); - } - template - result_type make_predicate(const Argument&, long) { - FC_THROW_EXCEPTION(fc::assert_exception, "Invalid argument types for predicate: ${Field}, ${Argument}", - ("Field", fc::get_typename::name())("Argument", fc::get_typename::name())); - } - template - result_type operator()(const Argument& a) { return make_predicate(a, short()); } -}; - // Forward declaration of restrictions_to_predicate, because attribute assertions and logical ORs recurse into it template object_restriction_predicate restrictions_to_predicate(vector, bool); @@ -353,21 +335,23 @@ struct attribute_assertion> { } }; +// Embed the argument into the predicate functor template> -object_restriction_predicate mkpred(P p, A a, short) { +object_restriction_predicate embed_argument(P p, A a, short) { return std::bind(p, std::placeholders::_1, std::move(a)); } template -object_restriction_predicate mkpred(P, A, long) { +object_restriction_predicate embed_argument(P, A, long) { FC_THROW_EXCEPTION(fc::assert_exception, "Invalid types for predicated"); } +// Resolve the argument type and make a predicate for it template class Predicate, typename Field, typename ArgVariant> object_restriction_predicate make_predicate(ArgVariant arg) { return typelist::runtime::dispatch(typename ArgVariant::list(), arg.which(), [&arg](auto t) mutable -> object_restriction_predicate { using Arg = typename decltype(t)::type; - return mkpred(Predicate(), std::move(arg.template get()), short()); + return embed_argument(Predicate(), std::move(arg.template get()), short()); }); } @@ -477,6 +461,7 @@ object_restriction_predicate restrictions_to_predicate(vector>; using operation_list_2 = static_variant>; using operation_list_3 = static_variant>; diff --git a/programs/build_helpers/build_protocol b/programs/build_helpers/build_protocol index 9eb783cf37..993c45adb3 100755 --- a/programs/build_helpers/build_protocol +++ b/programs/build_helpers/build_protocol @@ -6,6 +6,7 @@ ccache -s programs/build_helpers/buildstep Prepare 1 "sed -i '/tests/d' libraries/fc/CMakeLists.txt" programs/build_helpers/buildstep cmake 5 "cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS=--coverage -DCMAKE_CXX_FLAGS=--coverage -DBoost_USE_STATIC_LIBS=OFF -DCMAKE_CXX_OUTPUT_EXTENSION_REPLACE=ON ." programs/build_helpers/buildstep make.fc 200 "make -j 2 fc" -programs/build_helpers/buildstep make.custom_auths 1000 "make -j 2 graphene_protocol" +programs/build_helpers/buildstep make.custom_auths 1300 "make -j 1 graphene_protocol_custom_auths" +programs/build_helpers/buildstep make.protocol 500 "make -j 2 graphene_protocol" programs/build_helpers/buildstep end 0 ccache -s From 5f49a94c6abeaaa7f6d33e935e94a987e063335d Mon Sep 17 00:00:00 2001 From: Nathan Hourt Date: Mon, 5 Aug 2019 16:59:03 -0500 Subject: [PATCH 14/41] Disable sonar/gcov as it is hanging --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 3b46fc9e18..bff35d3e29 100644 --- a/.travis.yml +++ b/.travis.yml @@ -30,4 +30,3 @@ jobs: script: ./programs/build_helpers/build_protocol - stage: build and test script: ./programs/build_helpers/build_and_test - From e6fef06cb656ddc3d0bd570812a7c371e9eaa1eb Mon Sep 17 00:00:00 2001 From: Nathan Hourt Date: Tue, 6 Aug 2019 20:45:54 -0500 Subject: [PATCH 15/41] BSIP 40: Assign restrictions stable IDs --- .../chain/custom_authority_evaluator.cpp | 33 +++++++++++-------- .../chain/custom_authority_object.hpp | 22 ++++++++++--- libraries/chain/small_objects.cpp | 3 +- .../graphene/protocol/custom_authority.hpp | 4 +-- tests/tests/custom_authority_tests.cpp | 2 +- 5 files changed, 43 insertions(+), 21 deletions(-) diff --git a/libraries/chain/custom_authority_evaluator.cpp b/libraries/chain/custom_authority_evaluator.cpp index 783abf7c88..95229545f4 100644 --- a/libraries/chain/custom_authority_evaluator.cpp +++ b/libraries/chain/custom_authority_evaluator.cpp @@ -71,7 +71,9 @@ object_id_type custom_authority_create_evaluator::do_apply(const custom_authorit obj.valid_to = op.valid_to; obj.operation_type = op.operation_type; obj.auth = op.auth; - obj.restrictions = op.restrictions; + std::for_each(op.restrictions.begin(), op.restrictions.end(), [&obj](const restriction& r) mutable { + obj.restrictions.insert(std::make_pair(obj.restriction_counter++, r)); + }); // Update the predicate cache obj.update_predicate_cache(); @@ -116,9 +118,18 @@ void_result custom_authority_update_evaluator::do_evaluate(const custom_authorit account_weight_pair.first(d); } - auto largest_index = *(--op.restrictions_to_remove.end()); - FC_ASSERT(largest_index < old_object->restrictions.size(), - "Index of custom authority restriction to remove is out of bounds"); + std::for_each(op.restrictions_to_remove.begin(), op.restrictions_to_remove.end(), [this](uint16_t id) { + FC_ASSERT(old_object->restrictions.count(id) == 1, "Cannot remove restriction ID ${I}: ID not found", + ("I", id)); + }); + if (!op.restrictions_to_add.empty()) { + // Sanity check + if (!old_object->restrictions.empty()) + FC_ASSERT((--old_object->restrictions.end())->first < old_object->restriction_counter, + "LOGIC ERROR: Restriction counter overlaps restrictions. Please report this error."); + FC_ASSERT(old_object->restriction_counter + op.restrictions_to_add.size() > old_object->restriction_counter, + "Unable to add restrictions: causes wraparound of restriction IDs"); + } get_restriction_predicate(op.restrictions_to_add, old_object->operation_type); return void_result(); @@ -134,16 +145,12 @@ void_result custom_authority_update_evaluator::do_apply(const custom_authority_u if (op.new_valid_to) obj.valid_to = *op.new_valid_to; if (op.new_auth) obj.auth = *op.new_auth; - // Move restrictions at indexes to be removed to the end, then truncate them. - // Note: we could use partition instead of stable_partition, which would be slightly faster, but would also - // reorder the restrictions. I opted to preserve order as a courtesy to the user, who obviously does care about - // what items are at what indexes (removed items are specified by index) - std::stable_partition(obj.restrictions.begin(), obj.restrictions.end(), [&op, index=0](const auto&) mutable { - return op.restrictions_to_remove.count(index++) == 0; + std::for_each(op.restrictions_to_remove.begin(), op.restrictions_to_remove.end(), [&obj](auto id) mutable { + obj.restrictions.erase(id); + }); + std::for_each(op.restrictions_to_add.begin(), op.restrictions_to_add.end(), [&obj](const auto& r) mutable { + obj.restrictions.insert(std::make_pair(obj.restriction_counter++, r)); }); - obj.restrictions.resize(obj.restrictions.size() - op.restrictions_to_remove.size()); - - obj.restrictions.insert(obj.restrictions.end(), op.restrictions_to_add.begin(), op.restrictions_to_add.end()); // Update the predicate cache obj.update_predicate_cache(); diff --git a/libraries/chain/include/graphene/chain/custom_authority_object.hpp b/libraries/chain/include/graphene/chain/custom_authority_object.hpp index 15b61cb9b9..ff4a64bd62 100644 --- a/libraries/chain/include/graphene/chain/custom_authority_object.hpp +++ b/libraries/chain/include/graphene/chain/custom_authority_object.hpp @@ -38,7 +38,8 @@ namespace graphene { namespace chain { */ class custom_authority_object : public abstract_object { /// Unreflected field to store a cache of the predicate function - mutable optional predicate_cache; + /// Note that this cache can be modified when the object is const! + optional predicate_cache; public: static const uint8_t space_id = protocol_ids; @@ -50,18 +51,31 @@ namespace graphene { namespace chain { time_point_sec valid_to; unsigned_int operation_type; authority auth; - vector restrictions; + flat_map restrictions; + uint16_t restriction_counter = 0; /// Check whether the custom authority is valid bool is_valid(time_point_sec now) const { return enabled && now >= valid_from && now < valid_to; } + /// Get the restrictions as a vector rather than a map + vector get_restrictions() const { + vector rs; + std::transform(restrictions.begin(), restrictions.end(), + std::back_inserter(rs), [](auto i) { return i.second; }); + return rs; + } /// Get predicate, from cache if possible, and update cache if not (modifies const object!) restriction_predicate_function get_predicate() const { - if (!predicate_cache.valid()) predicate_cache = get_restriction_predicate(restrictions, operation_type); + if (predicate_cache.valid()) + return *predicate_cache; + + const_cast(this)->update_predicate_cache(); return *predicate_cache; } /// Regenerate predicate function and update predicate cache - void update_predicate_cache() { predicate_cache = get_restriction_predicate(restrictions, operation_type); } + void update_predicate_cache() { + predicate_cache = get_restriction_predicate(get_restrictions(), operation_type); + } }; struct by_account_custom; diff --git a/libraries/chain/small_objects.cpp b/libraries/chain/small_objects.cpp index 7b4e023180..0233edc54a 100644 --- a/libraries/chain/small_objects.cpp +++ b/libraries/chain/small_objects.cpp @@ -191,7 +191,8 @@ FC_REFLECT_DERIVED_NO_TYPENAME( graphene::chain::worker_object, (graphene::db::o ) FC_REFLECT_DERIVED_NO_TYPENAME( graphene::chain::custom_authority_object, (graphene::db::object), - (account)(enabled)(valid_from)(valid_to)(operation_type)(auth)(restrictions) ) + (account)(enabled)(valid_from)(valid_to)(operation_type) + (auth)(restrictions)(restriction_counter) ) GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::chain::balance_object ) GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::chain::block_summary_object ) diff --git a/libraries/protocol/include/graphene/protocol/custom_authority.hpp b/libraries/protocol/include/graphene/protocol/custom_authority.hpp index 59d1b7845e..6802271cce 100644 --- a/libraries/protocol/include/graphene/protocol/custom_authority.hpp +++ b/libraries/protocol/include/graphene/protocol/custom_authority.hpp @@ -87,9 +87,9 @@ namespace graphene { namespace protocol { optional new_valid_to; /// Change to the authentication for the custom authority optional new_auth; - /// Set of indexes of restrictions to remove + /// Set of IDs of restrictions to remove flat_set restrictions_to_remove; - /// Vector of new restrictions; will be appended to the old list + /// Vector of new restrictions vector restrictions_to_add; extensions_type extensions; diff --git a/tests/tests/custom_authority_tests.cpp b/tests/tests/custom_authority_tests.cpp index ce67c8b716..f59a650e3a 100644 --- a/tests/tests/custom_authority_tests.cpp +++ b/tests/tests/custom_authority_tests.cpp @@ -165,7 +165,7 @@ BOOST_AUTO_TEST_CASE(custom_auths) { try { // Alice publishes an update to the custom authority, making the restriction require exactly 100 PUSH_TX(db, trx); - BOOST_CHECK(auth_id(db).restrictions == uop.restrictions_to_add); + BOOST_CHECK(auth_id(db).get_restrictions() == uop.restrictions_to_add); trx.clear(); trx.operations = {top}; From fde930b080274fd0a9ef2b3528465bae4be8e206 Mon Sep 17 00:00:00 2001 From: Nathan Hourt Date: Tue, 6 Aug 2019 23:55:30 -0500 Subject: [PATCH 16/41] BSIP 40: Re-enable coverage, but exclude custom authorities code --- .travis.yml | 1 + programs/build_helpers/build_and_test | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index bff35d3e29..3b46fc9e18 100644 --- a/.travis.yml +++ b/.travis.yml @@ -30,3 +30,4 @@ jobs: script: ./programs/build_helpers/build_protocol - stage: build and test script: ./programs/build_helpers/build_and_test + diff --git a/programs/build_helpers/build_and_test b/programs/build_helpers/build_and_test index 062f4d1883..65d4003872 100755 --- a/programs/build_helpers/build_and_test +++ b/programs/build_helpers/build_and_test @@ -9,7 +9,7 @@ programs/build_helpers/buildstep make.everything 2400 "programs/build_helpers/ma set -o pipefail programs/build_helpers/buildstep run.chain_test 240 "libraries/fc/tests/run-parallel-tests.sh tests/chain_test" programs/build_helpers/buildstep run.cli_test 60 "libraries/fc/tests/run-parallel-tests.sh tests/cli_test" -programs/build_helpers/buildstep prepare.sonar 20 "find libraries/[acdenptuw]*/CMakeFiles/*.dir programs/[cdgjsw]*/CMakeFiles/*.dir -type d -print | while read d; do gcov -o \"\$d\" \"\${d/CMakeFiles*.dir//}\"/*.cpp; done >/dev/null; programs/build_helpers/set_sonar_branch sonar-project.properties" || true +programs/build_helpers/buildstep prepare.sonar 20 "find libraries/[acdenptuw]*/CMakeFiles/*.dir programs/[cdgjsw]*/CMakeFiles/*.dir -name graphene_protocol_custom_auths.dir -prune -o -name custom_authorities -prune -o -type d -print | while read d; do gcov -o \"\$d\" \"\${d/CMakeFiles*.dir//}\"/*.cpp; done >/dev/null; programs/build_helpers/set_sonar_branch sonar-project.properties" || true programs/build_helpers/buildstep run.sonar 1200 "which sonar-scanner && sonar-scanner" || true programs/build_helpers/buildstep end 0 ccache -s From 656699552dc203eb2d036e108312d92a32330579 Mon Sep 17 00:00:00 2001 From: Nathan Hourt Date: Thu, 8 Aug 2019 22:22:56 -0500 Subject: [PATCH 17/41] BSIP 40: Error reporting, part 1 Restriction predicates now collect data specifying exactly which restriction(s) rejected the operation. Part 2 will be rendering this data to a user-readable message. --- .../restriction_predicate.hxx | 32 +++++++++++++++---- .../protocol/restriction_predicate.hpp | 17 +++++++++- tests/tests/custom_authority_tests.cpp | 24 ++++++++++++++ 3 files changed, 65 insertions(+), 8 deletions(-) diff --git a/libraries/protocol/custom_authorities/restriction_predicate.hxx b/libraries/protocol/custom_authorities/restriction_predicate.hxx index 219d9365c7..ea3df83ae6 100644 --- a/libraries/protocol/custom_authorities/restriction_predicate.hxx +++ b/libraries/protocol/custom_authorities/restriction_predicate.hxx @@ -22,8 +22,7 @@ * THE SOFTWARE. */ -#include -#include +#include #include @@ -65,7 +64,7 @@ template constexpr static bool is_container = is_container_impl:: // Type alias for a predicate on a particular field type template -using object_restriction_predicate = std::function; +using object_restriction_predicate = std::function; ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // *** Restriction Predicate Logic *** @@ -338,7 +337,10 @@ struct attribute_assertion> { // Embed the argument into the predicate functor template> object_restriction_predicate embed_argument(P p, A a, short) { - return std::bind(p, std::placeholders::_1, std::move(a)); + return [p=std::move(p), a=std::move(a)](const F& f) { + if (p(f, a)) return predicate_result{true, {}}; + return predicate_result(); + }; } template object_restriction_predicate embed_argument(P, A, long) { @@ -435,7 +437,17 @@ object_restriction_predicate create_logical_or_predicate(vector failures; + bool success = std::any_of(predicates.begin(), predicates.end(), + [o=std::cref(obj), &failures](const auto& p) { + auto result = p(o); + if (!result) failures.push_back(std::move(result)); + return !!result; + }); + if (success) return predicate_result{true, {}}; + predicate_result r; + r.failure_path.emplace_back(std::move(failures)); + return r; }; } @@ -456,12 +468,18 @@ object_restriction_predicate restrictions_to_predicate(vector>; using operation_list_2 = static_variant>; using operation_list_3 = static_variant>; diff --git a/libraries/protocol/include/graphene/protocol/restriction_predicate.hpp b/libraries/protocol/include/graphene/protocol/restriction_predicate.hpp index ade2b7dd18..789089dbfa 100644 --- a/libraries/protocol/include/graphene/protocol/restriction_predicate.hpp +++ b/libraries/protocol/include/graphene/protocol/restriction_predicate.hpp @@ -30,9 +30,22 @@ namespace graphene { namespace protocol { +/// A type describing the result of a restriction predicate +struct predicate_result { + /// Whether or not the operation complied with the restrictions or not + bool success = false; + + /// Either the index of a restriction in a list, or a list of indexes of restrictions (for logical OR branches) + using restriction_index = static_variant>; + /// The path of indexes to the restriction(s) that failed + vector failure_path; + + operator bool() const { return success; } +}; + /// A restriction predicate is a function accepting an operation and returning a boolean which indicates whether the /// operation complies with the restrictions or not -using restriction_predicate_function = std::function; +using restriction_predicate_function = std::function; /** * @brief get_restriction_predicate Get a predicate function for the supplied restriction @@ -43,3 +56,5 @@ using restriction_predicate_function = std::function; restriction_predicate_function get_restriction_predicate(vector rs, operation::tag_type op_type); } } // namespace graphene::protocol + +FC_REFLECT_TYPENAME(graphene::protocol::predicate_result); diff --git a/tests/tests/custom_authority_tests.cpp b/tests/tests/custom_authority_tests.cpp index f59a650e3a..28f7d71c2a 100644 --- a/tests/tests/custom_authority_tests.cpp +++ b/tests/tests/custom_authority_tests.cpp @@ -56,8 +56,14 @@ BOOST_AUTO_TEST_CASE(restriction_predicate_tests) { try { auto to_index = member_index("to"); restrictions.emplace_back(to_index, FUNC(eq), account_id_type(12)); BOOST_CHECK(get_restriction_predicate(restrictions, operation::tag::value)(transfer) == false); + BOOST_CHECK(get_restriction_predicate(restrictions, operation::tag::value)(transfer) + .failure_path.size() == 1); + BOOST_CHECK(get_restriction_predicate(restrictions, operation::tag::value)(transfer) + .failure_path.front().get() == 0); transfer.to = account_id_type(12); BOOST_CHECK(get_restriction_predicate(restrictions, operation::tag::value)(transfer) == true); + BOOST_CHECK(get_restriction_predicate(restrictions, operation::tag::value)(transfer) + .failure_path.size() == 0); restrictions.front() = restriction(fc::typelist::length::native_members>(), FUNC(eq), account_id_type(12)); @@ -72,14 +78,28 @@ BOOST_AUTO_TEST_CASE(restriction_predicate_tests) { try { restrictions.front() = restriction(fee_index, FUNC(attr), vector{restriction(asset_id_index, FUNC(eq), asset_id_type(0))}); BOOST_CHECK(get_restriction_predicate(restrictions, operation::tag::value)(transfer) == true); + BOOST_CHECK(get_restriction_predicate(restrictions, operation::tag::value)(transfer) + .failure_path.size() == 0); restrictions.front().argument.get>().front().argument = asset_id_type(1); BOOST_CHECK(get_restriction_predicate(restrictions, operation::tag::value)(transfer) == false); + BOOST_CHECK(get_restriction_predicate(restrictions, operation::tag::value)(transfer) + .failure_path.size() == 2); + BOOST_CHECK(get_restriction_predicate(restrictions, operation::tag::value)(transfer) + .failure_path.front().get() == 0); + BOOST_CHECK(get_restriction_predicate(restrictions, operation::tag::value)(transfer) + .failure_path[1].get() == 0); restrictions.emplace_back(to_index, FUNC(eq), account_id_type(12)); transfer.to = account_id_type(12); transfer.fee.asset_id = asset_id_type(1); BOOST_CHECK(get_restriction_predicate(restrictions, operation::tag::value)(transfer) == true); + BOOST_CHECK(get_restriction_predicate(restrictions, operation::tag::value)(transfer) + .failure_path.size() == 0); transfer.to = account_id_type(10); BOOST_CHECK(get_restriction_predicate(restrictions, operation::tag::value)(transfer) == false); + BOOST_CHECK(get_restriction_predicate(restrictions, operation::tag::value)(transfer) + .failure_path.size() == 1); + BOOST_CHECK(get_restriction_predicate(restrictions, operation::tag::value)(transfer) + .failure_path.front().get() == 1); account_update_operation update; restrictions.clear(); @@ -90,8 +110,12 @@ BOOST_AUTO_TEST_CASE(restriction_predicate_tests) { try { auto predicate = get_restriction_predicate(restrictions, operation::tag::value); BOOST_CHECK_THROW(predicate(transfer), fc::assert_exception); BOOST_CHECK(predicate(update) == true); + BOOST_CHECK(predicate(update).failure_path.size() == 0); update.extensions.value.owner_special_authority = special_authority(); BOOST_CHECK(predicate(update) == false); + BOOST_CHECK(predicate(update).failure_path.size() == 2); + BOOST_CHECK(predicate(update).failure_path.front().get() == 0); + BOOST_CHECK(predicate(update).failure_path[1].get() == 0); restrictions.front().argument.get>().front().restriction_type = FUNC(ne); predicate = get_restriction_predicate(restrictions, operation::tag::value); BOOST_CHECK(predicate(update) == true); From 58e7ae569376b3d7389118dc28586acaaafe35db Mon Sep 17 00:00:00 2001 From: Nathan Hourt Date: Thu, 8 Aug 2019 22:40:43 -0500 Subject: [PATCH 18/41] BSIP 40: Make Travis use multi-stage build The multi-stage build makes builds more reliable by building only the protocol in the first stage, which reliably passes in time, caching those binaries, and then building the rest and running tests in the second stage, which uses the cache to complete in time as well. --- .travis.yml | 1 - programs/build_helpers/build_protocol | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 3b46fc9e18..bff35d3e29 100644 --- a/.travis.yml +++ b/.travis.yml @@ -30,4 +30,3 @@ jobs: script: ./programs/build_helpers/build_protocol - stage: build and test script: ./programs/build_helpers/build_and_test - diff --git a/programs/build_helpers/build_protocol b/programs/build_helpers/build_protocol index 993c45adb3..0daec72403 100755 --- a/programs/build_helpers/build_protocol +++ b/programs/build_helpers/build_protocol @@ -7,6 +7,6 @@ programs/build_helpers/buildstep Prepare 1 "sed -i '/tests/d' libraries/fc/CMake programs/build_helpers/buildstep cmake 5 "cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS=--coverage -DCMAKE_CXX_FLAGS=--coverage -DBoost_USE_STATIC_LIBS=OFF -DCMAKE_CXX_OUTPUT_EXTENSION_REPLACE=ON ." programs/build_helpers/buildstep make.fc 200 "make -j 2 fc" programs/build_helpers/buildstep make.custom_auths 1300 "make -j 1 graphene_protocol_custom_auths" -programs/build_helpers/buildstep make.protocol 500 "make -j 2 graphene_protocol" +programs/build_helpers/buildstep make.protocol 400 "make -j 2 graphene_protocol" programs/build_helpers/buildstep end 0 ccache -s From 0ff414fd9037c6d9fac18513b2ac054997d4524a Mon Sep 17 00:00:00 2001 From: Nathan Hourt Date: Fri, 9 Aug 2019 14:27:47 -0500 Subject: [PATCH 19/41] BSIP 40: [TEMPORARY] Workaround Travis failures --- programs/build_helpers/build_and_test | 2 +- programs/build_helpers/build_protocol | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/programs/build_helpers/build_and_test b/programs/build_helpers/build_and_test index 65d4003872..5768a4d69a 100755 --- a/programs/build_helpers/build_and_test +++ b/programs/build_helpers/build_and_test @@ -4,7 +4,7 @@ set -e programs/build_helpers/buildstep -s 3500 ccache -s programs/build_helpers/buildstep Prepare 1 "sed -i '/tests/d' libraries/fc/CMakeLists.txt" -programs/build_helpers/buildstep cmake 5 "cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS=--coverage -DCMAKE_CXX_FLAGS=--coverage -DBoost_USE_STATIC_LIBS=OFF -DCMAKE_CXX_OUTPUT_EXTENSION_REPLACE=ON ." +programs/build_helpers/buildstep cmake 5 "cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS=--coverage -DCMAKE_CXX_FLAGS=--coverage -DCMAKE_CXX_FLAGS_DEBUG=-DTRAVIS_BUILD -DBoost_USE_STATIC_LIBS=OFF -DCMAKE_CXX_OUTPUT_EXTENSION_REPLACE=ON ." programs/build_helpers/buildstep make.everything 2400 "programs/build_helpers/make_with_sonar bw-output -j 2 witness_node chain_test cli_test" set -o pipefail programs/build_helpers/buildstep run.chain_test 240 "libraries/fc/tests/run-parallel-tests.sh tests/chain_test" diff --git a/programs/build_helpers/build_protocol b/programs/build_helpers/build_protocol index 0daec72403..1bac5cf225 100755 --- a/programs/build_helpers/build_protocol +++ b/programs/build_helpers/build_protocol @@ -4,7 +4,7 @@ set -e programs/build_helpers/buildstep -s 3500 ccache -s programs/build_helpers/buildstep Prepare 1 "sed -i '/tests/d' libraries/fc/CMakeLists.txt" -programs/build_helpers/buildstep cmake 5 "cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS=--coverage -DCMAKE_CXX_FLAGS=--coverage -DBoost_USE_STATIC_LIBS=OFF -DCMAKE_CXX_OUTPUT_EXTENSION_REPLACE=ON ." +programs/build_helpers/buildstep cmake 5 "cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS=--coverage -DCMAKE_CXX_FLAGS=--coverage -DCMAKE_CXX_FLAGS_DEBUG=-DTRAVIS_BUILD -DBoost_USE_STATIC_LIBS=OFF -DCMAKE_CXX_OUTPUT_EXTENSION_REPLACE=ON ." programs/build_helpers/buildstep make.fc 200 "make -j 2 fc" programs/build_helpers/buildstep make.custom_auths 1300 "make -j 1 graphene_protocol_custom_auths" programs/build_helpers/buildstep make.protocol 400 "make -j 2 graphene_protocol" From e72835b1b6e806f270760f0230b92019e138fc46 Mon Sep 17 00:00:00 2001 From: Nathan Hourt Date: Sat, 10 Aug 2019 17:15:21 -0500 Subject: [PATCH 20/41] BSIP 40: Error reporting, part 2 Add the wrapping to report errors back to the user... such as it is Eventually, I'd like to add more logic towards generating a user- comprehensible error message, but this will suffice for now. This implementation provides enough information to construct, in conjunction with the restriction list and operation in question, a message describing the error in detail to the user... but actually assembling that message is, for now, regarded as a UI problem. --- libraries/app/database_api.cpp | 6 ++--- libraries/chain/db_block.cpp | 4 ++-- libraries/chain/db_getter.cpp | 22 +++++++++++------ .../chain/include/graphene/chain/database.hpp | 15 +++++++++++- libraries/chain/proposal_object.cpp | 4 ++-- .../protocol/restriction_predicate.hpp | 3 ++- .../include/graphene/protocol/transaction.hpp | 10 ++++---- libraries/protocol/transaction.cpp | 24 +++++++++++-------- tests/tests/authority_tests.cpp | 7 +++--- tests/tests/custom_authority_tests.cpp | 16 +++++++++++-- 10 files changed, 75 insertions(+), 36 deletions(-) diff --git a/libraries/app/database_api.cpp b/libraries/app/database_api.cpp index c9a2fcfd38..a73fe57012 100644 --- a/libraries/app/database_api.cpp +++ b/libraries/app/database_api.cpp @@ -2002,8 +2002,8 @@ bool database_api_impl::verify_authority( const signed_transaction& trx )const trx.verify_authority( _db.get_chain_id(), [this]( account_id_type id ){ return &id(_db).active; }, [this]( account_id_type id ){ return &id(_db).owner; }, - [this]( account_id_type id, const operation& op ) { - return _db.get_viable_custom_authorities(id, op); }, + [this]( account_id_type id, const operation& op, rejected_predicate_map* rejects ) { + return _db.get_viable_custom_authorities(id, op, rejects); }, allow_non_immediate_owner, _db.get_global_properties().parameters.max_authority_depth ); return true; @@ -2030,7 +2030,7 @@ bool database_api_impl::verify_account_authority( const string& account_name_or_ [this]( account_id_type id ){ return &id(_db).active; }, [this]( account_id_type id ){ return &id(_db).owner; }, // Use a no-op lookup for custom authorities; we don't want it even if one does apply for our dummy op - [](auto, auto) { return vector(); }, + [](auto, auto, auto*) { return vector(); }, true, MUST_IGNORE_CUSTOM_OP_REQD_AUTHS(_db.head_block_time()) ); } catch (fc::exception& ex) diff --git a/libraries/chain/db_block.cpp b/libraries/chain/db_block.cpp index f9fa0cec35..7361bb2ede 100644 --- a/libraries/chain/db_block.cpp +++ b/libraries/chain/db_block.cpp @@ -651,8 +651,8 @@ processed_transaction database::_apply_transaction(const signed_transaction& trx bool allow_non_immediate_owner = ( head_block_time() >= HARDFORK_CORE_584_TIME ); auto get_active = [this]( account_id_type id ) { return &id(*this).active; }; auto get_owner = [this]( account_id_type id ) { return &id(*this).owner; }; - auto get_custom = [this]( account_id_type id, const operation& op ) { - return get_viable_custom_authorities(id, op); + auto get_custom = [this]( account_id_type id, const operation& op, rejected_predicate_map* rejects ) { + return get_viable_custom_authorities(id, op, rejects); }; trx.verify_authority(chain_id, get_active, get_owner, get_custom, allow_non_immediate_owner, diff --git a/libraries/chain/db_getter.cpp b/libraries/chain/db_getter.cpp index 85d30aff36..af09f8f540 100644 --- a/libraries/chain/db_getter.cpp +++ b/libraries/chain/db_getter.cpp @@ -96,17 +96,25 @@ node_property_object& database::node_properties() return _node_property_object; } -vector database::get_viable_custom_authorities(account_id_type account, const operation &op) const +vector database::get_viable_custom_authorities( + account_id_type account, const operation &op, + rejected_predicate_map* rejected_authorities) const { const auto& index = get_index_type().indices().get(); auto range = index.equal_range(boost::make_tuple(account, unsigned_int(op.which()), true)); - vector results; - std::for_each(range.first, range.second, - [&results, &op, now=head_block_time()](const custom_authority_object& cust_auth) { - if (cust_auth.is_valid(now) && cust_auth.get_predicate()(op)) - results.insert(results.end(), cust_auth.auth); - }); + auto is_valid = [now=head_block_time()](const custom_authority_object& auth) { return auth.is_valid(now); }; + vector> valid_auths; + std::copy_if(range.first, range.second, std::back_inserter(valid_auths), is_valid); + + vector results; + for (const auto& cust_auth : valid_auths) { + auto result = cust_auth.get().get_predicate()(op); + if (result.success) + results.emplace_back(cust_auth.get().auth); + else if (rejected_authorities != nullptr) + rejected_authorities->insert(std::make_pair(cust_auth.get().id, std::move(result))); + } return results; } diff --git a/libraries/chain/include/graphene/chain/database.hpp b/libraries/chain/include/graphene/chain/database.hpp index 04882b796b..c2336bb13c 100644 --- a/libraries/chain/include/graphene/chain/database.hpp +++ b/libraries/chain/include/graphene/chain/database.hpp @@ -43,9 +43,12 @@ #include +namespace graphene { namespace protocol { struct predicate_result; } } + namespace graphene { namespace chain { using graphene::db::abstract_object; using graphene::db::object; + using rejected_predicate_map = map; class op_evaluator; class transaction_evaluation_state; class proposal_object; @@ -279,7 +282,17 @@ namespace graphene { namespace chain { node_property_object& node_properties(); - vector get_viable_custom_authorities( account_id_type account, const operation& op )const; + /** + * @brief Get a list of custom authorities which can validate the provided operation for the provided account + * @param account The account whose authority is required + * @param op The operation requring the specified account's authority + * @param rejected_authorities [Optional] A pointer to a map that should be populated with the custom + * authorities which were valid, but rejected because the operation did not comply with the restrictions + * @return A vector of authorities which can be used to authorize op in place of account + */ + vector get_viable_custom_authorities( + account_id_type account, const operation& op, + rejected_predicate_map* rejected_authorities = nullptr )const; uint32_t last_non_undoable_block_num() const; //////////////////// db_init.cpp //////////////////// diff --git a/libraries/chain/proposal_object.cpp b/libraries/chain/proposal_object.cpp index 4359826048..29e2fb65c4 100644 --- a/libraries/chain/proposal_object.cpp +++ b/libraries/chain/proposal_object.cpp @@ -39,8 +39,8 @@ bool proposal_object::is_authorized_to_execute( database& db ) const available_key_approvals, [&db]( account_id_type id ){ return &id( db ).active; }, [&db]( account_id_type id ){ return &id( db ).owner; }, - [&]( account_id_type id, const operation& op ){ - return db.get_viable_custom_authorities(id, op); }, + [&]( account_id_type id, const operation& op, rejected_predicate_map* rejects ){ + return db.get_viable_custom_authorities(id, op, rejects); }, allow_non_immediate_owner, MUST_IGNORE_CUSTOM_OP_REQD_AUTHS( db.head_block_time() ), db.get_global_properties().parameters.max_authority_depth, diff --git a/libraries/protocol/include/graphene/protocol/restriction_predicate.hpp b/libraries/protocol/include/graphene/protocol/restriction_predicate.hpp index 789089dbfa..f04756df7c 100644 --- a/libraries/protocol/include/graphene/protocol/restriction_predicate.hpp +++ b/libraries/protocol/include/graphene/protocol/restriction_predicate.hpp @@ -57,4 +57,5 @@ restriction_predicate_function get_restriction_predicate(vector rs, } } // namespace graphene::protocol -FC_REFLECT_TYPENAME(graphene::protocol::predicate_result); +FC_REFLECT_TYPENAME(graphene::protocol::predicate_result::restriction_index) +FC_REFLECT(graphene::protocol::predicate_result, (success)(failure_path)) diff --git a/libraries/protocol/include/graphene/protocol/transaction.hpp b/libraries/protocol/include/graphene/protocol/transaction.hpp index bc00638175..61b9f5cdfd 100644 --- a/libraries/protocol/include/graphene/protocol/transaction.hpp +++ b/libraries/protocol/include/graphene/protocol/transaction.hpp @@ -25,8 +25,10 @@ #include namespace graphene { namespace protocol { + struct predicate_result; - using custom_authority_lookup = std::function(account_id_type, const operation&)>; + using custom_authority_lookup = std::function(account_id_type, const operation&, + map*)>; /** * @defgroup transactions Transactions @@ -175,7 +177,7 @@ namespace graphene { namespace protocol { const chain_id_type& chain_id, const std::function& get_active, const std::function& get_owner, - custom_authority_lookup get_custom, + const custom_authority_lookup& get_custom, bool allow_non_immediate_owner, bool ignore_custom_operation_required_auths, uint32_t max_recursion = GRAPHENE_MAX_SIG_CHECK_DEPTH )const; @@ -191,7 +193,7 @@ namespace graphene { namespace protocol { const flat_set& available_keys, const std::function& get_active, const std::function& get_owner, - custom_authority_lookup get_custom, + const custom_authority_lookup& get_custom, bool allow_non_immediate_owner, bool ignore_custom_operation_required_auths, uint32_t max_recursion = GRAPHENE_MAX_SIG_CHECK_DEPTH) const; @@ -263,7 +265,7 @@ namespace graphene { namespace protocol { void verify_authority( const vector& ops, const flat_set& sigs, const std::function& get_active, const std::function& get_owner, - custom_authority_lookup get_custom, + const custom_authority_lookup& get_custom, bool allow_non_immediate_owner, bool ignore_custom_operation_required_auths, uint32_t max_recursion = GRAPHENE_MAX_SIG_CHECK_DEPTH, diff --git a/libraries/protocol/transaction.cpp b/libraries/protocol/transaction.cpp index 95874b45de..7a692f6a02 100644 --- a/libraries/protocol/transaction.cpp +++ b/libraries/protocol/transaction.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include @@ -268,14 +269,16 @@ struct sign_state void verify_authority( const vector& ops, const flat_set& sigs, const std::function& get_active, const std::function& get_owner, - custom_authority_lookup get_custom, + const custom_authority_lookup& get_custom, bool allow_non_immediate_owner, bool ignore_custom_operation_required_auths, uint32_t max_recursion_depth, bool allow_committee, const flat_set& active_aprovals, const flat_set& owner_approvals ) -{ try { +{ + map rejected_custom_auths; + try { flat_set required_active; flat_set required_owner; vector other; @@ -286,11 +289,12 @@ void verify_authority( const vector& ops, const flat_set& ops, const flat_set& signed_transaction::get_signature_keys( const chain_id_type& chain_id )const @@ -399,7 +403,7 @@ set signed_transaction::minimize_required_signatures( const flat_set& available_keys, const std::function& get_active, const std::function& get_owner, - custom_authority_lookup get_custom, + const custom_authority_lookup &get_custom, bool allow_non_immediate_owner, bool ignore_custom_operation_required_auths, uint32_t max_recursion )const @@ -459,7 +463,7 @@ const flat_set& precomputable_transaction::get_signature_keys( void signed_transaction::verify_authority( const chain_id_type& chain_id, const std::function& get_active, const std::function& get_owner, - custom_authority_lookup get_custom, + const custom_authority_lookup& get_custom, bool allow_non_immediate_owner, bool ignore_custom_operation_required_auths, uint32_t max_recursion )const diff --git a/tests/tests/authority_tests.cpp b/tests/tests/authority_tests.cpp index c9f578c14b..d4959b0856 100644 --- a/tests/tests/authority_tests.cpp +++ b/tests/tests/authority_tests.cpp @@ -44,7 +44,8 @@ using namespace graphene::chain::test; BOOST_FIXTURE_TEST_SUITE( authority_tests, database_fixture ) auto make_get_custom(const database& db) { - return [&db](account_id_type id, const operation& op) { return db.get_viable_custom_authorities(id, op); }; + return [&db](account_id_type id, const operation& op, rejected_predicate_map* rejects) { + return db.get_viable_custom_authorities(id, op, rejects); }; } BOOST_AUTO_TEST_CASE( simple_single_signature ) @@ -1328,9 +1329,7 @@ BOOST_FIXTURE_TEST_CASE( nonminimal_sig_test, database_fixture ) ) -> bool { //wdump( (tx)(available_keys) ); - auto get_custom = [&db=db](account_id_type id, const operation& op) { - return db.get_viable_custom_authorities(id, op); - }; + auto get_custom = make_get_custom(db); set result_set = tx.minimize_required_signatures(db.get_chain_id(), available_keys, get_active, get_owner, get_custom, false, false); diff --git a/tests/tests/custom_authority_tests.cpp b/tests/tests/custom_authority_tests.cpp index 28f7d71c2a..27641d1a6e 100644 --- a/tests/tests/custom_authority_tests.cpp +++ b/tests/tests/custom_authority_tests.cpp @@ -48,6 +48,18 @@ unsigned_int member_index(string name) { return index; } +template +void expect_exception_string(const string& s, Expression e) { + try{ + e(); + FC_THROW_EXCEPTION(fc::assert_exception, "Expected exception with string ${s}, but no exception thrown", + ("s", s)); + } catch (fc::exception e) { + FC_ASSERT(e.to_detail_string().find(s) != string::npos, "Did not find expected string ${s} in exception: ${e}", + ("s", s)("e", e)); + } +} + BOOST_AUTO_TEST_CASE(restriction_predicate_tests) { try { using namespace graphene::protocol; vector restrictions; @@ -175,7 +187,7 @@ BOOST_AUTO_TEST_CASE(custom_auths) { try { trx.clear_signatures(); sign(trx, bob_private_key); // If bob tries to transfer 100, it fails because the restriction is strictly less than 100 - BOOST_CHECK_THROW(PUSH_TX(db, trx), tx_missing_active_auth); + expect_exception_string("\"failure_path\":[[0,0],[0,0]]", [&] {PUSH_TX(db, trx);}); op.restrictions.front().argument.get>().front().restriction_type = restriction::func_eq; custom_authority_update_operation uop; @@ -196,7 +208,7 @@ BOOST_AUTO_TEST_CASE(custom_auths) { try { trx.expiration += 5; sign(trx, bob_private_key); // The transfer of 99 should fail now becaues the requirement is for exactly 100 - BOOST_CHECK_THROW(PUSH_TX(db, trx), tx_missing_active_auth); + expect_exception_string("\"failure_path\":[[0,0],[0,0]]", [&] {PUSH_TX(db, trx);}); trx.operations.front().get().amount.amount = 100*GRAPHENE_BLOCKCHAIN_PRECISION; trx.clear_signatures(); From 670b4741bd8f9e112223668072015c40d55d2ed7 Mon Sep 17 00:00:00 2001 From: Nathan Hourt Date: Sun, 11 Aug 2019 13:10:51 -0500 Subject: [PATCH 21/41] BSIP 40: Add hardfork_visitor Remove the committee parameter used to determine whether an operation type had been hardforked in yet, and replace it with hardfork_visitor --- .../chain/custom_authority_evaluator.cpp | 6 +- .../graphene/chain/hardfork_visitor.hpp | 83 +++++++++++++++++++ .../graphene/protocol/chain_parameters.hpp | 2 - .../include/graphene/protocol/config.hpp | 4 - 4 files changed, 86 insertions(+), 9 deletions(-) create mode 100644 libraries/chain/include/graphene/chain/hardfork_visitor.hpp diff --git a/libraries/chain/custom_authority_evaluator.cpp b/libraries/chain/custom_authority_evaluator.cpp index 95229545f4..7c3ce30de7 100644 --- a/libraries/chain/custom_authority_evaluator.cpp +++ b/libraries/chain/custom_authority_evaluator.cpp @@ -27,7 +27,7 @@ #include #include #include -#include +#include namespace graphene { namespace chain { @@ -45,8 +45,8 @@ void_result custom_authority_create_evaluator::do_evaluate(const custom_authorit FC_ASSERT((op.valid_to - now).to_seconds() <= config->max_custom_authority_lifetime_seconds, "Custom authority lifetime exceeds maximum limit"); - FC_ASSERT(op.operation_type.value <= (size_t)config->max_operation_tag, - "Cannot create custom authority for operation type which is not yet active"); + bool operation_forked_in = hardfork_visitor(now).visit((operation::tag_type)op.operation_type.value); + FC_ASSERT(operation_forked_in, "Cannot create custom authority for operation which is not valid yet"); for (const auto& account_weight_pair : op.auth.account_auths) account_weight_pair.first(d); diff --git a/libraries/chain/include/graphene/chain/hardfork_visitor.hpp b/libraries/chain/include/graphene/chain/hardfork_visitor.hpp new file mode 100644 index 0000000000..f9126c8fc6 --- /dev/null +++ b/libraries/chain/include/graphene/chain/hardfork_visitor.hpp @@ -0,0 +1,83 @@ +#pragma once +/* + * Copyright (c) 2019 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 + +namespace graphene { namespace chain { +using namespace protocol; +namespace TL { using namespace fc::typelist; } + +/** + * @brief The hardfork_visitor struct checks whether a given operation type has been hardforked in or not + * + * This visitor can be invoked in several different ways, including operation::visit, typelist::runtime::dispatch, or + * direct invocation by calling the visit() method passing an operation variant, narrow operation type, operation tag, + * or templating on the narrow operation type + */ +struct hardfork_visitor { + using result_type = bool; + using first_unforked_op = custom_authority_create_operation; + using BSIP_40_ops = TL::list; + fc::time_point_sec now; + + hardfork_visitor(fc::time_point_sec now) : now(now) {} + + /// The real visitor implementations. Future operation types get added in here. + /// @{ + template + std::enable_if_t::value < operation::tag::value, bool> + visit() { return true; } + template + std::enable_if_t(), bool> + visit() { return HARDFORK_BSIP_40_PASSED(now); } + /// @} + + /// typelist::runtime::dispatch adaptor + template + std::enable_if_t(), bool> + operator()(W) { return visit(); } + /// static_variant::visit adaptor + template + std::enable_if_t(), bool> + operator()(const Op&) { return visit(); } + /// Tag adaptor + bool visit(operation::tag_type tag) { + return TL::runtime::dispatch(operation::list(), (size_t)tag, *this); + } + /// operation adaptor + bool visit(const operation& op) { + return visit(op.which()); + } +}; + +} } // namespace graphene::chain diff --git a/libraries/protocol/include/graphene/protocol/chain_parameters.hpp b/libraries/protocol/include/graphene/protocol/chain_parameters.hpp index 549b65aca8..017abf6cf1 100644 --- a/libraries/protocol/include/graphene/protocol/chain_parameters.hpp +++ b/libraries/protocol/include/graphene/protocol/chain_parameters.hpp @@ -39,7 +39,6 @@ namespace graphene { namespace protocol { { uint32_t max_custom_authority_lifetime_seconds = GRAPHENE_DEFAULT_MAX_CUSTOM_AUTHORITY_LIFETIME_SECONDS; uint32_t max_custom_authorities_per_account = GRAPHENE_DEFAULT_MAX_CUSTOM_AUTHORITIES_PER_ACCOUNT; - int64_t max_operation_tag = GRAPHENE_DEFAULT_MAX_OPERATION_TAG; }; struct chain_parameters @@ -109,7 +108,6 @@ FC_REFLECT( graphene::protocol::htlc_options, FC_REFLECT( graphene::protocol::custom_authority_options_type, (max_custom_authority_lifetime_seconds) (max_custom_authorities_per_account) - (max_operation_tag) ) FC_REFLECT( graphene::protocol::chain_parameters::ext, diff --git a/libraries/protocol/include/graphene/protocol/config.hpp b/libraries/protocol/include/graphene/protocol/config.hpp index a73edbd4a6..3b005ee518 100644 --- a/libraries/protocol/include/graphene/protocol/config.hpp +++ b/libraries/protocol/include/graphene/protocol/config.hpp @@ -143,7 +143,3 @@ #define GRAPHENE_DEFAULT_MAX_CUSTOM_AUTHORITY_LIFETIME_SECONDS (60*60*24*365) /// Maximum number of custom authorities a particular account can set #define GRAPHENE_DEFAULT_MAX_CUSTOM_AUTHORITIES_PER_ACCOUNT 10 - -/// Maximum operation tag which has been forked in so far. Defaults to 56 because that's what it was when we first -/// began keeping track -#define GRAPHENE_DEFAULT_MAX_OPERATION_TAG 56 From de5049acc0b7e698871f7dde278640973f9a24de Mon Sep 17 00:00:00 2001 From: Nathan Hourt Date: Mon, 12 Aug 2019 13:37:14 -0500 Subject: [PATCH 22/41] BSIP 40: Fix build in MSVC 2017 Many thanks to @jmjatlanta! :) --- libraries/app/database_api.cpp | 1 + libraries/chain/proposal_object.cpp | 2 ++ libraries/protocol/CMakeLists.txt | 11 +++++++---- tests/tests/authority_tests.cpp | 2 ++ 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/libraries/app/database_api.cpp b/libraries/app/database_api.cpp index a73fe57012..5b0a43468e 100644 --- a/libraries/app/database_api.cpp +++ b/libraries/app/database_api.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include #include diff --git a/libraries/chain/proposal_object.cpp b/libraries/chain/proposal_object.cpp index 29e2fb65c4..ab6d661ad8 100644 --- a/libraries/chain/proposal_object.cpp +++ b/libraries/chain/proposal_object.cpp @@ -27,6 +27,8 @@ #include #include +#include + namespace graphene { namespace chain { bool proposal_object::is_authorized_to_execute( database& db ) const diff --git a/libraries/protocol/CMakeLists.txt b/libraries/protocol/CMakeLists.txt index 872313c48d..36dd40170f 100644 --- a/libraries/protocol/CMakeLists.txt +++ b/libraries/protocol/CMakeLists.txt @@ -40,14 +40,17 @@ list(APPEND CUSTOM_AUTHS_FILES custom_authorities/list_6.cpp custom_authorities/list_7.cpp) -if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") - set_source_files_properties(${CUSTOM_AUTHS_FILES} PROPERTIES COMPILE_FLAGS -Wno-sign-compare) -endif() - add_library( graphene_protocol_custom_auths ${CUSTOM_AUTHS_FILES} ) target_link_libraries( graphene_protocol_custom_auths fc ) target_include_directories( graphene_protocol_custom_auths PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include" ) +if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") + set_source_files_properties( ${CUSTOM_AUTHS_FILES} PROPERTIES COMPILE_FLAGS -Wno-sign-compare ) +endif() +if( MSVC ) + set_source_files_properties( ${CUSTOM_AUTHS_FILES} PROPERTIES COMPILE_FLAGS /bigobj ) +endif() + add_library( graphene_protocol ${SOURCES} ${HEADERS} ) target_link_libraries( graphene_protocol fc graphene_protocol_custom_auths ) target_include_directories( graphene_protocol PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include" ) diff --git a/tests/tests/authority_tests.cpp b/tests/tests/authority_tests.cpp index d4959b0856..8906ba0dd8 100644 --- a/tests/tests/authority_tests.cpp +++ b/tests/tests/authority_tests.cpp @@ -33,6 +33,8 @@ #include #include +#include + #include #include From d01a8d0e948b750c6ffc568d27f5cc24cef6d9e5 Mon Sep 17 00:00:00 2001 From: Nathan Hourt Date: Mon, 12 Aug 2019 15:54:34 -0500 Subject: [PATCH 23/41] BSIP 40: Use safe signed/unsigned comparisons --- libraries/protocol/CMakeLists.txt | 3 - .../restriction_predicate.hxx | 100 +++++++++++------- 2 files changed, 64 insertions(+), 39 deletions(-) diff --git a/libraries/protocol/CMakeLists.txt b/libraries/protocol/CMakeLists.txt index 36dd40170f..aa8c4293f2 100644 --- a/libraries/protocol/CMakeLists.txt +++ b/libraries/protocol/CMakeLists.txt @@ -44,9 +44,6 @@ add_library( graphene_protocol_custom_auths ${CUSTOM_AUTHS_FILES} ) target_link_libraries( graphene_protocol_custom_auths fc ) target_include_directories( graphene_protocol_custom_auths PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include" ) -if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") - set_source_files_properties( ${CUSTOM_AUTHS_FILES} PROPERTIES COMPILE_FLAGS -Wno-sign-compare ) -endif() if( MSVC ) set_source_files_properties( ${CUSTOM_AUTHS_FILES} PROPERTIES COMPILE_FLAGS /bigobj ) endif() diff --git a/libraries/protocol/custom_authorities/restriction_predicate.hxx b/libraries/protocol/custom_authorities/restriction_predicate.hxx index ea3df83ae6..bed0c1b8b8 100644 --- a/libraries/protocol/custom_authorities/restriction_predicate.hxx +++ b/libraries/protocol/custom_authorities/restriction_predicate.hxx @@ -26,6 +26,8 @@ #include +#include + namespace graphene { namespace protocol { namespace typelist = fc::typelist; using std::declval; @@ -66,45 +68,49 @@ template constexpr static bool is_container = is_container_impl:: template using object_restriction_predicate = std::function; +// Get the actual number when type might be a safe +template::value>> +const auto& to_num(const I& i) { return i; } +template +const auto& to_num(const fc::safe& i) { return i.value; } +inline auto to_num(const fc::time_point_sec& t) { return t.sec_since_epoch(); } + +namespace safenum = boost::safe_numerics::safe_compare; + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // *** Restriction Predicate Logic *** - -// This file is where the magic happens for BSIP 40, aka Custom Active Authorities. This gets fairly complicated, so -// let me break it down: we're given a restriction object and an operation type. The restriction contains one or more -// assertions that will eventually be made on operation instances, and this file creates a predicate function that -// takes such an operation instance, evaluates all of the assertions, and returns whether they all pass or not. // -// So this file works on restriction instances, but only operation *types* -- the actual operation instance won't -// show up until the predicate function this file returns is eventually called. But first, we need to dig into the -// operation/fields based on the specifications in the restrictions, make sure all the assertions are valid, then -// create a predicate that runs quickly, looking through the operation and checking all the assertions. +// This file implements the core logic of Custom Active Authorities. A CAA is an authority which is permitted by an +// account to execute a particular authority on that account's behalf, with some restrictions on the content of that +// operation. This file implements the logic to validate those restrictions, and create a predicate function which +// takes a particular operation and determines whether it complies with the restrictions or not. +// +// The restrictions are a recursive structure, which applies to a particular operation struct, but may recurse to +// specify restrictions on fields or subfields of that struct. This file explores the restriction structure in tandem +// with the operation struct to verify that all of the restrictions are valid and to produce a predicate function. +// Note that this file operates primarily on restriction data, but only operation *types*, meaning the actual +// operation value does not appear until the predicate returned by this file is run. // -// It kicks off at the bottom of this file, in get_restriction_predicate(), which gets a vector of restrictions and -// an operation type tag. So first, we've got to enter into a template context that knows the actual operation type, -// not just the type tag. And we do a lot of entering into deeper and deeper template contexts, resolving tags to -// actual types so that we know all the type information when we create the predicate, and it can execute quickly -// on an operation instance, knowing exactly what types and fields it needs, how to cast things, how to execute the -// assertions, etc. +// As a result, this file is very template heavy, and does a good deal of type manipulation. Its contents are +// organized as a series of layers, which recursively examine the restrictions and types they apply to, and finally, +// once all the types have been resolved, a predicate function is created which evaluates the restrictions on an +// operation. // // To give an overview of the logic, the layers stack up like so, from beginning (bottom of file) to end: -// - get_restriction_predicate() -- gets vector and operation::tag_type, visits operation with the tag -// type to get an actual operation type as a template parameter -// - operation_type_resolver -- gets vector and operation type tag, resolves tag to operation type -// template argument. This is the last layer that can assume the type being restricted is an operation; all -// subsequent layers work on any object or field // - restrictions_to_predicate() -- takes a vector and creates a predicate for each of them, // but returns a single predicate that returns true only if all sub-predicates return true // - create_field_predicate() -- Resolves which field of Object the restriction is referencing by indexing -// into the object's reflected fields with the predicates member_index -// - create_logical_or_predicate() -- If the predicate is a logical OR function, instead of using -// create_field_predicate, we recurse into restrictions_to_predicate for each branch of the OR, returning a -// predicate which returns true if any branch of the OR passes +// into the object's reflected fields with the predicate's member_index +// - create_logical_or_predicate() -- If the predicate is a logical OR function, the predicate does not +// specify a field to examine; rather, the predicates in its branches do. Thus this function recurses into +// restrictions_to_predicate for each branch of the OR, and combines the resulting predicates in a predicate +// which returns true if any branch of the OR passes // - create_predicate_function() -- switches on restriction type to determine which predicate template to use // going forward // - make_predicate -- Determines what type the restriction argument is and creates // a predicate functor for that type -// - attribute_assertion -- If the restriction is an attribute assertion, instead of using the -// restriction_argument_visitor, we recurse into restrictions_to_predicate with the current Field as the Object +// - attribute_assertion -- If the restriction is an attribute assertion, instead of using make_predicate +// to create a predicate function, we first recurse into restrictions_to_predicate with Field as the Object // - embed_argument() -- Embeds the argument into the predicate if it is a valid type // for the predicate, and throws otherwise. // - predicate_xyz -- These are functors implementing the various predicate function types @@ -142,22 +148,33 @@ struct predicate_invalid { // Equality comparison template struct predicate_eq : predicate_invalid {}; template -struct predicate_eq>> { - // Simple comparison +struct predicate_eq::value>> { + // Simple comparison, same type constexpr static bool valid = true; constexpr bool operator()(const Field& f, const Argument& a) const { return f == a; } }; template +struct predicate_eq && is_integral && + !std::is_same::value>> { + // Simple comparison, integral types + constexpr static bool valid = true; + constexpr bool operator()(const Field& f, const Argument& a) const { return safenum::equal(to_num(f), to_num(a)); } +}; +template struct predicate_eq && is_integral>> { // Compare container size against int constexpr static bool valid = true; - bool operator()(const Field& f, const Argument& a) const { return f.size() == a; } + bool operator()(const Field& f, const Argument& a) const { return safenum::equal(f.size(), to_num(a)); } }; template -struct predicate_eq, Argument, std::enable_if_t>> { - // Compare optional value against same type - constexpr static bool valid = true; - bool operator()(const fc::optional& f, const Argument& a) const { return f.valid() && *f == a; } +struct predicate_eq, Argument, std::enable_if_t>> + : predicate_eq { + // Compare optional value against comparable type + using base = predicate_eq; + bool operator()(const fc::optional& f, const Argument& a) const { + + return f.valid() && (*this)(*f, a); + } }; template struct predicate_eq, void_t, void> { @@ -174,16 +191,27 @@ template struct predicate_ne : predicate_eq struct predicate_compare : predicate_invalid {}; template -struct predicate_compare>> { - // Simple comparison, integral types +struct predicate_compare::value>> { + // Simple comparison, same types constexpr static bool valid = true; constexpr int8_t operator()(const Field& f, const Argument& a) const { return fa? 1 : 0); } }; template +struct predicate_compare && is_integral && + !std::is_same::value>> { + // Simple comparison, integral types + constexpr static bool valid = true; + constexpr int8_t operator()(const Field& f, const Argument& a) const { + auto nf = to_num(f); + auto na = to_num(a); + return safenum::less_than(nf, na)? -1 : (safenum::greater_than(nf, na)? 1 : 0); + } +}; +template struct predicate_compare, Argument, void> : predicate_compare { - // Compare optional value against same type + // Compare optional value against comparable type constexpr static bool valid = true; constexpr int8_t operator()(const fc::optional& f, const Argument& a) const { FC_ASSERT(f.valid(), "Cannot compute inequality comparison against a null optional"); From c02fb9833f4566562a511abd0674cc469d7746bc Mon Sep 17 00:00:00 2001 From: Nathan Hourt Date: Mon, 12 Aug 2019 16:10:29 -0500 Subject: [PATCH 24/41] BSIP 40: Ship a copy of boost's safe_compare.hpp Old boost apparently didn't have this, and it's worth stealing --- .../custom_authorities/BOOST_LICENSE_1_0.txt | 23 +++ .../restriction_predicate.hxx | 2 +- .../custom_authorities/safe_compare.hpp | 185 ++++++++++++++++++ 3 files changed, 209 insertions(+), 1 deletion(-) create mode 100644 libraries/protocol/custom_authorities/BOOST_LICENSE_1_0.txt create mode 100644 libraries/protocol/custom_authorities/safe_compare.hpp diff --git a/libraries/protocol/custom_authorities/BOOST_LICENSE_1_0.txt b/libraries/protocol/custom_authorities/BOOST_LICENSE_1_0.txt new file mode 100644 index 0000000000..36b7cd93cd --- /dev/null +++ b/libraries/protocol/custom_authorities/BOOST_LICENSE_1_0.txt @@ -0,0 +1,23 @@ +Boost Software License - Version 1.0 - August 17th, 2003 + +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +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, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/libraries/protocol/custom_authorities/restriction_predicate.hxx b/libraries/protocol/custom_authorities/restriction_predicate.hxx index bed0c1b8b8..61402b3dd0 100644 --- a/libraries/protocol/custom_authorities/restriction_predicate.hxx +++ b/libraries/protocol/custom_authorities/restriction_predicate.hxx @@ -26,7 +26,7 @@ #include -#include +#include "safe_compare.hpp" namespace graphene { namespace protocol { namespace typelist = fc::typelist; diff --git a/libraries/protocol/custom_authorities/safe_compare.hpp b/libraries/protocol/custom_authorities/safe_compare.hpp new file mode 100644 index 0000000000..5b05065c6f --- /dev/null +++ b/libraries/protocol/custom_authorities/safe_compare.hpp @@ -0,0 +1,185 @@ +#ifndef BOOST_NUMERIC_SAFE_COMPARE_HPP +#define BOOST_NUMERIC_SAFE_COMPARE_HPP + +// MS compatible compilers support #pragma once +#if defined(_MSC_VER) && (_MSC_VER >= 1020) +# pragma once +#endif + +// Copyright (c) 2012 Robert Ramey +// +// Distributed under the Boost Software License, Version 1.0. (See +// accompanying file BOOST_LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#include +#include + +namespace boost { +namespace safe_numerics { +namespace safe_compare { + +//////////////////////////////////////////////////// +// safe comparison on primitive integral types +namespace safe_compare_detail { + template + using make_unsigned = typename std::conditional< + std::is_signed::value, + std::make_unsigned, + T + >::type; + + // both arguments unsigned or signed + template + struct less_than { + template + constexpr static bool invoke(const T & t, const U & u){ + return t < u; + } + }; + + // T unsigned, U signed + template<> + struct less_than { + template + constexpr static bool invoke(const T & t, const U & u){ + return + (u < 0) ? + false + : + less_than::invoke( + t, + static_cast::type &>(u) + ) + ; + } + }; + // T signed, U unsigned + template<> + struct less_than { + template + constexpr static bool invoke(const T & t, const U & u){ + return + (t < 0) ? + true + : + less_than::invoke( + static_cast::type &>(t), + u + ) + ; + } + }; +} // safe_compare_detail + +template +typename std::enable_if< + std::is_integral::value && std::is_integral::value, + bool +>::type +constexpr less_than(const T & lhs, const U & rhs) { + return safe_compare_detail::less_than< + std::is_signed::value, + std::is_signed::value + >::template invoke(lhs, rhs); +} + +template +typename std::enable_if< + std::is_floating_point::value && std::is_floating_point::value, + bool +>::type +constexpr less_than(const T & lhs, const U & rhs) { + return lhs < rhs; +} + +template +constexpr bool greater_than(const T & lhs, const U & rhs) { + return less_than(rhs, lhs); +} + +template +constexpr bool less_than_equal(const T & lhs, const U & rhs) { + return ! greater_than(lhs, rhs); +} + +template +constexpr bool greater_than_equal(const T & lhs, const U & rhs) { + return ! less_than(lhs, rhs); +} + +namespace safe_compare_detail { + // both arguments unsigned or signed + template + struct equal { + template + constexpr static bool invoke(const T & t, const U & u){ + return t == u; + } + }; + + // T unsigned, U signed + template<> + struct equal { + template + constexpr static bool invoke(const T & t, const U & u){ + return + (u < 0) ? + false + : + equal::invoke( + t, + static_cast::type &>(u) + ) + ; + } + }; + // T signed, U unsigned + template<> + struct equal { + template + constexpr static bool invoke(const T & t, const U & u){ + return + (t < 0) ? + false + : + equal::invoke( + static_cast::type &>(t), + u + ) + ; + } + }; +} // safe_compare_detail + +template +typename std::enable_if< + std::is_integral::value && std::is_integral::value, + bool +>::type +constexpr equal(const T & lhs, const U & rhs) { + return safe_compare_detail::equal< + std::numeric_limits::is_signed, + std::numeric_limits::is_signed + >::template invoke(lhs, rhs); +} + +template +typename std::enable_if< + std::is_floating_point::value && std::is_floating_point::value, + bool +>::type +constexpr equal(const T & lhs, const U & rhs) { + return lhs == rhs; +} + +template +constexpr bool not_equal(const T & lhs, const U & rhs) { + return ! equal(lhs, rhs); +} + +} // safe_compare +} // safe_numerics +} // boost + +#endif // BOOST_NUMERIC_SAFE_COMPARE_HPP From 66d18351c475486842be643e10c075b0659847e3 Mon Sep 17 00:00:00 2001 From: Nathan Hourt Date: Fri, 16 Aug 2019 15:38:08 -0500 Subject: [PATCH 25/41] BSIP 40: Error handling & static_variant restrictions Added a new function type to restrict the value of a static_variant field on the operations. This also spun off into reworking the error handling to more adequately report the reason for predicate rejections. --- libraries/chain/db_getter.cpp | 14 +-- .../chain/include/graphene/chain/database.hpp | 1 - .../restriction_predicate.hxx | 88 ++++++++++++++----- .../include/graphene/protocol/restriction.hpp | 9 +- .../protocol/restriction_predicate.hpp | 26 ++++-- .../include/graphene/protocol/transaction.hpp | 4 +- libraries/protocol/transaction.cpp | 2 +- tests/tests/custom_authority_tests.cpp | 49 +++++++---- 8 files changed, 139 insertions(+), 54 deletions(-) diff --git a/libraries/chain/db_getter.cpp b/libraries/chain/db_getter.cpp index af09f8f540..65579eb25f 100644 --- a/libraries/chain/db_getter.cpp +++ b/libraries/chain/db_getter.cpp @@ -109,11 +109,15 @@ vector database::get_viable_custom_authorities( vector results; for (const auto& cust_auth : valid_auths) { - auto result = cust_auth.get().get_predicate()(op); - if (result.success) - results.emplace_back(cust_auth.get().auth); - else if (rejected_authorities != nullptr) - rejected_authorities->insert(std::make_pair(cust_auth.get().id, std::move(result))); + try { + auto result = cust_auth.get().get_predicate()(op); + if (result.success) + results.emplace_back(cust_auth.get().auth); + else if (rejected_authorities != nullptr) + rejected_authorities->insert(std::make_pair(cust_auth.get().id, std::move(result))); + } catch (fc::exception& e) { + rejected_authorities->insert(std::make_pair(cust_auth.get().id, std::move(e))); + } } return results; diff --git a/libraries/chain/include/graphene/chain/database.hpp b/libraries/chain/include/graphene/chain/database.hpp index c2336bb13c..fe30c89fc4 100644 --- a/libraries/chain/include/graphene/chain/database.hpp +++ b/libraries/chain/include/graphene/chain/database.hpp @@ -48,7 +48,6 @@ namespace graphene { namespace protocol { struct predicate_result; } } namespace graphene { namespace chain { using graphene::db::abstract_object; using graphene::db::object; - using rejected_predicate_map = map; class op_evaluator; class transaction_evaluation_state; class proposal_object; diff --git a/libraries/protocol/custom_authorities/restriction_predicate.hxx b/libraries/protocol/custom_authorities/restriction_predicate.hxx index 61402b3dd0..1c583a4b51 100644 --- a/libraries/protocol/custom_authorities/restriction_predicate.hxx +++ b/libraries/protocol/custom_authorities/restriction_predicate.hxx @@ -111,6 +111,8 @@ namespace safenum = boost::safe_numerics::safe_compare; // a predicate functor for that type // - attribute_assertion -- If the restriction is an attribute assertion, instead of using make_predicate // to create a predicate function, we first recurse into restrictions_to_predicate with Field as the Object +// - variant_assertion -- If the restriction is a variant assertion, instead of using make_predicate, we +// recurse into restrictions_to_predicate with the variant value as the Object // - embed_argument() -- Embeds the argument into the predicate if it is a valid type // for the predicate, and throws otherwise. // - predicate_xyz -- These are functors implementing the various predicate function types @@ -172,8 +174,8 @@ struct predicate_eq, Argument, std::enable_if_t; bool operator()(const fc::optional& f, const Argument& a) const { - - return f.valid() && (*this)(*f, a); + if (!f.valid()) return predicate_result::Rejection(predicate_result::null_optional); + return (*this)(*f, a); } }; template @@ -214,7 +216,7 @@ struct predicate_compare, Argument, void> : predicate_compar // Compare optional value against comparable type constexpr static bool valid = true; constexpr int8_t operator()(const fc::optional& f, const Argument& a) const { - FC_ASSERT(f.valid(), "Cannot compute inequality comparison against a null optional"); + if (!f.valid()) return predicate_result::Rejection(predicate_result::null_optional); return (*this)(*f, a); } }; @@ -255,7 +257,7 @@ struct predicate_in, flat_set, std::enable_if_t& f, const flat_set& c) const { - FC_ASSERT(f.valid(), "Cannot compute whether null optional is in list"); + if (!f.valid()) return predicate_result::Rejection(predicate_result::null_optional); return c.count(*f) != 0; } }; @@ -294,7 +296,7 @@ template struct predicate_has_all, Argument, void> : predicate_has_all { // Field is optional container bool operator()(const fc::optional& f, const Argument& a) const { - FC_ASSERT(f.valid(), "Cannot compute whether all elements of null optional container are in other container"); + if (!f.valid()) return predicate_result::Rejection(predicate_result::null_optional); return (*this)(*f, a); } }; @@ -329,14 +331,14 @@ template struct predicate_has_none, Argument, void> : predicate_has_all { // Field is optional container bool operator()(const fc::optional& f, const Argument& a) const { - FC_ASSERT(f.valid(), "Cannot evaluate list has-none-of list on null optional list"); + if (!f.valid()) return predicate_result::Rejection(predicate_result::null_optional); return (*this)(*f, a); } }; ////////////////////////////////////////////// END PREDICATE FUNCTORS ////////////////////////////////////////////// // Forward declaration of restrictions_to_predicate, because attribute assertions and logical ORs recurse into it -template object_restriction_predicate restrictions_to_predicate(vector, bool); +template object_restriction_predicate restrictions_to_predicate(vector, bool); template struct attribute_assertion { @@ -348,7 +350,7 @@ template struct attribute_assertion> { static object_restriction_predicate> create(vector&& rs) { return [p=restrictions_to_predicate(std::move(rs), false)](const fc::optional& f) { - FC_ASSERT(f.valid(), "Cannot evaluate attribute assertion on null optional field"); + if (!f.valid()) return predicate_result::Rejection(predicate_result::null_optional); return p(*f); }; } @@ -362,17 +364,61 @@ struct attribute_assertion> { } }; +template +struct variant_assertion { + static object_restriction_predicate create(restriction::variant_assert_argument_type&&) { + FC_THROW_EXCEPTION(fc::assert_exception, "Invalid variant assertion on non-variant field", + ("Field", fc::get_typename::name())); + } +}; +template +struct variant_assertion> { + using Variant = static_variant; + + template + static auto make_predicate(vector&& rs) { + return [p=restrictions_to_predicate(std::move(rs), true)](const Variant& v) { + if (v.which() == Variant::template tag::value) + return p(v.template get()); + return predicate_result::Rejection(predicate_result::incorrect_variant_type); + }; + } + static object_restriction_predicate create(restriction::variant_assert_argument_type&& arg) { + return typelist::runtime::dispatch(typelist::list(), arg.first, + [&arg](auto t) -> object_restriction_predicate { + using Value = typename decltype(t)::type; + return variant_assertion::make_predicate(std::move(arg.second)); + }); + } +}; +template +struct variant_assertion>> { + using Variant = static_variant; + using Optional = fc::optional; + static object_restriction_predicate create(restriction::variant_assert_argument_type&& arg) { + return typelist::runtime::dispatch(typelist::list(), arg.first, + [&arg](auto t) -> object_restriction_predicate { + using Value = typename decltype(t)::type; + auto pred = variant_assertion::template make_predicate(std::move(arg.second)); + return [p=std::move(pred)](const Optional& opt) { + if (!opt.valid()) return predicate_result::Rejection(predicate_result::null_optional); + return p(*opt); + }; + }); + } +}; + // Embed the argument into the predicate functor template> object_restriction_predicate embed_argument(P p, A a, short) { return [p=std::move(p), a=std::move(a)](const F& f) { - if (p(f, a)) return predicate_result{true, {}}; - return predicate_result(); + if (p(f, a)) return predicate_result::Success(); + return predicate_result::Rejection(predicate_result::predicate_was_false); }; } template object_restriction_predicate embed_argument(P, A, long) { - FC_THROW_EXCEPTION(fc::assert_exception, "Invalid types for predicated"); + FC_THROW_EXCEPTION(fc::assert_exception, "Invalid types for predicate"); } // Resolve the argument type and make a predicate for it @@ -420,6 +466,10 @@ object_restriction_predicate create_predicate_function(restriction_functi FC_ASSERT(arg.which() == restriction_argument::tag>::value, "Argument type for attribute assertion must be restriction list"); return attribute_assertion::create(std::move(arg.get>())); + case restriction::func_variant_assert: + FC_ASSERT(arg.which() == restriction_argument::tag::value, + "Argument type for attribute assertion must be pair of variant tag and restriction list"); + return variant_assertion::create(std::move(arg.get())); default: FC_THROW_EXCEPTION(fc::assert_exception, "Invalid function type on restriction"); } @@ -465,17 +515,15 @@ object_restriction_predicate create_logical_or_predicate(vector failures; + vector rejections; bool success = std::any_of(predicates.begin(), predicates.end(), - [o=std::cref(obj), &failures](const auto& p) { + [o=std::cref(obj), &rejections](const auto& p) { auto result = p(o); - if (!result) failures.push_back(std::move(result)); + if (!result) rejections.push_back(std::move(result)); return !!result; }); - if (success) return predicate_result{true, {}}; - predicate_result r; - r.failure_path.emplace_back(std::move(failures)); - return r; + if (success) return predicate_result::Success(); + return predicate_result::Rejection(std::move(rejections)); }; } @@ -499,11 +547,11 @@ object_restriction_predicate restrictions_to_predicate(vector>; + #define GRAPHENE_OP_RESTRICTION_ARGUMENTS_VARIADIC \ /* 0 */ void_t, \ /* 1 */ bool, \ @@ -88,7 +93,8 @@ struct restriction { /* 37 */ flat_set, \ /* 38 */ flat_set, \ /* 39 */ vector, \ - /* 40 */ vector> + /* 40 */ vector>, \ + /* 41 */ variant_assert_argument_type using argument_type = fc::static_variant; @@ -123,6 +129,7 @@ FC_REFLECT_ENUM(graphene::protocol::restriction::function_type, (func_has_none) (func_attr) (func_logical_or) + (func_variant_assert) (FUNCTION_TYPE_COUNT)) FC_REFLECT(graphene::protocol::restriction, diff --git a/libraries/protocol/include/graphene/protocol/restriction_predicate.hpp b/libraries/protocol/include/graphene/protocol/restriction_predicate.hpp index f04756df7c..8adbe70cd8 100644 --- a/libraries/protocol/include/graphene/protocol/restriction_predicate.hpp +++ b/libraries/protocol/include/graphene/protocol/restriction_predicate.hpp @@ -35,10 +35,22 @@ struct predicate_result { /// Whether or not the operation complied with the restrictions or not bool success = false; - /// Either the index of a restriction in a list, or a list of indexes of restrictions (for logical OR branches) - using restriction_index = static_variant>; - /// The path of indexes to the restriction(s) that failed - vector failure_path; + /// Enumeration of the general reasons a predicate may reject + enum rejection_reason { + predicate_was_false, + null_optional, + incorrect_variant_type + }; + + /// An indicator of what rejection occurred at a particular restriction -- either an index to a sub-restriction, a + /// list of rejection results from the branches of a logical OR, or the immediate reason for rejection + using rejection_indicator = static_variant, rejection_reason>; + /// Failure indicators, from specific (restriction at point of rejection) to general (outermost restriction) + vector rejection_path; + + static predicate_result Rejection(rejection_reason reason) { return {false, {reason}}; } + static predicate_result Rejection(vector branches) { return {false, {std::move(branches)}}; } + static predicate_result Success() { return {true, {}}; } operator bool() const { return success; } }; @@ -57,5 +69,7 @@ restriction_predicate_function get_restriction_predicate(vector rs, } } // namespace graphene::protocol -FC_REFLECT_TYPENAME(graphene::protocol::predicate_result::restriction_index) -FC_REFLECT(graphene::protocol::predicate_result, (success)(failure_path)) +FC_REFLECT_ENUM(graphene::protocol::predicate_result::rejection_reason, + (predicate_was_false)(null_optional)(incorrect_variant_type)) +FC_REFLECT_TYPENAME(graphene::protocol::predicate_result::rejection_indicator) +FC_REFLECT(graphene::protocol::predicate_result, (success)(rejection_path)) diff --git a/libraries/protocol/include/graphene/protocol/transaction.hpp b/libraries/protocol/include/graphene/protocol/transaction.hpp index 61b9f5cdfd..c41049de97 100644 --- a/libraries/protocol/include/graphene/protocol/transaction.hpp +++ b/libraries/protocol/include/graphene/protocol/transaction.hpp @@ -27,8 +27,10 @@ namespace graphene { namespace protocol { struct predicate_result; + using rejected_predicate = static_variant; + using rejected_predicate_map = map; using custom_authority_lookup = std::function(account_id_type, const operation&, - map*)>; + rejected_predicate_map*)>; /** * @defgroup transactions Transactions diff --git a/libraries/protocol/transaction.cpp b/libraries/protocol/transaction.cpp index 7a692f6a02..adad4ad910 100644 --- a/libraries/protocol/transaction.cpp +++ b/libraries/protocol/transaction.cpp @@ -277,7 +277,7 @@ void verify_authority( const vector& ops, const flat_set& active_aprovals, const flat_set& owner_approvals ) { - map rejected_custom_auths; + rejected_predicate_map rejected_custom_auths; try { flat_set required_active; flat_set required_owner; diff --git a/tests/tests/custom_authority_tests.cpp b/tests/tests/custom_authority_tests.cpp index 27641d1a6e..03b7d7f629 100644 --- a/tests/tests/custom_authority_tests.cpp +++ b/tests/tests/custom_authority_tests.cpp @@ -59,6 +59,9 @@ void expect_exception_string(const string& s, Expression e) { ("s", s)("e", e)); } } +#define EXPECT_EXCEPTION_STRING(S, E) \ + BOOST_TEST_CHECKPOINT("Expect exception containing string: " S); \ + expect_exception_string(S, E) BOOST_AUTO_TEST_CASE(restriction_predicate_tests) { try { using namespace graphene::protocol; @@ -69,13 +72,15 @@ BOOST_AUTO_TEST_CASE(restriction_predicate_tests) { try { restrictions.emplace_back(to_index, FUNC(eq), account_id_type(12)); BOOST_CHECK(get_restriction_predicate(restrictions, operation::tag::value)(transfer) == false); BOOST_CHECK(get_restriction_predicate(restrictions, operation::tag::value)(transfer) - .failure_path.size() == 1); + .rejection_path.size() == 2); BOOST_CHECK(get_restriction_predicate(restrictions, operation::tag::value)(transfer) - .failure_path.front().get() == 0); + .rejection_path[0].get() == predicate_result::predicate_was_false); + BOOST_CHECK(get_restriction_predicate(restrictions, operation::tag::value)(transfer) + .rejection_path[1].get() == 0); transfer.to = account_id_type(12); BOOST_CHECK(get_restriction_predicate(restrictions, operation::tag::value)(transfer) == true); BOOST_CHECK(get_restriction_predicate(restrictions, operation::tag::value)(transfer) - .failure_path.size() == 0); + .rejection_path.size() == 0); restrictions.front() = restriction(fc::typelist::length::native_members>(), FUNC(eq), account_id_type(12)); @@ -91,27 +96,31 @@ BOOST_AUTO_TEST_CASE(restriction_predicate_tests) { try { vector{restriction(asset_id_index, FUNC(eq), asset_id_type(0))}); BOOST_CHECK(get_restriction_predicate(restrictions, operation::tag::value)(transfer) == true); BOOST_CHECK(get_restriction_predicate(restrictions, operation::tag::value)(transfer) - .failure_path.size() == 0); + .rejection_path.size() == 0); restrictions.front().argument.get>().front().argument = asset_id_type(1); BOOST_CHECK(get_restriction_predicate(restrictions, operation::tag::value)(transfer) == false); BOOST_CHECK(get_restriction_predicate(restrictions, operation::tag::value)(transfer) - .failure_path.size() == 2); + .rejection_path.size() == 3); + BOOST_CHECK(get_restriction_predicate(restrictions, operation::tag::value)(transfer) + .rejection_path[0].get() == predicate_result::predicate_was_false); BOOST_CHECK(get_restriction_predicate(restrictions, operation::tag::value)(transfer) - .failure_path.front().get() == 0); + .rejection_path[1].get() == 0); BOOST_CHECK(get_restriction_predicate(restrictions, operation::tag::value)(transfer) - .failure_path[1].get() == 0); + .rejection_path[2].get() == 0); restrictions.emplace_back(to_index, FUNC(eq), account_id_type(12)); transfer.to = account_id_type(12); transfer.fee.asset_id = asset_id_type(1); BOOST_CHECK(get_restriction_predicate(restrictions, operation::tag::value)(transfer) == true); BOOST_CHECK(get_restriction_predicate(restrictions, operation::tag::value)(transfer) - .failure_path.size() == 0); + .rejection_path.size() == 0); transfer.to = account_id_type(10); BOOST_CHECK(get_restriction_predicate(restrictions, operation::tag::value)(transfer) == false); BOOST_CHECK(get_restriction_predicate(restrictions, operation::tag::value)(transfer) - .failure_path.size() == 1); + .rejection_path.size() == 2); + BOOST_CHECK(get_restriction_predicate(restrictions, operation::tag::value)(transfer) + .rejection_path[0].get() == predicate_result::predicate_was_false); BOOST_CHECK(get_restriction_predicate(restrictions, operation::tag::value)(transfer) - .failure_path.front().get() == 1); + .rejection_path[1].get() == 1); account_update_operation update; restrictions.clear(); @@ -122,12 +131,14 @@ BOOST_AUTO_TEST_CASE(restriction_predicate_tests) { try { auto predicate = get_restriction_predicate(restrictions, operation::tag::value); BOOST_CHECK_THROW(predicate(transfer), fc::assert_exception); BOOST_CHECK(predicate(update) == true); - BOOST_CHECK(predicate(update).failure_path.size() == 0); + BOOST_CHECK(predicate(update).rejection_path.size() == 0); update.extensions.value.owner_special_authority = special_authority(); BOOST_CHECK(predicate(update) == false); - BOOST_CHECK(predicate(update).failure_path.size() == 2); - BOOST_CHECK(predicate(update).failure_path.front().get() == 0); - BOOST_CHECK(predicate(update).failure_path[1].get() == 0); + BOOST_CHECK_EQUAL(predicate(update).rejection_path.size(), 3); + BOOST_CHECK(predicate(update).rejection_path[0].get() == + predicate_result::predicate_was_false); + BOOST_CHECK(predicate(update).rejection_path[1].get() == 0); + BOOST_CHECK(predicate(update).rejection_path[2].get() == 0); restrictions.front().argument.get>().front().restriction_type = FUNC(ne); predicate = get_restriction_predicate(restrictions, operation::tag::value); BOOST_CHECK(predicate(update) == true); @@ -165,7 +176,7 @@ BOOST_AUTO_TEST_CASE(custom_auths) { try { top.amount.amount = 99 * GRAPHENE_BLOCKCHAIN_PRECISION; trx.operations = {top}; sign(trx, bob_private_key); - // No custom auth yet; bob's transfer should fail + // No custom auth yet; bob's transfer should reject BOOST_CHECK_THROW(PUSH_TX(db, trx), tx_missing_active_auth); trx.clear(); @@ -186,8 +197,8 @@ BOOST_AUTO_TEST_CASE(custom_auths) { try { trx.operations.front().get().amount.amount = 100*GRAPHENE_BLOCKCHAIN_PRECISION; trx.clear_signatures(); sign(trx, bob_private_key); - // If bob tries to transfer 100, it fails because the restriction is strictly less than 100 - expect_exception_string("\"failure_path\":[[0,0],[0,0]]", [&] {PUSH_TX(db, trx);}); + // If bob tries to transfer 100, it rejects because the restriction is strictly less than 100 + EXPECT_EXCEPTION_STRING("\"rejection_path\":[[2,\"predicate_was_false\"],[0,0],[0,0]]", [&] {PUSH_TX(db, trx);}); op.restrictions.front().argument.get>().front().restriction_type = restriction::func_eq; custom_authority_update_operation uop; @@ -207,8 +218,8 @@ BOOST_AUTO_TEST_CASE(custom_auths) { try { trx.operations = {top}; trx.expiration += 5; sign(trx, bob_private_key); - // The transfer of 99 should fail now becaues the requirement is for exactly 100 - expect_exception_string("\"failure_path\":[[0,0],[0,0]]", [&] {PUSH_TX(db, trx);}); + // The transfer of 99 should reject because the requirement is for exactly 100 + EXPECT_EXCEPTION_STRING("\"rejection_path\":[[2,\"predicate_was_false\"],[0,0],[0,0]]", [&] {PUSH_TX(db, trx);}); trx.operations.front().get().amount.amount = 100*GRAPHENE_BLOCKCHAIN_PRECISION; trx.clear_signatures(); From f3bf34310be364e1b233aad54e55f1527aee7be7 Mon Sep 17 00:00:00 2001 From: Nathan Hourt Date: Tue, 20 Aug 2019 13:24:34 -0500 Subject: [PATCH 26/41] BSIP 40: Merge/Review Fixes --- libraries/chain/custom_authority_evaluator.cpp | 4 ++-- .../graphene/chain/custom_authority_evaluator.hpp | 1 - .../custom_authorities/restriction_predicate.hxx | 2 +- libraries/protocol/custom_authority.cpp | 2 ++ tests/common/database_fixture.cpp | 14 ++++++++------ 5 files changed, 13 insertions(+), 10 deletions(-) diff --git a/libraries/chain/custom_authority_evaluator.cpp b/libraries/chain/custom_authority_evaluator.cpp index 7c3ce30de7..dcba31dff1 100644 --- a/libraries/chain/custom_authority_evaluator.cpp +++ b/libraries/chain/custom_authority_evaluator.cpp @@ -56,7 +56,7 @@ void_result custom_authority_create_evaluator::do_evaluate(const custom_authorit FC_ASSERT(std::distance(range.first, range.second) < config->max_custom_authorities_per_account, "Cannot create custom authority for account: account already has maximum number"); - predicate = get_restriction_predicate(op.restrictions, op.operation_type); + get_restriction_predicate(op.restrictions, op.operation_type); return void_result(); } FC_CAPTURE_AND_RETHROW((op)) } @@ -64,7 +64,7 @@ object_id_type custom_authority_create_evaluator::do_apply(const custom_authorit { try { database& d = db(); - return d.create([&op, p=std::move(predicate)] (custom_authority_object& obj) mutable { + return d.create([&op] (custom_authority_object& obj) mutable { obj.account = op.account; obj.enabled = op.enabled; obj.valid_from = op.valid_from; diff --git a/libraries/chain/include/graphene/chain/custom_authority_evaluator.hpp b/libraries/chain/include/graphene/chain/custom_authority_evaluator.hpp index 4eebf70a59..3d33db1a7a 100644 --- a/libraries/chain/include/graphene/chain/custom_authority_evaluator.hpp +++ b/libraries/chain/include/graphene/chain/custom_authority_evaluator.hpp @@ -33,7 +33,6 @@ class custom_authority_object; class custom_authority_create_evaluator : public evaluator { public: using operation_type = custom_authority_create_operation; - restriction_predicate_function predicate; void_result do_evaluate(const operation_type& op); object_id_type do_apply(const operation_type& op); diff --git a/libraries/protocol/custom_authorities/restriction_predicate.hxx b/libraries/protocol/custom_authorities/restriction_predicate.hxx index 1c583a4b51..e5335bbb48 100644 --- a/libraries/protocol/custom_authorities/restriction_predicate.hxx +++ b/libraries/protocol/custom_authorities/restriction_predicate.hxx @@ -497,7 +497,7 @@ object_restriction_predicate create_field_predicate(restriction&& r, sho auto p = create_predicate_function(static_cast(f), std::move(a)); return [p=std::move(p)](const Object& o) { return p(FieldReflection::get(o)); }; }; - return typelist::runtime::dispatch(member_list(), r.member_index, predicator); + return typelist::runtime::dispatch(member_list(), r.member_index.value, predicator); } template object_restriction_predicate create_field_predicate(restriction&&, long) { diff --git a/libraries/protocol/custom_authority.cpp b/libraries/protocol/custom_authority.cpp index b91233ec64..2a6559c3e1 100644 --- a/libraries/protocol/custom_authority.cpp +++ b/libraries/protocol/custom_authority.cpp @@ -25,6 +25,8 @@ #include #include +#include + namespace graphene { namespace protocol { share_type custom_authority_create_operation::calculate_fee(const fee_parameters_type& k)const { diff --git a/tests/common/database_fixture.cpp b/tests/common/database_fixture.cpp index c8bf3517d9..56d35c8c77 100644 --- a/tests/common/database_fixture.cpp +++ b/tests/common/database_fixture.cpp @@ -41,6 +41,7 @@ #include #include #include +#include #include @@ -1452,14 +1453,15 @@ void database_fixture::set_htlc_committee_parameters() std::shared_ptr new_fee_schedule = std::make_shared(); new_fee_schedule->scale = GRAPHENE_100_PERCENT; // replace the old with the new - flat_map params_map = get_htlc_fee_parameters(); + flat_map htlc_fees = get_htlc_fee_parameters(); for(auto param : existing_fee_schedule.parameters) { - auto itr = params_map.find(param.which()); - if (itr == params_map.end()) - new_fee_schedule->parameters.insert(param); - else - { + auto itr = htlc_fees.find(param.which()); + if (itr == htlc_fees.end()) { + // Only define fees for operations which are already forked in! + if (hardfork_visitor(db.head_block_time()).visit(param.which())) + new_fee_schedule->parameters.insert(param); + } else { new_fee_schedule->parameters.insert( (*itr).second); } } From ad7f5ed1ab7fc6de7f92fab4f40fbe9d4afaf5b7 Mon Sep 17 00:00:00 2001 From: Nathan Hourt Date: Tue, 17 Sep 2019 15:24:38 -0500 Subject: [PATCH 27/41] BSIP 40: Rebase fixes --- libraries/fc | 2 +- .../include/graphene/protocol/restriction.hpp | 147 +++++++++--------- tests/tests/custom_authority_tests.cpp | 14 +- 3 files changed, 85 insertions(+), 78 deletions(-) diff --git a/libraries/fc b/libraries/fc index 344a948d9e..bae416a446 160000 --- a/libraries/fc +++ b/libraries/fc @@ -1 +1 @@ -Subproject commit 344a948d9e0fe364e4cefdc1fe0a21df8ea46c63 +Subproject commit bae416a4462a9d59b0f3078a407218bbe12f0f96 diff --git a/libraries/protocol/include/graphene/protocol/restriction.hpp b/libraries/protocol/include/graphene/protocol/restriction.hpp index 40b6f93cf9..718de3684a 100644 --- a/libraries/protocol/include/graphene/protocol/restriction.hpp +++ b/libraries/protocol/include/graphene/protocol/restriction.hpp @@ -28,90 +28,85 @@ namespace graphene { namespace protocol { /** - * Defines the set of valid operation restritions as a discriminated union type. - */ + * Defines the set of valid operation restritions as a discriminated union type. + */ struct restriction { - enum function_type { - func_eq, - func_ne, - func_lt, - func_le, - func_gt, - func_ge, - func_in, - func_not_in, - func_has_all, - func_has_none, - func_attr, - func_logical_or, - func_variant_assert, - FUNCTION_TYPE_COUNT ///< Sentry value which contains the number of different types - }; + enum function_type { + func_eq, + func_ne, + func_lt, + func_le, + func_gt, + func_ge, + func_in, + func_not_in, + func_has_all, + func_has_none, + func_attr, + func_logical_or, + func_variant_assert, + FUNCTION_TYPE_COUNT ///< Sentry value which contains the number of different types + }; - // A variant assertion argument is a pair of the tag expected to be in the variant, and the restrictions to apply - // to the value - using variant_assert_argument_type = pair>; + // A variant assertion argument is a pair of the tag expected to be in the variant, and the restrictions to apply + // to the value + using variant_assert_argument_type = pair>; #define GRAPHENE_OP_RESTRICTION_ARGUMENTS_VARIADIC \ - /* 0 */ void_t, \ - /* 1 */ bool, \ - /* 2 */ int64_t, \ - /* 3 */ string, \ - /* 4 */ time_point_sec, \ - /* 5 */ public_key_type, \ - /* 6 */ fc::sha256, \ - /* 7 */ account_id_type, \ - /* 8 */ asset_id_type, \ - /* 9 */ force_settlement_id_type, \ - /* 10 */ committee_member_id_type, \ - /* 11 */ witness_id_type, \ - /* 12 */ limit_order_id_type, \ - /* 13 */ call_order_id_type, \ - /* 14 */ custom_id_type, \ - /* 15 */ proposal_id_type, \ - /* 16 */ withdraw_permission_id_type, \ - /* 17 */ vesting_balance_id_type, \ - /* 18 */ worker_id_type, \ - /* 19 */ balance_id_type, \ - /* 20 */ flat_set, \ - /* 21 */ flat_set, \ - /* 22 */ flat_set, \ - /* 23 */ flat_set, \ - /* 24 */ flat_set, \ - /* 25 */ flat_set, \ - /* 26 */ flat_set, \ - /* 27 */ flat_set, \ - /* 28 */ flat_set, \ - /* 29 */ flat_set, \ - /* 30 */ flat_set, \ - /* 31 */ flat_set, \ - /* 32 */ flat_set, \ - /* 33 */ flat_set, \ - /* 34 */ flat_set, \ - /* 35 */ flat_set, \ - /* 36 */ flat_set, \ - /* 37 */ flat_set, \ - /* 38 */ flat_set, \ - /* 39 */ vector, \ - /* 40 */ vector>, \ - /* 41 */ variant_assert_argument_type + /* 0 */ void_t, \ + /* 1 */ bool, \ + /* 2 */ int64_t, \ + /* 3 */ string, \ + /* 4 */ time_point_sec, \ + /* 5 */ public_key_type, \ + /* 6 */ fc::sha256, \ + /* 7 */ account_id_type, \ + /* 8 */ asset_id_type, \ + /* 9 */ force_settlement_id_type, \ + /* 10 */ committee_member_id_type, \ + /* 11 */ witness_id_type, \ + /* 12 */ limit_order_id_type, \ + /* 13 */ call_order_id_type, \ + /* 14 */ custom_id_type, \ + /* 15 */ proposal_id_type, \ + /* 16 */ withdraw_permission_id_type, \ + /* 17 */ vesting_balance_id_type, \ + /* 18 */ worker_id_type, \ + /* 19 */ balance_id_type, \ + /* 20 */ flat_set, \ + /* 21 */ flat_set, \ + /* 22 */ flat_set, \ + /* 23 */ flat_set, \ + /* 24 */ flat_set, \ + /* 25 */ flat_set, \ + /* 26 */ flat_set, \ + /* 27 */ flat_set, \ + /* 28 */ flat_set, \ + /* 29 */ flat_set, \ + /* 30 */ flat_set, \ + /* 31 */ flat_set, \ + /* 32 */ flat_set, \ + /* 33 */ flat_set, \ + /* 34 */ flat_set, \ + /* 35 */ flat_set, \ + /* 36 */ flat_set, \ + /* 37 */ flat_set, \ + /* 38 */ flat_set, \ + /* 39 */ vector, \ + /* 40 */ vector>, \ + /* 41 */ variant_assert_argument_type - using argument_type = fc::static_variant; + using argument_type = fc::static_variant; - unsigned_int member_index; - unsigned_int restriction_type; - argument_type argument; + unsigned_int member_index; + unsigned_int restriction_type; + argument_type argument; - extensions_type extensions; + extensions_type extensions; - restriction() = default; - restriction(const unsigned_int& member_index, function_type type, const argument_type& argument) - : member_index(member_index), restriction_type(type), argument(argument) {} - - friend bool operator==(const restriction& a, const restriction& b) { - return std::tie(a.member_index, a.restriction_type, a.argument, a.extensions) == - std::tie(b.member_index, b.restriction_type, b.argument, b.extensions); - } + restriction() = default; + restriction(const unsigned_int& member_index, function_type type, const argument_type& argument) + : member_index(member_index), restriction_type(type), argument(argument) {} }; } } // graphene::protocol diff --git a/tests/tests/custom_authority_tests.cpp b/tests/tests/custom_authority_tests.cpp index 03b7d7f629..ee983df022 100644 --- a/tests/tests/custom_authority_tests.cpp +++ b/tests/tests/custom_authority_tests.cpp @@ -34,6 +34,18 @@ using namespace graphene::chain; using namespace graphene::chain::test; +namespace graphene { namespace protocol { +bool operator==(const restriction& a, const restriction& b) { + if (std::tie(a.member_index, a.restriction_type) != std::tie(b.member_index, b.restriction_type)) + return false; + if (a.argument.is_type()) + return b.argument.is_type(); + using Value_Argument = static_variant>; + return Value_Argument::import_from(a.argument) == Value_Argument::import_from(b.argument); +} +} } + + BOOST_FIXTURE_TEST_SUITE(custom_authority_tests, database_fixture) #define FUNC(TYPE) BOOST_PP_CAT(restriction::func_, TYPE) @@ -54,7 +66,7 @@ void expect_exception_string(const string& s, Expression e) { e(); FC_THROW_EXCEPTION(fc::assert_exception, "Expected exception with string ${s}, but no exception thrown", ("s", s)); - } catch (fc::exception e) { + } catch (const fc::exception& e) { FC_ASSERT(e.to_detail_string().find(s) != string::npos, "Did not find expected string ${s} in exception: ${e}", ("s", s)("e", e)); } From dbafa023c05f4277b1dd6886538ff4b3b58c454c Mon Sep 17 00:00:00 2001 From: Nathan Hourt Date: Tue, 17 Sep 2019 15:44:51 -0500 Subject: [PATCH 28/41] BSIP 40: Reverse order of predicate rejection paths Ordering the rejection path from the outermost restriction to the innermost (point of rejection) is more intuitive, so reverse the order of the path when returning the predicate result. --- .../restriction_predicate.cpp | 20 ++++++++++++++++- .../protocol/restriction_predicate.hpp | 8 ++++--- tests/tests/custom_authority_tests.cpp | 22 +++++++++---------- 3 files changed, 35 insertions(+), 15 deletions(-) diff --git a/libraries/protocol/custom_authorities/restriction_predicate.cpp b/libraries/protocol/custom_authorities/restriction_predicate.cpp index 2465422188..1b1094b3aa 100644 --- a/libraries/protocol/custom_authorities/restriction_predicate.cpp +++ b/libraries/protocol/custom_authorities/restriction_predicate.cpp @@ -29,7 +29,7 @@ namespace graphene { namespace protocol { restriction_predicate_function get_restriction_predicate(vector rs, operation::tag_type op_type) { - return typelist::runtime::dispatch(operation::list(), op_type, [&rs](auto t) -> restriction_predicate_function { + auto f = typelist::runtime::dispatch(operation::list(), op_type, [&rs](auto t) -> restriction_predicate_function { using Op = typename decltype(t)::type; if (typelist::contains()) return get_restriction_predicate_list_1(typelist::index_of(), std::move(rs)); @@ -55,6 +55,24 @@ restriction_predicate_function get_restriction_predicate(vector rs, "LOGIC ERROR: Operation type not handled by custom authorities implementation. " "Please report this error."); }); + + // Wrap function in a layer that, if the function returns an error, reverses the order of the rejection path. This + // is because the order the path is created in, from the top of the call stack to the bottom, is counterintuitive. + return [f=std::move(f)](const operation& op) { return f(op).reverse_path(); }; +} + +predicate_result& predicate_result::reverse_path() { + if (success == true) + return *this; + auto reverse_subpaths = [](rejection_indicator& indicator) { + if (indicator.is_type>()) { + auto& results = indicator.get>(); + for (predicate_result& result : results) result.reverse_path(); + } + }; + std::reverse(rejection_path.begin(), rejection_path.end()); + std::for_each(rejection_path.begin(), rejection_path.end(), reverse_subpaths); + return *this; } // These are some compile-time tests of the metafunctions and predicate type analysis. They are turned off to make diff --git a/libraries/protocol/include/graphene/protocol/restriction_predicate.hpp b/libraries/protocol/include/graphene/protocol/restriction_predicate.hpp index 8adbe70cd8..f492aa4ffa 100644 --- a/libraries/protocol/include/graphene/protocol/restriction_predicate.hpp +++ b/libraries/protocol/include/graphene/protocol/restriction_predicate.hpp @@ -45,7 +45,7 @@ struct predicate_result { /// An indicator of what rejection occurred at a particular restriction -- either an index to a sub-restriction, a /// list of rejection results from the branches of a logical OR, or the immediate reason for rejection using rejection_indicator = static_variant, rejection_reason>; - /// Failure indicators, from specific (restriction at point of rejection) to general (outermost restriction) + /// Failure indicators, ordered from the outermost restriction to the innermost (the location of the rejection) vector rejection_path; static predicate_result Rejection(rejection_reason reason) { return {false, {reason}}; } @@ -53,10 +53,12 @@ struct predicate_result { static predicate_result Success() { return {true, {}}; } operator bool() const { return success; } + + /// Reverse the order of the rejection path. Returns a reference to this object + predicate_result& reverse_path(); }; -/// A restriction predicate is a function accepting an operation and returning a boolean which indicates whether the -/// operation complies with the restrictions or not +/// A restriction predicate is a function accepting an operation and returning a predicate_result using restriction_predicate_function = std::function; /** diff --git a/tests/tests/custom_authority_tests.cpp b/tests/tests/custom_authority_tests.cpp index ee983df022..c549d7f243 100644 --- a/tests/tests/custom_authority_tests.cpp +++ b/tests/tests/custom_authority_tests.cpp @@ -86,9 +86,9 @@ BOOST_AUTO_TEST_CASE(restriction_predicate_tests) { try { BOOST_CHECK(get_restriction_predicate(restrictions, operation::tag::value)(transfer) .rejection_path.size() == 2); BOOST_CHECK(get_restriction_predicate(restrictions, operation::tag::value)(transfer) - .rejection_path[0].get() == predicate_result::predicate_was_false); + .rejection_path[0].get() == 0); BOOST_CHECK(get_restriction_predicate(restrictions, operation::tag::value)(transfer) - .rejection_path[1].get() == 0); + .rejection_path[1].get() == predicate_result::predicate_was_false); transfer.to = account_id_type(12); BOOST_CHECK(get_restriction_predicate(restrictions, operation::tag::value)(transfer) == true); BOOST_CHECK(get_restriction_predicate(restrictions, operation::tag::value)(transfer) @@ -114,11 +114,11 @@ BOOST_AUTO_TEST_CASE(restriction_predicate_tests) { try { BOOST_CHECK(get_restriction_predicate(restrictions, operation::tag::value)(transfer) .rejection_path.size() == 3); BOOST_CHECK(get_restriction_predicate(restrictions, operation::tag::value)(transfer) - .rejection_path[0].get() == predicate_result::predicate_was_false); + .rejection_path[0].get() == 0); BOOST_CHECK(get_restriction_predicate(restrictions, operation::tag::value)(transfer) .rejection_path[1].get() == 0); BOOST_CHECK(get_restriction_predicate(restrictions, operation::tag::value)(transfer) - .rejection_path[2].get() == 0); + .rejection_path[2].get() == predicate_result::predicate_was_false); restrictions.emplace_back(to_index, FUNC(eq), account_id_type(12)); transfer.to = account_id_type(12); transfer.fee.asset_id = asset_id_type(1); @@ -130,9 +130,9 @@ BOOST_AUTO_TEST_CASE(restriction_predicate_tests) { try { BOOST_CHECK(get_restriction_predicate(restrictions, operation::tag::value)(transfer) .rejection_path.size() == 2); BOOST_CHECK(get_restriction_predicate(restrictions, operation::tag::value)(transfer) - .rejection_path[0].get() == predicate_result::predicate_was_false); + .rejection_path[0].get() == 1); BOOST_CHECK(get_restriction_predicate(restrictions, operation::tag::value)(transfer) - .rejection_path[1].get() == 1); + .rejection_path[1].get() == predicate_result::predicate_was_false); account_update_operation update; restrictions.clear(); @@ -147,10 +147,10 @@ BOOST_AUTO_TEST_CASE(restriction_predicate_tests) { try { update.extensions.value.owner_special_authority = special_authority(); BOOST_CHECK(predicate(update) == false); BOOST_CHECK_EQUAL(predicate(update).rejection_path.size(), 3); - BOOST_CHECK(predicate(update).rejection_path[0].get() == - predicate_result::predicate_was_false); + BOOST_CHECK(predicate(update).rejection_path[0].get() == 0); BOOST_CHECK(predicate(update).rejection_path[1].get() == 0); - BOOST_CHECK(predicate(update).rejection_path[2].get() == 0); + BOOST_CHECK(predicate(update).rejection_path[2].get() == + predicate_result::predicate_was_false); restrictions.front().argument.get>().front().restriction_type = FUNC(ne); predicate = get_restriction_predicate(restrictions, operation::tag::value); BOOST_CHECK(predicate(update) == true); @@ -210,7 +210,7 @@ BOOST_AUTO_TEST_CASE(custom_auths) { try { trx.clear_signatures(); sign(trx, bob_private_key); // If bob tries to transfer 100, it rejects because the restriction is strictly less than 100 - EXPECT_EXCEPTION_STRING("\"rejection_path\":[[2,\"predicate_was_false\"],[0,0],[0,0]]", [&] {PUSH_TX(db, trx);}); + EXPECT_EXCEPTION_STRING("\"rejection_path\":[[0,0],[0,0],[2,\"predicate_was_false\"]]", [&] {PUSH_TX(db, trx);}); op.restrictions.front().argument.get>().front().restriction_type = restriction::func_eq; custom_authority_update_operation uop; @@ -231,7 +231,7 @@ BOOST_AUTO_TEST_CASE(custom_auths) { try { trx.expiration += 5; sign(trx, bob_private_key); // The transfer of 99 should reject because the requirement is for exactly 100 - EXPECT_EXCEPTION_STRING("\"rejection_path\":[[2,\"predicate_was_false\"],[0,0],[0,0]]", [&] {PUSH_TX(db, trx);}); + EXPECT_EXCEPTION_STRING("\"rejection_path\":[[0,0],[0,0],[2,\"predicate_was_false\"]]", [&] {PUSH_TX(db, trx);}); trx.operations.front().get().amount.amount = 100*GRAPHENE_BLOCKCHAIN_PRECISION; trx.clear_signatures(); From dd75b136cc9bbbf6b42417f2b9981a5e02d02ee4 Mon Sep 17 00:00:00 2001 From: Michel Santos Date: Fri, 30 Aug 2019 16:42:26 -0400 Subject: [PATCH 29/41] BSIP 40: Unit test comments --- tests/tests/custom_authority_tests.cpp | 326 ++++++++++++++++++++++++- 1 file changed, 316 insertions(+), 10 deletions(-) diff --git a/tests/tests/custom_authority_tests.cpp b/tests/tests/custom_authority_tests.cpp index c549d7f243..aa670b6f07 100644 --- a/tests/tests/custom_authority_tests.cpp +++ b/tests/tests/custom_authority_tests.cpp @@ -77,86 +77,323 @@ void expect_exception_string(const string& s, Expression e) { BOOST_AUTO_TEST_CASE(restriction_predicate_tests) { try { using namespace graphene::protocol; + ////// + // Create a restriction that authorizes transfers only made to Account ID 12 + ////// vector restrictions; - transfer_operation transfer; - auto to_index = member_index("to"); restrictions.emplace_back(to_index, FUNC(eq), account_id_type(12)); + + ////// + // Create an operation that transfers to Account ID 0 + // This should violate the restriction + ////// + transfer_operation transfer; + // Check that the proposed operation to account ID 0 is not compliant with the restriction to account ID 12 BOOST_CHECK(get_restriction_predicate(restrictions, operation::tag::value)(transfer) == false); + // Inspect the reasons why the proposed operation was rejected + // The rejection path will reference portions of the restrictions + //[ + // { + // "member_index": 2, + // "restriction_type": 0, + // "argument": [ + // 7, + // "1.2.12" + // ], + // "extensions": [] + // } + //] BOOST_CHECK(get_restriction_predicate(restrictions, operation::tag::value)(transfer) .rejection_path.size() == 2); + // Index 0 (the outer-most) rejection path refers to the first and only restriction BOOST_CHECK(get_restriction_predicate(restrictions, operation::tag::value)(transfer) .rejection_path[0].get() == 0); + // Index 1 (the inner-most) rejection path refers to the first and only argument for an account ID of 1.2.12 BOOST_CHECK(get_restriction_predicate(restrictions, operation::tag::value)(transfer) .rejection_path[1].get() == predicate_result::predicate_was_false); + + ////// + // Create an operation that transfer to Account ID 12 + // This should satisfy the restriction + ////// transfer.to = account_id_type(12); BOOST_CHECK(get_restriction_predicate(restrictions, operation::tag::value)(transfer) == true); BOOST_CHECK(get_restriction_predicate(restrictions, operation::tag::value)(transfer) .rejection_path.size() == 0); + + ////// + // Create an INVALID restriction that references an invalid member index + // (Index 6 is greater than the highest 0-based index of 5) + // of the transfer operation + ////// restrictions.front() = restriction(fc::typelist::length::native_members>(), FUNC(eq), account_id_type(12)); + //[ + // { + // "member_index": 6, + // "restriction_type": 0, + // "argument": [ + // 7, + // "1.2.12" + // ], + // "extensions": [] + // } + //] + // + // This restriction should throw an exception related to an invalid member index + // 10 assert_exception: Assert Exception + // r.member_index < typelist::length(): Invalid member index 6 for object graphene::protocol::transfer_operation + // {"I":6,"O":"graphene::protocol::transfer_operation"} + // th_a restriction_predicate.hxx:493 create_field_predicate BOOST_CHECK_THROW(get_restriction_predicate(restrictions, operation::tag::value), fc::assert_exception); + + + ////// + // Create an INVALID restriction that compares a transfer operation's account ID type to an asset ID type + ////// restrictions.front() = restriction(to_index, FUNC(eq), asset_id_type(12)); + //[ + // { + // "member_index": 2, + // "restriction_type": 0, + // "argument": [ + // 8, + // "1.3.12" + // ], + // "extensions": [] + // } + //] + // + // This restriction should throw an exception related to invalid type + // 10 assert_exception: Assert Exception + // Invalid types for predicate + // {} + // th_a restriction_predicate.hxx:147 predicate_invalid + // + // {"fc::get_typename::name()":"graphene::protocol::account_id_type","func":"func_eq","arg":[8,"1.3.12"]} + // th_a restriction_predicate.hxx:476 create_predicate_function BOOST_CHECK_THROW(get_restriction_predicate(restrictions, operation::tag::value), fc::assert_exception); + ////// + // Create a restriction such that the operation fee must be paid with Asset ID 0 + ////// auto fee_index = member_index("fee"); auto asset_id_index = member_index("asset_id"); restrictions.front() = restriction(fee_index, FUNC(attr), vector{restriction(asset_id_index, FUNC(eq), asset_id_type(0))}); + + ////// + // Check the transfer operation that pays the fee with Asset ID 0 + // This should satisfy the restriction. + ////// BOOST_CHECK(get_restriction_predicate(restrictions, operation::tag::value)(transfer) == true); BOOST_CHECK(get_restriction_predicate(restrictions, operation::tag::value)(transfer) .rejection_path.size() == 0); + + ////// + // Change the restriction such that the operation fee must be paid with Asset ID 1 + ////// restrictions.front().argument.get>().front().argument = asset_id_type(1); + //[ + // { + // "member_index": 0, + // "restriction_type": 10, + // "argument": [ + // 39, + // [ + // { + // "member_index": 1, + // "restriction_type": 0, + // "argument": [ + // 8, + // "1.3.1" + // ], + // "extensions": [] + // } + // ] + // ], + // "extensions": [] + // } + //] + + ////// + // Check the transfer operation that pays the fee with Asset ID 0 against the restriction. + // This should violate the restriction. + ////// BOOST_CHECK(get_restriction_predicate(restrictions, operation::tag::value)(transfer) == false); + // Inspect the reasons why the proposed operation was rejected + // The rejection path will reference portions of the restrictions BOOST_CHECK(get_restriction_predicate(restrictions, operation::tag::value)(transfer) .rejection_path.size() == 3); + // Index 0 (the outer-most) rejection path refers to the first and only restriction BOOST_CHECK(get_restriction_predicate(restrictions, operation::tag::value)(transfer) .rejection_path[0].get() == 0); + // Index 1 rejection path refers to the first and only argument of the restriction BOOST_CHECK(get_restriction_predicate(restrictions, operation::tag::value)(transfer) .rejection_path[1].get() == 0); + // Index 2 (the inner-most) rejection path refers to the first and only argument BOOST_CHECK(get_restriction_predicate(restrictions, operation::tag::value)(transfer) .rejection_path[2].get() == predicate_result::predicate_was_false); + + ////// + // Create a restriction that authorizes transfers only to Account ID 12 + ////// restrictions.emplace_back(to_index, FUNC(eq), account_id_type(12)); + //[ + // { + // "member_index": 0, + // "restriction_type": 10, + // "argument": [ + // 39, + // [ + // { + // "member_index": 1, + // "restriction_type": 0, + // "argument": [ + // 8, + // "1.3.1" + // ], + // "extensions": [] + // } + // ] + // ], + // "extensions": [] + // }, + // { + // "member_index": 2, + // "restriction_type": 0, + // "argument": [ + // 7, + // "1.2.12" + // ], + // "extensions": [] + // } + //] + + ////// + // Create a transfer operation that authorizes transfer to Account ID 12 + // This operation should satisfy the restriction + ////// transfer.to = account_id_type(12); transfer.fee.asset_id = asset_id_type(1); BOOST_CHECK(get_restriction_predicate(restrictions, operation::tag::value)(transfer) == true); BOOST_CHECK(get_restriction_predicate(restrictions, operation::tag::value)(transfer) .rejection_path.size() == 0); + + ////// + // Create a transfer operation that transfers to Account ID 10 + // This operation should violate the restriction + ////// transfer.to = account_id_type(10); BOOST_CHECK(get_restriction_predicate(restrictions, operation::tag::value)(transfer) == false); + // Inspect the reasons why the proposed operation was rejected + // The rejection path will reference portions of the restrictions BOOST_CHECK(get_restriction_predicate(restrictions, operation::tag::value)(transfer) .rejection_path.size() == 2); + // Index 0 (the outer-most) rejection path refers to the first and only restriction BOOST_CHECK(get_restriction_predicate(restrictions, operation::tag::value)(transfer) .rejection_path[0].get() == 1); + // Index 1 (the inner-most) rejection path refers to the first and only argument BOOST_CHECK(get_restriction_predicate(restrictions, operation::tag::value)(transfer) .rejection_path[1].get() == predicate_result::predicate_was_false); - account_update_operation update; + ////// + // Create a restriction where the ext.owner_special_authority field is unspecified + ////// restrictions.clear(); auto extensions_index = member_index("extensions"); auto authority_index = member_index("owner_special_authority"); restrictions.emplace_back(extensions_index, FUNC(attr), vector{restriction(authority_index, FUNC(eq), void_t())}); + //[ + // { + // "member_index": 5, + // "restriction_type": 10, + // "argument": [ + // 39, + // [ + // { + // "member_index": 1, + // "restriction_type": 0, + // "argument": [ + // 0, + // {} + // ], + // "extensions": [] + // } + // ] + // ], + // "extensions": [] + // } + //] auto predicate = get_restriction_predicate(restrictions, operation::tag::value); + + ////// + // Create an account update operation without any owner_special_authority extension + ////// + account_update_operation update; + // The transfer operation should violate the restriction because it does not have an ext field BOOST_CHECK_THROW(predicate(transfer), fc::assert_exception); + // The update operation should satisfy the restriction BOOST_CHECK(predicate(update) == true); BOOST_CHECK(predicate(update).rejection_path.size() == 0); + + ////// + // Change the update operation to include an owner_special_authority + // This should violate the restriction + ////// update.extensions.value.owner_special_authority = special_authority(); BOOST_CHECK(predicate(update) == false); BOOST_CHECK_EQUAL(predicate(update).rejection_path.size(), 3); + // Index 0 (the outer-most) rejection path refers to the first and only restriction BOOST_CHECK(predicate(update).rejection_path[0].get() == 0); + // Index 1 rejection path refers to the first and only argument of the restriction BOOST_CHECK(predicate(update).rejection_path[1].get() == 0); + // Index 2 (the inner-most) rejection path refers to the first and only argument BOOST_CHECK(predicate(update).rejection_path[2].get() == predicate_result::predicate_was_false); + + ////// + // Change the restriction where the ext.owner_special_authority field must be specified + ////// restrictions.front().argument.get>().front().restriction_type = FUNC(ne); + //[ + // { + // "member_index": 5, + // "restriction_type": 10, + // "argument": [ + // 39, + // [ + // { + // "member_index": 1, + // "restriction_type": 1, + // "argument": [ + // 0, + // {} + // ], + // "extensions": [] + // } + // ] + // ], + // "extensions": [] + // } + //] + + ////// + // The update operation should satisfy the new restriction because the ext.owner_special_authority is specified + ////// predicate = get_restriction_predicate(restrictions, operation::tag::value); BOOST_CHECK(predicate(update) == true); } FC_LOG_AND_RETHROW() } BOOST_AUTO_TEST_CASE(custom_auths) { try { + ////// + // Initialize the test + ////// generate_blocks(HARDFORK_BSIP_40_TIME); generate_blocks(5); db.modify(global_property_id_type()(db), [](global_property_object& gpo) { @@ -167,6 +404,11 @@ BOOST_AUTO_TEST_CASE(custom_auths) { try { fund(alice, asset(1000*GRAPHENE_BLOCKCHAIN_PRECISION)); fund(bob, asset(1000*GRAPHENE_BLOCKCHAIN_PRECISION)); + ////// + // Create a custom authority where Bob is authorized to transfer from Alice's account + // if and only if the transfer amount is less than 100 of Asset ID 0. + // This custom authority is NOT YET published. + ////// custom_authority_create_operation op; op.account = alice.get_id(); op.auth.add_authority(bob.get_id(), 1); @@ -181,7 +423,42 @@ BOOST_AUTO_TEST_CASE(custom_auths) { try { restriction(asset_amount_index, restriction::func_lt, int64_t(100*GRAPHENE_BLOCKCHAIN_PRECISION)), restriction(assed_id_index, restriction::func_eq, asset_id_type(0))})}; + //[ + // { + // "member_index": 3, + // "restriction_type": 10, + // "argument": [ + // 39, + // [ + // { + // "member_index": 0, + // "restriction_type": 2, + // "argument": [ + // 2, + // 10000000 + // ], + // "extensions": [] + // }, + // { + // "member_index": 1, + // "restriction_type": 0, + // "argument": [ + // 8, + // "1.3.0" + // ], + // "extensions": [] + // } + // ] + // ], + // "extensions": [] + // } + //] + + ////// + // Bob attempts to transfer 99 CORE from Alice's account + // This attempt should fail because it is attempted before the custom authority is published + ////// transfer_operation top; top.to = bob.get_id(); top.from = alice.get_id(); @@ -191,27 +468,41 @@ BOOST_AUTO_TEST_CASE(custom_auths) { try { // No custom auth yet; bob's transfer should reject BOOST_CHECK_THROW(PUSH_TX(db, trx), tx_missing_active_auth); + ////// + // Alice publishes the custom authority + ////// trx.clear(); trx.operations = {op}; sign(trx, alice_private_key); - // Alice publishes the custom authority PUSH_TX(db, trx); custom_authority_id_type auth_id = db.get_index_type().indices().get().find(alice_id)->id; + ////// + // Bob attempts to transfer 99 CORE from Alice's account + // This attempt should succeed because it is attempted after the custom authority is published + ////// trx.clear(); trx.operations = {top}; sign(trx, bob_private_key); - // Now bob's transfer should succeed due to the custom authority PUSH_TX(db, trx); + ////// + // Bob attempts to transfer 100 CORE from Alice's account + // This attempt should fail because it exceeds the authorized amount + ////// trx.operations.front().get().amount.amount = 100*GRAPHENE_BLOCKCHAIN_PRECISION; trx.clear_signatures(); sign(trx, bob_private_key); // If bob tries to transfer 100, it rejects because the restriction is strictly less than 100 EXPECT_EXCEPTION_STRING("\"rejection_path\":[[0,0],[0,0],[2,\"predicate_was_false\"]]", [&] {PUSH_TX(db, trx);}); + ////// + // Update the custom authority so that Bob is authorized to transfer from Alice's account + // if and only if the transfer amount EXACTLY EQUALS 100 of Asset ID 0. + // This custom authority is NOT YET published. + ////// op.restrictions.front().argument.get>().front().restriction_type = restriction::func_eq; custom_authority_update_operation uop; uop.account = alice.get_id(); @@ -221,11 +512,14 @@ BOOST_AUTO_TEST_CASE(custom_auths) { try { trx.clear(); trx.operations = {uop}; sign(trx, alice_private_key); - // Alice publishes an update to the custom authority, making the restriction require exactly 100 PUSH_TX(db, trx); BOOST_CHECK(auth_id(db).get_restrictions() == uop.restrictions_to_add); + ////// + // Bob attempts to transfer 99 CORE from Alice's account + // This attempt should fail because only transfers of 100 CORE are authorized + ////// trx.clear(); trx.operations = {top}; trx.expiration += 5; @@ -233,34 +527,46 @@ BOOST_AUTO_TEST_CASE(custom_auths) { try { // The transfer of 99 should reject because the requirement is for exactly 100 EXPECT_EXCEPTION_STRING("\"rejection_path\":[[0,0],[0,0],[2,\"predicate_was_false\"]]", [&] {PUSH_TX(db, trx);}); + ////// + // Bob attempts to transfer 100 CORE from Alice's account + // This attempt should succeed because transfers of exactly 100 CORE are authorized by Alice + ////// trx.operations.front().get().amount.amount = 100*GRAPHENE_BLOCKCHAIN_PRECISION; trx.clear_signatures(); sign(trx, bob_private_key); - // A transfer of 100 should succeed PUSH_TX(db, trx); auto transfer = trx; generate_block(); + ////// + // Bob attempts to transfer 100 CORE from Alice's account AGAIN + // This attempt should succeed because there are no limits to the transfer size nor quantity + // besides the available CORE in Alice's account + ////// trx.expiration += 5; trx.clear_signatures(); sign(trx, bob_private_key); - // Another one should succeed PUSH_TX(db, trx); + ////// + // Alice revokes the custom authority for Bob + ////// custom_authority_delete_operation dop; dop.account = alice.get_id(); dop.authority_to_delete = auth_id; trx.clear(); trx.operations = {dop}; sign(trx, alice_private_key); - // Alice deletes the custom authority PUSH_TX(db, trx); + ////// + // Bob attempts to transfer 100 CORE from Alice's account + // This attempt should fail because it is attempted after the custom authority has been revoked + ////// transfer.expiration += 10; transfer.clear_signatures(); sign(transfer, bob_private_key); - // The transfer should no longer work BOOST_CHECK_THROW(PUSH_TX(db, transfer), tx_missing_active_auth); } FC_LOG_AND_RETHROW() } From 9574d18a679a4b3ea5c6a2f4c705ae56b80cf6b0 Mon Sep 17 00:00:00 2001 From: Michel Santos Date: Fri, 6 Sep 2019 20:58:52 -0400 Subject: [PATCH 30/41] BSIP 40: Authorization and revocation of multiple concurrent CAA --- tests/tests/custom_authority_tests.cpp | 253 +++++++++++++++++++++++++ 1 file changed, 253 insertions(+) diff --git a/tests/tests/custom_authority_tests.cpp b/tests/tests/custom_authority_tests.cpp index aa670b6f07..abf7d8ac10 100644 --- a/tests/tests/custom_authority_tests.cpp +++ b/tests/tests/custom_authority_tests.cpp @@ -570,4 +570,257 @@ BOOST_AUTO_TEST_CASE(custom_auths) { try { BOOST_CHECK_THROW(PUSH_TX(db, transfer), tx_missing_active_auth); } FC_LOG_AND_RETHROW() } + + // Test of authorization and revocation of one account (Alice) authorizing multiple other accounts (Bob and Charlie) + // to transfer out of her account + BOOST_AUTO_TEST_CASE(selective_custom_auths) { + try { + ////// + // Initialize the test + ////// + generate_blocks(HARDFORK_BSIP_40_TIME); + generate_blocks(5); + db.modify(global_property_id_type()(db), [](global_property_object &gpo) { + gpo.parameters.extensions.value.custom_authority_options = custom_authority_options_type(); + }); + set_expiration(db, trx); + ACTORS((alice)(bob)(charlie)(diana)) + fund(alice, asset(1000 * GRAPHENE_BLOCKCHAIN_PRECISION)); + fund(bob, asset(1000 * GRAPHENE_BLOCKCHAIN_PRECISION)); + + + ////// + // Bob attempts to transfer 100 CORE from Alice's account to Charlie + // This attempt should fail because Alice has not authorized anyone to transfer from her account + ////// + transfer_operation bob_transfers_from_alice_to_charlie; + bob_transfers_from_alice_to_charlie.to = charlie.get_id(); + bob_transfers_from_alice_to_charlie.from = alice.get_id(); + bob_transfers_from_alice_to_charlie.amount.amount = 100 * GRAPHENE_BLOCKCHAIN_PRECISION; + trx.operations = {bob_transfers_from_alice_to_charlie}; + sign(trx, bob_private_key); + BOOST_CHECK_THROW(PUSH_TX(db, trx), tx_missing_active_auth); + + ////// + // Bob attempts to transfer 100 CORE from Alice's account to Diana + // This attempt should fail because Alice has not authorized anyone to transfer from her account + ////// + transfer_operation bob_transfers_from_alice_to_diana; + bob_transfers_from_alice_to_diana.to = diana.get_id(); + bob_transfers_from_alice_to_diana.from = alice.get_id(); + bob_transfers_from_alice_to_diana.amount.amount = 60 * GRAPHENE_BLOCKCHAIN_PRECISION; + trx.operations = {bob_transfers_from_alice_to_diana}; + sign(trx, bob_private_key); + BOOST_CHECK_THROW(PUSH_TX(db, trx), tx_missing_active_auth); + + ////// + // Charlie attempts to transfer 100 CORE from Alice's account to Diana + // This attempt should fail because Alice has not authorized anyone to transfer from her account + ////// + transfer_operation charlie_transfers_from_alice_to_diana; + charlie_transfers_from_alice_to_diana.to = diana.get_id(); + charlie_transfers_from_alice_to_diana.from = alice.get_id(); + charlie_transfers_from_alice_to_diana.amount.amount = 25 * GRAPHENE_BLOCKCHAIN_PRECISION; + trx.operations = {charlie_transfers_from_alice_to_diana}; + sign(trx, charlie_private_key); + BOOST_CHECK_THROW(PUSH_TX(db, trx), tx_missing_active_auth); + + ////// + // Create a custom authority where Bob is authorized to transfer from Alice's account to Charlie + ////// + custom_authority_create_operation op; + op.account = alice.get_id(); + op.auth.add_authority(bob.get_id(), 1); + op.auth.weight_threshold = 1; + op.enabled = true; + op.valid_to = db.head_block_time() + 1000; + op.operation_type = operation::tag::value; + auto to_index = member_index("to"); + vector restrictions; + restrictions.emplace_back(to_index, FUNC(eq), charlie.get_id()); + op.restrictions = restrictions; + //[ + // { + // "member_index": 2, + // "restriction_type": 0, + // "argument": [ + // 7, + // "1.2.18" + // ], + // "extensions": [] + // } + //] + + // Alice publishes the custom authority + trx.clear(); + trx.operations = {op}; + sign(trx, alice_private_key); + PUSH_TX(db, trx); + + custom_authority_id_type ca_bob_transfers_from_alice_to_charlie = + db.get_index_type().indices().get().find(alice_id)->id; + + ////// + // Bob attempts to transfer 100 CORE from Alice's account to Charlie + // This attempt should succeed because it is attempted after the custom authority is published + ////// + trx.clear(); + trx.operations = {bob_transfers_from_alice_to_charlie}; + sign(trx, bob_private_key); + PUSH_TX(db, trx); + + ////// + // Bob attempts to transfer 100 CORE from Alice's account to Diana + // This attempt should fail because Alice has not authorized Bob to transfer to Diana + ////// + trx.clear(); + trx.operations = {bob_transfers_from_alice_to_diana}; + sign(trx, charlie_private_key); + BOOST_CHECK_THROW(PUSH_TX(db, trx), tx_missing_active_auth); + + ////// + // Charlie attempts to transfer 100 CORE from Alice's account to Charlie + // This attempt should fail because Alice has not authorized Charlie to transfer to Diana + ////// + trx.clear(); + trx.operations = {charlie_transfers_from_alice_to_diana}; + sign(trx, charlie_private_key); + BOOST_CHECK_THROW(PUSH_TX(db, trx), tx_missing_active_auth); + + + + ////// + // Advance the blockchain to generate distinctive hash IDs for the re-used transactions + ////// + generate_blocks(1); + + ////// + // Create a custom authority where Charlie is authorized to transfer from Alice's account to Diana + ////// + op = custom_authority_create_operation(); + op.account = alice.get_id(); + op.auth.add_authority(charlie.get_id(), 1); + op.auth.weight_threshold = 1; + op.enabled = true; + op.valid_to = db.head_block_time() + 1000; + op.operation_type = operation::tag::value; + restrictions.clear(); + restrictions.emplace_back(to_index, FUNC(eq), diana.get_id()); + op.restrictions = restrictions; + //[ + // { + // "member_index": 2, + // "restriction_type": 0, + // "argument": [ + // 7, + // "1.2.19" + // ], + // "extensions": [] + // } + //] + + // Alice publishes the additional custom authority + trx.clear(); + trx.operations = {op}; + sign(trx, alice_private_key); + PUSH_TX(db, trx); + + // Note the additional custom authority + const auto &ca_index = db.get_index_type().indices().get(); + + auto ca_alice_range = ca_index.equal_range(alice_id); + long nbr_alice_auths = std::distance(ca_alice_range.first, ca_alice_range.second); + BOOST_CHECK_EQUAL(2, nbr_alice_auths); + auto iter = ca_alice_range.first; + custom_authority_id_type *ca_charlie_transfers_from_alice_to_diana = nullptr; + while (iter != ca_index.end()) { + custom_authority_id_type ca_id = iter->id; + const custom_authority_object *ca = db.find(ca_id); + flat_map ca_authorities = ca->auth.account_auths; + BOOST_CHECK_EQUAL(1, ca_authorities.size()); + if (ca_authorities.find(charlie.get_id()) != ca_authorities.end()) { + ca_charlie_transfers_from_alice_to_diana = &ca_id; + break; + } + + iter++; + } + BOOST_CHECK(ca_charlie_transfers_from_alice_to_diana != nullptr); + + ////// + // Charlie attempts to transfer 100 CORE from Alice's account to Diana + // This attempt should succeed because it is attempted after the custom authority is published + ////// + trx.clear(); + trx.operations = {charlie_transfers_from_alice_to_diana}; + sign(trx, charlie_private_key); + PUSH_TX(db, trx); + + ////// + // Bob should still be able to transfer from Alice to Charlie + // Bob attempts to transfer 100 CORE from Alice's account to Charlie + // This attempt should succeed because it was previously authorized by Alice + ////// + trx.clear(); + trx.operations = {bob_transfers_from_alice_to_charlie}; + sign(trx, bob_private_key); + PUSH_TX(db, trx); + + ////// + // Bob attempts to transfer 100 CORE from Alice's account to Diana + // This attempt should fail because Alice has not authorized Bob to transfer to Diana + ////// + trx.clear(); + trx.operations = {bob_transfers_from_alice_to_diana}; + sign(trx, bob_private_key); + BOOST_CHECK_THROW(PUSH_TX(db, trx), tx_missing_active_auth); + + + + ////// + // Advance the blockchain to generate distinctive hash IDs for the re-used transactions + ////// + generate_blocks(1); + + ////// + // Alice revokes the custom authority for Bob + ////// + custom_authority_delete_operation revoke_bob_authorization; + revoke_bob_authorization.account = alice.get_id(); + revoke_bob_authorization.authority_to_delete = ca_bob_transfers_from_alice_to_charlie; + trx.clear(); + trx.operations = {revoke_bob_authorization}; + sign(trx, alice_private_key); + PUSH_TX(db, trx); + + ////// + // Bob attempts to transfer 100 CORE from Alice's account to Charlie + // This attempt should fail because Alice has revoked authorized for Bob to transfer from her account + ////// + trx.clear(); + trx.operations = {bob_transfers_from_alice_to_charlie}; + sign(trx, bob_private_key); + BOOST_CHECK_THROW(PUSH_TX(db, trx), tx_missing_active_auth); + + ////// + // Charlie attempts to transfer 100 CORE from Alice's account to Diana + // This attempt should succeed because Alice should still be authorized to transfer from Alice account + ////// + trx.clear(); + trx.operations = {charlie_transfers_from_alice_to_diana}; + sign(trx, charlie_private_key); + PUSH_TX(db, trx); + + ////// + // Bob attempts to transfer 100 CORE from Alice's account to Diana + // This attempt should fail because Alice has not authorized Bob to transfer to Diana + ////// + trx.clear(); + trx.operations = {bob_transfers_from_alice_to_diana}; + sign(trx, bob_private_key); + BOOST_CHECK_THROW(PUSH_TX(db, trx), tx_missing_active_auth); + + } FC_LOG_AND_RETHROW() + } + BOOST_AUTO_TEST_SUITE_END() From 88f5fd1657ffc61c42d8c4d7c9b7a695322a60a8 Mon Sep 17 00:00:00 2001 From: Michel Santos Date: Mon, 9 Sep 2019 13:20:06 -0400 Subject: [PATCH 31/41] BSIP 40: Authorizations of multiple users with a single CAA containing logical OR branches --- tests/tests/custom_authority_tests.cpp | 118 ++++++++++++++++++++++++- 1 file changed, 115 insertions(+), 3 deletions(-) diff --git a/tests/tests/custom_authority_tests.cpp b/tests/tests/custom_authority_tests.cpp index abf7d8ac10..f2b60b7ffe 100644 --- a/tests/tests/custom_authority_tests.cpp +++ b/tests/tests/custom_authority_tests.cpp @@ -390,6 +390,113 @@ BOOST_AUTO_TEST_CASE(restriction_predicate_tests) { try { BOOST_CHECK(predicate(update) == true); } FC_LOG_AND_RETHROW() } + /** + * Test predicates containing logical ORs + * Test of authorization and revocation of one account (Alice) authorizing multiple other accounts (Bob and Charlie) + * to transfer out of her account by using a single custom active authority with two logical OR branches. + * + * This can alternatively be achieved by using two custom active authority authorizations + * as is done in multiple_transfer_custom_auths + */ + BOOST_AUTO_TEST_CASE(logical_or_transfer_predicate_tests) { + try { + using namespace graphene::protocol; + ////// + // Create a restriction that authorizes transfers only made to Account ID 12 or Account 15 + ////// + auto to_index = member_index("to"); + vector branch1 = vector{restriction(to_index, FUNC(eq), account_id_type(12))}; + vector branch2 = vector{restriction(to_index, FUNC(eq), account_id_type(15))}; + unsigned_int dummy_index = 999; + vector or_restrictions = { + restriction(dummy_index, FUNC(logical_or), vector>{branch1, branch2})}; + //[ + // { + // "member_index": 999, + // "restriction_type": 11, + // "argument": [ + // 40, + // [ + // [ + // { + // "member_index": 2, + // "restriction_type": 0, + // "argument": [ + // 7, + // "1.2.12" + // ], + // "extensions": [] + // } + // ], + // [ + // { + // "member_index": 2, + // "restriction_type": 0, + // "argument": [ + // 7, + // "1.2.15" + // ], + // "extensions": [] + // } + // ] + // ] + // ], + // "extensions": [] + // } + //] + auto predicate = get_restriction_predicate(or_restrictions, operation::tag::value); + + ////// + // Create an operation that transfers to Account ID 12 + // This should satisfy the restriction because Account ID 12 is authorized to transfer + ////// + transfer_operation transfer_to_12 = transfer_operation(); + transfer_to_12.to = account_id_type(12); + BOOST_CHECK_EQUAL(predicate(transfer_to_12).rejection_path.size(), 0); + + ////// + // Create an operation that transfers to Account ID 15 + // This should satisfy the restriction because Account ID 12 is authorized to transfer + ////// + transfer_operation transfer_to_15 = transfer_operation(); + transfer_to_15.to = account_id_type(15); + BOOST_CHECK(predicate(transfer_to_15) == true); + BOOST_CHECK_EQUAL(predicate(transfer_to_15).rejection_path.size(), 0); + + ////// + // Create an operation that transfers to Account ID 1 + // This should violate the restriction because Account 1 is not authorized to transfer + ////// + transfer_operation transfer_to_1; + transfer_to_1.to = account_id_type(1); + BOOST_CHECK(predicate(transfer_to_1) == false); + BOOST_CHECK_EQUAL(predicate(transfer_to_1).rejection_path.size(), 2); + // Index 0 (the outer-most) rejection path refers to the first and only restriction + BOOST_CHECK(predicate(transfer_to_1).rejection_path[0].get() == 0); + // Index 1 (the inner-most) rejection path refers to the first and only argument: + // the vector of branches each of which are one level deep + vector branch_results = predicate( + transfer_to_1).rejection_path[1].get>(); + unsigned long nbr_branches = branch_results.size(); + BOOST_CHECK_EQUAL(nbr_branches, 2); + for (unsigned long j = 0; j < nbr_branches; ++j) { + predicate_result &result = branch_results.at(j); + BOOST_CHECK_EQUAL(result.success, false); + + BOOST_CHECK_EQUAL(result.rejection_path.size(), 2); + // Index 0 (the outer-most) rejection path refers to the first and only restriction + BOOST_CHECK_EQUAL(result.rejection_path[0].get(), 0); + // Index 1 (the inner-most) rejection path refers to the first and only argument for an account ID: + // either 1.2.12 or 1.2.15 + BOOST_CHECK(result.rejection_path[1].get() == + predicate_result::predicate_was_false); + + } + + } FC_LOG_AND_RETHROW() + } + + BOOST_AUTO_TEST_CASE(custom_auths) { try { ////// // Initialize the test @@ -571,9 +678,14 @@ BOOST_AUTO_TEST_CASE(custom_auths) { try { } FC_LOG_AND_RETHROW() } - // Test of authorization and revocation of one account (Alice) authorizing multiple other accounts (Bob and Charlie) - // to transfer out of her account - BOOST_AUTO_TEST_CASE(selective_custom_auths) { + /** + * Test of authorization and revocation of one account (Alice) authorizing multiple other accounts (Bob and Charlie) + * to transfer out of her account by using two distinct custom active authorities. + * + * This can alternatively be achieved by using a single custom active authority with two logical OR branches + * as is done in logical_or_transfer_predicate_tests + */ + BOOST_AUTO_TEST_CASE(multiple_transfer_custom_auths) { try { ////// // Initialize the test From 69acd0dd520c6e9759b607e9cb94310870a41e1d Mon Sep 17 00:00:00 2001 From: Michel Santos Date: Thu, 19 Sep 2019 21:48:00 -0400 Subject: [PATCH 32/41] BSIP 40: Unit test for CAA trading --- tests/tests/custom_authority_tests.cpp | 187 +++++++++++++++++++++++++ 1 file changed, 187 insertions(+) diff --git a/tests/tests/custom_authority_tests.cpp b/tests/tests/custom_authority_tests.cpp index f2b60b7ffe..51821b941b 100644 --- a/tests/tests/custom_authority_tests.cpp +++ b/tests/tests/custom_authority_tests.cpp @@ -26,8 +26,10 @@ #include #include #include +#include #include #include +#include #include "../common/database_fixture.hpp" @@ -935,4 +937,189 @@ BOOST_AUTO_TEST_CASE(custom_auths) { try { } FC_LOG_AND_RETHROW() } + /** + * Test of authorization and revocation of one account (Alice) authorizing another account (Bob) + * to trade with her account but not to transfer out of her account + */ + BOOST_AUTO_TEST_CASE(authorized_trader_custom_auths) { + try { + ////// + // Initialize the blockchain + ////// + generate_blocks(HARDFORK_BSIP_40_TIME); + generate_blocks(5); + db.modify(global_property_id_type()(db), [](global_property_object &gpo) { + gpo.parameters.extensions.value.custom_authority_options = custom_authority_options_type(); + }); + set_expiration(db, trx); + + + ////// + // Initialize: Define a market-issued asset called USDBIT + ////// + ACTORS((feedproducer)); + const auto &bitusd = create_bitasset("USDBIT", feedproducer_id); + const auto &core = asset_id_type()(db); + update_feed_producers(bitusd, {feedproducer.id}); + + price_feed current_feed; + current_feed.maintenance_collateral_ratio = 1750; + current_feed.maximum_short_squeeze_ratio = 1100; + current_feed.settlement_price = bitusd.amount(1) / core.amount(5); + publish_feed(bitusd, feedproducer, current_feed); + + + ////// + // Initialize: Fund some accounts + ////// + ACTORS((alice)(bob)(charlie)(diana)) + fund(alice, asset(5000 * GRAPHENE_BLOCKCHAIN_PRECISION)); + fund(bob, asset(100 * GRAPHENE_BLOCKCHAIN_PRECISION)); + + + ////// + // Bob attempts to create a limit order on behalf of Alice + // This should fail because Bob is not authorized to trade with her account + ////// + set_expiration( db, trx ); + trx.operations.clear(); + + limit_order_create_operation buy_order; + buy_order.seller = alice_id; + buy_order.amount_to_sell = core.amount(59); + buy_order.min_to_receive = bitusd.amount(7); + buy_order.expiration = time_point_sec::maximum(); + + trx.clear(); + trx.operations = {buy_order}; + sign(trx, bob_private_key); + BOOST_CHECK_THROW(PUSH_TX(db, trx), tx_missing_active_auth); + + + ////// + // Alice authorizes Bob to place limit orders that offer the core asset for sale + ////// + custom_authority_create_operation authorize_limit_orders; + authorize_limit_orders.account = alice.get_id(); + authorize_limit_orders.auth.add_authority(bob.get_id(), 1); + authorize_limit_orders.auth.weight_threshold = 1; + authorize_limit_orders.enabled = true; + authorize_limit_orders.valid_to = db.head_block_time() + 1000; + authorize_limit_orders.operation_type = operation::tag::value; + trx.clear(); + trx.operations = {authorize_limit_orders}; + sign(trx, alice_private_key); + PUSH_TX(db, trx); + + auto caa = + db.get_index_type().indices().get().find(alice.get_id()); + custom_authority_id_type auth_id = caa->id; + + custom_authority_create_operation authorize_limit_order_cancellations; + authorize_limit_order_cancellations.account = alice.get_id(); + authorize_limit_order_cancellations.auth.add_authority(bob.get_id(), 1); + authorize_limit_order_cancellations.auth.weight_threshold = 1; + authorize_limit_order_cancellations.enabled = true; + authorize_limit_order_cancellations.valid_to = db.head_block_time() + 1000; + authorize_limit_order_cancellations.operation_type = operation::tag::value; + trx.clear(); + trx.operations = {authorize_limit_order_cancellations}; + sign(trx, alice_private_key); + PUSH_TX(db, trx); + + + ////// + // Advance the blockchain to generate a distinctive hash ID for the buy order transaction + ////// + generate_blocks(1); + + + ////// + // Bob attempts to create a limit order on behalf of Alice + // This should succeed because Bob is authorized to create limit orders + ////// + trx.clear(); + trx.operations = {buy_order}; + sign(trx, bob_private_key); + auto processed_buy = PUSH_TX(db, trx); + const limit_order_object *buy_order_object = db.find( processed_buy.operation_results[0].get() ); + + + ////// + // Bob attempts to cancel the limit order on behalf of Alice + // This should succeed because Bob is authorized to cancel limit orders + ////// + limit_order_cancel_operation cancel_order; + cancel_order.fee_paying_account = alice_id; + cancel_order.order = buy_order_object->id; + trx.clear(); + trx.operations = {cancel_order}; + sign(trx, bob_private_key); + auto processed_cancelled = PUSH_TX(db, trx); + + ////// + // Bob attempts to transfer funds out of Alice's account + // This should fail because Bob is not authorized to transfer funds out of her account + ////// + transfer_operation top; + top.to = bob.get_id(); + top.from = alice.get_id(); + top.amount.amount = 99 * GRAPHENE_BLOCKCHAIN_PRECISION; + trx.operations = {top}; + sign(trx, bob_private_key); + BOOST_CHECK_THROW(PUSH_TX(db, trx), tx_missing_active_auth); + + + ////// + // Advance the blockchain to generate a distinctive hash ID for the buy order transaction + ////// + generate_blocks(1); + + + ////// + // Alice attempts to create her own limit order + // This should succeed because Alice has not relinquished her own authority to trade + ////// + buy_order = limit_order_create_operation(); + buy_order.seller = alice_id; + buy_order.amount_to_sell = core.amount(59); + buy_order.min_to_receive = bitusd.amount(7); + buy_order.expiration = time_point_sec::maximum(); + trx.clear(); + trx.operations = {buy_order}; + sign(trx, alice_private_key); + PUSH_TX(db, trx); + + + ////// + // Alice revokes/disables the authorization to create limit orders + ////// + custom_authority_update_operation disable_authorizations; + disable_authorizations.account = alice.get_id(); + disable_authorizations.authority_to_update = auth_id; + disable_authorizations.new_enabled = false; + trx.clear(); + trx.operations = {disable_authorizations}; + sign(trx, alice_private_key); + PUSH_TX(db, trx); + + + ////// + // Advance the blockchain to generate a distinctive hash ID for the buy order transaction + ////// + generate_blocks(1); + + + ////// + // Bob attempts to create a limit order on behalf of Alice + // This should fail because Bob is not authorized to trade with her account + ////// + trx.clear(); + trx.operations = {buy_order}; + sign(trx, bob_private_key); + BOOST_CHECK_THROW(PUSH_TX(db, trx), tx_missing_active_auth); + + } FC_LOG_AND_RETHROW() + } + BOOST_AUTO_TEST_SUITE_END() From 7429cc4433240ba6155a29e5d51c2cfe5b0951de Mon Sep 17 00:00:00 2001 From: Michel Santos Date: Fri, 20 Sep 2019 18:47:29 -0400 Subject: [PATCH 33/41] BSIP 40: Unit test of feed publishing CAA for an account --- tests/tests/custom_authority_tests.cpp | 172 +++++++++++++++++++++++++ 1 file changed, 172 insertions(+) diff --git a/tests/tests/custom_authority_tests.cpp b/tests/tests/custom_authority_tests.cpp index 51821b941b..f3e0025e84 100644 --- a/tests/tests/custom_authority_tests.cpp +++ b/tests/tests/custom_authority_tests.cpp @@ -1122,4 +1122,176 @@ BOOST_AUTO_TEST_CASE(custom_auths) { try { } FC_LOG_AND_RETHROW() } + + /** + * Test of authorization of one account (feedproducer) authorizing another account (Bob) + * to publish feeds. The authorization remains associated with account even when the account changes its keys. + */ + BOOST_AUTO_TEST_CASE(feed_publisher_authorizes_other_account) { + try { + ////// + // Initialize the blockchain + ////// + generate_blocks(HARDFORK_BSIP_40_TIME); + generate_blocks(5); + db.modify(global_property_id_type()(db), [](global_property_object &gpo) { + gpo.parameters.extensions.value.custom_authority_options = custom_authority_options_type(); + }); + set_expiration(db, trx); + + + ////// + // Initialize: Define a market-issued asset called USDBIT + ////// + ACTORS((feedproducer)); + const auto &bitusd = create_bitasset("USDBIT", feedproducer_id); + const auto &core = asset_id_type()(db); + update_feed_producers(bitusd, {feedproducer.id}); + + price_feed current_feed; + current_feed.maintenance_collateral_ratio = 1750; + current_feed.maximum_short_squeeze_ratio = 1100; + current_feed.settlement_price = bitusd.amount(1) / core.amount(5); + publish_feed(bitusd, feedproducer, current_feed); + + + ////// + // Initialize: Fund other accounts + ////// + ACTORS((bob)) + fund(bob, asset(100 * GRAPHENE_BLOCKCHAIN_PRECISION)); + + + ////// + // Advance the blockchain to generate a distinctive hash ID for the publish feed transaction + ////// + generate_blocks(1); + + + ////// + // Bob attempts to publish feed of USDBIT on behalf of feedproducer + // This should fail because Bob is not authorized to publish the feed + ////// + asset_publish_feed_operation pop; + pop.publisher = feedproducer.id; + pop.asset_id = bitusd.id; + pop.feed = current_feed; + if (pop.feed.core_exchange_rate.is_null()) + pop.feed.core_exchange_rate = pop.feed.settlement_price; + trx.clear(); + trx.operations.emplace_back(std::move(pop)); + sign(trx, bob_private_key); + BOOST_CHECK_THROW(PUSH_TX(db, trx), tx_missing_active_auth); + + + ////// + // feedproducer authorizes Bob to publish feeds on its behalf + ////// + custom_authority_create_operation authorize_feed_publishing; + authorize_feed_publishing.account = feedproducer.get_id(); + authorize_feed_publishing.auth.add_authority(bob.get_id(), 1); + authorize_feed_publishing.auth.weight_threshold = 1; + authorize_feed_publishing.enabled = true; + authorize_feed_publishing.valid_to = db.head_block_time() + 1000; + authorize_feed_publishing.operation_type = operation::tag::value; + trx.clear(); + trx.operations = {authorize_feed_publishing}; + sign(trx, feedproducer_private_key); + PUSH_TX(db, trx); + + custom_authority_id_type auth_id = + db.get_index_type().indices().get().find(feedproducer.id)->id; + + ////// + // Bob attempts to publish feed of USDBIT on behalf of feedproducer + // This should succeed because Bob is authorized by feedproducer to publish the feed + ////// + trx.clear(); + trx.operations.emplace_back(std::move(pop)); + sign(trx, bob_private_key); + PUSH_TX(db, trx); + + + ////// + // Advance the blockchain to generate a distinctive hash ID for the publish feed transaction + ////// + generate_blocks(1); + + + ////// + // Bob creates a new key + ////// + fc::ecc::private_key new_bob_private_key = generate_private_key("new Bob key"); + public_key_type new_bob_public_key = public_key_type(new_bob_private_key.get_public_key()); + + + ////// + // Bob attempts to publish feed of USDBIT on behalf of feedproducer with new key + // This should fail because the new key is not associated with Bob on the blockchain + ////// + trx.clear(); + trx.operations.emplace_back(std::move(pop)); + sign(trx, new_bob_private_key); + BOOST_CHECK_THROW(PUSH_TX(db, trx), tx_missing_active_auth); + + + ////// + // Advance the blockchain to generate a distinctive hash ID for the publish feed transaction + ////// + generate_blocks(1); + + + ////// + // Bob changes his account's active key + ////// + account_update_operation uop; + uop.account = bob.get_id(); + uop.active = authority(1, new_bob_public_key, 1); + trx.clear(); + trx.operations.emplace_back(std::move(uop)); + sign(trx, bob_private_key); + PUSH_TX(db, trx); + + + ////// + // Bob attempts to publish feed of USDBIT on behalf of feedproducer + // This should succeed because Bob's new key is associated with Bob's authorized account. + ////// + trx.clear(); + trx.operations.emplace_back(std::move(pop)); + sign(trx, new_bob_private_key); + PUSH_TX(db, trx); + + + ////// + // Alice revokes/disables the authorization by disabling + ////// + custom_authority_update_operation disable_authorizations; + disable_authorizations.account = feedproducer.get_id(); + disable_authorizations.authority_to_update = auth_id; + disable_authorizations.new_enabled = false; + trx.clear(); + trx.operations = {disable_authorizations}; + sign(trx, feedproducer_private_key); + PUSH_TX(db, trx); + + + ////// + // Advance the blockchain to generate a distinctive hash ID for the publish feed transaction + ////// + generate_blocks(1); + + + ////// + // Bob attempts to publish feed of USDBIT on behalf of feedproducer with new key + // This should fail because Bob's account is no longer authorized by Alice + ////// + trx.clear(); + trx.operations.emplace_back(std::move(pop)); + sign(trx, new_bob_private_key); + BOOST_CHECK_THROW(PUSH_TX(db, trx), tx_missing_active_auth); + + } FC_LOG_AND_RETHROW() + } + BOOST_AUTO_TEST_SUITE_END() From 7486c9c111a244c855e91317e5facffa377b735e Mon Sep 17 00:00:00 2001 From: Michel Santos Date: Sat, 21 Sep 2019 12:20:12 -0400 Subject: [PATCH 34/41] BSIP 40: Unit test of feed publishing CAA for a key --- tests/tests/custom_authority_tests.cpp | 81 ++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/tests/tests/custom_authority_tests.cpp b/tests/tests/custom_authority_tests.cpp index f3e0025e84..3feff39836 100644 --- a/tests/tests/custom_authority_tests.cpp +++ b/tests/tests/custom_authority_tests.cpp @@ -1294,4 +1294,85 @@ BOOST_AUTO_TEST_CASE(custom_auths) { try { } FC_LOG_AND_RETHROW() } + + /** + * Test of authorization of one account (feedproducer) authorizing another key + * to publish feeds + */ + BOOST_AUTO_TEST_CASE(authorized_feed_publisher_other_key_custom_auths) { + try { + ////// + // Initialize the blockchain + ////// + generate_blocks(HARDFORK_BSIP_40_TIME); + generate_blocks(5); + db.modify(global_property_id_type()(db), [](global_property_object &gpo) { + gpo.parameters.extensions.value.custom_authority_options = custom_authority_options_type(); + }); + set_expiration(db, trx); + + + ////// + // Initialize: Define a market-issued asset called USDBIT + ////// + ACTORS((feedproducer)); + const auto &bitusd = create_bitasset("USDBIT", feedproducer_id); + const auto &core = asset_id_type()(db); + update_feed_producers(bitusd, {feedproducer.id}); + + price_feed current_feed; + current_feed.maintenance_collateral_ratio = 1750; + current_feed.maximum_short_squeeze_ratio = 1100; + current_feed.settlement_price = bitusd.amount(1) / core.amount(5); + // publish_feed(bitusd, feedproducer, current_feed); + asset_publish_feed_operation pop; + pop.publisher = feedproducer.id; + pop.asset_id = bitusd.id; + pop.feed = current_feed; + if (pop.feed.core_exchange_rate.is_null()) + pop.feed.core_exchange_rate = pop.feed.settlement_price; + + + ////// + // Advance the blockchain to generate a distinctive hash ID for the publish feed transaction + ////// + generate_blocks(1); + + + ////// + // Define a key that can be authorized + // This can be a new key or an existing key. The existing key may even be the active key of an account. + ////// + fc::ecc::private_key some_private_key = generate_private_key("some key"); + public_key_type some_public_key = public_key_type(some_private_key.get_public_key()); + + + ////// + // feedproducer authorizes a key to publish feeds on its behalf + ////// + custom_authority_create_operation authorize_feed_publishing; + authorize_feed_publishing.account = feedproducer.get_id(); + authorize_feed_publishing.auth.add_authority(some_public_key, 1); + authorize_feed_publishing.auth.weight_threshold = 1; + authorize_feed_publishing.enabled = true; + authorize_feed_publishing.valid_to = db.head_block_time() + 1000; + authorize_feed_publishing.operation_type = operation::tag::value; + trx.clear(); + trx.operations = {authorize_feed_publishing}; + sign(trx, feedproducer_private_key); + PUSH_TX(db, trx); + + + ////// + // Any software client with this key attempts to publish feed of USDBIT on behalf of feedproducer + // This should succeed because the pusher of this transaction signs the transaction with the authorized key + ////// + trx.clear(); + trx.operations.emplace_back(std::move(pop)); + sign(trx, some_private_key); + PUSH_TX(db, trx); + + } FC_LOG_AND_RETHROW() + } + BOOST_AUTO_TEST_SUITE_END() From af4cea7ecbd4037a582fd18cd0a63e545d5f863f Mon Sep 17 00:00:00 2001 From: Michel Santos Date: Sat, 21 Sep 2019 14:26:34 -0400 Subject: [PATCH 35/41] BSIP 40: Unit test of faucet key CAA --- tests/tests/custom_authority_tests.cpp | 149 +++++++++++++++++++++++++ 1 file changed, 149 insertions(+) diff --git a/tests/tests/custom_authority_tests.cpp b/tests/tests/custom_authority_tests.cpp index 3feff39836..a6d99d5179 100644 --- a/tests/tests/custom_authority_tests.cpp +++ b/tests/tests/custom_authority_tests.cpp @@ -1375,4 +1375,153 @@ BOOST_AUTO_TEST_CASE(custom_auths) { try { } FC_LOG_AND_RETHROW() } + + /** + * Test of authorization of one account (faucet) authorizing another key + * to register accounts + */ + BOOST_AUTO_TEST_CASE(authorized_faucet_other_key_custom_auths) { + try { + ////// + // Initialize the blockchain + ////// + generate_blocks(HARDFORK_BSIP_40_TIME); + generate_blocks(5); + db.modify(global_property_id_type()(db), [](global_property_object &gpo) { + gpo.parameters.extensions.value.custom_authority_options = custom_authority_options_type(); + }); + set_expiration(db, trx); + + + ////// + // Initialize: faucet account + ////// + ACTORS((faucet)(charlie)); + fund(faucet, asset(500000 * GRAPHENE_BLOCKCHAIN_PRECISION)); + account_upgrade_operation uop; + uop.account_to_upgrade = faucet.get_id(); + uop.upgrade_to_lifetime_member = true; + trx.clear(); + trx.operations.emplace_back(std::move(uop)); + sign(trx, faucet_private_key); + PUSH_TX(db, trx); + + // Lambda for creating account + auto create_account_by_name = [&](const string &name, const account_object& registrar) { + account_create_operation create_op; + create_op.name = name; + public_key_type new_key = public_key_type(generate_private_key(name + " seed").get_public_key()); + create_op.registrar = registrar.id; + create_op.owner = authority(1, new_key, 1); + create_op.active = authority(1, new_key, 1); + create_op.options.memo_key = new_key; + create_op.options.voting_account = GRAPHENE_PROXY_TO_SELF_ACCOUNT; + + return create_op; + }; + + + ////// + // Attempt to register an account with this key + // This should succeed because faucet is a lifetime member account + ////// + string name = "account1"; + account_create_operation create_op = create_account_by_name(name, faucet); + trx.clear(); + trx.operations = {create_op}; + sign(trx, faucet_private_key); + PUSH_TX(db, trx); + + + ////// + // Define a key that can be authorized + // This can be a new key or an existing key. The existing key may even be the active key of an account. + ////// + fc::ecc::private_key some_private_key = generate_private_key("some key"); + public_key_type some_public_key = public_key_type(some_private_key.get_public_key()); + + + ////// + // Attempt to register an account with this key + // This should fail because the key is not authorized to register any accounts + ////// + name = "account2"; + create_op = create_account_by_name(name, faucet); + trx.clear(); + trx.operations = {create_op}; + sign(trx, some_private_key); + BOOST_CHECK_THROW(PUSH_TX(db, trx), tx_missing_active_auth); + + + ////// + // faucet authorizes a key to register accounts on its behalf + ////// + custom_authority_create_operation authorize_account_registration; + authorize_account_registration.account = faucet.get_id(); + authorize_account_registration.auth.add_authority(some_public_key, 1); + authorize_account_registration.auth.weight_threshold = 1; + authorize_account_registration.enabled = true; + authorize_account_registration.valid_to = db.head_block_time() + 1000; + authorize_account_registration.operation_type = operation::tag::value; + trx.clear(); + trx.operations = {authorize_account_registration}; + sign(trx, faucet_private_key); + PUSH_TX(db, trx); + + + ////// + // Advance the blockchain to generate a distinctive hash ID for the account registration transaction + ////// + generate_blocks(1); + + + ////// + // Attempt to register an account with this key + // This should succeed because the key is authorized to register any accounts + ////// + trx.clear(); + trx.operations.emplace_back(std::move(create_op)); + sign(trx, some_private_key); + PUSH_TX(db, trx); + + + ////// + // Attempt to register an account with this key + // This should succeed because the key is authorized to register any accounts + ////// + create_op = create_account_by_name("account3", faucet); + trx.clear(); + trx.operations = {create_op}; + sign(trx, some_private_key); + PUSH_TX(db, trx); + + + ////// + // Attempt to transfer funds out of the faucet account + // This should fail because the key is not authorized to top from the faucet account + ////// + transfer_operation top; + top.amount.amount = 99 * GRAPHENE_BLOCKCHAIN_PRECISION; + top.from = faucet.get_id(); + top.to = charlie.get_id(); + top.fee.asset_id = asset_id_type(1); + trx.clear(); + trx.operations = {top}; + sign(trx, some_private_key); + BOOST_CHECK_THROW(PUSH_TX(db, trx), tx_missing_active_auth); + + + ////// + // Attempt to register an account with this key + // This should succeed because the key is authorized to register any accounts + ////// + create_op = create_account_by_name("account4", faucet); + trx.clear(); + trx.operations = {create_op}; + sign(trx, some_private_key); + PUSH_TX(db, trx); + + } FC_LOG_AND_RETHROW() + } + BOOST_AUTO_TEST_SUITE_END() From 58d556dc7a94a8f4f07ebbcfd2f7579d69ea9e40 Mon Sep 17 00:00:00 2001 From: Michel Santos Date: Sun, 22 Sep 2019 21:43:20 -0400 Subject: [PATCH 36/41] BSIP 40: Unit test of cold storage key CAA --- tests/tests/custom_authority_tests.cpp | 168 +++++++++++++++++++++++++ 1 file changed, 168 insertions(+) diff --git a/tests/tests/custom_authority_tests.cpp b/tests/tests/custom_authority_tests.cpp index a6d99d5179..9b04f5c304 100644 --- a/tests/tests/custom_authority_tests.cpp +++ b/tests/tests/custom_authority_tests.cpp @@ -1524,4 +1524,172 @@ BOOST_AUTO_TEST_CASE(custom_auths) { try { } FC_LOG_AND_RETHROW() } + /** + * Test of authorization of a key to transfer one asset type (USDBIT) from one account (coldwallet) + * to another account (hotwallet) + */ + BOOST_AUTO_TEST_CASE(authorized_cold_wallet_key_custom_auths) { + try { + ////// + // Initialize the blockchain + ////// + generate_blocks(HARDFORK_BSIP_40_TIME); + generate_blocks(5); + db.modify(global_property_id_type()(db), [](global_property_object &gpo) { + gpo.parameters.extensions.value.custom_authority_options = custom_authority_options_type(); + }); + set_expiration(db, trx); + + + ////// + // Initialize: Accounts + ACTORS((feedproducer)(coldwallet)(hotwallet)(hacker)); + int64_t init_balance(100 * GRAPHENE_BLOCKCHAIN_PRECISION); + + ////// + // Initialize: Define a market-issued asset called USDBIT + ////// + // Define core asset + const auto &core = asset_id_type()(db); + asset_id_type core_id = core.id; + + // Create a smart asset + const asset_object &bitusd = create_bitasset("USDBIT", feedproducer_id); + asset_id_type usd_id = bitusd.id; + update_feed_producers(bitusd, {feedproducer.id}); + price_feed current_feed; + current_feed.maintenance_collateral_ratio = 1750; + current_feed.maximum_short_squeeze_ratio = 1100; + current_feed.settlement_price = bitusd.amount(1) / core.amount(5); + publish_feed(bitusd, feedproducer, current_feed); + + + ////// + // Fund coldwallet with core asset + ////// + fund(coldwallet, asset(init_balance)); + // coldwallet will borrow 1000 bitUSD + borrow(coldwallet, bitusd.amount(1000), asset(15000)); + int64_t alice_balance_usd_before_offer = get_balance(coldwallet_id, usd_id); + BOOST_CHECK_EQUAL( 1000, alice_balance_usd_before_offer); + int64_t coldwallet_balance_core_before_offer = get_balance(coldwallet_id, core_id); + BOOST_CHECK_EQUAL( init_balance - 15000, coldwallet_balance_core_before_offer ); + + + ////// + // Define a key that can be authorized + // This can be a new key or an existing key. The existing key may even be the active key of an account. + ////// + fc::ecc::private_key some_private_key = generate_private_key("some key"); + public_key_type some_public_key = public_key_type(some_private_key.get_public_key()); + + + ////// + // Create a custom authority where the key is authorized to transfer from the coldwallet account + // if and only if the transfer asset type is USDBIT and the recipient account is hotwallet. + ////// + custom_authority_create_operation op; + op.account = coldwallet.get_id(); + op.auth.add_authority(some_public_key, 1); + op.auth.weight_threshold = 1; + op.enabled = true; + op.valid_to = db.head_block_time() + 1000; + + op.operation_type = operation::tag::value; + + auto to_index = member_index("to"); + op.restrictions.emplace_back(to_index, FUNC(eq), hotwallet_id); + + auto transfer_amount_index = member_index("amount"); + auto assed_id_index = member_index("asset_id"); + op.restrictions.emplace_back(restriction(transfer_amount_index, restriction::func_attr, vector{ + restriction(assed_id_index, restriction::func_eq, usd_id)})); + //[ + // { + // "member_index": 2, + // "restriction_type": 0, + // "argument": [ + // 7, + // "1.2.18" + // ], + // "extensions": [] + // }, + // { + // "member_index": 3, + // "restriction_type": 10, + // "argument": [ + // 39, + // [ + // { + // "member_index": 1, + // "restriction_type": 0, + // "argument": [ + // 8, + // "1.3.2" + // ], + // "extensions": [] + // } + // ] + // ], + // "extensions": [] + // } + //] + + // Publish the new custom authority + trx.clear(); + trx.operations = {op}; + sign(trx, coldwallet_private_key); + PUSH_TX(db, trx); + + + ////// + // Attempt to transfer USDBIT asset out of the coldwallet to the hacker account + // This should fail because the key is not authorized to transfer to the hacker account + ////// + transfer_operation top; + top.from = coldwallet.get_id(); + top.to = hacker.get_id(); + top.amount.asset_id = usd_id; + top.amount.amount = 99; + top.fee.asset_id = core_id; + trx.clear(); + trx.operations = {top}; + sign(trx, some_private_key); + BOOST_CHECK_THROW(PUSH_TX(db, trx), tx_missing_active_auth); + + + ////// + // Attempt to transfer CORE asset out of the coldwallet to the hotwallet account + // This should fail because the key is not authorized to transfer core asset to the hotwallet account + ////// + top = transfer_operation(); + top.from = coldwallet.get_id(); + top.to = hotwallet.get_id(); + top.amount.asset_id = core_id; + top.amount.amount = 99; + top.fee.asset_id = core_id; + trx.clear(); + trx.operations = {top}; + sign(trx, some_private_key); + BOOST_CHECK_THROW(PUSH_TX(db, trx), tx_missing_active_auth); + + + ////// + // Attempt to transfer USDBIT asset out of the coldwallet to the hotwallet account + // This should succeed because the key is authorized to transfer USDBIT asset to the hotwallet account + ////// + top = transfer_operation(); + top.from = coldwallet.get_id(); + top.to = hotwallet.get_id(); + top.amount.asset_id = usd_id; + top.amount.amount = 99; + top.fee.asset_id = core_id; + trx.clear(); + trx.operations = {top}; + sign(trx, some_private_key); + PUSH_TX(db, trx); + + } FC_LOG_AND_RETHROW() + } + BOOST_AUTO_TEST_SUITE_END() From ddc87259ffbbcf817d830d861a4a5360b7f608b4 Mon Sep 17 00:00:00 2001 From: Nathan Hourt Date: Fri, 18 Oct 2019 17:48:14 -0500 Subject: [PATCH 37/41] Update custom_authority_tests.cpp Just some typo fixes --- tests/tests/custom_authority_tests.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/tests/custom_authority_tests.cpp b/tests/tests/custom_authority_tests.cpp index 9b04f5c304..f3a675c4f0 100644 --- a/tests/tests/custom_authority_tests.cpp +++ b/tests/tests/custom_authority_tests.cpp @@ -458,7 +458,7 @@ BOOST_AUTO_TEST_CASE(restriction_predicate_tests) { try { ////// // Create an operation that transfers to Account ID 15 - // This should satisfy the restriction because Account ID 12 is authorized to transfer + // This should satisfy the restriction because Account ID 15 is authorized to transfer ////// transfer_operation transfer_to_15 = transfer_operation(); transfer_to_15.to = account_id_type(15); @@ -909,7 +909,7 @@ BOOST_AUTO_TEST_CASE(custom_auths) { try { ////// // Bob attempts to transfer 100 CORE from Alice's account to Charlie - // This attempt should fail because Alice has revoked authorized for Bob to transfer from her account + // This attempt should fail because Alice has revoked authorization for Bob to transfer from her account ////// trx.clear(); trx.operations = {bob_transfers_from_alice_to_charlie}; @@ -1264,7 +1264,7 @@ BOOST_AUTO_TEST_CASE(custom_auths) { try { ////// - // Alice revokes/disables the authorization by disabling + // Feedproducer revokes/disables the authorization by disabling it ////// custom_authority_update_operation disable_authorizations; disable_authorizations.account = feedproducer.get_id(); @@ -1284,7 +1284,7 @@ BOOST_AUTO_TEST_CASE(custom_auths) { try { ////// // Bob attempts to publish feed of USDBIT on behalf of feedproducer with new key - // This should fail because Bob's account is no longer authorized by Alice + // This should fail because Bob's account is no longer authorized by feedproducer ////// trx.clear(); trx.operations.emplace_back(std::move(pop)); @@ -1498,7 +1498,7 @@ BOOST_AUTO_TEST_CASE(custom_auths) { try { ////// // Attempt to transfer funds out of the faucet account - // This should fail because the key is not authorized to top from the faucet account + // This should fail because the key is not authorized to transfer from the faucet account ////// transfer_operation top; top.amount.amount = 99 * GRAPHENE_BLOCKCHAIN_PRECISION; From e658bdd788c9c3e343bbf84ceee2237d074ec7df Mon Sep 17 00:00:00 2001 From: Nathan Hourt Date: Sat, 19 Oct 2019 12:41:18 -0500 Subject: [PATCH 38/41] BSIP 40: Changes from code review --- .../chain/custom_authority_evaluator.cpp | 32 ++++++++++++------- .../chain/custom_authority_object.hpp | 11 ++++--- libraries/chain/proposal_object.cpp | 2 +- .../restriction_predicate.hxx | 11 +++---- .../graphene/protocol/chain_parameters.hpp | 4 +++ .../include/graphene/protocol/config.hpp | 8 +++-- .../include/graphene/protocol/restriction.hpp | 20 ++++++++++++ tests/tests/custom_authority_tests.cpp | 10 ++++++ 8 files changed, 72 insertions(+), 26 deletions(-) diff --git a/libraries/chain/custom_authority_evaluator.cpp b/libraries/chain/custom_authority_evaluator.cpp index dcba31dff1..654d57ac00 100644 --- a/libraries/chain/custom_authority_evaluator.cpp +++ b/libraries/chain/custom_authority_evaluator.cpp @@ -48,15 +48,21 @@ void_result custom_authority_create_evaluator::do_evaluate(const custom_authorit bool operation_forked_in = hardfork_visitor(now).visit((operation::tag_type)op.operation_type.value); FC_ASSERT(operation_forked_in, "Cannot create custom authority for operation which is not valid yet"); + auto restriction_count = std::for_each(op.restrictions.begin(), op.restrictions.end(), restriction::adder()).sum; + FC_ASSERT(restriction_count <= config->max_custom_authority_restrictions, + "Custom authority has more than the maximum number of restrictions"); + for (const auto& account_weight_pair : op.auth.account_auths) account_weight_pair.first(d); const auto& index = d.get_index_type().indices().get(); auto range = index.equal_range(op.account); FC_ASSERT(std::distance(range.first, range.second) < config->max_custom_authorities_per_account, - "Cannot create custom authority for account: account already has maximum number"); + "Cannot create custom authority: account already has maximum number"); + range = index.equal_range(boost::make_tuple(op.account, op.operation_type)); + FC_ASSERT(std::distance(range.first, range.second) < config->max_custom_authorities_per_account_op, + "Cannot create custom authority: account already has maximum number for this operation type"); - get_restriction_predicate(op.restrictions, op.operation_type); return void_result(); } FC_CAPTURE_AND_RETHROW((op)) } @@ -74,9 +80,6 @@ object_id_type custom_authority_create_evaluator::do_apply(const custom_authorit std::for_each(op.restrictions.begin(), op.restrictions.end(), [&obj](const restriction& r) mutable { obj.restrictions.insert(std::make_pair(obj.restriction_counter++, r)); }); - - // Update the predicate cache - obj.update_predicate_cache(); }).id; } FC_CAPTURE_AND_RETHROW((op)) } @@ -84,11 +87,9 @@ void_result custom_authority_update_evaluator::do_evaluate(const custom_authorit { try { const database& d = db(); auto now = d.head_block_time(); - FC_ASSERT(HARDFORK_BSIP_40_PASSED(now), "Custom active authorities are not yet enabled"); old_object = &op.authority_to_update(d); FC_ASSERT(old_object->account == op.account, "Cannot update a different account's custom authority"); - op.account(d); if (op.new_enabled) FC_ASSERT(*op.new_enabled != old_object->enabled, "Custom authority update specifies an enabled flag, but flag is not changed"); @@ -131,6 +132,17 @@ void_result custom_authority_update_evaluator::do_evaluate(const custom_authorit "Unable to add restrictions: causes wraparound of restriction IDs"); } + // Add up the restriction counts for all old restrictions not being removed, and all new ones + size_t restriction_count = 0; + for (const auto& restriction_pair : old_object->restrictions) + if (op.restrictions_to_remove.count(restriction_pair.first) == 0) + restriction_count += restriction_pair.second.restriction_count(); + restriction_count += std::for_each(op.restrictions_to_add.begin(), op.restrictions_to_add.end(), + restriction::adder()).sum; + // Check restriction count against limit + FC_ASSERT(restriction_count <= config->max_custom_authority_restrictions, + "Cannot update custom authority: updated authority would exceed the maximum number of restrictions"); + get_restriction_predicate(op.restrictions_to_add, old_object->operation_type); return void_result(); } FC_CAPTURE_AND_RETHROW((op)) } @@ -152,8 +164,8 @@ void_result custom_authority_update_evaluator::do_apply(const custom_authority_u obj.restrictions.insert(std::make_pair(obj.restriction_counter++, r)); }); - // Update the predicate cache - obj.update_predicate_cache(); + // Clear the predicate cache + obj.clear_predicate_cache(); }); return void_result(); @@ -162,9 +174,7 @@ void_result custom_authority_update_evaluator::do_apply(const custom_authority_u void_result custom_authority_delete_evaluator::do_evaluate(const custom_authority_delete_operation& op) { try { const database& d = db(); - FC_ASSERT(HARDFORK_BSIP_40_PASSED(d.head_block_time()), "Custom active authorities are not yet enabled"); - op.account(d); old_object = &op.authority_to_delete(d); FC_ASSERT(old_object->account == op.account, "Cannot delete a different account's custom authority"); diff --git a/libraries/chain/include/graphene/chain/custom_authority_object.hpp b/libraries/chain/include/graphene/chain/custom_authority_object.hpp index ff4a64bd62..c58b16d282 100644 --- a/libraries/chain/include/graphene/chain/custom_authority_object.hpp +++ b/libraries/chain/include/graphene/chain/custom_authority_object.hpp @@ -39,7 +39,7 @@ namespace graphene { namespace chain { class custom_authority_object : public abstract_object { /// Unreflected field to store a cache of the predicate function /// Note that this cache can be modified when the object is const! - optional predicate_cache; + mutable optional predicate_cache; public: static const uint8_t space_id = protocol_ids; @@ -66,16 +66,17 @@ namespace graphene { namespace chain { } /// Get predicate, from cache if possible, and update cache if not (modifies const object!) restriction_predicate_function get_predicate() const { - if (predicate_cache.valid()) - return *predicate_cache; + if (!predicate_cache.valid()) + update_predicate_cache(); - const_cast(this)->update_predicate_cache(); return *predicate_cache; } /// Regenerate predicate function and update predicate cache - void update_predicate_cache() { + void update_predicate_cache() const { predicate_cache = get_restriction_predicate(get_restrictions(), operation_type); } + /// Clear the cache of the predicate function + void clear_predicate_cache() { predicate_cache.reset(); } }; struct by_account_custom; diff --git a/libraries/chain/proposal_object.cpp b/libraries/chain/proposal_object.cpp index ab6d661ad8..3c5244d62c 100644 --- a/libraries/chain/proposal_object.cpp +++ b/libraries/chain/proposal_object.cpp @@ -41,7 +41,7 @@ bool proposal_object::is_authorized_to_execute( database& db ) const available_key_approvals, [&db]( account_id_type id ){ return &id( db ).active; }, [&db]( account_id_type id ){ return &id( db ).owner; }, - [&]( account_id_type id, const operation& op, rejected_predicate_map* rejects ){ + [&db]( account_id_type id, const operation& op, rejected_predicate_map* rejects ){ return db.get_viable_custom_authorities(id, op, rejects); }, allow_non_immediate_owner, MUST_IGNORE_CUSTOM_OP_REQD_AUTHS( db.head_block_time() ), diff --git a/libraries/protocol/custom_authorities/restriction_predicate.hxx b/libraries/protocol/custom_authorities/restriction_predicate.hxx index e5335bbb48..3b2d0c5e47 100644 --- a/libraries/protocol/custom_authorities/restriction_predicate.hxx +++ b/libraries/protocol/custom_authorities/restriction_predicate.hxx @@ -283,13 +283,12 @@ template struct predicate_has_all, std::enable_if_t && !is_flat_set && comparable_types>> { - predicate_has_all, flat_set> inner; // Field is other container; convert to flat_set constexpr static bool valid = true; bool operator()(const FieldContainer& f, const flat_set& a) const { if (f.size() < a.size()) return false; - flat_set fs(f.begin(), f.end()); - return inner(fs, a); + std::set fs(f.begin(), f.end()); + return std::includes(fs.begin(), fs.end(), a.begin(), a.end()); } }; template @@ -319,12 +318,10 @@ template struct predicate_has_none, std::enable_if_t && !is_flat_set && comparable_types>> { - predicate_has_none, flat_set> inner; - // Field is other container; convert to flat_set + // Field is other container constexpr static bool valid = true; bool operator()(const FieldContainer& f, const flat_set& a) const { - flat_set fs(f.begin(), f.end()); - return inner(fs, a); + return !std::any_of(f.begin(), f.end(), [&a](const auto& fe) { return a.count(fe) > 0; }); } }; template diff --git a/libraries/protocol/include/graphene/protocol/chain_parameters.hpp b/libraries/protocol/include/graphene/protocol/chain_parameters.hpp index 017abf6cf1..ff398cf815 100644 --- a/libraries/protocol/include/graphene/protocol/chain_parameters.hpp +++ b/libraries/protocol/include/graphene/protocol/chain_parameters.hpp @@ -39,6 +39,8 @@ namespace graphene { namespace protocol { { uint32_t max_custom_authority_lifetime_seconds = GRAPHENE_DEFAULT_MAX_CUSTOM_AUTHORITY_LIFETIME_SECONDS; uint32_t max_custom_authorities_per_account = GRAPHENE_DEFAULT_MAX_CUSTOM_AUTHORITIES_PER_ACCOUNT; + uint32_t max_custom_authorities_per_account_op = GRAPHENE_DEFAULT_MAX_CUSTOM_AUTHORITIES_PER_ACCOUNT_OP; + uint32_t max_custom_authority_restrictions = GRAPHENE_DEFAULT_MAX_CUSTOM_AUTHORITY_RESTRICTIONS; }; struct chain_parameters @@ -108,6 +110,8 @@ FC_REFLECT( graphene::protocol::htlc_options, FC_REFLECT( graphene::protocol::custom_authority_options_type, (max_custom_authority_lifetime_seconds) (max_custom_authorities_per_account) + (max_custom_authorities_per_account_op) + (max_custom_authority_restrictions) ) FC_REFLECT( graphene::protocol::chain_parameters::ext, diff --git a/libraries/protocol/include/graphene/protocol/config.hpp b/libraries/protocol/include/graphene/protocol/config.hpp index 3b005ee518..b535e67158 100644 --- a/libraries/protocol/include/graphene/protocol/config.hpp +++ b/libraries/protocol/include/graphene/protocol/config.hpp @@ -139,7 +139,11 @@ #define GRAPHENE_FBA_STEALTH_DESIGNATED_ASSET (asset_id_type(743)) -/// Maximum duration before a custom authority can expire -#define GRAPHENE_DEFAULT_MAX_CUSTOM_AUTHORITY_LIFETIME_SECONDS (60*60*24*365) +/// Maximum duration before a custom authority can expire (1 month) +#define GRAPHENE_DEFAULT_MAX_CUSTOM_AUTHORITY_LIFETIME_SECONDS (60*60*24*30) /// Maximum number of custom authorities a particular account can set #define GRAPHENE_DEFAULT_MAX_CUSTOM_AUTHORITIES_PER_ACCOUNT 10 +/// Maximum number of custom authorities a particular account can set for a particular operation +#define GRAPHENE_DEFAULT_MAX_CUSTOM_AUTHORITIES_PER_ACCOUNT_OP 3 +/// Maximum number of restrictions a custom authority can contain +#define GRAPHENE_DEFAULT_MAX_CUSTOM_AUTHORITY_RESTRICTIONS 10 diff --git a/libraries/protocol/include/graphene/protocol/restriction.hpp b/libraries/protocol/include/graphene/protocol/restriction.hpp index 718de3684a..bb0e4486a4 100644 --- a/libraries/protocol/include/graphene/protocol/restriction.hpp +++ b/libraries/protocol/include/graphene/protocol/restriction.hpp @@ -107,6 +107,26 @@ struct restriction { restriction() = default; restriction(const unsigned_int& member_index, function_type type, const argument_type& argument) : member_index(member_index), restriction_type(type), argument(argument) {} + + struct adder { + size_t sum = 0; + void operator()(const restriction& r) { sum += r.restriction_count(); } + void operator()(const vector& r) { sum += std::for_each(r.begin(), r.end(), adder()).sum; } + }; + + size_t restriction_count() const { + if (argument.is_type>()) { + const vector& rs = argument.get>(); + return 1 + std::for_each(rs.begin(), rs.end(), adder()).sum; + } else if (argument.is_type>>()) { + const vector>& rs = argument.get>>(); + return 1 + std::for_each(rs.begin(), rs.end(), adder()).sum; + } else if (argument.is_type()) { + const variant_assert_argument_type& arg = argument.get(); + return 1 + std::for_each(arg.second.begin(), arg.second.end(), adder()).sum; + } + return 1; + } }; } } // graphene::protocol diff --git a/tests/tests/custom_authority_tests.cpp b/tests/tests/custom_authority_tests.cpp index f3a675c4f0..b1afd8dbc7 100644 --- a/tests/tests/custom_authority_tests.cpp +++ b/tests/tests/custom_authority_tests.cpp @@ -52,6 +52,9 @@ BOOST_FIXTURE_TEST_SUITE(custom_authority_tests, database_fixture) #define FUNC(TYPE) BOOST_PP_CAT(restriction::func_, TYPE) +size_t restriction_count(const vector& rs) { + return std::for_each(rs.begin(), rs.end(), restriction::adder()).sum; +} template unsigned_int member_index(string name) { unsigned_int index; @@ -106,6 +109,7 @@ BOOST_AUTO_TEST_CASE(restriction_predicate_tests) { try { // "extensions": [] // } //] + BOOST_CHECK_EQUAL(restriction_count(restrictions), 1); BOOST_CHECK(get_restriction_predicate(restrictions, operation::tag::value)(transfer) .rejection_path.size() == 2); // Index 0 (the outer-most) rejection path refers to the first and only restriction @@ -222,6 +226,7 @@ BOOST_AUTO_TEST_CASE(restriction_predicate_tests) { try { // } //] + BOOST_CHECK_EQUAL(restriction_count(restrictions), 2); ////// // Check the transfer operation that pays the fee with Asset ID 0 against the restriction. // This should violate the restriction. @@ -275,6 +280,7 @@ BOOST_AUTO_TEST_CASE(restriction_predicate_tests) { try { // "extensions": [] // } //] + BOOST_CHECK_EQUAL(restriction_count(restrictions), 3); ////// // Create a transfer operation that authorizes transfer to Account ID 12 @@ -332,6 +338,7 @@ BOOST_AUTO_TEST_CASE(restriction_predicate_tests) { try { // "extensions": [] // } //] + BOOST_CHECK_EQUAL(restriction_count(restrictions), 2); auto predicate = get_restriction_predicate(restrictions, operation::tag::value); ////// @@ -446,6 +453,7 @@ BOOST_AUTO_TEST_CASE(restriction_predicate_tests) { try { // "extensions": [] // } //] + BOOST_CHECK_EQUAL(restriction_count(or_restrictions), 3); auto predicate = get_restriction_predicate(or_restrictions, operation::tag::value); ////// @@ -562,6 +570,7 @@ BOOST_AUTO_TEST_CASE(custom_auths) { try { // "extensions": [] // } //] + BOOST_CHECK_EQUAL(restriction_count(op.restrictions), 3); ////// @@ -1634,6 +1643,7 @@ BOOST_AUTO_TEST_CASE(custom_auths) { try { // "extensions": [] // } //] + BOOST_CHECK_EQUAL(restriction_count(op.restrictions), 3); // Publish the new custom authority trx.clear(); From 51da97d70c58565a71de070ce5db76d91e25b5c7 Mon Sep 17 00:00:00 2001 From: Nathan Hourt Date: Sun, 20 Oct 2019 08:55:49 -0500 Subject: [PATCH 39/41] BSIP 40: Code review, round 2 --- .../chain/custom_authority_evaluator.cpp | 5 +- libraries/chain/proposal_evaluator.cpp | 3 +- libraries/protocol/CMakeLists.txt | 1 + .../include/graphene/protocol/restriction.hpp | 21 +------- libraries/protocol/restriction.cpp | 53 +++++++++++++++++++ tests/tests/custom_authority_tests.cpp | 17 +++--- 6 files changed, 66 insertions(+), 34 deletions(-) create mode 100644 libraries/protocol/restriction.cpp diff --git a/libraries/chain/custom_authority_evaluator.cpp b/libraries/chain/custom_authority_evaluator.cpp index 654d57ac00..1962932c5c 100644 --- a/libraries/chain/custom_authority_evaluator.cpp +++ b/libraries/chain/custom_authority_evaluator.cpp @@ -48,7 +48,7 @@ void_result custom_authority_create_evaluator::do_evaluate(const custom_authorit bool operation_forked_in = hardfork_visitor(now).visit((operation::tag_type)op.operation_type.value); FC_ASSERT(operation_forked_in, "Cannot create custom authority for operation which is not valid yet"); - auto restriction_count = std::for_each(op.restrictions.begin(), op.restrictions.end(), restriction::adder()).sum; + auto restriction_count = restriction::restriction_count(op.restrictions); FC_ASSERT(restriction_count <= config->max_custom_authority_restrictions, "Custom authority has more than the maximum number of restrictions"); @@ -137,8 +137,7 @@ void_result custom_authority_update_evaluator::do_evaluate(const custom_authorit for (const auto& restriction_pair : old_object->restrictions) if (op.restrictions_to_remove.count(restriction_pair.first) == 0) restriction_count += restriction_pair.second.restriction_count(); - restriction_count += std::for_each(op.restrictions_to_add.begin(), op.restrictions_to_add.end(), - restriction::adder()).sum; + restriction_count += restriction::restriction_count(op.restrictions_to_add); // Check restriction count against limit FC_ASSERT(restriction_count <= config->max_custom_authority_restrictions, "Cannot update custom authority: updated authority would exceed the maximum number of restrictions"); diff --git a/libraries/chain/proposal_evaluator.cpp b/libraries/chain/proposal_evaluator.cpp index f783aed96e..349cc1b82a 100644 --- a/libraries/chain/proposal_evaluator.cpp +++ b/libraries/chain/proposal_evaluator.cpp @@ -95,8 +95,7 @@ struct proposal_operation_hardfork_visitor } void operator()(const graphene::chain::committee_member_update_global_parameters_operation &op) const { if (block_time < HARDFORK_CORE_1468_TIME) { - FC_ASSERT(!op.new_parameters.extensions.value.updatable_htlc_options.valid(), - "Unable to set HTLC options before hardfork 1468"); + FC_ASSERT(!op.new_parameters.extensions.value.updatable_htlc_options.valid(), "Unable to set HTLC options before hardfork 1468"); FC_ASSERT(!op.new_parameters.current_fees->exists()); FC_ASSERT(!op.new_parameters.current_fees->exists()); FC_ASSERT(!op.new_parameters.current_fees->exists()); diff --git a/libraries/protocol/CMakeLists.txt b/libraries/protocol/CMakeLists.txt index aa8c4293f2..877a1afc9f 100644 --- a/libraries/protocol/CMakeLists.txt +++ b/libraries/protocol/CMakeLists.txt @@ -16,6 +16,7 @@ list(APPEND SOURCES account.cpp asset.cpp authority.cpp special_authority.cpp + restriction.cpp custom_authority.cpp committee_member.cpp custom.cpp diff --git a/libraries/protocol/include/graphene/protocol/restriction.hpp b/libraries/protocol/include/graphene/protocol/restriction.hpp index bb0e4486a4..334cf63b03 100644 --- a/libraries/protocol/include/graphene/protocol/restriction.hpp +++ b/libraries/protocol/include/graphene/protocol/restriction.hpp @@ -108,25 +108,8 @@ struct restriction { restriction(const unsigned_int& member_index, function_type type, const argument_type& argument) : member_index(member_index), restriction_type(type), argument(argument) {} - struct adder { - size_t sum = 0; - void operator()(const restriction& r) { sum += r.restriction_count(); } - void operator()(const vector& r) { sum += std::for_each(r.begin(), r.end(), adder()).sum; } - }; - - size_t restriction_count() const { - if (argument.is_type>()) { - const vector& rs = argument.get>(); - return 1 + std::for_each(rs.begin(), rs.end(), adder()).sum; - } else if (argument.is_type>>()) { - const vector>& rs = argument.get>>(); - return 1 + std::for_each(rs.begin(), rs.end(), adder()).sum; - } else if (argument.is_type()) { - const variant_assert_argument_type& arg = argument.get(); - return 1 + std::for_each(arg.second.begin(), arg.second.end(), adder()).sum; - } - return 1; - } + static size_t restriction_count(const vector& restrictions); + size_t restriction_count() const; }; } } // graphene::protocol diff --git a/libraries/protocol/restriction.cpp b/libraries/protocol/restriction.cpp new file mode 100644 index 0000000000..908c5bea7b --- /dev/null +++ b/libraries/protocol/restriction.cpp @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2019 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 protocol { + +struct adder { + size_t sum = 0; + void operator()(const restriction& r) { sum += r.restriction_count(); } + void operator()(const vector& r) { sum += std::for_each(r.begin(), r.end(), adder()).sum; } +}; + +size_t restriction::restriction_count(const vector& restrictions) { + return std::for_each(restrictions.begin(), restrictions.end(), adder()).sum; +} + +size_t restriction::restriction_count() const { + if (argument.is_type>()) { + const vector& rs = argument.get>(); + return 1 + std::for_each(rs.begin(), rs.end(), adder()).sum; + } else if (argument.is_type>>()) { + const vector>& rs = argument.get>>(); + return 1 + std::for_each(rs.begin(), rs.end(), adder()).sum; + } else if (argument.is_type()) { + const variant_assert_argument_type& arg = argument.get(); + return 1 + std::for_each(arg.second.begin(), arg.second.end(), adder()).sum; + } + return 1; +} + +} } // namespace graphene::protocol diff --git a/tests/tests/custom_authority_tests.cpp b/tests/tests/custom_authority_tests.cpp index b1afd8dbc7..3d1c53649f 100644 --- a/tests/tests/custom_authority_tests.cpp +++ b/tests/tests/custom_authority_tests.cpp @@ -52,9 +52,6 @@ BOOST_FIXTURE_TEST_SUITE(custom_authority_tests, database_fixture) #define FUNC(TYPE) BOOST_PP_CAT(restriction::func_, TYPE) -size_t restriction_count(const vector& rs) { - return std::for_each(rs.begin(), rs.end(), restriction::adder()).sum; -} template unsigned_int member_index(string name) { unsigned_int index; @@ -109,7 +106,7 @@ BOOST_AUTO_TEST_CASE(restriction_predicate_tests) { try { // "extensions": [] // } //] - BOOST_CHECK_EQUAL(restriction_count(restrictions), 1); + BOOST_CHECK_EQUAL(restriction::restriction_count(restrictions), 1); BOOST_CHECK(get_restriction_predicate(restrictions, operation::tag::value)(transfer) .rejection_path.size() == 2); // Index 0 (the outer-most) rejection path refers to the first and only restriction @@ -226,7 +223,7 @@ BOOST_AUTO_TEST_CASE(restriction_predicate_tests) { try { // } //] - BOOST_CHECK_EQUAL(restriction_count(restrictions), 2); + BOOST_CHECK_EQUAL(restriction::restriction_count(restrictions), 2); ////// // Check the transfer operation that pays the fee with Asset ID 0 against the restriction. // This should violate the restriction. @@ -280,7 +277,7 @@ BOOST_AUTO_TEST_CASE(restriction_predicate_tests) { try { // "extensions": [] // } //] - BOOST_CHECK_EQUAL(restriction_count(restrictions), 3); + BOOST_CHECK_EQUAL(restriction::restriction_count(restrictions), 3); ////// // Create a transfer operation that authorizes transfer to Account ID 12 @@ -338,7 +335,7 @@ BOOST_AUTO_TEST_CASE(restriction_predicate_tests) { try { // "extensions": [] // } //] - BOOST_CHECK_EQUAL(restriction_count(restrictions), 2); + BOOST_CHECK_EQUAL(restriction::restriction_count(restrictions), 2); auto predicate = get_restriction_predicate(restrictions, operation::tag::value); ////// @@ -453,7 +450,7 @@ BOOST_AUTO_TEST_CASE(restriction_predicate_tests) { try { // "extensions": [] // } //] - BOOST_CHECK_EQUAL(restriction_count(or_restrictions), 3); + BOOST_CHECK_EQUAL(restriction::restriction_count(or_restrictions), 3); auto predicate = get_restriction_predicate(or_restrictions, operation::tag::value); ////// @@ -570,7 +567,7 @@ BOOST_AUTO_TEST_CASE(custom_auths) { try { // "extensions": [] // } //] - BOOST_CHECK_EQUAL(restriction_count(op.restrictions), 3); + BOOST_CHECK_EQUAL(restriction::restriction_count(op.restrictions), 3); ////// @@ -1643,7 +1640,7 @@ BOOST_AUTO_TEST_CASE(custom_auths) { try { // "extensions": [] // } //] - BOOST_CHECK_EQUAL(restriction_count(op.restrictions), 3); + BOOST_CHECK_EQUAL(restriction::restriction_count(op.restrictions), 3); // Publish the new custom authority trx.clear(); From 41d27a2e2a00151a4d939d32a6e56e4c0f075d73 Mon Sep 17 00:00:00 2001 From: Nathan Hourt Date: Sun, 20 Oct 2019 09:51:48 -0500 Subject: [PATCH 40/41] BSIP 40: Add container in and not_in specializations Add specializations to allow `in` and `not_in` restrictions to operate on all values in a container field --- .../restriction_predicate.hxx | 39 ++++++++++++++++++- tests/tests/custom_authority_tests.cpp | 39 +++++++++++++++++++ 2 files changed, 77 insertions(+), 1 deletion(-) diff --git a/libraries/protocol/custom_authorities/restriction_predicate.hxx b/libraries/protocol/custom_authorities/restriction_predicate.hxx index 3b2d0c5e47..c32741db09 100644 --- a/libraries/protocol/custom_authorities/restriction_predicate.hxx +++ b/libraries/protocol/custom_authorities/restriction_predicate.hxx @@ -261,11 +261,48 @@ struct predicate_in, flat_set, std::enable_if_t +struct predicate_in, + std::enable_if_t && + comparable_types>> { + // Check all values in container are in argument + constexpr static bool valid = true; + // Unsorted container + template, bool> = true> + bool operator()(const Container& c, const flat_set& a) const { + return std::all_of(c.begin(), c.end(), [&a](const auto& ce) { return a.count(ce) > 0; }); + } + // Sorted container + template, bool> = true> + bool operator()(const Container& c, const flat_set& a) const { + return std::includes(a.begin(), a.end(), c.begin(), c.end()); + } +}; // Field-not-in-list is just field-in-list wrapped in a negator -template struct predicate_not_in : predicate_in { +template struct predicate_not_in : predicate_in { using base = predicate_in; bool operator()(const Field& f, const Container& c) const { return !base::operator()(f, c); } }; +// Container-field-not-in-list is not a simple negation of predicate_in, specialize here +template +struct predicate_not_in, + std::enable_if_t && + comparable_types>> { + constexpr static bool valid = true; + // Unsorted container + template, bool> = true> + bool operator()(const Container& c, const flat_set& a) const { + return std::none_of(c.begin(), c.end(), [&a](const auto& ce) { return a.count(ce) > 0; }); + } + // Sorted container + template, bool> = true> + bool operator()(const Container& c, const flat_set& a) const { + flat_set intersection; + std::set_intersection(c.begin(), c.end(), a.begin(), a.end(), + std::inserter(intersection, intersection.begin())); + return intersection.empty(); + } +}; // List-contains-list predicate template struct predicate_has_all : predicate_invalid {}; diff --git a/tests/tests/custom_authority_tests.cpp b/tests/tests/custom_authority_tests.cpp index 3d1c53649f..db3d728fdd 100644 --- a/tests/tests/custom_authority_tests.cpp +++ b/tests/tests/custom_authority_tests.cpp @@ -396,6 +396,45 @@ BOOST_AUTO_TEST_CASE(restriction_predicate_tests) { try { BOOST_CHECK(predicate(update) == true); } FC_LOG_AND_RETHROW() } +BOOST_AUTO_TEST_CASE(container_in_not_in_checks) { try { + vector restrictions; + restrictions.emplace_back(member_index("new_feed_producers"), FUNC(in), + flat_set{account_id_type(5), account_id_type(6), account_id_type(7)}); + auto pred = get_restriction_predicate(restrictions, operation::tag::value); + + asset_update_feed_producers_operation op; + BOOST_CHECK(pred(op)); + op.new_feed_producers = {account_id_type(1)}; + BOOST_CHECK(!pred(op)); + op.new_feed_producers = {account_id_type(5)}; + BOOST_CHECK(pred(op)); + op.new_feed_producers = {account_id_type(5), account_id_type(6)}; + BOOST_CHECK(pred(op)); + op.new_feed_producers = {account_id_type(5), account_id_type(6), account_id_type(7)}; + BOOST_CHECK(pred(op)); + op.new_feed_producers = {account_id_type(1), account_id_type(5), account_id_type(6), account_id_type(7)}; + BOOST_CHECK(!pred(op)); + op.new_feed_producers = {account_id_type(5), account_id_type(6), account_id_type(7), account_id_type(8)}; + BOOST_CHECK(!pred(op)); + + restrictions.front().restriction_type = FUNC(not_in); + pred = get_restriction_predicate(restrictions, operation::tag::value); + op.new_feed_producers.clear(); + BOOST_CHECK(pred(op)); + op.new_feed_producers = {account_id_type(1)}; + BOOST_CHECK(pred(op)); + op.new_feed_producers = {account_id_type(5)}; + BOOST_CHECK(!pred(op)); + op.new_feed_producers = {account_id_type(5), account_id_type(6)}; + BOOST_CHECK(!pred(op)); + op.new_feed_producers = {account_id_type(5), account_id_type(6), account_id_type(7)}; + BOOST_CHECK(!pred(op)); + op.new_feed_producers = {account_id_type(1), account_id_type(5), account_id_type(6), account_id_type(7)}; + BOOST_CHECK(!pred(op)); + op.new_feed_producers = {account_id_type(5), account_id_type(6), account_id_type(7), account_id_type(8)}; + BOOST_CHECK(!pred(op)); +} FC_LOG_AND_RETHROW() } + /** * Test predicates containing logical ORs * Test of authorization and revocation of one account (Alice) authorizing multiple other accounts (Bob and Charlie) From 9da10fe1b9e3b089b5f1a13df4020e1e3d185a66 Mon Sep 17 00:00:00 2001 From: Nathan Hourt Date: Sun, 20 Oct 2019 10:57:13 -0500 Subject: [PATCH 41/41] BSIP 40: Maybe fix travis? --- tests/tests/custom_authority_tests.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/tests/tests/custom_authority_tests.cpp b/tests/tests/custom_authority_tests.cpp index db3d728fdd..d08551c472 100644 --- a/tests/tests/custom_authority_tests.cpp +++ b/tests/tests/custom_authority_tests.cpp @@ -1002,8 +1002,10 @@ BOOST_AUTO_TEST_CASE(custom_auths) { try { ////// // Initialize: Define a market-issued asset called USDBIT ////// - ACTORS((feedproducer)); - const auto &bitusd = create_bitasset("USDBIT", feedproducer_id); + ACTORS((feedproducer)) + create_bitasset("USDBIT", feedproducer_id); + generate_blocks(1); + const auto& bitusd = *db.get_index_type().indices().get().find("USDBIT"); const auto &core = asset_id_type()(db); update_feed_producers(bitusd, {feedproducer.id}); @@ -1189,7 +1191,9 @@ BOOST_AUTO_TEST_CASE(custom_auths) { try { // Initialize: Define a market-issued asset called USDBIT ////// ACTORS((feedproducer)); - const auto &bitusd = create_bitasset("USDBIT", feedproducer_id); + create_bitasset("USDBIT", feedproducer_id); + generate_blocks(1); + const auto& bitusd = *db.get_index_type().indices().get().find("USDBIT"); const auto &core = asset_id_type()(db); update_feed_producers(bitusd, {feedproducer.id});