Skip to content
This repository has been archived by the owner on Nov 15, 2023. It is now read-only.

Integrate pallet_contracts gas with the weight system #5712

Merged
merged 8 commits into from
Apr 24, 2020

Conversation

athei
Copy link
Member

@athei athei commented Apr 21, 2020

Related Items

#4147 This was a previous attempt which didn't had a working weight refund to build upon.
#5446 Ground work that rescaled the weights o a higher resolution so they can be used as gas without additional scaling
#5458 Allows our dispatchables to return the actual weight used in order to refund unspent gas.

Overview

This greatly simplifies the gas system in pallet_contracts by integrating it with Substrate's weight system. Additionally it makes contract execution contribute to the weight of a block which is not the case right now. It also fixes the problem of ext_dispatch_call calls not being billed based on their actual weight.

It effectively unifies gas and weight: 1 weight = 1 gas = 1 ps. ps is one pico second of execution on our reference system. However, in order to reduce to code churn of this PR I did not rename all occurrences of the word gas to weight. This could be done in a followup PR. From this PR forward these words are used interchangeably.

Changes

Gas price is gone

The contracts pallet no longer deals with buying and refunding gas. The gas_price is gone. The dispatchables are simply annotated to weigh their supplied gas limit and are billed accordingly by the ChargeTransactionPayment signed extension:

#[weight = FunctionOf(
    |args: (&Weight, &Vec<u8>)| args.0 + MINIMUM_WEIGHT,
    DispatchClass::Normal,
    true
)]
pub fn put_code(
    origin,
    #[compact] gas_limit: Gas,
    code: Vec<u8>
) -> DispatchResultWithPostInfo  {
    // gas is already payed for by the weight annotation
    let mut gas_meter = GasMeter::new(gas_limit);
    ... ??? ...
    // refund unused gas/weight
    gas_meter.into_dispatch_result(result)
}

Using the new weight refund system we can refund unused gas through the ChargeTransactionPayment signed extension.

CheckBlockGasLimit is gone

Because, unlike previously, the gas now contributes to the regular weight of a block we no longer need our own signed extension that checks that a block does not use too much gas. This is now checked by frame_system::CheckWeight as for any other extrinsic.

Remove the gas limit argument from put_code

The weight costs of put_code are known pre dispatch as they only depend on the length of the supplied code which is not executed at that time. Therefore we removed the argument and changed the weight annotation so that it calculates the weight from the code length.

Fees are removed from the config trait

All costs are now specified on the dynamically changeable (by root) Schedule and are specified in Weight / Gas instead of some fixed Balance.

ext_dispatch_call bills based on the real weight

Because it is now possible to refund deducted weight post dispatch we do exactly this on ext_dispatch_call: Remove the weight (+ base cost) from the GasMeter and refund the unspent weight post dispatch.

Tuning the gas schedule

Since we are no longer using a separate currency of gas but effectively weight to formulate the costs of contract actions we need to retune the default Schedule that contains these costs. It looks currently like this (unmodified values):

fn default() -> Schedule {
    Schedule {
        version: 0,
        put_code_per_byte_cost: 1,
        grow_mem_cost: 1,
        regular_op_cost: 1,
        return_data_per_byte_cost: 1,
        event_data_per_byte_cost: 1,
        event_per_topic_cost: 1,
        event_base_cost: 1,
        call_base_cost: 135,
        dispatch_base_cost: 135,
        instantiate_base_cost: 175,
        sandbox_data_read_cost: 1,
        sandbox_data_write_cost: 1,
        transfer_cost: 100,
        instantiate_cost: 200,
        max_event_topics: 4,
        max_stack_height: 64 * 1024,
        max_memory_pages: 16,
        max_table_size: 16 * 1024,
        enable_println: false,
        max_subject_len: 32,
    }
}

Given that one unit here constitutes 1 weight = 1 picosecond of execution these values are way to low now. These are the new values:

// This is 500 (2 instruction per nano second on 2GHZ) * 1000x slowdown through wasmi
const WASM_INSTRUCTION_COST: Gas = 500_000:

