Skip to content
This repository has been archived by the owner on Aug 2, 2022. It is now read-only.

Implement RAM_RESTRICTIONS protocol feature #7131

Merged
merged 9 commits into from
Apr 18, 2019
47 changes: 43 additions & 4 deletions libraries/chain/apply_context.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,29 @@ void apply_context::exec_one()
control.get_wasm_interface().apply( receiver_account->code_hash, receiver_account->vm_type, receiver_account->vm_version, *this );
} catch( const wasm_exit& ) {}
}

if( !privileged && control.is_builtin_activated( builtin_protocol_feature_t::ram_restrictions ) ) {
const size_t checktime_interval = 10;
size_t counter = 0;
bool not_in_notify_context = (receiver == act->account);
const auto end = _account_ram_deltas.end();
for( auto itr = _account_ram_deltas.begin(); itr != end; ++itr, ++counter ) {
if( counter == checktime_interval ) {
trx_context.checktime();
counter = 0;
}
if( itr->delta > 0 && itr->account != receiver ) {
EOS_ASSERT( not_in_notify_context, unauthorized_ram_usage_increase,
"unprivileged contract cannot increase RAM usage of another account within a notify context: ${account}",
("account", itr->account)
);
EOS_ASSERT( has_authorization( itr->account ), unauthorized_ram_usage_increase,
"unprivileged contract cannot increase RAM usage of another account that has not authorized the action: ${account}",
("account", itr->account)
);
}
}
}
} FC_RETHROW_EXCEPTIONS( warn, "pending console output: ${console}", ("console", _pending_console_output) )
} catch( const fc::exception& e ) {
action_trace& trace = trx_context.get_action_trace( action_ordinal );
Expand Down Expand Up @@ -373,7 +396,17 @@ void apply_context::schedule_deferred_transaction( const uint128_t& sender_id, a

if( !control.skip_auth_check() && !privileged ) { // Do not need to check authorization if replayng irreversible block or if contract is privileged
if( payer != receiver ) {
require_authorization(payer); /// uses payer's storage
if( control.is_builtin_activated( builtin_protocol_feature_t::ram_restrictions ) ) {
EOS_ASSERT( receiver == act->account, action_validate_exception,
"cannot bill RAM usage of deferred transactions to another account within notify context"
);
EOS_ASSERT( has_authorization( payer ), action_validate_exception,
"cannot bill RAM usage of deferred transaction to another account that has not authorized the action: ${payer}",
("payer", payer)
);
} else {
require_authorization(payer); /// uses payer's storage
}
}

// Originally this code bypassed authorization checks if a contract was deferring only actions to itself.
Expand Down Expand Up @@ -472,8 +505,12 @@ void apply_context::schedule_deferred_transaction( const uint128_t& sender_id, a
} );
}

EOS_ASSERT( control.is_ram_billing_in_notify_allowed() || (receiver == act->account) || (receiver == payer) || privileged,
subjective_block_production_exception, "Cannot charge RAM to other accounts during notify." );
EOS_ASSERT( control.is_builtin_activated( builtin_protocol_feature_t::ram_restrictions )
|| control.is_ram_billing_in_notify_allowed()
|| (receiver == act->account) || (receiver == payer) || privileged,
subjective_block_production_exception,
"Cannot charge RAM to other accounts during notify."
);
add_ram_usage( payer, (config::billable_size_v<generated_transaction_object> + trx_size) );
}

