-
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
Reference key storage api #447
Conversation
polkadot commit-hash: d96d3bea85 polkadot tag: v0.9.16-rc2 Signed-off-by: Alexandru Vasile <[email protected]>
Signed-off-by: Alexandru Vasile <[email protected]>
Signed-off-by: Alexandru Vasile <[email protected]>
Signed-off-by: Alexandru Vasile <[email protected]>
Signed-off-by: Alexandru Vasile <[email protected]>
Signed-off-by: Alexandru Vasile <[email protected]>
codegen/src/api/storage.rs
Outdated
} | ||
_ => { | ||
let should_ref = storage_entry.name != "Account"; |
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.
So I think this is the case because of the DefaultAccountData
thing that's generated not taking a ref?
I don't really like the inconsistency this would introduce in the storage API (this call doesn't take a ref but other similar calls do). I think it'd be good to see whether the generated DefaultAccountData
impl can be tweaked to take/allow the ref. @ascjones any thoughts on this?
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.
Looking good overall! I'm wondering whether we can avoid the inconsistency/hardcoded string w.r.t that Account entry though :)
Signed-off-by: Alexandru Vasile <[email protected]>
So I had a bit of a dig into this area, and it looks like the purpose of See Line 268 in cb9177a
By default this trait is implemented for some default enum which points at the Line 452 in cb9177a
Account struct that has a lifetime at the mo, which is why you currently are not adding a lifetime to a struct that looks like "Account".
In order to have a consistent storage API where everything provided is a reference (rather than having some exceptional thing that is not a reference), one approach I think would be to generate a version of the "system::Account" type with a reference to be consistent with the rest of the API, but to also generate a version of it without a reference. This non-reference version could then be used by that default You'll need to look for the correct type to do this with sortof like you do now, but it's worth making sure that the entire path of the type is correct (ie it is subxt/codegen/src/api/errors.rs Line 80 in cb9177a
@ascjones does that sound reasonable to you? |
Signed-off-by: Alexandru Vasile <[email protected]>
Signed-off-by: Alexandru Vasile <[email protected]>
Signed-off-by: Alexandru Vasile <[email protected]>
Signed-off-by: Alexandru Vasile <[email protected]>
Signed-off-by: Alexandru Vasile <[email protected]>
Signed-off-by: Alexandru Vasile <[email protected]>
Signed-off-by: Alexandru Vasile <[email protected]>
codegen/src/api/storage.rs
Outdated
let ty_path = type_gen.resolve_type_path(key.id(), &[]); | ||
let fields = vec![(format_ident!("_0"), ty_path.clone())]; | ||
// `::system::storage::Account` was utilized as associated type `StorageEntry` | ||
// for `::subxt::AccountData` implementation of generated `DefaultAccountData`. |
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.
// for `::subxt::AccountData` implementation of generated `DefaultAccountData`. | |
// for `::subxt::AccountData` implementation by the generated `DefaultAccountData`. |
codegen/src/api/storage.rs
Outdated
@@ -74,12 +74,15 @@ fn generate_storage_entry_fns( | |||
storage_entry: &StorageEntryMetadata<PortableForm>, | |||
) -> (TokenStream2, TokenStream2) { | |||
let entry_struct_ident = format_ident!("{}", storage_entry.name); | |||
let (fields, entry_struct, constructor, key_impl) = match storage_entry.ty { | |||
let is_account_wrapper = pallet.name == "System" && storage_entry.name == "Account"; | |||
let wrapper_struct_ident = format_ident!("{}DefaultData", storage_entry.name); |
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.
nitpicking: I'm not super fond of the name AccountDefaultData
; I'd lean towards something like AccountOwned
I guess.. but I'm not too fussed :D
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.
AccountOwned
sounds better :D Thanks
Signed-off-by: Alexandru Vasile <[email protected]>
Signed-off-by: Alexandru Vasile <[email protected]>
Signed-off-by: Alexandru Vasile <[email protected]>
Signed-off-by: Alexandru Vasile <[email protected]>
Signed-off-by: Alexandru Vasile <[email protected]>
Signed-off-by: Alexandru Vasile <[email protected]>
Signed-off-by: Alexandru Vasile <[email protected]>
codegen/src/types/type_path.rs
Outdated
match self { | ||
TypePath::Type(ty) => { | ||
match ty.ty.type_def() { | ||
TypeDef::Sequence(_) => { |
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 things like HashMap and HashSet and such also encode to a TypeDef::Sequence
, so you'll probably also need to check that the .path() == &["Vec"]
:)
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 currently substitute any Sequence<ty>
with std::vec<ty>
(from type_path.rs). It does make sense to constrain it further, but the ty.ty.path()
is empty in this case. Any other ways we could constrain it to just vec
?
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 discussed, my comment wasn't correct in fact, and it looks like only Vec and VecDeque are represented as Sequences, and so I think this is ok!
Signed-off-by: Alexandru Vasile <[email protected]>
Signed-off-by: Alexandru Vasile <[email protected]>
Signed-off-by: Alexandru Vasile <[email protected]>
codegen/src/types/type_path.rs
Outdated
TypePath::Type(ty) => { | ||
match ty.ty.type_def() { | ||
TypeDef::Sequence(_) => { | ||
if ty.params.is_empty() { |
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.
Looking at the scale-info
crate it looks like you could more safely do:
TypeDef::Sequence(seq) => {
let type_param = seq.type_param()
// ...
I'd also lean towards not bothering to quote!
the type_param before returning it (I think it'll work just fine if you don't), but I'm not too bothered either way!
Also as a matter of personal taste I'd probably avoid the indentation with some early returns, a bit like:
let ty = match self {
TypePath::Type(ty) => ty,
_ => return None
};
let sequence = match ty.ty.type_def() {
TypeDef::Sequence(seq) => seq,
_ => return None;
};
// ...
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.
That makes a lot of sense, and indeed the code looks a lot better 😄
I did give the type_param()
a try, but it seems to return a UntrackedSymbol<TypeId>
which does not implement the ToToken
trait.
Looking below in the code generation, it seems under TypeDef::Sequence
there is the assumption of always having at least one type_param. I've removed the is_empty()
check here for consistency.
Wondering if we could run into problems along the line due to this assumption. 🤔
examples/examples/staking_details.rs
Outdated
// You should have received a copy of the GNU General Public License | ||
// along with subxt. If not, see <http://www.gnu.org/licenses/>. | ||
|
||
//! To run this example, a local polkadot node should be running. Example verified against polkadot 0.9.13-82616422d0-aarch64-macos. |
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.
Question: Is this "Example verified against $version" correct for you? I'd either remove it or, better, use polkadot --version
to put whatever version you're using in (if it's different) :)
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.
Or, looking at the below, perhaps the example is using a substrate
instance rather than a polkadot
one (which the other examples are using)? I'd prolly stick to polkadot
just to be the same as the other examples (and consistent with the comments) :)
examples/examples/staking_details.rs
Outdated
let controller_acc = api | ||
.storage() | ||
.staking() | ||
.bonded(&alice_stash_id, None) |
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.
Cool!
I'm wondering whether it is worth renaming this ewxample to fetch_staking_details.rs
so that it lines up with the other fetch
example (both are about fetching stuff out of storage)?
.await?; | ||
let bob_pre = api | ||
.storage() | ||
.system() | ||
.account(bob.account_id().clone(), None) | ||
.account(bob.account_id(), None) |
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.
It's nice not having to clone these any more!
let ctx = test_context().await; | ||
let current_era_result = ctx | ||
let cxt = test_context().await; | ||
let eras = 0; |
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 it's just era
:)
@@ -79,7 +79,7 @@ async fn storage_n_mapish_key_is_properly_created( | |||
}; | |||
|
|||
// This is what the generated code hashes a `session().key_owner(..)` key into: | |||
let actual_key_bytes = KeyOwner(KeyTypeId([1, 2, 3, 4]), vec![5u8, 6, 7, 8]) | |||
let actual_key_bytes = KeyOwner(&KeyTypeId([1, 2, 3, 4]), &[5u8, 6, 7, 8]) |
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.
Nice!
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.
Thanks! Having the API as reference does improve the ergonomics IMO :D
Signed-off-by: Alexandru Vasile <[email protected]>
Signed-off-by: Alexandru Vasile <[email protected]>
Signed-off-by: Alexandru Vasile <[email protected]>
Signed-off-by: Alexandru Vasile <[email protected]>
Signed-off-by: Alexandru Vasile <[email protected]>
match ty.ty.type_def() { | ||
TypeDef::Sequence(_) => Some(&ty.params[0]), | ||
_ => None, | ||
} |
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.
Looking neater; nice :)
// Get Alice' Stash account ID | ||
// | ||
// *Note*: This is pre-funded, as declared in the [genesis state] | ||
// (https://github.com/substrate-developer-hub/substrate-node-template/blob/main/node/src/chain_spec.rs#L49) |
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.
Because it's polkadot and not substrate, I think the link would point to something more like:
But in other examples we've taken for granted that some test accounts have funds in, so I wouldn't bother with a comment about it myself :)
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.
Thanks, i ve removed it for now :D
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.
You'll need to merge in the latest stuff on master and then re-generate that polkadot.rs file, but otherwise lgtm!
Signed-off-by: Alexandru Vasile <[email protected]>
Signed-off-by: Alexandru Vasile <[email protected]>
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 huge usability improvement!
While I'm not a huge fan of the speciaI cased codegen for the Account
storage item, I can see that the AccountOwned
workaround is necessary to make this work with the current storage API, and I can't come up with anything better.
However we should keep an open mind as to how we can improve this in the future to make the codegen less convoluted.
One avenue to explore would be to modify fetch
and fetch_or_default
to accept preconstructed StorageKey
s, and have the macro construct those keys inline instead of generating and calling StorageEntry::key
. Then could modify the AccountData
trait to have a method which constructs the StorageKey
from the AccountId
.
This will all need a bit more thought, so not in the scope of this PR.
A
StorageEntryType::Plain
type requires no changes as it receives no arguments.Changes
If they contain multiple fields, all are bounded to the same lifetime.
With the exception of the
storage::Account
, which is an associated type forDefaultAccountData
trait's implementation.StorageEntry
trait is implemented for an anonymous lifetime.iter
methods bound the lifetime of the generated structs to the named lifetime of theStorageClient
.closes #411.
Example Output
Next Steps
&Vec<_>