-
Notifications
You must be signed in to change notification settings - Fork 700
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
Implement pallet view function queries #4722
base: master
Are you sure you want to change the base?
Conversation
Multiple runtimes importing the same pallets will offer the same semantics. Thats the whole point of bundling them into modules (aka pallets) and not implementing them at runtime level like a generic runtime API. And yes runtime wide view functions will put a slight wrinkle into this plan. But we can also just package them into a crate that we distribute and that everybody imports and wires them up with their pallets. Maybe you can view that as some sort of spec. |
Regarding the hashing: no one is strongly backing it, we should set our aim to merge this PR with old school dispatch. This method also has not benefit per-se, but it is at least more consistent. Some final counterarguments:
Having all this conversation is a good encouragement to already start thinking about a migration plan into compact encoded indices for enums to eliminate this time bomb. |
This all makes sense, and yup this is the bit that I'm thinking about as a spec, and I agree that the spec can be implemented in the form of code and probably from this we can generate documentation to also describe it to people who want to then use the facade interface at a higher level (client facade implementations for instance) |
Just a general thought on the naming in this PR: How do people feel about calling these "Pallet APIs" rather than "Pallet view functions"? My thought is to be consistent with the term "Runtime APIs", which (from the client perspective at least) act very similar; read-only functions that can be called with some arguments and return some value back. |
@kianenigma I was thinking what the metadata would look like in order that clients know how to call pallet view functions. For the case of eg transactions, we provide an enum in the metadata for each pallet, and that works great because each variant name corresponds to the transaction name and each enum field correpsonds to one of the transaction arguments. For the case of Pallet View Functions (same as rutime APIs), an enum doesn't provide enough information (ie we also need to know the return type for each Pallet View Function. So I suppose the metadata for the ciew functions in a pallet using enum dispatch would look more like: view_functions: vec![
PalletViewFunction {
index: 1u8,
name: String,
inputs: vec![12, 34, 8], // u32 type IDs for each argument
output: 34, // u32 type ID for return type,
docs: Vec<String>
},
// ...
] The index in the metadata exists only to dispatch to the right function using the enum approach. If we did hash based dispatch, then we could omit the index and describe pallet view functions identically to Runtime APIs, ie like this: pub struct PalletApiMethodMetadata<T: Form = MetaForm> {
/// Method name.
pub name: T::String,
/// Method parameters.
pub inputs: Vec<RuntimeApiMethodParamMetadata<T>>,
/// Method output.
pub output: T::Type,
/// Method documentation.
pub docs: Vec<T::String>,
} (see https://github.com/paritytech/frame-metadata/blob/main/frame-metadata/src/v15.rs#L132) Does this change your opinion on enum versus hash based dispatch, or is it actually a non issue? |
I don't think we should use bad names for the sake of consistency. Remember that we call host functions "Runtime Interface". It is confusing to use those generic names. There will also be runtime wide view functions eventually. |
Throwing some thoughts out there:
I like the idea of things being unified, OTOH if we know we are going there, why not do it here first where the impact is low (nobody is using view functions yet). The cost of migrating in the future should be considered. |
This is a great idea, and a nice bonus: If you think we can already experiment with Enum based dispatch, with compact encoded indices, let's do it here. @ascjones
Indeed, the metadata for view functions would need to be more sophisticated than dispatch, so it cannot be purely enum. @jsdw
About naming, I would go with view functions for now. We can always rename it later and pre-launch as well, so it is okay. |
I was in fact referring to "we can pursue someday moving all dispatches to hashes", so presenting the case for using hash instead of enum based dispatch 🙈 |
Me and @kianenigma had a chat about this the other day and the consensus was that, while our opinions differ on enum dispatch versus not, we shouldn't let merging this PR get too held up on this point. If there are concerns about merging this and then later altering it, then perhaps we could mark it as unstable like we do with new metadata versions and call the attr Does that work for everybody here? Should we mark as "unstable" eg like above or can we review and merge and still tweak it later without this? |
yes |
) -> Result<(), QueryDispatchError>; | ||
} | ||
|
||
impl DispatchQuery for () { |
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.
dq: but why we need to implement it for unit type?
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'd like somebody else to chime in if wrong, but I believe it's just a convenience impl for tests!
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 the default type for the type RuntimeQuery
associated type, as is the convention with the other Runtime*
types where there are no implementations.
let queries = view_fns.iter().map(|view_fn| { | ||
let query_struct_ident = view_fn.query_struct_ident(); | ||
let name = &view_fn.name; | ||
let args: Vec<TokenStream> = vec![]; // todo |
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 todo
still relevant?
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.
Yes, the args still need to be filled in the metadata
|
||
/// Metadata of a runtime method. | ||
#[derive(Clone, PartialEq, Eq, Encode, Debug)] | ||
pub struct QueryMetadataIR<T: Form = MetaForm> { |
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.
dq: but don't we want to have space for version/deprecation info on the query to allow iteration on already defined apis without explicitly removing them or redefining the type signature?
Or the intention is just o keep them all and fully rely on QueryId
for the resolution?
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 idea was not to have versioning, a new version would just have a different QueryId
. The old version would have to be maintained possibly indefinitely. Perhaps a deprecation flag would be useful, needs some thinking about.
/// The type implementing the runtime query dispatch. | ||
pub ty: T::Type, | ||
/// The query interfaces metadata. | ||
pub interfaces: Vec<QueryInterfaceIR<T>>, |
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.
Aren't pallet view functions scoped to certain pallets? So would it make sense for this information to be inside the PalletMetadataIR
?
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 PR allows them to be defined only as part of pallets (because it is convenient for the initial implementation), but the requirement is to make them pallet agnostic, so they can be defined independently of pallets.
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.
What's the advantage/reason to replace Runtime APIs in the future with these? My understanding (which might miss something; I haven't read the background docs in a while not) is:
- We have "Runtime APIs" as a pallet agnostic set of APIs (called externally via eg
state_call
by usingRuntimeApiTraitName ++ _ ++ method_name
and SCALE encoding the args. - We have here "Pallet View Functions", which are like Runtime APIs but are defined at the pallet level instead (and queried via a Runtime API call). These currently are accessed by using
twox_128(pallet_name) ++ twox_128("fn_name(fnarg_types) -> return_ty")
and SCALE encoding the args.
Both allow you to call functions and get the results back. In the future I'd expect both to become subscribable. The calling convetion of Pallet View Functions (hashing the type sig etc to query them) is different in order to enforce the type signature in the query (which means that changing the type signature of one would lead to old queries being invalid rather than being executed and complaining about wrongly encoded params or whatever).
I guess overall I see them as working alongside Runtime APIs but at the pallet level, which is also why I've tended to push for making them consistent with Runtime APIs in terms of how they are talked about and called etc so far, but I feel like I am missing something because others seem less keen on this so far :D
\cc @kianenigma maybe you also have some insight/thought I'm missing here :)
But all in all, I am happy to go ahead with whatever naming etc and figure it out in followup PRs anyways!
|
||
/// Metadata of a runtime query interface. | ||
#[derive(Clone, PartialEq, Eq, Encode, Debug)] | ||
pub struct QueryInterfaceIR<T: Form = MetaForm> { |
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.
How much could we align this with Runtime APIs in terms of the naming of structts/fields?
For runtime APIs we have:
RuntimeApiMetadataIR { name, methods, docs, deprecation_info }
RuntimeApiMethodMetadataIR { name, inputs, output, docs, deprecation_info }
RuntimeApiMethodParamMetadataIR { name, ty }
So here, we could, per pallet, have something like:
struct PalletApiMethodMetadataIR { name, id?, inputs: Vec<PalletApiMethodParamMetadataIR>, output, docs, deprecation_info }
struct PalletApiMethodParamMetadataIR { name, ty }
(I went for Api
instead of ViewFunction
just because I prefer it to viewFunctions and it's shorter, but it's all good!)
I guess having the id
field lets us avoid having to construct it, since it's a bit arduous hashing the relevant bits, so makes sense for now! Personally I'd simplify the way we call these things to be more like runtime APIs, but I'm happy to propose that as a separate PR after this merges!
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 agree should try to align the metadata with Runtime APIs...in theory we should be able to replace Runtime APIs in the future with "view functions".
As for naming...it would be good to come up with something consistent: as you can see I am using "query" interchangeably with "view_function". API is okay but maybe too generic and could be confused with existing Runtime API? Naming is hard, any suggestions welcome.
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 personally prefer Api to align with Runtime APIs, but happy for everything to be named consistently with any of Api
, Query
, ViewFunc
(just to help shorten a touch) or ViewFunction
. For the sake of being able to rename it all quite easily in a followup PR if we like, perhaps one of the latter two is ideal (easy to search and replace ViewFunc
/ViewFunction
!)
} | ||
|
||
impl<#type_impl_gen> #query_struct_ident<#type_use_gen> #where_clause { | ||
/// Create a new [`#query_struct_ident`] instance. |
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.
Nit: this outputs as written here rather than #query_struct_ident
being substituted with the ident as you'd want.
Best bet might be to build the doc string beforehand and then use #[doc = #doc_string]
to get it in!
This is generally looking good to merge to me now! It's behind an
Thw top 3 of those would be enough for a tick from me :) It'd be good to also have a FRAME/Runtime dev have a look over since I'm newer to this code. |
Okay thanks @jsdw, I will attempt to resolve these by the end of next week. |
Closes #216.
This PR allows pallets to define a
view_functions
impl like so:QueryId
Each view function is uniquely identified by a
QueryId
, which for this implementation is generated by:twox_128(pallet_name) ++ twox_128("fn_name(fnarg_types) -> return_ty")
The prefix
twox_128(pallet_name)
is the same as the storage prefix for pallets and take into account multiple instances of the same pallet.The suffix is generated from the fn type signature so is guaranteed to be unique for that pallet impl. For one of the view fns in the example above it would be
twox_128("get_value_with_arg(u32) -> Option<u32>")
. It is a known limitation that only the type names themselves are taken into account: in the case of type aliases the signature may have the same underlying types but a different id; for generics the concrete types may be different but the signatures will remain the same.The existing Runtime
Call
dispatchables are addressed by their concatenated indicespallet_index ++ call_index
, and the dispatching is handled by the SCALE decoding of theRuntimeCallEnum::PalletVariant(PalletCallEnum::dispatchable_variant(payload))
. Forview_functions
the runtime/pallet generated enum structure is replaced by implementing theDispatchQuery
trait on the outer (runtime) scope, dispatching to a pallet based on the id prefix, and the inner (pallet) scope dispatching to the specific function based on the id suffix.Future implementations could also modify/extend this scheme and routing to pallet agnostic queries.
Executing externally
These view functions can be executed externally via the system runtime api:
XCQ
Currently there is work going on by @xlc to implement
XCQ
which may eventually supersede this work.It may be that we still need the fixed function local query dispatching in addition to XCQ, in the same way that we have chain specific runtime dispatchables and XCM.
I have kept this in mind and the high level query API is agnostic to the underlying query dispatch and execution. I am just providing the implementation for the
view_function
definition.Metadata
Currently I am utilizing the
custom
section of the frame metadata, to avoid modifying the official metadata format until this is standardized.vs
runtime_api
There are similarities with
runtime_apis
, some differences being:QueryId
will change if the signature changes.Calling from contracts
Future work would be to add
weight
annotations to the view function queries, and a host function topallet_contracts
to allow executing these queries from contracts.TODO
runtime_api