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

Commit

Permalink
Ref #7: Progress towards auth
Browse files Browse the repository at this point in the history
We now check authorization of transactions when they come in as pending,
and again when we apply them in some block (either when generating the
block or when applying it). When applying the transactions in a block,
we check auth for all transactions in the block prior to processing any.

To check auth, we first scan all of the declared authorizations, and
check that the authorization is sufficient to grant permission for the
given account to execute the given message type (TODO: look up the
actual required permission level rather than just assuming it's
'active'); then, check that the transaction bears signatures to confer
the declared authorization.
  • Loading branch information
nathanielhourt committed Aug 8, 2017
1 parent 9b98190 commit 9ad3260
Show file tree
Hide file tree
Showing 9 changed files with 112 additions and 35 deletions.
78 changes: 56 additions & 22 deletions libraries/chain/chain_controller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,7 @@ ProcessedTransaction chain_controller::_push_transaction(const SignedTransaction
_pending_tx_session = _db.start_undo_session(true);

auto temp_session = _db.start_undo_session(true);
check_transaction_authorization(trx);
auto pt = _apply_transaction(trx);
_pending_transactions.push_back(trx);

Expand Down Expand Up @@ -328,6 +329,7 @@ signed_block chain_controller::_generate_block(
try
{
auto temp_session = _db.start_undo_session(true);
check_transaction_authorization(tx);
_apply_transaction(tx);
temp_session.squash();

Expand Down Expand Up @@ -434,6 +436,11 @@ void chain_controller::_apply_block(const signed_block& next_block)
("calc",next_block.calculate_merkle_root())("next_block",next_block)("id",next_block.id()));

const producer_object& signing_producer = validate_block_header(skip, next_block);

for (const auto& cycle : next_block.cycles)
for (const auto& thread : cycle)
for (const auto& trx : thread.user_input)
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 @@ -466,6 +473,40 @@ void chain_controller::_apply_block(const signed_block& next_block)

} FC_CAPTURE_AND_RETHROW( (next_block.block_num()) ) }

void chain_controller::check_transaction_authorization(const SignedTransaction& trx)const {
if ((_skip_flags & skip_transaction_signatures) && (_skip_flags & skip_authority_check)) {
ilog("Skipping auth and sigs checks");
return;
}

auto getPermission = [&db=_db](const types::AccountPermission& permission) {
auto key = boost::make_tuple(permission.account, permission.permission);
return db.get<permission_object, by_owner>(key);
};
auto getAuthority = [&getPermission](const types::AccountPermission& permission) {
return getPermission(permission).auth;
};
#warning TODO: Use a real chain_id here (where is this stored? Do we still need it?)
auto checker = MakeAuthorityChecker(std::move(getAuthority), trx.get_signature_keys(chain_id_type{}));

for (const auto& message : trx.messages)
for (const auto& declaredAuthority : message.authorization) {
const auto& minimumPermission = lookup_minimum_permission(declaredAuthority.account,
message.code, message.type);
if ((_skip_flags & skip_authority_check) == false) {
const auto& index = _db.get_index<permission_index>().indices();
EOS_ASSERT(getPermission(declaredAuthority).satisfies(minimumPermission, index), tx_irrelevant_auth,
"Message declares irrelevant authority '${auth}'", ("auth", declaredAuthority));
}
if ((_skip_flags & skip_transaction_signatures) == false) {
EOS_ASSERT(checker.satisfied(declaredAuthority), tx_missing_sigs,
"Transaction declares authority '${auth}', but does not have signatures for it.",
("auth", declaredAuthority));
}
}
ilog("Auth check passed");
}

ProcessedTransaction chain_controller::apply_transaction(const SignedTransaction& trx, uint32_t skip)
{
return with_skip_flags( skip, [&]() { return _apply_transaction(trx); });
Expand All @@ -480,33 +521,25 @@ try {
validate_uniqueness(trx);
validate_tapos(trx);
validate_referenced_accounts(trx);
validate_authority(trx);

} FC_CAPTURE_AND_RETHROW( (trx) ) }

