From 5e7edd3d480c5594e9226cba5a342e5c5206ce87 Mon Sep 17 00:00:00 2001 From: Brian Johnson Date: Fri, 15 Nov 2019 12:47:30 -0600 Subject: [PATCH] Merge pull request #8218 from EOSIO/8199-cleos-transaction-signatures Add option to provide transaction signature keys to cleos --- programs/cleos/main.cpp | 302 +++++++------ tests/Cluster.py | 6 +- tests/Node.py | 87 ++-- ...onsensus-validation-malicious-producers.py | 398 ++++++++++++++++++ tests/nodeos_run_test.py | 10 +- tests/nodeos_under_min_avail_ram.py | 2 +- tests/p2p_network_test.py | 2 +- tests/prod_preactivation_test.py | 6 +- 8 files changed, 635 insertions(+), 178 deletions(-) create mode 100755 tests/consensus-validation-malicious-producers.py diff --git a/programs/cleos/main.cpp b/programs/cleos/main.cpp index 36152bd21c..4e21e77430 100644 --- a/programs/cleos/main.cpp +++ b/programs/cleos/main.cpp @@ -234,6 +234,49 @@ void add_standard_transaction_options(CLI::App* cmd, string default_permission = cmd->add_option("--retry-num-blocks", tx_retry_num_blocks, localized("Request node to retry transaction until in a block of given height, blocking call")); } +bool is_public_key_str(const std::string& potential_key_str) { + return boost::istarts_with(potential_key_str, "EOS") || boost::istarts_with(potential_key_str, "PUB_R1") || boost::istarts_with(potential_key_str, "PUB_K1") || boost::istarts_with(potential_key_str, "PUB_WA"); +} + +class signing_keys_option { +public: + signing_keys_option() {} + void add_option(CLI::App* cmd) { + cmd->add_option("--sign-with", public_key_json, localized("The public key or json array of public keys to sign with")); + } + + std::vector get_keys() { + std::vector signing_keys; + if (!public_key_json.empty()) { + if (is_public_key_str(public_key_json)) { + try { + signing_keys.push_back(public_key_type(public_key_json)); + } EOS_RETHROW_EXCEPTIONS(public_key_type_exception, "Invalid public key: ${public_key}", ("public_key", public_key_json)) + } else { + fc::variant json_keys; + try { + json_keys = fc::json::from_string(public_key_json, fc::json::relaxed_parser); + } EOS_RETHROW_EXCEPTIONS(json_parse_exception, "Fail to parse JSON from string: ${string}", ("string", public_key_json)); + try { + std::vector keys = json_keys.template as>(); + signing_keys = std::move(keys); + } EOS_RETHROW_EXCEPTIONS(public_key_type_exception, "Invalid public key array format '${data}'", ("data", fc::json::to_string(json_keys))) + } + } + return signing_keys; + } +private: + string public_key_json; +}; + +signing_keys_option signing_keys_opt; + + +void add_standard_transaction_options_plus_signing(CLI::App* cmd, string default_permission = "") { + add_standard_transaction_options(cmd, default_permission); + signing_keys_opt.add_option(cmd); +} + vector get_account_permissions(const vector& permissions) { auto fixedPermissions = permissions | boost::adaptors::transformed([](const string& p) { vector pieces; @@ -325,7 +368,8 @@ void sign_transaction(signed_transaction& trx, fc::variant& required_keys, const trx = signed_trx.as(); } -fc::variant push_transaction( signed_transaction& trx, packed_transaction::compression_type compression = packed_transaction::compression_type::none ) { +fc::variant push_transaction( signed_transaction& trx, const std::vector& signing_keys = std::vector(), + packed_transaction::compression_type compression = packed_transaction::compression_type::none ) { auto info = get_info(); if (trx.signatures.size() == 0) { // #5445 can't change txn content if already signed @@ -352,7 +396,13 @@ fc::variant push_transaction( signed_transaction& trx, packed_transaction::compr } if (!tx_skip_sign) { - auto required_keys = determine_required_keys(trx); + fc::variant required_keys; + if (signing_keys.size() > 0) { + required_keys = fc::variant(signing_keys); + } + else { + required_keys = determine_required_keys(trx); + } sign_transaction(trx, required_keys, info.chain_id); } @@ -413,11 +463,11 @@ fc::variant push_transaction( signed_transaction& trx, packed_transaction::compr } } -fc::variant push_actions(std::vector&& actions, packed_transaction::compression_type compression = packed_transaction::compression_type::none ) { +fc::variant push_actions(std::vector&& actions, packed_transaction::compression_type compression = packed_transaction::compression_type::none, const std::vector& signing_keys = std::vector() ) { signed_transaction trx; trx.actions = std::forward(actions); - return push_transaction(trx, compression); + return push_transaction(trx, signing_keys, compression); } void print_return_value( const fc::variant& at ) { @@ -590,37 +640,14 @@ void print_result( const fc::variant& result ) { try { } } FC_CAPTURE_AND_RETHROW( (result) ) } -void send_actions(std::vector&& actions, packed_transaction::compression_type compression = packed_transaction::compression_type::none ) { +using std::cout; +void send_actions(std::vector&& actions, const std::vector& signing_keys = std::vector(), packed_transaction::compression_type compression = packed_transaction::compression_type::none ) { std::ofstream out; if (tx_json_save_file.length()) { out.open(tx_json_save_file); EOSC_ASSERT(!out.fail(), "ERROR: Failed to create file \"${p}\"", ("p", tx_json_save_file)); } - auto result = push_actions( move(actions), compression); - - string jsonstr; - if (tx_json_save_file.length()) { - jsonstr = fc::json::to_pretty_string( result ); - out << jsonstr; - out.close(); - } - if( tx_print_json ) { - if (jsonstr.length() == 0) { - jsonstr = fc::json::to_pretty_string( result ); - } - cout << jsonstr << endl; - } else { - print_result( result ); - } -} - -void send_transaction( signed_transaction& trx, packed_transaction::compression_type compression = packed_transaction::compression_type::none ) { - std::ofstream out; - if (tx_json_save_file.length()) { - out.open(tx_json_save_file); - EOSC_ASSERT(!out.fail(), "ERROR: Failed to create file \"${p}\"", ("p", tx_json_save_file)); - } - auto result = push_transaction(trx, compression); + auto result = push_actions( move(actions), compression, signing_keys); string jsonstr; if (tx_json_save_file.length()) { @@ -773,7 +800,7 @@ authority parse_json_authority(const std::string& authorityJsonOrFile) { } authority parse_json_authority_or_key(const std::string& authorityJsonOrFile) { - if (boost::istarts_with(authorityJsonOrFile, "EOS") || boost::istarts_with(authorityJsonOrFile, "PUB_R1")) { + if (is_public_key_str(authorityJsonOrFile)) { try { return authority(public_key_type(authorityJsonOrFile)); } EOS_RETHROW_EXCEPTIONS(public_key_type_exception, "Invalid public key: ${public_key}", ("public_key", authorityJsonOrFile)) @@ -963,7 +990,7 @@ struct set_action_permission_subcommand { permissions->add_option("type", typeStr, localized("The type of the action"))->required(); permissions->add_option("requirement", requirementStr, localized("[delete] NULL, [set/update] The permission name require for executing the given action"))->required(); - add_standard_transaction_options(permissions, "account@active"); + add_standard_transaction_options_plus_signing(permissions, "account@active"); permissions->callback([this] { name account = name(accountStr); @@ -972,10 +999,10 @@ struct set_action_permission_subcommand { bool is_delete = boost::iequals(requirementStr, "null"); if (is_delete) { - send_actions({create_unlinkauth(account, code, type)}); + send_actions({create_unlinkauth(account, code, type)}, signing_keys_opt.get_keys()); } else { name requirement = name(requirementStr); - send_actions({create_linkauth(account, code, type, requirement)}); + send_actions({create_linkauth(account, code, type, requirement)}, signing_keys_opt.get_keys()); } }); } @@ -1084,7 +1111,7 @@ struct register_producer_subcommand { register_producer->add_option("producer_key", producer_key_str, localized("The producer's public key"))->required(); register_producer->add_option("url", url, localized("The URL where info about producer can be found"), true); register_producer->add_option("location", loc, localized("Relative location for purpose of nearest neighbor scheduling"), true); - add_standard_transaction_options(register_producer, "account@active"); + add_standard_transaction_options_plus_signing(register_producer, "account@active"); register_producer->callback([this] { @@ -1095,7 +1122,7 @@ struct register_producer_subcommand { auto regprod_var = regproducer_variant(name(producer_str), producer_key, url, loc ); auto accountPermissions = get_account_permissions(tx_permission, {name(producer_str), config::active_name}); - send_actions({create_action(accountPermissions, config::system_account_name, "regproducer"_n, regprod_var)}); + send_actions({create_action(accountPermissions, config::system_account_name, "regproducer"_n, regprod_var)}, signing_keys_opt.get_keys()); }); } }; @@ -1139,7 +1166,7 @@ struct create_account_subcommand { (localized("Transfer voting power and right to unstake tokens to receiver"))); } - add_standard_transaction_options(createAccount, "creator@active"); + add_standard_transaction_options_plus_signing(createAccount, "creator@active"); createAccount->callback([this] { auth_type owner, active; @@ -1176,12 +1203,12 @@ struct create_account_subcommand { auto cpu = to_asset(stake_cpu); if ( net.get_amount() != 0 || cpu.get_amount() != 0 ) { action delegate = create_delegate( name(creator), name(account_name), net, cpu, transfer); - send_actions( { create, buyram, delegate } ); + send_actions( { create, buyram, delegate }, signing_keys_opt.get_keys()); } else { - send_actions( { create, buyram } ); + send_actions( { create, buyram }, signing_keys_opt.get_keys()); } } else { - send_actions( { create } ); + send_actions( { create }, signing_keys_opt.get_keys()); } }); } @@ -1193,14 +1220,14 @@ struct unregister_producer_subcommand { unregister_producer_subcommand(CLI::App* actionRoot) { auto unregister_producer = actionRoot->add_subcommand("unregprod", localized("Unregister an existing producer")); unregister_producer->add_option("account", producer_str, localized("The account to unregister as a producer"))->required(); - add_standard_transaction_options(unregister_producer, "account@active"); + add_standard_transaction_options_plus_signing(unregister_producer, "account@active"); unregister_producer->callback([this] { fc::variant act_payload = fc::mutable_variant_object() ("producer", producer_str); auto accountPermissions = get_account_permissions(tx_permission, {name(producer_str), config::active_name}); - send_actions({create_action(accountPermissions, config::system_account_name, "unregprod"_n, act_payload)}); + send_actions({create_action(accountPermissions, config::system_account_name, "unregprod"_n, act_payload)}, signing_keys_opt.get_keys()); }); } }; @@ -1213,7 +1240,7 @@ struct vote_producer_proxy_subcommand { auto vote_proxy = actionRoot->add_subcommand("proxy", localized("Vote your stake through a proxy")); vote_proxy->add_option("voter", voter_str, localized("The voting account"))->required(); vote_proxy->add_option("proxy", proxy_str, localized("The proxy account"))->required(); - add_standard_transaction_options(vote_proxy, "voter@active"); + add_standard_transaction_options_plus_signing(vote_proxy, "voter@active"); vote_proxy->callback([this] { fc::variant act_payload = fc::mutable_variant_object() @@ -1221,7 +1248,7 @@ struct vote_producer_proxy_subcommand { ("proxy", proxy_str) ("producers", std::vector{}); auto accountPermissions = get_account_permissions(tx_permission, {name(voter_str), config::active_name}); - send_actions({create_action(accountPermissions, config::system_account_name, "voteproducer"_n, act_payload)}); + send_actions({create_action(accountPermissions, config::system_account_name, "voteproducer"_n, act_payload)}, signing_keys_opt.get_keys()); }); } }; @@ -1234,7 +1261,7 @@ struct vote_producers_subcommand { auto vote_producers = actionRoot->add_subcommand("prods", localized("Vote for one or more producers")); vote_producers->add_option("voter", voter_str, localized("The voting account"))->required(); vote_producers->add_option("producers", producer_names, localized("The account(s) to vote for. All options from this position and following will be treated as the producer list."))->required(); - add_standard_transaction_options(vote_producers, "voter@active"); + add_standard_transaction_options_plus_signing(vote_producers, "voter@active"); vote_producers->callback([this] { @@ -1245,7 +1272,7 @@ struct vote_producers_subcommand { ("proxy", "") ("producers", producer_names); auto accountPermissions = get_account_permissions(tx_permission, {name(voter_str), config::active_name}); - send_actions({create_action(accountPermissions, config::system_account_name, "voteproducer"_n, act_payload)}); + send_actions({create_action(accountPermissions, config::system_account_name, "voteproducer"_n, act_payload)}, signing_keys_opt.get_keys()); }); } }; @@ -1258,7 +1285,7 @@ struct approve_producer_subcommand { auto approve_producer = actionRoot->add_subcommand("approve", localized("Add one producer to list of voted producers")); approve_producer->add_option("voter", voter, localized("The voting account"))->required(); approve_producer->add_option("producer", producer_name, localized("The account to vote for"))->required(); - add_standard_transaction_options(approve_producer, "voter@active"); + add_standard_transaction_options_plus_signing(approve_producer, "voter@active"); approve_producer->callback([this] { auto result = call(get_table_func, fc::mutable_variant_object("json", true) @@ -1298,7 +1325,7 @@ struct approve_producer_subcommand { ("proxy", "") ("producers", prods); auto accountPermissions = get_account_permissions(tx_permission, {name(voter), config::active_name}); - send_actions({create_action(accountPermissions, config::system_account_name, "voteproducer"_n, act_payload)}); + send_actions({create_action(accountPermissions, config::system_account_name, "voteproducer"_n, act_payload)}, signing_keys_opt.get_keys()); }); } }; @@ -1311,7 +1338,7 @@ struct unapprove_producer_subcommand { auto approve_producer = actionRoot->add_subcommand("unapprove", localized("Remove one producer from list of voted producers")); approve_producer->add_option("voter", voter, localized("The voting account"))->required(); approve_producer->add_option("producer", producer_name, localized("The account to remove from voted producers"))->required(); - add_standard_transaction_options(approve_producer, "voter@active"); + add_standard_transaction_options_plus_signing(approve_producer, "voter@active"); approve_producer->callback([this] { auto result = call(get_table_func, fc::mutable_variant_object("json", true) @@ -1350,7 +1377,7 @@ struct unapprove_producer_subcommand { ("proxy", "") ("producers", prods); auto accountPermissions = get_account_permissions(tx_permission, {name(voter), config::active_name}); - send_actions({create_action(accountPermissions, config::system_account_name, "voteproducer"_n, act_payload)}); + send_actions({create_action(accountPermissions, config::system_account_name, "voteproducer"_n, act_payload)}, signing_keys_opt.get_keys()); }); } }; @@ -1513,7 +1540,7 @@ struct delegate_bandwidth_subcommand { delegate_bandwidth->add_option("--buyram", buy_ram_amount, localized("The amount of tokens to buy RAM with")); delegate_bandwidth->add_option("--buy-ram-bytes", buy_ram_bytes, localized("The amount of RAM to buy in bytes")); delegate_bandwidth->add_flag("--transfer", transfer, localized("Transfer voting power and right to unstake tokens to receiver")); - add_standard_transaction_options(delegate_bandwidth, "from@active"); + add_standard_transaction_options_plus_signing(delegate_bandwidth, "from@active"); delegate_bandwidth->callback([this] { fc::variant act_payload = fc::mutable_variant_object() @@ -1530,7 +1557,7 @@ struct delegate_bandwidth_subcommand { } else if (buy_ram_bytes) { acts.push_back( create_buyrambytes(name(from_str), name(receiver_str), buy_ram_bytes) ); } - send_actions(std::move(acts)); + send_actions(std::move(acts), signing_keys_opt.get_keys()); }); } }; @@ -1548,7 +1575,7 @@ struct undelegate_bandwidth_subcommand { undelegate_bandwidth->add_option("receiver", receiver_str, localized("The account to undelegate bandwidth from"))->required(); undelegate_bandwidth->add_option("unstake_net_quantity", unstake_net_amount, localized("The amount of tokens to undelegate for network bandwidth"))->required(); undelegate_bandwidth->add_option("unstake_cpu_quantity", unstake_cpu_amount, localized("The amount of tokens to undelegate for CPU bandwidth"))->required(); - add_standard_transaction_options(undelegate_bandwidth, "from@active"); + add_standard_transaction_options_plus_signing(undelegate_bandwidth, "from@active"); undelegate_bandwidth->callback([this] { fc::variant act_payload = fc::mutable_variant_object() @@ -1557,7 +1584,7 @@ struct undelegate_bandwidth_subcommand { ("unstake_net_quantity", to_asset(unstake_net_amount)) ("unstake_cpu_quantity", to_asset(unstake_cpu_amount)); auto accountPermissions = get_account_permissions(tx_permission, {name(from_str), config::active_name}); - send_actions({create_action(accountPermissions, config::system_account_name, "undelegatebw"_n, act_payload)}); + send_actions({create_action(accountPermissions, config::system_account_name, "undelegatebw"_n, act_payload)}, signing_keys_opt.get_keys()); }); } }; @@ -1571,14 +1598,15 @@ struct bidname_subcommand { bidname->add_option("bidder", bidder_str, localized("The bidding account"))->required(); bidname->add_option("newname", newname_str, localized("The bidding name"))->required(); bidname->add_option("bid", bid_amount, localized("The amount of tokens to bid"))->required(); - add_standard_transaction_options(bidname, "bidder@active"); + add_standard_transaction_options_plus_signing(bidname, "bidder@active"); + bidname->callback([this] { fc::variant act_payload = fc::mutable_variant_object() ("bidder", bidder_str) ("newname", newname_str) ("bid", to_asset(bid_amount)); auto accountPermissions = get_account_permissions(tx_permission, {name(bidder_str), config::active_name}); - send_actions({create_action(accountPermissions, config::system_account_name, "bidname"_n, act_payload)}); + send_actions({create_action(accountPermissions, config::system_account_name, "bidname"_n, act_payload)}, signing_keys_opt.get_keys()); }); } }; @@ -1677,13 +1705,13 @@ struct buyram_subcommand { buyram->add_option("amount", amount, localized("The amount of tokens to pay for RAM, or number of bytes/kibibytes of RAM if --bytes/--kbytes is set"))->required(); buyram->add_flag("--kbytes,-k", kbytes, localized("The amount to buy in kibibytes (KiB)")); buyram->add_flag("--bytes,-b", bytes, localized("The amount to buy in bytes")); - add_standard_transaction_options(buyram, "payer@active"); + add_standard_transaction_options_plus_signing(buyram, "payer@active"); buyram->callback([this] { EOSC_ASSERT( !kbytes || !bytes, "ERROR: --kbytes and --bytes cannot be set at the same time" ); if (kbytes || bytes) { - send_actions( { create_buyrambytes(name(from_str), name(receiver_str), fc::to_uint64(amount) * ((kbytes) ? 1024ull : 1ull)) } ); + send_actions( { create_buyrambytes(name(from_str), name(receiver_str), fc::to_uint64(amount) * ((kbytes) ? 1024ull : 1ull)) }, signing_keys_opt.get_keys()); } else { - send_actions( { create_buyram(name(from_str), name(receiver_str), to_asset(amount)) } ); + send_actions( { create_buyram(name(from_str), name(receiver_str), to_asset(amount)) }, signing_keys_opt.get_keys()); } }); } @@ -1698,14 +1726,14 @@ struct sellram_subcommand { auto sellram = actionRoot->add_subcommand("sellram", localized("Sell RAM")); sellram->add_option("account", receiver_str, localized("The account to receive tokens for sold RAM"))->required(); sellram->add_option("bytes", amount, localized("The amount of RAM bytes to sell"))->required(); - add_standard_transaction_options(sellram, "account@active"); + add_standard_transaction_options_plus_signing(sellram, "account@active"); sellram->callback([this] { fc::variant act_payload = fc::mutable_variant_object() ("account", receiver_str) ("bytes", amount); auto accountPermissions = get_account_permissions(tx_permission, {name(receiver_str), config::active_name}); - send_actions({create_action(accountPermissions, config::system_account_name, "sellram"_n, act_payload)}); + send_actions({create_action(accountPermissions, config::system_account_name, "sellram"_n, act_payload)}, signing_keys_opt.get_keys()); }); } }; @@ -1716,13 +1744,13 @@ struct claimrewards_subcommand { claimrewards_subcommand(CLI::App* actionRoot) { auto claim_rewards = actionRoot->add_subcommand("claimrewards", localized("Claim producer rewards")); claim_rewards->add_option("owner", owner, localized("The account to claim rewards for"))->required(); - add_standard_transaction_options(claim_rewards, "owner@active"); + add_standard_transaction_options_plus_signing(claim_rewards, "owner@active"); claim_rewards->callback([this] { fc::variant act_payload = fc::mutable_variant_object() ("owner", owner); auto accountPermissions = get_account_permissions(tx_permission, {name(owner), config::active_name}); - send_actions({create_action(accountPermissions, config::system_account_name, "claimrewards"_n, act_payload)}); + send_actions({create_action(accountPermissions, config::system_account_name, "claimrewards"_n, act_payload)}, signing_keys_opt.get_keys()); }); } }; @@ -1733,14 +1761,14 @@ struct regproxy_subcommand { regproxy_subcommand(CLI::App* actionRoot) { auto register_proxy = actionRoot->add_subcommand("regproxy", localized("Register an account as a proxy (for voting)")); register_proxy->add_option("proxy", proxy, localized("The proxy account to register"))->required(); - add_standard_transaction_options(register_proxy, "proxy@active"); + add_standard_transaction_options_plus_signing(register_proxy, "proxy@active"); register_proxy->callback([this] { fc::variant act_payload = fc::mutable_variant_object() ("proxy", proxy) ("isproxy", true); auto accountPermissions = get_account_permissions(tx_permission, {name(proxy), config::active_name}); - send_actions({create_action(accountPermissions, config::system_account_name, "regproxy"_n, act_payload)}); + send_actions({create_action(accountPermissions, config::system_account_name, "regproxy"_n, act_payload)}, signing_keys_opt.get_keys()); }); } }; @@ -1751,14 +1779,14 @@ struct unregproxy_subcommand { unregproxy_subcommand(CLI::App* actionRoot) { auto unregister_proxy = actionRoot->add_subcommand("unregproxy", localized("Unregister an account as a proxy (for voting)")); unregister_proxy->add_option("proxy", proxy, localized("The proxy account to unregister"))->required(); - add_standard_transaction_options(unregister_proxy, "proxy@active"); + add_standard_transaction_options_plus_signing(unregister_proxy, "proxy@active"); unregister_proxy->callback([this] { fc::variant act_payload = fc::mutable_variant_object() ("proxy", proxy) ("isproxy", false); auto accountPermissions = get_account_permissions(tx_permission, {name(proxy), config::active_name}); - send_actions({create_action(accountPermissions, config::system_account_name, "regproxy"_n, act_payload)}); + send_actions({create_action(accountPermissions, config::system_account_name, "regproxy"_n, act_payload)}, signing_keys_opt.get_keys()); }); } }; @@ -1773,7 +1801,7 @@ struct canceldelay_subcommand { cancel_delay->add_option("canceling_account", canceling_account, localized("Account from authorization on the original delayed transaction"))->required(); cancel_delay->add_option("canceling_permission", canceling_permission, localized("Permission from authorization on the original delayed transaction"))->required(); cancel_delay->add_option("trx_id", trx_id, localized("The transaction id of the original delayed transaction"))->required(); - add_standard_transaction_options(cancel_delay, "canceling_account@canceling_permission"); + add_standard_transaction_options_plus_signing(cancel_delay, "canceling_account@canceling_permission"); cancel_delay->callback([this] { auto canceling_auth = permission_level{name(canceling_account), name(canceling_permission)}; @@ -1781,7 +1809,7 @@ struct canceldelay_subcommand { ("canceling_auth", canceling_auth) ("trx_id", trx_id); auto accountPermissions = get_account_permissions(tx_permission, canceling_auth); - send_actions({create_action(accountPermissions, config::system_account_name, "canceldelay"_n, act_payload)}); + send_actions({create_action(accountPermissions, config::system_account_name, "canceldelay"_n, act_payload)}, signing_keys_opt.get_keys()); }); } }; @@ -1795,13 +1823,13 @@ struct deposit_subcommand { auto deposit = actionRoot->add_subcommand("deposit", localized("Deposit into owner's REX fund by transfering from owner's liquid token balance")); deposit->add_option("owner", owner_str, localized("Account which owns the REX fund"))->required(); deposit->add_option("amount", amount_str, localized("Amount to be deposited into REX fund"))->required(); - add_standard_transaction_options(deposit, "owner@active"); + add_standard_transaction_options_plus_signing(deposit, "owner@active"); deposit->callback([this] { fc::variant act_payload = fc::mutable_variant_object() ("owner", owner_str) ("amount", amount_str); auto accountPermissions = get_account_permissions(tx_permission, {name(owner_str), config::active_name}); - send_actions({create_action(accountPermissions, config::system_account_name, act_name, act_payload)}); + send_actions({create_action(accountPermissions, config::system_account_name, act_name, act_payload)}, signing_keys_opt.get_keys()); }); } }; @@ -1815,13 +1843,13 @@ struct withdraw_subcommand { auto withdraw = actionRoot->add_subcommand("withdraw", localized("Withdraw from owner's REX fund by transfering to owner's liquid token balance")); withdraw->add_option("owner", owner_str, localized("Account which owns the REX fund"))->required(); withdraw->add_option("amount", amount_str, localized("Amount to be withdrawn from REX fund"))->required(); - add_standard_transaction_options(withdraw, "owner@active"); + add_standard_transaction_options_plus_signing(withdraw, "owner@active"); withdraw->callback([this] { fc::variant act_payload = fc::mutable_variant_object() ("owner", owner_str) ("amount", amount_str); auto accountPermissions = get_account_permissions(tx_permission, {name(owner_str), config::active_name}); - send_actions({create_action(accountPermissions, config::system_account_name, act_name, act_payload)}); + send_actions({create_action(accountPermissions, config::system_account_name, act_name, act_payload)}, signing_keys_opt.get_keys()); }); } }; @@ -1835,13 +1863,13 @@ struct buyrex_subcommand { auto buyrex = actionRoot->add_subcommand("buyrex", localized("Buy REX using tokens in owner's REX fund")); buyrex->add_option("from", from_str, localized("Account buying REX tokens"))->required(); buyrex->add_option("amount", amount_str, localized("Amount to be taken from REX fund and used in buying REX"))->required(); - add_standard_transaction_options(buyrex, "from@active"); + add_standard_transaction_options_plus_signing(buyrex, "from@active"); buyrex->callback([this] { fc::variant act_payload = fc::mutable_variant_object() ("from", from_str) ("amount", amount_str); auto accountPermissions = get_account_permissions(tx_permission, {name(from_str), config::active_name}); - send_actions({create_action(accountPermissions, config::system_account_name, act_name, act_payload)}); + send_actions({create_action(accountPermissions, config::system_account_name, act_name, act_payload)}, signing_keys_opt.get_keys()); }); } }; @@ -1856,7 +1884,7 @@ struct lendrex_subcommand { auto lendrex = actionRoot->add_subcommand("lendrex", localized("Deposit tokens to REX fund and use the tokens to buy REX")); lendrex->add_option("from", from_str, localized("Account buying REX tokens"))->required(); lendrex->add_option("amount", amount_str, localized("Amount of liquid tokens to be used in buying REX"))->required(); - add_standard_transaction_options(lendrex, "from@active"); + add_standard_transaction_options_plus_signing(lendrex, "from@active"); lendrex->callback([this] { fc::variant act_payload1 = fc::mutable_variant_object() ("owner", from_str) @@ -1866,7 +1894,7 @@ struct lendrex_subcommand { ("amount", amount_str); auto accountPermissions = get_account_permissions(tx_permission, {name(from_str), config::active_name}); send_actions({create_action(accountPermissions, config::system_account_name, act_name1, act_payload1), - create_action(accountPermissions, config::system_account_name, act_name2, act_payload2)}); + create_action(accountPermissions, config::system_account_name, act_name2, act_payload2)}, signing_keys_opt.get_keys()); }); } }; @@ -1884,7 +1912,7 @@ struct unstaketorex_subcommand { unstaketorex->add_option("receiver", receiver_str, localized("Account that tokens have been staked to"))->required(); unstaketorex->add_option("from_net", from_net_str, localized("Amount to be unstaked from Net resources and used in REX purchase"))->required(); unstaketorex->add_option("from_cpu", from_cpu_str, localized("Amount to be unstaked from CPU resources and used in REX purchase"))->required(); - add_standard_transaction_options(unstaketorex, "owner@active"); + add_standard_transaction_options_plus_signing(unstaketorex, "owner@active"); unstaketorex->callback([this] { fc::variant act_payload = fc::mutable_variant_object() ("owner", owner_str) @@ -1892,7 +1920,7 @@ struct unstaketorex_subcommand { ("from_net", from_net_str) ("from_cpu", from_cpu_str); auto accountPermissions = get_account_permissions(tx_permission, {name(owner_str), config::active_name}); - send_actions({create_action(accountPermissions, config::system_account_name, act_name, act_payload)}); + send_actions({create_action(accountPermissions, config::system_account_name, act_name, act_payload)}, signing_keys_opt.get_keys()); }); } }; @@ -1906,13 +1934,13 @@ struct sellrex_subcommand { auto sellrex = actionRoot->add_subcommand("sellrex", localized("Sell REX tokens")); sellrex->add_option("from", from_str, localized("Account selling REX tokens"))->required(); sellrex->add_option("rex", rex_str, localized("Amount of REX tokens to be sold"))->required(); - add_standard_transaction_options(sellrex, "from@active"); + add_standard_transaction_options_plus_signing(sellrex, "from@active"); sellrex->callback([this] { fc::variant act_payload = fc::mutable_variant_object() ("from", from_str) ("rex", rex_str); auto accountPermissions = get_account_permissions(tx_permission, {name(from_str), config::active_name}); - send_actions({create_action(accountPermissions, config::system_account_name, act_name, act_payload)}); + send_actions({create_action(accountPermissions, config::system_account_name, act_name, act_payload)}, signing_keys_opt.get_keys()); }); } }; @@ -1924,11 +1952,11 @@ struct cancelrexorder_subcommand { cancelrexorder_subcommand(CLI::App* actionRoot) { auto cancelrexorder = actionRoot->add_subcommand("cancelrexorder", localized("Cancel queued REX sell order if one exists")); cancelrexorder->add_option("owner", owner_str, localized("Owner account of sell order"))->required(); - add_standard_transaction_options(cancelrexorder, "owner@active"); + add_standard_transaction_options_plus_signing(cancelrexorder, "owner@active"); cancelrexorder->callback([this] { fc::variant act_payload = fc::mutable_variant_object()("owner", owner_str); auto accountPermissions = get_account_permissions(tx_permission, {name(owner_str), config::active_name}); - send_actions({create_action(accountPermissions, config::system_account_name, act_name, act_payload)}); + send_actions({create_action(accountPermissions, config::system_account_name, act_name, act_payload)}, signing_keys_opt.get_keys()); }); } }; @@ -1946,7 +1974,7 @@ struct rentcpu_subcommand { rentcpu->add_option("receiver", receiver_str, localized("Account to whom rented CPU bandwidth is staked"))->required(); rentcpu->add_option("loan_payment", loan_payment_str, localized("Loan fee to be paid, used to calculate amount of rented bandwidth"))->required(); rentcpu->add_option("loan_fund", loan_fund_str, localized("Loan fund to be used in automatic renewal, can be 0 tokens"))->required(); - add_standard_transaction_options(rentcpu, "from@active"); + add_standard_transaction_options_plus_signing(rentcpu, "from@active"); rentcpu->callback([this] { fc::variant act_payload = fc::mutable_variant_object() ("from", from_str) @@ -1954,7 +1982,7 @@ struct rentcpu_subcommand { ("loan_payment", loan_payment_str) ("loan_fund", loan_fund_str); auto accountPermissions = get_account_permissions(tx_permission, {name(from_str), config::active_name}); - send_actions({create_action(accountPermissions, config::system_account_name, act_name, act_payload)}); + send_actions({create_action(accountPermissions, config::system_account_name, act_name, act_payload)}, signing_keys_opt.get_keys()); }); } }; @@ -1972,7 +2000,7 @@ struct rentnet_subcommand { rentnet->add_option("receiver", receiver_str, localized("Account to whom rented Network bandwidth is staked"))->required(); rentnet->add_option("loan_payment", loan_payment_str, localized("Loan fee to be paid, used to calculate amount of rented bandwidth"))->required(); rentnet->add_option("loan_fund", loan_fund_str, localized("Loan fund to be used in automatic renewal, can be 0 tokens"))->required(); - add_standard_transaction_options(rentnet, "from@active"); + add_standard_transaction_options_plus_signing(rentnet, "from@active"); rentnet->callback([this] { fc::variant act_payload = fc::mutable_variant_object() ("from", from_str) @@ -1980,7 +2008,7 @@ struct rentnet_subcommand { ("loan_payment", loan_payment_str) ("loan_fund", loan_fund_str); auto accountPermissions = get_account_permissions(tx_permission, {name(from_str), config::active_name}); - send_actions({create_action(accountPermissions, config::system_account_name, act_name, act_payload)}); + send_actions({create_action(accountPermissions, config::system_account_name, act_name, act_payload)}, signing_keys_opt.get_keys()); }); } }; @@ -1996,14 +2024,14 @@ struct fundcpuloan_subcommand { fundcpuloan->add_option("from", from_str, localized("Loan owner"))->required(); fundcpuloan->add_option("loan_num", loan_num_str, localized("Loan ID"))->required(); fundcpuloan->add_option("payment", payment_str, localized("Amount to be deposited"))->required(); - add_standard_transaction_options(fundcpuloan, "from@active"); + add_standard_transaction_options_plus_signing(fundcpuloan, "from@active"); fundcpuloan->callback([this] { fc::variant act_payload = fc::mutable_variant_object() ("from", from_str) ("loan_num", loan_num_str) ("payment", payment_str); auto accountPermissions = get_account_permissions(tx_permission, {name(from_str), config::active_name}); - send_actions({create_action(accountPermissions, config::system_account_name, act_name, act_payload)}); + send_actions({create_action(accountPermissions, config::system_account_name, act_name, act_payload)}, signing_keys_opt.get_keys()); }); } }; @@ -2019,14 +2047,14 @@ struct fundnetloan_subcommand { fundnetloan->add_option("from", from_str, localized("Loan owner"))->required(); fundnetloan->add_option("loan_num", loan_num_str, localized("Loan ID"))->required(); fundnetloan->add_option("payment", payment_str, localized("Amount to be deposited"))->required(); - add_standard_transaction_options(fundnetloan, "from@active"); + add_standard_transaction_options_plus_signing(fundnetloan, "from@active"); fundnetloan->callback([this] { fc::variant act_payload = fc::mutable_variant_object() ("from", from_str) ("loan_num", loan_num_str) ("payment", payment_str); auto accountPermissions = get_account_permissions(tx_permission, {name(from_str), config::active_name}); - send_actions({create_action(accountPermissions, config::system_account_name, act_name, act_payload)}); + send_actions({create_action(accountPermissions, config::system_account_name, act_name, act_payload)}, signing_keys_opt.get_keys()); }); } }; @@ -2042,14 +2070,14 @@ struct defcpuloan_subcommand { defcpuloan->add_option("from", from_str, localized("Loan owner"))->required(); defcpuloan->add_option("loan_num", loan_num_str, localized("Loan ID"))->required(); defcpuloan->add_option("amount", amount_str, localized("Amount to be withdrawn"))->required(); - add_standard_transaction_options(defcpuloan, "from@active"); + add_standard_transaction_options_plus_signing(defcpuloan, "from@active"); defcpuloan->callback([this] { fc::variant act_payload = fc::mutable_variant_object() ("from", from_str) ("loan_num", loan_num_str) ("amount", amount_str); auto accountPermissions = get_account_permissions(tx_permission, {name(from_str), config::active_name}); - send_actions({create_action(accountPermissions, config::system_account_name, act_name, act_payload)}); + send_actions({create_action(accountPermissions, config::system_account_name, act_name, act_payload)}, signing_keys_opt.get_keys()); }); } }; @@ -2065,14 +2093,14 @@ struct defnetloan_subcommand { defnetloan->add_option("from", from_str, localized("Loan owner"))->required(); defnetloan->add_option("loan_num", loan_num_str, localized("Loan ID"))->required(); defnetloan->add_option("amount", amount_str, localized("Amount to be withdrawn"))->required(); - add_standard_transaction_options(defnetloan, "from@active"); + add_standard_transaction_options_plus_signing(defnetloan, "from@active"); defnetloan->callback([this] { fc::variant act_payload = fc::mutable_variant_object() ("from", from_str) ("loan_num", loan_num_str) ("amount", amount_str); auto accountPermissions = get_account_permissions(tx_permission, {name(from_str), config::active_name}); - send_actions({create_action(accountPermissions, config::system_account_name, act_name, act_payload)}); + send_actions({create_action(accountPermissions, config::system_account_name, act_name, act_payload)}, signing_keys_opt.get_keys()); }); } }; @@ -2086,13 +2114,13 @@ struct mvtosavings_subcommand { auto mvtosavings = actionRoot->add_subcommand("mvtosavings", localized("Move REX tokens to savings bucket")); mvtosavings->add_option("owner", owner_str, localized("REX owner"))->required(); mvtosavings->add_option("rex", rex_str, localized("Amount of REX to be moved to savings bucket"))->required(); - add_standard_transaction_options(mvtosavings, "owner@active"); + add_standard_transaction_options_plus_signing(mvtosavings, "owner@active"); mvtosavings->callback([this] { fc::variant act_payload = fc::mutable_variant_object() ("owner", owner_str) ("rex", rex_str); auto accountPermissions = get_account_permissions(tx_permission, {name(owner_str), config::active_name}); - send_actions({create_action(accountPermissions, config::system_account_name, act_name, act_payload)}); + send_actions({create_action(accountPermissions, config::system_account_name, act_name, act_payload)}, signing_keys_opt.get_keys()); }); } }; @@ -2106,13 +2134,13 @@ struct mvfrsavings_subcommand { auto mvfrsavings = actionRoot->add_subcommand("mvfromsavings", localized("Move REX tokens out of savings bucket")); mvfrsavings->add_option("owner", owner_str, localized("REX owner"))->required(); mvfrsavings->add_option("rex", rex_str, localized("Amount of REX to be moved out of savings bucket"))->required(); - add_standard_transaction_options(mvfrsavings, "owner@active"); + add_standard_transaction_options_plus_signing(mvfrsavings, "owner@active"); mvfrsavings->callback([this] { fc::variant act_payload = fc::mutable_variant_object() ("owner", owner_str) ("rex", rex_str); auto accountPermissions = get_account_permissions(tx_permission, {name(owner_str), config::active_name}); - send_actions({create_action(accountPermissions, config::system_account_name, act_name, act_payload)}); + send_actions({create_action(accountPermissions, config::system_account_name, act_name, act_payload)}, signing_keys_opt.get_keys()); }); } }; @@ -2124,11 +2152,11 @@ struct updaterex_subcommand { updaterex_subcommand(CLI::App* actionRoot) { auto updaterex = actionRoot->add_subcommand("updaterex", localized("Update REX owner vote stake and vote weight")); updaterex->add_option("owner", owner_str, localized("REX owner"))->required(); - add_standard_transaction_options(updaterex, "owner@active"); + add_standard_transaction_options_plus_signing(updaterex, "owner@active"); updaterex->callback([this] { fc::variant act_payload = fc::mutable_variant_object()("owner", owner_str); auto accountPermissions = get_account_permissions(tx_permission, {name(owner_str), config::active_name}); - send_actions({create_action(accountPermissions, config::system_account_name, act_name, act_payload)}); + send_actions({create_action(accountPermissions, config::system_account_name, act_name, act_payload)}, signing_keys_opt.get_keys()); }); } }; @@ -2140,11 +2168,11 @@ struct consolidate_subcommand { consolidate_subcommand(CLI::App* actionRoot) { auto consolidate = actionRoot->add_subcommand("consolidate", localized("Consolidate REX maturity buckets into one that matures in 4 days")); consolidate->add_option("owner", owner_str, localized("REX owner"))->required(); - add_standard_transaction_options(consolidate, "owner@active"); + add_standard_transaction_options_plus_signing(consolidate, "owner@active"); consolidate->callback([this] { fc::variant act_payload = fc::mutable_variant_object()("owner", owner_str); auto accountPermissions = get_account_permissions(tx_permission, {name(owner_str), config::active_name}); - send_actions({create_action(accountPermissions, config::system_account_name, act_name, act_payload)}); + send_actions({create_action(accountPermissions, config::system_account_name, act_name, act_payload)}, signing_keys_opt.get_keys()); }); } }; @@ -2158,13 +2186,13 @@ struct rexexec_subcommand { auto rexexec = actionRoot->add_subcommand("rexexec", localized("Perform REX maintenance by processing expired loans and unfilled sell orders")); rexexec->add_option("user", user_str, localized("User executing the action"))->required(); rexexec->add_option("max", max_str, localized("Maximum number of CPU loans, Network loans, and sell orders to be processed"))->required(); - add_standard_transaction_options(rexexec, "user@active"); + add_standard_transaction_options_plus_signing(rexexec, "user@active"); rexexec->callback([this] { fc::variant act_payload = fc::mutable_variant_object() ("user", user_str) ("max", max_str); auto accountPermissions = get_account_permissions(tx_permission, {name(user_str), config::active_name}); - send_actions({create_action(accountPermissions, config::system_account_name, act_name, act_payload)}); + send_actions({create_action(accountPermissions, config::system_account_name, act_name, act_payload)}, signing_keys_opt.get_keys()); }); } }; @@ -2176,11 +2204,11 @@ struct closerex_subcommand { closerex_subcommand(CLI::App* actionRoot) { auto closerex = actionRoot->add_subcommand("closerex", localized("Delete unused REX-related user table entries")); closerex->add_option("owner", owner_str, localized("REX owner"))->required(); - add_standard_transaction_options(closerex, "owner@active"); + add_standard_transaction_options_plus_signing(closerex, "owner@active"); closerex->callback([this] { fc::variant act_payload = fc::mutable_variant_object()("owner", owner_str); auto accountPermissions = get_account_permissions(tx_permission, {name(owner_str), config::active_name}); - send_actions({create_action(accountPermissions, config::system_account_name, act_name, act_payload)}); + send_actions({create_action(accountPermissions, config::system_account_name, act_name, act_payload)}, signing_keys_opt.get_keys()); }); } }; @@ -3203,7 +3231,7 @@ int main( int argc, char** argv ) { actions.emplace_back( create_setcode(name(account), code_bytes ) ); if ( shouldSend ) { std::cerr << localized("Setting Code...") << std::endl; - send_actions(std::move(actions), packed_transaction::compression_type::zlib); + send_actions(std::move(actions), signing_keys_opt.get_keys(), packed_transaction::compression_type::zlib); } } else { std::cerr << localized("Skipping set code because the new code is the same as the existing code") << std::endl; @@ -3251,16 +3279,16 @@ int main( int argc, char** argv ) { } EOS_RETHROW_EXCEPTIONS(abi_type_exception, "Fail to parse ABI JSON") if ( shouldSend ) { std::cerr << localized("Setting ABI...") << std::endl; - send_actions(std::move(actions), packed_transaction::compression_type::zlib); + send_actions(std::move(actions), signing_keys_opt.get_keys(), packed_transaction::compression_type::zlib); } } else { std::cerr << localized("Skipping set abi because the new abi is the same as the existing abi") << std::endl; } }; - add_standard_transaction_options(contractSubcommand, "account@active"); - add_standard_transaction_options(codeSubcommand, "account@active"); - add_standard_transaction_options(abiSubcommand, "account@active"); + add_standard_transaction_options_plus_signing(contractSubcommand, "account@active"); + add_standard_transaction_options_plus_signing(codeSubcommand, "account@active"); + add_standard_transaction_options_plus_signing(abiSubcommand, "account@active"); contractSubcommand->callback([&] { if(!contract_clear) EOS_ASSERT( !contractPath.empty(), contract_exception, " contract-dir is null ", ("f", contractPath) ); shouldSend = false; @@ -3268,7 +3296,7 @@ int main( int argc, char** argv ) { set_abi_callback(); if (actions.size()) { std::cerr << localized("Publishing contract...") << std::endl; - send_actions(std::move(actions), packed_transaction::compression_type::zlib); + send_actions(std::move(actions), signing_keys_opt.get_keys(), packed_transaction::compression_type::zlib); } else { std::cout << "no transaction is sent" << std::endl; } @@ -3303,7 +3331,7 @@ int main( int argc, char** argv ) { transfer->add_option("--contract,-c", con, localized("The contract that controls the token")); transfer->add_flag("--pay-ram-to-open", pay_ram, localized("Pay RAM to open recipient's token balance row")); - add_standard_transaction_options(transfer, "sender@active"); + add_standard_transaction_options_plus_signing(transfer, "sender@active"); transfer->callback([&] { if (tx_force_unique && memo.size() == 0) { // use the memo to add a nonce @@ -3314,10 +3342,10 @@ int main( int argc, char** argv ) { auto transfer_amount = to_asset(name(con), amount); auto transfer = create_transfer(con, name(sender), name(recipient), transfer_amount, memo); if (!pay_ram) { - send_actions( { transfer }); + send_actions( { transfer }, signing_keys_opt.get_keys()); } else { auto open_ = create_open(con, name(recipient), transfer_amount.get_symbol(), name(sender)); - send_actions( { open_, transfer } ); + send_actions( { open_, transfer }, signing_keys_opt.get_keys()); } }); @@ -3615,7 +3643,7 @@ int main( int argc, char** argv ) { localized("A JSON string or filename defining the action to execute on the contract"), true)->required(); actionsSubcommand->add_option("data", data, localized("The arguments to the contract"))->required(); - add_standard_transaction_options(actionsSubcommand); + add_standard_transaction_options_plus_signing(actionsSubcommand); actionsSubcommand->callback([&] { fc::variant action_args_var; if( !data.empty() ) { @@ -3624,26 +3652,26 @@ int main( int argc, char** argv ) { auto accountPermissions = get_account_permissions(tx_permission); send_actions({chain::action{accountPermissions, name(contract_account), name(action), - variant_to_bin( name(contract_account), name(action), action_args_var ) }}); + variant_to_bin( name(contract_account), name(action), action_args_var ) }}, signing_keys_opt.get_keys()); }); // push transaction string trx_to_push; auto trxSubcommand = push->add_subcommand("transaction", localized("Push an arbitrary JSON transaction")); trxSubcommand->add_option("transaction", trx_to_push, localized("The JSON string or filename defining the transaction to push"))->required(); - add_standard_transaction_options(trxSubcommand); + add_standard_transaction_options_plus_signing(trxSubcommand); trxSubcommand->add_flag("-o,--read-only", tx_read_only, localized("Specify a transaction is read-only")); trxSubcommand->callback([&] { fc::variant trx_var = json_from_file_or_string(trx_to_push); try { signed_transaction trx = trx_var.as(); - std::cout << fc::json::to_pretty_string( push_transaction( trx )) << std::endl; + std::cout << fc::json::to_pretty_string( push_transaction( trx, signing_keys_opt.get_keys() )) << std::endl; } catch( const std::exception& ) { // unable to convert so try via abi signed_transaction trx; abi_serializer::from_variant( trx_var, trx, abi_serializer_resolver, abi_serializer::create_yield_function( abi_serializer_max_time ) ); - std::cout << fc::json::to_pretty_string( push_transaction( trx )) << std::endl; + std::cout << fc::json::to_pretty_string( push_transaction( trx, signing_keys_opt.get_keys() )) << std::endl; } }); @@ -3682,7 +3710,7 @@ int main( int argc, char** argv ) { }; auto propose_action = msig->add_subcommand("propose", localized("Propose action")); - add_standard_transaction_options(propose_action, "proposer@active"); + add_standard_transaction_options_plus_signing(propose_action, "proposer@active"); propose_action->add_option("proposal_name", proposal_name, localized("The proposal name (string)"))->required(); propose_action->add_option("requested_permissions", requested_perm, localized("The JSON string or filename defining requested permissions"))->required(); propose_action->add_option("trx_permissions", transaction_perm, localized("The JSON string or filename defining transaction permissions"))->required(); @@ -3743,12 +3771,12 @@ int main( int argc, char** argv ) { ("requested", requested_perm_var) ("trx", trx_var); - send_actions({chain::action{accountPermissions, "eosio.msig"_n, "propose"_n, variant_to_bin( "eosio.msig"_n, "propose"_n, args ) }}); + send_actions({chain::action{accountPermissions, "eosio.msig"_n, "propose"_n, variant_to_bin( "eosio.msig"_n, "propose"_n, args ) }}, signing_keys_opt.get_keys()); }); //multisig propose transaction auto propose_trx = msig->add_subcommand("propose_trx", localized("Propose transaction")); - add_standard_transaction_options(propose_trx, "proposer@active"); + add_standard_transaction_options_plus_signing(propose_trx, "proposer@active"); propose_trx->add_option("proposal_name", proposal_name, localized("The proposal name (string)"))->required(); propose_trx->add_option("requested_permissions", requested_perm, localized("The JSON string or filename defining requested permissions"))->required(); propose_trx->add_option("transaction", trx_to_push, localized("The JSON string or filename defining the transaction to push"))->required(); @@ -3776,7 +3804,7 @@ int main( int argc, char** argv ) { ("requested", requested_perm_var) ("trx", trx_var); - send_actions({chain::action{accountPermissions, "eosio.msig"_n, "propose"_n, variant_to_bin( "eosio.msig"_n, "propose"_n, args ) }}); + send_actions({chain::action{accountPermissions, "eosio.msig"_n, "propose"_n, variant_to_bin( "eosio.msig"_n, "propose"_n, args ) }}, signing_keys_opt.get_keys()); }); @@ -3993,12 +4021,12 @@ int main( int argc, char** argv ) { } auto accountPermissions = get_account_permissions(tx_permission, {name(proposer), config::active_name}); - send_actions({chain::action{accountPermissions, "eosio.msig"_n, name(action), variant_to_bin( "eosio.msig"_n, name(action), args ) }}); + send_actions({chain::action{accountPermissions, "eosio.msig"_n, name(action), variant_to_bin( "eosio.msig"_n, name(action), args ) }}, signing_keys_opt.get_keys()); }; // multisig approve auto approve = msig->add_subcommand("approve", localized("Approve proposed transaction")); - add_standard_transaction_options(approve, "proposer@active"); + add_standard_transaction_options_plus_signing(approve, "proposer@active"); approve->add_option("proposer", proposer, localized("The proposer name (string)"))->required(); approve->add_option("proposal_name", proposal_name, localized("The proposal name (string)"))->required(); approve->add_option("permissions", perm, localized("The JSON string of filename defining approving permissions"))->required(); @@ -4007,7 +4035,7 @@ int main( int argc, char** argv ) { // multisig unapprove auto unapprove = msig->add_subcommand("unapprove", localized("Unapprove proposed transaction")); - add_standard_transaction_options(unapprove, "proposer@active"); + add_standard_transaction_options_plus_signing(unapprove, "proposer@active"); unapprove->add_option("proposer", proposer, localized("The proposer name (string)"))->required(); unapprove->add_option("proposal_name", proposal_name, localized("The proposal name (string)"))->required(); unapprove->add_option("permissions", perm, localized("The JSON string of filename defining approving permissions"))->required(); @@ -4016,20 +4044,20 @@ int main( int argc, char** argv ) { // multisig invalidate string invalidator; auto invalidate = msig->add_subcommand("invalidate", localized("Invalidate all multisig approvals of an account")); - add_standard_transaction_options(invalidate, "invalidator@active"); - invalidate->add_option("invalidator", invalidator, localized("invalidator name (string)"))->required(); + add_standard_transaction_options_plus_signing(invalidate, "invalidator@active"); + invalidate->add_option("invalidator", invalidator, localized("Invalidator name (string)"))->required(); invalidate->callback([&] { auto args = fc::mutable_variant_object() ("account", invalidator); auto accountPermissions = get_account_permissions(tx_permission, {name(invalidator), config::active_name}); - send_actions({chain::action{accountPermissions, "eosio.msig"_n, "invalidate"_n, variant_to_bin( "eosio.msig"_n, "invalidate"_n, args ) }}); + send_actions({chain::action{accountPermissions, "eosio.msig"_n, "invalidate"_n, variant_to_bin( "eosio.msig"_n, "invalidate"_n, args ) }}, signing_keys_opt.get_keys()); }); // multisig cancel string canceler; auto cancel = msig->add_subcommand("cancel", localized("Cancel proposed transaction")); - add_standard_transaction_options(cancel, "canceler@active"); + add_standard_transaction_options_plus_signing(cancel, "canceler@active"); cancel->add_option("proposer", proposer, localized("The proposer name (string)"))->required(); cancel->add_option("proposal_name", proposal_name, localized("proposal name (string)"))->required(); cancel->add_option("canceler", canceler, localized("The canceler name (string)")); @@ -4050,14 +4078,14 @@ int main( int argc, char** argv ) { ("proposal_name", proposal_name) ("canceler", canceler); - send_actions({chain::action{accountPermissions, "eosio.msig"_n, "cancel"_n, variant_to_bin( "eosio.msig"_n, "cancel"_n, args ) }}); + send_actions({chain::action{accountPermissions, "eosio.msig"_n, "cancel"_n, variant_to_bin( "eosio.msig"_n, "cancel"_n, args ) }}, signing_keys_opt.get_keys()); } ); // multisig exec string executer; auto exec = msig->add_subcommand("exec", localized("Execute proposed transaction")); - add_standard_transaction_options(exec, "executer@active"); + add_standard_transaction_options_plus_signing(exec, "executer@active"); exec->add_option("proposer", proposer, localized("The proposer name (string)"))->required(); exec->add_option("proposal_name", proposal_name, localized("The proposal name (string)"))->required(); exec->add_option("executer", executer, localized("The account paying for execution (string)")); @@ -4079,7 +4107,7 @@ int main( int argc, char** argv ) { ("proposal_name", proposal_name) ("executer", executer); - send_actions({chain::action{accountPermissions, "eosio.msig"_n, "exec"_n, variant_to_bin( "eosio.msig"_n, "exec"_n, args ) }}); + send_actions({chain::action{accountPermissions, "eosio.msig"_n, "exec"_n, variant_to_bin( "eosio.msig"_n, "exec"_n, args ) }}, signing_keys_opt.get_keys()); } ); @@ -4092,7 +4120,7 @@ int main( int argc, char** argv ) { executer = ""; string trx_to_exec; auto wrap_exec = wrap->add_subcommand("exec", localized("Execute a transaction while bypassing authorization checks")); - add_standard_transaction_options(wrap_exec, "executer@active & --contract@active"); + add_standard_transaction_options_plus_signing(wrap_exec, "executer@active & --contract@active"); wrap_exec->add_option("executer", executer, localized("Account executing the transaction and paying for the deferred transaction RAM"))->required(); wrap_exec->add_option("transaction", trx_to_exec, localized("The JSON string or filename defining the transaction to execute"))->required(); wrap_exec->add_option("--contract,-c", wrap_con, localized("The account which controls the wrap contract")); @@ -4109,7 +4137,7 @@ int main( int argc, char** argv ) { ("executer", executer ) ("trx", trx_var); - send_actions({chain::action{accountPermissions, name(wrap_con), "exec"_n, variant_to_bin( name(wrap_con), "exec"_n, args ) }}); + send_actions({chain::action{accountPermissions, name(wrap_con), "exec"_n, variant_to_bin( name(wrap_con), "exec"_n, args ) }}, signing_keys_opt.get_keys()); }); // system subcommand diff --git a/tests/Cluster.py b/tests/Cluster.py index 9c3bbb685c..df1bf1b499 100644 --- a/tests/Cluster.py +++ b/tests/Cluster.py @@ -1109,7 +1109,7 @@ def bootstrap(self, biosNode, totalNodes, prodCount, totalProducers, pfSetupPoli wasmFile="%s.wasm" % (contract) abiFile="%s.abi" % (contract) Utils.Print("Publish %s contract" % (contract)) - trans=biosNode.publishContract(eosioAccount.name, contractDir, wasmFile, abiFile, waitForTransBlock=True) + trans=biosNode.publishContract(eosioAccount, contractDir, wasmFile, abiFile, waitForTransBlock=True) if trans is None: Utils.Print("ERROR: Failed to publish contract %s." % (contract)) return None @@ -1238,7 +1238,7 @@ def bootstrap(self, biosNode, totalNodes, prodCount, totalProducers, pfSetupPoli wasmFile="%s.wasm" % (contract) abiFile="%s.abi" % (contract) Utils.Print("Publish %s contract" % (contract)) - trans=biosNode.publishContract(eosioTokenAccount.name, contractDir, wasmFile, abiFile, waitForTransBlock=True) + trans=biosNode.publishContract(eosioTokenAccount, contractDir, wasmFile, abiFile, waitForTransBlock=True) if trans is None: Utils.Print("ERROR: Failed to publish contract %s." % (contract)) return None @@ -1294,7 +1294,7 @@ def bootstrap(self, biosNode, totalNodes, prodCount, totalProducers, pfSetupPoli wasmFile="%s.wasm" % (contract) abiFile="%s.abi" % (contract) Utils.Print("Publish %s contract" % (contract)) - trans=biosNode.publishContract(eosioAccount.name, contractDir, wasmFile, abiFile, waitForTransBlock=True) + trans=biosNode.publishContract(eosioAccount, contractDir, wasmFile, abiFile, waitForTransBlock=True) if trans is None: Utils.Print("ERROR: Failed to publish contract %s." % (contract)) return None diff --git a/tests/Node.py b/tests/Node.py index bc133bedd1..63a65b1c0e 100644 --- a/tests/Node.py +++ b/tests/Node.py @@ -372,10 +372,11 @@ def isTransFinalized(self, transId): # Create & initialize account and return creation transactions. Return transaction json object - def createInitializeAccount(self, account, creatorAccount, stakedDeposit=1000, waitForTransBlock=False, stakeNet=100, stakeCPU=100, buyRAM=10000, exitOnError=False, additionalArgs=''): + def createInitializeAccount(self, account, creatorAccount, stakedDeposit=1000, waitForTransBlock=False, stakeNet=100, stakeCPU=100, buyRAM=10000, exitOnError=False, sign=False, additionalArgs=''): + signStr = Node.__sign_str(sign, [ creatorAccount.activePublicKey ]) cmdDesc="system newaccount" - cmd='%s -j %s %s %s %s --stake-net "%s %s" --stake-cpu "%s %s" --buy-ram "%s %s" %s' % ( - cmdDesc, creatorAccount.name, account.name, account.ownerPublicKey, + cmd='%s -j %s %s %s %s %s --stake-net "%s %s" --stake-cpu "%s %s" --buy-ram "%s %s" %s' % ( + cmdDesc, signStr, creatorAccount.name, account.name, account.ownerPublicKey, account.activePublicKey, stakeNet, CORE_SYMBOL, stakeCPU, CORE_SYMBOL, buyRAM, CORE_SYMBOL, additionalArgs) msg="(creator account=%s, account=%s)" % (creatorAccount.name, account.name); trans=self.processCleosCmd(cmd, cmdDesc, silentErrors=False, exitOnError=exitOnError, exitMsg=msg) @@ -389,12 +390,13 @@ def createInitializeAccount(self, account, creatorAccount, stakedDeposit=1000, w return self.waitForTransBlockIfNeeded(trans, waitForTransBlock, exitOnError=exitOnError) - def createAccount(self, account, creatorAccount, stakedDeposit=1000, waitForTransBlock=False, exitOnError=False): + def createAccount(self, account, creatorAccount, stakedDeposit=1000, waitForTransBlock=False, exitOnError=False, sign=False): """Create account and return creation transactions. Return transaction json object. waitForTransBlock: wait on creation transaction id to appear in a block.""" + signStr = Node.__sign_str(sign, [ creatorAccount.activePublicKey ]) cmdDesc="create account" - cmd="%s -j %s %s %s %s" % ( - cmdDesc, creatorAccount.name, account.name, account.ownerPublicKey, account.activePublicKey) + cmd="%s -j %s %s %s %s %s" % ( + cmdDesc, signStr, creatorAccount.name, account.name, account.ownerPublicKey, account.activePublicKey) msg="(creator account=%s, account=%s)" % (creatorAccount.name, account.name); trans=self.processCleosCmd(cmd, cmdDesc, silentErrors=False, exitOnError=exitOnError, exitMsg=msg) self.trackCmdTransaction(trans) @@ -524,7 +526,7 @@ def __call__(self): def waitForIrreversibleBlock(self, blockNum, timeout=None, reportInterval=None): return self.waitForBlock(blockNum, timeout=timeout, blockType=BlockType.lib, reportInterval=reportInterval) - def __transferFundsCmdArr(self, source, destination, amountStr, memo, force, retry): + def __transferFundsCmdArr(self, source, destination, amountStr, memo, force, retry, sign): assert isinstance(amountStr, str) assert(source) assert(isinstance(source, Account)) @@ -534,6 +536,13 @@ def __transferFundsCmdArr(self, source, destination, amountStr, memo, force, ret cmd="%s %s -v transfer --expiration 90 %s -j %s %s" % ( Utils.EosClientPath, self.eosClientArgs(), self.getRetryCmdArg(retry), source.name, destination.name) cmdArr=cmd.split() + # not using __sign_str, since cmdArr messes up the string + if sign: + cmdArr.append("--sign-with") + cmdArr.append("[ \"%s\" ]" % (source.activePublicKey)) + + cmdArr.append(source.name) + cmdArr.append(destination.name) cmdArr.append(amountStr) cmdArr.append(memo) if force: @@ -543,8 +552,8 @@ def __transferFundsCmdArr(self, source, destination, amountStr, memo, force, ret return cmdArr # Trasfer funds. Returns "transfer" json return object - def transferFunds(self, source, destination, amountStr, memo="memo", force=False, waitForTransBlock=False, exitOnError=True, reportStatus=True, retry=None): - cmdArr = self.__transferFundsCmdArr(source, destination, amountStr, memo, force, retry) + def transferFunds(self, source, destination, amountStr, memo="memo", force=False, waitForTransBlock=False, exitOnError=True, reportStatus=True, retry=None, sign=False): + cmdArr = self.__transferFundsCmdArr(source, destination, amountStr, memo, force, retry, sign) trans=None start=time.perf_counter() try: @@ -569,8 +578,8 @@ def transferFunds(self, source, destination, amountStr, memo="memo", force=False return self.waitForTransBlockIfNeeded(trans, waitForTransBlock, exitOnError=exitOnError) # Trasfer funds. Returns (popen, cmdArr) for checkDelayedOutput - def transferFundsAsync(self, source, destination, amountStr, memo="memo", force=False, exitOnError=True, retry=None): - cmdArr = self.__transferFundsCmdArr(source, destination, amountStr, memo, force, retry) + def transferFundsAsync(self, source, destination, amountStr, memo="memo", force=False, exitOnError=True, retry=None, sign=False): + cmdArr = self.__transferFundsCmdArr(source, destination, amountStr, memo, force, retry, sign) start=time.perf_counter() try: popen=Utils.delayedCheckOutput(cmdArr) @@ -744,8 +753,9 @@ def getAccountCodeHash(self, account): return None # publish contract and return transaction as json object - def publishContract(self, account, contractDir, wasmFile, abiFile, waitForTransBlock=False, shouldFail=False): - cmd="%s %s -v set contract -j %s %s" % (Utils.EosClientPath, self.eosClientArgs(), account, contractDir) + def publishContract(self, account, contractDir, wasmFile, abiFile, waitForTransBlock=False, shouldFail=False, sign=False): + signStr = Node.__sign_str(sign, [ account.activePublicKey ]) + cmd="%s %s -v set contract -j %s %s %s" % (Utils.EosClientPath, self.eosClientArgs(), signStr, account.name, contractDir) cmd += "" if wasmFile is None else (" "+ wasmFile) cmd += "" if abiFile is None else (" " + abiFile) if Utils.Debug: Utils.Print("cmd: %s" % (cmd)) @@ -846,9 +856,13 @@ def pushTransaction(self, trans, opts="", silentErrors=False, permissions=None): return (False, msg) # returns tuple with transaction execution status and transaction - def pushMessage(self, account, action, data, opts, silentErrors=False): + def pushMessage(self, account, action, data, opts, silentErrors=False, signatures=None): cmd="%s %s push action -j %s %s" % (Utils.EosClientPath, self.eosClientArgs(), account, action) cmdArr=cmd.split() + # not using __sign_str, since cmdArr messes up the string + if signatures is not None: + cmdArr.append("--sign-with") + cmdArr.append("[ \"%s\" ]" % ("\", \"".join(signatures))) if data is not None: cmdArr.append(data) if opts is not None: @@ -870,55 +884,72 @@ def pushMessage(self, account, action, data, opts, silentErrors=False): Utils.Print("ERROR: Exception during push message. cmd Duration=%.3f sec. %s" % (end - start, msg)) return (False, msg) - def setPermission(self, account, code, pType, requirement, waitForTransBlock=False, exitOnError=False): + @staticmethod + def __sign_str(sign, keys): + assert(isinstance(sign, bool)) + assert(isinstance(keys, list)) + if not sign: + return "" + + return "--sign-with '[ \"" + "\", \"".join(keys) + "\" ]'" + + def setPermission(self, account, code, pType, requirement, waitForTransBlock=False, exitOnError=False, sign=False): + assert(isinstance(account, Account)) + assert(isinstance(code, Account)) + signStr = Node.__sign_str(sign, [ account.activePublicKey ]) + Utils.Print("REMOVE signStr: <%s>" % (signStr)) cmdDesc="set action permission" - cmd="%s -j %s %s %s %s" % (cmdDesc, account, code, pType, requirement) + cmd="%s -j %s %s %s %s %s" % (cmdDesc, signStr, account.name, code.name, pType, requirement) trans=self.processCleosCmd(cmd, cmdDesc, silentErrors=False, exitOnError=exitOnError) self.trackCmdTransaction(trans) return self.waitForTransBlockIfNeeded(trans, waitForTransBlock, exitOnError=exitOnError) - def delegatebw(self, fromAccount, netQuantity, cpuQuantity, toAccount=None, transferTo=False, waitForTransBlock=False, exitOnError=False, reportStatus=True): + def delegatebw(self, fromAccount, netQuantity, cpuQuantity, toAccount=None, transferTo=False, waitForTransBlock=False, exitOnError=False, reportStatus=True, sign=False): if toAccount is None: toAccount=fromAccount + signStr = Node.__sign_str(sign, [ fromAccount.activePublicKey ]) cmdDesc="system delegatebw" transferStr="--transfer" if transferTo else "" - cmd="%s -j %s %s \"%s %s\" \"%s %s\" %s" % ( - cmdDesc, fromAccount.name, toAccount.name, netQuantity, CORE_SYMBOL, cpuQuantity, CORE_SYMBOL, transferStr) + cmd="%s -j %s %s %s \"%s %s\" \"%s %s\" %s" % ( + cmdDesc, signStr, fromAccount.name, toAccount.name, netQuantity, CORE_SYMBOL, cpuQuantity, CORE_SYMBOL, transferStr) msg="fromAccount=%s, toAccount=%s" % (fromAccount.name, toAccount.name); trans=self.processCleosCmd(cmd, cmdDesc, exitOnError=exitOnError, exitMsg=msg) self.trackCmdTransaction(trans, reportStatus=reportStatus) return self.waitForTransBlockIfNeeded(trans, waitForTransBlock, exitOnError=exitOnError) - def undelegatebw(self, fromAccount, netQuantity, cpuQuantity, toAccount=None, waitForTransBlock=False, exitOnError=False): + def undelegatebw(self, fromAccount, netQuantity, cpuQuantity, toAccount=None, waitForTransBlock=False, exitOnError=False, sign=False): if toAccount is None: toAccount=fromAccount + signStr = Node.__sign_str(sign, [ fromAccount.activePublicKey ]) cmdDesc="system undelegatebw" - cmd="%s -j %s %s \"%s %s\" \"%s %s\"" % ( - cmdDesc, fromAccount.name, toAccount.name, netQuantity, CORE_SYMBOL, cpuQuantity, CORE_SYMBOL) + cmd="%s -j %s %s %s \"%s %s\" \"%s %s\"" % ( + cmdDesc, signStr, fromAccount.name, toAccount.name, netQuantity, CORE_SYMBOL, cpuQuantity, CORE_SYMBOL) msg="fromAccount=%s, toAccount=%s" % (fromAccount.name, toAccount.name); trans=self.processCleosCmd(cmd, cmdDesc, exitOnError=exitOnError, exitMsg=msg) self.trackCmdTransaction(trans) return self.waitForTransBlockIfNeeded(trans, waitForTransBlock, exitOnError=exitOnError) - def regproducer(self, producer, url, location, waitForTransBlock=False, exitOnError=False): + def regproducer(self, producer, url, location, waitForTransBlock=False, exitOnError=False, sign=False): + signStr = Node.__sign_str(sign, [ producer.activePublicKey ]) cmdDesc="system regproducer" - cmd="%s -j %s %s %s %s" % ( - cmdDesc, producer.name, producer.activePublicKey, url, location) + cmd="%s -j %s %s %s %s %s" % ( + cmdDesc, signStr, producer.name, producer.activePublicKey, url, location) msg="producer=%s" % (producer.name); trans=self.processCleosCmd(cmd, cmdDesc, exitOnError=exitOnError, exitMsg=msg) self.trackCmdTransaction(trans) return self.waitForTransBlockIfNeeded(trans, waitForTransBlock, exitOnError=exitOnError) - def vote(self, account, producers, waitForTransBlock=False, exitOnError=False): + def vote(self, account, producers, waitForTransBlock=False, exitOnError=False, sign=False): + signStr = Node.__sign_str(sign, [ account.activePublicKey ]) cmdDesc = "system voteproducer prods" - cmd="%s -j %s %s" % ( - cmdDesc, account.name, " ".join(producers)) + cmd="%s -j %s %s %s" % ( + cmdDesc, signStr, account.name, " ".join(producers)) msg="account=%s, producers=[ %s ]" % (account.name, ", ".join(producers)); trans=self.processCleosCmd(cmd, cmdDesc, exitOnError=exitOnError, exitMsg=msg) self.trackCmdTransaction(trans) diff --git a/tests/consensus-validation-malicious-producers.py b/tests/consensus-validation-malicious-producers.py new file mode 100755 index 0000000000..16b98f8408 --- /dev/null +++ b/tests/consensus-validation-malicious-producers.py @@ -0,0 +1,398 @@ +#!/usr/bin/env python3 + +import testUtils + +import argparse +import signal +from collections import namedtuple +import os +import shutil + +############################################################### +# Test for validating consensus based block production. We introduce malicious producers which +# reject all transactions. +# We have three test scenarios: +# - No malicious producers. Transactions should be incorporated into the chain. +# - Minority malicious producers (less than a third producer count). Transactions will get incorporated +# into the chain as majority appoves the transactions. +# - Majority malicious producer count (greater than a third producer count). Transactions won't get +# incorporated into the chain as majority rejects the transactions. +############################################################### + + +Print=testUtils.Utils.Print +errorExit=Utils.errorExit + +StagedNodeInfo=namedtuple("StagedNodeInfo", "config logging") + + +logging00="""{ + "includes": [], + "appenders": [{ + "name": "stderr", + "type": "console", + "args": { + "stream": "std_error", + "level_colors": [{ + "level": "debug", + "color": "green" + },{ + "level": "warn", + "color": "brown" + },{ + "level": "error", + "color": "red" + } + ] + }, + "enabled": true + },{ + "name": "stdout", + "type": "console", + "args": { + "stream": "std_out", + "level_colors": [{ + "level": "debug", + "color": "green" + },{ + "level": "warn", + "color": "brown" + },{ + "level": "error", + "color": "red" + } + ] + }, + "enabled": true + },{ + "name": "net", + "type": "gelf", + "args": { + "endpoint": "10.160.11.21:12201", + "host": "testnet_00" + }, + "enabled": true + } + ], + "loggers": [{ + "name": "default", + "level": "debug", + "enabled": true, + "additivity": false, + "appenders": [ + "stderr", + "net" + ] + } + ] +}""" + +config00="""genesis-json = ./genesis.json +block-log-dir = blocks +readonly = 0 +send-whole-blocks = true +shared-file-dir = blockchain +shared-file-size = 8192 +http-server-address = 127.0.0.1:8888 +p2p-listen-endpoint = 0.0.0.0:9876 +p2p-server-address = localhost:9876 +allowed-connection = any +p2p-peer-address = localhost:9877 +required-participation = true +private-key = ["EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV","5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3"] +producer-name = initu +plugin = eosio::producer_plugin +plugin = eosio::chain_api_plugin +plugin = eosio::history_plugin +plugin = eosio::history_api_plugin""" + + +config01="""genesis-json = ./genesis.json +block-log-dir = blocks +readonly = 0 +send-whole-blocks = true +shared-file-dir = blockchain +shared-file-size = 8192 +http-server-address = 127.0.0.1:8889 +p2p-listen-endpoint = 0.0.0.0:9877 +p2p-server-address = localhost:9877 +allowed-connection = any +p2p-peer-address = localhost:9876 +required-participation = true +private-key = ["EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV","5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3"] +producer-name = defproducerb +plugin = eosio::producer_plugin +plugin = eosio::chain_api_plugin +plugin = eosio::history_plugin +plugin = eosio::history_api_plugin""" + + +producers="""producer-name = defproducerd +producer-name = defproducerf +producer-name = defproducerh +producer-name = defproducerj +producer-name = defproducerl +producer-name = defproducern +producer-name = defproducerp +producer-name = defproducerr +producer-name = defproducert +producer-name = defproducera +producer-name = defproducerc +producer-name = defproducere +producer-name = defproducerg +producer-name = defproduceri +producer-name = defproducerk +producer-name = defproducerm +producer-name = defproducero +producer-name = defproducerq +producer-name = defproducers""" + +zeroExecTime="trans-execution-time = 0" + +def getNoMaliciousStagedNodesInfo(): + stagedNodesInfo=[] + myConfig00=config00 + stagedNodesInfo.append(StagedNodeInfo(myConfig00, logging00)) + myConfig01=config01+"\n"+producers + stagedNodesInfo.append(StagedNodeInfo(myConfig01, logging00)) + return stagedNodesInfo + +def getMinorityMaliciousProducerStagedNodesInfo(): + stagedNodesInfo=[] + myConfig00=config00+"\n"+producers + stagedNodesInfo.append(StagedNodeInfo(myConfig00, logging00)) + myConfig01=config01+"\n"+zeroExecTime + stagedNodesInfo.append(StagedNodeInfo(myConfig01, logging00)) + return stagedNodesInfo + +def getMajorityMaliciousProducerStagedNodesInfo(): + stagedNodesInfo=[] + myConfig00=config00 + stagedNodesInfo.append(StagedNodeInfo(myConfig00, logging00)) + myConfig01=config01+"\n"+producers+"\n"+zeroExecTime + stagedNodesInfo.append(StagedNodeInfo(myConfig01, logging00)) + return stagedNodesInfo + +stagingDir="staging" +def stageScenario(stagedNodeInfos): + assert(stagedNodeInfos != None) + assert(len(stagedNodeInfos) > 1) + + os.makedirs(stagingDir) + count=0 + for stagedNodeInfo in stagedNodeInfos: + configPath=os.path.join(stagingDir, "etc/eosio/node_%02d" % (count)) + os.makedirs(configPath) + with open(os.path.join(configPath, "config.ini"), "w") as textFile: + print(stagedNodeInfo.config,file=textFile) + with open(os.path.join(configPath, "logging.json"), "w") as textFile: + print(stagedNodeInfo.logging,file=textFile) + count += 1 + return + +def cleanStaging(): + os.path.exists(stagingDir) and shutil.rmtree(stagingDir) + +def error(msg="", errorCode=1): + Print("ERROR:", msg) + +parser = argparse.ArgumentParser() +tests=[1,2,3] + +parser.add_argument("-t", "--tests", type=str, help="1|2|3 1=run no malicious producers test, 2=minority malicious, 3=majority malicious.", default=None) +parser.add_argument("-w", type=int, help="system wait time", default=testUtils.Utils.systemWaitTimeout) +parser.add_argument("-v", help="verbose logging", action='store_true') +parser.add_argument("--dump-error-details", + help="Upon error print etc/eosio/node_*/config.ini and var/lib/node_*/stderr.log to stdout", + action='store_true') +parser.add_argument("--keep-logs", help="Don't delete var/lib/node_* folders upon test completion", + action='store_true') +parser.add_argument("--not-noon", help="This is not the Noon branch.", action='store_true') +parser.add_argument("--dont-kill", help="Leave cluster running after test finishes", action='store_true') + +args = parser.parse_args() +testsArg=args.tests +debug=args.v +waitTimeout=args.w +dumpErrorDetails=args.dump-error-details +keepLogs=args.keep-logs +amINoon=not args.not_noon +killEosInstances= not args.dont-kill +killWallet= not args.dont-kill + +testUtils.Utils.Debug=debug + +assert (testsArg is None or testsArg == "1" or testsArg == "2" or testsArg == "3") +if testsArg is not None: + tests=[int(testsArg)] + +testUtils.Utils.setSystemWaitTimeout(waitTimeout) +testUtils.Utils.iAmNotNoon() + +def myTest(transWillEnterBlock): + testSuccessful=False + + cluster=testUtils.Cluster(walletd=True, staging=True) + walletMgr=testUtils.WalletMgr(True) + + try: + cluster.killall() + cluster.cleanup() + walletMgr.killall() + walletMgr.cleanup() + + pnodes=2 + total_nodes=pnodes + topo="mesh" + delay=0 + Print("Stand up cluster") + if cluster.launch(pnodes=pnodes, totalNodes=total_nodes, topo=topo, delay=delay) is False: + error("Failed to stand up eos cluster.") + return False + + accounts=testUtils.Cluster.createAccountKeys(1) + if accounts is None: + error("FAILURE - create keys") + return False + currencyAccount=accounts[0] + currencyAccount.name="currency0000" + + testWalletName="test" + Print("Creating wallet \"%s\"." % (testWalletName)) + testWallet=walletMgr.create(testWalletName) + + for account in accounts: + Print("Importing keys for account %s into wallet %s." % (account.name, testWallet.name)) + if not walletMgr.importKey(account, testWallet): + error("Failed to import key for account %s" % (account.name)) + return False + + node=cluster.getNode(0) + node2=cluster.getNode(1) + + defproduceraAccount=testUtils.Cluster.defproduceraAccount + + Print("Importing keys for account %s into wallet %s." % (defproduceraAccount.name, testWallet.name)) + if not walletMgr.importKey(defproduceraAccount, testWallet): + error("Failed to import key for account %s" % (defproduceraAccount.name)) + return False + + Print("Create new account %s via %s" % (currencyAccount.name, defproduceraAccount.name)) + transId=node.createAccount(currencyAccount, defproduceraAccount, stakedDeposit=5000, waitForTransBlock=True) + if transId is None: + error("Failed to create account %s" % (currencyAccount.name)) + return False + + wasmFile="currency.wasm" + abiFile="currency.abi" + Print("Publish contract") + trans=node.publishContract(currencyAccount, wasmFile, abiFile, waitForTransBlock=True) + if trans is None: + error("Failed to publish contract.") + return False + + Print("push transfer action to currency0000 contract") + contract="currency0000" + action="transfer" + data="{\"from\":\"currency0000\",\"to\":\"defproducera\",\"quantity\":" + if amINoon: + data +="\"00.0050 CUR\",\"memo\":\"test\"}" + else: + data +="50}" + opts="--permission currency0000@active" + if not amINoon: + opts += " --scope currency0000,defproducera" + + trans=node.pushMessage(contract, action, data, opts, silentErrors=True) + transInBlock=False + if not trans[0]: + # On slower systems e.g Travis the transaction rejection can happen immediately + # We want to handle fast and slow failures. + if "allocated processing time was exceeded" in trans[1]: + Print("Push message transaction immediately failed.") + else: + error("Exception in push message. %s" % (trans[1])) + return False + + else: + transId=testUtils.Node.getTransId(trans[1]) + + Print("verify transaction exists") + if not node2.waitForTransInBlock(transId): + error("Transaction never made it to node2") + return False + + Print("Get details for transaction %s" % (transId)) + transaction=node2.getTransaction(transId, exitOnError=True) + signature=transaction["transaction"]["signatures"][0] + + blockNum=int(transaction["transaction"]["ref_block_num"]) + blockNum += 1 + Print("Our transaction is in block %d" % (blockNum)) + + block=node2.getBlock(blockNum, exitOnError=True) + cycles=block["cycles"] + if len(cycles) > 0: + blockTransSignature=cycles[0][0]["user_input"][0]["signatures"][0] + # Print("Transaction signature: %s\nBlock transaction signature: %s" % + # (signature, blockTransSignature)) + transInBlock=(signature == blockTransSignature) + + if transWillEnterBlock: + if not transInBlock: + error("Transaction did not enter the chain.") + return False + else: + Print("SUCCESS: Transaction1 entered in the chain.") + elif not transWillEnterBlock: + if transInBlock: + error("Transaction entered the chain.") + return False + else: + Print("SUCCESS: Transaction2 did not enter the chain.") + + testSuccessful=True + finally: + if not testSuccessful and dumpErrorDetails: + cluster.dumpErrorDetails() + walletMgr.dumpErrorDetails() + Print("== Errors see above ==") + + if killEosInstances: + Print("Shut down the cluster%s" % (" and cleanup." if (testSuccessful and not keepLogs) else ".")) + cluster.killall() + walletMgr.killall() + if testSuccessful and not keepLogs: + Print("Cleanup cluster and wallet data.") + cluster.cleanup() + walletMgr.cleanup() + + return True + + +try: + if 1 in tests: + Print("Cluster with no malicious producers. All producers expected to approve transaction. Hence transaction is expected to enter the chain.") + cleanStaging() + stageScenario(getNoMaliciousStagedNodesInfo()) + if not myTest(True): + exit(1) + + if 2 in tests: + Print("\nCluster with minority(1) malicious nodes. Majority producers expected to approve transaction. Hence transaction is expected to enter the chain.") + cleanStaging() + stageScenario(getMinorityMaliciousProducerStagedNodesInfo()) + if not myTest(True): + exit(1) + + if 3 in tests: + Print("\nCluster with majority(20) malicious nodes. Majority producers expected to block transaction. Hence transaction is not expected to enter the chain.") + cleanStaging() + stageScenario(getMajorityMaliciousProducerStagedNodesInfo()) + if not myTest(False): + exit(1) + +finally: + cleanStaging() + +exit(0) diff --git a/tests/nodeos_run_test.py b/tests/nodeos_run_test.py index 7324891347..4f96715577 100755 --- a/tests/nodeos_run_test.py +++ b/tests/nodeos_run_test.py @@ -309,7 +309,7 @@ wasmFile="eosio.token.wasm" abiFile="eosio.token.abi" Print("Publish contract") - trans=node.publishContract(currencyAccount.name, contractDir, wasmFile, abiFile, waitForTransBlock=True) + trans=node.publishContract(currencyAccount, contractDir, wasmFile, abiFile, waitForTransBlock=True) if trans is None: cmdError("%s set contract currency1111" % (ClientName)) errorExit("Failed to publish contract.") @@ -576,12 +576,13 @@ errorExit("Failed to lock wallet %s" % (defproduceraWallet.name)) + simpleDB = Account("simpledb") contractDir="contracts/simpledb" wasmFile="simpledb.wasm" abiFile="simpledb.abi" Print("Setting simpledb contract without simpledb account was causing core dump in %s." % (ClientName)) Print("Verify %s generates an error, but does not core dump." % (ClientName)) - retMap=node.publishContract("simpledb", contractDir, wasmFile, abiFile, shouldFail=True) + retMap=node.publishContract(simpleDB, contractDir, wasmFile, abiFile, shouldFail=True) if retMap is None: errorExit("Failed to publish, but should have returned a details map") if retMap["returncode"] == 0 or retMap["returncode"] == 139: # 139 SIGSEGV @@ -590,14 +591,13 @@ Print("Test successful, %s returned error code: %d" % (ClientName, retMap["returncode"])) Print("set permission") - code="currency1111" pType="transfer" requirement="active" - trans=node.setPermission(testeraAccount.name, code, pType, requirement, waitForTransBlock=True, exitOnError=True) + trans=node.setPermission(testeraAccount, currencyAccount, pType, requirement, waitForTransBlock=True, exitOnError=True) Print("remove permission") requirement="null" - trans=node.setPermission(testeraAccount.name, code, pType, requirement, waitForTransBlock=True, exitOnError=True) + trans=node.setPermission(testeraAccount, currencyAccount, pType, requirement, waitForTransBlock=True, exitOnError=True) Print("Locking all wallets.") if not walletMgr.lockAllWallets(): diff --git a/tests/nodeos_under_min_avail_ram.py b/tests/nodeos_under_min_avail_ram.py index 04c85d0cf9..42603e25a9 100755 --- a/tests/nodeos_under_min_avail_ram.py +++ b/tests/nodeos_under_min_avail_ram.py @@ -112,7 +112,7 @@ wasmFile="integration_test.wasm" abiFile="integration_test.abi" Print("Publish contract") - trans=nodes[0].publishContract(contractAccount.name, contractDir, wasmFile, abiFile, waitForTransBlock=True) + trans=nodes[0].publishContract(contractAccount, contractDir, wasmFile, abiFile, waitForTransBlock=True) if trans is None: Utils.cmdError("%s set contract %s" % (ClientName, contractAccount.name)) errorExit("Failed to publish contract.") diff --git a/tests/p2p_network_test.py b/tests/p2p_network_test.py index 2205a4aa0a..7df694523f 100755 --- a/tests/p2p_network_test.py +++ b/tests/p2p_network_test.py @@ -149,7 +149,7 @@ wasmFile="eosio.system.wasm" abiFile="eosio.system.abi" Print("\nPush system contract %s %s" % (wasmFile, abiFile)) -trans=node0.publishContract(eosio.name, wasmFile, abiFile, waitForTransBlock=True) +trans=node0.publishContract(eosio, wasmFile, abiFile, waitForTransBlock=True) if trans is None: Utils.errorExit("Failed to publish eosio.system.") else: diff --git a/tests/prod_preactivation_test.py b/tests/prod_preactivation_test.py index 406366dacd..2fb906935e 100755 --- a/tests/prod_preactivation_test.py +++ b/tests/prod_preactivation_test.py @@ -110,7 +110,7 @@ abiFile="%s.abi" % (contract) Print("publish a new bios contract %s should fails because env.is_feature_activated unresolveable" % (contractDir)) - retMap = node0.publishContract("eosio", contractDir, wasmFile, abiFile, True, shouldFail=True) + retMap = node0.publishContract(cluster.eosioAccount, contractDir, wasmFile, abiFile, True, shouldFail=True) if retMap["output"].decode("utf-8").find("unresolveable") < 0: errorExit("bios contract not result in expected unresolveable error") @@ -150,7 +150,7 @@ time.sleep(0.6) Print("publish a new bios contract %s should fails because node1 is not producing block yet" % (contractDir)) - retMap = node0.publishContract("eosio", contractDir, wasmFile, abiFile, True, shouldFail=True) + retMap = node0.publishContract(cluster.eosioAccount, contractDir, wasmFile, abiFile, True, shouldFail=True) if retMap["output"].decode("utf-8").find("unresolveable") < 0: errorExit("bios contract not result in expected unresolveable error") @@ -167,7 +167,7 @@ errorExit("No blocks produced by node 1") time.sleep(0.6) - retMap = node0.publishContract("eosio", contractDir, wasmFile, abiFile, True) + retMap = node0.publishContract(cluster.eosioAccount, contractDir, wasmFile, abiFile, True) Print("sucessfully set new contract with new intrinsic!!!") testSuccessful=True