5

What is the approach to weight calculation in a pallet with a user defined dependency that will do some work,
e.g.

#[pallet::config]
pub trait Config: frame_system::Config {
    type Handler: Handler;
}

trait Handler {
    fn do_thing(input: &[u8]) -> TheResult;
}

#[pallet::call]
impl<T: Config> Pallet<T> {
    #[pallet::weight(T::WeightInfo::my_ext())]
    pub fn my_ext(origin: OriginFor<T>, input: Vec<u8>) -> DispatchResult {
        // T::Handler::do_thing(&input)
    }
}

How to calculate the weight of the extrinsic calling the Handler::do_thing method that could be doing anything?

1 Answer 1

4

A great question.

In general, the nice thing about the benchmarking framework for Substrate is that we run our benchmarks against the precise configuration that you have set up for your actual chain. So as a user changes the configuration of this generic handler (sometimes called a runtime hook), the benchmarks will respond and actually run this code.

However, the issue becomes how can the benchmark know what the worst case scenario for that hook would be? It is impossible to write a benchmark that would test the worst case scenario for some arbitrary user defined code, so you will need to solve this one of a few different ways.

  1. You can write clear expectations to the implementer on the complexity allowed in the hook. For example, the average expectation for hooks is that they should always be fixed time complexity, and thus it would not matter the other inputs into this function, the benchmark would always be accurate in benchmarking the worst case scenario since there is no variability in complexity.

  2. Otherwise, if this hook has some input and variability, and you could clarify that the user is allowed to have some linear complexity over this input, and that your benchmarking will try values over this complexity, and thus get an accurate output. But it is important not to introduce any super-linear complexity to any of the runtime logic.

  3. Ask the user to provide you with the worst case scenario to use in the benchmark. For example, your T::Handler::do_thing(input) could also have a function let worst_input = T::Handler::worst_input_for_do_thing(), and then your benchmarking will fetch the worst case input before running the benchmark and use that as the input into your handler. This is the strategy we used a lot in XCM benchmarking, which is extremely customizable.

  4. Have the implementer write their own custom benchmark which handles the complexity of their custom handler. Remember that benchmarks are not "baked into the pallet". You can always write your own custom benchmarking library which tests all of your extrinsic functions in different ways. As you customize your runtime more and more, this is probably the only reasonable option available since there will always be fundamental assumptions related to writing benchmarks, and thus if you truly want to make a custom chain, you will need custom benchmarks.

2
  • 1
    Would it be recommended to also return PostDispatchInfo in the handler method to refund fees when the function is not doing its worst case?
    – olanod
    Feb 20, 2022 at 0:18
  • 1
    If you can detect that, sure. Refunding weight is always a best practice.
    – Shawn Tabrizi
    Feb 20, 2022 at 1:43

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.