diff --git a/libraries/chain/apply_context.cpp b/libraries/chain/apply_context.cpp index 274458bbb4..de52aa0d99 100644 --- a/libraries/chain/apply_context.cpp +++ b/libraries/chain/apply_context.cpp @@ -182,7 +182,7 @@ void apply_context::exec_one() print_debug(receiver, trace); } - if (auto dm_logger = control.get_deep_mind_logger()) + if (auto dm_logger = control.get_deep_mind_logger(trx_context.is_transient())) { dm_logger->on_end_action(); } @@ -289,7 +289,7 @@ void apply_context::require_recipient( account_name recipient ) { schedule_action( action_ordinal, recipient, false ) ); - if (auto dm_logger = control.get_deep_mind_logger()) { + if (auto dm_logger = control.get_deep_mind_logger(trx_context.is_transient())) { dm_logger->on_require_recipient(); } } @@ -354,7 +354,7 @@ void apply_context::execute_inline( action&& a ) { "inline action too big for nonprivileged account ${account}", ("account", a.account)); } // No need to check authorization if replaying irreversible blocks or contract is privileged - if( !control.skip_auth_check() && !privileged ) { + if( !control.skip_auth_check() && !privileged && !trx_context.is_read_only() ) { try { control.get_authorization_manager() .check_authorization( {a}, @@ -363,7 +363,7 @@ void apply_context::execute_inline( action&& a ) { control.pending_block_time() - trx_context.published, std::bind(&transaction_context::checktime, &this->trx_context), false, - trx_context.is_dry_run(), + trx_context.is_dry_run(), // check_but_dont_fail inherited_authorizations ); @@ -394,7 +394,7 @@ void apply_context::execute_inline( action&& a ) { schedule_action( std::move(a), inline_receiver, false ) ); - if (auto dm_logger = control.get_deep_mind_logger()) { + if (auto dm_logger = control.get_deep_mind_logger(trx_context.is_transient())) { dm_logger->on_send_inline(); } } @@ -419,13 +419,14 @@ void apply_context::execute_context_free_inline( action&& a ) { schedule_action( std::move(a), inline_receiver, true ) ); - if (auto dm_logger = control.get_deep_mind_logger()) { + if (auto dm_logger = control.get_deep_mind_logger(trx_context.is_transient())) { dm_logger->on_send_context_free_inline(); } } void apply_context::schedule_deferred_transaction( const uint128_t& sender_id, account_name payer, transaction&& trx, bool replace_existing ) { + EOS_ASSERT( !trx_context.is_read_only(), transaction_exception, "cannot schedule a deferred transaction from within a readonly transaction" ); EOS_ASSERT( trx.context_free_actions.size() == 0, cfa_inside_generated_tx, "context free actions are not currently allowed in generated transactions" ); bool enforce_actor_whitelist_blacklist = trx_context.enforce_whiteblacklist && control.is_speculative_block() @@ -557,7 +558,7 @@ void apply_context::schedule_deferred_transaction( const uint128_t& sender_id, a subjective_block_production_exception, "Replacing a deferred transaction is temporarily disabled." ); - if (auto dm_logger = control.get_deep_mind_logger()) { + if (auto dm_logger = control.get_deep_mind_logger(trx_context.is_transient())) { dm_logger->on_ram_trace(RAM_EVENT_ID("${id}", ("id", ptr->id)), "deferred_trx", "cancel", "deferred_trx_cancel"); } @@ -575,7 +576,7 @@ void apply_context::schedule_deferred_transaction( const uint128_t& sender_id, a trx_id_for_new_obj = ptr->trx_id; } - if (auto dm_logger = control.get_deep_mind_logger()) { + if (auto dm_logger = control.get_deep_mind_logger(trx_context.is_transient())) { dm_logger->on_cancel_deferred(deep_mind_handler::operation_qualifier::modify, *ptr); } @@ -592,7 +593,7 @@ void apply_context::schedule_deferred_transaction( const uint128_t& sender_id, a trx_size = gtx.set( trx ); - if (auto dm_logger = control.get_deep_mind_logger()) { + if (auto dm_logger = control.get_deep_mind_logger(trx_context.is_transient())) { dm_logger->on_send_deferred(deep_mind_handler::operation_qualifier::modify, gtx); dm_logger->on_ram_trace(RAM_EVENT_ID("${id}", ("id", gtx.id)), "deferred_trx", "update", "deferred_trx_add"); } @@ -609,7 +610,7 @@ void apply_context::schedule_deferred_transaction( const uint128_t& sender_id, a trx_size = gtx.set( trx ); - if (auto dm_logger = control.get_deep_mind_logger()) { + if (auto dm_logger = control.get_deep_mind_logger(trx_context.is_transient())) { dm_logger->on_send_deferred(deep_mind_handler::operation_qualifier::none, gtx); dm_logger->on_ram_trace(RAM_EVENT_ID("${id}", ("id", gtx.id)), "deferred_trx", "add", "deferred_trx_add"); } @@ -626,10 +627,11 @@ void apply_context::schedule_deferred_transaction( const uint128_t& sender_id, a } bool apply_context::cancel_deferred_transaction( const uint128_t& sender_id, account_name sender ) { + EOS_ASSERT( !trx_context.is_read_only(), transaction_exception, "cannot cancel a deferred transaction from within a readonly transaction" ); auto& generated_transaction_idx = db.get_mutable_index(); const auto* gto = db.find(boost::make_tuple(sender, sender_id)); if ( gto ) { - if (auto dm_logger = control.get_deep_mind_logger()) { + if (auto dm_logger = control.get_deep_mind_logger(trx_context.is_transient())) { dm_logger->on_cancel_deferred(deep_mind_handler::operation_qualifier::none, *gto); dm_logger->on_ram_trace(RAM_EVENT_ID("${id}", ("id", gto->id)), "deferred_trx", "cancel", "deferred_trx_cancel"); } @@ -670,7 +672,7 @@ const table_id_object& apply_context::find_or_create_table( name code, name scop return *existing_tid; } - if (auto dm_logger = control.get_deep_mind_logger()) { + if (auto dm_logger = control.get_deep_mind_logger(trx_context.is_transient())) { std::string event_id = RAM_EVENT_ID("${code}:${scope}:${table}", ("code", code) ("scope", scope) @@ -687,14 +689,14 @@ const table_id_object& apply_context::find_or_create_table( name code, name scop t_id.table = table; t_id.payer = payer; - if (auto dm_logger = control.get_deep_mind_logger()) { + if (auto dm_logger = control.get_deep_mind_logger(trx_context.is_transient())) { dm_logger->on_create_table(t_id); } }); } void apply_context::remove_table( const table_id_object& tid ) { - if (auto dm_logger = control.get_deep_mind_logger()) { + if (auto dm_logger = control.get_deep_mind_logger(trx_context.is_transient())) { std::string event_id = RAM_EVENT_ID("${code}:${scope}:${table}", ("code", tid.code) ("scope", tid.scope) @@ -705,7 +707,7 @@ void apply_context::remove_table( const table_id_object& tid ) { update_db_usage(tid.payer, - config::billable_size_v); - if (auto dm_logger = control.get_deep_mind_logger()) { + if (auto dm_logger = control.get_deep_mind_logger(trx_context.is_transient())) { dm_logger->on_remove_table(tid); } @@ -778,11 +780,13 @@ int apply_context::get_context_free_data( uint32_t index, char* buffer, size_t b } int apply_context::db_store_i64( name scope, name table, const account_name& payer, uint64_t id, const char* buffer, size_t buffer_size ) { + EOS_ASSERT( !trx_context.is_read_only(), table_operation_not_permitted, "cannot store a db record when executing a readonly transaction" ); return db_store_i64( receiver, scope, table, payer, id, buffer, buffer_size); } int apply_context::db_store_i64( name code, name scope, name table, const account_name& payer, uint64_t id, const char* buffer, size_t buffer_size ) { // require_write_lock( scope ); + EOS_ASSERT( !trx_context.is_read_only(), table_operation_not_permitted, "cannot store a db record when executing a readonly transaction" ); const auto& tab = find_or_create_table( code, scope, table, payer ); auto tableid = tab.id; @@ -801,7 +805,7 @@ int apply_context::db_store_i64( name code, name scope, name table, const accoun int64_t billable_size = (int64_t)(buffer_size + config::billable_size_v); - if (auto dm_logger = control.get_deep_mind_logger()) { + if (auto dm_logger = control.get_deep_mind_logger(trx_context.is_transient())) { std::string event_id = RAM_EVENT_ID("${table_code}:${scope}:${table_name}:${primkey}", ("table_code", tab.code) ("scope", tab.scope) @@ -813,7 +817,7 @@ int apply_context::db_store_i64( name code, name scope, name table, const accoun update_db_usage( payer, billable_size); - if (auto dm_logger = control.get_deep_mind_logger()) { + if (auto dm_logger = control.get_deep_mind_logger(trx_context.is_transient())) { dm_logger->on_db_store_i64(tab, obj); } @@ -822,6 +826,7 @@ int apply_context::db_store_i64( name code, name scope, name table, const accoun } void apply_context::db_update_i64( int iterator, account_name payer, const char* buffer, size_t buffer_size ) { + EOS_ASSERT( !trx_context.is_read_only(), table_operation_not_permitted, "cannot update a db record when executing a readonly transaction" ); const key_value_object& obj = keyval_cache.get( iterator ); const auto& table_obj = keyval_cache.get_table( obj.t_id ); @@ -836,7 +841,7 @@ void apply_context::db_update_i64( int iterator, account_name payer, const char* if( payer == account_name() ) payer = obj.payer; std::string event_id; - if (control.get_deep_mind_logger() != nullptr) { + if (control.get_deep_mind_logger(trx_context.is_transient()) != nullptr) { event_id = RAM_EVENT_ID("${table_code}:${scope}:${table_name}:${primkey}", ("table_code", table_obj.code) ("scope", table_obj.scope) @@ -847,27 +852,27 @@ void apply_context::db_update_i64( int iterator, account_name payer, const char* if( account_name(obj.payer) != payer ) { // refund the existing payer - if (auto dm_logger = control.get_deep_mind_logger()) + if (auto dm_logger = control.get_deep_mind_logger(trx_context.is_transient())) { dm_logger->on_ram_trace(std::string(event_id), "table_row", "remove", "primary_index_update_remove_old_payer"); } update_db_usage( obj.payer, -(old_size) ); // charge the new payer - if (auto dm_logger = control.get_deep_mind_logger()) + if (auto dm_logger = control.get_deep_mind_logger(trx_context.is_transient())) { dm_logger->on_ram_trace(std::move(event_id), "table_row", "add", "primary_index_update_add_new_payer"); } update_db_usage( payer, (new_size)); } else if(old_size != new_size) { // charge/refund the existing payer the difference - if (auto dm_logger = control.get_deep_mind_logger()) + if (auto dm_logger = control.get_deep_mind_logger(trx_context.is_transient())) { dm_logger->on_ram_trace(std::move(event_id) , "table_row", "update", "primary_index_update"); } update_db_usage( obj.payer, new_size - old_size); } - if (auto dm_logger = control.get_deep_mind_logger()) { + if (auto dm_logger = control.get_deep_mind_logger(trx_context.is_transient())) { dm_logger->on_db_update_i64(table_obj, obj, payer, buffer, buffer_size); } @@ -878,6 +883,7 @@ void apply_context::db_update_i64( int iterator, account_name payer, const char* } void apply_context::db_remove_i64( int iterator ) { + EOS_ASSERT( !trx_context.is_read_only(), table_operation_not_permitted, "cannot remove a db record when executing a readonly transaction" ); const key_value_object& obj = keyval_cache.get( iterator ); const auto& table_obj = keyval_cache.get_table( obj.t_id ); @@ -885,7 +891,7 @@ void apply_context::db_remove_i64( int iterator ) { // require_write_lock( table_obj.scope ); - if (auto dm_logger = control.get_deep_mind_logger()) { + if (auto dm_logger = control.get_deep_mind_logger(trx_context.is_transient())) { std::string event_id = RAM_EVENT_ID("${table_code}:${scope}:${table_name}:${primkey}", ("table_code", table_obj.code) ("scope", table_obj.scope) @@ -897,7 +903,7 @@ void apply_context::db_remove_i64( int iterator ) { update_db_usage( obj.payer, -(obj.value.size() + config::billable_size_v) ); - if (auto dm_logger = control.get_deep_mind_logger()) { + if (auto dm_logger = control.get_deep_mind_logger(trx_context.is_transient())) { dm_logger->on_db_remove_i64(table_obj, obj); } @@ -1029,17 +1035,27 @@ int apply_context::db_end_i64( name code, name scope, name table ) { uint64_t apply_context::next_global_sequence() { const auto& p = control.get_dynamic_global_properties(); - db.modify( p, [&]( auto& dgp ) { - ++dgp.global_action_sequence; - }); - return p.global_action_sequence; + if ( trx_context.is_read_only() ) { + // To avoid confusion of duplicated global sequence number, hard code to be 0. + return 0; + } else { + db.modify( p, [&]( auto& dgp ) { + ++dgp.global_action_sequence; + }); + return p.global_action_sequence; + } } uint64_t apply_context::next_recv_sequence( const account_metadata_object& receiver_account ) { - db.modify( receiver_account, [&]( auto& ra ) { - ++ra.recv_sequence; - }); - return receiver_account.recv_sequence; + if ( trx_context.is_read_only() ) { + // To avoid confusion of duplicated receive sequence number, hard code to be 0. + return 0; + } else { + db.modify( receiver_account, [&]( auto& ra ) { + ++ra.recv_sequence; + }); + return receiver_account.recv_sequence; + } } uint64_t apply_context::next_auth_sequence( account_name actor ) { const auto& amo = db.get( actor ); diff --git a/libraries/chain/authorization_manager.cpp b/libraries/chain/authorization_manager.cpp index 7f2b6375dd..6f3e598d19 100644 --- a/libraries/chain/authorization_manager.cpp +++ b/libraries/chain/authorization_manager.cpp @@ -134,6 +134,7 @@ namespace eosio { namespace chain { permission_name name, permission_id_type parent, const authority& auth, + bool is_trx_transient, time_point initial_creation_time ) { @@ -158,7 +159,7 @@ namespace eosio { namespace chain { p.last_updated = creation_time; p.auth = auth; - if (auto dm_logger = _control.get_deep_mind_logger()) { + if (auto dm_logger = _control.get_deep_mind_logger(is_trx_transient)) { dm_logger->on_create_permission(p); } }); @@ -169,6 +170,7 @@ namespace eosio { namespace chain { permission_name name, permission_id_type parent, authority&& auth, + bool is_trx_transient, time_point initial_creation_time ) { @@ -193,20 +195,20 @@ namespace eosio { namespace chain { p.last_updated = creation_time; p.auth = std::move(auth); - if (auto dm_logger = _control.get_deep_mind_logger()) { + if (auto dm_logger = _control.get_deep_mind_logger(is_trx_transient)) { dm_logger->on_create_permission(p); } }); return perm; } - void authorization_manager::modify_permission( const permission_object& permission, const authority& auth ) { + void authorization_manager::modify_permission( const permission_object& permission, const authority& auth, bool is_trx_transient ) { for(const key_weight& k: auth.keys) EOS_ASSERT(k.key.which() < _db.get().num_supported_key_types, unactivated_key_type, "Unactivated key type used when modifying permission"); _db.modify( permission, [&](permission_object& po) { - auto dm_logger = _control.get_deep_mind_logger(); + auto dm_logger = _control.get_deep_mind_logger(is_trx_transient); std::optional old_permission; if (dm_logger) { @@ -216,13 +218,13 @@ namespace eosio { namespace chain { po.auth = auth; po.last_updated = _control.pending_block_time(); - if (auto dm_logger = _control.get_deep_mind_logger()) { + if (auto dm_logger = _control.get_deep_mind_logger(is_trx_transient)) { dm_logger->on_modify_permission(*old_permission, po); } }); } - void authorization_manager::remove_permission( const permission_object& permission ) { + void authorization_manager::remove_permission( const permission_object& permission, bool is_trx_transient ) { const auto& index = _db.template get_index(); auto range = index.equal_range(permission.id); EOS_ASSERT( range.first == range.second, action_validate_exception, @@ -230,7 +232,7 @@ namespace eosio { namespace chain { _db.get_mutable_index().remove_object( permission.usage_id._id ); - if (auto dm_logger = _control.get_deep_mind_logger()) { + if (auto dm_logger = _control.get_deep_mind_logger(is_trx_transient)) { dm_logger->on_remove_permission(permission); } diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index aabdad1c39..0105eb303e 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -296,9 +296,9 @@ struct controller_impl { blog( cfg.blocks_dir, cfg.blog ), fork_db( cfg.blocks_dir / config::reversible_blocks_dir_name ), wasmif( cfg.wasm_runtime, cfg.eosvmoc_tierup, db, cfg.state_dir, cfg.eosvmoc_config, !cfg.profile_accounts.empty() ), - resource_limits( db, [&s]() { return s.get_deep_mind_logger(); }), + resource_limits( db, [&s](bool is_trx_transient) { return s.get_deep_mind_logger(is_trx_transient); }), authorization( s, db ), - protocol_features( std::move(pfs), [&s]() { return s.get_deep_mind_logger(); } ), + protocol_features( std::move(pfs), [&s](bool is_trx_transient) { return s.get_deep_mind_logger(is_trx_transient); } ), conf( cfg ), chain_id( chain_id ), read_mode( cfg.read_mode ), @@ -384,7 +384,10 @@ struct controller_impl { } void dmlog_applied_transaction(const transaction_trace_ptr& t) { - if (auto dm_logger = get_deep_mind_logger()) { + // dmlog_applied_transaction is called by push_scheduled_transaction + // where transient transactions are not possible, and by push_transaction + // only when the transaction is not transient + if (auto dm_logger = get_deep_mind_logger(false)) { dm_logger->on_applied_transaction(self.head_block_num() + 1, t); } } @@ -700,7 +703,8 @@ struct controller_impl { protocol_features.init( db ); - if (auto dm_logger = get_deep_mind_logger()) { + // At startup, no transaction specific logging is possible + if (auto dm_logger = get_deep_mind_logger(false)) { dm_logger->on_startup(db, head->block_num); } @@ -1016,22 +1020,23 @@ struct controller_impl { }); const auto& owner_permission = authorization.create_permission(name, config::owner_name, 0, - owner, initial_timestamp ); + owner, false, initial_timestamp ); const auto& active_permission = authorization.create_permission(name, config::active_name, owner_permission.id, - active, initial_timestamp ); + active, false, initial_timestamp ); - resource_limits.initialize_account(name); + resource_limits.initialize_account(name, false); int64_t ram_delta = config::overhead_per_account_ram_bytes; ram_delta += 2*config::billable_size_v; ram_delta += owner_permission.auth.get_billable_size(); ram_delta += active_permission.auth.get_billable_size(); - if (auto dm_logger = get_deep_mind_logger()) { + // This is only called at startup, no transaction specific logging is possible + if (auto dm_logger = get_deep_mind_logger(false)) { dm_logger->on_ram_trace(RAM_EVENT_ID("${name}", ("name", name)), "account", "add", "newaccount"); } - resource_limits.add_pending_ram_usage(name, ram_delta); + resource_limits.add_pending_ram_usage(name, ram_delta, false); // false for doing dm logging resource_limits.verify_account_ram_usage(name); } @@ -1084,11 +1089,13 @@ struct controller_impl { config::majority_producers_permission_name, active_permission.id, active_producers_authority, + false, genesis.initial_timestamp ); authorization.create_permission( config::producers_account_name, config::minority_producers_permission_name, majority_permission.id, active_producers_authority, + false, genesis.initial_timestamp ); } @@ -1141,14 +1148,14 @@ struct controller_impl { etrx.set_reference_block( self.head_block_id() ); } - if (auto dm_logger = get_deep_mind_logger()) { - dm_logger->on_onerror(etrx); - } - transaction_checktime_timer trx_timer(timer); const packed_transaction trx( std::move( etrx ) ); transaction_context trx_context( self, trx, std::move(trx_timer), start ); + if (auto dm_logger = get_deep_mind_logger(trx_context.is_transient())) { + dm_logger->on_onerror(etrx); + } + trx_context.block_deadline = block_deadline; trx_context.max_transaction_time_subjective = max_transaction_time; trx_context.explicit_billed_cpu_time = explicit_billed_cpu_time; @@ -1197,12 +1204,13 @@ struct controller_impl { } int64_t remove_scheduled_transaction( const generated_transaction_object& gto ) { - if (auto dm_logger = get_deep_mind_logger()) { + // deferred transactions cannot be transient. + if (auto dm_logger = get_deep_mind_logger(false)) { dm_logger->on_ram_trace(RAM_EVENT_ID("${id}", ("id", gto.id)), "deferred_trx", "remove", "deferred_trx_removed"); } int64_t ram_delta = -(config::billable_size_v + gto.packed_trx.size()); - resource_limits.add_pending_ram_usage( gto.payer, ram_delta ); + resource_limits.add_pending_ram_usage( gto.payer, ram_delta, false ); // false for doing dm logging // No need to verify_account_ram_usage since we are only reducing memory db.remove( gto ); @@ -1325,7 +1333,8 @@ struct controller_impl { trace->except_ptr = std::current_exception(); trace->elapsed = fc::time_point::now() - start; - if (auto dm_logger = get_deep_mind_logger()) { + // deferred transactions cannot be transient + if (auto dm_logger = get_deep_mind_logger(false)) { dm_logger->on_fail_deferred(); } }; @@ -1506,7 +1515,7 @@ struct controller_impl { transaction_trace_ptr trace; try { auto start = fc::time_point::now(); - const bool check_auth = !self.skip_auth_check() && !trx->implicit(); + const bool check_auth = !self.skip_auth_check() && !trx->implicit() && !trx->is_read_only(); const fc::microseconds sig_cpu_usage = trx->signature_cpu_usage(); if( !explicit_billed_cpu_time ) { @@ -1586,7 +1595,7 @@ struct controller_impl { std::move(trx_context.executed_action_receipt_digests) ); // call the accept signal but only once for this transaction - if (!trx->is_dry_run()) { + if (!trx->is_transient()) { if (!trx->accepted) { trx->accepted = true; emit(self.accepted_transaction, trx); @@ -1597,7 +1606,7 @@ struct controller_impl { } - if ( trx->is_dry_run() ) { + if ( trx->is_transient() ) { // remove trx from pending block by not canceling 'restore' trx_context.undo(); // this will happen automatically in destructor, but make it more explicit } else if ( pending->_block_status == controller::block_status::ephemeral ) { @@ -1612,7 +1621,7 @@ struct controller_impl { trx_context.squash(); } - if( !trx->is_dry_run() ) { + if( !trx->is_transient() ) { pending->_block_report.total_net_usage += trace->net_usage; pending->_block_report.total_cpu_usage_us += trace->receipt->cpu_usage_us; pending->_block_report.total_elapsed_time += trace->elapsed; @@ -1635,7 +1644,7 @@ struct controller_impl { handle_exception(wrapper); } - if (!trx->is_dry_run()) { + if (!trx->is_transient()) { emit(self.accepted_transaction, trx); dmlog_applied_transaction(trace); emit(self.applied_transaction, std::tie(trace, trx->packed_trx())); @@ -1661,7 +1670,8 @@ struct controller_impl { emit( self.block_start, head->block_num + 1 ); - if (auto dm_logger = get_deep_mind_logger()) { + // at block level, no transaction specific logging is possible + if (auto dm_logger = get_deep_mind_logger(false)) { // The head block represents the block just before this one that is about to start, so add 1 to get this block num dm_logger->on_start_block(head->block_num + 1); } @@ -1913,7 +1923,8 @@ struct controller_impl { EOS_ASSERT( bsp == head, fork_database_exception, "committed block did not become the new head in fork database"); } - if (auto dm_logger = get_deep_mind_logger()) { + // at block level, no transaction specific logging is possible + if (auto dm_logger = get_deep_mind_logger(false)) { dm_logger->on_accepted_block(bsp); } @@ -2308,7 +2319,8 @@ struct controller_impl { ilog("switching forks from ${current_head_id} (block number ${current_head_num}) to ${new_head_id} (block number ${new_head_num})", ("current_head_id", head->id)("current_head_num", head->block_num)("new_head_id", new_head->id)("new_head_num", new_head->block_num) ); - if (auto dm_logger = get_deep_mind_logger()) { + // not possible to log transaction specific infor when switching forks + if (auto dm_logger = get_deep_mind_logger(false)) { dm_logger->on_switch_forks(head->id, new_head->id); } @@ -2627,21 +2639,22 @@ struct controller_impl { trx.set_reference_block( self.head_block_id() ); } - if (auto dm_logger = get_deep_mind_logger()) { + // onblock transaction cannot be transient + if (auto dm_logger = get_deep_mind_logger(false)) { dm_logger->on_onblock(trx); } return trx; } - inline deep_mind_handler* get_deep_mind_logger() const { - return deep_mind_logger; + inline deep_mind_handler* get_deep_mind_logger(bool is_trx_transient) const { + // do not perform deep mind logging for read-only and dry-run transactions + return is_trx_transient ? nullptr : deep_mind_logger; } uint32_t earliest_available_block_num() const { return (blog.first_block_num() != 0) ? blog.first_block_num() : fork_db.root()->block_num; } - }; /// controller_impl const resource_limits_manager& controller::get_resource_limits_manager()const @@ -2714,7 +2727,7 @@ chainbase::database& controller::mutable_db()const { return my->db; } const fork_database& controller::fork_db()const { return my->fork_db; } -void controller::preactivate_feature( const digest_type& feature_digest ) { +void controller::preactivate_feature( const digest_type& feature_digest, bool is_trx_transient ) { const auto& pfs = my->protocol_features.get_protocol_feature_set(); auto cur_time = pending_block_time(); @@ -2811,7 +2824,7 @@ void controller::preactivate_feature( const digest_type& feature_digest ) { ("digest", feature_digest) ); - if (auto dm_logger = get_deep_mind_logger()) { + if (auto dm_logger = get_deep_mind_logger(is_trx_transient)) { const auto feature = pfs.get_protocol_feature(feature_digest); dm_logger->on_preactivate_feature(feature); @@ -3474,7 +3487,9 @@ void controller::add_to_ram_correction( account_name account, uint64_t ram_bytes } ); } - if (auto dm_logger = get_deep_mind_logger()) { + // on_add_ram_correction is only called for deferred transaction + // (in apply_context::schedule_deferred_transaction) + if (auto dm_logger = get_deep_mind_logger(false)) { dm_logger->on_add_ram_correction(*ptr, ram_bytes); } } @@ -3483,8 +3498,8 @@ bool controller::all_subjective_mitigations_disabled()const { return my->conf.disable_all_subjective_mitigations; } -deep_mind_handler* controller::get_deep_mind_logger()const { - return my->get_deep_mind_logger(); +deep_mind_handler* controller::get_deep_mind_logger(bool is_trx_transient)const { + return my->get_deep_mind_logger(is_trx_transient); } void controller::enable_deep_mind(deep_mind_handler* logger) { @@ -3598,10 +3613,18 @@ void controller::replace_account_keys( name account, name permission, const publ p.auth = authority(key); }); int64_t new_size = (int64_t)(chain::config::billable_size_v + perm->auth.get_billable_size()); - rlm.add_pending_ram_usage(account, new_size - old_size); + rlm.add_pending_ram_usage(account, new_size - old_size, false); // false for doing dm logging rlm.verify_account_ram_usage(account); } +void controller::set_db_read_only_mode() { + mutable_db().set_read_only_mode(); +} + +void controller::unset_db_read_only_mode() { + mutable_db().unset_read_only_mode(); +} + /// Protocol feature activation handlers: template<> @@ -3631,11 +3654,12 @@ void controller_impl::on_activationname)("adjust", itr->ram_correction)("current", current_ram_usage) ); } - if (auto dm_logger = get_deep_mind_logger()) { + // This method is only called for deferred transaction + if (auto dm_logger = get_deep_mind_logger(false)) { dm_logger->on_ram_trace(RAM_EVENT_ID("${id}", ("id", itr->id._id)), "deferred_trx", "correction", "deferred_trx_ram_correction"); } - resource_limits.add_pending_ram_usage( itr->name, ram_delta ); + resource_limits.add_pending_ram_usage( itr->name, ram_delta, false ); // false for doing dm logging db.remove( *itr ); } } diff --git a/libraries/chain/eosio_contract.cpp b/libraries/chain/eosio_contract.cpp index e7553e551d..6845272908 100644 --- a/libraries/chain/eosio_contract.cpp +++ b/libraries/chain/eosio_contract.cpp @@ -65,6 +65,7 @@ void validate_authority_precondition( const apply_context& context, const author * This method is called assuming precondition_system_newaccount succeeds a */ void apply_eosio_newaccount(apply_context& context) { + EOS_ASSERT( !context.trx_context.is_read_only(), action_validate_exception, "newaccount not allowed in read-only transaction" ); auto create = context.get_action().data_as(); try { context.require_authorization(create.creator); @@ -107,18 +108,18 @@ void apply_eosio_newaccount(apply_context& context) { } const auto& owner_permission = authorization.create_permission( create.name, config::owner_name, 0, - std::move(create.owner) ); + std::move(create.owner), context.trx_context.is_transient() ); const auto& active_permission = authorization.create_permission( create.name, config::active_name, owner_permission.id, - std::move(create.active) ); + std::move(create.active), context.trx_context.is_transient() ); - context.control.get_mutable_resource_limits_manager().initialize_account(create.name); + context.control.get_mutable_resource_limits_manager().initialize_account(create.name, context.trx_context.is_transient()); int64_t ram_delta = config::overhead_per_account_ram_bytes; ram_delta += 2*config::billable_size_v; ram_delta += owner_permission.auth.get_billable_size(); ram_delta += active_permission.auth.get_billable_size(); - if (auto dm_logger = context.control.get_deep_mind_logger()) { + if (auto dm_logger = context.control.get_deep_mind_logger(context.trx_context.is_transient())) { dm_logger->on_ram_trace(RAM_EVENT_ID("${name}", ("name", create.name)), "account", "add", "newaccount"); } @@ -127,6 +128,7 @@ void apply_eosio_newaccount(apply_context& context) { } FC_CAPTURE_AND_RETHROW( (create) ) } void apply_eosio_setcode(apply_context& context) { + EOS_ASSERT( !context.trx_context.is_read_only(), action_validate_exception, "setcode not allowed in read-only transaction" ); auto& db = context.db; auto act = context.get_action().data_as(); context.require_authorization(act.account); @@ -194,7 +196,7 @@ void apply_eosio_setcode(apply_context& context) { }); if (new_size != old_size) { - if (auto dm_logger = context.control.get_deep_mind_logger()) { + if (auto dm_logger = context.control.get_deep_mind_logger(context.trx_context.is_transient())) { const char* operation = "update"; if (old_size <= 0) { operation = "add"; @@ -210,6 +212,7 @@ void apply_eosio_setcode(apply_context& context) { } void apply_eosio_setabi(apply_context& context) { + EOS_ASSERT( !context.trx_context.is_read_only(), action_validate_exception, "setabi ot allowed in read-only transaction" ); auto& db = context.db; auto act = context.get_action().data_as(); @@ -232,7 +235,7 @@ void apply_eosio_setabi(apply_context& context) { }); if (new_size != old_size) { - if (auto dm_logger = context.control.get_deep_mind_logger()) { + if (auto dm_logger = context.control.get_deep_mind_logger(context.trx_context.is_transient())) { const char* operation = "update"; if (old_size <= 0) { operation = "add"; @@ -248,6 +251,7 @@ void apply_eosio_setabi(apply_context& context) { } void apply_eosio_updateauth(apply_context& context) { + EOS_ASSERT( !context.trx_context.is_read_only(), action_validate_exception, "updateauth not allowed in read-only transaction" ); auto update = context.get_action().data_as(); context.require_authorization(update.account); // only here to mark the single authority on this action as used @@ -297,21 +301,21 @@ void apply_eosio_updateauth(apply_context& context) { int64_t old_size = (int64_t)(config::billable_size_v + permission->auth.get_billable_size()); - authorization.modify_permission( *permission, update.auth ); + authorization.modify_permission( *permission, update.auth, context.trx_context.is_transient() ); int64_t new_size = (int64_t)(config::billable_size_v + permission->auth.get_billable_size()); - if (auto dm_logger = context.control.get_deep_mind_logger()) { + if (auto dm_logger = context.control.get_deep_mind_logger(context.trx_context.is_transient())) { dm_logger->on_ram_trace(RAM_EVENT_ID("${id}", ("id", permission->id)), "auth", "update", "updateauth_update"); } context.add_ram_usage( permission->owner, new_size - old_size ); } else { - const auto& p = authorization.create_permission( update.account, update.permission, parent_id, update.auth ); + const auto& p = authorization.create_permission( update.account, update.permission, parent_id, update.auth, context.trx_context.is_transient() ); int64_t new_size = (int64_t)(config::billable_size_v + p.auth.get_billable_size()); - if (auto dm_logger = context.control.get_deep_mind_logger()) { + if (auto dm_logger = context.control.get_deep_mind_logger(context.trx_context.is_transient())) { dm_logger->on_ram_trace(RAM_EVENT_ID("${id}", ("id", p.id)), "auth", "add", "updateauth_create"); } @@ -322,6 +326,8 @@ void apply_eosio_updateauth(apply_context& context) { void apply_eosio_deleteauth(apply_context& context) { // context.require_write_lock( config::eosio_auth_scope ); + EOS_ASSERT( !context.trx_context.is_read_only(), action_validate_exception, "deleteauth not allowed in read-only transaction" ); + auto remove = context.get_action().data_as(); context.require_authorization(remove.account); // only here to mark the single authority on this action as used @@ -344,11 +350,11 @@ void apply_eosio_deleteauth(apply_context& context) { const auto& permission = authorization.get_permission({remove.account, remove.permission}); int64_t old_size = config::billable_size_v + permission.auth.get_billable_size(); - if (auto dm_logger = context.control.get_deep_mind_logger()) { + if (auto dm_logger = context.control.get_deep_mind_logger(context.trx_context.is_transient())) { dm_logger->on_ram_trace(RAM_EVENT_ID("${id}", ("id", permission.id)), "auth", "remove", "deleteauth"); } - authorization.remove_permission( permission ); + authorization.remove_permission( permission, context.trx_context.is_transient() ); context.add_ram_usage( remove.account, -old_size ); @@ -357,6 +363,8 @@ void apply_eosio_deleteauth(apply_context& context) { void apply_eosio_linkauth(apply_context& context) { // context.require_write_lock( config::eosio_auth_scope ); + EOS_ASSERT( !context.trx_context.is_read_only(), action_validate_exception, "linkauth not allowed in read-only transaction" ); + auto requirement = context.get_action().data_as(); try { EOS_ASSERT(!requirement.requirement.empty(), action_validate_exception, "Required permission cannot be empty"); @@ -401,7 +409,7 @@ void apply_eosio_linkauth(apply_context& context) { link.required_permission = requirement.requirement; }); - if (auto dm_logger = context.control.get_deep_mind_logger()) { + if (auto dm_logger = context.control.get_deep_mind_logger(context.trx_context.is_transient())) { dm_logger->on_ram_trace(RAM_EVENT_ID("${id}", ("id", l.id)), "auth_link", "add", "linkauth"); } @@ -417,6 +425,8 @@ void apply_eosio_linkauth(apply_context& context) { void apply_eosio_unlinkauth(apply_context& context) { // context.require_write_lock( config::eosio_auth_scope ); + EOS_ASSERT( !context.trx_context.is_read_only(), action_validate_exception, "unlinkauth not allowed in read-only transaction" ); + auto& db = context.db; auto unlink = context.get_action().data_as(); @@ -426,7 +436,7 @@ void apply_eosio_unlinkauth(apply_context& context) { auto link = db.find(link_key); EOS_ASSERT(link != nullptr, action_validate_exception, "Attempting to unlink authority, but no link found"); - if (auto dm_logger = context.control.get_deep_mind_logger()) { + if (auto dm_logger = context.control.get_deep_mind_logger(context.trx_context.is_transient())) { dm_logger->on_ram_trace(RAM_EVENT_ID("${id}", ("id", link->id)), "auth_link", "remove", "unlinkauth"); } @@ -439,6 +449,7 @@ void apply_eosio_unlinkauth(apply_context& context) { } void apply_eosio_canceldelay(apply_context& context) { + EOS_ASSERT( !context.trx_context.is_read_only(), action_validate_exception, "canceldelay not allowed in read-only transaction" ); auto cancel = context.get_action().data_as(); context.require_authorization(cancel.canceling_auth.actor); // only here to mark the single authority on this action as used diff --git a/libraries/chain/include/eosio/chain/apply_context.hpp b/libraries/chain/include/eosio/chain/apply_context.hpp index 51e09cfec5..78a4fa0e0a 100644 --- a/libraries/chain/include/eosio/chain/apply_context.hpp +++ b/libraries/chain/include/eosio/chain/apply_context.hpp @@ -1,6 +1,7 @@ #pragma once #include #include +#include #include #include #include @@ -13,7 +14,6 @@ namespace chainbase { class database; } namespace eosio { namespace chain { class controller; -class transaction_context; class apply_context { private: @@ -177,6 +177,7 @@ class apply_context { int store( uint64_t scope, uint64_t table, const account_name& payer, uint64_t id, secondary_key_proxy_const_type value ) { + EOS_ASSERT( !context.trx_context.is_read_only(), table_operation_not_permitted, "cannot store a db record when executing a readonly transaction" ); EOS_ASSERT( payer != account_name(), invalid_table_payer, "must specify a valid account to pay for new record" ); // context.require_write_lock( scope ); @@ -193,7 +194,7 @@ class apply_context { context.db.modify( tab, [&]( auto& t ) { ++t.count; - if (auto dm_logger = context.control.get_deep_mind_logger()) { + if (auto dm_logger = context.control.get_deep_mind_logger(context.trx_context.is_transient())) { std::string event_id = RAM_EVENT_ID("${code}:${scope}:${table}:${index_name}", ("code", t.code) ("scope", t.scope) @@ -211,12 +212,13 @@ class apply_context { } void remove( int iterator ) { + EOS_ASSERT( !context.trx_context.is_read_only(), table_operation_not_permitted, "cannot remove a db record when executing a readonly transaction" ); const auto& obj = itr_cache.get( iterator ); const auto& table_obj = itr_cache.get_table( obj.t_id ); EOS_ASSERT( table_obj.code == context.receiver, table_access_violation, "db access violation" ); - if (auto dm_logger = context.control.get_deep_mind_logger()) { + if (auto dm_logger = context.control.get_deep_mind_logger(context.trx_context.is_transient())) { std::string event_id = RAM_EVENT_ID("${code}:${scope}:${table}:${index_name}", ("code", table_obj.code) ("scope", table_obj.scope) @@ -243,6 +245,7 @@ class apply_context { } void update( int iterator, account_name payer, secondary_key_proxy_const_type secondary ) { + EOS_ASSERT( !context.trx_context.is_read_only(), table_operation_not_permitted, "cannot update a db record when executing a readonly transaction" ); const auto& obj = itr_cache.get( iterator ); const auto& table_obj = itr_cache.get_table( obj.t_id ); @@ -255,7 +258,7 @@ class apply_context { int64_t billing_size = config::billable_size_v; std::string event_id; - if (context.control.get_deep_mind_logger() != nullptr) { + if (context.control.get_deep_mind_logger(context.trx_context.is_transient()) != nullptr) { event_id = RAM_EVENT_ID("${code}:${scope}:${table}:${index_name}", ("code", table_obj.code) ("scope", table_obj.scope) @@ -265,12 +268,12 @@ class apply_context { } if( obj.payer != payer ) { - if (auto dm_logger = context.control.get_deep_mind_logger()) + if (auto dm_logger = context.control.get_deep_mind_logger(context.trx_context.is_transient())) { dm_logger->on_ram_trace(std::string(event_id), "secondary_index", "remove", "secondary_index_remove"); } context.update_db_usage( obj.payer, -(billing_size) ); - if (auto dm_logger = context.control.get_deep_mind_logger()) + if (auto dm_logger = context.control.get_deep_mind_logger(context.trx_context.is_transient())) { dm_logger->on_ram_trace(std::move(event_id), "secondary_index", "add", "secondary_index_update_add_new_payer"); } diff --git a/libraries/chain/include/eosio/chain/authorization_manager.hpp b/libraries/chain/include/eosio/chain/authorization_manager.hpp index 4fee6db841..081b28deea 100644 --- a/libraries/chain/include/eosio/chain/authorization_manager.hpp +++ b/libraries/chain/include/eosio/chain/authorization_manager.hpp @@ -31,6 +31,7 @@ namespace eosio { namespace chain { permission_name name, permission_id_type parent, const authority& auth, + bool is_trx_transient, time_point initial_creation_time = time_point() ); @@ -38,12 +39,13 @@ namespace eosio { namespace chain { permission_name name, permission_id_type parent, authority&& auth, + bool is_trx_transient, time_point initial_creation_time = time_point() ); - void modify_permission( const permission_object& permission, const authority& auth ); + void modify_permission( const permission_object& permission, const authority& auth, bool is_trx_transient ); - void remove_permission( const permission_object& permission ); + void remove_permission( const permission_object& permission, bool is_trx_transient ); void update_permission_usage( const permission_object& permission ); diff --git a/libraries/chain/include/eosio/chain/controller.hpp b/libraries/chain/include/eosio/chain/controller.hpp index 5dede8a9fc..fa32f25fbd 100644 --- a/libraries/chain/include/eosio/chain/controller.hpp +++ b/libraries/chain/include/eosio/chain/controller.hpp @@ -119,7 +119,7 @@ namespace eosio { namespace chain { void startup( std::function shutdown, std::function check_shutdown, const genesis_state& genesis); void startup( std::function shutdown, std::function check_shutdown); - void preactivate_feature( const digest_type& feature_digest ); + void preactivate_feature( const digest_type& feature_digest, bool is_trx_transient ); vector get_preactivated_protocol_features()const; @@ -311,7 +311,7 @@ namespace eosio { namespace chain { void add_to_ram_correction( account_name account, uint64_t ram_bytes ); bool all_subjective_mitigations_disabled()const; - deep_mind_handler* get_deep_mind_logger() const; + deep_mind_handler* get_deep_mind_logger(bool is_trx_transient) const; void enable_deep_mind( deep_mind_handler* logger ); uint32_t earliest_available_block_num() const; @@ -370,6 +370,9 @@ namespace eosio { namespace chain { void replace_producer_keys( const public_key_type& key ); void replace_account_keys( name account, name permission, const public_key_type& key ); + void set_db_read_only_mode(); + void unset_db_read_only_mode(); + private: friend class apply_context; friend class transaction_context; diff --git a/libraries/chain/include/eosio/chain/protocol_feature_manager.hpp b/libraries/chain/include/eosio/chain/protocol_feature_manager.hpp index b432a8cf5e..25af9247a9 100644 --- a/libraries/chain/include/eosio/chain/protocol_feature_manager.hpp +++ b/libraries/chain/include/eosio/chain/protocol_feature_manager.hpp @@ -264,7 +264,7 @@ class protocol_feature_set { class protocol_feature_manager { public: - protocol_feature_manager( protocol_feature_set&& pfs, std::function get_deep_mind_logger ); + protocol_feature_manager( protocol_feature_set&& pfs, std::function get_deep_mind_logger ); class const_iterator { public: @@ -393,7 +393,7 @@ class protocol_feature_manager { bool _initialized = false; private: - std::function _get_deep_mind_logger; + std::function _get_deep_mind_logger; }; std::optional read_builtin_protocol_feature( const fc::path& p ); diff --git a/libraries/chain/include/eosio/chain/resource_limits.hpp b/libraries/chain/include/eosio/chain/resource_limits.hpp index 9854edf4d7..8d11aea7ba 100644 --- a/libraries/chain/include/eosio/chain/resource_limits.hpp +++ b/libraries/chain/include/eosio/chain/resource_limits.hpp @@ -64,7 +64,7 @@ namespace eosio { namespace chain { class resource_limits_manager { public: - explicit resource_limits_manager(chainbase::database& db, std::function get_deep_mind_logger) + explicit resource_limits_manager(chainbase::database& db, std::function get_deep_mind_logger) :_db(db),_get_deep_mind_logger(get_deep_mind_logger) { } @@ -74,17 +74,17 @@ namespace eosio { namespace chain { void add_to_snapshot( const snapshot_writer_ptr& snapshot ) const; void read_from_snapshot( const snapshot_reader_ptr& snapshot ); - void initialize_account( const account_name& account ); + void initialize_account( const account_name& account, bool is_trx_transient ); void set_block_parameters( const elastic_limit_parameters& cpu_limit_parameters, const elastic_limit_parameters& net_limit_parameters ); void update_account_usage( const flat_set& accounts, uint32_t ordinal ); - void add_transaction_usage( const flat_set& accounts, uint64_t cpu_usage, uint64_t net_usage, uint32_t ordinal ); + void add_transaction_usage( const flat_set& accounts, uint64_t cpu_usage, uint64_t net_usage, uint32_t ordinal, bool is_trx_transient = false ); - void add_pending_ram_usage( const account_name account, int64_t ram_delta ); + void add_pending_ram_usage( const account_name account, int64_t ram_delta, bool is_trx_transient = false ); void verify_account_ram_usage( const account_name accunt )const; /// set_account_limits returns true if new ram_bytes limit is more restrictive than the previously set one - bool set_account_limits( const account_name& account, int64_t ram_bytes, int64_t net_weight, int64_t cpu_weight); + bool set_account_limits( const account_name& account, int64_t ram_bytes, int64_t net_weight, int64_t cpu_weight, bool is_trx_transient); void get_account_limits( const account_name& account, int64_t& ram_bytes, int64_t& net_weight, int64_t& cpu_weight) const; bool is_unlimited_cpu( const account_name& account ) const; @@ -114,7 +114,7 @@ namespace eosio { namespace chain { private: chainbase::database& _db; - std::function _get_deep_mind_logger; + std::function _get_deep_mind_logger; }; } } } /// eosio::chain diff --git a/libraries/chain/include/eosio/chain/transaction_context.hpp b/libraries/chain/include/eosio/chain/transaction_context.hpp index cd887b1d90..5870850180 100644 --- a/libraries/chain/include/eosio/chain/transaction_context.hpp +++ b/libraries/chain/include/eosio/chain/transaction_context.hpp @@ -84,6 +84,8 @@ namespace eosio { namespace chain { void validate_referenced_accounts( const transaction& trx, bool enforce_actor_whitelist_blacklist )const; bool is_dry_run()const { return trx_type == transaction_metadata::trx_type::dry_run; }; + bool is_read_only()const { return trx_type == transaction_metadata::trx_type::read_only; }; + bool is_transient()const { return trx_type == transaction_metadata::trx_type::read_only || trx_type == transaction_metadata::trx_type::dry_run; }; private: @@ -179,7 +181,8 @@ namespace eosio { namespace chain { on_chain_consensus_max_transaction_cpu_usage, user_specified_trx_max_cpu_usage_ms, node_configured_max_transaction_time, - speculative_executed_adjusted_max_transaction_time // prev_billed_cpu_time_us > 0 + speculative_executed_adjusted_max_transaction_time, // prev_billed_cpu_time_us > 0 + node_configured_max_read_only_transaction_time }; tx_cpu_usage_exceeded_reason tx_cpu_usage_reason = tx_cpu_usage_exceeded_reason::account_cpu_limit; fc::microseconds tx_cpu_usage_amount; diff --git a/libraries/chain/include/eosio/chain/transaction_metadata.hpp b/libraries/chain/include/eosio/chain/transaction_metadata.hpp index 07bd105374..5e585df6e7 100644 --- a/libraries/chain/include/eosio/chain/transaction_metadata.hpp +++ b/libraries/chain/include/eosio/chain/transaction_metadata.hpp @@ -24,7 +24,8 @@ class transaction_metadata { input, implicit, scheduled, - dry_run + dry_run, + read_only }; private: @@ -73,6 +74,8 @@ class transaction_metadata { bool implicit() const { return _trx_type == trx_type::implicit; }; bool scheduled() const { return _trx_type == trx_type::scheduled; }; bool is_dry_run() const { return _trx_type == trx_type::dry_run; }; + bool is_read_only() const { return _trx_type == trx_type::read_only; }; + bool is_transient() const { return _trx_type == trx_type::read_only || _trx_type == trx_type::dry_run; }; /// Thread safe. /// @returns transaction_metadata_ptr or exception via future diff --git a/libraries/chain/protocol_feature_manager.cpp b/libraries/chain/protocol_feature_manager.cpp index bde450938d..f971f25923 100644 --- a/libraries/chain/protocol_feature_manager.cpp +++ b/libraries/chain/protocol_feature_manager.cpp @@ -569,7 +569,7 @@ Enables new `get_block_num` intrinsic which returns the current block number. protocol_feature_manager::protocol_feature_manager( protocol_feature_set&& pfs, - std::function get_deep_mind_logger + std::function get_deep_mind_logger ):_protocol_feature_set( std::move(pfs) ), _get_deep_mind_logger(get_deep_mind_logger) { _builtin_protocol_features.resize( _protocol_feature_set._recognized_builtin_protocol_features.size() ); @@ -750,7 +750,8 @@ Enables new `get_block_num` intrinsic which returns the current block number. ("digest", feature_digest) ); - if (auto dm_logger = _get_deep_mind_logger()) { + // activate_feature is called by init. no transaction specific logging is possible + if (auto dm_logger = _get_deep_mind_logger(false)) { dm_logger->on_activate_feature(*itr); } diff --git a/libraries/chain/resource_limits.cpp b/libraries/chain/resource_limits.cpp index 472c940c9b..b11eea1dba 100644 --- a/libraries/chain/resource_limits.cpp +++ b/libraries/chain/resource_limits.cpp @@ -65,7 +65,8 @@ void resource_limits_manager::initialize_database() { state.virtual_net_limit = config.net_limit_parameters.max; }); - if (auto dm_logger = _get_deep_mind_logger()) { + // At startup, no transaction specific logging is possible + if (auto dm_logger = _get_deep_mind_logger(false)) { dm_logger->on_init_resource_limits(config, state); } } @@ -93,7 +94,7 @@ void resource_limits_manager::read_from_snapshot( const snapshot_reader_ptr& sna }); } -void resource_limits_manager::initialize_account(const account_name& account) { +void resource_limits_manager::initialize_account(const account_name& account, bool is_trx_transient) { const auto& limits = _db.create([&]( resource_limits_object& bl ) { bl.owner = account; }); @@ -101,7 +102,7 @@ void resource_limits_manager::initialize_account(const account_name& account) { const auto& usage = _db.create([&]( resource_usage_object& bu ) { bu.owner = account; }); - if (auto dm_logger = _get_deep_mind_logger()) { + if (auto dm_logger = _get_deep_mind_logger(is_trx_transient)) { dm_logger->on_newaccount_resource_limits(limits, usage); } } @@ -116,7 +117,9 @@ void resource_limits_manager::set_block_parameters(const elastic_limit_parameter c.cpu_limit_parameters = cpu_limit_parameters; c.net_limit_parameters = net_limit_parameters; - if (auto dm_logger = _get_deep_mind_logger()) { + // set_block_parameters is called by controller::finalize_block, + // where transaction specific logging is not possible + if (auto dm_logger = _get_deep_mind_logger(false)) { dm_logger->on_update_resource_limits_config(c); } }); @@ -133,7 +136,7 @@ void resource_limits_manager::update_account_usage(const flat_set& } } -void resource_limits_manager::add_transaction_usage(const flat_set& accounts, uint64_t cpu_usage, uint64_t net_usage, uint32_t time_slot ) { +void resource_limits_manager::add_transaction_usage(const flat_set& accounts, uint64_t cpu_usage, uint64_t net_usage, uint32_t time_slot, bool is_trx_transient ) { const auto& state = _db.get(); const auto& config = _db.get(); @@ -149,7 +152,7 @@ void resource_limits_manager::add_transaction_usage(const flat_set bu.net_usage.add( net_usage, time_slot, config.account_net_usage_average_window ); bu.cpu_usage.add( cpu_usage, time_slot, config.account_cpu_usage_average_window ); - if (auto dm_logger = _get_deep_mind_logger()) { + if (auto dm_logger = _get_deep_mind_logger(is_trx_transient)) { dm_logger->on_update_account_usage(bu); } }); @@ -205,7 +208,7 @@ void resource_limits_manager::add_transaction_usage(const flat_set EOS_ASSERT( state.pending_net_usage <= config.net_limit_parameters.max, block_resource_exhausted, "Block has insufficient net resources" ); } -void resource_limits_manager::add_pending_ram_usage( const account_name account, int64_t ram_delta ) { +void resource_limits_manager::add_pending_ram_usage( const account_name account, int64_t ram_delta, bool is_trx_transient ) { if (ram_delta == 0) { return; } @@ -220,7 +223,7 @@ void resource_limits_manager::add_pending_ram_usage( const account_name account, _db.modify( usage, [&]( auto& u ) { u.ram_usage += ram_delta; - if (auto dm_logger = _get_deep_mind_logger()) { + if (auto dm_logger = _get_deep_mind_logger(is_trx_transient)) { dm_logger->on_ram_event(account, u.ram_usage, ram_delta); } }); @@ -243,7 +246,7 @@ int64_t resource_limits_manager::get_account_ram_usage( const account_name& name } -bool resource_limits_manager::set_account_limits( const account_name& account, int64_t ram_bytes, int64_t net_weight, int64_t cpu_weight) { +bool resource_limits_manager::set_account_limits( const account_name& account, int64_t ram_bytes, int64_t net_weight, int64_t cpu_weight, bool is_trx_transient) { //const auto& usage = _db.get( account ); /* * Since we need to delay these until the next resource limiting boundary, these are created in a "pending" @@ -289,7 +292,7 @@ bool resource_limits_manager::set_account_limits( const account_name& account, i pending_limits.net_weight = net_weight; pending_limits.cpu_weight = cpu_weight; - if (auto dm_logger = _get_deep_mind_logger()) { + if (auto dm_logger = _get_deep_mind_logger(is_trx_transient)) { dm_logger->on_set_account_limits(pending_limits); } }); @@ -356,7 +359,9 @@ void resource_limits_manager::process_account_limit_updates() { multi_index.remove(*itr); } - if (auto dm_logger = _get_deep_mind_logger()) { + // process_account_limit_updates is called by controller::finalize_block, + // where transaction specific logging is not possible + if (auto dm_logger = _get_deep_mind_logger(false)) { dm_logger->on_update_resource_limits_state(state); } }); @@ -376,7 +381,9 @@ void resource_limits_manager::process_block_usage(uint32_t block_num) { state.update_virtual_net_limit(config); state.pending_net_usage = 0; - if (auto dm_logger = _get_deep_mind_logger()) { + // process_block_usage is called by controller::finalize_block, + // where transaction specific logging is not possible + if (auto dm_logger = _get_deep_mind_logger(false)) { dm_logger->on_update_resource_limits_state(state); } }); diff --git a/libraries/chain/transaction.cpp b/libraries/chain/transaction.cpp index 86bfb557b4..c1fb96fb9d 100644 --- a/libraries/chain/transaction.cpp +++ b/libraries/chain/transaction.cpp @@ -62,16 +62,19 @@ fc::microseconds transaction::get_signature_keys( const vector& { try { auto start = fc::time_point::now(); recovered_pub_keys.clear(); - const digest_type digest = sig_digest(chain_id, cfd); - - for(const signature_type& sig : signatures) { - auto now = fc::time_point::now(); - EOS_ASSERT( now < deadline, tx_cpu_usage_exceeded, "transaction signature verification executed for too long ${time}us", - ("time", now - start)("now", now)("deadline", deadline)("start", start) ); - auto[ itr, successful_insertion ] = recovered_pub_keys.emplace( sig, digest ); - EOS_ASSERT( allow_duplicate_keys || successful_insertion, tx_duplicate_sig, - "transaction includes more than one signature signed using the same key associated with public key: ${key}", - ("key", *itr ) ); + + if ( !signatures.empty() ) { + const digest_type digest = sig_digest(chain_id, cfd); + + for(const signature_type& sig : signatures) { + auto now = fc::time_point::now(); + EOS_ASSERT( now < deadline, tx_cpu_usage_exceeded, "transaction signature verification executed for too long ${time}us", + ("time", now - start)("now", now)("deadline", deadline)("start", start) ); + auto[ itr, successful_insertion ] = recovered_pub_keys.emplace( sig, digest ); + EOS_ASSERT( allow_duplicate_keys || successful_insertion, tx_duplicate_sig, + "transaction includes more than one signature signed using the same key associated with public key: ${key}", + ("key", *itr ) ); + } } return fc::time_point::now() - start; diff --git a/libraries/chain/transaction_context.cpp b/libraries/chain/transaction_context.cpp index d5978b63ce..ad777e8229 100644 --- a/libraries/chain/transaction_context.cpp +++ b/libraries/chain/transaction_context.cpp @@ -59,7 +59,7 @@ namespace eosio { namespace chain { ,net_usage(trace->net_usage) ,pseudo_start(s) { - if (!c.skip_db_sessions()) { + if (!c.skip_db_sessions() && !is_read_only()) { undo_session.emplace(c.mutable_db().start_undo_session(true)); } trace->id = packed_trx.id(); @@ -67,7 +67,7 @@ namespace eosio { namespace chain { trace->block_time = c.pending_block_time(); trace->producer_block_id = c.pending_producer_block_id(); - if(auto dm_logger = control.get_deep_mind_logger()) + if(auto dm_logger = c.get_deep_mind_logger(is_transient())) { dm_logger->on_start_transaction(); } @@ -75,7 +75,7 @@ namespace eosio { namespace chain { transaction_context::~transaction_context() { - if(auto dm_logger = control.get_deep_mind_logger()) + if(auto dm_logger = control.get_deep_mind_logger(is_transient())) { dm_logger->on_end_transaction(); } @@ -105,13 +105,13 @@ namespace eosio { namespace chain { _deadline = start + objective_duration_limit; // Possibly lower net_limit to the maximum net usage a transaction is allowed to be billed - if( cfg.max_transaction_net_usage <= net_limit ) { + if( cfg.max_transaction_net_usage <= net_limit && !is_read_only() ) { net_limit = cfg.max_transaction_net_usage; net_limit_due_to_block = false; } // Possibly lower objective_duration_limit to the maximum cpu usage a transaction is allowed to be billed - if( cfg.max_transaction_cpu_usage <= objective_duration_limit.count() ) { + if( cfg.max_transaction_cpu_usage <= objective_duration_limit.count() && !is_read_only() ) { objective_duration_limit = fc::microseconds(cfg.max_transaction_cpu_usage); billing_timer_exception_code = tx_cpu_usage_exceeded::code_value; tx_cpu_usage_reason = tx_cpu_usage_exceeded_reason::on_chain_consensus_max_transaction_cpu_usage; @@ -138,53 +138,60 @@ namespace eosio { namespace chain { } initial_objective_duration_limit = objective_duration_limit; + int64_t account_net_limit = 0; + int64_t account_cpu_limit = 0; - if( explicit_billed_cpu_time ) - validate_cpu_usage_to_bill( billed_cpu_time_us, std::numeric_limits::max(), false, subjective_cpu_bill_us); // Fail early if the amount to be billed is too high + if ( !is_read_only() ) { + if( explicit_billed_cpu_time ) + validate_cpu_usage_to_bill( billed_cpu_time_us, std::numeric_limits::max(), false, subjective_cpu_bill_us); // Fail early if the amount to be billed is too high - // Record accounts to be billed for network and CPU usage - if( control.is_builtin_activated(builtin_protocol_feature_t::only_bill_first_authorizer) ) { - bill_to_accounts.insert( trx.first_authorizer() ); - } else { - for( const auto& act : trx.actions ) { - for( const auto& auth : act.authorization ) { - bill_to_accounts.insert( auth.actor ); + // Record accounts to be billed for network and CPU usage + if( control.is_builtin_activated(builtin_protocol_feature_t::only_bill_first_authorizer) ) { + bill_to_accounts.insert( trx.first_authorizer() ); + } else { + for( const auto& act : trx.actions ) { + for( const auto& auth : act.authorization ) { + bill_to_accounts.insert( auth.actor ); + } } } - } - validate_ram_usage.reserve( bill_to_accounts.size() ); + validate_ram_usage.reserve( bill_to_accounts.size() ); - // Update usage values of accounts to reflect new time - rl.update_account_usage( bill_to_accounts, block_timestamp_type(control.pending_block_time()).slot ); + // Update usage values of accounts to reflect new time + rl.update_account_usage( bill_to_accounts, block_timestamp_type(control.pending_block_time()).slot ); - // Calculate the highest network usage and CPU time that all of the billed accounts can afford to be billed - int64_t account_net_limit = 0; - int64_t account_cpu_limit = 0; - bool greylisted_net = false, greylisted_cpu = false; - std::tie( account_net_limit, account_cpu_limit, greylisted_net, greylisted_cpu) = max_bandwidth_billed_accounts_can_pay(); - net_limit_due_to_greylist |= greylisted_net; - cpu_limit_due_to_greylist |= greylisted_cpu; + // Calculate the highest network usage and CPU time that all of the billed accounts can afford to be billed + bool greylisted_net = false, greylisted_cpu = false; + std::tie( account_net_limit, account_cpu_limit, greylisted_net, greylisted_cpu) = max_bandwidth_billed_accounts_can_pay(); + net_limit_due_to_greylist |= greylisted_net; + cpu_limit_due_to_greylist |= greylisted_cpu; + } eager_net_limit = net_limit; - // Possibly lower eager_net_limit to what the billed accounts can pay plus some (objective) leeway - auto new_eager_net_limit = std::min( eager_net_limit, static_cast(account_net_limit + cfg.net_usage_leeway) ); - if( new_eager_net_limit < eager_net_limit ) { - eager_net_limit = new_eager_net_limit; - net_limit_due_to_block = false; - } + if ( !is_read_only() ) { + // Possibly lower eager_net_limit to what the billed accounts can pay plus some (objective) leeway + auto new_eager_net_limit = std::min( eager_net_limit, static_cast(account_net_limit + cfg.net_usage_leeway) ); + if( new_eager_net_limit < eager_net_limit ) { + eager_net_limit = new_eager_net_limit; + net_limit_due_to_block = false; + } - // Possibly limit deadline if the duration accounts can be billed for (+ a subjective leeway) does not exceed current delta - if( (fc::microseconds(account_cpu_limit) + leeway) <= (_deadline - start) ) { - _deadline = start + fc::microseconds(account_cpu_limit) + leeway; - billing_timer_exception_code = leeway_deadline_exception::code_value; + // Possibly limit deadline if the duration accounts can be billed for (+ a subjective leeway) does not exceed current delta + if( (fc::microseconds(account_cpu_limit) + leeway) <= (_deadline - start) ) { + _deadline = start + fc::microseconds(account_cpu_limit) + leeway; + billing_timer_exception_code = leeway_deadline_exception::code_value; + } } // Possibly limit deadline to subjective max_transaction_time if( max_transaction_time_subjective != fc::microseconds::maximum() && (start + max_transaction_time_subjective) <= _deadline ) { _deadline = start + max_transaction_time_subjective; - tx_cpu_usage_reason = billed_cpu_time_us > 0 ? tx_cpu_usage_exceeded_reason::speculative_executed_adjusted_max_transaction_time : - tx_cpu_usage_exceeded_reason::node_configured_max_transaction_time; + tx_cpu_usage_reason = billed_cpu_time_us > 0 ? + tx_cpu_usage_exceeded_reason::speculative_executed_adjusted_max_transaction_time : + ( is_read_only() ? + tx_cpu_usage_exceeded_reason::node_configured_max_read_only_transaction_time : + tx_cpu_usage_exceeded_reason::node_configured_max_transaction_time ); billing_timer_exception_code = tx_cpu_usage_exceeded::code_value; } @@ -194,20 +201,22 @@ namespace eosio { namespace chain { billing_timer_exception_code = deadline_exception::code_value; } - if( !explicit_billed_cpu_time ) { - int64_t validate_account_cpu_limit = account_cpu_limit - subjective_cpu_bill_us + leeway.count(); // Add leeway to allow powerup - // Possibly limit deadline to account subjective cpu left - if( subjective_cpu_bill_us > 0 && (start + fc::microseconds(validate_account_cpu_limit) < _deadline) ) { - _deadline = start + fc::microseconds(validate_account_cpu_limit); - billing_timer_exception_code = tx_cpu_usage_exceeded::code_value; - tx_cpu_usage_reason = tx_cpu_usage_exceeded_reason::account_cpu_limit; - } + if ( !is_read_only() ) { + if( !explicit_billed_cpu_time ) { + int64_t validate_account_cpu_limit = account_cpu_limit - subjective_cpu_bill_us + leeway.count(); // Add leeway to allow powerup + // Possibly limit deadline to account subjective cpu left + if( subjective_cpu_bill_us > 0 && (start + fc::microseconds(validate_account_cpu_limit) < _deadline) ) { + _deadline = start + fc::microseconds(validate_account_cpu_limit); + billing_timer_exception_code = tx_cpu_usage_exceeded::code_value; + tx_cpu_usage_reason = tx_cpu_usage_exceeded_reason::account_cpu_limit; + } - // Fail early if amount of the previous speculative execution is within 10% of remaining account cpu available - if( validate_account_cpu_limit > 0 ) - validate_account_cpu_limit -= EOS_PERCENT( validate_account_cpu_limit, 10 * config::percent_1 ); - if( validate_account_cpu_limit < 0 ) validate_account_cpu_limit = 0; - validate_account_cpu_usage_estimate( billed_cpu_time_us, validate_account_cpu_limit, subjective_cpu_bill_us ); + // Fail early if amount of the previous speculative execution is within 10% of remaining account cpu available + if( validate_account_cpu_limit > 0 ) + validate_account_cpu_limit -= EOS_PERCENT( validate_account_cpu_limit, 10 * config::percent_1 ); + if( validate_account_cpu_limit < 0 ) validate_account_cpu_limit = 0; + validate_account_cpu_usage_estimate( billed_cpu_time_us, validate_account_cpu_limit, subjective_cpu_bill_us ); + } } // Explicit billed_cpu_time_us should be used, block_deadline will be maximum unless in test code @@ -248,6 +257,9 @@ namespace eosio { namespace chain { uint64_t packed_trx_prunable_size ) { const transaction& trx = packed_trx.get_transaction(); + if ( is_transient() ) { + EOS_ASSERT( trx.delay_sec.value == 0, transaction_exception, "dry-run or read-only transaction cannot be delayed" ); + } if( trx.transaction_extensions.size() > 0 ) { disallow_transaction_extensions( "no transaction extensions supported yet for input transactions" ); } @@ -277,12 +289,17 @@ namespace eosio { namespace chain { published = control.pending_block_time(); is_input = true; if (!control.skip_trx_checks()) { - control.validate_expiration(trx); - control.validate_tapos(trx); + if ( !is_read_only() ) { + control.validate_expiration(trx); + control.validate_tapos(trx); + } validate_referenced_accounts( trx, enforce_whiteblacklist && control.is_speculative_block() ); } - init( initial_net_usage); - record_transaction( packed_trx.id(), trx.expiration ); /// checks for dupes + + init( initial_net_usage ); + if ( !is_read_only() ) { + record_transaction( packed_trx.id(), trx.expiration ); + } } void transaction_context::init_for_deferred_trx( fc::time_point p ) @@ -332,6 +349,13 @@ namespace eosio { namespace chain { void transaction_context::finalize() { EOS_ASSERT( is_initialized, transaction_exception, "must first initialize" ); + // read-only transactions only need net_usage and elapsed in the trace + if ( is_read_only() ) { + net_usage = ((net_usage + 7)/8)*8; // Round up to nearest multiple of word size (8 bytes) + trace->elapsed = fc::time_point::now() - start; + return; + } + if( is_input ) { const transaction& trx = packed_trx.get_transaction(); auto& am = control.get_mutable_authorization_manager(); @@ -383,7 +407,7 @@ namespace eosio { namespace chain { validate_cpu_usage_to_bill( billed_cpu_time_us, account_cpu_limit, true, subjective_cpu_bill_us ); rl.add_transaction_usage( bill_to_accounts, static_cast(billed_cpu_time_us), net_usage, - block_timestamp_type(control.pending_block_time()).slot ); // Should never fail + block_timestamp_type(control.pending_block_time()).slot, is_transient() ); // Should never fail } void transaction_context::squash() { @@ -431,6 +455,9 @@ namespace eosio { namespace chain { case tx_cpu_usage_exceeded_reason::speculative_executed_adjusted_max_transaction_time: limit = max_transaction_time_subjective; return " reached speculative executed adjusted trx max time ${limit}us"; + case tx_cpu_usage_exceeded_reason::node_configured_max_read_only_transaction_time: + limit = max_transaction_time_subjective; + return " reached node configured max-read-only-transaction-time ${limit}us"; } return "unknown tx_cpu_usage_exceeded"; } @@ -588,7 +615,7 @@ namespace eosio { namespace chain { void transaction_context::add_ram_usage( account_name account, int64_t ram_delta ) { auto& rl = control.get_mutable_resource_limits_manager(); - rl.add_pending_ram_usage( account, ram_delta ); + rl.add_pending_ram_usage( account, ram_delta, is_transient() ); if( ram_delta > 0 ) { validate_ram_usage.insert( account ); } @@ -709,7 +736,7 @@ namespace eosio { namespace chain { apply_context acontext( control, *this, action_ordinal, recurse_depth ); if (recurse_depth == 0) { - if (auto dm_logger = control.get_deep_mind_logger()) { + if (auto dm_logger = control.get_deep_mind_logger(is_transient())) { dm_logger->on_input_action(); } } @@ -741,7 +768,7 @@ namespace eosio { namespace chain { gto.expiration = gto.delay_until + fc::seconds(control.get_global_properties().configuration.deferred_trx_expiration_window); trx_size = gto.set( trx ); - if (auto dm_logger = control.get_deep_mind_logger()) { + if (auto dm_logger = control.get_deep_mind_logger(is_transient())) { std::string event_id = RAM_EVENT_ID("${id}", ("id", gto.id)); dm_logger->on_create_deferred(deep_mind_handler::operation_qualifier::push, gto, packed_trx); @@ -789,6 +816,10 @@ namespace eosio { namespace chain { auto* code = db.find(a.account); EOS_ASSERT( code != nullptr, transaction_exception, "action's code account '${account}' does not exist", ("account", a.account) ); + if ( is_read_only() ) { + EOS_ASSERT( a.authorization.size() == 0, transaction_exception, + "read-only action '${name}' cannot have authorizations", ("name", a.name) ); + } for( const auto& auth : a.authorization ) { one_auth = true; auto* actor = db.find(auth.actor); @@ -801,7 +832,7 @@ namespace eosio { namespace chain { actors.insert( auth.actor ); } } - EOS_ASSERT( one_auth || is_dry_run(), tx_no_auths, "transaction must have at least one authorization" ); + EOS_ASSERT( one_auth || is_transient(), tx_no_auths, "transaction must have at least one authorization" ); if( enforce_actor_whitelist_blacklist ) { control.check_actor_list( actors ); diff --git a/libraries/chain/webassembly/privileged.cpp b/libraries/chain/webassembly/privileged.cpp index 4deefa8704..1e8ce2382c 100644 --- a/libraries/chain/webassembly/privileged.cpp +++ b/libraries/chain/webassembly/privileged.cpp @@ -17,14 +17,16 @@ namespace eosio { namespace chain { namespace webassembly { } void interface::preactivate_feature( legacy_ptr feature_digest ) { - context.control.preactivate_feature( *feature_digest ); + EOS_ASSERT(!context.trx_context.is_read_only(), wasm_execution_error, "preactivate_feature not allowed in a readonly transaction"); + context.control.preactivate_feature( *feature_digest, context.trx_context.is_transient() ); } void interface::set_resource_limits( account_name account, int64_t ram_bytes, int64_t net_weight, int64_t cpu_weight ) { + EOS_ASSERT(!context.trx_context.is_read_only(), wasm_execution_error, "set_resource_limits not allowed in a readonly transaction"); EOS_ASSERT(ram_bytes >= -1, wasm_execution_error, "invalid value for ram resource limit expected [-1,INT64_MAX]"); EOS_ASSERT(net_weight >= -1, wasm_execution_error, "invalid value for net resource weight expected [-1,INT64_MAX]"); EOS_ASSERT(cpu_weight >= -1, wasm_execution_error, "invalid value for cpu resource weight expected [-1,INT64_MAX]"); - if( context.control.get_mutable_resource_limits_manager().set_account_limits(account, ram_bytes, net_weight, cpu_weight) ) { + if( context.control.get_mutable_resource_limits_manager().set_account_limits(account, ram_bytes, net_weight, cpu_weight, context.trx_context.is_transient()) ) { context.trx_context.validate_ram_usage.insert( account ); } } @@ -99,6 +101,7 @@ namespace eosio { namespace chain { namespace webassembly { return s; } void interface::set_wasm_parameters_packed( span packed_parameters ) { + EOS_ASSERT(!context.trx_context.is_read_only(), wasm_execution_error, "set_wasm_parameters_packed not allowed in a readonly transaction"); datastream ds( packed_parameters.data(), packed_parameters.size() ); uint32_t version; chain::wasm_config cfg; @@ -113,6 +116,7 @@ namespace eosio { namespace chain { namespace webassembly { ); } int64_t interface::set_proposed_producers( legacy_span packed_producer_schedule) { + EOS_ASSERT(!context.trx_context.is_read_only(), wasm_execution_error, "set_proposed_producers not allowed in a readonly transaction"); datastream ds( packed_producer_schedule.data(), packed_producer_schedule.size() ); std::vector producers; std::vector old_version; @@ -129,6 +133,7 @@ namespace eosio { namespace chain { namespace webassembly { } int64_t interface::set_proposed_producers_ex( uint64_t packed_producer_format, legacy_span packed_producer_schedule) { + EOS_ASSERT(!context.trx_context.is_read_only(), wasm_execution_error, "set_proposed_producers_ex not allowed in a readonly transaction"); if (packed_producer_format == 0) { return set_proposed_producers(std::move(packed_producer_schedule)); } else if (packed_producer_format == 1) { @@ -157,6 +162,7 @@ namespace eosio { namespace chain { namespace webassembly { } void interface::set_blockchain_parameters_packed( legacy_span packed_blockchain_parameters ) { + EOS_ASSERT(!context.trx_context.is_read_only(), wasm_execution_error, "set_blockchain_parameters_packed not allowed in a readonly transaction"); datastream ds( packed_blockchain_parameters.data(), packed_blockchain_parameters.size() ); chain::chain_config_v0 cfg; fc::raw::unpack(ds, cfg); @@ -188,6 +194,7 @@ namespace eosio { namespace chain { namespace webassembly { } void interface::set_parameters_packed( span packed_parameters ){ + EOS_ASSERT(!context.trx_context.is_read_only(), wasm_execution_error, "set_parameters_packed not allowed in a readonly transaction"); datastream ds( packed_parameters.data(), packed_parameters.size() ); chain::chain_config cfg = context.control.get_global_properties().configuration; @@ -207,6 +214,7 @@ namespace eosio { namespace chain { namespace webassembly { } void interface::set_privileged( account_name n, bool is_priv ) { + EOS_ASSERT(!context.trx_context.is_read_only(), wasm_execution_error, "set_privileged not allowed in a readonly transaction"); const auto& a = context.db.get( n ); context.db.modify( a, [&]( auto& ma ){ ma.set_privileged( is_priv ); diff --git a/libraries/chainbase b/libraries/chainbase index 1f058fff94..50540fa7bc 160000 --- a/libraries/chainbase +++ b/libraries/chainbase @@ -1 +1 @@ -Subproject commit 1f058fff94fa10b82c0198508ebe84897c250862 +Subproject commit 50540fa7bca1dc7e0a1df377e7e291d4ca222498 diff --git a/libraries/testing/include/eosio/testing/tester.hpp b/libraries/testing/include/eosio/testing/tester.hpp index 5781a5fc9c..0338df3662 100644 --- a/libraries/testing/include/eosio/testing/tester.hpp +++ b/libraries/testing/include/eosio/testing/tester.hpp @@ -198,7 +198,7 @@ namespace eosio { namespace testing { unapplied_transaction_queue& get_unapplied_transaction_queue() { return unapplied_transactions; } transaction_trace_ptr push_transaction( packed_transaction& trx, fc::time_point deadline = fc::time_point::maximum(), uint32_t billed_cpu_time_us = DEFAULT_BILLED_CPU_TIME_US ); - transaction_trace_ptr push_transaction( signed_transaction& trx, fc::time_point deadline = fc::time_point::maximum(), uint32_t billed_cpu_time_us = DEFAULT_BILLED_CPU_TIME_US, bool no_throw = false ); + transaction_trace_ptr push_transaction( signed_transaction& trx, fc::time_point deadline = fc::time_point::maximum(), uint32_t billed_cpu_time_us = DEFAULT_BILLED_CPU_TIME_US, bool no_throw = false, transaction_metadata::trx_type trx_type = transaction_metadata::trx_type::input ); [[nodiscard]] action_result push_action(action&& cert_act, uint64_t authorizer); // TODO/QUESTION: Is this needed? diff --git a/libraries/testing/tester.cpp b/libraries/testing/tester.cpp index b0e23ab5d8..7b3df4bba6 100644 --- a/libraries/testing/tester.cpp +++ b/libraries/testing/tester.cpp @@ -582,7 +582,8 @@ namespace eosio { namespace testing { transaction_trace_ptr base_tester::push_transaction( signed_transaction& trx, fc::time_point deadline, uint32_t billed_cpu_time_us, - bool no_throw + bool no_throw, + transaction_metadata::trx_type trx_type ) { try { if( !control->is_building_block() ) @@ -597,7 +598,7 @@ namespace eosio { namespace testing { fc::microseconds::maximum() : fc::microseconds( deadline - fc::time_point::now() ); auto ptrx = std::make_shared( trx, c ); - auto fut = transaction_metadata::start_recover_keys( ptrx, control->get_thread_pool(), control->get_chain_id(), time_limit, transaction_metadata::trx_type::input ); + auto fut = transaction_metadata::start_recover_keys( ptrx, control->get_thread_pool(), control->get_chain_id(), time_limit, trx_type ); auto r = control->push_transaction( fut.get(), deadline, fc::microseconds::maximum(), billed_cpu_time_us, billed_cpu_time_us > 0, 0 ); if (no_throw) return r; if( r->except_ptr ) std::rethrow_exception( r->except_ptr ); diff --git a/plugins/chain_api_plugin/chain_api_plugin.cpp b/plugins/chain_api_plugin/chain_api_plugin.cpp index aef0b6d7c7..403fa48e16 100644 --- a/plugins/chain_api_plugin/chain_api_plugin.cpp +++ b/plugins/chain_api_plugin/chain_api_plugin.cpp @@ -130,6 +130,7 @@ void chain_api_plugin::plugin_startup() { CHAIN_RO_CALL(get_required_keys, 200, http_params_types::params_required), CHAIN_RO_CALL(get_transaction_id, 200, http_params_types::params_required), CHAIN_RO_CALL_ASYNC(compute_transaction, chain_apis::read_only::compute_transaction_results, 200, http_params_types::params_required), + CHAIN_RO_CALL_ASYNC(send_read_only_transaction, chain_apis::read_only::send_read_only_transaction_results, 200, http_params_types::params_required), CHAIN_RW_CALL_ASYNC(push_block, chain_apis::read_write::push_block_results, 202, http_params_types::params_required), CHAIN_RW_CALL_ASYNC(push_transaction, chain_apis::read_write::push_transaction_results, 202, http_params_types::params_required), CHAIN_RW_CALL_ASYNC(push_transactions, chain_apis::read_write::push_transactions_results, 202, http_params_types::params_required), diff --git a/plugins/chain_plugin/chain_plugin.cpp b/plugins/chain_plugin/chain_plugin.cpp index c6dbcd0a89..0abd54d9f4 100644 --- a/plugins/chain_plugin/chain_plugin.cpp +++ b/plugins/chain_plugin/chain_plugin.cpp @@ -2557,41 +2557,51 @@ read_only::get_required_keys_result read_only::get_required_keys( const get_requ result.required_keys = required_keys_set; return result; } -void read_only::compute_transaction(const compute_transaction_params& params, next_function next) const { - try { - auto pretty_input = std::make_shared(); - auto resolver = make_resolver(db, abi_serializer::create_yield_function( abi_serializer_max_time )); - try { - abi_serializer::from_variant(params.transaction, *pretty_input, resolver, abi_serializer::create_yield_function( abi_serializer_max_time )); - } EOS_RETHROW_EXCEPTIONS(chain::packed_transaction_type_exception, "Invalid packed transaction") - - app().get_method()(pretty_input, false, transaction_metadata::trx_type::dry_run, true, - [this, next](const std::variant& result) -> void { - if (std::holds_alternative(result)) { - next(std::get(result)); - } else { - auto trx_trace_ptr = std::get(result); +template +void read_only::send_transient_transaction(const Params& params, next_function next, chain::transaction_metadata::trx_type trx_type) const { + try { + auto pretty_input = std::make_shared(); + auto resolver = make_resolver(db, abi_serializer::create_yield_function( abi_serializer_max_time )); + try { + abi_serializer::from_variant(params.transaction, *pretty_input, resolver, abi_serializer::create_yield_function( abi_serializer_max_time )); + } EOS_RETHROW_EXCEPTIONS(chain::packed_transaction_type_exception, "Invalid packed transaction") - try { - fc::variant output; - try { - output = db.to_variant_with_abi( *trx_trace_ptr, abi_serializer::create_yield_function( abi_serializer_max_time ) ); - } catch( chain::abi_exception& ) { - output = *trx_trace_ptr; - } - - const chain::transaction_id_type& id = trx_trace_ptr->id; - next(compute_transaction_results{id, output}); - } CATCH_AND_CALL(next); - } - }); - } catch ( boost::interprocess::bad_alloc& ) { - chain_plugin::handle_db_exhaustion(); - } catch ( const std::bad_alloc& ) { - chain_plugin::handle_bad_alloc(); - } CATCH_AND_CALL(next); + app().get_method()(pretty_input, true /* api_trx */, trx_type, true /* return_failure_trace */, + [this, next](const std::variant& result) -> void { + if (std::holds_alternative(result)) { + next(std::get(result)); + } else { + auto trx_trace_ptr = std::get(result); + + try { + fc::variant output; + try { + output = db.to_variant_with_abi( *trx_trace_ptr, abi_serializer::create_yield_function( abi_serializer_max_time ) ); + } catch( chain::abi_exception& ) { + output = *trx_trace_ptr; + } + + const chain::transaction_id_type& id = trx_trace_ptr->id; + next(Results{id, output}); + } CATCH_AND_CALL(next); + } + }); + } catch ( boost::interprocess::bad_alloc& ) { + chain_plugin::handle_db_exhaustion(); + } catch ( const std::bad_alloc& ) { + chain_plugin::handle_bad_alloc(); + } CATCH_AND_CALL(next); } + +void read_only::compute_transaction(const compute_transaction_params& params, next_function next) const { + return send_transient_transaction(params, next, transaction_metadata::trx_type::dry_run); +} + +void read_only::send_read_only_transaction(const send_read_only_transaction_params& params, next_function next) const { + return send_transient_transaction(params, next, transaction_metadata::trx_type::read_only); +} + read_only::get_transaction_id_result read_only::get_transaction_id( const read_only::get_transaction_id_params& params, const fc::time_point& deadline)const { return params.id(); } diff --git a/plugins/chain_plugin/include/eosio/chain_plugin/chain_plugin.hpp b/plugins/chain_plugin/include/eosio/chain_plugin/chain_plugin.hpp index ce5064829e..3430a6df37 100644 --- a/plugins/chain_plugin/include/eosio/chain_plugin/chain_plugin.hpp +++ b/plugins/chain_plugin/include/eosio/chain_plugin/chain_plugin.hpp @@ -495,6 +495,15 @@ class read_only { void compute_transaction(const compute_transaction_params& params, chain::plugin_interface::next_function next ) const; + struct send_read_only_transaction_results { + chain::transaction_id_type transaction_id; + fc::variant processed; + }; + struct send_read_only_transaction_params { + fc::variant transaction; + }; + void send_read_only_transaction(const send_read_only_transaction_params& params, chain::plugin_interface::next_function next ) const; + static void copy_inline_row(const chain::key_value_object& obj, vector& data) { data.resize( obj.value.size() ); memcpy( data.data(), obj.value.data(), obj.value.size() ); @@ -718,6 +727,10 @@ class read_only { chain::wasm_config wasm_config; }; get_consensus_parameters_results get_consensus_parameters(const get_consensus_parameters_params&, const fc::time_point& deadline) const; + +private: + template + void send_transient_transaction(const Params& params, next_function next, chain::transaction_metadata::trx_type trx_type) const; }; class read_write { @@ -961,4 +974,6 @@ FC_REFLECT( eosio::chain_apis::read_only::get_required_keys_params, (transaction FC_REFLECT( eosio::chain_apis::read_only::get_required_keys_result, (required_keys) ) FC_REFLECT( eosio::chain_apis::read_only::compute_transaction_params, (transaction)) FC_REFLECT( eosio::chain_apis::read_only::compute_transaction_results, (transaction_id)(processed) ) +FC_REFLECT( eosio::chain_apis::read_only::send_read_only_transaction_params, (transaction)) +FC_REFLECT( eosio::chain_apis::read_only::send_read_only_transaction_results, (transaction_id)(processed) ) FC_REFLECT( eosio::chain_apis::read_only::get_consensus_parameters_results, (chain_config)(wasm_config)) diff --git a/plugins/producer_plugin/include/eosio/producer_plugin/producer_plugin.hpp b/plugins/producer_plugin/include/eosio/producer_plugin/producer_plugin.hpp index bf54bc514d..37b08aa028 100644 --- a/plugins/producer_plugin/include/eosio/producer_plugin/producer_plugin.hpp +++ b/plugins/producer_plugin/include/eosio/producer_plugin/producer_plugin.hpp @@ -22,6 +22,7 @@ class producer_plugin : public appbase::plugin { std::optional subjective_cpu_leeway_us; std::optional incoming_defer_ratio; std::optional greylist_limit; + std::optional max_read_only_transaction_time; }; struct whitelist_blacklist { diff --git a/plugins/producer_plugin/producer_plugin.cpp b/plugins/producer_plugin/producer_plugin.cpp index 9a9fdecf82..ad45bf1daf 100644 --- a/plugins/producer_plugin/producer_plugin.cpp +++ b/plugins/producer_plugin/producer_plugin.cpp @@ -360,6 +360,9 @@ class producer_plugin_impl : public std::enable_shared_from_this next) { chain::controller& chain = chain_plug->chain(); - const auto max_trx_time_ms = _max_transaction_time_ms.load(); + const auto max_trx_time_ms = ( trx_type == transaction_metadata::trx_type::read_only ) ? -1 : _max_transaction_time_ms.load(); fc::microseconds max_trx_cpu_usage = max_trx_time_ms < 0 ? fc::microseconds::maximum() : fc::milliseconds( max_trx_time_ms ); auto future = transaction_metadata::start_recover_keys( trx, _thread_pool.get_executor(), @@ -538,7 +541,7 @@ class producer_plugin_impl : public std::enable_shared_from_this& response ) { next( response ); @@ -771,6 +774,8 @@ void producer_plugin::set_program_options( "Number of worker threads in producer thread pool") ("snapshots-dir", bpo::value()->default_value("snapshots"), "the location of the snapshots directory (absolute path or relative to application data dir)") + ("max-read-only-transaction-time", bpo::value()->default_value(my->_max_read_only_transaction_time_ms), + "Limits the maximum time (in milliseconds) a read-only transaction can execute before being considered invalid") ; config_file_options.add(producer_options); } @@ -957,6 +962,8 @@ void producer_plugin::plugin_initialize(const boost::program_options::variables_ } } + my->_max_read_only_transaction_time_ms = options.at("max-read-only-transaction-time").as(); + my->_incoming_block_sync_provider = app().get_method().register_provider( [this](const signed_block_ptr& block, const std::optional& block_id, const block_state_ptr& bsp) { return my->on_incoming_block(block, block_id, bsp); @@ -1136,6 +1143,10 @@ void producer_plugin::update_runtime_options(const runtime_options& options) { if (options.greylist_limit) { chain.set_greylist_limit(*options.greylist_limit); } + + if (options.max_read_only_transaction_time) { + my->_max_read_only_transaction_time_ms = *options.max_read_only_transaction_time; + } } producer_plugin::runtime_options producer_plugin::get_runtime_options() const { @@ -1149,7 +1160,8 @@ producer_plugin::runtime_options producer_plugin::get_runtime_options() const { my->chain_plug->chain().get_subjective_cpu_leeway()->count() : std::optional(), my->_incoming_defer_ratio, - my->chain_plug->chain().get_greylist_limit() + my->chain_plug->chain().get_greylist_limit(), + my->_max_read_only_transaction_time_ms }; } @@ -1443,6 +1455,7 @@ producer_plugin::get_unapplied_transactions( const get_unapplied_transactions_pa auto get_trx_type = [&](trx_enum_type t, transaction_metadata::trx_type type) { if( type == transaction_metadata::trx_type::dry_run ) return "dry_run"; + if( type == transaction_metadata::trx_type::read_only ) return "read_only"; switch( t ) { case trx_enum_type::unknown: return "unknown"; @@ -1976,7 +1989,7 @@ producer_plugin_impl::push_transaction( const fc::time_point& block_deadline, bool disable_subjective_enforcement = (api_trx && _disable_subjective_api_billing) || (!api_trx && _disable_subjective_p2p_billing) - || trx->is_dry_run(); + || trx->is_transient(); auto first_auth = trx->packed_trx()->get_transaction().first_authorizer(); if( !disable_subjective_enforcement && _account_fails.failure_limit( first_auth ) ) { @@ -1992,7 +2005,7 @@ producer_plugin_impl::push_transaction( const fc::time_point& block_deadline, } chain::controller& chain = chain_plug->chain(); - fc::microseconds max_trx_time = fc::milliseconds( _max_transaction_time_ms.load() ); + fc::microseconds max_trx_time = trx->is_read_only() ? fc::milliseconds( _max_read_only_transaction_time_ms ) : fc::milliseconds( _max_transaction_time_ms.load() ); if( max_trx_time.count() < 0 ) max_trx_time = fc::microseconds::maximum(); int64_t sub_bill = 0; @@ -2008,7 +2021,20 @@ producer_plugin_impl::push_transaction( const fc::time_point& block_deadline, } } + // setting and unsetting chainbase read_only mode will be moved to right + // place when multi-threaded read-only transaction is implemented + auto unset_db_read_only_mode = fc::make_scoped_exit([trx, &chain]{ + if( trx->is_read_only() ) + chain.unset_db_read_only_mode(); + }); + if( trx->is_read_only() ) + chain.set_db_read_only_mode(); auto trace = chain.push_transaction( trx, block_deadline, max_trx_time, prev_billed_cpu_time_us, false, sub_bill ); + if( trx->is_read_only() ) { + chain.unset_db_read_only_mode(); + unset_db_read_only_mode.cancel(); + } + auto end = fc::time_point::now(); push_result pr; if( trace->except ) { diff --git a/programs/cleos/httpc.hpp b/programs/cleos/httpc.hpp index fec0a9afa9..ec3654fd51 100644 --- a/programs/cleos/httpc.hpp +++ b/programs/cleos/httpc.hpp @@ -26,6 +26,7 @@ namespace eosio { namespace client { namespace http { const string send_txn_func = chain_func_base + "/send_transaction"; const string push_txn_func = chain_func_base + "/push_transaction"; const string send2_txn_func = chain_func_base + "/send_transaction2"; + const string send_read_only_txn_func = chain_func_base + "/send_read_only_transaction"; const string compute_txn_func = chain_func_base + "/compute_transaction"; const string push_txns_func = chain_func_base + "/push_transactions"; const string json_to_bin_func = chain_func_base + "/abi_json_to_bin"; diff --git a/programs/cleos/main.cpp b/programs/cleos/main.cpp index f08b8a46e3..8baf72379e 100644 --- a/programs/cleos/main.cpp +++ b/programs/cleos/main.cpp @@ -172,6 +172,7 @@ bool tx_print_json = false; bool tx_rtn_failure_trace = true; bool tx_read_only = false; bool tx_dry_run = false; +bool tx_read = false; bool tx_retry_lib = false; uint16_t tx_retry_num_blocks = 0; bool tx_use_old_rpc = false; @@ -446,7 +447,7 @@ fc::variant push_transaction( signed_transaction& trx, const std::vector 0 ) { @@ -464,6 +465,7 @@ fc::variant push_transaction( signed_transaction& trx, const std::vectoradd_option("action", action, localized("A JSON string or filename defining the action to execute on the contract"))->required()->capture_default_str(); actionsSubcommand->add_option("data", data, localized("The arguments to the contract"))->required(); + actionsSubcommand->add_flag("--read", tx_read, localized("Specify an action is read-only")); add_standard_transaction_options_plus_signing(actionsSubcommand); actionsSubcommand->callback([&] { @@ -3933,6 +3940,7 @@ int main( int argc, char** argv ) { add_standard_transaction_options_plus_signing(trxSubcommand); trxSubcommand->add_flag("-o,--read-only", tx_read_only, localized("Deprecated, use --dry-run instead")); trxSubcommand->add_flag("--dry-run", tx_dry_run, localized("Specify a transaction is dry-run")); + trxSubcommand->add_flag("--read", tx_read, localized("Specify a transaction is read-only")); trxSubcommand->callback([&] { fc::variant trx_var = json_from_file_or_string(trx_to_push); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 7d57833de6..56b786633c 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -61,6 +61,7 @@ configure_file(${CMAKE_CURRENT_SOURCE_DIR}/trx_finality_status_test.py ${CMAKE_C configure_file(${CMAKE_CURRENT_SOURCE_DIR}/trx_finality_status_forked_test.py ${CMAKE_CURRENT_BINARY_DIR}/trx_finality_status_forked_test.py COPYONLY) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/plugin_http_api_test.py ${CMAKE_CURRENT_BINARY_DIR}/plugin_http_api_test.py COPYONLY) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/nodeos_contrl_c_test.py ${CMAKE_CURRENT_BINARY_DIR}/nodeos_contrl_c_test.py COPYONLY) +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/read_only_trx_test.py ${CMAKE_CURRENT_BINARY_DIR}/read_only_trx_test.py COPYONLY) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/resource_monitor_plugin_test.py ${CMAKE_CURRENT_BINARY_DIR}/resource_monitor_plugin_test.py COPYONLY) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/light_validation_sync_test.py ${CMAKE_CURRENT_BINARY_DIR}/light_validation_sync_test.py COPYONLY) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/trace_plugin_test.py ${CMAKE_CURRENT_BINARY_DIR}/trace_plugin_test.py COPYONLY) @@ -118,6 +119,8 @@ add_test(NAME nodeos_protocol_feature_test COMMAND tests/nodeos_protocol_feature set_property(TEST nodeos_protocol_feature_test PROPERTY LABELS nonparallelizable_tests) add_test(NAME compute_transaction_test COMMAND tests/compute_transaction_test.py -v -p 2 -n 3 --clean-run WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) set_property(TEST compute_transaction_test PROPERTY LABELS nonparallelizable_tests) +add_test(NAME read_only_trx_test COMMAND tests/read_only_trx_test.py -v -p 2 -n 3 --clean-run WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) +set_property(TEST read_only_trx_test PROPERTY LABELS nonparallelizable_tests) add_test(NAME subjective_billing_test COMMAND tests/subjective_billing_test.py -v -p 2 -n 4 --clean-run WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) set_property(TEST subjective_billing_test PROPERTY LABELS nonparallelizable_tests) add_test(NAME get_account_test COMMAND tests/get_account_test.py -v -p 2 --clean-run WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) diff --git a/tests/read_only_trx_test.py b/tests/read_only_trx_test.py new file mode 100755 index 0000000000..60e4479cf5 --- /dev/null +++ b/tests/read_only_trx_test.py @@ -0,0 +1,142 @@ +#!/usr/bin/env python3 + +import random + +from TestHarness import Account, Cluster, ReturnType, TestHelper, Utils, WalletMgr +from core_symbol import CORE_SYMBOL + +############################################################### +# send_read_only_transaction_tests +# +# Tests to exercise the send_read_only_transaction RPC endpoint functionality. +# More internal tests are in unittest/read_only_trx_tests.cpp +# +############################################################### + +Print=Utils.Print +errorExit=Utils.errorExit + +args=TestHelper.parse_args({"-p","-n","-d","-s","--nodes-file","--seed" + ,"--dump-error-details","-v","--leave-running" + ,"--clean-run","--keep-logs"}) + +pnodes=args.p +topo=args.s +delay=args.d +total_nodes = pnodes if args.n < pnodes else args.n +debug=args.v +nodesFile=args.nodes_file +dontLaunch=nodesFile is not None +seed=args.seed +dontKill=args.leave_running +dumpErrorDetails=args.dump_error_details +killAll=args.clean_run +keepLogs=args.keep_logs + +killWallet=not dontKill +killEosInstances=not dontKill +if nodesFile is not None: + killEosInstances=False + +Utils.Debug=debug +testSuccessful=False + +random.seed(seed) # Use a fixed seed for repeatability. +cluster=Cluster(walletd=True) + +walletMgr=WalletMgr(True) +EOSIO_ACCT_PRIVATE_DEFAULT_KEY = "5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3" +EOSIO_ACCT_PUBLIC_DEFAULT_KEY = "EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV" + +try: + if dontLaunch: # run test against remote cluster + jsonStr=None + with open(nodesFile, "r") as f: + jsonStr=f.read() + if not cluster.initializeNodesFromJson(jsonStr): + errorExit("Failed to initilize nodes from Json string.") + total_nodes=len(cluster.getNodes()) + + walletMgr.killall(allInstances=killAll) + walletMgr.cleanup() + print("Stand up walletd") + if walletMgr.launch() is False: + errorExit("Failed to stand up keosd.") + else: + cluster.killall(allInstances=killAll) + cluster.cleanup() + + Print ("producing nodes: %s, non-producing nodes: %d, topology: %s, delay between nodes launch(seconds): %d" % (pnodes, total_nodes-pnodes, topo, delay)) + + Print("Stand up cluster") + extraNodeosArgs=" --http-max-response-time-ms 990000 --disable-subjective-api-billing false " + if cluster.launch(pnodes=pnodes, totalNodes=total_nodes, topo=topo, delay=delay,extraNodeosArgs=extraNodeosArgs ) is False: + errorExit("Failed to stand up eos cluster.") + + Print ("Wait for Cluster stabilization") + # wait for cluster to start producing blocks + if not cluster.waitOnClusterBlockNumSync(3): + errorExit("Cluster never stabilized") + + producerNode = cluster.getNode() + apiNode = cluster.nodes[-1] + + Utils.Print("create test accounts") + testAccountName = "test" + testAccount = Account(testAccountName) + testAccount.ownerPublicKey = EOSIO_ACCT_PUBLIC_DEFAULT_KEY + testAccount.activePublicKey = EOSIO_ACCT_PUBLIC_DEFAULT_KEY + cluster.createAccountAndVerify(testAccount, cluster.eosioAccount, buyRAM=500000) # 95632 bytes required for test contract + + userAccountName = "user" + userAccount = Account(userAccountName) + userAccount.ownerPublicKey = EOSIO_ACCT_PUBLIC_DEFAULT_KEY + userAccount.activePublicKey = EOSIO_ACCT_PUBLIC_DEFAULT_KEY + cluster.createAccountAndVerify(userAccount, cluster.eosioAccount) + + noAuthTableContractDir="unittests/test-contracts/no_auth_table" + noAuthTableWasmFile="no_auth_table.wasm" + noAuthTableAbiFile="no_auth_table.abi" + Utils.Print("Publish no_auth_table contract") + producerNode.publishContract(testAccount, noAuthTableContractDir,noAuthTableWasmFile, noAuthTableAbiFile, waitForTransBlock=True) + + def sendTransaction(action, data, auth=[], opts=None): + trx = { + "actions": [{ + "account": testAccountName, + "name": action, + "authorization": auth, + "data": data + }] + } + return apiNode.pushTransaction(trx, opts) + + Print("Insert a user") + results = sendTransaction('insert', {"user": userAccountName, "id": 1, "age": 10}, auth=[{"actor": userAccountName, "permission":"active"}]) + assert(results[0]) + apiNode.waitForTransactionInBlock(results[1]['transaction_id']) + + # verify the return value (age) from read-only is the same as created. + Print("Send a read-only Get transaction to verify previous Insert") + results = sendTransaction('getage', {"user": userAccountName}, opts='--read') + assert(results[0]) + assert(results[1]['processed']['action_traces'][0]['return_value_data'] == 10) + + # verify non-read-only modification works + Print("Send a non-read-only Modify transaction") + results = sendTransaction('modify', {"user": userAccountName, "age": 25}, auth=[{"actor": userAccountName, "permission": "active"}]) + assert(results[0]) + apiNode.waitForTransactionInBlock(results[1]['transaction_id']) + + # verify 'cleos push action getage "user": "user" --read' works + Print("Send a read-only Get action") + results = apiNode.pushMessage(testAccountName, 'getage', "{{\"user\": \"{}\"}}".format(userAccountName), opts='--read'); + assert(results[0]) + assert(results[1]['processed']['action_traces'][0]['return_value_data'] == 25) + + testSuccessful = True +finally: + TestHelper.shutdown(cluster, walletMgr, testSuccessful, killEosInstances, killWallet, keepLogs, killAll, dumpErrorDetails) + +errorCode = 0 if testSuccessful else 1 +exit(errorCode) diff --git a/unittests/api_tests.cpp b/unittests/api_tests.cpp index a62710ede7..5e017107bc 100644 --- a/unittests/api_tests.cpp +++ b/unittests/api_tests.cpp @@ -949,23 +949,30 @@ BOOST_FIXTURE_TEST_CASE(checktime_pass_tests, TESTER) { try { template void push_trx(Tester& test, T ac, uint32_t billed_cpu_time_us , uint32_t max_cpu_usage_ms, uint32_t max_block_cpu_ms, - bool explicit_bill, std::vector payload = {}, name account = "testapi"_n ) { + bool explicit_bill, std::vector payload = {}, name account = "testapi"_n, transaction_metadata::trx_type trx_type = transaction_metadata::trx_type::input ) { signed_transaction trx; - auto pl = vector{{account, config::active_name}}; - action act(pl, ac); + action act; + act.account = ac.get_account(); + act.name = ac.get_name(); + if ( trx_type != transaction_metadata::trx_type::read_only ) { + auto pl = vector{{account, config::active_name}}; + act.authorization = pl; + } act.data = payload; trx.actions.push_back(act); test.set_transaction_headers(trx); - auto sigs = trx.sign(test.get_private_key(account, "active"), test.control->get_chain_id()); + if ( trx_type != transaction_metadata::trx_type::read_only ) { + auto sigs = trx.sign(test.get_private_key(account, "active"), test.control->get_chain_id()); + } flat_set keys; trx.get_signature_keys(test.control->get_chain_id(), fc::time_point::maximum(), keys); auto ptrx = std::make_shared( std::move(trx) ); auto fut = transaction_metadata::start_recover_keys( std::move( ptrx ), test.control->get_thread_pool(), test.control->get_chain_id(), fc::microseconds::maximum(), - transaction_metadata::trx_type::input ); + trx_type ); auto res = test.control->push_transaction( fut.get(), fc::time_point::now() + fc::milliseconds(max_block_cpu_ms), fc::milliseconds(max_cpu_usage_ms), billed_cpu_time_us, explicit_bill, 0 ); if( res->except_ptr ) std::rethrow_exception( res->except_ptr ); @@ -974,8 +981,8 @@ void push_trx(Tester& test, T ac, uint32_t billed_cpu_time_us , uint32_t max_cpu template void call_test(Tester& test, T ac, uint32_t billed_cpu_time_us , uint32_t max_cpu_usage_ms, uint32_t max_block_cpu_ms, - std::vector payload = {}, name account = "testapi"_n ) { - push_trx(test, ac, billed_cpu_time_us, max_cpu_usage_ms, max_block_cpu_ms, billed_cpu_time_us > 0, payload, account); + std::vector payload = {}, name account = "testapi"_n, transaction_metadata::trx_type trx_type = transaction_metadata::trx_type::input ) { + push_trx(test, ac, billed_cpu_time_us, max_cpu_usage_ms, max_block_cpu_ms, billed_cpu_time_us > 0, payload, account, trx_type); test.produce_block(); } @@ -1078,6 +1085,11 @@ BOOST_AUTO_TEST_CASE(checktime_pause_max_trx_cpu_extended_test) { try { 0, 5, 50, fc::raw::pack(10000000000000000000ULL), "pause"_n ), tx_cpu_usage_exceeded, fc_exception_message_contains("reached node configured max-transaction-time") ); + // Test hitting max_read_only_transaction_time throws tx_cpu_usage_exceeded + BOOST_CHECK_EXCEPTION( call_test( t, test_pause_action{}, + 0, 5, 50, fc::raw::pack(10000000000000000000ULL), "pause"_n, transaction_metadata::trx_type::read_only ), + tx_cpu_usage_exceeded, fc_exception_message_contains("reached node configured max-read-only-transaction-time") ); + // Test hitting block deadline throws deadline_exception BOOST_CHECK_EXCEPTION( call_test( t, test_pause_action{}, 0, 50, 5, fc::raw::pack(10000000000000000000ULL), "pause"_n ), diff --git a/unittests/read_only_trx_tests.cpp b/unittests/read_only_trx_tests.cpp new file mode 100644 index 0000000000..5db9332481 --- /dev/null +++ b/unittests/read_only_trx_tests.cpp @@ -0,0 +1,339 @@ +#include +#include +#include +#include +#include +#include + +#ifdef NON_VALIDATING_TEST +#define TESTER tester +#else +#define TESTER validating_tester +#endif + +using namespace eosio; +using namespace eosio::chain; +using namespace eosio::testing; +using namespace fc; + +using mvo = fc::mutable_variant_object; + +struct read_only_trx_tester : TESTER { + read_only_trx_tester() { + produce_block(); + }; + + void set_up_test_contract() { + create_accounts( {"noauthtable"_n, "alice"_n} ); + set_code( "noauthtable"_n, test_contracts::no_auth_table_wasm() ); + set_abi( "noauthtable"_n, test_contracts::no_auth_table_abi().data() ); + produce_block(); + + insert_data = abi_ser.variant_to_binary( "insert", mutable_variant_object() + ("user", "alice") ("id", 1) ("age", 10), + abi_serializer::create_yield_function( abi_serializer_max_time ) ); + getage_data = abi_ser.variant_to_binary("getage", mutable_variant_object() + ("user", "alice"), + abi_serializer::create_yield_function( abi_serializer_max_time )); + produce_block(); + } + + void send_action(const action& act) { + signed_transaction trx; + trx.actions.push_back( act ); + set_transaction_headers( trx ); + + push_transaction( trx, fc::time_point::maximum(), DEFAULT_BILLED_CPU_TIME_US, false, transaction_metadata::trx_type::read_only ); + } + + auto send_db_api_transaction( action_name name, bytes data, const vector& auth={{"alice"_n, config::active_name}}, transaction_metadata::trx_type type=transaction_metadata::trx_type::input, uint32_t delay_sec=0 ) { + action act; + signed_transaction trx; + + act.account = "noauthtable"_n; + act.name = name; + act.authorization = auth; + act.data = data; + + trx.actions.push_back( act ); + set_transaction_headers( trx ); + trx.delay_sec = delay_sec; + if ( type == transaction_metadata::trx_type::input ) { + trx.sign(get_private_key("alice"_n, "active"), control->get_chain_id()); + } + + return push_transaction( trx, fc::time_point::maximum(), DEFAULT_BILLED_CPU_TIME_US, false, type ); + } + + void insert_a_record() { + auto res = send_db_api_transaction("insert"_n, insert_data); + BOOST_CHECK_EQUAL(res->receipt->status, transaction_receipt::executed); + produce_block(); + } + + abi_serializer abi_ser{ json::from_string(test_contracts::no_auth_table_abi().data()).as(), abi_serializer::create_yield_function(abi_serializer_max_time )}; + bytes insert_data; + bytes getage_data; +}; + +BOOST_AUTO_TEST_SUITE(read_only_trx_tests) + +BOOST_FIXTURE_TEST_CASE(newaccount_test, read_only_trx_tester) { try { + produce_blocks( 1 ); + + action act = { + {}, + newaccount{ + .creator = config::system_account_name, + .name = "alice"_n, + .owner = authority( get_public_key( "alice"_n, "owner" ) ), + .active = authority( get_public_key( "alice"_n, "active" ) ) + } + }; + + BOOST_CHECK_THROW( send_action(act), action_validate_exception ); +} FC_LOG_AND_RETHROW() } + +BOOST_FIXTURE_TEST_CASE(setcode_test, read_only_trx_tester) { try { + produce_blocks( 1 ); + + std::vector code(10); + action act = { + {}, setcode { "eosio"_n, 0, 0, bytes(code.begin(), code.end()) } + }; + + BOOST_CHECK_THROW( send_action(act), action_validate_exception ); +} FC_LOG_AND_RETHROW() } + +BOOST_FIXTURE_TEST_CASE(setabi_test, read_only_trx_tester) { try { + produce_blocks( 1 ); + + std::vector abi(10); + action act = { + {}, + setabi { + .account = "alice"_n, .abi = bytes(abi.begin(), abi.end()) + } + }; + + BOOST_CHECK_THROW( send_action(act), action_validate_exception ); +} FC_LOG_AND_RETHROW() } + +BOOST_FIXTURE_TEST_CASE(updateauth_test, read_only_trx_tester) { try { + produce_blocks( 1 ); + + auto auth = authority( get_public_key( "alice"_n, "test" ) ); + action act = { + vector{{config::system_account_name,config::active_name}}, + updateauth { + .account = "alice"_n, .permission = "active"_n, .parent = "owner"_n, .auth = auth + } + }; + + BOOST_CHECK_THROW( send_action(act), transaction_exception ); +} FC_LOG_AND_RETHROW() } + + +BOOST_FIXTURE_TEST_CASE(deleteauth_test, read_only_trx_tester) { try { + produce_blocks( 1 ); + + name account = "alice"_n; + name permission = "active"_n; + action act = { + vector{{config::system_account_name,config::active_name}}, + deleteauth { account, permission } + }; + + BOOST_CHECK_THROW( send_action(act), transaction_exception ); +} FC_LOG_AND_RETHROW() } + +BOOST_FIXTURE_TEST_CASE(linkauth_test, read_only_trx_tester) { try { + produce_blocks( 1 ); + + name account = "alice"_n; + name code = "eosio_token"_n; + name type = "transfer"_n; + name requirement = "first"_n; + action act = { + vector{{config::system_account_name,config::active_name}}, + linkauth { account, code, type, requirement } + }; + + BOOST_CHECK_THROW( send_action(act), transaction_exception ); +} FC_LOG_AND_RETHROW() } + +BOOST_FIXTURE_TEST_CASE(unlinkauth_test, read_only_trx_tester) { try { + produce_blocks( 1 ); + + name account = "alice"_n; + name code = "eosio_token"_n; + name type = "transfer"_n; + action act = { + vector{{config::system_account_name,config::active_name}}, + unlinkauth { account, code, type } + }; + + BOOST_CHECK_THROW( send_action(act), transaction_exception ); +} FC_LOG_AND_RETHROW() } + +BOOST_FIXTURE_TEST_CASE(canceldelay_test, read_only_trx_tester) { try { + produce_blocks( 1 ); + + permission_level canceling_auth { config::system_account_name,config::active_name }; + transaction_id_type trx_id { "0718886aa8a3895510218b523d3d694280d1dbc1f6d30e173a10b2039fc894f1" }; + action act = { + vector{{config::system_account_name,config::active_name}}, + canceldelay { canceling_auth, trx_id } + }; + + BOOST_CHECK_THROW( send_action(act), transaction_exception ); +} FC_LOG_AND_RETHROW() } + +BOOST_FIXTURE_TEST_CASE(db_read_only_mode_test, read_only_trx_tester) { try { + set_up_test_contract(); + + insert_a_record(); + + control->set_db_read_only_mode(); + // verify no write is allowed in read-only mode + BOOST_CHECK_THROW( create_account("bob"_n), std::exception ); + + // verify a read-only transaction in read-only mode + auto res = send_db_api_transaction("getage"_n, getage_data, {}, transaction_metadata::trx_type::read_only); + BOOST_CHECK_EQUAL(res->receipt->status, transaction_receipt::executed); + BOOST_CHECK_EQUAL(res->action_traces[0].return_value[0], 10); + control->unset_db_read_only_mode(); + + // verify db write is allowed in regular mode + BOOST_REQUIRE_NO_THROW( create_account("bob"_n) ); +} FC_LOG_AND_RETHROW() } + +BOOST_FIXTURE_TEST_CASE(db_insert_test, read_only_trx_tester) { try { + set_up_test_contract(); + + // verify DB insert is not allowed by read-only transaction + BOOST_CHECK_THROW(send_db_api_transaction("insert"_n, insert_data, {}, transaction_metadata::trx_type::read_only), table_operation_not_permitted); + + // verify DB insert still works with non-read-only transaction after read-only + insert_a_record(); + + // do a read-only transaction and verify the return value (age) is the same as inserted + auto res = send_db_api_transaction("getage"_n, getage_data, {}, transaction_metadata::trx_type::read_only); + BOOST_CHECK_EQUAL(res->receipt->status, transaction_receipt::executed); + BOOST_CHECK_EQUAL(res->action_traces[0].return_value[0], 10); + BOOST_CHECK_GT(res->net_usage, 0); + BOOST_CHECK_GT(res->elapsed.count(), 0); +} FC_LOG_AND_RETHROW() } + +BOOST_FIXTURE_TEST_CASE(auth_test, read_only_trx_tester) { try { + set_up_test_contract(); + + // verify read-only transaction does not allow authorizations. + BOOST_CHECK_THROW(send_db_api_transaction("getage"_n, getage_data, {{"alice"_n, config::active_name}}, transaction_metadata::trx_type::read_only), transaction_exception); +} FC_LOG_AND_RETHROW() } + +BOOST_FIXTURE_TEST_CASE(delay_sec_test, read_only_trx_tester) { try { + set_up_test_contract(); + + // verify read-only transaction does not allow non-zero delay_sec. + BOOST_CHECK_THROW(send_db_api_transaction("getage"_n, getage_data, {}, transaction_metadata::trx_type::read_only, 3), transaction_exception); +} FC_LOG_AND_RETHROW() } + +BOOST_FIXTURE_TEST_CASE(db_modify_test, read_only_trx_tester) { try { + set_up_test_contract(); + + insert_a_record(); + + // verify DB update is not allowed by read-only transaction + auto modify_data = abi_ser.variant_to_binary("modify", mutable_variant_object() + ("user", "alice") ("age", 25), + abi_serializer::create_yield_function( abi_serializer_max_time ) + ); + BOOST_CHECK_THROW(send_db_api_transaction("modify"_n, modify_data, {}, transaction_metadata::trx_type::read_only), table_operation_not_permitted); + + // verify DB update still works in by non-read-only transaction + auto res = send_db_api_transaction("modify"_n, modify_data); + BOOST_CHECK_EQUAL(res->receipt->status, transaction_receipt::executed); + produce_block(); + + // verify the value was successfully updated + res = send_db_api_transaction("getage"_n, getage_data, {}, transaction_metadata::trx_type::read_only); + BOOST_CHECK_EQUAL(res->receipt->status, transaction_receipt::executed); + BOOST_CHECK_EQUAL(res->action_traces[0].return_value[0], 25); + + // verify DB update by secondary key is not allowed by read-only transaction + auto modifybyid_data = abi_ser.variant_to_binary("modifybyid", mutable_variant_object() + ("id", 1) ("age", 50), + abi_serializer::create_yield_function( abi_serializer_max_time ) + ); + BOOST_CHECK_THROW(send_db_api_transaction("modifybyid"_n, modifybyid_data, {}, transaction_metadata::trx_type::read_only), table_operation_not_permitted); + + // verify DB update by secondary key still works in by non-read-only transaction + res = send_db_api_transaction("modifybyid"_n, modifybyid_data); + BOOST_CHECK_EQUAL(res->receipt->status, transaction_receipt::executed); + produce_block(); + + // verify the value was successfully updated + res = send_db_api_transaction("getage"_n, getage_data, {}, transaction_metadata::trx_type::read_only); + BOOST_CHECK_EQUAL(res->receipt->status, transaction_receipt::executed); + BOOST_CHECK_EQUAL(res->action_traces[0].return_value[0], 50); +} FC_LOG_AND_RETHROW() } + +BOOST_FIXTURE_TEST_CASE(db_erase_test, read_only_trx_tester) { try { + set_up_test_contract(); + + insert_a_record(); + + // verify DB erase is not allowed by read-only transaction + auto erase_data = abi_ser.variant_to_binary("erase", mutable_variant_object() + ("user", "alice"), + abi_serializer::create_yield_function( abi_serializer_max_time ) + ); + BOOST_CHECK_THROW(send_db_api_transaction("erase"_n, erase_data, {}, transaction_metadata::trx_type::read_only), table_operation_not_permitted); + + // verify DB erase by secondary key is not allowed by read-only transaction + auto erasebyid_data = abi_ser.variant_to_binary("erasebyid", mutable_variant_object() + ("id", 1), + abi_serializer::create_yield_function( abi_serializer_max_time ) + ); + BOOST_CHECK_THROW(send_db_api_transaction("erasebyid"_n, erasebyid_data, {}, transaction_metadata::trx_type::read_only), table_operation_not_permitted); + + // verify DB erase still works in by non-read-only transaction + auto res = send_db_api_transaction("erase"_n, erase_data); + BOOST_CHECK_EQUAL(res->receipt->status, transaction_receipt::executed); +} FC_LOG_AND_RETHROW() } + +BOOST_FIXTURE_TEST_CASE(sequence_numbers_test, read_only_trx_tester) { try { + set_up_test_contract(); + + const auto& p = control->get_dynamic_global_properties(); + auto receiver_account = control->db().find("noauthtable"_n); + auto amo = control->db().find("alice"_n); + + // verify sequence numbers in state increment for non-read-only transactions + auto prev_global_action_sequence = p.global_action_sequence; + auto prev_recv_sequence = receiver_account->recv_sequence; + auto prev_auth_sequence = amo->auth_sequence; + + auto res = send_db_api_transaction("insert"_n, insert_data); + BOOST_CHECK_EQUAL(res->receipt->status, transaction_receipt::executed); + + BOOST_CHECK_EQUAL( prev_global_action_sequence + 1, p.global_action_sequence ); + BOOST_CHECK_EQUAL( prev_recv_sequence + 1, receiver_account->recv_sequence ); + BOOST_CHECK_EQUAL( prev_auth_sequence + 1, amo->auth_sequence ); + + produce_block(); + + // verify sequence numbers in state do not change for read-only transactions + prev_global_action_sequence = p.global_action_sequence; + prev_recv_sequence = receiver_account->recv_sequence; + prev_auth_sequence = amo->auth_sequence; + + send_db_api_transaction("getage"_n, getage_data, {}, transaction_metadata::trx_type::read_only); + + BOOST_CHECK_EQUAL( prev_global_action_sequence, p.global_action_sequence ); + BOOST_CHECK_EQUAL( prev_recv_sequence, receiver_account->recv_sequence ); + BOOST_CHECK_EQUAL( prev_auth_sequence, amo->auth_sequence ); +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_SUITE_END() diff --git a/unittests/resource_limits_test.cpp b/unittests/resource_limits_test.cpp index ab291cc473..64bb5a6a30 100644 --- a/unittests/resource_limits_test.cpp +++ b/unittests/resource_limits_test.cpp @@ -16,7 +16,7 @@ class resource_limits_fixture: private chainbase_fixture<1024*1024>, public reso public: resource_limits_fixture() :chainbase_fixture() - ,resource_limits_manager(*chainbase_fixture::_db, []() { return nullptr; }) + ,resource_limits_manager(*chainbase_fixture::_db, [](bool) { return nullptr; }) { add_indices(); initialize_database(); @@ -71,8 +71,8 @@ BOOST_AUTO_TEST_SUITE(resource_limits_test) expected_elastic_iterations( desired_virtual_limit, config::default_max_block_cpu_usage, 99, 100 ) - 1; const account_name account(1); - initialize_account(account); - set_account_limits(account, -1, -1, -1); + initialize_account(account, false); + set_account_limits(account, -1, -1, -1, false); process_account_limit_updates(); // relax from the starting state (congested) to the idle state as fast as possible @@ -110,8 +110,8 @@ BOOST_AUTO_TEST_SUITE(resource_limits_test) expected_elastic_iterations( desired_virtual_limit, config::default_max_block_net_usage, 99, 100 ) - 1; const account_name account(1); - initialize_account(account); - set_account_limits(account, -1, -1, -1); + initialize_account(account, false); + set_account_limits(account, -1, -1, -1, false); process_account_limit_updates(); // relax from the starting state (congested) to the idle state as fast as possible @@ -206,8 +206,8 @@ BOOST_AUTO_TEST_SUITE(resource_limits_test) BOOST_FIXTURE_TEST_CASE(enforce_block_limits_cpu, resource_limits_fixture) try { const account_name account(1); - initialize_account(account); - set_account_limits(account, -1, -1, -1 ); + initialize_account(account, false); + set_account_limits(account, -1, -1, -1, false); process_account_limit_updates(); const uint64_t increment = 1000; @@ -223,8 +223,8 @@ BOOST_AUTO_TEST_SUITE(resource_limits_test) BOOST_FIXTURE_TEST_CASE(enforce_block_limits_net, resource_limits_fixture) try { const account_name account(1); - initialize_account(account); - set_account_limits(account, -1, -1, -1 ); + initialize_account(account, false); + set_account_limits(account, -1, -1, -1, false); process_account_limit_updates(); const uint64_t increment = 1000; @@ -245,8 +245,8 @@ BOOST_AUTO_TEST_SUITE(resource_limits_test) const account_name account(1); - initialize_account(account); - set_account_limits(account, limit, -1, -1 ); + initialize_account(account, false); + set_account_limits(account, limit, -1, -1, false); process_account_limit_updates(); for (uint64_t idx = 0; idx < expected_iterations - 1; idx++) { @@ -260,8 +260,8 @@ BOOST_AUTO_TEST_SUITE(resource_limits_test) BOOST_FIXTURE_TEST_CASE(enforce_account_ram_limit_underflow, resource_limits_fixture) try { const account_name account(1); - initialize_account(account); - set_account_limits(account, 100, -1, -1 ); + initialize_account(account, false); + set_account_limits(account, 100, -1, -1, false); verify_account_ram_usage(account); process_account_limit_updates(); BOOST_REQUIRE_THROW(add_pending_ram_usage(account, -101), transaction_exception); @@ -270,8 +270,8 @@ BOOST_AUTO_TEST_SUITE(resource_limits_test) BOOST_FIXTURE_TEST_CASE(enforce_account_ram_limit_overflow, resource_limits_fixture) try { const account_name account(1); - initialize_account(account); - set_account_limits(account, UINT64_MAX, -1, -1 ); + initialize_account(account, false); + set_account_limits(account, UINT64_MAX, -1, -1, false); verify_account_ram_usage(account); process_account_limit_updates(); add_pending_ram_usage(account, UINT64_MAX/2); @@ -290,19 +290,19 @@ BOOST_AUTO_TEST_SUITE(resource_limits_test) const account_name account(1); - initialize_account(account); - set_account_limits(account, limit, -1, -1 ); + initialize_account(account, false); + set_account_limits(account, limit, -1, -1, false); process_account_limit_updates(); add_pending_ram_usage(account, commit); verify_account_ram_usage(account); for (int idx = 0; idx < expected_iterations - 1; idx++) { - set_account_limits(account, limit - increment * idx, -1, -1); + set_account_limits(account, limit - increment * idx, -1, -1, false); verify_account_ram_usage(account); process_account_limit_updates(); } - set_account_limits(account, limit - increment * expected_iterations, -1, -1); + set_account_limits(account, limit - increment * expected_iterations, -1, -1, false); BOOST_REQUIRE_THROW(verify_account_ram_usage(account), ram_usage_exceeded); } FC_LOG_AND_RETHROW(); @@ -319,10 +319,10 @@ BOOST_AUTO_TEST_SUITE(resource_limits_test) double uncongested_cpu_time_per_period = congested_cpu_time_per_period * config::maximum_elastic_resource_multiplier; wdump((uncongested_cpu_time_per_period)); - initialize_account( "dan"_n ); - initialize_account( "everyone"_n ); - set_account_limits( "dan"_n, 0, 0, user_stake ); - set_account_limits( "everyone"_n, 0, 0, (total_staked_tokens - user_stake) ); + initialize_account( "dan"_n, false ); + initialize_account( "everyone"_n, false ); + set_account_limits( "dan"_n, 0, 0, user_stake, false ); + set_account_limits( "everyone"_n, 0, 0, (total_staked_tokens - user_stake), false ); process_account_limit_updates(); // dan cannot consume more than 34 us per day @@ -358,8 +358,8 @@ BOOST_AUTO_TEST_SUITE(resource_limits_test) constexpr uint32_t greylist_limit = config::maximum_elastic_resource_multiplier; const block_timestamp_type time_stamp_now(delta_slot + 1); BOOST_CHECK_LT(delta_slot, window); - initialize_account(test_account); - set_account_limits(test_account, unlimited, unlimited, unlimited); + initialize_account(test_account, false); + set_account_limits(test_account, unlimited, unlimited, unlimited, false); process_account_limit_updates(); // unlimited { @@ -373,7 +373,7 @@ BOOST_AUTO_TEST_SUITE(resource_limits_test) } const int64_t cpu_limit = 2048; const int64_t net_limit = 1024; - set_account_limits(test_account, -1, net_limit, cpu_limit); + set_account_limits(test_account, -1, net_limit, cpu_limit, false); process_account_limit_updates(); // limited, with no usage, current time stamp { diff --git a/unittests/test-contracts/CMakeLists.txt b/unittests/test-contracts/CMakeLists.txt index 4d79070668..a578ef9e01 100644 --- a/unittests/test-contracts/CMakeLists.txt +++ b/unittests/test-contracts/CMakeLists.txt @@ -26,6 +26,7 @@ add_subdirectory( get_sender_test ) add_subdirectory( get_table_test ) add_subdirectory( get_table_seckey_test ) add_subdirectory( integration_test ) +add_subdirectory( no_auth_table ) add_subdirectory( noop ) add_subdirectory( payloadless ) add_subdirectory( proxy ) diff --git a/unittests/test-contracts/no_auth_table/CMakeLists.txt b/unittests/test-contracts/no_auth_table/CMakeLists.txt new file mode 100644 index 0000000000..d4f9cdfcc4 --- /dev/null +++ b/unittests/test-contracts/no_auth_table/CMakeLists.txt @@ -0,0 +1,6 @@ +if( EOSIO_COMPILE_TEST_CONTRACTS ) + add_contract( no_auth_table no_auth_table no_auth_table.cpp ) +else() + configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/no_auth_table.wasm ${CMAKE_CURRENT_BINARY_DIR}/no_auth_table.wasm COPYONLY ) + configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/no_auth_table.abi ${CMAKE_CURRENT_BINARY_DIR}/no_auth_table.abi COPYONLY ) +endif() diff --git a/unittests/test-contracts/no_auth_table/no_auth_table.abi b/unittests/test-contracts/no_auth_table/no_auth_table.abi new file mode 100644 index 0000000000..1e44184c82 --- /dev/null +++ b/unittests/test-contracts/no_auth_table/no_auth_table.abi @@ -0,0 +1,150 @@ +{ + "____comment": "This file was generated with eosio-abigen. DO NOT EDIT ", + "version": "eosio::abi/1.2", + "types": [], + "structs": [ + { + "name": "erase", + "base": "", + "fields": [ + { + "name": "user", + "type": "name" + } + ] + }, + { + "name": "erasebyid", + "base": "", + "fields": [ + { + "name": "id", + "type": "uint64" + } + ] + }, + { + "name": "getage", + "base": "", + "fields": [ + { + "name": "user", + "type": "name" + } + ] + }, + { + "name": "insert", + "base": "", + "fields": [ + { + "name": "user", + "type": "name" + }, + { + "name": "id", + "type": "uint64" + }, + { + "name": "age", + "type": "uint64" + } + ] + }, + { + "name": "modify", + "base": "", + "fields": [ + { + "name": "user", + "type": "name" + }, + { + "name": "age", + "type": "uint64" + } + ] + }, + { + "name": "modifybyid", + "base": "", + "fields": [ + { + "name": "id", + "type": "uint64" + }, + { + "name": "age", + "type": "uint64" + } + ] + }, + { + "name": "person", + "base": "", + "fields": [ + { + "name": "key", + "type": "name" + }, + { + "name": "id", + "type": "uint64" + }, + { + "name": "age", + "type": "uint64" + } + ] + } + ], + "actions": [ + { + "name": "erase", + "type": "erase", + "ricardian_contract": "" + }, + { + "name": "erasebyid", + "type": "erasebyid", + "ricardian_contract": "" + }, + { + "name": "getage", + "type": "getage", + "ricardian_contract": "" + }, + { + "name": "insert", + "type": "insert", + "ricardian_contract": "" + }, + { + "name": "modify", + "type": "modify", + "ricardian_contract": "" + }, + { + "name": "modifybyid", + "type": "modifybyid", + "ricardian_contract": "" + } + ], + "tables": [ + { + "name": "people", + "type": "person", + "index_type": "i64", + "key_names": [], + "key_types": [] + } + ], + "ricardian_clauses": [], + "variants": [], + "action_results": [ + { + "name": "getage", + "result_type": "uint64" + } + ] +} \ No newline at end of file diff --git a/unittests/test-contracts/no_auth_table/no_auth_table.cpp b/unittests/test-contracts/no_auth_table/no_auth_table.cpp new file mode 100644 index 0000000000..5dceb0524d --- /dev/null +++ b/unittests/test-contracts/no_auth_table/no_auth_table.cpp @@ -0,0 +1,87 @@ +#include + +// This creates a simple table without require_auth used. +// It is mainly used to test read-only transactions. + +using namespace eosio; +using namespace std; + +class [[eosio::contract]] no_auth_table : public contract { +public: + using contract::contract; + + no_auth_table(name receiver, name code, datastream ds): contract(receiver, code, ds) {} + + [[eosio::action]] + uint64_t getage(name user) { + person_index people( get_self(), get_first_receiver().value ); + auto iterator = people.find(user.value); + check(iterator != people.end(), "Record does not exist"); + return iterator->age; + } + + [[eosio::action]] + void insert(name user, uint64_t id, uint64_t age) { + person_index people( get_self(), get_first_receiver().value ); + auto iterator = people.find(user.value); + check(iterator == people.end(), "Record already exists"); + people.emplace(user, [&]( auto& row ) { + row.key = user; + row.id = id; + row.age = age; + }); + } + + [[eosio::action]] + void modify(name user, uint64_t age) { + person_index people( get_self(), get_first_receiver().value ); + auto iterator = people.find(user.value); + check(iterator != people.end(), "Record does not exist"); + people.modify(iterator, user, [&]( auto& row ) { + row.key = user; + row.age = age; + }); + } + + [[eosio::action]] + void modifybyid(uint64_t id, uint64_t age) { + person_index people( get_self(), get_first_receiver().value ); + auto secondary_index = people.template get_index<"byid"_n>(); + auto iterator = secondary_index.find(id); + check(iterator != secondary_index.end(), "Record does not exist"); + secondary_index.modify(iterator, get_self(), [&]( auto& row ) { + row.id = id; + row.age = age; + }); + } + + [[eosio::action]] + void erase(name user) { + person_index people( get_self(), get_first_receiver().value); + + auto iterator = people.find(user.value); + check(iterator != people.end(), "Record does not exist"); + people.erase(iterator); + } + + [[eosio::action]] + void erasebyid(uint64_t id) { + person_index people( get_self(), get_first_receiver().value ); + auto secondary_index = people.template get_index<"byid"_n>(); + auto iterator = secondary_index.find(id); + check(iterator != secondary_index.end(), "Record does not exist"); + secondary_index.erase(iterator); + } + +private: + struct [[eosio::table]] person { + name key; + uint64_t id; + uint64_t age; + uint64_t primary_key() const { return key.value; } + uint64_t sec64_key() const { return id; } + }; + using person_index = eosio::multi_index<"people"_n, person, + indexed_by<"byid"_n, const_mem_fun> + >; +}; diff --git a/unittests/test-contracts/no_auth_table/no_auth_table.wasm b/unittests/test-contracts/no_auth_table/no_auth_table.wasm new file mode 100755 index 0000000000..5598f9208e Binary files /dev/null and b/unittests/test-contracts/no_auth_table/no_auth_table.wasm differ diff --git a/unittests/test-contracts/test_api/test_api.cpp b/unittests/test-contracts/test_api/test_api.cpp index e7e3f3e090..d51d3dc71b 100644 --- a/unittests/test-contracts/test_api/test_api.cpp +++ b/unittests/test-contracts/test_api/test_api.cpp @@ -38,7 +38,8 @@ extern "C" { WASM_TEST_HANDLER( test_action, assert_true_cf ); if ( action != WASM_TEST_ACTION("test_transaction", "stateful_api") && - action != WASM_TEST_ACTION("test_transaction", "context_free_api") ) + action != WASM_TEST_ACTION("test_transaction", "context_free_api") && + action != WASM_TEST_ACTION("test_checktime", "checktime_no_auth_failure") ) require_auth(code); //test_types @@ -144,6 +145,7 @@ extern "C" { // test checktime WASM_TEST_HANDLER( test_checktime, checktime_pass ); WASM_TEST_HANDLER( test_checktime, checktime_failure ); + WASM_TEST_HANDLER( test_checktime, checktime_no_auth_failure ); WASM_TEST_HANDLER( test_checktime, checktime_sha1_failure ); WASM_TEST_HANDLER( test_checktime, checktime_assert_sha1_failure ); WASM_TEST_HANDLER( test_checktime, checktime_sha256_failure ); diff --git a/unittests/test-contracts/test_api/test_api.hpp b/unittests/test-contracts/test_api/test_api.hpp index 5ee997c727..cffdc30db8 100644 --- a/unittests/test-contracts/test_api/test_api.hpp +++ b/unittests/test-contracts/test_api/test_api.hpp @@ -291,6 +291,7 @@ struct test_memory { struct test_checktime { static void checktime_pass(); static void checktime_failure(); + static void checktime_no_auth_failure(); static void checktime_sha1_failure(); static void checktime_assert_sha1_failure(); static void checktime_sha256_failure(); diff --git a/unittests/test-contracts/test_api/test_api.wasm b/unittests/test-contracts/test_api/test_api.wasm index 2c57d1c5b1..987a355952 100755 Binary files a/unittests/test-contracts/test_api/test_api.wasm and b/unittests/test-contracts/test_api/test_api.wasm differ diff --git a/unittests/test-contracts/test_api/test_checktime.cpp b/unittests/test-contracts/test_api/test_checktime.cpp index 0057b583c7..5a2b22d10a 100644 --- a/unittests/test-contracts/test_api/test_checktime.cpp +++ b/unittests/test-contracts/test_api/test_checktime.cpp @@ -15,8 +15,7 @@ void test_checktime::checktime_pass() { eosio::print(p); } - -void test_checktime::checktime_failure() { +static void checktime_failure_common() { volatile unsigned long long bound{}; // `volatile' necessary to prevent loop optimization read_action_data( (char*)&bound, sizeof(bound) ); @@ -28,6 +27,15 @@ void test_checktime::checktime_failure() { eosio::print(p); } +void test_checktime::checktime_failure() { + checktime_failure_common(); +} + +// used by tests where authorization is not allowed, like read-only transactions +void test_checktime::checktime_no_auth_failure() { + checktime_failure_common(); +} + constexpr size_t size = 20000000; void test_checktime::checktime_sha1_failure() { diff --git a/unittests/test_contracts.hpp.in b/unittests/test_contracts.hpp.in index cb5f81f79e..9b81dbe59f 100644 --- a/unittests/test_contracts.hpp.in +++ b/unittests/test_contracts.hpp.in @@ -31,6 +31,7 @@ namespace eosio { MAKE_READ_WASM_ABI(get_sender_test, get_sender_test, test-contracts) MAKE_READ_WASM_ABI(get_table_test, get_table_test, test-contracts) MAKE_READ_WASM_ABI(get_table_seckey_test, get_table_seckey_test, test-contracts) + MAKE_READ_WASM_ABI(no_auth_table, no_auth_table, test-contracts) MAKE_READ_WASM_ABI(noop, noop, test-contracts) MAKE_READ_WASM_ABI(payloadless, payloadless, test-contracts) MAKE_READ_WASM_ABI(proxy, proxy, test-contracts)