fn default() -> Schedule {
    Schedule {
        version: 0,
        put_code_per_byte_cost: WASM_INSTRUCTION_COST,
        grow_mem_cost: WASM_INSTRUCTION_COST,
        regular_op_cost: WASM_INSTRUCTION_COST,
        return_data_per_byte_cost: WASM_INSTRUCTION_COST,
        event_data_per_byte_cost: WASM_INSTRUCTION_COST,
        event_per_topic_cost: WASM_INSTRUCTION_COST,
        event_base_cost: WASM_INSTRUCTION_COST,
        call_base_cost: 135 * WASM_INSTRUCTION_COST,
        dispatch_base_cost: 135 * WASM_INSTRUCTION_COST,
        instantiate_base_cost: 175 * WASM_INSTRUCTION_COST,
        sandbox_data_read_cost: WASM_INSTRUCTION_COST,
        sandbox_data_write_cost: WASM_INSTRUCTION_COST,
        transfer_cost: 100 * WASM_INSTRUCTION_COST,
        instantiate_cost: 200 * WASM_INSTRUCTION_COST,
        max_event_topics: 4,
        max_stack_height: 64 * 1024,
        max_memory_pages: 16,
        max_table_size: 16 * 1024,
        enable_println: false,
        max_subject_len: 32,
    }
}

This is just a ballpark over estimation that scales the old defaults which where also just a ballpark estimation. Benchmarking and properly tuning the gas costs is not in scope here.

@parity-cla-bot
Copy link

It looks like @athei signed our Contributor License Agreement. 👍

Many thanks,

Parity Technologies CLA Bot

frame/contracts/src/lib.rs Outdated Show resolved Hide resolved
@@ -713,7 +700,6 @@ pub type SignedExtra = (
frame_system::CheckNonce<Runtime>,
frame_system::CheckWeight<Runtime>,
pallet_transaction_payment::ChargeTransactionPayment<Runtime>,
pallet_contracts::CheckBlockGasLimit<Runtime>,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As a curious note, this could have been a breaking change.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It brakes chains which use the contracts module as they will be referencing the now gone signed extension.

What are you saying here?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, what do you mean by "referencing"? I was thinking that this could have been a breaking change if CheckBlockGasLimit required including some data into the extrinsics extras (but AFAIK it didn't).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay then you need to define "braking". Because every chain node compiled against an older version of substrate that uses the contracts pallet will no longer compile after this change. That is what I call braking change.

And yes you are right. It did only validation. No inclusion of additional data.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By "breaking" i meant that the format of the extrinsics would have been incompatible.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What would be the implication of that? I mean you would import the old extrinsics with a compatible runtime then upgrade the runtime and expect other extrinsics.

Copy link
Contributor

@tomusdrw tomusdrw left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks reasonable.

// them efficiently.
pub type Gas = u64;
// Gas is essentially the same as weight. It is a 1 to 1 correspondence.
pub type Gas = frame_support::weights::Weight;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would consider making a wrapper type to have type safety and make sure we actually do convert one to another and not blindly pass weight to wherever gas is expected.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Eventually I want to remove Gas as a unit altogether as there is no difference from Weight anymore. So in a sense it is OK to "blindly pass weight to wherever gas is expected". I just decided to leave Gas as is here as changing it would added too many changes to this PR.

frame/contracts/src/gas.rs Outdated Show resolved Hide resolved
frame/contracts/src/gas.rs Outdated Show resolved Hide resolved
frame/contracts/src/gas.rs Outdated Show resolved Hide resolved
frame/contracts/src/wasm/runtime.rs Outdated Show resolved Hide resolved
frame/support/src/lib.rs Outdated Show resolved Hide resolved
@athei
Copy link
Member Author

athei commented Apr 22, 2020

Resolved the TODO by scaling the Schedule modeled after a ballpark worst case estimate for a wasm instruction on wasmi after discussing this with @pepyakin. I also adjusted all tests to supply enough gas.

Ready for review now.

@athei athei added the A0-please_review Pull request needs code review. label Apr 22, 2020
@athei athei marked this pull request as ready for review April 22, 2020 13:25
@pepyakin
Copy link
Contributor

Looks good. The only thing that I'd change is the comment on WASM_INSTRUCTION_COST. Now it looks (not reading the PR description) as something that has some science behind and in reality it doesn't.

So a comment stating that it is just a ballpark and that we need to perform proper benchmarks would be good

athei and others added 2 commits April 22, 2020 18:42
Co-Authored-By: Sergei Pepyakin <sergei@parity.io>
@athei
Copy link
Member Author

athei commented Apr 22, 2020

So a comment stating that it is just a ballpark and that we need to perform proper benchmarks would be good

I agree. Added the comment.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
Status: Done
Development

Successfully merging this pull request may close these issues.

6 participants