5

Is there a way to check the exact final_fee that the signer of an extrinsic was charged? I'd like to do so from subxt for monitoring purposes, but could not find this info in the TransactionProgress struct. If this can't be read from events, what would I have to query given the blockhash of the finalized extrinsic?

For reference, I asked the same question in the subxt repo: https://github.com/paritytech/subxt/issues/527

2 Answers 2

9

Just to elaborate on this, since I've been digging into it and asking around...

We can use the following formula to calculate the partial_fee for an extrinsic (ie the total/final fee minus any tip).

partial_fee = base_fee + len_fee + ((adjusted_weight_fee/estimated_weight)*actual_weight)

Where:

  • base_fee is a fixed base fee to include some transaction in a block. It accounts for the work needed to verify the signature and such and is constant for any tx.
  • len_fee is a fee paid based on the size (length in bytes) of the transaction. Longer transactions require more storage.
  • adjusted_weight_fee is a fee that is itself estimated_weight * targeted_fee_adjustment. targeted_fee_adjustment is some adjustment made based on the network load and such, and is an opaque internal value we have no access to.
  • estimated_weight is the "pre-dispatch" weight of the transaction. It's set based on the cost of processing the transaction on reference hardware.
  • actual_weight is the weight that is found in the ExtrinsicSuccess event for the extrinsic in a block (it's just called weight in the event), and is often the same as estimated_weight, but the node has the opportunity to change it to whatever it likes, I think.

The RPC endpoint payment_queryFeeDetails returns base_fee, len_fee and adjusted_weight_fee.

The RPC endpoint payment_queryInfo returns estimated_weight (called weight in the response), and a partialFee value, which is our best guess at the inclusion fee for the tx without actually submitting it and seeing whether the node changes the weight/decides not to take a fee at all.

To get the correct values for some extrinsic from those endpoints, provide the extrinsic bytes, and the block number one before the block it made it into (eg if the extrinsic was in block 100, you'd use block 99 as an argument). Using the correct block number is very important.

Once you've called these endpoints, access the ExtrinsicSuccess event to find the actual_weight (it's just called weight in the event props), but also a paysFee value which signals whether the extrinsic actually incurred a fee at all or not (a node has the opportunity to refund the fee entirely if it likes by setting this to No).

So, the steps to calculate the fee are basically:

  • if paysFee is No, the fee paid is 0
  • else, gather the values needed above and plus them into the equation above to return the final partial fee that was paid.

Why the above equation?!

Well, the basic way to calculate a pre-dispatch fee is:

partial_fee = base_fee + len_fee + adjusted_weight_fee

(see above for explanations of each value).

We can obtain this result just from the payment RPC endpoints.

Once the extrinsic is in a block though, the ExtrinsicSuccess event signals whether the weight used to calculate the fee changed (and whether we need to pay at all via paysFee). So we need to recalculate the adjusted_weight_fee by swapping out the original weight it was based on with the correct weight from the ExtrinsicSuccess event. That's all there really is to it.

Note that doing this calculation may incur some precision loss, and so it's recommended to use high precision maths libraries to get the most accurate values back.

Caveats

Substrate based chains can use whatever custom fee logic they like, and so this logic/calculation only applies to chains that copy the Polkadot fee calculation logic.

There will soon be a transactionPayment::TransactionFeePaid event, which will include the final fee paid for some transaction (ie the inclusion/partial fee + any tip). This will become a more reliable way to calculate fees going forwards (and is on Kusama already), but of course there is always a need to calculate fees for historic blocks too :)

3

There is no event for the final fee, but you can calculate it either using the runtime API for transaction_payment.query_info or by executing the fee calculation from weight/length yourself. Sidecar does the latter here (note that this is an imported Rust package, just linking to its use to see the context).

3
  • This doesn't include the tip though right? where would you query that?
    – Kyle
    Dec 20, 2022 at 14:16
  • It is part of the signed extension in the extrinsic. But as @jsdw said, there is now an event with the fee details that makes it easier. Dec 27, 2022 at 12:13
  • 1
    @joepetrowski to confirm, in order to calculate sum of transaction fees for a given day on Polkadot Relay Chain (at time of writing), one would index TransactionPayment.TransactionFeePaid events and parse the balance in position 1 of the list? (Position 2 is the tip?). Thanks!
    – olliecorbs
    Apr 5, 2023 at 10:05

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.