diff --git a/libraries/chain/chain_controller.cpp b/libraries/chain/chain_controller.cpp index 16f51d2bc2c..917cd645924 100644 --- a/libraries/chain/chain_controller.cpp +++ b/libraries/chain/chain_controller.cpp @@ -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); @@ -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(); @@ -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 @@ -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(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().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); }); @@ -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(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(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; @@ -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); } } diff --git a/libraries/chain/include/eos/chain/chain_controller.hpp b/libraries/chain/include/eos/chain/chain_controller.hpp index 5657455fc0f..6f34d98343e 100644 --- a/libraries/chain/include/eos/chain/chain_controller.hpp +++ b/libraries/chain/include/eos/chain/chain_controller.hpp @@ -24,6 +24,7 @@ #pragma once #include #include +#include #include #include @@ -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 }; @@ -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); @@ -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; @@ -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); diff --git a/libraries/chain/include/eos/chain/permission_object.hpp b/libraries/chain/include/eos/chain/permission_object.hpp index fda72bbc0a1..e7c64c39e3b 100644 --- a/libraries/chain/include/eos/chain/permission_object.hpp +++ b/libraries/chain/include/eos/chain/permission_object.hpp @@ -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 + 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().find(other.parent); + while (parent) { + if (id == parent->parent) + return true; + if (parent->parent._id == 0) + return false; + parent = &*permission_index.template get().find(parent->parent); + } + // This permission is not a parent of other, and so does not satisfy other + return false; + } }; struct by_parent; diff --git a/libraries/chain/include/eos/chain/transaction.hpp b/libraries/chain/include/eos/chain/transaction.hpp index f686b3ed665..25d994e8a5e 100644 --- a/libraries/chain/include/eos/chain/transaction.hpp +++ b/libraries/chain/include/eos/chain/transaction.hpp @@ -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; }; diff --git a/libraries/types/types.eos b/libraries/types/types.eos index 96384e9d8e2..5c5be6218a8 100644 --- a/libraries/types/types.eos +++ b/libraries/types/types.eos @@ -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 diff --git a/tests/common/database_fixture.cpp b/tests/common/database_fixture.cpp index 849f86b6d97..e04ae6f037d 100644 --- a/tests/common/database_fixture.cpp +++ b/tests/common/database_fixture.cpp @@ -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); } } diff --git a/tests/common/database_fixture.hpp b/tests/common/database_fixture.hpp index b5f940fd66e..bab115a0d6e 100644 --- a/tests/common/database_fixture.hpp +++ b/tests/common/database_fixture.hpp @@ -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 diff --git a/tests/common/macro_support.hpp b/tests/common/macro_support.hpp index 81240f0ab4b..48670b4965b 100644 --- a/tests/common/macro_support.hpp +++ b/tests/common/macro_support.hpp @@ -68,10 +68,10 @@ inline std::vector sort_names( std::vector&& names ) { trx.scope = sort_names({#sender,#recipient}); \ trx.emplaceMessage(config::EosContractName, \ vector{ {#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, "") @@ -147,7 +147,7 @@ inline std::vector sort_names( std::vector&& 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{}, \ "setproducer", types::setproducer{owner, key, cfg}); \ diff --git a/tests/tests/block_tests.cpp b/tests/tests/block_tests.cpp index 44ab8fe0957..7bc9380133d 100644 --- a/tests/tests/block_tests.cpp +++ b/tests/tests/block_tests.cpp @@ -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)); @@ -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() }