Expand Down Expand Up @@ -549,7 +586,9 @@ bytes apply_context::get_packed_transaction() {

void apply_context::update_db_usage( const account_name& payer, int64_t delta ) {
if( delta > 0 ) {
if( !(privileged || payer == account_name(receiver)) ) {
if( !(privileged || payer == account_name(receiver)
|| control.is_builtin_activated( builtin_protocol_feature_t::ram_restrictions ) ) )
{
EOS_ASSERT( control.is_ram_billing_in_notify_allowed() || (receiver == act->account),
subjective_block_production_exception, "Cannot charge RAM to other accounts during notify." );
require_authorization( payer );
Expand Down
2 changes: 1 addition & 1 deletion libraries/chain/controller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2909,7 +2909,7 @@ bool controller::is_producing_block()const {
}

bool controller::is_ram_billing_in_notify_allowed()const {
return !is_producing_block() || my->conf.allow_ram_billing_in_notify;
return my->conf.disable_all_subjective_mitigations || !is_producing_block() || my->conf.allow_ram_billing_in_notify;
}

void controller::validate_expiration( const transaction& trx )const { try {
Expand Down
2 changes: 2 additions & 0 deletions libraries/chain/include/eosio/chain/exceptions.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,8 @@ namespace eosio { namespace chain {
3050008, "Abort Called" )
FC_DECLARE_DERIVED_EXCEPTION( inline_action_too_big, action_validate_exception,
3050009, "Inline Action exceeds maximum size limit" )
FC_DECLARE_DERIVED_EXCEPTION( unauthorized_ram_usage_increase, action_validate_exception,
3050010, "Action attempts to increase RAM usage of account without authorization" )

FC_DECLARE_DERIVED_EXCEPTION( database_exception, chain_exception,
3060000, "Database exception" )
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ enum class builtin_protocol_feature_t : uint32_t {
restrict_action_to_self,
only_bill_first_authorizer,
forward_setcode,
get_sender
get_sender,
ram_restrictions
};

struct protocol_feature_subjective_restrictions {
Expand Down
22 changes: 22 additions & 0 deletions libraries/chain/protocol_feature_manager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,28 @@ Forward eosio::setcode actions to the WebAssembly code deployed on the eosio acc
Builtin protocol feature: GET_SENDER

Allows contracts to determine which account is the sender of an inline action.
*/
{}
} )
( builtin_protocol_feature_t::ram_restrictions, builtin_protocol_feature_spec{
"RAM_RESTRICTIONS",
fc::variant("1812fdb5096fd854a4958eb9d53b43219d114de0e858ce00255bd46569ad2c68").as<digest_type>(),
// SHA256 hash of the raw message below within the comment delimiters (do not modify message below).
/*
Builtin protocol feature: RAM_RESTRICTIONS

Modifies the restrictions on operations within actions that increase RAM usage of accounts other than the receiver.

An unprivileged contract responding to a notification:
is not allowed to schedule a deferred transaction in which the RAM costs are paid by an account other than the receiver;
but is allowed to execute database operations that increase RAM usage of an account other than the receiver as long as
the action's net effect on RAM usage for the account is to not increase it.

An unprivileged contract executing an action (but not as a response to a notification):
is not allowed to schedule a deferred transaction in which the RAM costs are paid by an account other than the receiver
unless that account authorized the action;
but is allowed to execute database operations that increase RAM usage of an account other than the receiver as long as
either the account authorized the action or the action's net effect on RAM usage for the account is to not increase it.
*/
{}
} )
Expand Down
38 changes: 22 additions & 16 deletions unittests/api_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -589,25 +589,31 @@ BOOST_FIXTURE_TEST_CASE(require_notice_tests, TESTER) { try {

} FC_LOG_AND_RETHROW() }

BOOST_FIXTURE_TEST_CASE(ram_billing_in_notify_tests, TESTER) { try {
produce_blocks(2);
create_account( N(testapi) );
create_account( N(testapi2) );
produce_blocks(10);
set_code( N(testapi), contracts::test_api_wasm() );
produce_blocks(1);
set_code( N(testapi2), contracts::test_api_wasm() );
produce_blocks(1);

BOOST_CHECK_EXCEPTION( CALL_TEST_FUNCTION( *this, "test_action", "test_ram_billing_in_notify", fc::raw::pack( ((unsigned __int128)N(testapi2) << 64) | N(testapi) ) ),
subjective_block_production_exception, fc_exception_message_is("Cannot charge RAM to other accounts during notify.") );
BOOST_AUTO_TEST_CASE(ram_billing_in_notify_tests) { try {
validating_tester chain( validating_tester::default_config() );
chain.execute_setup_policy( setup_policy::preactivate_feature_and_new_bios );

chain.produce_blocks(2);
chain.create_account( N(testapi) );
chain.create_account( N(testapi2) );
chain.produce_blocks(10);
chain.set_code( N(testapi), contracts::test_api_wasm() );
chain.produce_blocks(1);
chain.set_code( N(testapi2), contracts::test_api_wasm() );
chain.produce_blocks(1);

BOOST_CHECK_EXCEPTION( CALL_TEST_FUNCTION( chain, "test_action", "test_ram_billing_in_notify",
fc::raw::pack( ((unsigned __int128)N(testapi2) << 64) | N(testapi) ) ),
subjective_block_production_exception,
fc_exception_message_is("Cannot charge RAM to other accounts during notify.")
);


CALL_TEST_FUNCTION( *this, "test_action", "test_ram_billing_in_notify", fc::raw::pack( ((unsigned __int128)N(testapi2) << 64) | 0 ) );
CALL_TEST_FUNCTION( chain, "test_action", "test_ram_billing_in_notify", fc::raw::pack( ((unsigned __int128)N(testapi2) << 64) | 0 ) );

CALL_TEST_FUNCTION( *this, "test_action", "test_ram_billing_in_notify", fc::raw::pack( ((unsigned __int128)N(testapi2) << 64) | N(testapi2) ) );
CALL_TEST_FUNCTION( chain, "test_action", "test_ram_billing_in_notify", fc::raw::pack( ((unsigned __int128)N(testapi2) << 64) | N(testapi2) ) );

BOOST_REQUIRE_EQUAL( validate(), true );
BOOST_REQUIRE_EQUAL( chain.validate(), true );
} FC_LOG_AND_RETHROW() }

/*************************************************************************************
Expand Down Expand Up @@ -1251,7 +1257,7 @@ BOOST_FIXTURE_TEST_CASE(deferred_transaction_tests, TESTER) { try {
// Payer is alice in this case, this tx should fail since we don't have the authorization of alice
dtt_action dtt_act1;
dtt_act1.payer = N(alice);
BOOST_CHECK_THROW(CALL_TEST_FUNCTION(*this, "test_transaction", "send_deferred_tx_with_dtt_action", fc::raw::pack(dtt_act1)), missing_auth_exception);
BOOST_CHECK_THROW(CALL_TEST_FUNCTION(*this, "test_transaction", "send_deferred_tx_with_dtt_action", fc::raw::pack(dtt_act1)), action_validate_exception);

// Send a tx which in turn sends a deferred tx with the deferred tx's receiver != this tx receiver
// This will include the authorization of the receiver, and impose any related delay associated with the authority
Expand Down
27 changes: 14 additions & 13 deletions unittests/contracts.hpp.in
Original file line number Diff line number Diff line change
Expand Up @@ -39,19 +39,20 @@ namespace eosio {
MAKE_READ_WASM_ABI(before_preactivate_eosio_bios, eosio.bios, contracts/old_versions/v1.6.0-rc3)

// Contracts in `eos/unittests/unittests/test-contracts' directory
MAKE_READ_WASM_ABI(asserter, asserter, test-contracts)
MAKE_READ_WASM_ABI(deferred_test, deferred_test, test-contracts)
MAKE_READ_WASM_ABI(get_sender_test, get_sender_test, 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)
MAKE_READ_WASM_ABI(reject_all, reject_all, test-contracts)
MAKE_READ_WASM_ABI(restrict_action_test, restrict_action_test, test-contracts)
MAKE_READ_WASM_ABI(snapshot_test, snapshot_test, test-contracts)
MAKE_READ_WASM_ABI(test_api, test_api, test-contracts)
MAKE_READ_WASM_ABI(test_api_db, test_api_db, test-contracts)
MAKE_READ_WASM_ABI(test_api_multi_index, test_api_multi_index, test-contracts)
MAKE_READ_WASM_ABI(test_ram_limit, test_ram_limit, test-contracts)
MAKE_READ_WASM_ABI(asserter, asserter, test-contracts)
MAKE_READ_WASM_ABI(deferred_test, deferred_test, test-contracts)
MAKE_READ_WASM_ABI(get_sender_test, get_sender_test, 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)
MAKE_READ_WASM_ABI(ram_restrictions_test, ram_restrictions_test, test-contracts)
MAKE_READ_WASM_ABI(reject_all, reject_all, test-contracts)
MAKE_READ_WASM_ABI(restrict_action_test, restrict_action_test, test-contracts)
MAKE_READ_WASM_ABI(snapshot_test, snapshot_test, test-contracts)
MAKE_READ_WASM_ABI(test_api, test_api, test-contracts)
MAKE_READ_WASM_ABI(test_api_db, test_api_db, test-contracts)
MAKE_READ_WASM_ABI(test_api_multi_index, test_api_multi_index, test-contracts)
MAKE_READ_WASM_ABI(test_ram_limit, test_ram_limit, test-contracts)
};
} /// eosio::testing
} /// eosio
Loading