Skip to content
This repository has been archived by the owner on Aug 2, 2022. It is now read-only.

Commit

Permalink
Merge pull request #151 from EOSIO/permission-links-123
Browse files Browse the repository at this point in the history
Progress on #7, upgrades to testing framework
  • Loading branch information
bytemaster authored Aug 10, 2017
2 parents 5ccf1e4 + afa2fe2 commit c405fc9
Show file tree
Hide file tree
Showing 11 changed files with 376 additions and 88 deletions.
11 changes: 9 additions & 2 deletions libraries/chain/chain_controller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
#include <eos/chain/transaction_object.hpp>
#include <eos/chain/producer_object.hpp>
#include <eos/chain/permission_link_object.hpp>
#include <eos/chain/authority_checker.hpp>

#include <eos/chain/wasm_interface.hpp>

Expand Down Expand Up @@ -245,6 +246,7 @@ ProcessedTransaction chain_controller::_push_transaction(const SignedTransaction
_pending_tx_session = _db.start_undo_session(true);

auto temp_session = _db.start_undo_session(true);
validate_referenced_accounts(trx);
check_transaction_authorization(trx);
auto pt = _apply_transaction(trx);
_pending_transactions.push_back(trx);
Expand Down Expand Up @@ -330,6 +332,7 @@ signed_block chain_controller::_generate_block(
try
{
auto temp_session = _db.start_undo_session(true);
validate_referenced_accounts(tx);
check_transaction_authorization(tx);
_apply_transaction(tx);
temp_session.squash();
Expand Down Expand Up @@ -440,8 +443,10 @@ void chain_controller::_apply_block(const signed_block& next_block)

for (const auto& cycle : next_block.cycles)
for (const auto& thread : cycle)
for (const auto& trx : thread.user_input)
for (const auto& trx : thread.user_input) {
validate_referenced_accounts(trx);
check_transaction_authorization(trx);
}

/* We do not need to push the undo state for each transaction
* because they either all apply and are valid or the
Expand Down Expand Up @@ -505,6 +510,9 @@ void chain_controller::check_transaction_authorization(const SignedTransaction&
("auth", declaredAuthority));
}
}

EOS_ASSERT(checker.all_keys_used(), tx_irrelevant_sig,
"Transaction bears irrelevant signatures from these keys: ${keys}", ("keys", checker.unused_keys()));
}

ProcessedTransaction chain_controller::apply_transaction(const SignedTransaction& trx, uint32_t skip)
Expand All @@ -520,7 +528,6 @@ try {
validate_expiration(trx);
validate_uniqueness(trx);
validate_tapos(trx);
validate_referenced_accounts(trx);

} FC_CAPTURE_AND_RETHROW( (trx) ) }

Expand Down
53 changes: 3 additions & 50 deletions libraries/chain/include/eos/chain/authority.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,62 +30,15 @@ struct shared_authority {
shared_vector<types::KeyPermissionWeight> keys;
};

/**
* @brief This class determines whether a set of signing keys are sufficient to satisfy an authority or not
*
* To determine whether an authority is satisfied or not, we first determine which keys have approved of a message, and
* then determine whether that list of keys is sufficient to satisfy the authority. This class takes a list of keys and
* provides the @ref satisfied method to determine whether that list of keys satisfies a provided authority.
*
* @tparam F A callable which takes a single argument of type @ref AccountPermission and returns the corresponding
* authority
*/
template<typename F>
class AuthorityChecker {
F PermissionToAuthority;
flat_set<public_key_type> signingKeys;

public:
AuthorityChecker(F PermissionToAuthority, const flat_set<public_key_type>& signingKeys)
: PermissionToAuthority(PermissionToAuthority), signingKeys(signingKeys) {}

bool satisfied(const types::AccountPermission& permission) const {
return satisfied(PermissionToAuthority(permission));
}
template<typename AuthorityType>
bool satisfied(const AuthorityType& authority) const {
UInt32 weight = 0;
for (const auto& kpw : authority.keys) {
if (signingKeys.count(kpw.key)) {
weight += kpw.weight;
if (weight >= authority.threshold)
return true;
}
}
for (const auto& apw : authority.accounts)
//#warning TODO: Recursion limit? Yes: implement as producer-configurable parameter
if (satisfied(apw.permission)) {
weight += apw.weight;
if (weight >= authority.threshold)
return true;
}
return false;
}
};

inline bool operator < ( const types::AccountPermission& a, const types::AccountPermission& b ) {
return std::tie( a.account, a.permission ) < std::tie( b.account, b.permission );
}
template<typename F>
AuthorityChecker<F> MakeAuthorityChecker(F&& pta, const flat_set<public_key_type>& signingKeys) {
return AuthorityChecker<F>(std::forward<F>(pta), signingKeys);
inline bool operator< (const types::AccountPermission& a, const types::AccountPermission& b) {
return std::tie(a.account, a.permission) < std::tie(b.account, b.permission);
}

/**
* Makes sure all keys are unique and sorted and all account permissions are unique and sorted and that authority can
* be satisfied
*/
inline bool validate( types::Authority& auth ) {
inline bool validate(const types::Authority& auth) {
const types::KeyPermissionWeight* prev = nullptr;
decltype(auth.threshold) totalWeight = 0;

Expand Down
127 changes: 127 additions & 0 deletions libraries/chain/include/eos/chain/authority_checker.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
#pragma once

#include <eos/chain/types.hpp>
#include <eos/types/generated.hpp>

#include <eos/utilities/parallel_markers.hpp>

#include <fc/scoped_exit.hpp>

#include <boost/range/algorithm/find.hpp>
#include <boost/algorithm/cxx11/all_of.hpp>

namespace eos { namespace chain {

namespace detail {
using MetaPermission = static_variant<types::KeyPermissionWeight, types::AccountPermissionWeight>;

struct GetWeightVisitor {
using result_type = UInt32;

template<typename Permission>
UInt32 operator()(const Permission& permission) { return permission.weight; }
};

// Orders permissions descending by weight, and breaks ties with Key permissions being less than Account permissions
struct MetaPermissionComparator {
bool operator()(const MetaPermission& a, const MetaPermission& b) const {
GetWeightVisitor scale;
if (a.visit(scale) > b.visit(scale)) return true;
return a.contains<types::KeyPermissionWeight>() && !b.contains<types::KeyPermissionWeight>();
}
};

using MetaPermissionSet = boost::container::flat_multiset<MetaPermission, MetaPermissionComparator>;
}

/**
* @brief This class determines whether a set of signing keys are sufficient to satisfy an authority or not
*
* To determine whether an authority is satisfied or not, we first determine which keys have approved of a message, and
* then determine whether that list of keys is sufficient to satisfy the authority. This class takes a list of keys and
* provides the @ref satisfied method to determine whether that list of keys satisfies a provided authority.
*
* @tparam F A callable which takes a single argument of type @ref AccountPermission and returns the corresponding
* authority
*/
template<typename F>
class AuthorityChecker {
F PermissionToAuthority;
vector<public_key_type> signingKeys;
vector<bool> usedKeys;

struct WeightTallyVisitor {
using result_type = UInt32;

AuthorityChecker& checker;
UInt32 totalWeight = 0;

WeightTallyVisitor(AuthorityChecker& checker)
: checker(checker) {}

UInt32 operator()(const types::KeyPermissionWeight& permission) {
auto itr = boost::find(checker.signingKeys, permission.key);
if (itr != checker.signingKeys.end()) {
checker.usedKeys[itr - checker.signingKeys.begin()] = true;
totalWeight += permission.weight;
}
return totalWeight;
}
UInt32 operator()(const types::AccountPermissionWeight& permission) {
//TODO: Recursion limit? Yes: implement as producer-configurable parameter
if (checker.satisfied(permission.permission))
totalWeight += permission.weight;
return totalWeight;
}
};

public:
AuthorityChecker(F PermissionToAuthority, const flat_set<public_key_type>& signingKeys)
: PermissionToAuthority(PermissionToAuthority),
signingKeys(signingKeys.begin(), signingKeys.end()),
usedKeys(signingKeys.size(), false)
{}

bool satisfied(const types::AccountPermission& permission) {
return satisfied(PermissionToAuthority(permission));
}
template<typename AuthorityType>
bool satisfied(const AuthorityType& authority) {
// Save the current used keys; if we do not satisfy this authority, the newly used keys aren't actually used
auto KeyReverter = fc::make_scoped_exit([this, keys = usedKeys] () mutable {
usedKeys = keys;
});

// Sort key permissions and account permissions together into a single set of MetaPermissions
detail::MetaPermissionSet permissions;
permissions.insert(authority.keys.begin(), authority.keys.end());
permissions.insert(authority.accounts.begin(), authority.accounts.end());

// Check all permissions, from highest weight to lowest, seeing if signingKeys satisfies them or not
WeightTallyVisitor visitor(*this);
for (const auto& permission : permissions)
// If we've got enough weight, to satisfy the authority, return!
if (permission.visit(visitor) >= authority.threshold) {
KeyReverter.cancel();
return true;
}
return false;
}

bool all_keys_used() const { return boost::algorithm::all_of_equal(usedKeys, true); }
flat_set<public_key_type> used_keys() const {
auto range = utilities::FilterDataByMarker(signingKeys, usedKeys, true);
return {range.begin(), range.end()};
}
flat_set<public_key_type> unused_keys() const {
auto range = utilities::FilterDataByMarker(signingKeys, usedKeys, false);
return {range.begin(), range.end()};
}
};

template<typename F>
AuthorityChecker<F> MakeAuthorityChecker(F&& pta, const flat_set<public_key_type>& signingKeys) {
return AuthorityChecker<F>(std::forward<F>(pta), signingKeys);
}

}} // namespace eos::chain
15 changes: 3 additions & 12 deletions libraries/chain/message_handling_contexts.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,10 @@
#include <eos/chain/key_value_object.hpp>
#include <eos/chain/chain_controller.hpp>

#include <eos/utilities/parallel_markers.hpp>

#include <boost/algorithm/cxx11/all_of.hpp>
#include <boost/range/algorithm/find_if.hpp>
#include <boost/range/combine.hpp>
#include <boost/range/adaptor/filtered.hpp>
#include <boost/range/adaptor/transformed.hpp>

namespace eos { namespace chain {

Expand Down Expand Up @@ -48,15 +47,7 @@ bool apply_context::all_authorizations_used() const {
}

vector<types::AccountPermission> apply_context::unused_authorizations() const {
auto RemoveUsed = boost::adaptors::filtered([](const auto& tuple) {
return !boost::get<0>(tuple);
});
auto ToPermission = boost::adaptors::transformed([](const auto& tuple) {
return boost::get<1>(tuple);
});

// zip the parallel arrays, filter out the used authorizations, and return just the permissions that are left
auto range = boost::combine(used_authorizations, msg.authorization) | RemoveUsed | ToPermission;
auto range = utilities::FilterDataByMarker(msg.authorization, used_authorizations, false);
return {range.begin(), range.end()};
}

Expand Down
8 changes: 6 additions & 2 deletions libraries/fc/include/fc/scoped_exit.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,23 @@ namespace fc {
scoped_exit( C&& c ):callback( std::forward<C>(c) ){}
scoped_exit( scoped_exit&& mv ):callback( std::move( mv.callback ) ){}

void cancel() { canceled = true; }

~scoped_exit() {
try { callback(); } catch( ... ) {}
if (!canceled)
try { callback(); } catch( ... ) {}
}

scoped_exit& operator = ( scoped_exit&& mv ) {
callback = std::move(mv);
callback = std::move(mv.callback);
return *this;
}
private:
scoped_exit( const scoped_exit& );
scoped_exit& operator=( const scoped_exit& );

Callback callback;
bool canceled = false;
};

template<typename Callback>
Expand Down
39 changes: 39 additions & 0 deletions libraries/utilities/include/eos/utilities/parallel_markers.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#pragma once

#include <boost/range/combine.hpp>
#include <boost/range/adaptor/filtered.hpp>
#include <boost/range/adaptor/transformed.hpp>

namespace eos { namespace utilities {

/**
* @brief Return values in DataRange corresponding to matching Markers
*
* Takes two parallel ranges, a Data range containing data values, and a Marker range containing markers on the
* corresponding data values. Returns a new Data range containing only the values corresponding to markers which match
* markerValue
*
* For example:
* @code{.cpp}
* vector<char> data = {'A', 'B', 'C'};
* vector<bool> markers = {true, false, true};
* auto markedData = FilterDataByMarker(data, markers, true);
* // markedData contains {'A', 'C'}
* @endcode
*/
template<typename DataRange, typename MarkerRange, typename Marker>
DataRange FilterDataByMarker(DataRange data, MarkerRange markers, const Marker& markerValue) {
auto RemoveMismatchedMarkers = boost::adaptors::filtered([&markerValue](const auto& tuple) {
return boost::get<0>(tuple) == markerValue;
});
auto ToData = boost::adaptors::transformed([](const auto& tuple) {
return boost::get<1>(tuple);
});

// Zip the ranges together, filter out data with markers that don't match, and return the data without the markers
auto range = boost::combine(markers, data) | RemoveMismatchedMarkers | ToData;
return {range.begin(), range.end()};
}

}} // namespace eos::utilities

Loading

0 comments on commit c405fc9

Please sign in to comment.