-
Notifications
You must be signed in to change notification settings - Fork 248
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
Conversation
There was a problem hiding this 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
There was a problem hiding this 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?
There was a problem hiding this 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).
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. |
I agree this should be moved to the |
@niklasad1, @dvdplm, @ascjones Updated: |
@@ -165,6 +171,28 @@ impl Metadata { | |||
} | |||
string | |||
} | |||
|
|||
/// Derive a mortal period | |||
pub(crate) fn derive_mortal_period(&self) -> Result<u64, MetadataError> { |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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")) |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we can use TimeStamp::MinimumPeriod and double it
let mortal_period = if let Some(period) = self.mortal_period { | ||
period | ||
} else { | ||
match metadata.derive_mortal_period() { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
It is fine from my side but you might want to print the actual error because it might fail for other reasons than |
Co-authored-by: Niklas Adolfsson <niklasadolfsson1@gmail.com>
…rate-subxt into zeke-mortal-ext
Co-authored-by: Niklas Adolfsson <niklasadolfsson1@gmail.com>
…rate-subxt into zeke-mortal-ext
There was a problem hiding this 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.
( | ||
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), |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
Co-authored-by: David <dvdplm@gmail.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
What's the status of this? Waiting for a review from @dvdplm? |
There was a problem hiding this 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?
Just needs merge conflicts fixing now |
Not sure as to the mergability of this since the recent refactoring. Certainly we need to functionality still. |
Closes #202
public api updates
This PR allows for users to configure both mortal and immortal extrinsics through a newSignedOptions
struct, where they can pass in the desiredera_period
for a mortal ext orNone
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 themortal_period
defaults to 64 blocks. Themortal_period
can be set via the client methodset_mortal_period
, whereNone
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 testsNo need to update tests