massalabs / massa

The Decentralized and Scaled Blockchain
https://massa.net
5.56k stars 712 forks source link

calibrate gas for transaction/roll_buy/roll_sell operations #4464

Closed bilboquet closed 11 months ago

bilboquet commented 1 year ago

relates to: https://www.notion.so/massa-innoteam/Operation-base-gas-98e2f29a11ce41aa8765e7bdb458aac0

Description

Executing an operation costs a "base" amount of computational power. For example, the execution thread is loaded at 60% when we have 5000 simple Transaction operations per second. Transactions, RollBuys, RollSells, ExecuteSCs (with max_gas=0) and CallSCs (with max_gas=0) all cost zero gas.

This means that if someone emits blocks with 4999 Transactions, and a callSC with max_gas=MAX_BLOCK_GAS, the execution of that slot will take more time than allowed by MAX_BLOCK_GAS which causes a security issue.

Same problem for async executions: if max_gas = 0 we will end up executing many async messages without any cost.

Constraints and requirements

Definitions

Remark: gas emerged from wasmer metering. According to previous definitions, for simple operation like execute_transaction_op one can estimate the gas as the ratio:

 gas = {MAX\_BLOC\_GAS \over number\ of\ operation\ in\ 0.5s}

Problem analysis

Involved components

The call graph below shows the main entry point where the gas is consumed: execute_slot. This functions delegates the "real job" to execute_async_message and execute_operation. For execute_operation the gas consumption is estimated by get_gas_usage. The async messages to run are selected by take_batch_to_execute.

Overload with operations

Currently:

Overload with async messages

The following code is an extract of take_batch_to_execute

for (message_id, message_info) in message_infos.iter() {
    if available_gas >= message_info.max_gas
        && slot >= message_info.validity_start
        && slot < message_info.validity_end
        && message_info.can_be_executed
    {
        available_gas -= message_info.max_gas;
        wanted_messages.push(message_id);
    }
}

We see that if message_info.max_gas is 0 a lots of async messages can be selected without taking into account the initialization cost of the virtual machine and leads to an overload of the computational power.

Counter measure

operations

The best solution here seems to evaluate the gas that operations cost and use those values in get_gas_usage:

async messages

Here we want to prevent the selection of message with a max_gas that is less than the gas needed to initialize the VM. The best solution that appears is prevent such messages to even be sent.

The drawback of this solution is that it will fail at runtime while in most circumstance it should be possible to validate the condition before hand.

Note: massa_execution_worker::execution::ExecutionState::execute_executesc_op and massa_execution_worker::execution::ExecutionState::execute_callsc_op may also benefit from the same kind of filter

Optimization

As show below, execute_slot is composed of two major blocs. Simply executing the operations before the async messages would allow to use the "operations gas" to compute more async messages. image

What is unknown for the moment is if there is a requirement to execute operations before async messages.

Note: similarly if there is no async message, maybe we can spend more gas executing operations

Testing

Leo-Besancon commented 1 year ago

Thanks for this write-up! The development aspect of this task seems on track. The only additionnal feedback I would provide is to make sure that it is clear when reading the code what gas-related variable we should use where. (This will disambiguate max_block_gas, max_gas, and the quantity recovered from get_gas_usage).

@Eitu33, could you link your work on the cache and (briefly) edit the relevant paragraphs here?

Eitu33 commented 1 year ago

Here is all the information: https://github.com/massalabs/massa/issues/4483

bilboquet commented 11 months ago

Update regarding async messages: We changed our mind and applied a different solution: We are now applaying a constant cost to async messages, like what is done for operations. see #4546