void chain_controller::validate_authority( const SignedTransaction& trx )const {
if (_skip_flags | skip_transaction_signatures)
return;

auto getAuthority = [&db=_db](const types::AccountPermission& permission) {
auto key = boost::make_tuple(permission.account, permission.permission);
return db.get<permission_object, by_owner>(key).auth;
};
#warning TODO: Use a real chain_id here (where is this stored? Do we still need it?)
auto checker = MakeAuthorityChecker(std::move(getAuthority), trx.get_signature_keys(chain_id_type{}));

for (const auto& declaredAuthority : trx.authorizations)
EOS_ASSERT(checker.satisfied(declaredAuthority), tx_missing_sigs,
"Transaction declares authority '${auth}', but does not have signatures for it.",
("auth", declaredAuthority));
}

void chain_controller::validate_scope( const SignedTransaction& trx )const {
EOS_ASSERT(trx.scope.size() > 0, transaction_exception, "No scope specified by transaction" );
for( uint32_t i = 1; i < trx.scope.size(); ++i )
EOS_ASSERT( trx.scope[i-1] < trx.scope[i], transaction_exception, "Scopes must be sorted and unique" );
}

const permission_object& chain_controller::lookup_minimum_permission(types::AccountName authorizer_account,
types::AccountName code_account,
types::FuncName type) const {
try {
#warning TODO: Define messages/contracts/index for users to specify which authority levels correspond to which message types
// ... and look up the real minimum permission
return _db.get<permission_object, by_owner>(boost::make_tuple(authorizer_account, "active"));
} FC_CAPTURE_AND_RETHROW((authorizer_account)(code_account)(type))
}

