From eaef8b18d9cc6c1ae4217bd3a984948d209582c7 Mon Sep 17 00:00:00 2001 From: Nathan Hourt Date: Tue, 1 Aug 2017 13:14:15 -0500 Subject: [PATCH 01/27] Partially revert 67c9b8b4615f412ec8f47cadc90ca98bd73386ef Remove the changes around moving authorizations from Message to Transaction, as we decided this is not the best way to go. See details at https://github.com/EOSIO/eos/issues/2#issuecomment-318755302 --- libraries/chain/chain_controller.cpp | 48 ++---------- .../include/eos/chain/chain_controller.hpp | 3 +- libraries/chain/include/eos/chain/message.hpp | 8 +- .../eos/chain/message_handling_contexts.hpp | 20 +++-- .../chain/include/eos/chain/transaction.hpp | 74 ------------------- libraries/chain/message_handling_contexts.cpp | 5 +- libraries/native_contract/eos_contract.cpp | 3 +- .../native_contract_chain_initializer.cpp | 6 +- libraries/types/types.eos | 3 +- programs/eosc/main.cpp | 13 ++-- tests/api_tests/api_tests.cpp | 15 ++-- tests/common/database_fixture.hpp | 8 +- tests/common/macro_support.hpp | 40 +++++----- tests/slow_tests/slow_tests.cpp | 30 +++++--- tests/tests/block_tests.cpp | 7 +- tests/tests/native_contract_tests.cpp | 3 +- 16 files changed, 97 insertions(+), 189 deletions(-) diff --git a/libraries/chain/chain_controller.cpp b/libraries/chain/chain_controller.cpp index da96bb55a91..16f51d2bc2c 100644 --- a/libraries/chain/chain_controller.cpp +++ b/libraries/chain/chain_controller.cpp @@ -567,9 +567,8 @@ void chain_controller::validate_expiration(const SignedTransaction& trx) const * The order of execution of precondition and apply can impact the validity of the * entire message. */ -void chain_controller::process_message( const ProcessedTransaction& trx, AccountName code, const Message& message, - TransactionAuthorizationChecker* authChecker, MessageOutput& output) { - apply_context apply_ctx(*this, _db, trx, message, code, authChecker); +void chain_controller::process_message( const ProcessedTransaction& trx, AccountName code, const Message& message, MessageOutput& output) { + apply_context apply_ctx(*this, _db, trx, message, code); apply_message(apply_ctx); output.notify.reserve( apply_ctx.notified.size() ); @@ -578,7 +577,7 @@ void chain_controller::process_message( const ProcessedTransaction& trx, Account try { auto notify_code = apply_ctx.notified[i]; output.notify.push_back( {notify_code} ); - process_message( trx, notify_code, message, authChecker, output.notify.back().output ); + process_message( trx, notify_code, message, output.notify.back().output ); } FC_CAPTURE_AND_RETHROW((apply_ctx.notified[i])) } @@ -637,44 +636,13 @@ ProcessedTransaction chain_controller::_apply_transaction(const SignedTransactio */ ProcessedTransaction chain_controller::process_transaction( const SignedTransaction& trx ) { try { - // This lambda takes an AccountPermission, and returns its parent. It caches the permission_object it last returned, - // which allows it to simplify the database lookups when it is next used to fetch that object's parent. - auto getParentPermission = - [&index = _db.get_index().indices(), cache = static_cast(nullptr)] - (const types::AccountPermission& child) mutable -> fc::optional { - // Ensure cache points to permission_object corresponding to child - if (!(cache && cache->owner == child.account && cache->name == child.permission)) { - auto& ownerIndex = index.get(); - auto itr = ownerIndex.find(boost::make_tuple(child.account, child.permission)); - FC_ASSERT(itr != ownerIndex.end(), "Unable to find permission_object for AccountPermission '${perm}'", - ("perm", child)); - cache = &*itr; - } - - // Make cache point to the parent of child and return result - if (cache) { - if (cache->parent._id == 0) { - cache = nullptr; - return {}; - } else { - cache = &*index.get().find(cache->parent); - return types::AccountPermission(cache->owner, cache->name); - } - } - return {}; - }; - ProcessedTransaction ptrx( trx ); - TransactionAuthorizationChecker authChecker(trx.authorizations, getParentPermission); ptrx.output.resize( trx.messages.size() ); for( uint32_t i = 0; i < ptrx.messages.size(); ++i ) { - process_message(ptrx, ptrx.messages[i].code, ptrx.messages[i], &authChecker, ptrx.output[i] ); + process_message(ptrx, ptrx.messages[i].code, ptrx.messages[i], ptrx.output[i] ); } - EOS_ASSERT(authChecker.allPermissionsUsed(), tx_irrelevant_auth, - "Transaction declared an authorization it did not need"); - return ptrx; } FC_CAPTURE_AND_RETHROW( (trx) ) } @@ -830,11 +798,11 @@ void chain_controller::initialize_chain(chain_initializer_interface& starter) auto messages = starter.prepare_database(*this, _db); std::for_each(messages.begin(), messages.end(), [&](const Message& m) { MessageOutput output; - ProcessedTransaction trx; /// dummy transaction required for scope validation + ProcessedTransaction trx; /// dummy tranaction required for scope validation trx.scope = { config::EosContractName, "inita" }; std::sort(trx.scope.begin(), trx.scope.end() ); with_skip_flags( skip_scope_check, [&](){ - process_message(trx,m.code,m,nullptr,output); + process_message(trx,m.code,m,output); } ); }); }); @@ -1103,7 +1071,6 @@ ProcessedTransaction chain_controller::transaction_from_variant( const fc::varia GET_FIELD( vo, expiration, result ); GET_FIELD( vo, scope, result ); GET_FIELD( vo, signatures, result ); - GET_FIELD( vo, authorizations, result ); if( vo.contains( "messages" ) ) { const vector& msgs = vo["messages"].get_array(); @@ -1112,6 +1079,7 @@ ProcessedTransaction chain_controller::transaction_from_variant( const fc::varia const auto& vo = msgs[i].get_object(); GET_FIELD( vo, code, result.messages[i] ); GET_FIELD( vo, type, result.messages[i] ); + GET_FIELD( vo, authorization, result.messages[i] ); if( vo.contains( "data" ) ) { const auto& data = vo["data"]; @@ -1172,7 +1140,6 @@ fc::variant chain_controller::transaction_to_variant( const ProcessedTransactio SET_FIELD( trx_mvo, trx, expiration ); SET_FIELD( trx_mvo, trx, scope ); SET_FIELD( trx_mvo, trx, signatures ); - SET_FIELD( trx_mvo, trx, authorizations ); vector msgs( trx.messages.size() ); vector msgsv(msgs.size()); @@ -1182,6 +1149,7 @@ fc::variant chain_controller::transaction_to_variant( const ProcessedTransactio auto& msg = trx.messages[i]; SET_FIELD( msg_mvo, msg, code ); SET_FIELD( msg_mvo, msg, type ); + SET_FIELD( msg_mvo, msg, authorization ); const auto& code_account = _db.get( msg.code ); if( code_account.abi.size() > 4 ) { /// 4 == packsize of empty Abi diff --git a/libraries/chain/include/eos/chain/chain_controller.hpp b/libraries/chain/include/eos/chain/chain_controller.hpp index 6b79c1d3843..5657455fc0f 100644 --- a/libraries/chain/include/eos/chain/chain_controller.hpp +++ b/libraries/chain/include/eos/chain/chain_controller.hpp @@ -275,8 +275,7 @@ namespace eos { namespace chain { void validate_authority(const SignedTransaction& trx )const; /// @} - void process_message(const ProcessedTransaction& trx, AccountName code, const Message& message, - TransactionAuthorizationChecker* authChecker, MessageOutput& output); + void process_message(const ProcessedTransaction& trx, AccountName code, const Message& message, MessageOutput& output); void apply_message(apply_context& c); bool should_check_for_duplicate_transactions()const { return !(_skip_flags&skip_transaction_dupe_check); } diff --git a/libraries/chain/include/eos/chain/message.hpp b/libraries/chain/include/eos/chain/message.hpp index 30f1211a3f1..050b4978484 100644 --- a/libraries/chain/include/eos/chain/message.hpp +++ b/libraries/chain/include/eos/chain/message.hpp @@ -22,13 +22,13 @@ namespace eos { namespace chain { struct Message : public types::Message { Message() = default; template - Message(const AccountName& code, const types::FuncName& type, T&& value) - :types::Message(code, type, Bytes()) { + Message(const AccountName& code, const vector& authorization, const types::FuncName& type, T&& value) + :types::Message(code, type, authorization, Bytes()) { set(type, std::forward(value)); } - Message(const AccountName& code, const types::FuncName& type) - :types::Message(code, type, Bytes()) {} + Message(const AccountName& code, const vector& authorization, const types::FuncName& type) + :types::Message(code, type, authorization, Bytes()) {} Message(const types::Message& m) : types::Message(m) {} diff --git a/libraries/chain/include/eos/chain/message_handling_contexts.hpp b/libraries/chain/include/eos/chain/message_handling_contexts.hpp index 4ff0402f3cf..3e05e78610f 100644 --- a/libraries/chain/include/eos/chain/message_handling_contexts.hpp +++ b/libraries/chain/include/eos/chain/message_handling_contexts.hpp @@ -15,9 +15,8 @@ class message_validate_context { const chainbase::database& d, const chain::Transaction& t, const chain::Message& m, - types::AccountName c, - TransactionAuthorizationChecker* authChecker) - :controller(control),db(d),trx(t),msg(m),code(c),authChecker(authChecker){} + types::AccountName c ) + :controller(control),db(d),trx(t),msg(m),code(c),used_authorizations(msg.authorization.size(), false){} /** * @brief Require @ref account to have approved of this message @@ -37,8 +36,6 @@ class message_validate_context { const chain::Message& msg; ///< message being applied types::AccountName code; ///< the code that is currently running - TransactionAuthorizationChecker* authChecker; - int32_t load_i64( Name scope, Name code, Name table, Name Key, char* data, uint32_t maxlen ); @@ -59,6 +56,9 @@ class message_validate_context { uint128_t* primary, uint128_t* secondary, char* data, uint32_t maxlen ); int32_t lowerbound_secondary_i128i128( Name scope, Name code, Name table, uint128_t* primary, uint128_t* secondary, char* data, uint32_t maxlen ); + + ///< Parallel to msg.authorization; tracks which permissions have been used while processing the message + vector used_authorizations; }; class precondition_validate_context : public message_validate_context { @@ -67,9 +67,8 @@ class precondition_validate_context : public message_validate_context { const chainbase::database& db, const chain::Transaction& t, const chain::Message& m, - const types::AccountName& code, - TransactionAuthorizationChecker* authChecker) - :message_validate_context(con, db, t, m, code, authChecker){} + const types::AccountName& code) + :message_validate_context(con, db, t, m, code){} }; class apply_context : public precondition_validate_context { @@ -78,9 +77,8 @@ class apply_context : public precondition_validate_context { chainbase::database& db, const chain::Transaction& t, const chain::Message& m, - const types::AccountName& code, - TransactionAuthorizationChecker* authChecker) - :precondition_validate_context(con,db,t,m,code,authChecker),mutable_controller(con),mutable_db(db){} + const types::AccountName& code) + :precondition_validate_context(con,db,t,m,code),mutable_controller(con),mutable_db(db){} int32_t store_i64( Name scope, Name table, Name key, const char* data, uint32_t len); int32_t remove_i64( Name scope, Name table, Name key ); diff --git a/libraries/chain/include/eos/chain/transaction.hpp b/libraries/chain/include/eos/chain/transaction.hpp index 773df23dc9c..f686b3ed665 100644 --- a/libraries/chain/include/eos/chain/transaction.hpp +++ b/libraries/chain/include/eos/chain/transaction.hpp @@ -146,80 +146,6 @@ namespace eos { namespace chain { vector output; }; - /** - * @brief This class aids in checking that a transaction is properly authorized, and that no unnecessary - * authorizations are present in it. - * - * In order for a transaction to be valid, it must declare the permissions it requires to execute all of its - * contained messages (@see SignedTransaction::authorizations). The blockchain can verify that the transaction bears - * signatures necessary and sufficient to satisfy its declared permissions before the transaction is executed; - * however, the blockchain cannot know whether the declared permissions are necessary and sufficient to authorize - * the transaction until the transaction is fully executed. This is because the permissions required is a - * contract-layer concern, so the way we discover what permissions are required is by executing the messages, and as - * the message handlers execute, they assert that certain permissions are required. - * - * This class takes the list of declared permissions provided by a transaction at construction. As the transaction - * is subsequently executed, and the message handlers assert that permissions are required, and these required - * permissions can be passed to @ref requirePermission which will verify that the declared permissions satisfy the - * required permissions. This class also tracks which of the declared permissions have been used to satisfy a - * permission passed to @ref requirePermission and which ones have not. - * - * When the transaction is finished executing, call @ref allPermissionsUsed to determine whether any declared - * permissions were unnecessary to fully authorize the transaction. - */ - class TransactionAuthorizationChecker { - public: - using ParentGetter = std::function(const types::AccountPermission&)>; - - /** - * @param declaredPermissions The permissions declared by the transaction as necessary and sufficient to - * authorize it - * @param getParentPermission A callable which takes a @ref types::AccountPermission and returns its parent, or - * an empty optional if no parent exists - */ - TransactionAuthorizationChecker(const vector& declaredPermissions, - ParentGetter getParentPermission) - : declaredPermissions(declaredPermissions), getParentPermission(getParentPermission) {} - - bool requirePermission(const types::AccountPermission& permission) { - auto Matches = [](const types::AccountPermission& permission) { - return [&permission](const types::AccountPermission& other) { - return permission.account == other.account && permission.permission == other.permission; - }; - }; - - auto itr = std::find_if(declaredPermissions.begin(), declaredPermissions.end(), Matches(permission)); - if (itr != declaredPermissions.end()) { - usedPermissions[itr - declaredPermissions.begin()] = true; - return true; - } - - auto parent = getParentPermission(permission); - while (parent) { - itr = std::find_if(declaredPermissions.begin(), declaredPermissions.end(), Matches(*parent)); - if (itr != declaredPermissions.end()) { - usedPermissions[itr - declaredPermissions.begin()] = true; - return true; - } - parent = getParentPermission(*parent); - } - - return false; - } - - bool allPermissionsUsed() const { - return std::all_of(usedPermissions.begin(), usedPermissions.end(), [](bool b){return b;}); - } - - private: - /// The list of permissions declared by the transaction - const vector declaredPermissions; - /// Parallel to @ref declaredPermissions; usedPermissions[N] is true iff declaredPermissions[N] has been required - vector usedPermissions = vector(declaredPermissions.size(), false); - - ParentGetter getParentPermission; - }; - /// @} transactions group } } // eos::chain diff --git a/libraries/chain/message_handling_contexts.cpp b/libraries/chain/message_handling_contexts.cpp index 8574224cf03..acab6c6c8dc 100644 --- a/libraries/chain/message_handling_contexts.cpp +++ b/libraries/chain/message_handling_contexts.cpp @@ -10,10 +10,7 @@ namespace eos { namespace chain { void message_validate_context::require_authorization(const types::AccountName& account) { -#warning TODO: Look up the permission_object that account has specified to use for this message type - if (authChecker) - EOS_ASSERT(authChecker->requirePermission({account, "active"}), tx_missing_auth, - "Transaction does not declare required authority '${auth}'", ("auth", account)); +#warning TODO } void message_validate_context::require_scope(const types::AccountName& account)const { diff --git a/libraries/native_contract/eos_contract.cpp b/libraries/native_contract/eos_contract.cpp index 73cf5b5ae22..cf8ea4d943a 100644 --- a/libraries/native_contract/eos_contract.cpp +++ b/libraries/native_contract/eos_contract.cpp @@ -212,8 +212,7 @@ void apply_eos_setcode(apply_context& context) { a.set_abi( msg.abi ); }); - apply_context init_context( context.mutable_controller, context.mutable_db, context.trx, context.msg, msg.account, - context.authChecker); + apply_context init_context( context.mutable_controller, context.mutable_db, context.trx, context.msg, msg.account ); wasm_interface::get().init( init_context ); } diff --git a/libraries/native_contract/native_contract_chain_initializer.cpp b/libraries/native_contract/native_contract_chain_initializer.cpp index 90d0d3dab81..aff9daf8e65 100644 --- a/libraries/native_contract/native_contract_chain_initializer.cpp +++ b/libraries/native_contract/native_contract_chain_initializer.cpp @@ -85,6 +85,7 @@ std::vector native_contract_chain_initializer::prepare_database( }; for (const auto& acct : genesis.initial_accounts) { chain::Message message(config::EosContractName, + vector{{config::EosContractName, "active"}}, "newaccount", types::newaccount(config::EosContractName, acct.name, KeyAuthority(acct.owner_key), KeyAuthority(acct.active_key), @@ -92,7 +93,8 @@ std::vector native_contract_chain_initializer::prepare_database( acct.staking_balance)); messages_to_process.emplace_back(std::move(message)); if (acct.liquid_balance > 0) { - message = chain::Message(config::EosContractName, + message = chain::Message(config::EosContractName, + vector{{config::EosContractName, "active"}}, "transfer", types::transfer(config::EosContractName, acct.name, acct.liquid_balance.amount/*, "Genesis Allocation"*/)); messages_to_process.emplace_back(std::move(message)); @@ -101,7 +103,7 @@ std::vector native_contract_chain_initializer::prepare_database( // Create initial producers auto CreateProducer = boost::adaptors::transformed([config = genesis.initial_configuration](const auto& p) { - return chain::Message(config::EosContractName, + return chain::Message(config::EosContractName, vector{{p.owner_name, "active"}}, "setproducer", types::setproducer(p.owner_name, p.block_signing_key, config)); }); boost::copy(genesis.initial_producers | CreateProducer, std::back_inserter(messages_to_process)); diff --git a/libraries/types/types.eos b/libraries/types/types.eos index d562830863e..96384e9d8e2 100644 --- a/libraries/types/types.eos +++ b/libraries/types/types.eos @@ -12,7 +12,8 @@ struct AccountPermission struct Message code AccountName # the contract defining the primary code to execute for code/type - type FuncName # the action to be taken + type FuncName # the action to be taken + authorization AccountPermission[] # the accounts and permission levels provided data Bytes # opaque data processed by code struct AccountPermissionWeight diff --git a/programs/eosc/main.cpp b/programs/eosc/main.cpp index 8ffaea9b866..9bf6cc2192a 100644 --- a/programs/eosc/main.cpp +++ b/programs/eosc/main.cpp @@ -108,8 +108,8 @@ void create_account( const vector& args ) { SignedTransaction trx; trx.scope = sort_names({creator,eosaccnt}); - trx.authorizations = vector{{creator,"active"}}; - trx.emplaceMessage(config::EosContractName, + trx.emplaceMessage(config::EosContractName, + vector{{creator, "active"}}, "newaccount", types::newaccount{creator, newaccount, owner_auth, active_auth, recovery_auth, deposit}); @@ -159,10 +159,10 @@ int main( int argc, char** argv ) { SignedTransaction trx; trx.messages.resize(1); - trx.authorizations = fc::json::from_string( args[6] ).as>(); auto& msg = trx.messages.back(); msg.code = code; msg.type = action; + msg.authorization = fc::json::from_string( args[6] ).as>(); msg.data = result.get_object()["binargs"].as(); trx.scope = fc::json::from_string( args[5] ).as>(); @@ -202,8 +202,8 @@ int main( int argc, char** argv ) { SignedTransaction trx; trx.scope = { config::EosContractName, account }; - trx.authorizations = vector{{account,"active"}}; trx.emplaceMessage( config::EosContractName, + vector{ {account,"active"} }, "setcode", handler ); std::cout << fc::json::to_pretty_string( push_transaction(trx) ); @@ -217,8 +217,9 @@ int main( int argc, char** argv ) { SignedTransaction trx; trx.scope = sort_names({sender,recipient}); - trx.authorizations = vector{{sender,"active"}}; - trx.emplaceMessage(config::EosContractName, "transfer", types::transfer{sender, recipient, amount}); + trx.emplaceMessage(config::EosContractName, + vector{ {sender,"active"} }, + "transfer", types::transfer{sender, recipient, amount/*, memo*/}); auto info = get_info(); trx.expiration = info.head_block_time + 100; //chain.head_block_time() + 100; trx.set_reference_block(info.head_block_id); diff --git a/tests/api_tests/api_tests.cpp b/tests/api_tests/api_tests.cpp index 941a017d72d..b3d14f7d46f 100644 --- a/tests/api_tests/api_tests.cpp +++ b/tests/api_tests/api_tests.cpp @@ -99,10 +99,9 @@ void SetCode( testing_blockchain& chain, AccountName account, const char* wast ) } } FC_LOG_AND_RETHROW( ) } -uint32_t CallFunction( testing_blockchain& chain, const types::Message& msg, const vector& auths, const vector& data, const vector& scope = {N(test_api)}) { +uint32_t CallFunction( testing_blockchain& chain, const types::Message& msg, const vector& data, const vector& scope = {N(test_api)}) { static int expiration = 1; eos::chain::SignedTransaction trx; - trx.authorizations = auths; trx.scope = scope; //msg.data.clear(); @@ -149,8 +148,8 @@ uint64_t TEST_METHOD(const char* CLASS, const char *METHOD) { return ( (uint64_t(DJBH(CLASS))<<32) | uint32_t(DJBH(METHOD)) ); } -#define CALL_TEST_FUNCTION(TYPE, AUTH, DATA) CallFunction(chain, Message{"test_api", TYPE}, AUTH, DATA) -#define CALL_TEST_FUNCTION_SCOPE(TYPE, AUTH, DATA, SCOPE) CallFunction(chain, Message{"test_api", TYPE}, AUTH, DATA, SCOPE) +#define CALL_TEST_FUNCTION(TYPE, AUTH, DATA) CallFunction(chain, Message{"test_api", AUTH, TYPE}, DATA) +#define CALL_TEST_FUNCTION_SCOPE(TYPE, AUTH, DATA, SCOPE) CallFunction(chain, Message{"test_api", AUTH, TYPE}, DATA, SCOPE) bool is_access_violation(fc::unhandled_exception const & e) { try { @@ -230,23 +229,23 @@ BOOST_FIXTURE_TEST_CASE(test_all, testing_fixture) BOOST_CHECK_MESSAGE( CALL_TEST_FUNCTION( TEST_METHOD("test_message", "read_message_to_0"), {}, raw_bytes) == WASM_TEST_PASS, "test_message::read_message_to_0()" ); raw_bytes.resize((1<<16)+1); - BOOST_CHECK_EXCEPTION( CALL_TEST_FUNCTION( TEST_METHOD("test_message", "read_message_to_0"), {}, raw_bytes), + BOOST_CHECK_EXCEPTION( CALL_TEST_FUNCTION( TEST_METHOD("test_message", "read_message_to_0"), {}, raw_bytes), fc::unhandled_exception, is_access_violation ); raw_bytes.resize(1); BOOST_CHECK_MESSAGE( CALL_TEST_FUNCTION( TEST_METHOD("test_message", "read_message_to_64k"), {}, raw_bytes) == WASM_TEST_PASS, "test_message::read_message_to_64k()" ); raw_bytes.resize(2); - BOOST_CHECK_EXCEPTION( CALL_TEST_FUNCTION( TEST_METHOD("test_message", "read_message_to_64k"), {}, raw_bytes), + BOOST_CHECK_EXCEPTION( CALL_TEST_FUNCTION( TEST_METHOD("test_message", "read_message_to_64k"), {}, raw_bytes), fc::unhandled_exception, is_access_violation ); BOOST_CHECK_MESSAGE( CALL_TEST_FUNCTION( TEST_METHOD("test_message", "require_notice"), {}, raw_bytes) == WASM_TEST_PASS, "test_message::require_notice()" ); - BOOST_CHECK_EXCEPTION( CALL_TEST_FUNCTION( TEST_METHOD("test_message", "require_auth"), {}, {}), + BOOST_CHECK_EXCEPTION( CALL_TEST_FUNCTION( TEST_METHOD("test_message", "require_auth"), {}, {}), tx_missing_auth, is_tx_missing_auth ); auto a3only = vector{{"acc3","active"}}; - BOOST_CHECK_EXCEPTION( CALL_TEST_FUNCTION( TEST_METHOD("test_message", "require_auth"), a3only, {}), + BOOST_CHECK_EXCEPTION( CALL_TEST_FUNCTION( TEST_METHOD("test_message", "require_auth"), a3only, {}), tx_missing_auth, is_tx_missing_auth ); auto a4only = vector{{"acc4","active"}}; diff --git a/tests/common/database_fixture.hpp b/tests/common/database_fixture.hpp index 4e141050fb3..b5f940fd66e 100644 --- a/tests/common/database_fixture.hpp +++ b/tests/common/database_fixture.hpp @@ -417,8 +417,12 @@ class testing_network { #define Set_Proxy(chain, stakeholder, proxy) \ { \ eos::chain::SignedTransaction trx; \ - trx.authorizations = vector{{#stakeholder,"active"}}; \ - trx.emplaceMessage(config::EosContractName, "setproxy", types::setproxy{#stakeholder, #proxy}); \ + if (std::string(#stakeholder) != std::string(#proxy)) \ + trx.emplaceMessage(config::EosContractName, \ + vector{ {#stakeholder,"active"} }, "setproxy", types::setproxy{#stakeholder, #proxy}); \ + else \ + trx.emplaceMessage(config::EosContractName, \ + vector{ {#stakeholder,"active"} }, "setproxy", types::setproxy{#stakeholder, #proxy}); \ trx.expiration = chain.head_block_time() + 100; \ trx.set_reference_block(chain.head_block_id()); \ chain.push_transaction(trx); \ diff --git a/tests/common/macro_support.hpp b/tests/common/macro_support.hpp index 39a125ec17d..81240f0ab4b 100644 --- a/tests/common/macro_support.hpp +++ b/tests/common/macro_support.hpp @@ -34,11 +34,12 @@ inline std::vector sort_names( std::vector&& names ) { { \ eos::chain::SignedTransaction trx; \ trx.scope = sort_names({ #creator, "eos" }); \ - trx.authorizations = vector{{#creator,"active"}}; \ - trx.emplaceMessage(config::EosContractName, "newaccount", types::newaccount{#creator, #name, owner, active, recovery, deposit}); \ + trx.emplaceMessage(config::EosContractName, \ + vector{}, \ + "newaccount", types::newaccount{#creator, #name, owner, active, recovery, deposit}); \ trx.expiration = chain.head_block_time() + 100; \ trx.set_reference_block(chain.head_block_id()); \ - chain.push_transaction(trx, chain_controller::skip_transaction_signatures); \ + chain.push_transaction(trx); \ BOOST_TEST_CHECKPOINT("Created account " << #name); \ } #define MKACCT2(chain, name) \ @@ -65,8 +66,9 @@ inline std::vector sort_names( std::vector&& names ) { { \ eos::chain::SignedTransaction trx; \ trx.scope = sort_names({#sender,#recipient}); \ - trx.authorizations = vector{{#sender,"active"}}; \ - trx.emplaceMessage(config::EosContractName, "transfer", types::transfer{#sender, #recipient, Amount.amount}); \ + trx.emplaceMessage(config::EosContractName, \ + vector{ {#sender,"active"} }, \ + "transfer", types::transfer{#sender, #recipient, Amount.amount/*, memo*/}); \ trx.expiration = chain.head_block_time() + 100; \ trx.set_reference_block(chain.head_block_id()); \ chain.push_transaction(trx); \ @@ -78,8 +80,8 @@ inline std::vector sort_names( std::vector&& names ) { { \ eos::chain::SignedTransaction trx; \ trx.scope = sort_names( { #sender, #recipient } ); \ - trx.authorizations = vector{{#sender,"active"}}; \ - trx.emplaceMessage(config::EosContractName, "lock", types::lock{#sender, #recipient, amount}); \ + trx.emplaceMessage(config::EosContractName, \ + vector{}, "lock", types::lock{#sender, #recipient, amount}); \ trx.expiration = chain.head_block_time() + 100; \ trx.set_reference_block(chain.head_block_id()); \ chain.push_transaction(trx); \ @@ -91,8 +93,9 @@ inline std::vector sort_names( std::vector&& names ) { { \ eos::chain::SignedTransaction trx; \ trx.scope = sort_names( { "eos" } ); \ - trx.authorizations.emplace_back(types::AccountPermission{#account,"active"}); \ - trx.emplaceMessage(config::EosContractName, "unlock", types::unlock{#account, amount}); \ + trx.emplaceMessage(config::EosContractName, \ + vector{}, \ + "unlock", types::unlock{#account, amount}); \ trx.expiration = chain.head_block_time() + 100; \ trx.set_reference_block(chain.head_block_id()); \ chain.push_transaction(trx); \ @@ -103,8 +106,8 @@ inline std::vector sort_names( std::vector&& names ) { { \ eos::chain::SignedTransaction trx; \ trx.scope = sort_names( { "eos", #account } ); \ - trx.authorizations.emplace_back(types::AccountPermission{#account,"active"}); \ - trx.emplaceMessage(config::EosContractName, "claim", types::claim{#account, amount}); \ + trx.emplaceMessage(config::EosContractName, \ + vector{}, "claim", types::claim{#account, amount}); \ trx.expiration = chain.head_block_time() + 100; \ trx.set_reference_block(chain.head_block_id()); \ chain.push_transaction(trx); \ @@ -115,8 +118,9 @@ inline std::vector sort_names( std::vector&& names ) { { \ eos::chain::SignedTransaction trx; \ trx.scope = sort_names( {#owner, "eos"} ); \ - trx.authorizations = vector{{#owner,"active"}}; \ - trx.emplaceMessage(config::EosContractName, "setproducer", types::setproducer{#owner, key, cfg}); \ + trx.emplaceMessage(config::EosContractName, \ + vector{}, \ + "setproducer", types::setproducer{#owner, key, cfg}); \ trx.expiration = chain.head_block_time() + 100; \ trx.set_reference_block(chain.head_block_id()); \ chain.push_transaction(trx); \ @@ -131,8 +135,9 @@ inline std::vector sort_names( std::vector&& names ) { { \ eos::chain::SignedTransaction trx; \ trx.scope = sort_names( {#voter, "eos"} ); \ - trx.authorizations = vector{{#voter,"active"}}; \ - trx.emplaceMessage(config::EosContractName, "okproducer", types::okproducer{#voter, #producer, approved? 1:0}); \ + trx.emplaceMessage(config::EosContractName, \ + vector{}, \ + "okproducer", types::okproducer{#voter, #producer, approved? 1 : 0}); \ trx.expiration = chain.head_block_time() + 100; \ trx.set_reference_block(chain.head_block_id()); \ chain.push_transaction(trx); \ @@ -143,8 +148,9 @@ inline std::vector sort_names( std::vector&& names ) { { \ eos::chain::SignedTransaction trx; \ trx.scope = sort_names( {#owner, "eos"} ); \ - trx.authorizations = vector{{owner,"active"}}; \ - trx.emplaceMessage(config::EosContractName, "setproducer", types::setproducer{owner, key, cfg}); \ + trx.emplaceMessage(config::EosContractName, \ + vector{}, \ + "setproducer", types::setproducer{owner, key, cfg}); \ trx.expiration = chain.head_block_time() + 100; \ trx.set_reference_block(chain.head_block_id()); \ chain.push_transaction(trx); \ diff --git a/tests/slow_tests/slow_tests.cpp b/tests/slow_tests/slow_tests.cpp index 4f4a0e7c4a7..f64a26e25ff 100644 --- a/tests/slow_tests/slow_tests.cpp +++ b/tests/slow_tests/slow_tests.cpp @@ -301,8 +301,9 @@ void SetCode( testing_blockchain& chain, AccountName account, const char* wast ) void TransferCurrency( testing_blockchain& chain, AccountName from, AccountName to, uint64_t amount ) { eos::chain::SignedTransaction trx; trx.scope = sort_names({from,to}); - trx.authorizations = vector{{from,"active"}}; - trx.emplaceMessage("currency", "transfer", types::transfer{from, to, amount}); + trx.emplaceMessage("currency", + vector{ {from,"active"} }, + "transfer", types::transfer{from, to, amount}); trx.expiration = chain.head_block_time() + 100; trx.set_reference_block(chain.head_block_id()); @@ -313,8 +314,9 @@ void TransferCurrency( testing_blockchain& chain, AccountName from, AccountName void WithdrawCurrency( testing_blockchain& chain, AccountName from, AccountName to, uint64_t amount ) { eos::chain::SignedTransaction trx; trx.scope = sort_names({from,to}); - trx.authorizations = vector{{from,"active"},{to,"active"}}; - trx.emplaceMessage("currency", "transfer", types::transfer{from, to, amount}); + trx.emplaceMessage("currency", + vector{ {from,"active"},{to,"active"} }, + "transfer", types::transfer{from, to, amount}); trx.expiration = chain.head_block_time() + 100; trx.set_reference_block(chain.head_block_id()); chain.push_transaction(trx); @@ -354,8 +356,9 @@ BOOST_FIXTURE_TEST_CASE(create_script, testing_fixture) { eos::chain::SignedTransaction trx; trx.scope = sort_names({"currency","inita"}); - trx.authorizations = vector{{"currency","active"}}; - trx.emplaceMessage("currency", "transfer", types::transfer{"currency", "inita", 1+i}); + trx.emplaceMessage("currency", + vector{ {"currency","active"} }, + "transfer", types::transfer{"currency", "inita", 1+i}); trx.expiration = chain.head_block_time() + 100; trx.set_reference_block(chain.head_block_id()); //idump((trx)); @@ -391,8 +394,9 @@ void SellCurrency( testing_blockchain& chain, AccountName seller, AccountName ex eos::chain::SignedTransaction trx; trx.scope = sort_names({"exchange"}); - trx.authorizations = vector{{seller,"active"}}; - trx.emplaceMessage("exchange", "sell", b ); + trx.emplaceMessage("exchange", + vector{ {seller,"active"} }, + "sell", b ); //trx.messages.back().set_packed( "sell", b); trx.expiration = chain.head_block_time() + 100; trx.set_reference_block(chain.head_block_id()); @@ -408,8 +412,9 @@ void BuyCurrency( testing_blockchain& chain, AccountName buyer, AccountName exch eos::chain::SignedTransaction trx; trx.scope = sort_names({"exchange"}); - trx.authorizations = vector{{buyer,"active"}}; - trx.emplaceMessage("exchange", "buy", b ); + trx.emplaceMessage("exchange", + vector{ {buyer,"active"} }, + "buy", b ); //trx.messages.back().set_packed( "buy", b); trx.expiration = chain.head_block_time() + 100; trx.set_reference_block(chain.head_block_id()); @@ -1153,8 +1158,9 @@ BOOST_FIXTURE_TEST_CASE(create_script_w_loop, testing_fixture) { eos::chain::SignedTransaction trx; trx.scope = sort_names({"currency","inita"}); - trx.authorizations = vector{{"currency","active"}}; - trx.emplaceMessage("currency", "transfer", types::transfer{"currency", "inita", 1}); + trx.emplaceMessage("currency", + vector{ {"currency","active"} }, + "transfer", types::transfer{"currency", "inita", 1}); trx.expiration = chain.head_block_time() + 100; trx.set_reference_block(chain.head_block_id()); try diff --git a/tests/tests/block_tests.cpp b/tests/tests/block_tests.cpp index 3b774aaf8fd..44ab8fe0957 100644 --- a/tests/tests/block_tests.cpp +++ b/tests/tests/block_tests.cpp @@ -145,8 +145,9 @@ BOOST_FIXTURE_TEST_CASE(trx_variant, testing_fixture) { eos::chain::ProcessedTransaction trx; trx.scope = sort_names({from,to}); - trx.authorizations = vector{{from,"active"}}; - trx.emplaceMessage("eos", "transfer", types::transfer{from, to, amount}); + trx.emplaceMessage("eos", + vector{ {from,"active"} }, + "transfer", types::transfer{from, to, amount/*, ""*/}); trx.expiration = chain.head_block_time() + 100; trx.set_reference_block(chain.head_block_id()); @@ -155,11 +156,13 @@ BOOST_FIXTURE_TEST_CASE(trx_variant, testing_fixture) { auto from_var = chain.transaction_from_variant( var ); auto _process = fc::raw::pack( from_var ); + /* idump((trx)); idump((var)); idump((from_var)); idump((original)); idump((_process)); + */ FC_ASSERT( original == _process, "Transaction seralization not reversible" ); } catch ( const fc::exception& e ) { edump((e.to_detail_string())); diff --git a/tests/tests/native_contract_tests.cpp b/tests/tests/native_contract_tests.cpp index b162c9593a1..2a58af116af 100644 --- a/tests/tests/native_contract_tests.cpp +++ b/tests/tests/native_contract_tests.cpp @@ -91,9 +91,8 @@ BOOST_FIXTURE_TEST_CASE(transfer, testing_fixture) trx.set_reference_block(chain.head_block_id()); trx.expiration = chain.head_block_time() + 100; trx.scope = sort_names( {"inita", "initb"} ); - trx.authorizations = {{"inita", "active"}}; - types::transfer trans = {"inita", "initb", (100)}; + types::transfer trans = { "inita", "initb", (100) }; UInt64 value(5); auto packed = fc::raw::pack(value); From 824bea0182b805532843465f700fad370e05b7ea Mon Sep 17 00:00:00 2001 From: kalloc Date: Tue, 1 Aug 2017 23:57:26 +0300 Subject: [PATCH 02/27] Fix typo --- programs/eosc/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/programs/eosc/main.cpp b/programs/eosc/main.cpp index 9bf6cc2192a..ba378bdde4f 100644 --- a/programs/eosc/main.cpp +++ b/programs/eosc/main.cpp @@ -124,7 +124,7 @@ void create_account( const vector& args ) { /** * Usage: * - * eocs create wallet walletname ***PASS1*** ***PASS2*** + * eosc create wallet walletname ***PASS1*** ***PASS2*** * eosc unlock walletname ***PASSWORD*** * eosc wallets -> prints list of wallets with * next to currently unlocked * eosc keys -> prints list of private keys From 8b1fbf78e15accadbd2b47761750edc5835b618a Mon Sep 17 00:00:00 2001 From: kalloc Date: Wed, 2 Aug 2017 02:42:38 +0300 Subject: [PATCH 03/27] Temporary add help command 1) add default command - help 2) prevent segfault --- programs/eosc/main.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/programs/eosc/main.cpp b/programs/eosc/main.cpp index ba378bdde4f..bd375472ae2 100644 --- a/programs/eosc/main.cpp +++ b/programs/eosc/main.cpp @@ -141,7 +141,15 @@ int main( int argc, char** argv ) { for( uint32_t i = 0; i < argc; ++i ) { args[i] = string(argv[i]); } - const auto& command = args[1]; + string command = "help"; + + if( args.size() > 1 ) { + command = args[1]; + } + if( command == "help" ) { + std::cout << "Command list: info, block, exec, account, push-trx, setcode, transfer, create, import, unlock, lock, and do\n"; + return -1; + } if( command == "info" ) { std::cout << fc::json::to_pretty_string( get_info() ); } From f30ccb006d45f8d3b30bb62b8ab0415064d49dc4 Mon Sep 17 00:00:00 2001 From: alex Date: Wed, 2 Aug 2017 11:19:05 +0800 Subject: [PATCH 04/27] Adjust standard docker makefile --- Docker/Dockerfile | 24 ++++++++---------------- Docker/README.md | 11 +++++++++++ Docker/config.ini | 15 ++++++++------- Docker/entrypoint.sh | 13 +++++++++---- README.md | 11 +++++++++++ 5 files changed, 47 insertions(+), 27 deletions(-) diff --git a/Docker/Dockerfile b/Docker/Dockerfile index cd0b4226013..bbed7a6bf97 100644 --- a/Docker/Dockerfile +++ b/Docker/Dockerfile @@ -1,5 +1,5 @@ FROM ubuntu -MAINTAINER xiaobo (peterwillcn@gmail.com) # Dapao Xie (wzxiejinbin@me.com edit) +MAINTAINER xiaobo (peterwillcn@gmail.com) RUN echo 'APT::Install-Recommends 0;' >> /etc/apt/apt.conf.d/01norecommends \ && echo 'APT::Install-Suggests 0;' >> /etc/apt/apt.conf.d/01norecommends \ @@ -10,7 +10,7 @@ RUN echo "deb http://apt.llvm.org/xenial/ llvm-toolchain-xenial-4.0 main" >> /et && wget -O - http://apt.llvm.org/llvm-snapshot.gpg.key|sudo apt-key add - \ && apt-get update \ && DEBIAN_FRONTEND=noninteractive apt-get install -y git-core automake autoconf libtool build-essential pkg-config libtool \ - mpi-default-dev libicu-dev python-dev python3-dev libbz2-dev zlib1g-dev libssl-dev \ + mpi-default-dev libicu-dev python-dev python3-dev libbz2-dev zlib1g-dev libssl-dev libgmp-dev \ clang-4.0 lldb-4.0 lld-4.0 \ && rm -rf /var/lib/apt/lists/* @@ -20,7 +20,7 @@ RUN update-alternatives --install /usr/bin/clang clang /usr/lib/llvm-4.0/bin/cla RUN cd /tmp && wget https://cmake.org/files/v3.9/cmake-3.9.0-Linux-x86_64.sh \ && mkdir /opt/cmake && chmod +x /tmp/cmake-3.9.0-Linux-x86_64.sh \ && sh /tmp/cmake-3.9.0-Linux-x86_64.sh --prefix=/opt/cmake --skip-license \ - && ln -s /opt/cmake/bin/cmake /usr/local/bin/cmake + && ln -s /opt/cmake/bin/cmake /usr/local/bin RUN cd /tmp && wget https://dl.bintray.com/boostorg/release/1.64.0/source/boost_1_64_0.tar.gz \ && tar zxf boost_1_64_0.tar.gz \ @@ -40,26 +40,18 @@ RUN cd /tmp && mkdir wasm-compiler && cd wasm-compiler \ && cd llvm/tools && git clone --depth 1 --single-branch --branch release_40 https://github.com/llvm-mirror/clang.git \ && cd .. && mkdir build && cd build \ && cmake -G "Unix Makefiles" -DCMAKE_INSTALL_PREFIX=/opt/wasm -DLLVM_TARGETS_TO_BUILD= -DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD=WebAssembly -DCMAKE_BUILD_TYPE=Release ../ \ - && make -j2 install && rm -rf /tmp/wasm-compiler - -RUN cd /tmp && wget https://gmplib.org/download/gmp/gmp-6.1.2.tar.bz2 \ - && tar -xvf gmp-6.1.2.tar.bz2 && cd gmp-6.1.2 \ - && ./configure && make && sudo make install \ - && make check \ - && rm -rf /tmp/gmp-6.1.2 - -RUN mkdir -p /opt/eos/bin/data-dir + && make -j$(nproc) install && rm -rf /tmp/wasm-compiler RUN cd /tmp && git clone https://github.com/EOSIO/eos.git --recursive \ - && cd eos && mkdir build && cd build \ + && mkdir -p /opt/eos/bin/data-dir && cd eos && mkdir build && cd build \ && WASM_LLVM_CONFIG=/opt/wasm/bin/llvm-config cmake -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_C_COMPILER=clang -DCMAKE_INSTALL_PREFIX=/opt/eos ../ \ - && make -j2 && make install \ - && cp -a ../contracts /opt/eos/contracts \ + && make -j$(nproc) && make install && mv ../contracts / \ + && ln -s /opt/eos/bin/eos* /usr/local/bin \ && rm -rf /tmp/eos* COPY config.ini genesis.json / COPY entrypoint.sh /sbin -RUN cd /opt/eos/bin && chmod +x /sbin/entrypoint.sh +RUN chmod +x /sbin/entrypoint.sh VOLUME /opt/eos/bin/data-dir EXPOSE 9876 8888 ENTRYPOINT ["/sbin/entrypoint.sh"] diff --git a/Docker/README.md b/Docker/README.md index 5d5fe727ec2..dcc03f97150 100644 --- a/Docker/README.md +++ b/Docker/README.md @@ -21,4 +21,15 @@ sudo mkdir -p /data/store/eos docker-compose -f docker-compose.yml up ``` +Run example contracts + +``` +cd /data/store/eos/contracts/exchange +docker exec docker_eos_1 eosc setcode exchange contracts/exchange/exchange.wast contracts/exchange/exchange.abi + +cd /data/store/eos/contracts/currency +docker exec docker_eos_1 eosc setcode currency contracts/currency/currency.wast contracts/currency/currency.abi + +``` + Done diff --git a/Docker/config.ini b/Docker/config.ini index bf3a0c6d89e..515670322be 100644 --- a/Docker/config.ini +++ b/Docker/config.ini @@ -1,12 +1,12 @@ # File to read Genesis State from -# genesis-json = -genesis-json = "/opt/eos/bin/data-dir/genesis.json" +# genesis-json = +genesis-json = "/opt/eos/bin/data-dir/genesis.json" # the location of the block log (absolute path or relative to application data dir) block-log-dir = "blocks" # Pairs of [BLOCK_NUM,BLOCK_ID] that should be enforced as checkpoints. -# checkpoint = +# checkpoint = # open the database in read only mode readonly = 0 @@ -24,19 +24,19 @@ http-server-endpoint = 127.0.0.1:8888 listen-endpoint = 127.0.0.1:9876 # The IP address and port of a remote peer to sync with. -# remote-endpoint = +# remote-endpoint = # The public IP address and port that should be advertized to peers. public-endpoint = 0.0.0.0:9876 # Enable block production, even if the chain is stale. -enable-stale-production = true +enable-stale-production = true # Percent of producers (0-99) that must be participating in order to produce blocks required-participation = false # ID of producer controlled by this node (e.g. "inita", quotes are required, may specify multiple times) -# producer-name = +# producer-name = producer-name = inita producer-name = initb producer-name = initc @@ -63,7 +63,8 @@ producer-name = initu private-key = ["EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV","5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3"] # Plugin(s) to enable, may be specified multiple times -# plugin = +# plugin = plugin = eos::producer_plugin plugin = eos::chain_api_plugin +plugin = eos::http_plugin diff --git a/Docker/entrypoint.sh b/Docker/entrypoint.sh index b372eb777e4..6fd2524226f 100644 --- a/Docker/entrypoint.sh +++ b/Docker/entrypoint.sh @@ -1,17 +1,22 @@ #!/bin/sh cd /opt/eos/bin -if [ -f '/opt/eos/bin/data-dir/config.ini' ] - then +if [ -f '/opt/eos/bin/data-dir/config.ini' ]; then echo else cp /config.ini /opt/eos/bin/data-dir fi -if [ -f '/opt/eos/bin/data-dir/genesis.json' ] - then + +if [ -f '/opt/eos/bin/data-dir/genesis.json' ]; then echo else cp /genesis.json /opt/eos/bin/data-dir fi +if [ -d '/opt/eos/bin/data-dir/contracts' ]; then + echo + else + cp -r /contracts /opt/eos/bin/data-dir +fi + exec /opt/eos/bin/eosd diff --git a/README.md b/README.md index a2bf186af27..411a618bcf9 100644 --- a/README.md +++ b/README.md @@ -103,6 +103,17 @@ sudo mkdir -p /data/store/eos docker-compose -f docker-compose.yml up ``` +Run example contracts + +``` +cd /data/store/eos/contracts/exchange +docker exec docker_eos_1 eosc setcode exchange contracts/exchange/exchange.wast contracts/exchange/exchange.abi + +cd /data/store/eos/contracts/currency +docker exec docker_eos_1 eosc setcode currency contracts/currency/currency.wast contracts/currency/currency.abi + +``` + Done From aa057ccc7be8e2507e8a0471e3c59af6e4c911e1 Mon Sep 17 00:00:00 2001 From: alex Date: Wed, 2 Aug 2017 12:53:22 +0800 Subject: [PATCH 05/27] Add packages for ci --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 9498c7c0183..7733e2828c2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,6 +12,7 @@ addons: - clang-4.0 - g++-6 - ninja-build + - libgmp-dev before_install: - mkdir ext && cd ext - wget https://dl.bintray.com/boostorg/release/1.64.0/source/boost_1_64_0.tar.bz2 && tar xjf boost_1_64_0.tar.bz2 @@ -25,4 +26,3 @@ script: - WASM_LLVM_CONFIG=$TRAVIS_BUILD_DIR/ext/wasm-compiler/bin/llvm-config ext/cmake-3.9.0-Linux-x86_64/bin/cmake -G Ninja -DCMAKE_CXX_COMPILER=clang++-4.0 -DCMAKE_C_COMPILER=clang-4.0 -DBOOST_ROOT=$TRAVIS_BUILD_DIR/ext -DSecp256k1_ROOT_DIR=$TRAVIS_BUILD_DIR/ext - ninja -j4 - tests/chain_test - - tests/slow_test From 45926310ba0c5d9bdd6a7261650d6ca9af5fabb6 Mon Sep 17 00:00:00 2001 From: alex Date: Wed, 2 Aug 2017 13:23:13 +0800 Subject: [PATCH 06/27] Add build status --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 411a618bcf9..68997b13632 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # Eos +[![Build Status](https://travis-ci.org/EOSIO/eos.svg?branch=master)](https://travis-ci.org/EOSIO/eos) + Welcome to the EOS.IO source code repository! ## Getting Started From 9b981907d072f4f29f4daa6322eb21cfc3d3f195 Mon Sep 17 00:00:00 2001 From: Zhenwei Date: Thu, 3 Aug 2017 11:09:38 -0400 Subject: [PATCH 07/27] merged apply, precondition and validate contexts into apply_context --- .../eos/chain/message_handling_contexts.hpp | 114 ++++++------------ .../include/eos/chain/wasm_interface.hpp | 12 +- libraries/chain/message_handling_contexts.cpp | 33 +++-- libraries/chain/wasm_interface.cpp | 19 ++- 4 files changed, 69 insertions(+), 109 deletions(-) diff --git a/libraries/chain/include/eos/chain/message_handling_contexts.hpp b/libraries/chain/include/eos/chain/message_handling_contexts.hpp index 3e05e78610f..15f4ceed380 100644 --- a/libraries/chain/include/eos/chain/message_handling_contexts.hpp +++ b/libraries/chain/include/eos/chain/message_handling_contexts.hpp @@ -9,26 +9,44 @@ namespace chainbase { class database; } namespace eos { namespace chain { class chain_controller; -class message_validate_context { + +class apply_context { public: - explicit message_validate_context(const chain_controller& control, - const chainbase::database& d, - const chain::Transaction& t, - const chain::Message& m, - types::AccountName c ) - :controller(control),db(d),trx(t),msg(m),code(c),used_authorizations(msg.authorization.size(), false){} + apply_context(chain_controller& con, + chainbase::database& db, + const chain::Transaction& t, + const chain::Message& m, + const types::AccountName& code) + :controller(con),db(db),trx(t),msg(m),code(code),mutable_controller(con),mutable_db(db){} + + int32_t store_i64( Name scope, Name table, Name key, const char* data, uint32_t len); + int32_t remove_i64( Name scope, Name table, Name key ); + int32_t remove_i128i128( Name scope, Name table, uint128_t primary, uint128_t secondary ); + int32_t store_i128i128( Name scope, Name table, uint128_t primary, uint128_t secondary, + const char* data, uint32_t len ); + + int32_t load_i64( Name scope, Name code, Name table, Name Key, char* data, uint32_t maxlen ); + + int32_t front_primary_i128i128( Name scope, Name code, Name table, + uint128_t* primary, uint128_t* secondary, char* data, uint32_t maxlen ); + int32_t back_primary_i128i128( Name scope, Name code, Name table, + uint128_t* primary, uint128_t* secondary, char* data, uint32_t maxlen ); + int32_t front_secondary_i128i128( Name scope, Name code, Name table, + uint128_t* primary, uint128_t* secondary, char* data, uint32_t maxlen ); + int32_t back_secondary_i128i128( Name scope, Name code, Name table, + uint128_t* primary, uint128_t* secondary, char* data, uint32_t maxlen ); + int32_t load_primary_i128i128( Name scope, Name code, Name table, + uint128_t* primary, uint128_t* secondary, char* data, uint32_t maxlen ); + int32_t load_secondary_i128i128( Name scope, Name code, Name table, + uint128_t* primary, uint128_t* secondary, char* data, uint32_t maxlen ); + int32_t lowerbound_primary_i128i128( Name scope, Name code, Name table, + uint128_t* primary, uint128_t* secondary, char* data, uint32_t maxlen ); + int32_t lowerbound_secondary_i128i128( Name scope, Name code, Name table, + uint128_t* primary, uint128_t* secondary, char* data, uint32_t maxlen ); - /** - * @brief Require @ref account to have approved of this message - * @param account The account whose approval is required - * - * This method will check that @ref account is listed in the message's declared authorizations, and marks the - * authorization as used. Note that all authorizations on a message must be used, or the message is invalid. - * - * @throws tx_missing_auth If no sufficient permission was found - */ void require_authorization(const types::AccountName& account); void require_scope(const types::AccountName& account)const; + void require_recipient(const types::AccountName& account); const chain_controller& controller; const chainbase::database& db; ///< database where state is stored @@ -36,69 +54,17 @@ class message_validate_context { const chain::Message& msg; ///< message being applied types::AccountName code; ///< the code that is currently running + chain_controller& mutable_controller; + chainbase::database& mutable_db; - int32_t load_i64( Name scope, Name code, Name table, Name Key, char* data, uint32_t maxlen ); - - - int32_t front_primary_i128i128( Name scope, Name code, Name table, - uint128_t* primary, uint128_t* secondary, char* data, uint32_t maxlen ); - int32_t back_primary_i128i128( Name scope, Name code, Name table, - uint128_t* primary, uint128_t* secondary, char* data, uint32_t maxlen ); - int32_t front_secondary_i128i128( Name scope, Name code, Name table, - uint128_t* primary, uint128_t* secondary, char* data, uint32_t maxlen ); - int32_t back_secondary_i128i128( Name scope, Name code, Name table, - uint128_t* primary, uint128_t* secondary, char* data, uint32_t maxlen ); - int32_t load_primary_i128i128( Name scope, Name code, Name table, - uint128_t* primary, uint128_t* secondary, char* data, uint32_t maxlen ); - int32_t load_secondary_i128i128( Name scope, Name code, Name table, - uint128_t* primary, uint128_t* secondary, char* data, uint32_t maxlen ); - int32_t lowerbound_primary_i128i128( Name scope, Name code, Name table, - uint128_t* primary, uint128_t* secondary, char* data, uint32_t maxlen ); - int32_t lowerbound_secondary_i128i128( Name scope, Name code, Name table, - uint128_t* primary, uint128_t* secondary, char* data, uint32_t maxlen ); + std::deque notified; + std::deque sync_transactions; ///< sync calls made + std::deque async_transactions; ///< async calls requested ///< Parallel to msg.authorization; tracks which permissions have been used while processing the message - vector used_authorizations; -}; - -class precondition_validate_context : public message_validate_context { -public: - precondition_validate_context(const chain_controller& con, - const chainbase::database& db, - const chain::Transaction& t, - const chain::Message& m, - const types::AccountName& code) - :message_validate_context(con, db, t, m, code){} -}; - -class apply_context : public precondition_validate_context { - public: - apply_context(chain_controller& con, - chainbase::database& db, - const chain::Transaction& t, - const chain::Message& m, - const types::AccountName& code) - :precondition_validate_context(con,db,t,m,code),mutable_controller(con),mutable_db(db){} - - int32_t store_i64( Name scope, Name table, Name key, const char* data, uint32_t len); - int32_t remove_i64( Name scope, Name table, Name key ); - int32_t remove_i128i128( Name scope, Name table, uint128_t primary, uint128_t secondary ); - int32_t store_i128i128( Name scope, Name table, uint128_t primary, uint128_t secondary, - const char* data, uint32_t len ); - - bool has_recipient( const types::AccountName& account )const; - void require_recipient(const types::AccountName& account); - - std::deque notified; - std::deque sync_transactions; ///< sync calls made - std::deque async_transactions; ///< async calls requested - - chain_controller& mutable_controller; - chainbase::database& mutable_db; + vector used_authorizations; }; -using message_validate_handler = std::function; -using precondition_validate_handler = std::function; using apply_handler = std::function; } } // namespace eos::chain diff --git a/libraries/chain/include/eos/chain/wasm_interface.hpp b/libraries/chain/include/eos/chain/wasm_interface.hpp index 1b80753f0fd..e63dc438645 100644 --- a/libraries/chain/include/eos/chain/wasm_interface.hpp +++ b/libraries/chain/include/eos/chain/wasm_interface.hpp @@ -8,7 +8,7 @@ namespace eos { namespace chain { class chain_controller; -typedef int32_t (message_validate_context::*load_i128i128_fnc)(Name, Name, Name, uint128_t* , uint128_t*, char* , uint32_t); +typedef int32_t (apply_context::*load_i128i128_fnc)(Name, Name, Name, uint128_t* , uint128_t*, char* , uint32_t); /** * @class wasm_interface @@ -33,14 +33,14 @@ class wasm_interface { void init( apply_context& c ); void apply( apply_context& c ); - void validate( message_validate_context& c ); - void precondition( precondition_validate_context& c ); + void validate( apply_context& c ); + void precondition( apply_context& c ); int64_t current_execution_time(); - apply_context* current_apply_context = nullptr; - message_validate_context* current_validate_context = nullptr; - precondition_validate_context* current_precondition_context = nullptr; + apply_context* current_apply_context = nullptr; + apply_context* current_validate_context = nullptr; + apply_context* current_precondition_context = nullptr; Runtime::MemoryInstance* current_memory = nullptr; Runtime::ModuleInstance* current_module = nullptr; diff --git a/libraries/chain/message_handling_contexts.cpp b/libraries/chain/message_handling_contexts.cpp index acab6c6c8dc..7fbb059ec36 100644 --- a/libraries/chain/message_handling_contexts.cpp +++ b/libraries/chain/message_handling_contexts.cpp @@ -9,11 +9,11 @@ namespace eos { namespace chain { -void message_validate_context::require_authorization(const types::AccountName& account) { +void apply_context::require_authorization(const types::AccountName& account) { #warning TODO } -void message_validate_context::require_scope(const types::AccountName& account)const { +void apply_context::require_scope(const types::AccountName& account)const { auto itr = boost::find_if(trx.scope, [&account](const auto& scope) { return scope == account; }); @@ -24,23 +24,20 @@ void message_validate_context::require_scope(const types::AccountName& account)c } } -bool apply_context::has_recipient( const types::AccountName& account )const { - if( msg.code == account ) return true; +void apply_context::require_recipient(const types::AccountName& account) { + if (account == msg.code) + return; auto itr = boost::find_if(notified, [&account](const auto& recipient) { return recipient == account; }); - return itr != notified.end(); -} - -void apply_context::require_recipient(const types::AccountName& account) { - if( !has_recipient( account ) ) { + if( itr == notified.end() ) { notified.push_back(account); } } -int32_t message_validate_context::load_i64( Name scope, Name code, Name table, Name key, char* value, uint32_t valuelen ) { +int32_t apply_context::load_i64( Name scope, Name code, Name table, Name key, char* value, uint32_t valuelen ) { require_scope( scope ); const auto* obj = db.find( boost::make_tuple( @@ -56,7 +53,7 @@ int32_t message_validate_context::load_i64( Name scope, Name code, Name table, N return copylen; } -int32_t message_validate_context::back_primary_i128i128( Name scope, Name code, Name table, +int32_t apply_context::back_primary_i128i128( Name scope, Name code, Name table, uint128_t* primary, uint128_t* secondary, char* value, uint32_t valuelen ) { return -1; @@ -89,7 +86,7 @@ int32_t message_validate_context::back_primary_i128i128( Name scope, Name code, */ } -int32_t message_validate_context::back_secondary_i128i128( Name scope, Name code, Name table, +int32_t apply_context::back_secondary_i128i128( Name scope, Name code, Name table, uint128_t* primary, uint128_t* secondary, char* value, uint32_t valuelen ) { require_scope( scope ); @@ -120,7 +117,7 @@ int32_t message_validate_context::back_secondary_i128i128( Name scope, Name code } -int32_t message_validate_context::front_primary_i128i128( Name scope, Name code, Name table, +int32_t apply_context::front_primary_i128i128( Name scope, Name code, Name table, uint128_t* primary, uint128_t* secondary, char* value, uint32_t valuelen ) { require_scope( scope ); @@ -149,7 +146,7 @@ int32_t message_validate_context::front_primary_i128i128( Name scope, Name code, } return copylen; } -int32_t message_validate_context::front_secondary_i128i128( Name scope, Name code, Name table, +int32_t apply_context::front_secondary_i128i128( Name scope, Name code, Name table, uint128_t* primary, uint128_t* secondary, char* value, uint32_t valuelen ) { require_scope( scope ); @@ -183,7 +180,7 @@ int32_t message_validate_context::front_secondary_i128i128( Name scope, Name cod } -int32_t message_validate_context::load_primary_i128i128( Name scope, Name code, Name table, +int32_t apply_context::load_primary_i128i128( Name scope, Name code, Name table, uint128_t* primary, uint128_t* secondary, char* value, uint32_t valuelen ) { require_scope( scope ); @@ -208,7 +205,7 @@ int32_t message_validate_context::load_primary_i128i128( Name scope, Name code, return copylen; } -int32_t message_validate_context::load_secondary_i128i128( Name scope, Name code, Name table, +int32_t apply_context::load_secondary_i128i128( Name scope, Name code, Name table, uint128_t* primary, uint128_t* secondary, char* value, uint32_t valuelen ) { require_scope( scope ); @@ -233,7 +230,7 @@ int32_t message_validate_context::load_secondary_i128i128( Name scope, Name code return copylen; } -int32_t message_validate_context::lowerbound_primary_i128i128( Name scope, Name code, Name table, +int32_t apply_context::lowerbound_primary_i128i128( Name scope, Name code, Name table, uint128_t* primary, uint128_t* secondary, char* value, uint32_t valuelen ) { require_scope( scope ); @@ -258,7 +255,7 @@ int32_t message_validate_context::lowerbound_primary_i128i128( Name scope, Name return copylen; } -int32_t message_validate_context::lowerbound_secondary_i128i128( Name scope, Name code, Name table, +int32_t apply_context::lowerbound_secondary_i128i128( Name scope, Name code, Name table, uint128_t* primary, uint128_t* secondary, char* value, uint32_t valuelen ) { require_scope( scope ); diff --git a/libraries/chain/wasm_interface.cpp b/libraries/chain/wasm_interface.cpp index 4bdf4eeae5c..a2ef40212bb 100644 --- a/libraries/chain/wasm_interface.cpp +++ b/libraries/chain/wasm_interface.cpp @@ -139,27 +139,27 @@ DEFINE_INTRINSIC_FUNCTION3(env,remove_i128i128,remove_i128i128,i32,i64,scope,i64 } DEFINE_INTRINSIC_FUNCTION5(env,load_primary_i128i128,load_primary_i128i128,i32,i64,scope,i64,code,i64,table,i32,valueptr,i32,valuelen) { - return load_i128i128_object(scope, code, table, valueptr, valuelen, &message_validate_context::load_primary_i128i128); + return load_i128i128_object(scope, code, table, valueptr, valuelen, &apply_context::load_primary_i128i128); } DEFINE_INTRINSIC_FUNCTION5(env,load_secondary_i128i128,load_secondary_i128i128,i32,i64,scope,i64,code,i64,table,i32,valueptr,i32,valuelen) { - return load_i128i128_object(scope, code, table, valueptr, valuelen, &message_validate_context::load_secondary_i128i128); + return load_i128i128_object(scope, code, table, valueptr, valuelen, &apply_context::load_secondary_i128i128); } DEFINE_INTRINSIC_FUNCTION5(env,back_primary_i128i128,back_primary_i128i128,i32,i64,scope,i64,code,i64,table,i32,valueptr,i32,valuelen) { - return load_i128i128_object(scope, code, table, valueptr, valuelen, &message_validate_context::back_primary_i128i128); + return load_i128i128_object(scope, code, table, valueptr, valuelen, &apply_context::back_primary_i128i128); } DEFINE_INTRINSIC_FUNCTION5(env,front_primary_i128i128,front_primary_i128i128,i32,i64,scope,i64,code,i64,table,i32,valueptr,i32,valuelen) { - return load_i128i128_object(scope, code, table, valueptr, valuelen, &message_validate_context::front_primary_i128i128); + return load_i128i128_object(scope, code, table, valueptr, valuelen, &apply_context::front_primary_i128i128); } DEFINE_INTRINSIC_FUNCTION5(env,back_secondary_i128i128,back_secondary_i128i128,i32,i64,scope,i64,code,i64,table,i32,valueptr,i32,valuelen) { - return load_i128i128_object(scope, code, table, valueptr, valuelen, &message_validate_context::back_secondary_i128i128); + return load_i128i128_object(scope, code, table, valueptr, valuelen, &apply_context::back_secondary_i128i128); } DEFINE_INTRINSIC_FUNCTION5(env,front_secondary_i128i128,front_secondary_i128i128,i32,i64,scope,i64,code,i64,table,i32,valueptr,i32,valuelen) { - return load_i128i128_object(scope, code, table, valueptr, valuelen, &message_validate_context::front_secondary_i128i128); + return load_i128i128_object(scope, code, table, valueptr, valuelen, &apply_context::front_secondary_i128i128); } DEFINE_INTRINSIC_FUNCTION0(env,currentCode,currentCode,i64) { @@ -175,9 +175,6 @@ DEFINE_INTRINSIC_FUNCTION1(env,requireNotice,requireNotice,none,i64,account) { wasm_interface::get().current_apply_context->require_recipient( account ); } -DEFINE_INTRINSIC_FUNCTION1(env,hasRecipient,hasRecipient,i32,i64,account) { - return wasm_interface::get().current_apply_context->has_recipient( account ); -} DEFINE_INTRINSIC_FUNCTION1(env,requireScope,requireScope,none,i64,scope) { wasm_interface::get().current_validate_context->require_scope( scope ); } @@ -458,7 +455,7 @@ DEFINE_INTRINSIC_FUNCTION1(env,free,free,none,i32,ptr) { } } FC_CAPTURE_AND_RETHROW() } - void wasm_interface::validate( message_validate_context& c ) { + void wasm_interface::validate( apply_context& c ) { /* current_validate_context = &c; current_precondition_context = nullptr; @@ -468,7 +465,7 @@ DEFINE_INTRINSIC_FUNCTION1(env,free,free,none,i32,ptr) { vm_validate(); */ } - void wasm_interface::precondition( precondition_validate_context& c ) { + void wasm_interface::precondition( apply_context& c ) { try { /* From 9ad3260d65cb2f186f47ecc731fa1d3e83009d04 Mon Sep 17 00:00:00 2001 From: Nathan Hourt Date: Tue, 1 Aug 2017 16:31:01 -0500 Subject: [PATCH 08/27] Ref #7: Progress towards auth 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. --- libraries/chain/chain_controller.cpp | 78 +++++++++++++------ .../include/eos/chain/chain_controller.hpp | 19 ++++- .../include/eos/chain/permission_object.hpp | 31 ++++++++ .../chain/include/eos/chain/transaction.hpp | 2 +- libraries/types/types.eos | 1 - tests/common/database_fixture.cpp | 2 +- tests/common/database_fixture.hpp | 3 +- tests/common/macro_support.hpp | 6 +- tests/tests/block_tests.cpp | 5 +- 9 files changed, 112 insertions(+), 35 deletions(-) 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() } From 0393f95b0bf4fafc1c98ddd0ed492c6e7786eb54 Mon Sep 17 00:00:00 2001 From: Nathan Hourt Date: Tue, 1 Aug 2017 17:13:05 -0500 Subject: [PATCH 09/27] Ref #7: Implement require_authorization, fix tests Require_authorization is now implemented so as the contract executes and asserts that a particular account approved the transaction, the chain asserts that this is so and throws if not. Also, update the tests, since the auth checks now bring to bear the rule that an account cannot be used in the same block that creates it. The tests now comply with this rule. TODO: - Check that all declared authorizations get required by the contract - Implement the mapping from user permissions to message types - Use mapping of permission to message type in lookup_minimum_permission --- libraries/chain/chain_controller.cpp | 1 - libraries/chain/message_handling_contexts.cpp | 4 ++- tests/common/macro_support.hpp | 32 +++++++++---------- tests/tests/native_contract_tests.cpp | 17 +++++----- tests/tests/special_accounts_tests.cpp | 12 ++++--- 5 files changed, 36 insertions(+), 30 deletions(-) diff --git a/libraries/chain/chain_controller.cpp b/libraries/chain/chain_controller.cpp index 917cd645924..db2a7e28af8 100644 --- a/libraries/chain/chain_controller.cpp +++ b/libraries/chain/chain_controller.cpp @@ -504,7 +504,6 @@ void chain_controller::check_transaction_authorization(const SignedTransaction& ("auth", declaredAuthority)); } } - ilog("Auth check passed"); } ProcessedTransaction chain_controller::apply_transaction(const SignedTransaction& trx, uint32_t skip) diff --git a/libraries/chain/message_handling_contexts.cpp b/libraries/chain/message_handling_contexts.cpp index 7fbb059ec36..63c16c79dcd 100644 --- a/libraries/chain/message_handling_contexts.cpp +++ b/libraries/chain/message_handling_contexts.cpp @@ -10,7 +10,9 @@ namespace eos { namespace chain { void apply_context::require_authorization(const types::AccountName& account) { -#warning TODO + auto itr = boost::find_if(msg.authorization, [&account](const auto& auth) { return auth.account == account; }); + EOS_ASSERT(itr != msg.authorization.end(), tx_missing_auth, + "Transaction is missing required authorization from ${acct}", ("acct", account)); } void apply_context::require_scope(const types::AccountName& account)const { diff --git a/tests/common/macro_support.hpp b/tests/common/macro_support.hpp index 48670b4965b..be43fc958c3 100644 --- a/tests/common/macro_support.hpp +++ b/tests/common/macro_support.hpp @@ -35,11 +35,11 @@ inline std::vector sort_names( std::vector&& names ) { eos::chain::SignedTransaction trx; \ trx.scope = sort_names({ #creator, "eos" }); \ trx.emplaceMessage(config::EosContractName, \ - vector{}, \ + vector{{#creator, "active"}}, \ "newaccount", types::newaccount{#creator, #name, owner, active, recovery, deposit}); \ 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("Created account " << #name); \ } #define MKACCT2(chain, name) \ @@ -80,11 +80,11 @@ inline std::vector sort_names( std::vector&& names ) { { \ eos::chain::SignedTransaction trx; \ trx.scope = sort_names( { #sender, #recipient } ); \ - trx.emplaceMessage(config::EosContractName, \ - vector{}, "lock", types::lock{#sender, #recipient, amount}); \ + trx.emplaceMessage(config::EosContractName, vector{{#sender, "active"}}, \ + "lock", types::lock{#sender, #recipient, 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("Staked " << amount << " to " << #recipient); \ } #define STAKE3(chain, account, amount) STAKE4(chain, account, account, amount) @@ -94,11 +94,11 @@ inline std::vector sort_names( std::vector&& names ) { eos::chain::SignedTransaction trx; \ trx.scope = sort_names( { "eos" } ); \ trx.emplaceMessage(config::EosContractName, \ - vector{}, \ + vector{{#account, "active"}}, \ "unlock", types::unlock{#account, 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("Begin unstake " << amount << " to " << #account); \ } @@ -106,11 +106,11 @@ inline std::vector sort_names( std::vector&& names ) { { \ eos::chain::SignedTransaction trx; \ trx.scope = sort_names( { "eos", #account } ); \ - trx.emplaceMessage(config::EosContractName, \ - vector{}, "claim", types::claim{#account, amount}); \ + trx.emplaceMessage(config::EosContractName, vector{{#account, "active"}}, \ + "claim", types::claim{#account, 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("Finish unstake " << amount << " to " << #account); \ } @@ -119,11 +119,11 @@ inline std::vector sort_names( std::vector&& names ) { eos::chain::SignedTransaction trx; \ trx.scope = sort_names( {#owner, "eos"} ); \ trx.emplaceMessage(config::EosContractName, \ - vector{}, \ + vector{{#owner, "active"}}, \ "setproducer", types::setproducer{#owner, key, cfg}); \ 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("Create producer " << #owner); \ } #define MKPDCR3(chain, owner, key) MKPDCR4(chain, owner, key, BlockchainConfiguration{}); @@ -136,11 +136,11 @@ inline std::vector sort_names( std::vector&& names ) { eos::chain::SignedTransaction trx; \ trx.scope = sort_names( {#voter, "eos"} ); \ trx.emplaceMessage(config::EosContractName, \ - vector{}, \ + vector{{#voter, "active"}}, \ "okproducer", types::okproducer{#voter, #producer, approved? 1 : 0}); \ 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("Set producer approval from " << #voter << " for " << #producer << " to " << approved); \ } @@ -149,11 +149,11 @@ inline std::vector sort_names( std::vector&& names ) { eos::chain::SignedTransaction trx; \ trx.scope = sort_names( {owner, "eos"} ); \ trx.emplaceMessage(config::EosContractName, \ - vector{}, \ + vector{{owner, "active"}}, \ "setproducer", types::setproducer{owner, key, cfg}); \ 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("Update producer " << owner); \ } #define UPPDCR3(chain, owner, key) UPPDCR4(chain, owner, key, chain.get_producer(owner).configuration) diff --git a/tests/tests/native_contract_tests.cpp b/tests/tests/native_contract_tests.cpp index 2a58af116af..963eccc3503 100644 --- a/tests/tests/native_contract_tests.cpp +++ b/tests/tests/native_contract_tests.cpp @@ -99,9 +99,10 @@ BOOST_FIXTURE_TEST_CASE(transfer, testing_fixture) auto unpacked = fc::raw::unpack(packed); BOOST_CHECK_EQUAL( value, unpacked ); trx.messages[0].type = "transfer"; + trx.messages[0].authorization = {{"inita", "active"}}; trx.messages[0].code = config::EosContractName; trx.setMessage(0, "transfer", trans); - chain.push_transaction(trx); + chain.push_transaction(trx, chain_controller::skip_transaction_signatures); BOOST_CHECK_EQUAL(chain.get_liquid_balance("inita"), Asset(100000 - 100)); BOOST_CHECK_EQUAL(chain.get_liquid_balance("initb"), Asset(100000 + 100)); @@ -118,10 +119,10 @@ BOOST_FIXTURE_TEST_CASE(transfer, testing_fixture) BOOST_FIXTURE_TEST_CASE(producer_creation, testing_fixture) { try { Make_Blockchain(chain) + Make_Account(chain, producer); chain.produce_blocks(); BOOST_CHECK_EQUAL(chain.head_block_num(), 1); - Make_Account(chain, producer); Make_Producer(chain, producer, producer_public_key); while (chain.head_block_num() < 3) { @@ -237,10 +238,10 @@ BOOST_FIXTURE_TEST_CASE(producer_voting_parameters_2, testing_fixture) BOOST_FIXTURE_TEST_CASE(producer_voting_1, testing_fixture) { try { Make_Blockchain(chain) - chain.produce_blocks(); - Make_Account(chain, joe); Make_Account(chain, bob); + chain.produce_blocks(); + Make_Producer(chain, joe); Approve_Producer(chain, bob, joe, true); // Produce blocks up to, but not including, the last block in the round @@ -274,10 +275,10 @@ BOOST_FIXTURE_TEST_CASE(producer_voting_1, testing_fixture) { BOOST_FIXTURE_TEST_CASE(producer_voting_2, testing_fixture) { try { Make_Blockchain(chain) - chain.produce_blocks(); - Make_Account(chain, joe); Make_Account(chain, bob); + chain.produce_blocks(); + Make_Producer(chain, joe); Approve_Producer(chain, bob, joe, true); chain.produce_blocks(); @@ -332,11 +333,11 @@ BOOST_FIXTURE_TEST_CASE(producer_proxy_voting, testing_fixture) { auto run = [this](std::vector actions) { Make_Blockchain(chain) - chain.produce_blocks(); - Make_Account(chain, stakeholder); Make_Account(chain, proxy); Make_Account(chain, producer); + chain.produce_blocks(); + Make_Producer(chain, producer); for (auto& action : actions) diff --git a/tests/tests/special_accounts_tests.cpp b/tests/tests/special_accounts_tests.cpp index 31f554e1b1f..52e1e25add5 100644 --- a/tests/tests/special_accounts_tests.cpp +++ b/tests/tests/special_accounts_tests.cpp @@ -86,15 +86,19 @@ BOOST_FIXTURE_TEST_CASE(producers_authority, testing_fixture) { try { Make_Blockchain(chain) - chain.produce_blocks(); Make_Account(chain, alice); Make_Account(chain, bob); Make_Account(chain, charlie); - Make_Account(chain, newproducer1); Make_Producer(chain, newproducer1); - Make_Account(chain, newproducer2); Make_Producer(chain, newproducer2); - Make_Account(chain, newproducer3); Make_Producer(chain, newproducer3); + Make_Account(chain, newproducer1); + Make_Account(chain, newproducer2); + Make_Account(chain, newproducer3); + chain.produce_blocks(); + + Make_Producer(chain, newproducer1); + Make_Producer(chain, newproducer2); + Make_Producer(chain, newproducer3); Approve_Producer(chain, alice, newproducer1, true); Approve_Producer(chain, bob, newproducer2, true); From ec03f1f08edfdc93bc91efe28ebfa221dc44c50f Mon Sep 17 00:00:00 2001 From: Nathan Hourt Date: Tue, 1 Aug 2017 18:25:37 -0500 Subject: [PATCH 10/27] Ref #7: Check all declared auths get required --- libraries/chain/chain_controller.cpp | 10 ++++++--- .../eos/chain/message_handling_contexts.hpp | 15 ++++++++++++- libraries/chain/message_handling_contexts.cpp | 7 +++++- tests/tests/block_tests.cpp | 22 +++++++++++++++++++ 4 files changed, 49 insertions(+), 5 deletions(-) diff --git a/libraries/chain/chain_controller.cpp b/libraries/chain/chain_controller.cpp index db2a7e28af8..42a1a9d5460 100644 --- a/libraries/chain/chain_controller.cpp +++ b/libraries/chain/chain_controller.cpp @@ -604,6 +604,11 @@ void chain_controller::process_message( const ProcessedTransaction& trx, Account apply_context apply_ctx(*this, _db, trx, message, code); apply_message(apply_ctx); + // process_message recurses for each notified account, but we only want to run this check at the top level + if (code == message.code && (_skip_flags & skip_authority_check) == false) + EOS_ASSERT(apply_ctx.all_authorizations_used(), tx_irrelevant_auth, + "Message declared an authority it did not need", ("message", message)); + output.notify.reserve( apply_ctx.notified.size() ); for( uint32_t i = 0; i < apply_ctx.notified.size(); ++i ) { @@ -832,11 +837,10 @@ void chain_controller::initialize_chain(chain_initializer_interface& starter) std::for_each(messages.begin(), messages.end(), [&](const Message& m) { MessageOutput output; ProcessedTransaction trx; /// dummy tranaction required for scope validation - trx.scope = { config::EosContractName, "inita" }; std::sort(trx.scope.begin(), trx.scope.end() ); - with_skip_flags( skip_scope_check, [&](){ + with_skip_flags(skip_scope_check | skip_transaction_signatures | skip_authority_check, [&](){ process_message(trx,m.code,m,output); - } ); + }); }); }); } diff --git a/libraries/chain/include/eos/chain/message_handling_contexts.hpp b/libraries/chain/include/eos/chain/message_handling_contexts.hpp index 15f4ceed380..4b53216a93f 100644 --- a/libraries/chain/include/eos/chain/message_handling_contexts.hpp +++ b/libraries/chain/include/eos/chain/message_handling_contexts.hpp @@ -17,7 +17,8 @@ class apply_context { const chain::Transaction& t, const chain::Message& m, const types::AccountName& code) - :controller(con),db(db),trx(t),msg(m),code(code),mutable_controller(con),mutable_db(db){} + : controller(con), db(db), trx(t), msg(m), code(code), mutable_controller(con), + mutable_db(db), used_authorizations(msg.authorization.size(), false){} int32_t store_i64( Name scope, Name table, Name key, const char* data, uint32_t len); int32_t remove_i64( Name scope, Name table, Name key ); @@ -27,6 +28,7 @@ class apply_context { int32_t load_i64( Name scope, Name code, Name table, Name Key, char* data, uint32_t maxlen ); +<<<<<<< HEAD int32_t front_primary_i128i128( Name scope, Name code, Name table, uint128_t* primary, uint128_t* secondary, char* data, uint32_t maxlen ); int32_t back_primary_i128i128( Name scope, Name code, Name table, @@ -44,10 +46,21 @@ class apply_context { int32_t lowerbound_secondary_i128i128( Name scope, Name code, Name table, uint128_t* primary, uint128_t* secondary, char* data, uint32_t maxlen ); + /** + * @brief Require @ref account to have approved of this message + * @param account The account whose approval is required + * + * This method will check that @ref account is listed in the message's declared authorizations, and marks the + * authorization as used. Note that all authorizations on a message must be used, or the message is invalid. + * + * @throws tx_missing_auth If no sufficient permission was found + */ void require_authorization(const types::AccountName& account); void require_scope(const types::AccountName& account)const; void require_recipient(const types::AccountName& account); + bool all_authorizations_used() const; + const chain_controller& controller; const chainbase::database& db; ///< database where state is stored const chain::Transaction& trx; ///< used to gather the valid read/write scopes diff --git a/libraries/chain/message_handling_contexts.cpp b/libraries/chain/message_handling_contexts.cpp index 63c16c79dcd..69004ef718a 100644 --- a/libraries/chain/message_handling_contexts.cpp +++ b/libraries/chain/message_handling_contexts.cpp @@ -13,6 +13,7 @@ void apply_context::require_authorization(const types::AccountName& account) { auto itr = boost::find_if(msg.authorization, [&account](const auto& auth) { return auth.account == account; }); EOS_ASSERT(itr != msg.authorization.end(), tx_missing_auth, "Transaction is missing required authorization from ${acct}", ("acct", account)); + used_authorizations[itr - msg.authorization.begin()] = true; } void apply_context::require_scope(const types::AccountName& account)const { @@ -34,11 +35,15 @@ void apply_context::require_recipient(const types::AccountName& account) { return recipient == account; }); - if( itr == notified.end() ) { + if (itr == notified.end()) { notified.push_back(account); } } +bool apply_context::all_authorizations_used() const { + return boost::algorithm::all_of_equal(used_authorizations, true); +} + int32_t apply_context::load_i64( Name scope, Name code, Name table, Name key, char* value, uint32_t valuelen ) { require_scope( scope ); diff --git a/tests/tests/block_tests.cpp b/tests/tests/block_tests.cpp index 7bc9380133d..57021344169 100644 --- a/tests/tests/block_tests.cpp +++ b/tests/tests/block_tests.cpp @@ -170,6 +170,28 @@ BOOST_FIXTURE_TEST_CASE(trx_variant, testing_fixture) { } } +BOOST_FIXTURE_TEST_CASE(irrelevant_auth, testing_fixture) { + try { + Make_Blockchain(chain) + Make_Account(chain, joe); + chain.produce_blocks(); + + ProcessedTransaction trx; + trx.scope = sort_names({"joe", "inita"}); + trx.emplaceMessage(config::EosContractName, vector{{"inita", "active"}}, + "transfer", types::transfer{"inita", "joe", 50}); + trx.expiration = chain.head_block_time() + 100; + trx.set_reference_block(chain.head_block_id()); + chain.push_transaction(trx, chain_controller::skip_transaction_signatures); + + chain.clear_pending(); + chain.push_transaction(trx, chain_controller::skip_transaction_signatures); + chain.clear_pending(); + + trx.messages.front().authorization.emplace_back(types::AccountPermission{"initb", "active"}); + BOOST_CHECK_THROW(chain.push_transaction(trx, chain_controller::skip_transaction_signatures), tx_irrelevant_auth); +} FC_LOG_AND_RETHROW() } + BOOST_AUTO_TEST_CASE(name_test) { using eos::types::Name; Name temp; From 94fc4331de974e2aceb18d970da29ce1a778e924 Mon Sep 17 00:00:00 2001 From: Nathan Hourt Date: Wed, 2 Aug 2017 10:51:16 -0500 Subject: [PATCH 11/27] Ref #123: Define types Define the types documented in the issue. TODO: Implement logic --- .../eos/chain/permission_link_object.hpp | 81 +++++++++++++++++++ libraries/chain/include/eos/chain/types.hpp | 3 + libraries/types/types.eos | 6 ++ 3 files changed, 90 insertions(+) create mode 100644 libraries/chain/include/eos/chain/permission_link_object.hpp diff --git a/libraries/chain/include/eos/chain/permission_link_object.hpp b/libraries/chain/include/eos/chain/permission_link_object.hpp new file mode 100644 index 00000000000..ec286b54ad4 --- /dev/null +++ b/libraries/chain/include/eos/chain/permission_link_object.hpp @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2017, Respective Authors. + * + * 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 "multi_index_includes.hpp" + +namespace eos { namespace chain { + /** + * @brief The permission_link_object class assigns permission_objects to message types + * + * This class records the links from contracts and message types within those contracts to permission_objects + * defined by the user to record the authority required to execute those messages within those contracts. For + * example, suppose we have a contract called "currency" and that contract defines a message called "transfer". + * Furthermore, suppose a user, "joe", has a permission level called "money" and joe would like to require that + * permission level in order for his account to invoke currency.transfer. To do this, joe would create a + * permission_link_object for his account with "currency" as the code account, "transfer" as the message_type, and + * "money" as the required_permission. After this, in order to validate, any message to "currency" of type + * "transfer" that requires joe's approval would require signatures sufficient to satisfy joe's "money" authority. + */ + class permission_link_object : public chainbase::object { + OBJECT_CTOR(permission_link_object) + + id_type id; + AccountName account; + AccountName code; + FuncName message_type; + PermissionName required_permission; + }; + + struct by_message_type; + struct by_permission_name; + using permission_link_index = chainbase::shared_multi_index_container< + permission_link_object, + indexed_by< + ordered_unique, + BOOST_MULTI_INDEX_MEMBER(permission_link_object, permission_link_object::id_type, id) + >, + ordered_unique, + composite_key + >, + ordered_unique, + composite_key + > + > + >; +} } // eos::chain + +CHAINBASE_SET_INDEX_TYPE(eos::chain::permission_link_object, eos::chain::permission_link_index) + +FC_REFLECT(eos::chain::permission_link_object, (id)(account)(code)(message_type)(required_permission)) diff --git a/libraries/chain/include/eos/chain/types.hpp b/libraries/chain/include/eos/chain/types.hpp index 62949e704a7..e1bb51d7387 100644 --- a/libraries/chain/include/eos/chain/types.hpp +++ b/libraries/chain/include/eos/chain/types.hpp @@ -118,6 +118,7 @@ namespace eos { namespace chain { using eos::types::Transaction; using eos::types::PermissionName; using eos::types::TypeName; + using eos::types::FuncName; using eos::types::Time; using eos::types::Field; using eos::types::String; @@ -161,6 +162,7 @@ namespace eos { namespace chain { null_object_type, account_object_type, permission_object_type, + permission_link_object_type, action_code_object_type, key_value_object_type, key128x128_value_object_type, @@ -206,6 +208,7 @@ FC_REFLECT_ENUM(eos::chain::object_type, (null_object_type) (account_object_type) (permission_object_type) + (permission_link_object_type) (action_code_object_type) (key_value_object_type) (key128x128_value_object_type) diff --git a/libraries/types/types.eos b/libraries/types/types.eos index 5c5be6218a8..7cfaefee69a 100644 --- a/libraries/types/types.eos +++ b/libraries/types/types.eos @@ -125,3 +125,9 @@ struct UpdatePermission struct DeletePermission account AccountName permission PermissionName + +struct requirepermission + account AccountName + code AccountName + type FuncName + requirement PermissionName From e4f39796749a6e8eac8c8046803387c16c3037ad Mon Sep 17 00:00:00 2001 From: Nathan Hourt Date: Wed, 2 Aug 2017 11:33:30 -0500 Subject: [PATCH 12/27] Ref #123: Implement lookup_minimum_permission Add docs on some of the types, register the index, and implement chain_controller::lookup_minimum_permission. --- libraries/chain/chain_controller.cpp | 19 ++++++++++++++++--- .../eos/chain/permission_link_object.hpp | 12 ++++++++++++ libraries/types/types.eos | 8 ++++---- 3 files changed, 32 insertions(+), 7 deletions(-) diff --git a/libraries/chain/chain_controller.cpp b/libraries/chain/chain_controller.cpp index 42a1a9d5460..23bfa1f5a31 100644 --- a/libraries/chain/chain_controller.cpp +++ b/libraries/chain/chain_controller.cpp @@ -31,6 +31,7 @@ #include #include #include +#include #include @@ -533,9 +534,20 @@ const permission_object& chain_controller::lookup_minimum_permission(types::Acco 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")); + // First look up a specific link for this message type + auto key = boost::make_tuple(authorizer_account, code_account, type); + auto link = _db.find(key); + // If no specific link found, check for a contract-wide default + if (link == nullptr) { + get<2>(key) = ""; + link = _db.find(key); + } + + // If no specific or default link found, use active permission + auto permissionKey = boost::make_tuple(authorizer_account, "active"); + if (link != nullptr) + get<1>(permissionKey) = link->required_permission; + return _db.get(permissionKey); } FC_CAPTURE_AND_RETHROW((authorizer_account)(code_account)(type)) } @@ -799,6 +811,7 @@ uint32_t chain_controller::last_irreversible_block_num() const { void chain_controller::initialize_indexes() { _db.add_index(); _db.add_index(); + _db.add_index(); _db.add_index(); _db.add_index(); _db.add_index(); diff --git a/libraries/chain/include/eos/chain/permission_link_object.hpp b/libraries/chain/include/eos/chain/permission_link_object.hpp index ec286b54ad4..d940f2822b4 100644 --- a/libraries/chain/include/eos/chain/permission_link_object.hpp +++ b/libraries/chain/include/eos/chain/permission_link_object.hpp @@ -38,14 +38,26 @@ namespace eos { namespace chain { * permission_link_object for his account with "currency" as the code account, "transfer" as the message_type, and * "money" as the required_permission. After this, in order to validate, any message to "currency" of type * "transfer" that requires joe's approval would require signatures sufficient to satisfy joe's "money" authority. + * + * Accounts may set links to individual message types, or may set default permission requirements for all messages + * to a given contract. To set the default for all messages to a given contract, set @ref message_type to the empty + * string. When looking up which permission to use, if a link is found for a particular {account, code, + * message_type} triplet, then the required_permission from that link is used. If no such link is found, but a link + * is found for {account, code, ""}, then the required_permission from that link is used. If no such link is found, + * account's active authority is used. */ class permission_link_object : public chainbase::object { OBJECT_CTOR(permission_link_object) id_type id; + /// The account which is defining its permission requirements AccountName account; + /// The contract which account requires @ref required_permission to invoke AccountName code; + /// The message type which account requires @ref required_permission to invoke + /// May be empty; if so, it sets a default @ref required_permission for all messages to @ref code FuncName message_type; + /// The permission level which @ref account requires for the specified message types PermissionName required_permission; }; diff --git a/libraries/types/types.eos b/libraries/types/types.eos index 7cfaefee69a..ecdabdc06cc 100644 --- a/libraries/types/types.eos +++ b/libraries/types/types.eos @@ -127,7 +127,7 @@ struct DeletePermission permission PermissionName struct requirepermission - account AccountName - code AccountName - type FuncName - requirement PermissionName + account AccountName # The account to require permissions for + code AccountName # The contract to require permissions to invoke + type FuncName # The message type to require permissions to invoke (if empty, all message types for contract) + requirement PermissionName # The permission name to require From 05550b8258a1e0d9329556e1f460f56261d1dae6 Mon Sep 17 00:00:00 2001 From: Nathan Hourt Date: Wed, 2 Aug 2017 13:51:58 -0500 Subject: [PATCH 13/27] Ref #123: Rename requirepermission->linkauth The name requirepermission was too long, so I renamed it linkauth --- .../include/eos/native_contract/eos_contract.hpp | 1 + .../native_contract/native_contract_chain_initializer.cpp | 1 + libraries/types/types.eos | 4 ++-- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/libraries/native_contract/include/eos/native_contract/eos_contract.hpp b/libraries/native_contract/include/eos/native_contract/eos_contract.hpp index 3099ddd869e..9866dbd6f4c 100644 --- a/libraries/native_contract/include/eos/native_contract/eos_contract.hpp +++ b/libraries/native_contract/include/eos/native_contract/eos_contract.hpp @@ -18,6 +18,7 @@ void apply_eos_okproducer(chain::apply_context&); void apply_eos_setproducer(chain::apply_context&); void apply_eos_setproxy(chain::apply_context&); void apply_eos_setcode(chain::apply_context&); +void apply_eos_linkauth(chain::apply_context&); } // namespace eos } // namespace native diff --git a/libraries/native_contract/native_contract_chain_initializer.cpp b/libraries/native_contract/native_contract_chain_initializer.cpp index aff9daf8e65..bbf0519ff76 100644 --- a/libraries/native_contract/native_contract_chain_initializer.cpp +++ b/libraries/native_contract/native_contract_chain_initializer.cpp @@ -48,6 +48,7 @@ void native_contract_chain_initializer::register_types(chain_controller& chain, SET_APP_HANDLER( eos, eos, setproducer ); SET_APP_HANDLER( eos, eos, setproxy ); SET_APP_HANDLER( eos, eos, setcode ); + SET_APP_HANDLER( eos, eos, linkauth ); } std::vector native_contract_chain_initializer::prepare_database(chain_controller& chain, diff --git a/libraries/types/types.eos b/libraries/types/types.eos index ecdabdc06cc..b1ca2d1a611 100644 --- a/libraries/types/types.eos +++ b/libraries/types/types.eos @@ -126,8 +126,8 @@ struct DeletePermission account AccountName permission PermissionName -struct requirepermission +struct linkauth account AccountName # The account to require permissions for code AccountName # The contract to require permissions to invoke type FuncName # The message type to require permissions to invoke (if empty, all message types for contract) - requirement PermissionName # The permission name to require + requirement PermissionName # The permission name to require (if empty, use default permission requirement) From 8c5a0e700c9b5bc80c6faeb902a76dfe5b2a9810 Mon Sep 17 00:00:00 2001 From: Nathan Hourt Date: Wed, 2 Aug 2017 13:52:33 -0500 Subject: [PATCH 14/27] Ref #123: Implement linkauth Implement the linkauth handler in the system contract --- libraries/native_contract/eos_contract.cpp | 35 ++++++++++++++++++++-- libraries/types/types.eos | 4 +-- 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/libraries/native_contract/eos_contract.cpp b/libraries/native_contract/eos_contract.cpp index cf8ea4d943a..13117b56818 100644 --- a/libraries/native_contract/eos_contract.cpp +++ b/libraries/native_contract/eos_contract.cpp @@ -1,12 +1,13 @@ #include -#include #include #include -#include #include +#include +#include #include +#include #include #include #include @@ -360,8 +361,36 @@ void apply_eos_setproxy(apply_context& context) { */ } +void apply_eos_linkauth(apply_context& context) { + auto requirement = context.msg.as(); -///@} + EOS_ASSERT(!requirement.requirement.empty(), message_validate_exception, "Required permission cannot be empty"); + + context.require_authorization(requirement.account); + + auto& db = context.mutable_db; + db.get(requirement.account); + db.get(requirement.code); + db.get(requirement.requirement); + + auto linkKey = boost::make_tuple(requirement.account, requirement.code, requirement.type); + auto link = db.find(linkKey); + + if (link) { + EOS_ASSERT(link->required_permission != requirement.requirement, message_precondition_exception, + "Attempting to update required authority, but new requirement is same as old."); + db.modify(*link, [requirement = requirement.requirement](permission_link_object& link) { + link.required_permission = requirement; + }); + } else { + db.create([&requirement](permission_link_object& link) { + link.account = requirement.account; + link.code = requirement.code; + link.message_type = requirement.type; + link.required_permission = requirement.requirement; + }); + } +} } // namespace eos } // namespace native diff --git a/libraries/types/types.eos b/libraries/types/types.eos index b1ca2d1a611..5aaea9332b6 100644 --- a/libraries/types/types.eos +++ b/libraries/types/types.eos @@ -129,5 +129,5 @@ struct DeletePermission struct linkauth account AccountName # The account to require permissions for code AccountName # The contract to require permissions to invoke - type FuncName # The message type to require permissions to invoke (if empty, all message types for contract) - requirement PermissionName # The permission name to require (if empty, use default permission requirement) + type FuncName + requirement PermissionName # The permission name to require From 12dcd352dba3ce163f4c542cf390da59dcf12fd2 Mon Sep 17 00:00:00 2001 From: Nathan Hourt Date: Wed, 2 Aug 2017 15:49:35 -0500 Subject: [PATCH 15/27] Ref #123: Define/Implement unlinkauth Add an unlinkauth message type which removes a link from a message type to a required authority --- libraries/native_contract/eos_contract.cpp | 11 ++++++++++- .../include/eos/native_contract/eos_contract.hpp | 1 + .../native_contract_chain_initializer.cpp | 1 + libraries/types/types.eos | 5 +++++ 4 files changed, 17 insertions(+), 1 deletion(-) diff --git a/libraries/native_contract/eos_contract.cpp b/libraries/native_contract/eos_contract.cpp index 13117b56818..d617174980e 100644 --- a/libraries/native_contract/eos_contract.cpp +++ b/libraries/native_contract/eos_contract.cpp @@ -378,7 +378,7 @@ void apply_eos_linkauth(apply_context& context) { if (link) { EOS_ASSERT(link->required_permission != requirement.requirement, message_precondition_exception, - "Attempting to update required authority, but new requirement is same as old."); + "Attempting to update required authority, but new requirement is same as old"); db.modify(*link, [requirement = requirement.requirement](permission_link_object& link) { link.required_permission = requirement; }); @@ -392,5 +392,14 @@ void apply_eos_linkauth(apply_context& context) { } } +void apply_eos_unlinkauth(apply_context& context) { + auto& db = context.mutable_db; + auto unlink = context.msg.as(); + auto linkKey = boost::make_tuple(unlink.account, unlink.code, unlink.type); + auto link = db.find(linkKey); + EOS_ASSERT(link != nullptr, message_precondition_exception, "Attempting to unlink authority, but no link found"); + db.remove(*link); +} + } // namespace eos } // namespace native diff --git a/libraries/native_contract/include/eos/native_contract/eos_contract.hpp b/libraries/native_contract/include/eos/native_contract/eos_contract.hpp index 9866dbd6f4c..e820a6dc9b1 100644 --- a/libraries/native_contract/include/eos/native_contract/eos_contract.hpp +++ b/libraries/native_contract/include/eos/native_contract/eos_contract.hpp @@ -19,6 +19,7 @@ void apply_eos_setproducer(chain::apply_context&); void apply_eos_setproxy(chain::apply_context&); void apply_eos_setcode(chain::apply_context&); void apply_eos_linkauth(chain::apply_context&); +void apply_eos_unlinkauth(chain::apply_context&); } // namespace eos } // namespace native diff --git a/libraries/native_contract/native_contract_chain_initializer.cpp b/libraries/native_contract/native_contract_chain_initializer.cpp index bbf0519ff76..ba5c0bb2e09 100644 --- a/libraries/native_contract/native_contract_chain_initializer.cpp +++ b/libraries/native_contract/native_contract_chain_initializer.cpp @@ -49,6 +49,7 @@ void native_contract_chain_initializer::register_types(chain_controller& chain, SET_APP_HANDLER( eos, eos, setproxy ); SET_APP_HANDLER( eos, eos, setcode ); SET_APP_HANDLER( eos, eos, linkauth ); + SET_APP_HANDLER( eos, eos, unlinkauth ); } std::vector native_contract_chain_initializer::prepare_database(chain_controller& chain, diff --git a/libraries/types/types.eos b/libraries/types/types.eos index 5aaea9332b6..f2f684ee8d6 100644 --- a/libraries/types/types.eos +++ b/libraries/types/types.eos @@ -131,3 +131,8 @@ struct linkauth code AccountName # The contract to require permissions to invoke type FuncName requirement PermissionName # The permission name to require + +struct unlinkauth + account AccountName # The account to require permissions for + code AccountName # The contract to require permissions to invoke + type FuncName From d78f1a6360fc6d609c1e498828064a69380742b7 Mon Sep 17 00:00:00 2001 From: Nathan Hourt Date: Wed, 2 Aug 2017 16:09:10 -0500 Subject: [PATCH 16/27] Add satisfiability check to validate(Authority) Add a check to the validate function on authority which checks that the authority can be satisfied, which is to say, that the total of all the weights meets or exceeds the threshold. --- libraries/chain/include/eos/chain/authority.hpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/libraries/chain/include/eos/chain/authority.hpp b/libraries/chain/include/eos/chain/authority.hpp index e23576ea588..df8165f78ea 100644 --- a/libraries/chain/include/eos/chain/authority.hpp +++ b/libraries/chain/include/eos/chain/authority.hpp @@ -81,20 +81,25 @@ AuthorityChecker MakeAuthorityChecker(F&& pta, const flat_setkey < k.key ) return false; + totalWeight += k.weight; } const types::AccountPermissionWeight* pa = nullptr; for( const auto& a : auth.accounts ) { if( !pa ) pa = &a; else if( pa->permission < a.permission ) return false; + totalWeight += a.weight; } - return true; + return totalWeight >= auth.threshold; } } } // namespace eos::chain From fbff3c3f0ad08dc51b8e0742e1c4f3c2a4b11229 Mon Sep 17 00:00:00 2001 From: Nathan Hourt Date: Wed, 2 Aug 2017 16:56:13 -0500 Subject: [PATCH 17/27] Ref #123: Implement updateauth, deleteauth Though not strictly part of issue 123, this is a necessary step in order to complete #123. Rename UpdatePermission->updateauth and DeletePermission->deleteauth, and implement them. At this point, I think #123 is fully implemented and ready for testing. --- libraries/native_contract/eos_contract.cpp | 64 ++++++++++++++++++- .../eos/native_contract/eos_contract.hpp | 2 + .../native_contract_chain_initializer.cpp | 2 + libraries/types/types.eos | 6 +- 4 files changed, 70 insertions(+), 4 deletions(-) diff --git a/libraries/native_contract/eos_contract.cpp b/libraries/native_contract/eos_contract.cpp index d617174980e..c5fa51f33bc 100644 --- a/libraries/native_contract/eos_contract.cpp +++ b/libraries/native_contract/eos_contract.cpp @@ -361,9 +361,71 @@ void apply_eos_setproxy(apply_context& context) { */ } +void apply_eos_updateauth(apply_context& context) { + auto update = context.msg.as(); + EOS_ASSERT(!update.permission.empty(), message_validate_exception, "Cannot create authority with empty name"); + EOS_ASSERT(validate(update.authority), message_validate_exception, + "Invalid authority: ${auth}", ("auth", update.authority)); + if (update.permission == "active") + EOS_ASSERT(update.parent == "owner", message_validate_exception, "Cannot change active authority's parent"); + if (update.permission == "owner") + EOS_ASSERT(update.parent.empty(), message_validate_exception, "Cannot change owner authority's parent"); + + auto& db = context.mutable_db; + context.require_authorization(update.account); + + db.get(update.account); + auto& parent = db.get(boost::make_tuple(update.account, update.parent)); + for (auto accountPermission : update.authority.accounts) { + db.get(accountPermission.permission.account); + db.get(boost::make_tuple(accountPermission.permission.account, + accountPermission.permission.permission)); + } + + const auto& permission = db.find(boost::make_tuple(update.account, update.permission)); + if (permission) { + db.modify(*permission, [&update, parent = parent.id](permission_object& po) { + po.auth = update.authority; + po.parent = parent; + }); + } else { + db.create([&update, parent = parent.id](permission_object& po) { + po.name = update.permission; + po.owner = update.account; + po.auth = update.authority; + po.parent = parent; + }); + } +} + +void apply_eos_deleteauth(apply_context& context) { + auto remove = context.msg.as(); + EOS_ASSERT(remove.permission != "active", message_validate_exception, "Cannot delete active authority"); + EOS_ASSERT(remove.permission != "owner", message_validate_exception, "Cannot delete owner authority"); + + auto& db = context.mutable_db; + context.require_authorization(remove.account); + const auto& permission = db.get(boost::make_tuple(remove.account, remove.permission)); + + { // Check for children + const auto& index = db.get_index(); + auto range = index.equal_range(permission.id); + EOS_ASSERT(range.first == range.second, message_precondition_exception, + "Cannot delete an authority which has children. Delete the children first"); + } + + { // Check for links to this permission + const auto& index = db.get_index(); + auto range = index.equal_range(boost::make_tuple(remove.account, remove.permission)); + EOS_ASSERT(range.first == range.second, message_precondition_exception, + "Cannot delete a linked authority. Unlink the authority first"); + } + + db.remove(permission); +} + void apply_eos_linkauth(apply_context& context) { auto requirement = context.msg.as(); - EOS_ASSERT(!requirement.requirement.empty(), message_validate_exception, "Required permission cannot be empty"); context.require_authorization(requirement.account); diff --git a/libraries/native_contract/include/eos/native_contract/eos_contract.hpp b/libraries/native_contract/include/eos/native_contract/eos_contract.hpp index e820a6dc9b1..0471e53339e 100644 --- a/libraries/native_contract/include/eos/native_contract/eos_contract.hpp +++ b/libraries/native_contract/include/eos/native_contract/eos_contract.hpp @@ -18,6 +18,8 @@ void apply_eos_okproducer(chain::apply_context&); void apply_eos_setproducer(chain::apply_context&); void apply_eos_setproxy(chain::apply_context&); void apply_eos_setcode(chain::apply_context&); +void apply_eos_updateauth(chain::apply_context&); +void apply_eos_deleteauth(chain::apply_context&); void apply_eos_linkauth(chain::apply_context&); void apply_eos_unlinkauth(chain::apply_context&); diff --git a/libraries/native_contract/native_contract_chain_initializer.cpp b/libraries/native_contract/native_contract_chain_initializer.cpp index ba5c0bb2e09..13dffb4cc95 100644 --- a/libraries/native_contract/native_contract_chain_initializer.cpp +++ b/libraries/native_contract/native_contract_chain_initializer.cpp @@ -48,6 +48,8 @@ void native_contract_chain_initializer::register_types(chain_controller& chain, SET_APP_HANDLER( eos, eos, setproducer ); SET_APP_HANDLER( eos, eos, setproxy ); SET_APP_HANDLER( eos, eos, setcode ); + SET_APP_HANDLER( eos, eos, updateauth ); + SET_APP_HANDLER( eos, eos, deleteauth ); SET_APP_HANDLER( eos, eos, linkauth ); SET_APP_HANDLER( eos, eos, unlinkauth ); } diff --git a/libraries/types/types.eos b/libraries/types/types.eos index f2f684ee8d6..d60ff179458 100644 --- a/libraries/types/types.eos +++ b/libraries/types/types.eos @@ -116,17 +116,17 @@ struct setproxy proxy AccountName # The account to cast votes with stakeholder's stake weight -struct UpdatePermission +struct updateauth account AccountName permission PermissionName parent PermissionName authority Authority -struct DeletePermission +struct deleteauth account AccountName permission PermissionName -struct linkauth +struct linkauth # Specify a particular required permission for account to approve specified message type account AccountName # The account to require permissions for code AccountName # The contract to require permissions to invoke type FuncName From d15ce5ebbc58869aeaebbd78b3fddb73343e4d46 Mon Sep 17 00:00:00 2001 From: Nathan Hourt Date: Thu, 3 Aug 2017 13:28:16 -0500 Subject: [PATCH 18/27] Add Complex_Authority test macro Add a new testing helper macro, Complex_Authority, to create an arbitrary Authority inline with a slightly less confusing syntax --- tests/common/database_fixture.hpp | 271 +------------------------- tests/common/macro_support.hpp | 12 ++ tests/common/testing_macros.hpp | 311 ++++++++++++++++++++++++++++++ tests/tests/misc_tests.cpp | 29 +-- 4 files changed, 342 insertions(+), 281 deletions(-) create mode 100644 tests/common/testing_macros.hpp diff --git a/tests/common/database_fixture.hpp b/tests/common/database_fixture.hpp index bab115a0d6e..b655700cd88 100644 --- a/tests/common/database_fixture.hpp +++ b/tests/common/database_fixture.hpp @@ -45,6 +45,9 @@ using namespace eos::chain; extern uint32_t EOS_TESTING_GENESIS_TIMESTAMP; +#define HAVE_DATABASE_FIXTURE +#include "testing_macros.hpp" + #define TEST_DB_SIZE (1024*1024*1000) #define EOS_REQUIRE_THROW( expr, exc_type ) \ @@ -217,272 +220,4 @@ class testing_network { std::map blockchains; }; -/// Some helpful macros to reduce boilerplate when making testcases -/// @{ -#include "macro_support.hpp" - -/** - * @brief Create/Open a testing_blockchain, optionally with an ID - * - * Creates and opens a testing_blockchain with the first argument as its name, and, if present, the second argument as - * its ID. The ID should be provided without quotes. - * - * Example: - * @code{.cpp} - * // Create testing_blockchain chain1 - * Make_Blockchain(chain1) - * - * // The above creates the following objects: - * chainbase::database chain1_db; - * block_log chain1_log; - * fork_database chain1_fdb; - * native_contract::native_contract_chain_initializer chain1_initializer; - * testing_blockchain chain1; - * @endcode - */ -#define Make_Blockchain(...) BOOST_PP_OVERLOAD(MKCHAIN, __VA_ARGS__)(__VA_ARGS__) -/** - * @brief Similar to @ref Make_Blockchain, but works with several chains at once - * - * Creates and opens several testing_blockchains - * - * Example: - * @code{.cpp} - * // Create testing_blockchains chain1 and chain2, with chain2 having ID "id2" - * Make_Blockchains((chain1)(chain2, id2)) - * @endcode - */ -#define Make_Blockchains(...) BOOST_PP_SEQ_FOR_EACH(MKCHAINS_MACRO, _, __VA_ARGS__) - -/** - * @brief Make_Network is a shorthand way to create a testing_network and connect some testing_blockchains to it. - * - * Example usage: - * @code{.cpp} - * // Create and open testing_blockchains named alice, bob, and charlie - * MKDBS((alice)(bob)(charlie)) - * // Create a testing_network named net and connect alice and bob to it - * Make_Network(net, (alice)(bob)) - * - * // Connect charlie to net, then disconnect alice - * net.connect_blockchain(charlie); - * net.disconnect_blockchain(alice); - * - * // Create a testing_network named net2 with no blockchains connected - * Make_Network(net2) - * @endcode - */ -#define Make_Network(...) BOOST_PP_OVERLOAD(MKNET, __VA_ARGS__)(__VA_ARGS__) - -/** - * @brief Make_Key is a shorthand way to create a keypair - * - * @code{.cpp} - * // This line: - * Make_Key(a_key) - * // ...defines these objects: - * private_key_type a_key_private_key; - * PublicKey a_key_public_key; - * // The private key is generated off of the sha256 hash of "a_key_private_key", so it should be unique from all - * // other keys created with MKKEY in the same scope. - * @endcode - */ -#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(); \ - BOOST_TEST_CHECKPOINT("Created key " #name "_public_key"); - -/** - * @brief Key_Authority is a shorthand way to create an inline Authority based on a key - * - * Invoke Key_Authority passing the name of a public key in the current scope, and AUTHK will resolve inline to an authority - * which can be satisfied by a signature generated by the corresponding private key. - */ -#define Key_Authority(pubkey) (Authority{1, {{pubkey, 1}}, {}}) -/** - * @brief Account_Authority is a shorthand way to create an inline Authority based on an account - * - * Invoke Account_Authority passing the name of an account, and AUTHA will resolve inline to an authority which can be satisfied by - * the provided account's active authority. - */ -#define Account_Authority(account) (Authority{1, {}, {{{#account, "active"}, 1}}}) - -/** - * @brief Make_Account is a shorthand way to create an account - * - * Use Make_Account to create an account, including keys. The changes will be applied via a transaction applied to the - * provided blockchain object. The changes will not be incorporated into a block; they will be left in the pending - * state. - * - * Unless overridden, new accounts are created with a balance of Asset(100) - * - * Example: - * @code{.cpp} - * Make_Account(chain, joe) - * // ... creates these objects: - * private_key_type joe_private_key; - * PublicKey joe_public_key; - * // ...and also registers the account joe with owner and active authorities satisfied by these keys, created by - * // init0, with init0's active authority as joe's recovery authority, and initially endowed with Asset(100) - * @endcode - * - * You may specify a third argument for the creating account: - * @code{.cpp} - * // Same as MKACCT(chain, joe) except that sam will create joe's account instead of init0 - * Make_Account(chain, joe, sam) - * @endcode - * - * You may specify a fourth argument for the amount to transfer in account creation: - * @code{.cpp} - * // Same as MKACCT(chain, joe, sam) except that sam will send joe ASSET(100) during creation - * Make_Account(chain, joe, sam, Asset(100)) - * @endcode - * - * You may specify a fifth argument, which will be used as the owner authority (must be an Authority, NOT a key!). - * - * You may specify a sixth argument, which will be used as the active authority. If six or more arguments are provided, - * the default keypair will NOT be created or put into scope. - * - * You may specify a seventh argument, which will be used as the recovery authority. - */ -#define Make_Account(...) BOOST_PP_OVERLOAD(MKACCT, __VA_ARGS__)(__VA_ARGS__) - -/** - * @brief Shorthand way to transfer funds - * - * Use Transfer_Asset to send funds from one account to another: - * @code{.cpp} - * // Send 10 EOS from alice to bob - * Transfer_Asset(chain, alice, bob, Asset(10)); - * - * // Send 10 EOS from alice to bob with memo "Thanks for all the fish!" - * Transfer_Asset(chain, alice, bob, Asset(10), "Thanks for all the fish!"); - * @endcode - * - * The changes will be applied via a transaction applied to the provided blockchain object. The changes will not be - * incorporated into a block; they will be left in the pending state. - */ -#define Transfer_Asset(...) BOOST_PP_OVERLOAD(XFER, __VA_ARGS__)(__VA_ARGS__) - -/** - * @brief Shorthand way to convert liquid funds to staked funds - * - * Use Stake_Asset to stake liquid funds: - * @code{.cpp} - * // Convert 10 of bob's EOS from liquid to staked - * Stake_Asset(chain, bob, Asset(10).amount); - * - * // Stake and transfer 10 EOS from alice to bob (alice pays liquid EOS and bob receives stake) - * Stake_Asset(chain, alice, bob, Asset(10).amount); - * @endcode - */ -#define Stake_Asset(...) BOOST_PP_OVERLOAD(STAKE, __VA_ARGS__)(__VA_ARGS__) - -/** - * @brief Shorthand way to begin conversion from staked funds to liquid funds - * - * Use Unstake_Asset to begin unstaking funds: - * @code{.cpp} - * // Begin unstaking 10 of bob's EOS - * Unstake_Asset(chain, bob, Asset(10).amount); - * @endcode - * - * This can also be used to cancel an unstaking in progress, by passing Asset(0) as the amount. - */ -#define Begin_Unstake_Asset(...) BOOST_PP_OVERLOAD(BEGIN_UNSTAKE, __VA_ARGS__)(__VA_ARGS__) - -/** - * @brief Shorthand way to claim unstaked EOS as liquid - * - * Use Finish_Unstake_Asset to liquidate unstaked funds: - * @code{.cpp} - * // Reclaim as liquid 10 of bob's unstaked EOS - * Unstake_Asset(chain, bob, Asset(10).amount); - * @endcode - */ -#define Finish_Unstake_Asset(...) BOOST_PP_OVERLOAD(FINISH_UNSTAKE, __VA_ARGS__)(__VA_ARGS__) - - -/** - * @brief Shorthand way to set voting proxy - * - * Use Set_Proxy to set what account a stakeholding account proxies its voting power to - * @code{.cpp} - * // Proxy sam's votes to bob - * Set_Proxy(chain, sam, bob); - * - * // Unproxy sam's votes - * Set_Proxy(chain, sam, sam); - * @endcode - */ -#define Set_Proxy(chain, stakeholder, proxy) \ -{ \ - eos::chain::SignedTransaction trx; \ - if (std::string(#stakeholder) != std::string(#proxy)) \ - trx.emplaceMessage(config::EosContractName, \ - vector{ {#stakeholder,"active"} }, "setproxy", types::setproxy{#stakeholder, #proxy}); \ - else \ - trx.emplaceMessage(config::EosContractName, \ - vector{ {#stakeholder,"active"} }, "setproxy", types::setproxy{#stakeholder, #proxy}); \ - trx.expiration = chain.head_block_time() + 100; \ - trx.set_reference_block(chain.head_block_id()); \ - chain.push_transaction(trx); \ -} - -/** - * @brief Shorthand way to create a block producer - * - * Use Make_Producer to create a block producer: - * @code{.cpp} - * // Create a block producer belonging to joe using signing_key as the block signing key and config as the producer's - * // vote for a @ref BlockchainConfiguration: - * Make_Producer(chain, joe, signing_key, config); - * - * // Create a block producer belonging to joe using signing_key as the block signing key: - * Make_Producer(chain, joe, signing_key); - * - * // Create a block producer belonging to joe, using a new key as the block signing key: - * Make_Producer(chain, joe); - * // ... creates the objects: - * private_key_type joe_producer_private_key; - * PublicKey joe_producer_public_key; - * @endcode - */ -#define Make_Producer(...) BOOST_PP_OVERLOAD(MKPDCR, __VA_ARGS__)(__VA_ARGS__) - -/** - * @brief Shorthand way to set approval of a block producer - * - * Use Approve_Producer to change an account's approval of a block producer: - * @code{.cpp} - * // Set joe's approval for pete's block producer to Approve - * Approve_Producer(chain, joe, pete, true); - * // Set joe's approval for pete's block producer to Disapprove - * Approve_Producer(chain, joe, pete, false); - * @endcode - */ -#define Approve_Producer(...) BOOST_PP_OVERLOAD(APPDCR, __VA_ARGS__)(__VA_ARGS__) - -/** - * @brief Shorthand way to update a block producer - * - * @note Unlike with the Make_* macros, the Update_* macros take an expression as the owner/name field, so be sure to - * wrap names like this in quotes. You may also pass a normal C++ expression to be evaulated here instead. The reason - * for this discrepancy is that the Make_* macros add identifiers to the current scope based on the owner/name field; - * moreover, which can't be done with C++ expressions; however, the Update_* macros do not add anything to the scope, - * and it's more likely that these will be used in a loop or other context where it is inconvenient to know the - * owner/name at compile time. - * - * Use Update_Producer to update a block producer: - * @code{.cpp} - * // Update a block producer belonging to joe using signing_key as the new block signing key, and config as the - * // producer's new vote for a @ref BlockchainConfiguration: - * Update_Producer(chain, "joe", signing_key, config) - * - * // Update a block producer belonging to joe using signing_key as the new block signing key: - * Update_Producer(chain, "joe", signing_key) - * @endcode - */ -#define Update_Producer(...) BOOST_PP_OVERLOAD(UPPDCR, __VA_ARGS__)(__VA_ARGS__) -/// @} } } diff --git a/tests/common/macro_support.hpp b/tests/common/macro_support.hpp index be43fc958c3..6cc6f1978b0 100644 --- a/tests/common/macro_support.hpp +++ b/tests/common/macro_support.hpp @@ -1,3 +1,7 @@ +#pragma once + +#include + /** * @file Contains support macros for the testcase helper macros. These macros are implementation details, and thus * should not be used directly. Use their frontends instead. @@ -30,6 +34,14 @@ inline std::vector sort_names( std::vector&& names ) { return names; } +#define Complex_Authority_macro_Key(r, data, key_bubble) \ + data.keys.emplace_back(BOOST_PP_CAT(BOOST_PP_TUPLE_ELEM(2, 0, key_bubble), _public_key), \ + BOOST_PP_TUPLE_ELEM(2, 1, key_bubble)); +#define Complex_Authority_macro_Account(r, data, account_bubble) \ + data.accounts.emplace_back(types::AccountPermission{BOOST_PP_TUPLE_ELEM(3, 0, account_bubble), \ + BOOST_PP_TUPLE_ELEM(3, 1, account_bubble)}, \ + BOOST_PP_TUPLE_ELEM(3, 2, account_bubble)); + #define MKACCT_IMPL(chain, name, creator, active, owner, recovery, deposit) \ { \ eos::chain::SignedTransaction trx; \ diff --git a/tests/common/testing_macros.hpp b/tests/common/testing_macros.hpp new file mode 100644 index 00000000000..4005724f832 --- /dev/null +++ b/tests/common/testing_macros.hpp @@ -0,0 +1,311 @@ +#pragma once + +#include "macro_support.hpp" + +/// Some helpful macros to reduce boilerplate when making testcases +/// @{ + +/** + * @brief Create/Open a testing_blockchain, optionally with an ID + * + * Creates and opens a testing_blockchain with the first argument as its name, and, if present, the second argument as + * its ID. The ID should be provided without quotes. + * + * Example: + * @code{.cpp} + * // Create testing_blockchain chain1 + * Make_Blockchain(chain1) + * + * // The above creates the following objects: + * chainbase::database chain1_db; + * block_log chain1_log; + * fork_database chain1_fdb; + * native_contract::native_contract_chain_initializer chain1_initializer; + * testing_blockchain chain1; + * @endcode + */ +#define Make_Blockchain(...) BOOST_PP_OVERLOAD(MKCHAIN, __VA_ARGS__)(__VA_ARGS__) +/** + * @brief Similar to @ref Make_Blockchain, but works with several chains at once + * + * Creates and opens several testing_blockchains + * + * Example: + * @code{.cpp} + * // Create testing_blockchains chain1 and chain2, with chain2 having ID "id2" + * Make_Blockchains((chain1)(chain2, id2)) + * @endcode + */ +#define Make_Blockchains(...) BOOST_PP_SEQ_FOR_EACH(MKCHAINS_MACRO, _, __VA_ARGS__) + +/** + * @brief Make_Network is a shorthand way to create a testing_network and connect some testing_blockchains to it. + * + * Example usage: + * @code{.cpp} + * // Create and open testing_blockchains named alice, bob, and charlie + * MKDBS((alice)(bob)(charlie)) + * // Create a testing_network named net and connect alice and bob to it + * Make_Network(net, (alice)(bob)) + * + * // Connect charlie to net, then disconnect alice + * net.connect_blockchain(charlie); + * net.disconnect_blockchain(alice); + * + * // Create a testing_network named net2 with no blockchains connected + * Make_Network(net2) + * @endcode + */ +#define Make_Network(...) BOOST_PP_OVERLOAD(MKNET, __VA_ARGS__)(__VA_ARGS__) + +/** + * @brief Make_Key is a shorthand way to create a keypair + * + * @code{.cpp} + * // This line: + * Make_Key(a_key) + * // ...defines these objects: + * private_key_type a_key_private_key; + * PublicKey a_key_public_key; + * // The private key is generated off of the sha256 hash of "a_key_private_key", so it should be unique from all + * // other keys created with Make_Key in the same scope. + * @endcode + */ +#ifdef HAVE_DATABASE_FIXTURE +#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(); \ + BOOST_TEST_CHECKPOINT("Created key " #name "_public_key"); +#else +#define Make_Key(name) auto name ## _private_key = private_key_type::regenerate(fc::digest(#name "_private_key")); \ + PublicKey name ## _public_key = name ## _private_key.get_public_key(); \ + BOOST_TEST_CHECKPOINT("Created key " #name "_public_key"); +#endif + +/** + * @brief Key_Authority is a shorthand way to create an inline Authority based on a key + * + * Invoke Key_Authority passing the name of a public key in the current scope, and Key_Authority will resolve inline to + * an authority which can be satisfied by a signature generated by the corresponding private key. + */ +#define Key_Authority(pubkey) (Authority{1, {{pubkey, 1}}, {}}) +/** + * @brief Account_Authority is a shorthand way to create an inline Authority based on an account + * + * Invoke Account_Authority passing the name of an account, and Account_Authority will resolve inline to an authority + * which can be satisfied by the provided account's active authority. + */ +#define Account_Authority(account) (Authority{1, {}, {{{#account, "active"}, 1}}}) +/** + * @brief Complex_Authority is a shorthand way to create an arbitrary inline @ref Authority + * + * Invoke Complex_Authority passing the weight threshold necessary to satisfy the authority, a bubble list of keys and + * weights, and a bubble list of accounts and weights. + * + * Key bubbles are structured as ((key_name, key_weight)) + * Account bubbles are structured as (("account_name", "account_authority", weight)) + * + * Example: + * @code{.cpp} + * // Create an authority which can be satisfied with a master key, or with any three of: + * // - key_1 + * // - key_2 + * // - key_3 + * // - Account alice's "test_multisig" authority + * // - Account bob's "test_multisig" authority + * Make_Key(master_key) + * Make_Key(key_1) + * Make_Key(key_2) + * Make_Key(key_3) + * auto auth = Complex_Authority(5, ((master_key, 5))((key_1, 2))((key_2, 2))((key_3, 2)), + * (("alice", "test_multisig", 2))(("bob", "test_multisig", 2)); + * @endcode + */ +#define Complex_Authority(THRESHOLD, KEY_BUBBLES, ACCOUNT_BUBBLES) \ + [&]{ \ + Authority x; \ + x.threshold = THRESHOLD; \ + BOOST_PP_SEQ_FOR_EACH(Complex_Authority_macro_Key, x, KEY_BUBBLES) \ + BOOST_PP_SEQ_FOR_EACH(Complex_Authority_macro_Account, x, ACCOUNT_BUBBLES) \ + return x; \ + }() + +/** + * @brief Make_Account is a shorthand way to create an account + * + * Use Make_Account to create an account, including keys. The changes will be applied via a transaction applied to the + * provided blockchain object. The changes will not be incorporated into a block; they will be left in the pending + * state. + * + * Unless overridden, new accounts are created with a balance of Asset(100) + * + * Example: + * @code{.cpp} + * Make_Account(chain, joe) + * // ... creates these objects: + * private_key_type joe_private_key; + * PublicKey joe_public_key; + * // ...and also registers the account joe with owner and active authorities satisfied by these keys, created by + * // init0, with init0's active authority as joe's recovery authority, and initially endowed with Asset(100) + * @endcode + * + * You may specify a third argument for the creating account: + * @code{.cpp} + * // Same as MKACCT(chain, joe) except that sam will create joe's account instead of init0 + * Make_Account(chain, joe, sam) + * @endcode + * + * You may specify a fourth argument for the amount to transfer in account creation: + * @code{.cpp} + * // Same as MKACCT(chain, joe, sam) except that sam will send joe ASSET(100) during creation + * Make_Account(chain, joe, sam, Asset(100)) + * @endcode + * + * You may specify a fifth argument, which will be used as the owner authority (must be an Authority, NOT a key!). + * + * You may specify a sixth argument, which will be used as the active authority. If six or more arguments are provided, + * the default keypair will NOT be created or put into scope. + * + * You may specify a seventh argument, which will be used as the recovery authority. + */ +#define Make_Account(...) BOOST_PP_OVERLOAD(MKACCT, __VA_ARGS__)(__VA_ARGS__) + +/** + * @brief Shorthand way to transfer funds + * + * Use Transfer_Asset to send funds from one account to another: + * @code{.cpp} + * // Send 10 EOS from alice to bob + * Transfer_Asset(chain, alice, bob, Asset(10)); + * + * // Send 10 EOS from alice to bob with memo "Thanks for all the fish!" + * Transfer_Asset(chain, alice, bob, Asset(10), "Thanks for all the fish!"); + * @endcode + * + * The changes will be applied via a transaction applied to the provided blockchain object. The changes will not be + * incorporated into a block; they will be left in the pending state. + */ +#define Transfer_Asset(...) BOOST_PP_OVERLOAD(XFER, __VA_ARGS__)(__VA_ARGS__) + +/** + * @brief Shorthand way to convert liquid funds to staked funds + * + * Use Stake_Asset to stake liquid funds: + * @code{.cpp} + * // Convert 10 of bob's EOS from liquid to staked + * Stake_Asset(chain, bob, Asset(10).amount); + * + * // Stake and transfer 10 EOS from alice to bob (alice pays liquid EOS and bob receives stake) + * Stake_Asset(chain, alice, bob, Asset(10).amount); + * @endcode + */ +#define Stake_Asset(...) BOOST_PP_OVERLOAD(STAKE, __VA_ARGS__)(__VA_ARGS__) + +/** + * @brief Shorthand way to begin conversion from staked funds to liquid funds + * + * Use Unstake_Asset to begin unstaking funds: + * @code{.cpp} + * // Begin unstaking 10 of bob's EOS + * Unstake_Asset(chain, bob, Asset(10).amount); + * @endcode + * + * This can also be used to cancel an unstaking in progress, by passing Asset(0) as the amount. + */ +#define Begin_Unstake_Asset(...) BOOST_PP_OVERLOAD(BEGIN_UNSTAKE, __VA_ARGS__)(__VA_ARGS__) + +/** + * @brief Shorthand way to claim unstaked EOS as liquid + * + * Use Finish_Unstake_Asset to liquidate unstaked funds: + * @code{.cpp} + * // Reclaim as liquid 10 of bob's unstaked EOS + * Unstake_Asset(chain, bob, Asset(10).amount); + * @endcode + */ +#define Finish_Unstake_Asset(...) BOOST_PP_OVERLOAD(FINISH_UNSTAKE, __VA_ARGS__)(__VA_ARGS__) + + +/** + * @brief Shorthand way to set voting proxy + * + * Use Set_Proxy to set what account a stakeholding account proxies its voting power to + * @code{.cpp} + * // Proxy sam's votes to bob + * Set_Proxy(chain, sam, bob); + * + * // Unproxy sam's votes + * Set_Proxy(chain, sam, sam); + * @endcode + */ +#define Set_Proxy(chain, stakeholder, proxy) \ +{ \ + eos::chain::SignedTransaction trx; \ + if (std::string(#stakeholder) != std::string(#proxy)) \ + trx.emplaceMessage(config::EosContractName, \ + vector{ {#stakeholder,"active"} }, "setproxy", types::setproxy{#stakeholder, #proxy}); \ + else \ + trx.emplaceMessage(config::EosContractName, \ + vector{ {#stakeholder,"active"} }, "setproxy", types::setproxy{#stakeholder, #proxy}); \ + trx.expiration = chain.head_block_time() + 100; \ + trx.set_reference_block(chain.head_block_id()); \ + chain.push_transaction(trx); \ +} + +/** + * @brief Shorthand way to create a block producer + * + * Use Make_Producer to create a block producer: + * @code{.cpp} + * // Create a block producer belonging to joe using signing_key as the block signing key and config as the producer's + * // vote for a @ref BlockchainConfiguration: + * Make_Producer(chain, joe, signing_key, config); + * + * // Create a block producer belonging to joe using signing_key as the block signing key: + * Make_Producer(chain, joe, signing_key); + * + * // Create a block producer belonging to joe, using a new key as the block signing key: + * Make_Producer(chain, joe); + * // ... creates the objects: + * private_key_type joe_producer_private_key; + * PublicKey joe_producer_public_key; + * @endcode + */ +#define Make_Producer(...) BOOST_PP_OVERLOAD(MKPDCR, __VA_ARGS__)(__VA_ARGS__) + +/** + * @brief Shorthand way to set approval of a block producer + * + * Use Approve_Producer to change an account's approval of a block producer: + * @code{.cpp} + * // Set joe's approval for pete's block producer to Approve + * Approve_Producer(chain, joe, pete, true); + * // Set joe's approval for pete's block producer to Disapprove + * Approve_Producer(chain, joe, pete, false); + * @endcode + */ +#define Approve_Producer(...) BOOST_PP_OVERLOAD(APPDCR, __VA_ARGS__)(__VA_ARGS__) + +/** + * @brief Shorthand way to update a block producer + * + * @note Unlike with the Make_* macros, the Update_* macros take an expression as the owner/name field, so be sure to + * wrap names like this in quotes. You may also pass a normal C++ expression to be evaulated here instead. The reason + * for this discrepancy is that the Make_* macros add identifiers to the current scope based on the owner/name field; + * moreover, which can't be done with C++ expressions; however, the Update_* macros do not add anything to the scope, + * and it's more likely that these will be used in a loop or other context where it is inconvenient to know the + * owner/name at compile time. + * + * Use Update_Producer to update a block producer: + * @code{.cpp} + * // Update a block producer belonging to joe using signing_key as the new block signing key, and config as the + * // producer's new vote for a @ref BlockchainConfiguration: + * Update_Producer(chain, "joe", signing_key, config) + * + * // Update a block producer belonging to joe using signing_key as the new block signing key: + * Update_Producer(chain, "joe", signing_key) + * @endcode + */ +#define Update_Producer(...) BOOST_PP_OVERLOAD(UPPDCR, __VA_ARGS__)(__VA_ARGS__) + +/// @} diff --git a/tests/tests/misc_tests.cpp b/tests/tests/misc_tests.cpp index 5d9ec70a0c9..e197926d399 100644 --- a/tests/tests/misc_tests.cpp +++ b/tests/tests/misc_tests.cpp @@ -9,6 +9,8 @@ #include +#include "../common/testing_macros.hpp" + namespace eos { using namespace chain; @@ -79,45 +81,46 @@ BOOST_AUTO_TEST_CASE(deterministic_distributions) BOOST_AUTO_TEST_CASE(authority_checker) { try { - #define KEY(x) auto x = fc::ecc::private_key::regenerate(fc::sha256::hash(#x)).get_public_key() - - KEY(a); - KEY(b); - KEY(c); + Make_Key(a); + auto& a = a_public_key; + Make_Key(b); + auto& b = b_public_key; + Make_Key(c); + auto& c = c_public_key; auto GetNullAuthority = [](auto){return Authority();}; - Authority A(2, {{a, 1}, {b, 1}}, {}); + auto A = Complex_Authority(2, ((a,1))((b,1)),); BOOST_CHECK(MakeAuthorityChecker(GetNullAuthority, {a, b}).satisfied(A)); BOOST_CHECK(MakeAuthorityChecker(GetNullAuthority, {a, b, c}).satisfied(A)); BOOST_CHECK(!MakeAuthorityChecker(GetNullAuthority, {a, c}).satisfied(A)); BOOST_CHECK(!MakeAuthorityChecker(GetNullAuthority, {b, c}).satisfied(A)); - A = Authority(3, {{a,1},{b,1},{c,1}}, {}); + A = Complex_Authority(3, ((a,1))((b,1))((c,1)),); BOOST_CHECK(MakeAuthorityChecker(GetNullAuthority, {c, b, a}).satisfied(A)); BOOST_CHECK(!MakeAuthorityChecker(GetNullAuthority, {a, b}).satisfied(A)); BOOST_CHECK(!MakeAuthorityChecker(GetNullAuthority, {a, c}).satisfied(A)); BOOST_CHECK(!MakeAuthorityChecker(GetNullAuthority, {b, c}).satisfied(A)); - A = Authority(1, {{a, 1}, {b, 1}}, {}); + A = Complex_Authority(1, ((a, 1))((b, 1)),); BOOST_CHECK(MakeAuthorityChecker(GetNullAuthority, {a}).satisfied(A)); BOOST_CHECK(MakeAuthorityChecker(GetNullAuthority, {b}).satisfied(A)); BOOST_CHECK(!MakeAuthorityChecker(GetNullAuthority, {c}).satisfied(A)); - A = Authority(1, {{a, 2}, {b, 1}}, {}); + A = Complex_Authority(1, ((a, 2))((b, 1)),); BOOST_CHECK(MakeAuthorityChecker(GetNullAuthority, {a}).satisfied(A)); BOOST_CHECK(MakeAuthorityChecker(GetNullAuthority, {b}).satisfied(A)); BOOST_CHECK(!MakeAuthorityChecker(GetNullAuthority, {c}).satisfied(A)); - auto GetCAuthority = [c](auto){return Authority(1, {{c, 1}}, {});}; + auto GetCAuthority = [c_public_key](auto){return Complex_Authority(1, ((c, 1)),);}; - A = Authority(2, {{a, 2}, {b, 1}}, {{{"hello", "world"}, 1}}); + A = Complex_Authority(2, ((a, 2))((b, 1)), (("hello", "world", 1))); BOOST_CHECK(MakeAuthorityChecker(GetCAuthority, {a}).satisfied(A)); BOOST_CHECK(!MakeAuthorityChecker(GetCAuthority, {b}).satisfied(A)); BOOST_CHECK(!MakeAuthorityChecker(GetCAuthority, {c}).satisfied(A)); BOOST_CHECK(MakeAuthorityChecker(GetCAuthority, {b,c}).satisfied(A)); - A = Authority(2, {{a, 1}, {b, 1}}, {{{"hello", "world"}, 1}}); + A = Complex_Authority(2, ((a, 1))((b, 1)), (("hello", "world", 1))); BOOST_CHECK(!MakeAuthorityChecker(GetCAuthority, {a}).satisfied(A)); BOOST_CHECK(!MakeAuthorityChecker(GetCAuthority, {b}).satisfied(A)); BOOST_CHECK(!MakeAuthorityChecker(GetCAuthority, {c}).satisfied(A)); @@ -125,7 +128,7 @@ BOOST_AUTO_TEST_CASE(authority_checker) BOOST_CHECK(MakeAuthorityChecker(GetCAuthority, {b,c}).satisfied(A)); BOOST_CHECK(MakeAuthorityChecker(GetCAuthority, {a,c}).satisfied(A)); - A = Authority(2, {{a, 1}, {b, 1}}, {{{"hello", "world"}, 2}}); + A = Complex_Authority(2, ((a, 1))((b, 1)), (("hello", "world", 2))); BOOST_CHECK(MakeAuthorityChecker(GetCAuthority, {a,b}).satisfied(A)); BOOST_CHECK(MakeAuthorityChecker(GetCAuthority, {c}).satisfied(A)); BOOST_CHECK(!MakeAuthorityChecker(GetCAuthority, {a}).satisfied(A)); From d6f89ca4c13a49f59be3d87454772f2db16724f5 Mon Sep 17 00:00:00 2001 From: Nathan Hourt Date: Thu, 3 Aug 2017 13:55:28 -0500 Subject: [PATCH 19/27] Ref #123: Test macros to set/delete auths Create and smoke test Add_Authority and Delete_Authority test helper macros --- tests/common/macro_support.hpp | 26 ++++++++++++++++++++++++++ tests/common/testing_macros.hpp | 15 +++++++++++++++ tests/tests/native_contract_tests.cpp | 12 ++++++++++++ 3 files changed, 53 insertions(+) diff --git a/tests/common/macro_support.hpp b/tests/common/macro_support.hpp index 6cc6f1978b0..9a892b637aa 100644 --- a/tests/common/macro_support.hpp +++ b/tests/common/macro_support.hpp @@ -74,6 +74,32 @@ inline std::vector sort_names( std::vector&& names ) { #define MKACCT7(chain, name, creator, deposit, owner, active, recovery) \ MKACCT_IMPL(chain, name, creator, owner, active, recovery, deposit) +#define SETAUTH5(chain, account, authname, parentname, auth) \ + { \ + eos::chain::SignedTransaction trx; \ + trx.scope = {#account}; \ + trx.emplaceMessage(config::EosContractName, \ + vector{{#account,"active"}}, \ + "updateauth", types::updateauth{#account, #authname, parentname, auth}); \ + trx.expiration = chain.head_block_time() + 100; \ + trx.set_reference_block(chain.head_block_id()); \ + chain.push_transaction(trx, chain_controller::skip_transaction_signatures); \ + BOOST_TEST_CHECKPOINT("Set " << #account << "'s " << authname << " authority."); \ + } + +#define DELAUTH3(chain, account, authname) \ + { \ + eos::chain::SignedTransaction trx; \ + trx.scope = {#account}; \ + trx.emplaceMessage(config::EosContractName, \ + vector{{#account,"active"}}, \ + "deleteauth", types::deleteauth{#account, #authname}); \ + trx.expiration = chain.head_block_time() + 100; \ + trx.set_reference_block(chain.head_block_id()); \ + chain.push_transaction(trx, chain_controller::skip_transaction_signatures); \ + BOOST_TEST_CHECKPOINT("Deleted " << #account << "'s " << authname << " authority."); \ + } + #define XFER5(chain, sender, recipient, Amount, memo) \ { \ eos::chain::SignedTransaction trx; \ diff --git a/tests/common/testing_macros.hpp b/tests/common/testing_macros.hpp index 4005724f832..918329af7c1 100644 --- a/tests/common/testing_macros.hpp +++ b/tests/common/testing_macros.hpp @@ -170,6 +170,21 @@ */ #define Make_Account(...) BOOST_PP_OVERLOAD(MKACCT, __VA_ARGS__)(__VA_ARGS__) +/** + * @brief Shorthand way to create or update a named authority on an account + * + * @code{.cpp} + * // Add a new authority named "money" to account "alice" as a child of her active authority + * Authority newAuth = //... + * Set_Authority(chain, alice, "money", "active", newAuth); + * @endcode + */ +#define Set_Authority(...) BOOST_PP_OVERLOAD(SETAUTH, __VA_ARGS__)(__VA_ARGS__) +/** + * @brief Shorthand way to delete a named authority from an account + */ +#define Delete_Authority(...) BOOST_PP_OVERLOAD(DELAUTH, __VA_ARGS__)(__VA_ARGS__) + /** * @brief Shorthand way to transfer funds * diff --git a/tests/tests/native_contract_tests.cpp b/tests/tests/native_contract_tests.cpp index 963eccc3503..b49d590d8f7 100644 --- a/tests/tests/native_contract_tests.cpp +++ b/tests/tests/native_contract_tests.cpp @@ -384,4 +384,16 @@ BOOST_FIXTURE_TEST_CASE(producer_proxy_voting, testing_fixture) { } FC_LOG_AND_RETHROW() } +BOOST_FIXTURE_TEST_CASE(auth_tests, testing_fixture) { + try { + Make_Blockchain(chain) + Make_Account(chain, alice); + chain.produce_blocks(); + + Make_Key(k1); + Set_Authority(chain, alice, "spending", "active", Key_Authority(k1_public_key)); + Set_Authority(chain, alice, "spending", "owner", Key_Authority(k1_public_key)); + Delete_Authority(chain, alice, "spending"); +} FC_LOG_AND_RETHROW() } + BOOST_AUTO_TEST_SUITE_END() From d4989d4996db6ad194442351acad5bc7f1614da3 Mon Sep 17 00:00:00 2001 From: Nathan Hourt Date: Thu, 3 Aug 2017 14:18:35 -0500 Subject: [PATCH 20/27] Ref #123: Write tests, fix bugs --- libraries/native_contract/eos_contract.cpp | 2 ++ tests/common/macro_support.hpp | 4 ++-- tests/common/testing_macros.hpp | 9 +++++++-- tests/tests/native_contract_tests.cpp | 2 ++ 4 files changed, 13 insertions(+), 4 deletions(-) diff --git a/libraries/native_contract/eos_contract.cpp b/libraries/native_contract/eos_contract.cpp index c5fa51f33bc..7366c4bfeec 100644 --- a/libraries/native_contract/eos_contract.cpp +++ b/libraries/native_contract/eos_contract.cpp @@ -364,6 +364,8 @@ void apply_eos_setproxy(apply_context& context) { void apply_eos_updateauth(apply_context& context) { auto update = context.msg.as(); EOS_ASSERT(!update.permission.empty(), message_validate_exception, "Cannot create authority with empty name"); + EOS_ASSERT(update.permission != update.parent, message_validate_exception, + "Cannot set an authority as its own parent"); EOS_ASSERT(validate(update.authority), message_validate_exception, "Invalid authority: ${auth}", ("auth", update.authority)); if (update.permission == "active") diff --git a/tests/common/macro_support.hpp b/tests/common/macro_support.hpp index 9a892b637aa..2d3be220f7b 100644 --- a/tests/common/macro_support.hpp +++ b/tests/common/macro_support.hpp @@ -80,7 +80,7 @@ inline std::vector sort_names( std::vector&& names ) { trx.scope = {#account}; \ trx.emplaceMessage(config::EosContractName, \ vector{{#account,"active"}}, \ - "updateauth", types::updateauth{#account, #authname, parentname, auth}); \ + "updateauth", types::updateauth{#account, authname, parentname, auth}); \ trx.expiration = chain.head_block_time() + 100; \ trx.set_reference_block(chain.head_block_id()); \ chain.push_transaction(trx, chain_controller::skip_transaction_signatures); \ @@ -93,7 +93,7 @@ inline std::vector sort_names( std::vector&& names ) { trx.scope = {#account}; \ trx.emplaceMessage(config::EosContractName, \ vector{{#account,"active"}}, \ - "deleteauth", types::deleteauth{#account, #authname}); \ + "deleteauth", types::deleteauth{#account, authname}); \ trx.expiration = chain.head_block_time() + 100; \ trx.set_reference_block(chain.head_block_id()); \ chain.push_transaction(trx, chain_controller::skip_transaction_signatures); \ diff --git a/tests/common/testing_macros.hpp b/tests/common/testing_macros.hpp index 918329af7c1..3aaab52a7a6 100644 --- a/tests/common/testing_macros.hpp +++ b/tests/common/testing_macros.hpp @@ -171,7 +171,7 @@ #define Make_Account(...) BOOST_PP_OVERLOAD(MKACCT, __VA_ARGS__)(__VA_ARGS__) /** - * @brief Shorthand way to create or update a named authority on an account + * @brief Shorthand way to create or update named authority on an account * * @code{.cpp} * // Add a new authority named "money" to account "alice" as a child of her active authority @@ -181,7 +181,12 @@ */ #define Set_Authority(...) BOOST_PP_OVERLOAD(SETAUTH, __VA_ARGS__)(__VA_ARGS__) /** - * @brief Shorthand way to delete a named authority from an account + * @brief Shorthand way to delete named authority from an account + * + * @code{.cpp} + * // Delete authority named "money" from account "alice" + * Delete_Authority(chain, alice, "money"); + * @endcode */ #define Delete_Authority(...) BOOST_PP_OVERLOAD(DELAUTH, __VA_ARGS__)(__VA_ARGS__) diff --git a/tests/tests/native_contract_tests.cpp b/tests/tests/native_contract_tests.cpp index b49d590d8f7..7b5ce6c166c 100644 --- a/tests/tests/native_contract_tests.cpp +++ b/tests/tests/native_contract_tests.cpp @@ -392,6 +392,8 @@ BOOST_FIXTURE_TEST_CASE(auth_tests, testing_fixture) { Make_Key(k1); Set_Authority(chain, alice, "spending", "active", Key_Authority(k1_public_key)); + BOOST_CHECK_THROW(Set_Authority(chain, alice, "spending", "spending", Key_Authority(k1_public_key)), + message_validate_exception); Set_Authority(chain, alice, "spending", "owner", Key_Authority(k1_public_key)); Delete_Authority(chain, alice, "spending"); } FC_LOG_AND_RETHROW() } From 6218ccbdba97ba47f6a47cc68d9921b446a2980e Mon Sep 17 00:00:00 2001 From: Nathan Hourt Date: Thu, 3 Aug 2017 17:40:59 -0500 Subject: [PATCH 21/27] Ref #123: Testing, fixing, 1 behavior change The behavior change is that I am forbidding changing a permission_object's parent until we come up with a safe way to support it. The issue is that it's possible to create loops by creating an object A with an existing parent B, then setting B's parent to A. The obvious solution is to ensure with every parent change that there is a path back to the owner authority by following parents, but to do this we need a tree depth limit. I haven't explored the implications of that, so I'm just disabling parent changes for the time being. The user can simply delete the old subtree and create a new one if he wants to move a subtree from one parent to another. --- libraries/chain/chain_controller.cpp | 2 +- libraries/chain/include/eos/chain/exceptions.hpp | 1 + libraries/native_contract/eos_contract.cpp | 6 ++++-- tests/tests/native_contract_tests.cpp | 16 +++++++++++++++- 4 files changed, 21 insertions(+), 4 deletions(-) diff --git a/libraries/chain/chain_controller.cpp b/libraries/chain/chain_controller.cpp index 23bfa1f5a31..0c915275334 100644 --- a/libraries/chain/chain_controller.cpp +++ b/libraries/chain/chain_controller.cpp @@ -555,7 +555,7 @@ void chain_controller::validate_uniqueness( const SignedTransaction& trx )const if( !should_check_for_duplicate_transactions() ) return; auto transaction = _db.find(trx.id()); - EOS_ASSERT(transaction == nullptr, transaction_exception, "Transaction is not unique"); + EOS_ASSERT(transaction == nullptr, tx_duplicate, "Transaction is not unique"); } void chain_controller::validate_tapos(const SignedTransaction& trx)const { diff --git a/libraries/chain/include/eos/chain/exceptions.hpp b/libraries/chain/include/eos/chain/exceptions.hpp index d9acb6f49f1..095a05298fa 100644 --- a/libraries/chain/include/eos/chain/exceptions.hpp +++ b/libraries/chain/include/eos/chain/exceptions.hpp @@ -52,6 +52,7 @@ namespace eos { namespace chain { FC_DECLARE_DERIVED_EXCEPTION( tx_missing_scope, eos::chain::transaction_exception, 3030008, "missing required scope" ) FC_DECLARE_DERIVED_EXCEPTION( tx_missing_recipient, eos::chain::transaction_exception, 3030009, "missing required recipient" ) FC_DECLARE_DERIVED_EXCEPTION( checktime_exceeded, eos::chain::transaction_exception, 3030010, "allotted processing time was exceeded" ) + FC_DECLARE_DERIVED_EXCEPTION( tx_duplicate, eos::chain::transaction_exception, 3030011, "duplicate transaction" ) FC_DECLARE_DERIVED_EXCEPTION( invalid_pts_address, eos::chain::utility_exception, 3060001, "invalid pts address" ) FC_DECLARE_DERIVED_EXCEPTION( insufficient_feeds, eos::chain::chain_exception, 37006, "insufficient feeds" ) diff --git a/libraries/native_contract/eos_contract.cpp b/libraries/native_contract/eos_contract.cpp index 7366c4bfeec..73f5a22e0ed 100644 --- a/libraries/native_contract/eos_contract.cpp +++ b/libraries/native_contract/eos_contract.cpp @@ -377,15 +377,17 @@ void apply_eos_updateauth(apply_context& context) { context.require_authorization(update.account); db.get(update.account); - auto& parent = db.get(boost::make_tuple(update.account, update.parent)); for (auto accountPermission : update.authority.accounts) { db.get(accountPermission.permission.account); db.get(boost::make_tuple(accountPermission.permission.account, accountPermission.permission.permission)); } - const auto& permission = db.find(boost::make_tuple(update.account, update.permission)); + auto permission = db.find(boost::make_tuple(update.account, update.permission)); + auto& parent = db.get(boost::make_tuple(update.account, update.parent)); if (permission) { + EOS_ASSERT(parent.id == permission->parent, message_precondition_exception, + "Changing parent authority is not currently supported"); db.modify(*permission, [&update, parent = parent.id](permission_object& po) { po.auth = update.authority; po.parent = parent; diff --git a/tests/tests/native_contract_tests.cpp b/tests/tests/native_contract_tests.cpp index 7b5ce6c166c..69d35ea492b 100644 --- a/tests/tests/native_contract_tests.cpp +++ b/tests/tests/native_contract_tests.cpp @@ -390,12 +390,26 @@ BOOST_FIXTURE_TEST_CASE(auth_tests, testing_fixture) { Make_Account(chain, alice); chain.produce_blocks(); + BOOST_CHECK_THROW(Delete_Authority(chain, alice, "active"), message_validate_exception); + BOOST_CHECK_THROW(Delete_Authority(chain, alice, "owner"), message_validate_exception); + Make_Key(k1); Set_Authority(chain, alice, "spending", "active", Key_Authority(k1_public_key)); BOOST_CHECK_THROW(Set_Authority(chain, alice, "spending", "spending", Key_Authority(k1_public_key)), message_validate_exception); - Set_Authority(chain, alice, "spending", "owner", Key_Authority(k1_public_key)); + BOOST_CHECK_THROW(Set_Authority(chain, alice, "spending", "owner", Key_Authority(k1_public_key)), + message_precondition_exception); + Delete_Authority(chain, alice, "spending"); + + chain.produce_blocks(); + + Set_Authority(chain, alice, "trading", "active", Key_Authority(k1_public_key)); + Set_Authority(chain, alice, "spending", "trading", Key_Authority(k1_public_key)); + BOOST_CHECK_THROW(Delete_Authority(chain, alice, "trading"), message_precondition_exception); + BOOST_CHECK_THROW(Set_Authority(chain, alice, "trading", "spending", Key_Authority(k1_public_key)), + message_precondition_exception); Delete_Authority(chain, alice, "spending"); + Delete_Authority(chain, alice, "trading"); } FC_LOG_AND_RETHROW() } BOOST_AUTO_TEST_SUITE_END() From a210f9cbc1eb222d1ae975789195e9549ea90994 Mon Sep 17 00:00:00 2001 From: Nathan Hourt Date: Tue, 8 Aug 2017 10:28:54 -0500 Subject: [PATCH 22/27] Use C++14, not 17 Otherwise the build fails on my system --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index dcc81205055..529ad3d2906 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,7 +8,7 @@ INCLUDE( VersionMacros ) INCLUDE( SetupTargetMacros ) set( BLOCKCHAIN_NAME "Eos" ) -set( CMAKE_CXX_STANDARD 17 ) +set( CMAKE_CXX_STANDARD 14 ) set( CLI_CLIENT_EXECUTABLE_NAME eos_client ) set( GUI_CLIENT_EXECUTABLE_NAME eos ) From 4adbf0b81d6252d4118cfb373879caa1ba10dc71 Mon Sep 17 00:00:00 2001 From: Nathan Hourt Date: Tue, 8 Aug 2017 10:32:37 -0500 Subject: [PATCH 23/27] Remove conflict marker that snuck past --- libraries/chain/include/eos/chain/message_handling_contexts.hpp | 1 - 1 file changed, 1 deletion(-) diff --git a/libraries/chain/include/eos/chain/message_handling_contexts.hpp b/libraries/chain/include/eos/chain/message_handling_contexts.hpp index 4b53216a93f..dd38d31208f 100644 --- a/libraries/chain/include/eos/chain/message_handling_contexts.hpp +++ b/libraries/chain/include/eos/chain/message_handling_contexts.hpp @@ -28,7 +28,6 @@ class apply_context { int32_t load_i64( Name scope, Name code, Name table, Name Key, char* data, uint32_t maxlen ); -<<<<<<< HEAD int32_t front_primary_i128i128( Name scope, Name code, Name table, uint128_t* primary, uint128_t* secondary, char* data, uint32_t maxlen ); int32_t back_primary_i128i128( Name scope, Name code, Name table, From 0ec8c9d7831f0a085abf90a32920f5f8dc7985c8 Mon Sep 17 00:00:00 2001 From: Nathan Hourt Date: Tue, 8 Aug 2017 14:47:55 -0500 Subject: [PATCH 24/27] Mark submodules 'ignore = dirty' --- .gitmodules | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitmodules b/.gitmodules index 58295bf9705..4ae25dc3baa 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,9 +1,11 @@ [submodule "libraries/chainbase"] path = libraries/chainbase url = https://github.com/eosio/chainbase + ignore = dirty [submodule "libraries/appbase"] path = libraries/appbase url = https://github.com/eosio/appbase + ignore = dirty [submodule "libraries/binaryen"] path = libraries/binaryen url = https://github.com/WebAssembly/binaryen.git From 806508a2092ed49e0114f6ab12010a0fa592a6e4 Mon Sep 17 00:00:00 2001 From: Nathan Hourt Date: Tue, 8 Aug 2017 15:43:48 -0500 Subject: [PATCH 25/27] Ref #123: Testing, fixing --- .../chain/include/eos/chain/authority.hpp | 5 +- tests/tests/native_contract_tests.cpp | 53 ++++++++++++++++++- 2 files changed, 55 insertions(+), 3 deletions(-) diff --git a/libraries/chain/include/eos/chain/authority.hpp b/libraries/chain/include/eos/chain/authority.hpp index df8165f78ea..3b68ab211a6 100644 --- a/libraries/chain/include/eos/chain/authority.hpp +++ b/libraries/chain/include/eos/chain/authority.hpp @@ -43,7 +43,7 @@ struct shared_authority { template class AuthorityChecker { F PermissionToAuthority; - const flat_set& signingKeys; + flat_set signingKeys; public: AuthorityChecker(F PermissionToAuthority, const flat_set& signingKeys) @@ -55,12 +55,13 @@ class AuthorityChecker { template bool satisfied(const AuthorityType& authority) const { UInt32 weight = 0; - for (const auto& kpw : authority.keys) + 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)) { diff --git a/tests/tests/native_contract_tests.cpp b/tests/tests/native_contract_tests.cpp index 69d35ea492b..d0c0230ce73 100644 --- a/tests/tests/native_contract_tests.cpp +++ b/tests/tests/native_contract_tests.cpp @@ -394,22 +394,73 @@ BOOST_FIXTURE_TEST_CASE(auth_tests, testing_fixture) { BOOST_CHECK_THROW(Delete_Authority(chain, alice, "owner"), message_validate_exception); Make_Key(k1); + Make_Key(k2); Set_Authority(chain, alice, "spending", "active", Key_Authority(k1_public_key)); + + { + auto obj = chain_db.find(boost::make_tuple("alice", "spending")); + BOOST_CHECK_NE(obj, nullptr); + BOOST_CHECK_EQUAL(obj->owner, "alice"); + BOOST_CHECK_EQUAL(obj->name, "spending"); + BOOST_CHECK_EQUAL(chain_db.get(obj->parent).owner, "alice"); + BOOST_CHECK_EQUAL(chain_db.get(obj->parent).name, "active"); + } + BOOST_CHECK_THROW(Set_Authority(chain, alice, "spending", "spending", Key_Authority(k1_public_key)), message_validate_exception); BOOST_CHECK_THROW(Set_Authority(chain, alice, "spending", "owner", Key_Authority(k1_public_key)), message_precondition_exception); Delete_Authority(chain, alice, "spending"); + { + auto obj = chain_db.find(boost::make_tuple("alice", "spending")); + BOOST_CHECK_EQUAL(obj, nullptr); + } + chain.produce_blocks(); Set_Authority(chain, alice, "trading", "active", Key_Authority(k1_public_key)); - Set_Authority(chain, alice, "spending", "trading", Key_Authority(k1_public_key)); + Set_Authority(chain, alice, "spending", "trading", Key_Authority(k2_public_key)); + + { + auto trading = chain_db.find(boost::make_tuple("alice", "trading")); + auto spending = chain_db.find(boost::make_tuple("alice", "spending")); + BOOST_CHECK_NE(trading, nullptr); + BOOST_CHECK_NE(spending, nullptr); + BOOST_CHECK_EQUAL(trading->owner, "alice"); + BOOST_CHECK_EQUAL(spending->owner, "alice"); + BOOST_CHECK_EQUAL(trading->name, "trading"); + BOOST_CHECK_EQUAL(spending->name, "spending"); + BOOST_CHECK_EQUAL(spending->parent, trading->id); + BOOST_CHECK_EQUAL(chain_db.get(trading->parent).owner, "alice"); + BOOST_CHECK_EQUAL(chain_db.get(trading->parent).name, "active"); + + // Abort if this gets run; it shouldn't get run in this test + auto PermissionToAuthority = [](auto)->Authority{abort();}; + + auto tradingChecker = MakeAuthorityChecker(PermissionToAuthority, {k1_public_key}); + auto spendingChecker = MakeAuthorityChecker(PermissionToAuthority, {k2_public_key}); + + BOOST_CHECK(tradingChecker.satisfied(trading->auth)); + BOOST_CHECK(spendingChecker.satisfied(spending->auth)); + BOOST_CHECK(!spendingChecker.satisfied(trading->auth)); + BOOST_CHECK(!tradingChecker.satisfied(spending->auth)); + } + BOOST_CHECK_THROW(Delete_Authority(chain, alice, "trading"), message_precondition_exception); BOOST_CHECK_THROW(Set_Authority(chain, alice, "trading", "spending", Key_Authority(k1_public_key)), message_precondition_exception); + BOOST_CHECK_THROW(Set_Authority(chain, alice, "spending", "active", Key_Authority(k1_public_key)), + message_precondition_exception); Delete_Authority(chain, alice, "spending"); Delete_Authority(chain, alice, "trading"); + + { + auto trading = chain_db.find(boost::make_tuple("alice", "trading")); + auto spending = chain_db.find(boost::make_tuple("alice", "spending")); + BOOST_CHECK_EQUAL(trading, nullptr); + BOOST_CHECK_EQUAL(spending, nullptr); + } } FC_LOG_AND_RETHROW() } BOOST_AUTO_TEST_SUITE_END() From 35c4740fd8172db90031aaa7d339b669fa7b6d89 Mon Sep 17 00:00:00 2001 From: Nathan Hourt Date: Tue, 8 Aug 2017 16:58:07 -0500 Subject: [PATCH 26/27] Ref #123: Add test macros, testing, and fixing Add macros for Link_Authority and Unlink_Authority, write some initial tests of these operations, and fix a bug (unlinkauth didn't require any authority) --- libraries/native_contract/eos_contract.cpp | 3 +++ tests/common/macro_support.hpp | 29 ++++++++++++++++++++++ tests/common/testing_macros.hpp | 22 ++++++++++++++++ tests/tests/native_contract_tests.cpp | 29 ++++++++++++++++++++++ 4 files changed, 83 insertions(+) diff --git a/libraries/native_contract/eos_contract.cpp b/libraries/native_contract/eos_contract.cpp index 73f5a22e0ed..bd551a0d813 100644 --- a/libraries/native_contract/eos_contract.cpp +++ b/libraries/native_contract/eos_contract.cpp @@ -461,6 +461,9 @@ void apply_eos_linkauth(apply_context& context) { void apply_eos_unlinkauth(apply_context& context) { auto& db = context.mutable_db; auto unlink = context.msg.as(); + + context.require_authorization(unlink.account); + auto linkKey = boost::make_tuple(unlink.account, unlink.code, unlink.type); auto link = db.find(linkKey); EOS_ASSERT(link != nullptr, message_precondition_exception, "Attempting to unlink authority, but no link found"); diff --git a/tests/common/macro_support.hpp b/tests/common/macro_support.hpp index 2d3be220f7b..ee9df2096c7 100644 --- a/tests/common/macro_support.hpp +++ b/tests/common/macro_support.hpp @@ -100,6 +100,35 @@ inline std::vector sort_names( std::vector&& names ) { BOOST_TEST_CHECKPOINT("Deleted " << #account << "'s " << authname << " authority."); \ } +#define LINKAUTH5(chain, account, authname, codeacct, messagetype) \ + { \ + eos::chain::SignedTransaction trx; \ + trx.scope = {#account}; \ + trx.emplaceMessage(config::EosContractName, \ + vector{{#account,"active"}}, \ + "linkauth", types::linkauth{#account, #codeacct, messagetype, authname}); \ + trx.expiration = chain.head_block_time() + 100; \ + trx.set_reference_block(chain.head_block_id()); \ + chain.push_transaction(trx, chain_controller::skip_transaction_signatures); \ + BOOST_TEST_CHECKPOINT("Link " << #codeacct << "::" << messagetype << " to " << #account \ + << "'s " << authname << " authority."); \ + } +#define LINKAUTH4(chain, account, authname, codeacct) LINKAUTH5(chain, account, authname, codeacct, "") + +#define UNLINKAUTH4(chain, account, codeacct, messagetype) \ + { \ + eos::chain::SignedTransaction trx; \ + trx.scope = {#account}; \ + trx.emplaceMessage(config::EosContractName, \ + vector{{#account,"active"}}, \ + "unlinkauth", types::unlinkauth{#account, #codeacct, messagetype}); \ + trx.expiration = chain.head_block_time() + 100; \ + trx.set_reference_block(chain.head_block_id()); \ + chain.push_transaction(trx, chain_controller::skip_transaction_signatures); \ + BOOST_TEST_CHECKPOINT("Unlink " << #codeacct << "::" << messagetype << " from " << #account); \ + } +#define LINKAUTH3(chain, account, codeacct) LINKAUTH5(chain, account, codeacct, "") + #define XFER5(chain, sender, recipient, Amount, memo) \ { \ eos::chain::SignedTransaction trx; \ diff --git a/tests/common/testing_macros.hpp b/tests/common/testing_macros.hpp index 3aaab52a7a6..2c1cd368a0b 100644 --- a/tests/common/testing_macros.hpp +++ b/tests/common/testing_macros.hpp @@ -189,6 +189,28 @@ * @endcode */ #define Delete_Authority(...) BOOST_PP_OVERLOAD(DELAUTH, __VA_ARGS__)(__VA_ARGS__) +/** + * @brief Shorthand way to link named authority with a contract/message type + * + * @code{.cpp} + * // Link alice's "money" authority with eos::transfer + * Link_Authority(chain, alice, "money", eos, "transfer"); + * // Set alice's "native" authority as default for eos contract + * Link_Authority(chain, alice, "money", eos); + * @endcode + */ +#define Link_Authority(...) BOOST_PP_OVERLOAD(LINKAUTH, __VA_ARGS__)(__VA_ARGS__) +/** + * @brief Shorthand way to unlink named authority from a contract/message type + * + * @code{.cpp} + * // Unlink alice's authority for eos::transfer + * Unlink_Authority(chain, alice, eos, "transfer"); + * // Unset alice's default authority for eos contract + * Unlink_Authority(chain, alice, eos); + * @endcode + */ +#define Unlink_Authority(...) BOOST_PP_OVERLOAD(UNLINKAUTH, __VA_ARGS__)(__VA_ARGS__) /** * @brief Shorthand way to transfer funds diff --git a/tests/tests/native_contract_tests.cpp b/tests/tests/native_contract_tests.cpp index d0c0230ce73..4ccf99f080a 100644 --- a/tests/tests/native_contract_tests.cpp +++ b/tests/tests/native_contract_tests.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -463,4 +464,32 @@ BOOST_FIXTURE_TEST_CASE(auth_tests, testing_fixture) { } } FC_LOG_AND_RETHROW() } +BOOST_FIXTURE_TEST_CASE(auth_links, testing_fixture) { try { + Make_Blockchain(chain); + Make_Account(chain, alice); + chain.produce_blocks(); + + Make_Key(spending); + Make_Key(scud); + + Set_Authority(chain, alice, "spending", "active", Key_Authority(spending_public_key)); + Set_Authority(chain, alice, "scud", "spending", Key_Authority(scud_public_key)); + Link_Authority(chain, alice, "spending", eos, "transfer"); + + { + auto obj = chain_db.find(boost::make_tuple("alice", "eos", "transfer")); + BOOST_CHECK_NE(obj, nullptr); + BOOST_CHECK_EQUAL(obj->account, "alice"); + BOOST_CHECK_EQUAL(obj->code, "eos"); + BOOST_CHECK_EQUAL(obj->message_type, "transfer"); + } + + Unlink_Authority(chain, alice, eos, "transfer"); + + { + auto obj = chain_db.find(boost::make_tuple("alice", "eos", "transfer")); + BOOST_CHECK_EQUAL(obj, nullptr); + } +} FC_LOG_AND_RETHROW() } + BOOST_AUTO_TEST_SUITE_END() From f7c6021645c7ae180bc10034781c6413996c2c77 Mon Sep 17 00:00:00 2001 From: Nathan Hourt Date: Wed, 9 Aug 2017 11:23:06 -0500 Subject: [PATCH 27/27] Improve error message When rejecting a transaction due to excessive authorizations on a message, include the authorizations which were not necessary in the reported error --- libraries/chain/chain_controller.cpp | 6 ++++-- .../eos/chain/message_handling_contexts.hpp | 1 + libraries/chain/message_handling_contexts.cpp | 16 ++++++++++++++++ 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/libraries/chain/chain_controller.cpp b/libraries/chain/chain_controller.cpp index 0c915275334..2f68c5f34fb 100644 --- a/libraries/chain/chain_controller.cpp +++ b/libraries/chain/chain_controller.cpp @@ -612,14 +612,16 @@ void chain_controller::validate_expiration(const SignedTransaction& trx) const * The order of execution of precondition and apply can impact the validity of the * entire message. */ -void chain_controller::process_message( const ProcessedTransaction& trx, AccountName code, const Message& message, MessageOutput& output) { +void chain_controller::process_message(const ProcessedTransaction& trx, AccountName code, + const Message& message, MessageOutput& output) { apply_context apply_ctx(*this, _db, trx, message, code); apply_message(apply_ctx); // process_message recurses for each notified account, but we only want to run this check at the top level if (code == message.code && (_skip_flags & skip_authority_check) == false) EOS_ASSERT(apply_ctx.all_authorizations_used(), tx_irrelevant_auth, - "Message declared an authority it did not need", ("message", message)); + "Message declared authorities it did not need: ${unused}", + ("unused", apply_ctx.unused_authorizations())("message", message)); output.notify.reserve( apply_ctx.notified.size() ); diff --git a/libraries/chain/include/eos/chain/message_handling_contexts.hpp b/libraries/chain/include/eos/chain/message_handling_contexts.hpp index 239094d5354..89549dcd71e 100644 --- a/libraries/chain/include/eos/chain/message_handling_contexts.hpp +++ b/libraries/chain/include/eos/chain/message_handling_contexts.hpp @@ -136,6 +136,7 @@ class apply_context { void require_recipient(const types::AccountName& account); bool all_authorizations_used() const; + vector unused_authorizations() const; const chain_controller& controller; const chainbase::database& db; ///< database where state is stored diff --git a/libraries/chain/message_handling_contexts.cpp b/libraries/chain/message_handling_contexts.cpp index a2fc326c463..098872c5cd7 100644 --- a/libraries/chain/message_handling_contexts.cpp +++ b/libraries/chain/message_handling_contexts.cpp @@ -6,6 +6,9 @@ #include #include +#include +#include +#include namespace eos { namespace chain { @@ -44,6 +47,19 @@ bool apply_context::all_authorizations_used() const { return boost::algorithm::all_of_equal(used_authorizations, true); } +vector 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; + return {range.begin(), range.end()}; +} + // // i64 functions //