diff --git a/contracts/eosiolib/transaction.hpp b/contracts/eosiolib/transaction.hpp index f0e9dafc45a..362fd2fafbe 100644 --- a/contracts/eosiolib/transaction.hpp +++ b/contracts/eosiolib/transaction.hpp @@ -42,14 +42,14 @@ namespace eosio { public: transaction(time_point_sec exp = time_point_sec(now() + 60)) : transaction_header( exp ) {} - void send(uint64_t sender_id, account_name payer, bool replace_existing = false) const { + void send(const uint128_t& sender_id, account_name payer, bool replace_existing = false) const { auto serialize = pack(*this); send_deferred(sender_id, payer, serialize.data(), serialize.size(), replace_existing); } vector context_free_actions; vector actions; - extensions_type transaction_extensions; + extensions_type transaction_extensions; EOSLIB_SERIALIZE_DERIVED( transaction, transaction_header, (context_free_actions)(actions)(transaction_extensions) ) }; diff --git a/libraries/chain/apply_context.cpp b/libraries/chain/apply_context.cpp index d94cfe2e111..0f3b8557f54 100644 --- a/libraries/chain/apply_context.cpp +++ b/libraries/chain/apply_context.cpp @@ -39,7 +39,7 @@ action_trace apply_context::exec_one() privileged = a.privileged; auto native = control.find_apply_handler(receiver, act.account, act.name); if( native ) { - if( control.is_producing_block() ) { + if( trx_context.can_subjectively_fail && control.is_producing_block() ) { control.check_contract_list( receiver ); } (*native)(*this); @@ -48,7 +48,7 @@ action_trace apply_context::exec_one() if( a.code.size() > 0 && !(act.account == config::system_account_name && act.name == N(setcode) && receiver == config::system_account_name) ) { - if( control.is_producing_block() ) { + if( trx_context.can_subjectively_fail && control.is_producing_block() ) { control.check_contract_list( receiver ); } try { @@ -375,8 +375,8 @@ int apply_context::get_action( uint32_t type, uint32_t index, char* buffer, size return -1; act_ptr = &trx.actions[index]; } - - FC_ASSERT(act_ptr, "action is not found" ); + + FC_ASSERT(act_ptr, "action is not found" ); auto ps = fc::raw::pack_size( *act_ptr ); if( ps <= buffer_size ) { diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index 1924e99a9a0..b65453cc728 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -492,10 +492,14 @@ struct controller_impl { bool failure_is_subjective( const fc::exception& e ) { auto code = e.code(); - return (code == block_net_usage_exceeded::code_value) || - (code == block_cpu_usage_exceeded::code_value) || - (code == deadline_exception::code_value) || - (code == leeway_deadline_exception::code_value); + return (code == block_net_usage_exceeded::code_value) + || (code == block_cpu_usage_exceeded::code_value) + || (code == deadline_exception::code_value) + || (code == leeway_deadline_exception::code_value) + || (code == actor_whitelist_exception::code_value) + || (code == actor_blacklist_exception::code_value) + || (code == contract_whitelist_exception::code_value) + || (code == contract_blacklist_exception::code_value); } transaction_trace_ptr push_scheduled_transaction( const transaction_id_type& trxid, fc::time_point deadline, uint32_t billed_cpu_time_us ) { @@ -633,13 +637,14 @@ struct controller_impl { try { if( implicit ) { trx_context.init_for_implicit_trx(); + trx_context.can_subjectively_fail = false; } else { trx_context.init_for_input_trx( trx->packed_trx.get_unprunable_size(), trx->packed_trx.get_prunable_size(), trx->trx.signatures.size() ); } - if( !implicit && pending->_block_status == controller::block_status::incomplete ) { + if( trx_context.can_subjectively_fail && pending->_block_status == controller::block_status::incomplete ) { check_actor_list( trx_context.bill_to_accounts ); // Assumes bill_to_accounts is the set of actors authorizing the transaction } @@ -791,10 +796,13 @@ struct controller_impl { auto& pt = receipt.trx.get(); auto mtrx = std::make_shared(pt); push_transaction( mtrx, fc::time_point::maximum(), false, receipt.cpu_usage_us ); - } - else if( receipt.trx.contains() ) { + } else if( receipt.trx.contains() ) { push_scheduled_transaction( receipt.trx.get(), fc::time_point::maximum(), receipt.cpu_usage_us ); } + const transaction_receipt_header& r = pending->_pending_block_state->block->transactions.back(); + EOS_ASSERT( r == static_cast(receipt), + block_validate_exception, "receipt does not match", + ("producer_receipt", receipt)("validator_receipt", pending->_pending_block_state->block->transactions.back()) ); } finalize_block(); diff --git a/libraries/chain/include/eosio/chain/block.hpp b/libraries/chain/include/eosio/chain/block.hpp index 2cd4b261700..0e85b167df8 100644 --- a/libraries/chain/include/eosio/chain/block.hpp +++ b/libraries/chain/include/eosio/chain/block.hpp @@ -21,6 +21,10 @@ namespace eosio { namespace chain { transaction_receipt_header():status(hard_fail){} transaction_receipt_header( status_enum s ):status(s){} + friend inline bool operator ==( const transaction_receipt_header& lhs, const transaction_receipt_header& rhs ) { + return std::tie(lhs.status, lhs.cpu_usage_us, lhs.net_usage_words) == std::tie(rhs.status, rhs.cpu_usage_us, rhs.net_usage_words); + } + fc::enum_type status; uint32_t cpu_usage_us; ///< total billed CPU usage (microseconds) fc::unsigned_int net_usage_words; ///< total billed NET usage, so we can reconstruct resource state when skipping context free data... hard failures... diff --git a/libraries/chain/include/eosio/chain/transaction_context.hpp b/libraries/chain/include/eosio/chain/transaction_context.hpp index 008c341a179..941ce2cd3d4 100644 --- a/libraries/chain/include/eosio/chain/transaction_context.hpp +++ b/libraries/chain/include/eosio/chain/transaction_context.hpp @@ -75,6 +75,7 @@ namespace eosio { namespace chain { fc::microseconds delay; bool is_input = false; bool apply_context_free = true; + bool can_subjectively_fail = true; fc::time_point deadline = fc::time_point::maximum(); fc::microseconds leeway = fc::microseconds(3000); diff --git a/libraries/testing/include/eosio/testing/tester.hpp b/libraries/testing/include/eosio/testing/tester.hpp index 194d3383ee9..134f663bc1c 100644 --- a/libraries/testing/include/eosio/testing/tester.hpp +++ b/libraries/testing/include/eosio/testing/tester.hpp @@ -201,6 +201,9 @@ namespace eosio { namespace testing { vector get_row_by_account( uint64_t code, uint64_t scope, uint64_t table, const account_name& act ); + map get_last_produced_block_map()const { return last_produced_block; }; + void set_last_produced_block_map( const map& lpb ) { last_produced_block = lpb; } + static vector to_uint8_vector(const string& s); static vector to_uint8_vector(uint64_t x); @@ -303,7 +306,8 @@ namespace eosio { namespace testing { public: virtual ~validating_tester() { try { - produce_block(); + if( num_blocks_to_producer_before_shutdown > 0 ) + produce_blocks( num_blocks_to_producer_before_shutdown ); BOOST_REQUIRE_EQUAL( validate(), true ); } catch( const fc::exception& e ) { wdump((e.to_detail_string())); @@ -385,7 +389,8 @@ namespace eosio { namespace testing { return ok; } - unique_ptr validating_node; + unique_ptr validating_node; + uint32_t num_blocks_to_producer_before_shutdown = 0; }; /** diff --git a/scripts/abigen.sh b/scripts/abigen.sh new file mode 100755 index 00000000000..eb5cce98357 --- /dev/null +++ b/scripts/abigen.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +if (( $# < 3 )); then + echo "$0 SOURCE_DIR BUILD_DIR ARGS..." + exit 0 +fi + +function get_absolute_dir_path() { + pushd $1 > /dev/null + echo $PWD + popd > /dev/null +} + +SOURCE_DIR=$(get_absolute_dir_path $1) + +BUILD_DIR=$2 + +shift; shift + +#echo "Source directory: ${SOURCE_DIR}" + +eval "${BUILD_DIR}/programs/eosio-abigen/eosio-abigen -extra-arg=--std=c++14 -extra-arg=--target=wasm32 -extra-arg=\"-I${SOURCE_DIR}/contracts/libc++/upstream/include\" -extra-arg=\"-I${SOURCE_DIR}/contracts/musl/upstream/include\" -extra-arg=\"-I${SOURCE_DIR}/externals/magic_get/include\" -extra-arg=\"-I${SOURCE_DIR}/contracts\" $@" diff --git a/unittests/CMakeLists.txt b/unittests/CMakeLists.txt index 7eccb70e200..aaa0a64da40 100644 --- a/unittests/CMakeLists.txt +++ b/unittests/CMakeLists.txt @@ -12,8 +12,11 @@ link_directories(${LLVM_LIBRARY_DIR}) set( CMAKE_CXX_STANDARD 14 ) +add_subdirectory(contracts) + include_directories("${CMAKE_BINARY_DIR}/contracts") include_directories("${CMAKE_SOURCE_DIR}/contracts") +include_directories("${CMAKE_BINARY_DIR}/unittests/contracts") include_directories("${CMAKE_SOURCE_DIR}/unittests/contracts") include_directories("${CMAKE_SOURCE_DIR}/libraries/testing/include") diff --git a/unittests/contracts/CMakeLists.txt b/unittests/contracts/CMakeLists.txt new file mode 100644 index 00000000000..24b1890d9a6 --- /dev/null +++ b/unittests/contracts/CMakeLists.txt @@ -0,0 +1,7 @@ +# will be implictly used for any compilation unit if not overrided by SYSTEM_INCLUDE_FOLDERS parameter +# these directories go as -isystem to avoid warnings from code of third-party libraries +set(DEFAULT_SYSTEM_INCLUDE_FOLDERS ${CMAKE_SOURCE_DIR}/contracts/libc++/upstream/include ${CMAKE_SOURCE_DIR}/contracts/musl/upstream/include ${Boost_INCLUDE_DIR}) + +set(STANDARD_INCLUDE_FOLDERS ${CMAKE_SOURCE_DIR}/contracts ${CMAKE_SOURCE_DIR}/unittests/contracts ${CMAKE_SOURCE_DIR}/externals/magic_get/include) + +add_subdirectory(deferred_test) diff --git a/unittests/contracts/deferred_test/CMakeLists.txt b/unittests/contracts/deferred_test/CMakeLists.txt new file mode 100644 index 00000000000..9d0b08a7b4c --- /dev/null +++ b/unittests/contracts/deferred_test/CMakeLists.txt @@ -0,0 +1,8 @@ +file(GLOB ABI_FILES "*.abi") +configure_file("${ABI_FILES}" "${CMAKE_CURRENT_BINARY_DIR}" COPYONLY) + +add_wast_executable(TARGET deferred_test + INCLUDE_FOLDERS "${STANDARD_INCLUDE_FOLDERS}" ${Boost_INCLUDE_DIR} + LIBRARIES libc++ libc eosiolib + DESTINATION_FOLDER ${CMAKE_CURRENT_BINARY_DIR} +) diff --git a/unittests/contracts/deferred_test/deferred_test.abi b/unittests/contracts/deferred_test/deferred_test.abi new file mode 100644 index 00000000000..c4dc13d8eb1 --- /dev/null +++ b/unittests/contracts/deferred_test/deferred_test.abi @@ -0,0 +1,45 @@ +{ + "version": "eosio::abi/1.0", + "types": [], + "structs": [{ + "name": "defercall", + "base": "", + "fields": [{ + "name": "payer", + "type": "name" + },{ + "name": "sender_id", + "type": "uint64" + },{ + "name": "contract", + "type": "name" + },{ + "name": "payload", + "type": "uint64" + } + ] + },{ + "name": "deferfunc", + "base": "", + "fields": [{ + "name": "payload", + "type": "uint64" + } + ] + } + ], + "actions": [{ + "name": "defercall", + "type": "defercall", + "ricardian_contract": "" + },{ + "name": "deferfunc", + "type": "deferfunc", + "ricardian_contract": "" + } + ], + "tables": [], + "ricardian_clauses": [], + "error_messages": [], + "abi_extensions": [] +} diff --git a/unittests/contracts/deferred_test/deferred_test.cpp b/unittests/contracts/deferred_test/deferred_test.cpp new file mode 100644 index 00000000000..68559a406b7 --- /dev/null +++ b/unittests/contracts/deferred_test/deferred_test.cpp @@ -0,0 +1,60 @@ +/** + * @file + * @copyright defined in eos/LICENSE.txt + */ + +#include +#include +#include + +using namespace eosio; + +class deferred_test : public eosio::contract { + public: + using contract::contract; + + struct deferfunc_args { + uint64_t payload; + }; + + //@abi action + void defercall( account_name payer, uint64_t sender_id, account_name contract, uint64_t payload ) { + print( "defercall called on ", name{_self}, "\n" ); + require_auth( payer ); + + print( "deferred send of deferfunc action to ", name{contract}, " by ", name{payer}, " with sender id ", sender_id ); + transaction trx; + deferfunc_args a = {.payload = payload}; + trx.actions.emplace_back(permission_level{_self, N(active)}, contract, N(deferfunc), a); + trx.send( (static_cast(payer) << 64) | sender_id, payer); + } + + //@abi action + void deferfunc( uint64_t payload ) { + print("deferfunc called on ", name{_self}, "\n"); + } + + private: +}; + +void apply_onerror(uint64_t receiver, const onerror& error ) { + print("onerror called on ", name{receiver}, "\n"); +} + +extern "C" { + /// The apply method implements the dispatch of events to this contract + void apply( uint64_t receiver, uint64_t code, uint64_t action ) { + if( code == N(eosio) && action == N(onerror) ) { + apply_onerror( receiver, onerror::from_current_action() ); + } else if( code == receiver ) { + deferred_test thiscontract(receiver); + if( action == N(defercall) ) { + execute_action( &thiscontract, &deferred_test::defercall ); + } else if( action == N(deferfunc) ) { + execute_action( &thiscontract, &deferred_test::deferfunc ); + } + } + } +} + +//EOSIO_ABI( deferred_test, (defercall)(deferfunc) ) diff --git a/unittests/whitelist_blacklist_tests.cpp b/unittests/whitelist_blacklist_tests.cpp index 1d960a8a56b..8bada8a7cf4 100644 --- a/unittests/whitelist_blacklist_tests.cpp +++ b/unittests/whitelist_blacklist_tests.cpp @@ -7,6 +7,9 @@ #include #include +#include +#include + #ifdef NON_VALIDATING_TEST #define TESTER tester #else @@ -19,6 +22,7 @@ using namespace eosio::testing; using mvo = fc::mutable_variant_object; +template class whitelist_blacklist_tester { public: whitelist_blacklist_tester() {} @@ -46,7 +50,9 @@ class whitelist_blacklist_tester { return cfg; } - void init() { + void init( bool bootstrap = true ) { + FC_ASSERT( !chain, "chain is already up" ); + auto cfg = get_default_chain_configuration( tempdir.path() ); cfg.actor_whitelist = actor_whitelist; cfg.actor_blacklist = actor_blacklist; @@ -54,6 +60,11 @@ class whitelist_blacklist_tester { cfg.contract_blacklist = contract_blacklist; chain.emplace(cfg); + wdump((last_produced_block)); + chain->set_last_produced_block_map( last_produced_block ); + + if( !bootstrap ) return; + chain->create_accounts({N(eosio.token), N(alice), N(bob), N(charlie)}); chain->set_code(N(eosio.token), eosio_token_wast); chain->set_abi(N(eosio.token), eosio_token_abi); @@ -69,6 +80,13 @@ class whitelist_blacklist_tester { chain->produce_blocks(); } + void shutdown() { + FC_ASSERT( chain.valid(), "chain is not up" ); + last_produced_block = chain->get_last_produced_block_map(); + wdump((last_produced_block)); + chain.reset(); + } + transaction_trace_ptr transfer( account_name from, account_name to, string quantity = "1.00 TOK" ) { return chain->push_action( N(eosio.token), N(transfer), from, mvo() ( "from", from ) @@ -78,12 +96,13 @@ class whitelist_blacklist_tester { ); } - fc::temp_directory tempdir; // Must come before chain - fc::optional chain; - flat_set actor_whitelist; - flat_set actor_blacklist; - flat_set contract_whitelist; - flat_set contract_blacklist; + fc::temp_directory tempdir; // Must come before chain + fc::optional chain; + flat_set actor_whitelist; + flat_set actor_blacklist; + flat_set contract_whitelist; + flat_set contract_blacklist; + map last_produced_block; }; struct transfer_args { @@ -99,7 +118,7 @@ FC_REFLECT( transfer_args, (from)(to)(quantity)(memo) ) BOOST_AUTO_TEST_SUITE(whitelist_blacklist_tests) BOOST_AUTO_TEST_CASE( actor_whitelist ) { try { - whitelist_blacklist_tester test; + whitelist_blacklist_tester<> test; test.actor_whitelist = {N(eosio), N(eosio.token), N(alice)}; test.init(); @@ -132,7 +151,7 @@ BOOST_AUTO_TEST_CASE( actor_whitelist ) { try { } FC_LOG_AND_RETHROW() } BOOST_AUTO_TEST_CASE( actor_blacklist ) { try { - whitelist_blacklist_tester test; + whitelist_blacklist_tester<> test; test.actor_blacklist = {N(bob)}; test.init(); @@ -166,7 +185,7 @@ BOOST_AUTO_TEST_CASE( actor_blacklist ) { try { } FC_LOG_AND_RETHROW() } BOOST_AUTO_TEST_CASE( contract_whitelist ) { try { - whitelist_blacklist_tester test; + whitelist_blacklist_tester<> test; test.contract_whitelist = {N(eosio), N(eosio.token), N(bob)}; test.init(); @@ -215,7 +234,7 @@ BOOST_AUTO_TEST_CASE( contract_whitelist ) { try { } FC_LOG_AND_RETHROW() } BOOST_AUTO_TEST_CASE( contract_blacklist ) { try { - whitelist_blacklist_tester test; + whitelist_blacklist_tester<> test; test.contract_blacklist = {N(charlie)}; test.init(); @@ -263,4 +282,80 @@ BOOST_AUTO_TEST_CASE( contract_blacklist ) { try { test.chain->produce_blocks(); } FC_LOG_AND_RETHROW() } +BOOST_AUTO_TEST_CASE( blacklist_eosio ) { try { + whitelist_blacklist_tester tester1; + tester1.init(); + tester1.chain->produce_blocks(); + tester1.chain->set_code(N(eosio), eosio_token_wast); + tester1.chain->produce_blocks(); + tester1.shutdown(); + tester1.contract_blacklist = {N(eosio)}; + tester1.init(false); + + whitelist_blacklist_tester tester2; + tester2.init(false); + + while( tester2.chain->control->head_block_num() < tester1.chain->control->head_block_num() ) { + auto b = tester1.chain->control->fetch_block_by_number( tester2.chain->control->head_block_num()+1 ); + tester2.chain->push_block( b ); + } + + tester1.chain->produce_blocks(2); + + while( tester2.chain->control->head_block_num() < tester1.chain->control->head_block_num() ) { + auto b = tester1.chain->control->fetch_block_by_number( tester2.chain->control->head_block_num()+1 ); + tester2.chain->push_block( b ); + } +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_CASE( deferred_blacklist_failure ) { try { + whitelist_blacklist_tester tester1; + tester1.init(); + tester1.chain->produce_blocks(); + tester1.chain->set_code( N(bob), deferred_test_wast ); + tester1.chain->set_abi( N(bob), deferred_test_abi ); + tester1.chain->set_code( N(charlie), deferred_test_wast ); + tester1.chain->set_abi( N(charlie), deferred_test_abi ); + tester1.chain->produce_blocks(); + + tester1.chain->push_action( N(bob), N(defercall), N(alice), mvo() + ( "payer", "alice" ) + ( "sender_id", 0 ) + ( "contract", "charlie" ) + ( "payload", 10 ) + ); + + tester1.chain->produce_blocks(2); + + tester1.shutdown(); + + tester1.contract_blacklist = {N(charlie)}; + tester1.init(false); + + whitelist_blacklist_tester tester2; + tester2.init(false); + + while( tester2.chain->control->head_block_num() < tester1.chain->control->head_block_num() ) { + auto b = tester1.chain->control->fetch_block_by_number( tester2.chain->control->head_block_num()+1 ); + tester2.chain->push_block( b ); + } + + tester1.chain->push_action( N(bob), N(defercall), N(alice), mvo() + ( "payer", "alice" ) + ( "sender_id", 1 ) + ( "contract", "charlie" ) + ( "payload", 10 ) + ); + + BOOST_CHECK_EXCEPTION( tester1.chain->produce_blocks(), fc::exception, + fc_exception_message_is("account 'charlie' is on the contract blacklist") + ); + tester1.chain->produce_blocks(2, true); // Produce 2 empty blocks (other than onblock of course). + + while( tester2.chain->control->head_block_num() < tester1.chain->control->head_block_num() ) { + auto b = tester1.chain->control->fetch_block_by_number( tester2.chain->control->head_block_num()+1 ); + tester2.chain->push_block( b ); + } +} FC_LOG_AND_RETHROW() } + BOOST_AUTO_TEST_SUITE_END()