From 8147efd7d0f3eeae2945bcbc91e0a709023f34db Mon Sep 17 00:00:00 2001 From: dtaghavi Date: Fri, 13 Dec 2024 01:09:56 +0000 Subject: [PATCH] Fixed finalize() fallback logic Fixed an issue in finalize() logic when it checks contracts resources. Logic was incorrect for measuring accounts with unlimited CPU / NET. --- libraries/chain/transaction_context.cpp | 49 +++++++++++++++---------- 1 file changed, 30 insertions(+), 19 deletions(-) diff --git a/libraries/chain/transaction_context.cpp b/libraries/chain/transaction_context.cpp index 79fcda962d..c28b3c8324 100644 --- a/libraries/chain/transaction_context.cpp +++ b/libraries/chain/transaction_context.cpp @@ -358,13 +358,11 @@ namespace sysio { namespace chain { uint32_t greylist_limit = (greylisted_net || greylisted_cpu) ? 1 : config::maximum_elastic_resource_multiplier; - // Possibly lower net_limit to what the billed accounts can pay if (static_cast(account_net_limit) <= net_limit) { net_limit = static_cast(account_net_limit); net_limit_due_to_block = false; } - // Possibly lower objective_duration_limit to what the billed accounts can pay if (account_cpu_limit <= objective_duration_limit.count()) { objective_duration_limit = fc::microseconds(account_cpu_limit); billing_timer_exception_code = tx_cpu_usage_exceeded::code_value; @@ -393,13 +391,11 @@ namespace sysio { namespace chain { } } - // Helper lambda to check if an account is considered a "system" account auto is_system_account = [&](const account_name& acct) { std::string acct_str = acct.to_string(); return (acct == config::system_account_name || acct_str.rfind("sysio.", 0) == 0); }; - // Determine if we are still in a BIOS scenario by checking if all billed accounts are system-level bool only_system_accounts = true; for (const auto& acct : bill_to_accounts) { if (!is_system_account(acct)) { @@ -408,16 +404,15 @@ namespace sysio { namespace chain { } } + // If only system accounts, consider it BIOS (or system initialization) scenario and skip fallback logic if (only_system_accounts) { - // All involved accounts are system or sysio.* accounts - // This implies we are still in the BIOS boot or system initialization phase - // Skip the new fallback billing logic and revert to original behavior return; } - // If we reach this point, we assume BIOS boot is complete and apply fallback logic + // Past BIOS boot, apply fallback logic SYS_ASSERT(user_account.good(), transaction_exception, "No user account found among bill_to_accounts"); + // Get contract resource limits auto [contract_cpu, contract_cpu_grey] = rl.get_account_cpu_limit_ex(contract_account, greylist_limit); auto [contract_net, contract_net_grey] = rl.get_account_net_limit_ex(contract_account, greylist_limit); @@ -425,31 +420,48 @@ namespace sysio { namespace chain { int64_t c_ram_limit, c_net_w, c_cpu_w; rl.get_account_limits(contract_account, c_ram_limit, c_net_w, c_cpu_w); int64_t c_ram_usage = rl.get_account_ram_usage(contract_account); - int64_t c_ram_available = (c_ram_limit < 0) ? std::numeric_limits::max() : (c_ram_limit - c_ram_usage); + int64_t c_ram_available = (c_ram_limit < 0) ? -1 : (c_ram_limit - c_ram_usage); + // Note: If c_ram_limit < 0, it's just a negative limit (not unlimited), handle as insufficient if it doesn't cover usage. - bool contract_can_pay = (contract_cpu.available >= billed_cpu_time_us) - && (contract_net.available >= (int64_t)net_usage) - && (c_ram_available >= total_ram_used); + // Handle unlimited resources for CPU/NET only + bool contract_cpu_unlimited = (contract_cpu.available < 0); + bool contract_net_unlimited = (contract_net.available < 0); + + bool contract_can_pay_cpu = contract_cpu_unlimited || (contract_cpu.available >= billed_cpu_time_us); + bool contract_can_pay_net = contract_net_unlimited || (contract_net.available >= (int64_t)net_usage); + + // RAM: no unlimited concept. Just check c_ram_available. + // If c_ram_limit < 0 or c_ram_available < total_ram_used, then insufficient. + // Negative c_ram_limit or zero doesn't mean unlimited; it means no or insufficient quota. + bool contract_can_pay_ram = (c_ram_available >= total_ram_used); + + bool contract_can_pay = contract_can_pay_cpu && contract_can_pay_net && contract_can_pay_ram; if (contract_can_pay) { - // Charge the contract + // Charge contract rl.add_pending_ram_usage(contract_account, total_ram_used); rl.verify_account_ram_usage(contract_account); rl.add_transaction_usage({contract_account}, static_cast(billed_cpu_time_us), net_usage, block_timestamp_type(control.pending_block_time()).slot ); } else { - // Try user as fallback + // Get user resource limits auto [user_cpu, user_cpu_grey] = rl.get_account_cpu_limit_ex(user_account, greylist_limit); auto [user_net, user_net_grey] = rl.get_account_net_limit_ex(user_account, greylist_limit); int64_t u_ram_limit, u_net_w, u_cpu_w; rl.get_account_limits(user_account, u_ram_limit, u_net_w, u_cpu_w); int64_t u_ram_usage = rl.get_account_ram_usage(user_account); - int64_t u_ram_available = (u_ram_limit < 0) ? std::numeric_limits::max() : (u_ram_limit - u_ram_usage); + int64_t u_ram_available = (u_ram_limit < 0) ? -1 : (u_ram_limit - u_ram_usage); + + bool user_cpu_unlimited = (user_cpu.available < 0); + bool user_net_unlimited = (user_net.available < 0); - bool user_can_pay = (user_cpu.available >= billed_cpu_time_us) - && (user_net.available >= (int64_t)net_usage) - && (u_ram_available >= total_ram_used); + bool user_can_pay_cpu = user_cpu_unlimited || (user_cpu.available >= billed_cpu_time_us); + bool user_can_pay_net = user_net_unlimited || (user_net.available >= (int64_t)net_usage); + + bool user_can_pay_ram = (u_ram_available >= total_ram_used); + + bool user_can_pay = user_can_pay_cpu && user_can_pay_net && user_can_pay_ram; SYS_ASSERT(user_can_pay, resource_exhausted_exception, "Neither contract nor user can pay for this transaction"); @@ -462,7 +474,6 @@ namespace sysio { namespace chain { } - void transaction_context::squash() { if (undo_session) undo_session->squash(); }