State Queries¶
Clients need to be able to query the local state to build effective front-ends. For example, a wallet might be interested in querying all state objects that a specific user owns. We’ve designed a querying system to make this process as easy as possible.
Query Generation¶
Parsing Predicate API¶
The Predicate API provided by each predicate specifies a list of queries that can be made on state objects locked with that predicate. For example, the API of the SimpleOwnership predicate specifies the following function:
{
"name": "getOwner",
"constant": true,
"inputs": [],
"outputs": [
{
"name": "owner",
"type": "address"
}
]
}
Users first need to parse this API to figure out what methods are available for the predicate they’re attempting to query. Once the user has determined the name and required parameters for the method they want to query, they can generate a StateQuery. The user can then send this query to a client via the state query RPC method.
Parsing Query Results¶
The result of a state query is a list of `StateQueryResult`_ objects. If a filter was provided as part of the StateQuery
, only StateQueryResult
objects that passed the provided filter will be returned.
Example: SimpleOwnership¶
We’ll now provide an example state query for the getOwner
method of the SimpleOwnership
predicate. The Predicate API for SimpleOwnership
describes getOwner
as:
{
"name": "getOwner",
"constant": true,
"inputs": [],
"outputs": [
{
"name": "owner",
"type": "address"
}
]
}
Let’s assume that our plasmaContract
is 0x1b33c35be86be9d214f54af218c443c2623d3d0a
and our SimpleOwnership
predicate is located at 0xf25746ac8621a7998e0992b9d88e260c117c145f
.
To query all state updates where 0x5a0b54d5dc17e0aadc383d2db43b0a0d3e029c4c
is the owner, we construct the following query:
const query: StateQuery = {
plasmaContract: '0x1b33c35be86be9d214f54af218c443c2623d3d0a',
predicateAddress: '0xf25746ac8621a7998e0992b9d88e260c117c145f',
method: 'getOwner',
params: [],
filter: {
$eq: [ '$0', '0x5a0b54d5dc17e0aadc383d2db43b0a0d3e029c4c' ]
}
}
Our filter here specifies that the first output of the function call (which we know to be the owner in this case) should be equal to a given address. Results where this is not the case will not be returned.
Once we send this request via the state query RPC method, we’ll receive a result that looks like this:
[
{
stateUpdate: {
block: 123,
start: 0,
end: 100,
predicate: '0xf25746ac8621a7998e0992b9d88e260c117c145f',
data: '0x5a0b54d5dc17e0aadc383d2db43b0a0d3e029c4c'
},
result: ['0x5a0b54d5dc17e0aadc383d2db43b0a0d3e029c4c']
},
...
]
We can then present this data in any way that we might want to.
Query Handling¶
Range Intersection¶
Clients will receive a StateQuery object when receiving a state query. Clients first MUST use the range provided by the StateQuery
to find all state updates in the current head state that match the provided predicateAddress
.
Passing Queries to Predicate Plugins¶
Once the client has found all relevant state updates, they MUST call the queryState method in the predicate plugin that corresponds to the provided predicateAddress
. queryState
takes the method
and parameters
from the StateQuery
and returns an array of results.
Filtering Queries¶
Users filter their results by providing an Expression that performs some operation on the provided result. If a filter
was given in the StateQuery
, then the client MUST correctly remove results according to the filter. More information about filters is given in the page about Expressions.
Users can filter based on the outputs of the query method by inserting strings in the form of \$[0-9]+
(starting at $0
).
For example, a user could filter results where the first output is greater than 0
and the second result is less than 100
like this:
{
$and: [
{ $gt: [ '$0', 0 ] },
{ $lt: [ '$1', 100 ] }
]
}
Any results that have not been removed by the filter can then be returned to the requesting client.