* query-lifecycle and started modules-interfaces * query-lifecycle first draft done * module interfaces first draft * rest and intro skeletons * rest and intro done * small edits and links * comments * revisions * cli.md comments * comments * minor edits * better flow for query lifecycle * add transactions into core * hans comments * add transactions into core * checkout master-docs files * deleted some * remove modules readme * cli.md comments * comments * module-interfaces comments * Merge PR #4857: Add Context concept doc * working * working * finish messages and queries * handler * querier * last comments! * punctuation * querier2 * consolidate intro * querier * workiiiing * refactor for new module interface * karoly review * working on baseapp doc * baseapp work * reorg * almost there * finish first draft * remove old files * finish intro * workinnn * initial commit after rebase * query-lifecycle and started modules-interfaces * query-lifecycle first draft done * module interfaces first draft * rest and intro skeletons * rest and intro done * small edits and links * comments * revisions * cli.md comments * comments * minor edits * better flow for query lifecycle * checkout master-docs files * deleted some * remove modules readme * cli.md comments * comments * module-interfaces comments * keeper * genesis * finish * Apply suggestions from code review Co-Authored-By: Hans Schoenburg <hschoenburg@users.noreply.github.com> * hans review * Update docs/core/baseapp.md Co-Authored-By: Hans Schoenburg <hschoenburg@users.noreply.github.com> * working * last comment * workin * Apply suggestions from code review * encoding and node * almost finish store * finish docs * fixes * fede comments + permalinks * hans review * add more permalinks * update docs theme version (#5239) * R4R: Docs Cleanup (#5246) * start * work * work * work * remove table of content * links intro * fix links * remove junk * cleanup * cleanup * work * finish cleanup * addback readmes * remove nft * fix links * remove dup * remove dup * remove dup * remove dup * remove dup * fix links * add subscribe events * refine rest * index page * sidebar * theme version * theme version * testing netlify * theme version * tooltip example * version * testing code embedding * reverting back * theme version * version * version * version * readme and version * cleanup * redo app anatomy * modules readme, theme version * theme version * fix modules list * theme version * new snippets * modules readme * update docs readme * modify synopsis * version * fix yaml * version * version * version * version * version * version * version * version * version * version * add hide banner * version * version * version * small fixes * modules readme, version * remove hotkeys dep, version * version * version * version * version * version * version * version * slight notice * fix links and hide * permalinks * small clean * version * resolve conflicts, add google analytics * fix merge remants * version * changelog 1/2 * Changelog: docs UI * version * remove merge conflicts * Code: Update link for Contributing to the docs to docs_readme * HTML/CSS: Update layout of homepage footer to match new layout in Figma * version * final modifs * modules, version * modules readme * link to module list from homepage * version * building modules link * version * version * fonts * version * version * fix link * fix package.json * links in explore sdk section * core concepts * version * change delimeters for frontmatter * frontmatter in comments * version * temp add tiny-cookie * fixed link issues * fixed styling issues, copy * hide frontmatter * hide frontmatter * layout fixes, padded ascii diagram * fira sans font for code * scrollbar color * move synopsis from frontmatter * move synopsis from frontmatter * code styling in synopsis fix * tutorial link * active headers * 404 fix * homepage links fix * fix link in footer * misc fixes * version * prerequisite links on mobile * version * version * Fix footer links * version * Fix permalink popup * Update version * package-lock.json * Update Questions section in the footer * Config for Algolia Docsearch * Update version * Update to the latest version of the theme * Use docs-staging as a branch for staging website * Update version * Add google analytics * Remove {hide} from Pre-Requisite Readings * Replace Riot with Discord Co-Authored-By: billy rennekamp <billy.rennekamp@gmail.com> * Fix yaml syntax error in docs * Fix formatting in keyring.md Co-authored-by: Gloria Zhao <gzhao408@berkeley.edu> Co-authored-by: gamarin <gautier@tendermint.com> Co-authored-by: Jack Zampolin <jack.zampolin@gmail.com> Co-authored-by: Hans Schoenburg <hschoenburg@users.noreply.github.com> Co-authored-by: Federico Kunze <31522760+fedekunze@users.noreply.github.com> Co-authored-by: Alexander Bezobchuk <alexanderbez@users.noreply.github.com> Co-authored-by: billy rennekamp <billy.rennekamp@gmail.com>
13 KiB
Query Lifecycle
This document describes the lifecycle of a query in a SDK application, from the user interface to application stores and back. The query will be referred to as Query. {synopsis}
Pre-requisite Readings
- Introduction to Interfaces {prereq}
Query Creation
A query is a request for information made by end-users of applications through an interface and processed by a full-node. Users can query information about the network, the application itself, and application state directly from the application's stores or modules. Note that queries are different from transactions (view the lifecycle here), particularly in that they do not require consensus to be processed (as they do not trigger state-transitions); they can be fully handled by one full-node.
For the purpose of explaining the query lifecycle, let's say Query is requesting a list of delegations made by a certain delegator address in the application called app. As to be expected, the staking module handles this query. But first, there are a few ways Query can be created by users.
CLI
The main interface for an application is the command-line interface. Users connect to a full-node and run the CLI directly from their machines - the CLI interacts directly with the full-node. To create Query from their terminal, users type the following command:
appcli query staking delegations <delegatorAddress>
This query command was defined by the staking module developer and added to the list of subcommands by the application developer when creating the CLI. The code for this particular command is the following:
+++ 7d7821b9af/x/staking/client/cli/query.go (L250-L293)
Note that the general format is as follows:
appcli query [moduleName] [command] <arguments> --flag <flagArg>
To provide values such as --node (the full-node the CLI connects to), the user can use the config command to set themn or provide them as flags.
The CLI understands a specific set of commands, defined in a hierarchical structure by the application developer: from the root command (appcli), the type of command (query), the module that contains the command (staking), and command itself (delegations). Thus, the CLI knows exactly which module handles this command and directly passes the call there.
REST
Another interface through which users can make queries is through HTTP Requests to a REST server. The REST server contains, among other things, a CLIContext and mux router. The request looks like this:
GET http://localhost:{PORT}/staking/delegators/{delegatorAddr}/delegations
To provide values such as --node (the full-node the CLI connects to) that are required by baseReq, the user must configure their local REST server with the values or provide them in the request body.
The router automatically routes the Query HTTP request to the staking module delegatorDelegationsHandlerFn() function.
+++ 7d7821b9af/x/staking/client/rest/query.go (L103-L106)
Since this function is defined within the module and thus has no inherent knowledge of the application Query belongs to, it takes in the application codec and CLIContext as parameters.
To summarize, when users interact with the interfaces, they create a CLI command or HTTP request. Query now exists in one of these two forms, but needs to be transformed into an object understood by a full-node.
Query Preparation
The interactions from the users' perspective are a bit different, but the underlying functions are almost identical because they are implementations of the same command defined by the module developer. This step of processing happens within the CLI or REST server and heavily involves a CLIContext.
CLIContext
The first thing that is created in the execution of a CLI command is a CLIContext, while the REST Server directly provides a CLIContext for the REST Request handler. A Context is an immutable object that stores all the data needed to process a request on the user side. In particular, a CLIContext stores the following:
- Codec: The encoder/decoder used by the application, used to marshal the parameters and query before making the Tendermint RPC request and unmarshal the returned response into a JSON object.
- Account Decoder: The account decoder from the
authmodule, which translates[]bytes into accounts. - RPC Client: The Tendermint RPC Client, or node, to which the request will be relayed to.
- Keybase: A Key Manager used to sign transactions and handle other operations with keys.
- Output Writer: A Writer used to output the response.
- Configurations: The flags configured by the user for this command, including
--height, specifying the height of the blockchain to query and--indent, which indicates to add an indent to the JSON response.
The CLIContext also contains various functions such as Query() which retrieves the RPC Client and makes an ABCI call to relay a query to a full-node.
+++ 7d7821b9af/client/context/context.go (L23-L47)
The CLIContext's primary role is to store data used during interactions with the end-user and provide methods to interact with this data - it is used before and after the query is processed by the full-node. Specifically, in handling Query, the CLIContext is utilized to encode the query parameters, retrieve the full-node, and write the output. Prior to being relayed to a full-node, the query needs to be encoded into a []byte form, as full-nodes are application-agnostic and do not understand specific types. The full-node (RPC Client) itself is retrieved using the CLIContext, which knows which node the user CLI is connected to. The query is relayed to this full-node to be processed. Finally, the CLIContext contains a Writer to write output when the response is returned. These steps are further described in later sections.
Arguments and Route Creation
At this point in the lifecycle, the user has created a CLI command or HTTP Request with all of the data they wish to include in their Query. A CLIContext exists to assist in the rest of the Query's journey. Now, the next step is to parse the command or request, extract the arguments, create a queryRoute, and encode everything. These steps all happen on the user side within the interface they are interacting with.
Encoding
In this case, Query contains an address delegatorAddress as its only argument. However, the request can only contain []bytes, as it will be relayed to a consensus engine (e.g. Tendermint Core) of a full-node that has no inherent knowledge of the application types. Thus, the codec of CLIContext is used to marshal the address.
Here is what the code looks like for the CLI command:
delAddr, err := sdk.AccAddressFromBech32(args[0])
bz, err := cdc.MarshalJSON(types.NewQueryDelegatorParams(delAddr))
Here is what the code looks like for the HTTP Request:
vars := mux.Vars(r)
bech32delegator := vars["delegatorAddr"]
delegatorAddr, err := sdk.AccAddressFromBech32(bech32delegator)
cliCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r)
if !ok {
return
}
params := types.NewQueryDelegatorParams(delegatorAddr)
Query Route Creation
Important to note is that there will never be a "query" object created for Query; the SDK actually takes a simpler approach. Instead of an object, all the full-node needs to process a query is its route which specifies exactly which module to route the query to and the name of this query type. The route will be passed to the application baseapp, then module, then querier, and each will understand the route and pass it to the appropriate next step. baseapp will understand this query to be a custom query in the module staking, and the staking module querier supports the type QueryDelegatorDelegations. Thus, the route will be "custom/staking/delegatorDelegations".
Here is what the code looks like:
route := fmt.Sprintf("custom/%s/%s", queryRoute, types.QueryDelegatorDelegations)
Now, Query exists as a set of encoded arguments and a route to a specific module and its query type. It is ready to be relayed to a full-node.
ABCI Query Call
The CLIContext has a Query() function used to retrieve the pre-configured node and relay a query to it; the function takes the query route and arguments as parameters. It first retrieves the RPC Client (called the node) configured by the user to relay this query to, and creates the ABCIQueryOptions (parameters formatted for the ABCI call). The node is then used to make the ABCI call, ABCIQueryWithOptions().
Here is what the code looks like:
+++ 7d7821b9af/client/context/query.go (L75-L112)
RPC
With a call to ABCIQueryWithOptions(), Query is received by a full-node which will then process the request. Note that, while the RPC is made to the consensus engine (e.g. Tendermint Core) of a full-node, queries are not part of consensus and will not be broadcasted to the rest of the network, as they do not require anything the network needs to agree upon.
Read more about ABCI Clients and Tendermint RPC in the Tendermint documentation here.
Application Query Handling
When a query is received by the full-node after it has been relayed from the underlying consensus engine, it is now being handled within an environment that understands application-specific types and has a copy of the state. baseapp implements the ABCI Query() function and handles four different types of queries: app, store, p2p, and custom. The queryRoute is parsed such that the first string must be one of the four options, then the rest of the path is parsed within the subroutines handling each type of query. The first three types (app, store, p2p) are purely application-level and thus directly handled by baseapp or the stores, but the custom query type requires baseapp to route the query to a module's querier.
Since Query is a custom query type from the staking module, baseapp first parses the path, then uses the QueryRouter to retrieve the corresponding querier, and routes the query to the module. The querier is responsible for recognizing this query, retrieving the appropriate values from the application's stores, and returning a response. Read more about queriers here.
Once a result is received from the querier, baseapp begins the process of returning a response to the user.
Response
Since Query() is an ABCI function, baseapp returns the response as an abci.ResponseQuery type. The CLIContext Query() routine receives the response and, if --trust-node is toggled to false and a proof needs to be verified, the response is verified with the CLIContext verifyProof() function before the response is returned.
+++ 7d7821b9af/client/context/query.go (L127-L165)
CLI Response
The application codec is used to unmarshal the response to a JSON and the CLIContext prints the output to the command line, applying any configurations such as --indent.
+++ 7d7821b9af/x/staking/client/cli/query.go (L252-L293)
REST Response
The REST server uses the CLIContext to format the response properly, then uses the HTTP package to write the appropriate response or error.
+++ 7d7821b9af/x/staking/client/rest/utils.go (L115-L148)
Next {hide}
Read about how to build a Command-Line Interface, or a REST Interface {hide}