Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extrinsic Params Refinement #1439

Merged
merged 6 commits into from
Mar 1, 2024

Conversation

tadeohepperle
Copy link
Contributor

Follow-Up PR to #1432, see discussion between James and me there to get some background information.

This PR introduces a new trait RefineParams that the OtherParams (renamed to Params) of signed extensions must implement:

pub trait RefineParams<T: Config> {
    fn refine(&mut self, _account_nonce: u64, _block_number: u64, _block_hash: T::Hash) {}
}

The 'refinement' of params is executed by an online client before creating an extrinsic and can automatically configure the newest account nonce for CheckNonce and the latest block hash for CheckMortality.

/// Note: This function will **not** refine the params to use the latest account nonce and the latest block hash/number.
/// Instead your call will get default values of nonce = 0 and mortality = Immortal for the signed extensions, if the params
/// have not been 'refined' before.
pub fn create_partial_signed_offline<Call>(
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I am not sure about the name here, maybe create_partial_signed_unrefined? Ideally, we would have different implementations of create_partial_signed for offline and online clients, but without specialization I don't see how that would be possible.

Copy link
Collaborator

Choose a reason for hiding this comment

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

An alternative to specialization with stable rust is the autoref workaround. (James pointed this out a while ago and we ended up using it in substrate for metadata v15)

The default implementation should be implemented on the &T. Whenever users what to override that, they must implement it on T. Then the compiler will add as many references as needed to T to find the method call. Not entirely sure if this is applicable here

Copy link
Collaborator

Choose a reason for hiding this comment

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

Hehe it might be possible but we probably shouldn't go down that route for fear of over-complexifying :D

Copy link
Collaborator

Choose a reason for hiding this comment

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

I like the _offline suffix too FWIW, and also this suffix plays nicely with my suggested comment tweak so that it is consistent that if we call an _offline function we get worse defaults because it runs offline.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah I remember reading dtolnays article about autoref specialization on stable rust a while ago, but it seemed like a weird workaround to me.

@tadeohepperle tadeohepperle marked this pull request as ready for review February 26, 2024 15:32
@tadeohepperle tadeohepperle requested a review from a team as a code owner February 26, 2024 15:32
pub trait RefineParams<T: Config> {
/// Refine params to an extrinsic. There is usually some notion of 'the param is already set/unset' in types implementing this trait.
/// The refinement should most likely not affect cases where a param is in a 'is already set by the user' state.
fn refine(&mut self, _account_nonce: u64, _block_number: u64, _block_hash: T::Hash) {}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Are there currently other types that substrate/polkadot extensions take for constructing their state?
I'm thinking more about how we could easily extend this in the future without causing a breaking change

Copy link
Collaborator

@jsdw jsdw Feb 28, 2024

Choose a reason for hiding this comment

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

One thing that we could do to maybe minimise the risk of breaking changes a little is to wrap these into a struct like:

pub struct RefinableParamsData<T: Config> {
    account_nonce: u64,
    block_number: u64,
    block_hash: T::Hash
}

impl <T: Config> RefinableParamsData {
    pub fn account_nonce(&self) -> u64 { ... }
    pub fn block_number(&self) -> u64 { ... }
    pub fn block_hash(&self) -> u64 { ... }
}

That way, we can add params without changing the existing interface :)

//! // here, or can use `create_partial_signed` to fetch the correct nonce.
//! let partial_tx = client.tx().create_partial_signed_with_nonce(
//! // Construct the tx but don't sign it. The account nonce here defaults to 0.
//! // You Can use `create_partial_signed` to fetch the correct nonce.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
//! // You Can use `create_partial_signed` to fetch the correct nonce.
//! // You can use `create_partial_signed` to fetch the correct nonce.

client: Client,
other_params: Self::OtherParams,
other_params: Self::Params,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
other_params: Self::Params,
params: Self::Params,

@@ -12,6 +12,7 @@ mod default_extrinsic_params;
mod extrinsic_params;

pub mod polkadot;
pub mod refine_params;
Copy link
Collaborator

Choose a reason for hiding this comment

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

I wouldn't export this mod I reckon; there's no real need since the only item it exports is below anyway :)

Comment on lines 170 to 171
/// Fetch the latest block header and account nonce from the backend and use them to refine [`ExtrinsicParams::Params`].
pub async fn refine_params(
Copy link
Collaborator

Choose a reason for hiding this comment

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

I wouldn't publically expose this; I think in general that I don't want users to really know about "refining params". If they are offline, they can obtain the relevant data and configure the params exactly as they want, and if they are online then we will be smart and apply some sensible defaults for them is all :)

Comment on lines 107 to 109
/// Note: This function will **not** refine the params to use the latest account nonce and the latest block hash/number.
/// Instead your call will get default values of nonce = 0 and mortality = Immortal for the signed extensions, if the params
/// have not been 'refined' before.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
/// Note: This function will **not** refine the params to use the latest account nonce and the latest block hash/number.
/// Instead your call will get default values of nonce = 0 and mortality = Immortal for the signed extensions, if the params
/// have not been 'refined' before.
/// Note: if not provided, the default account nonce will be set to 0 and the default mortality will be _immortal_.
/// This is because this method runs offline, and so is unable to fetch the data needed for more appropriate values.

Just to make it clear that because offline, we have to use worse defaults, and to avoid mentioning any new concept like "refining params"

Comment on lines 139 to 141
/// Note: This function will **not** refine the params to use the latest account nonce and the latest block hash/number.
/// Instead your call will get default values `nonce = 0` and `mortality = Immortal` for the signed extensions, if the
/// params have not been manually set or 'refined' before.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
/// Note: This function will **not** refine the params to use the latest account nonce and the latest block hash/number.
/// Instead your call will get default values `nonce = 0` and `mortality = Immortal` for the signed extensions, if the
/// params have not been manually set or 'refined' before.
/// Note: if not provided, the default account nonce will be set to 0 and the default mortality will be _immortal_.
/// This is because this method runs offline, and so is unable to fetch the data needed for more appropriate values.

As above

@jsdw
Copy link
Collaborator

jsdw commented Feb 28, 2024

Some small suggestions and such but overall this looks really good, and doesn't feel like it adds too much complexity in the end while giving us the ability to set some great defaults!

It's sortof a shame that we have to ask for the block hash and account nonce etc now every time, but I'm ok with it for now!

(if we think this perf hit is an issue here then we can potentially as an optimisation we could expand the RefinableParams trait with a can_be_refined(&self) -> bool { true } method, and then signed extensions can return false from that when they don't need refining, and if nothing needs refining we can skip obtaining the details at all :))

Copy link
Collaborator

@jsdw jsdw left a comment

Choose a reason for hiding this comment

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

This looks great to me; good job Tadeo!

Copy link
Collaborator

@lexnv lexnv left a comment

Choose a reason for hiding this comment

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

Nice job here!

@tadeohepperle tadeohepperle merged commit 2727f77 into master Mar 1, 2024
13 checks passed
@tadeohepperle tadeohepperle deleted the tadeohepperle/extrinsic-params-refinement branch March 1, 2024 09:04
@jsdw jsdw mentioned this pull request Mar 21, 2024
cmichi added a commit to use-ink/cargo-contract that referenced this pull request Mar 28, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants