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

feat: Configurable mortal extrinsic construction #204

Closed
wants to merge 28 commits into from

Conversation

emostov
Copy link
Contributor

@emostov emostov commented Dec 14, 2020

Closes #202

public api updates

This PR allows for users to configure both mortal and immortal extrinsics through a new SignedOptions struct, where they can pass in the desired era_period for a mortal ext or None for immortal ext. I chose this design because I believe it will make it easier to append more options, such as tip (#187), with minimal maintenance.

This PR changes default extrinsic construction to Era::mortal, where the mortal_period defaults to 64 blocks. The mortal_period can be set via the client method set_mortal_period, where None indicates an immortal transaction.

This PR also exposes a DEFAULT_MORTAL_PERIOD to give users a reasonable starting point.

review notes

If there is no immediate major grumbles I go ahead and update tests No need to update tests

@emostov emostov added the enhancement New feature or request label Dec 14, 2020
src/lib.rs Outdated Show resolved Hide resolved
src/lib.rs Outdated Show resolved Hide resolved
src/lib.rs Outdated Show resolved Hide resolved
src/lib.rs Outdated Show resolved Hide resolved
src/lib.rs Outdated Show resolved Hide resolved
Copy link
Member

@niklasad1 niklasad1 left a comment

Choose a reason for hiding this comment

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

Looks overall good but I don't know the rationale why mortal transactions wasn't implemented before

Copy link
Contributor

@dvdplm dvdplm left a comment

Choose a reason for hiding this comment

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

Good stuff here. My main question here is whether we should do more with the proc macro and ask users to annotate their structs with an attribute somehow, e.g.:

#[derive(Call)]
pub struct MyVeryOwnCall<'a, T: Balances> {
  pub to: &'a <T as System>::Address,
  pub amount: T::Balance,
  [#Call(signed_options)]
  pub era_period: u64, 
}

In other words, piggy back on the args parsing machinery we already have and add some annotation to guide its usage? Or maybe I'm completely wrong here and all extrinsics need the era info anyway?

proc-macro/src/call.rs Outdated Show resolved Hide resolved
src/extrinsic/extra.rs Outdated Show resolved Hide resolved
src/extrinsic/mod.rs Outdated Show resolved Hide resolved
src/extrinsic/mod.rs Outdated Show resolved Hide resolved
src/extrinsic/mod.rs Outdated Show resolved Hide resolved
src/lib.rs Outdated Show resolved Hide resolved
src/lib.rs Outdated Show resolved Hide resolved
src/lib.rs Outdated Show resolved Hide resolved
src/lib.rs Outdated Show resolved Hide resolved
Copy link
Contributor

@ascjones ascjones left a comment

Choose a reason for hiding this comment

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

I suggest moving the era into the Signer, in the same way that it handles the nonce. Instead of having this extra options argument to pass around.

Alternatively, or in addition to the above, this option and other similar could be provided as part of the Client, so would get applied to all calls from that client instance (if such an option is likely to be the same across all transactions).

@ascjones
Copy link
Contributor

Good stuff here. My main question here is whether we should do more with the proc macro and ask users to annotate their structs with an attribute somehow, e.g.:

#[derive(Call)]
pub struct MyVeryOwnCall<'a, T: Balances> {
  pub to: &'a <T as System>::Address,
  pub amount: T::Balance,
  [#Call(signed_options)]
  pub era_period: u64, 
}

In other words, piggy back on the args parsing machinery we already have and add some annotation to guide its usage? Or maybe I'm completely wrong here and all extrinsics need the era info anyway?

I think it's good enough to have the global default and leave it up to the callers at runtime to configure the options per call.

@emostov
Copy link
Contributor Author

emostov commented Dec 14, 2020

I suggest moving the era into the Signer, in the same way that it handles the nonce. Instead of having this extra options argument to pass around.

Alternatively, or in addition to the above, this option and other similar could be provided as part of the Client, so would get applied to all calls from that client instance (if such an option is likely to be the same across all transactions).

I agree this should be moved to the Signer or Client as it would likely be the same across transactions for users. I think of mortality preferences being something non-specific to account, so in that sense I think it makes sense to put on the Client - but I think both setups would work.

@emostov
Copy link
Contributor Author

emostov commented Dec 14, 2020

@niklasad1, @dvdplm, @ascjones Updated: mortal_period now lives on the client. I think ready for another round of reviews

src/lib.rs Outdated Show resolved Hide resolved
src/extrinsic/mod.rs Outdated Show resolved Hide resolved
@@ -165,6 +171,28 @@ impl Metadata {
}
string
}

/// Derive a mortal period
pub(crate) fn derive_mortal_period(&self) -> Result<u64, MetadataError> {
Copy link
Member

@niklasad1 niklasad1 Dec 23, 2020

Choose a reason for hiding this comment

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

I was thinking a little bit about the concrete/hardcoded types u32 and u64 here which might not work for all runtimes. Ideally, we would read the types from the runtime but it seems quite complicated to get it to work and pallet-babe is not implemented for substrate-subxt. Thus, I think it's best to leave this out of this PR and open an issue or something.

However, for example if the runtime type parameter was passed in here we could do:

    /// Derive a mortal period
    pub(crate) fn derive_mortal_period<T: Runtime>(&self) -> Result<TODO, MetadataError> {
        let block_hash_count = self
            .module("System")
            .and_then(|sys| sys.constant("BlockHashCount"))
            .and_then(|count| count.value::<<T as System>::BlockNumber>())?;

        let expected_block_time = self
            .module("Babe")
            .and_then(|babe| babe.constant("ExpectedBlockTime"))
            // babe-pallet trait doesn't exist yet....
            .and_then(|e| e.value::<<T as Babe>::ExpectedBlockTime>())?;

        // maybe convert by try_into or something.
        match expected_block_time {
            0 => Err(MetadataError::ZeroValue("Babe::ExpectedBlockTime")),
            expected_block_time => {
                Ok((BASELINE_MORTAL_PERIOD / expected_block_time)
                    .next_power_of_two()
                    .min(block_hash_count.next_power_of_two()))
            }
        }
    }

//cc @ascjones

Copy link
Contributor

Choose a reason for hiding this comment

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

As we speak I am working on upgrading substrate metadata to use scale-info for full type information and once we have that we should be able to do runtime specific builds for subxt

src/metadata.rs Outdated
.map(Into::into)?;
let expected_block_time = self
.module("Babe")
.and_then(|babe| babe.constant("ExpectedBlockTime"))
Copy link
Member

Choose a reason for hiding this comment

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

Is it fair to assume that BABE is used for all runtimes?

Copy link
Contributor Author

@emostov emostov Dec 23, 2020

Choose a reason for hiding this comment

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

No, I think Aura is popular as well... should look into a more generic solution

Copy link
Contributor Author

Choose a reason for hiding this comment

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

src/lib.rs Outdated Show resolved Hide resolved
src/lib.rs Outdated Show resolved Hide resolved
let mortal_period = if let Some(period) = self.mortal_period {
period
} else {
match metadata.derive_mortal_period() {
Copy link
Member

Choose a reason for hiding this comment

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

I had in mind to pass mortal_period into derive_mortal_period in order to check if it was valid but I have no strong opinions on it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Honestly it is not even clear to me what would be an invalid mortal period a user could set. I think we could check if mortal_period <= BlockHashCount.next_power_of_two(), but the tx will still be processed by the node with larger values. Knowing that, it seems artificially limiting, especially if for some reason a runtime follows a different rule.

Copy link
Member

Choose a reason for hiding this comment

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

Fair enough, I was thinking of the initial annoyance, if the mortal_period > BlockHashCount

@niklasad1
Copy link
Member

Is this overkill?

It is fine from my side but you might want to print the actual error because it might fail for other reasons than mortal_period :)

src/lib.rs Outdated Show resolved Hide resolved
Copy link
Contributor

@dvdplm dvdplm left a comment

Choose a reason for hiding this comment

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

I'm a bit out of my depth here, but left a few comments and questions.

src/metadata.rs Outdated Show resolved Hide resolved
src/lib.rs Show resolved Hide resolved
src/lib.rs Outdated Show resolved Hide resolved
src/lib.rs Outdated Show resolved Hide resolved
src/lib.rs Outdated Show resolved Hide resolved
(
CheckSpecVersion(PhantomData, self.spec_version),
CheckTxVersion(PhantomData, self.tx_version),
CheckGenesis(PhantomData, self.genesis_hash),
CheckEra((Era::Immortal, PhantomData), self.genesis_hash),
CheckEra((self.era_info.0, PhantomData), era_hash),
Copy link
Contributor

Choose a reason for hiding this comment

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

This is a bit clunky. Ideally we'd have CheckEra(PhantomData, self.era_info) but that seems tricky to make work?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The inner tuple is what actually gets encoded, while the era_hash is not part of the CheckEra struct in Substrate and is just used here for implementing additional_signed. We could make a function like create_check_era(PhantomData, self.era_info), but it seems like that would just create unnecessary code.

Substrate FRAME nodes do not need the hash because they have a map of canon block number => block hash, which we do not have

emostov and others added 2 commits December 29, 2020 10:30
Copy link
Member

@niklasad1 niklasad1 left a comment

Choose a reason for hiding this comment

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

LGTM

@emostov emostov requested a review from dvdplm January 25, 2021 02:33
@ascjones
Copy link
Contributor

What's the status of this? Waiting for a review from @dvdplm?

@paritytech paritytech deleted a comment from cla-bot-2021 bot Jun 3, 2021
Copy link
Contributor

@dvdplm dvdplm left a comment

Choose a reason for hiding this comment

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

Better late than never?

@ascjones
Copy link
Contributor

Just needs merge conflicts fixing now

@ascjones
Copy link
Contributor

Not sure as to the mergability of this since the recent refactoring. Certainly we need to functionality still.

@ascjones
Copy link
Contributor

I believe this has now been superseded by #490 /cc @jsdw

@ascjones ascjones closed this Mar 31, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Extrinsic construction should default to Era::mortal
5 participants