Skip to content

Commit

Permalink
BSIP 40: Add evaluators, transaction eval code, testing
Browse files Browse the repository at this point in the history
Implementation passes cursory tests.
  • Loading branch information
nathanielhourt committed Jul 20, 2019
1 parent 7c3bdb2 commit fc168ec
Show file tree
Hide file tree
Showing 20 changed files with 527 additions and 65 deletions.
4 changes: 4 additions & 0 deletions libraries/app/database_api.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2341,6 +2341,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;
Expand All @@ -2365,6 +2367,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<authority>(); },
true );
}
catch (fc::exception& ex)
Expand Down
1 change: 1 addition & 0 deletions libraries/chain/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
166 changes: 166 additions & 0 deletions libraries/chain/custom_authority_evaluator.cpp
Original file line number Diff line number Diff line change
@@ -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 <graphene/chain/custom_authority_evaluator.hpp>
#include <graphene/chain/custom_authority_object.hpp>
#include <graphene/chain/account_object.hpp>
#include <graphene/chain/database.hpp>
#include <graphene/chain/exceptions.hpp>
#include <graphene/chain/hardfork.hpp>

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<custom_authority_index>().indices().get<by_account_custom>();
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<custom_authority_object>([&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
8 changes: 6 additions & 2 deletions libraries/chain/db_block.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -644,11 +644,15 @@ processed_transaction database::_apply_transaction(const signed_transaction& trx
if( !(skip & skip_transaction_signatures) )
{
bool allow_non_immediate_owner = ( head_block_time() >= HARDFORK_CORE_584_TIME );
auto get_active = [&]( account_id_type id ) { return &id(*this).active; };
auto get_owner = [&]( account_id_type id ) { return &id(*this).owner; };
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,
get_custom,
allow_non_immediate_owner,
get_global_properties().parameters.max_authority_depth );
}
Expand Down
16 changes: 16 additions & 0 deletions libraries/chain/db_getter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
#include <graphene/chain/asset_object.hpp>
#include <graphene/chain/chain_property_object.hpp>
#include <graphene/chain/global_property_object.hpp>
#include <graphene/chain/custom_authority_object.hpp>

namespace graphene { namespace chain {

Expand Down Expand Up @@ -95,6 +96,21 @@ node_property_object& database::node_properties()
return _node_property_object;
}

vector<authority> database::get_viable_custom_authorities(account_id_type account, const operation &op) const
{
const auto& index = get_index_type<custom_authority_index>().indices().get<by_account_custom>();
auto range = index.equal_range(boost::make_tuple(account, unsigned_int(op.which()), true));
vector<authority> 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
Expand Down
4 changes: 4 additions & 0 deletions libraries/chain/db_init.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
#include <graphene/chain/witness_evaluator.hpp>
#include <graphene/chain/worker_evaluator.hpp>
#include <graphene/chain/htlc_evaluator.hpp>
#include <graphene/chain/custom_authority_evaluator.hpp>

#include <fc/uint128.hpp>
#include <fc/crypto/digest.hpp>
Expand Down Expand Up @@ -182,6 +183,9 @@ void database::initialize_evaluators()
register_evaluator<htlc_create_evaluator>();
register_evaluator<htlc_redeem_evaluator>();
register_evaluator<htlc_extend_evaluator>();
register_evaluator<custom_authority_create_evaluator>();
register_evaluator<custom_authority_update_evaluator>();
register_evaluator<custom_authority_delete_evaluator>();
}

void database::initialize_indexes()
Expand Down
6 changes: 6 additions & 0 deletions libraries/chain/hardfork.d/BSIP_40.hf
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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 <graphene/protocol/restriction_predicate.hpp>

#include <graphene/chain/evaluator.hpp>

namespace graphene { namespace chain {

class custom_authority_create_evaluator : public evaluator<custom_authority_create_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<custom_authority_update_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<custom_authority_delete_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
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,17 @@ namespace graphene { namespace chain {
authority auth;
vector<restriction> 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<restriction_predicate_function> predicate_cache;
mutable optional<restriction_predicate_function> 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;
Expand All @@ -66,6 +75,8 @@ namespace graphene { namespace chain {
composite_key<
custom_authority_object,
member<custom_authority_object, account_id_type, &custom_authority_object::account>,
member<custom_authority_object, unsigned_int, &custom_authority_object::operation_type>,
member<custom_authority_object, bool, &custom_authority_object::enabled>,
member<object, object_id_type, &object::id>
>
>
Expand Down
1 change: 1 addition & 0 deletions libraries/chain/include/graphene/chain/database.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,7 @@ namespace graphene { namespace chain {

node_property_object& node_properties();

vector<authority> get_viable_custom_authorities( account_id_type account, const operation& op )const;

uint32_t last_non_undoable_block_num() const;
//////////////////// db_init.cpp ////////////////////
Expand Down
2 changes: 2 additions & 0 deletions libraries/chain/proposal_object.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ bool proposal_object::is_authorized_to_execute(database& db) const
available_key_approvals,
[&]( account_id_type id ){ return &id(db).active; },
[&]( 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,
db.get_global_properties().parameters.max_authority_depth,
true, /* allow committee */
Expand Down
1 change: 1 addition & 0 deletions libraries/protocol/include/graphene/protocol/authority.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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; }

Expand Down
Loading

0 comments on commit fc168ec

Please sign in to comment.