void chain_controller::validate_uniqueness( const SignedTransaction& trx )const {
if( !should_check_for_duplicate_transactions() ) return;

Expand All @@ -526,11 +559,12 @@ void chain_controller::validate_tapos(const SignedTransaction& trx)const {
}

void chain_controller::validate_referenced_accounts(const SignedTransaction& trx)const {
for(const auto& auth : trx.authorizations) {
require_account(auth.account);
}
for(const auto& msg : trx.messages) {
for (const auto& scope : trx.scope)
require_account(scope);
for (const auto& msg : trx.messages) {
require_account(msg.code);
for (const auto& auth : msg.authorization)
require_account(auth.account);
}
}

Expand Down
19 changes: 16 additions & 3 deletions libraries/chain/include/eos/chain/chain_controller.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
#pragma once
#include <eos/chain/global_property_object.hpp>
#include <eos/chain/account_object.hpp>
#include <eos/chain/permission_object.hpp>
#include <eos/chain/fork_database.hpp>
#include <eos/chain/block_log.hpp>

Expand Down Expand Up @@ -93,7 +94,7 @@ namespace eos { namespace chain {
skip_merkle_check = 1 << 7, ///< used while reindexing
skip_assert_evaluation = 1 << 8, ///< used while reindexing
skip_undo_history_check = 1 << 9, ///< used while reindexing
skip_producer_schedule_check= 1 << 10, ///< used while reindexing
skip_producer_schedule_check= 1 << 10, ///< used while reindexing
skip_validate = 1 << 11, ///< used prior to checkpoint, skips validate() call on transaction
skip_scope_check = 1 << 12 ///< used to skip checks for proper scope
};
Expand Down Expand Up @@ -254,6 +255,7 @@ namespace eos { namespace chain {
void apply_block(const signed_block& next_block, uint32_t skip = skip_nothing);
void _apply_block(const signed_block& next_block);

void check_transaction_authorization(const SignedTransaction& trx)const;

ProcessedTransaction apply_transaction(const SignedTransaction& trx, uint32_t skip = skip_nothing);
ProcessedTransaction _apply_transaction(const SignedTransaction& trx);
Expand All @@ -262,7 +264,7 @@ namespace eos { namespace chain {
void require_account(const AccountName& name) const;

/**
* This method validates transactions without adding it to the pending state.
* This method performs some consistency checks on a transaction.
* @return true if the transaction would validate
*/
void validate_transaction(const SignedTransaction& trx)const;
Expand All @@ -272,9 +274,20 @@ namespace eos { namespace chain {
void validate_referenced_accounts(const SignedTransaction& trx)const;
void validate_expiration(const SignedTransaction& trx) const;
void validate_scope(const SignedTransaction& trx) const;
void validate_authority(const SignedTransaction& trx )const;
/// @}

/**
* @brief Find the lowest authority level required for @ref authorizer_account to authorize a message of the
* specified type
* @param authorizer_account The account authorizing the message
* @param code_account The account which publishes the contract that handles the message
* @param type The type of message
* @return
*/
const permission_object& lookup_minimum_permission(types::AccountName authorizer_account,
types::AccountName code_account,
types::FuncName type) const;

void process_message(const ProcessedTransaction& trx, AccountName code, const Message& message, MessageOutput& output);
void apply_message(apply_context& c);

Expand Down
31 changes: 31 additions & 0 deletions libraries/chain/include/eos/chain/permission_object.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,37 @@ namespace eos { namespace chain {
id_type parent; ///< parent permission
PermissionName name; ///< human-readable name for the permission
shared_authority auth; ///< authority required to execute this permission

/**
* @brief Checks if this permission is equivalent or greater than other
* @tparam Index The permission_index
* @return True if this permission matches, or is a parent of, other; false otherwise
*
* Permissions are organized hierarchically such that a parent permission is strictly more powerful than its
* children/grandchildren. This method checks whether this permission is of greater or equal power (capable of
* satisfying) permission @ref other. This would be the case if this permission matches, or is some parent of,
* other.
*/
template <typename Index>
bool satisfies(const permission_object& other, const Index& permission_index) const {
// If the owners are not the same, this permission cannot satisfy other
if (owner != other.owner)
return false;
// If this permission matches other, or is the immediate parent of other, then this permission satisfies other
if (id == other.id || id == other.parent)
return true;
// Walk up other's parent tree, seeing if we find this permission. If so, this permission satisfies other
const permission_object* parent = &*permission_index.template get<by_id>().find(other.parent);
while (parent) {
if (id == parent->parent)
return true;
if (parent->parent._id == 0)
return false;
parent = &*permission_index.template get<by_id>().find(parent->parent);
}
// This permission is not a parent of other, and so does not satisfy other
return false;
}
};

struct by_parent;
Expand Down
2 changes: 1 addition & 1 deletion libraries/chain/include/eos/chain/transaction.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ namespace eos { namespace chain {
/**
* Removes all messages, signatures, and authorizations
*/
void clear() { messages.clear(); signatures.clear(); authorizations.clear(); }
void clear() { messages.clear(); signatures.clear(); }

digest_type merkle_digest()const;
};
Expand Down
1 change: 0 additions & 1 deletion libraries/types/types.eos
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ struct Transaction

struct SignedTransaction inherits Transaction
signatures Signature[]
authorizations AccountPermission[] # The authorizations this transaction bears signatures to satisfy

struct KeyPermissionWeight
key PublicKey
Expand Down
2 changes: 1 addition & 1 deletion tests/common/database_fixture.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ void testing_blockchain::produce_blocks(uint32_t count, uint32_t blocks_to_miss)
auto slot = blocks_to_miss + 1;
auto producer = get_producer(get_scheduled_producer(slot));
auto private_key = fixture.get_private_key(producer.signing_key);
generate_block(get_slot_time(slot), producer.owner, private_key, 0);
generate_block(get_slot_time(slot), producer.owner, private_key, chain_controller::skip_transaction_signatures);
}
}

Expand Down
3 changes: 2 additions & 1 deletion tests/common/database_fixture.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,8 @@ class testing_network {
*/
#define Make_Key(name) auto name ## _private_key = private_key_type::regenerate(fc::digest(#name "_private_key")); \
store_private_key(name ## _private_key); \
PublicKey name ## _public_key = name ## _private_key.get_public_key();
PublicKey name ## _public_key = name ## _private_key.get_public_key(); \
BOOST_TEST_CHECKPOINT("Created key " #name "_public_key");

/**
* @brief Key_Authority is a shorthand way to create an inline Authority based on a key
Expand Down
6 changes: 3 additions & 3 deletions tests/common/macro_support.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,10 @@ inline std::vector<Name> sort_names( std::vector<Name>&& names ) {
trx.scope = sort_names({#sender,#recipient}); \
trx.emplaceMessage(config::EosContractName, \
vector<types::AccountPermission>{ {#sender,"active"} }, \
"transfer", types::transfer{#sender, #recipient, Amount.amount/*, memo*/}); \
"transfer", types::transfer{#sender, #recipient, Amount.amount}); \
trx.expiration = chain.head_block_time() + 100; \
trx.set_reference_block(chain.head_block_id()); \
chain.push_transaction(trx); \
chain.push_transaction(trx, chain_controller::skip_transaction_signatures); \
BOOST_TEST_CHECKPOINT("Transfered " << Amount << " from " << #sender << " to " << #recipient); \
}
#define XFER4(chain, sender, recipient, amount) XFER5(chain, sender, recipient, amount, "")
Expand Down Expand Up @@ -147,7 +147,7 @@ inline std::vector<Name> sort_names( std::vector<Name>&& names ) {
#define UPPDCR4(chain, owner, key, cfg) \
{ \
eos::chain::SignedTransaction trx; \
trx.scope = sort_names( {#owner, "eos"} ); \
trx.scope = sort_names( {owner, "eos"} ); \
trx.emplaceMessage(config::EosContractName, \
vector<types::AccountPermission>{}, \
"setproducer", types::setproducer{owner, key, cfg}); \
Expand Down
5 changes: 2 additions & 3 deletions tests/tests/block_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -199,11 +199,10 @@ BOOST_FIXTURE_TEST_CASE(produce_blocks, testing_fixture)
BOOST_FIXTURE_TEST_CASE(order_dependent_transactions, testing_fixture)
{ try {
Make_Blockchain(chain);
Make_Account(chain, newguy);
chain.produce_blocks(10);

Make_Account(chain, newguy);
Transfer_Asset(chain, inita, newguy, Asset(100));

Transfer_Asset(chain, newguy, inita, Asset(1));
BOOST_CHECK_EQUAL(chain.get_liquid_balance("newguy"), Asset(99));
BOOST_CHECK_EQUAL(chain.get_liquid_balance("inita"), Asset(100000-199));
Expand All @@ -213,7 +212,7 @@ BOOST_FIXTURE_TEST_CASE(order_dependent_transactions, testing_fixture)
BOOST_CHECK(chain.fetch_block_by_number(11).valid());
BOOST_CHECK(!chain.fetch_block_by_number(11)->cycles.empty());
BOOST_CHECK(!chain.fetch_block_by_number(11)->cycles.front().empty());
BOOST_CHECK_EQUAL(chain.fetch_block_by_number(11)->cycles.front().front().user_input.size(), 3);
BOOST_CHECK_EQUAL(chain.fetch_block_by_number(11)->cycles.front().front().user_input.size(), 2);
BOOST_CHECK_EQUAL(chain.get_liquid_balance("newguy"), Asset(99));
BOOST_CHECK_EQUAL(chain.get_liquid_balance("inita"), Asset(100000-199));
} FC_LOG_AND_RETHROW() }
Expand Down

0 comments on commit 9ad3260

Please sign in to comment.