queryLedgerState/* START queryNetwork/* ⇓ ╭────╮ releaseLedgerState ┌──────────────┴┐ │ ╭─────▶│ Idle │◀──╯ │ └───────┬───────┘ │ │ │ │ acquireLedgerState │ │ │ │ (re)acquireLedgerState │ ▼ ╭────────╮ │ ┌──────────┴────┐ │ ╰──────┤ Acquired │◀──╯ └───┬───────────┘ │ ▲ queryLedgerState/* │ │ queryNetwork/* │ │ ╰───────╯
The state query protocol is likely the most versatile of the three Ouroboros mini-protocols. As a matter of fact, it allows for querying various types of information directly from the ledger. In essence, it is like a very simpler request/response pattern where the types of questions one can ask are specified by the protocols. Those questions include: information about the chain tip, information about stake pools but also the balance of a particular address.
In order to run a question by the ledger, one must first acquire a particular position on the chain, so that the node can reliably answer a few questions on a chosen, frozen state while continuing maintaining more recent version of the ledger on the side. It is important to note that:
The node cannot acquire any arbitrary state. One can only rewind up to a certain point.
Should a client keep a state acquired for too long, it is likely to become unreachable at some point, forcing clients to re-acquire.
Ogmios uses a simplified version of the above state-machine. Or more exactly, it exposes a simplified version and handles some of the complexity behind the scene for you. As clients, Ogmios will give you method to acquire a state, query that state and release the state. A typical sequence would be to start by acquiring a state on a given point and then make a few queries, and then release. The release step is optional although it is a bit more polite to say goodbye at the end of a conversation.
It is also possible to submit queries directly without acquiring. As a consequence, Ogmios will acquire the tip of the chain, run the query and release it for you. This is the easiest way to send queries if you don’t care about capturing a particular state. Note however that this may create race conditions if you send multiple queries via this method. Indeed, the tip is changing quite often on the network, and two subsequent queries may actually run on two different points of the chain. While this is generally safe for most queries, it may also put your application in an unexpected state when crossing epoch boundaries or hard-forks.
The acquireLedgerState
method expects one argument named point
. The point has the same format as points in the chain synchronization protocol. That is, they can be block header hashes or the special keyword "origin"
(though there’s very little chance that one will be able to acquire the origin!).
{
"jsonrpc": "2.0",
"method": "acquireLedgerState",
"params": {
"point": {
"slot": 1234,
"hash": "9e871633f7aa356ef11cdcabb6fdd6d8f4b00bc919c57aed71a91af8f86df590"
}
}
}
One thing that doesn’t strike as obvious is that, as clients, you need points to query any information. There are many ways to get those hashes but in the context of Ogmios, the most logical way is via the chain synchronization protocol.
You can acquire multiple times, the last one will prevail. If you need to re-acquire, simply send another acquire
request.
You can skip acquiring a state should you want to run a query on the current state of the chain. This is good for one-off queries, but if you need to chain multiple queries together it is highly recommended to acquire a state first to preserve data-consistency between queries!
There are many queries that can be sent to the ledger, and the list is growing days after days as the Cardano team implements new ones. With Ogmios, all queries follow the same pattern and are identified by a method. There exists two types of queries: ledger-state queries and network queries. The former is performed on the ledger state and are era-dependent. The latter are always available (even when the node is synchronizing) and are era-independent. In both cases, queries are constructed in a similar fashion:
queryLedgerState/*
, where *
has to be replaced with an actual ledger-state query name (see below);queryNetwork/*
, where *
has to be replaced with an actual network query name (see below)For example, to query the ongoing epoch of the ledger:
{
"jsonrpc": "2.0",
"method": "queryLedgerState/epoch",
}
At the moment of writing this guide, the following queries are available:
queryNetwork | Information |
---|---|
blockHeight | The chain’s highest block number. |
genesisConfiguration | Get the genesis configuration of a specific era. |
startTime | The chain’s start time (UTC). |
tip | The network’s current tip. |
queryLedgerState | Information |
---|---|
constitution | The on-chain constitution. |
constitutionalCommittee | A complete summary of the constitutional committee. |
epoch | The current epoch of the ledger. |
eraStart | The information regarding the beginning of the current ledger era. |
eraSummaries | Era bounds and slot parameters details, required for proper slotting arithmetic. |
governanceProposals | Currently active governance proposals and their ratification state (i.e. votes). |
liveStakeDistribution | Distribution of the stake across all known stake pools, relative to the total stake in the network. |
projectedRewards | The projected rewards of an account in a context where the top stake pools are fully saturated. This projection gives, in principle, a ranking of stake pools that maximizes delegator rewards. |
protocolParameters | The current protocol parameters. |
proposedProtocolParameters | The last update proposal w.r.t. protocol parameters, if any. |
rewardAccountSummaries | Current delegation settings and rewards of chosen reward accounts. |
rewardsProvenance | Get details about rewards calculation for the ongoing epoch. |
stakePools | The list of all currently registered and active stake pools with their current parameters. This query can be made with or without specifying list of target stakePools . When no stake pools are provided as parameter, the query returns the list of all stake pools and their parameters from the node. |
tip | The current tip the ledger is at. Said differently, the slot number and header hash of the last block that has been processed by the ledger. |
treasuryAndReserves | The Ada value of the treasury and reserves of the protocol. |
utxo | Current UTXO, possibly filtered by output reference. |
To know more about arguments and results of each query, have a look at the API reference.
In this example, we’ll consider a simple direct query on the network tip to fetch the latest protocol parameters. The next section gives a more elaborate example which shows how to acquire a specific point on chain.
const WebSocket = require('ws');
const client = new WebSocket("ws://localhost:1337");
function rpc(method, params = {}, id) {
client.send(JSON.stringify({
jsonrpc: "2.0",
method,
params,
id
}));
}
client.once('open', () => {
rpc("queryLedgerState/protocolParameters");
});
client.on('message', function(msg) {
const response = JSON.parse(msg);
console.log(JSON.stringify(response.result, null, 4));
client.close();
});
This little excerpt outputs the most recent protocol parameters in a nice JSON:
{
"minFeeCoefficient": 44,
"minFeeConstant": { "lovelace": 155381 },
"maxBlockBodySize": { "bytes": 90112 },
"maxBlockHeaderSize": { "bytes": 1100 },
"maxTransactionSize": { "bytes": 16384 },
"stakeCredentialDeposit": { "lovelace": 2000000 },
"stakePoolDeposit": { "lovelace": 500000000 },
"stakePoolRetirementEpochBound": 18,
"desiredNumberOfStakePools": 500,
"stakePoolPledgeInfluence": "3/10",
"monetaryExpansion": "3/1000",
"treasuryExpansion": "1/5",
"minStakePoolCost": { "lovelace": 340000000 },
"minUtxoDepositConstant": 0,
"minUtxoDepositCoefficient": 4310,
"plutusCostModels": {
"plutus:v1": [
205665, 812, 1, 1, 1000, 571, 0, 1,
1000, 24177, 4, 1, 1000, 32, 117366, 10475,
4, 23000, 100, 23000, 100, 23000, 100, 23000,
100, 23000, 100, 23000, 100, 100, 100, 23000,
100, 19537, 32, 175354, 32, 46417, 4, 221973,
511, 0, 1, 89141, 32, 497525, 14068, 4,
2, 196500, 453240, 220, 0, 1, 1, 1000,
28662, 4, 2, 245000, 216773, 62, 1, 1060367,
12586, 1, 208512, 421, 1, 187000, 1000, 52998,
1, 80436, 32, 43249, 32, 1000, 32, 80556,
1, 57667, 4, 1000, 10, 197145, 156, 1,
197145, 156, 1, 204924, 473, 1, 208896, 511,
1, 52467, 32, 64832
],
"plutus:v2": [
205665, 812, 1, 1, 1000, 571, 0, 1,
1000, 24177, 4, 1, 1000, 32, 117366, 10475,
4, 23000, 100, 23000, 100, 23000, 100, 23000,
100, 23000, 100, 23000, 100, 100, 100, 23000,
100, 19537, 32, 175354, 32, 46417, 4, 221973,
511, 0, 1, 89141, 32, 497525, 14068, 4,
2, 196500, 453240, 220, 0, 1, 1, 1000,
28662, 4, 2, 245000, 216773, 62, 1, 1060367,
12586, 1, 208512, 421, 1, 187000, 1000, 52998,
1, 80436, 32, 43249, 32, 1000, 32, 80556,
1, 57667, 4, 1000, 10, 197145, 156, 1,
197145, 156, 1, 204924, 473, 1, 208896, 511,
1, 52467, 32, 64832
]
},
"scriptExecutionPrices": {
"memory": '577/10000',
"cpu": '721/10000000'
},
"maxExecutionUnitsPerTransaction": {
"memory": 14000000,
"cpu": 10000000000
},
"maxExecutionUnitsPerBlock": {
"memory": 62000000,
"cpu": 20000000000
},
"maxValueSize": { "bytes": 5000 },
"collateralPercentage": 150,
"maxCollateralInputs": 3,
"version": {
"major": 8,
"minor": 0
}
}
Let’s see a full example getting the stake distribution of all stake pools of the Cardano mainnet. In the example, we’ll also use a network query to find the current chain tip, and then try to acquire it for subsequent queries.
const WebSocket = require('ws');
const client = new WebSocket("ws://localhost:1337");
function rpc(method, params = {}, id) {
client.send(JSON.stringify({
jsonrpc: "2.0",
method,
params,
id
}));
}
client.once('open', () => {
rpc("queryNetwork/tip", {})
});
client.on('message', function(msg) {
const response = JSON.parse(msg);
switch (response.method) {
case "queryNetwork/tip":
const point = response.result;
rpc("acquireLedgerState", { point });
break;
case "acquireLedgerState":
rpc("queryLedgerState/liveStakeDistribution");
break;
default:
console.log(response.result);
client.close();
break;
}
});
Here’s a walk-though describing what happens when running the above script:
An initial request ask the network tip. That is guaranteed to succeed and is a little trick in order to access the ledger tip easily. As a response, Ogmios replies with:
Using the tip
from the previous response, we can now safely aquire a state on that particular tip which we know exists and is not too old. Ogmios replies successfully with:
Now in a position to make an actual query, we do it and ask for the stake distribution across all stake pools. The (truncated) response from the server looks like:
Be aware that it is possible for an acquire request to fail even if (and in particular if) made immediately after finding the ledger tip. In Ouroboros Praos frequent small rollbacks of the chain are not rare and the few last blocks of the chain can be a bit volatile. A real application may require more elaborate error handling than the toy example above.
{
"jsonrpc": "2.0",
"method": "queryLedgerState/constitution",
"result": {
"metadata": {
"url": "",
"hash": "0000000000000000000000000000000000000000000000000000000000000000"
},
"guardrails": null
},
"id": null
}
{
"jsonrpc": "2.0",
"method": "queryLedgerState/constitutionalCommittee",
"result": {
"members": [
{
"id": "5f1b4429fe3bda963a7b70ab81135112a785afcf55ccd695b122e794",
"delegate": {
"status": "authorized",
"credential": "5aa349227e4068c85c03400396bcea13c7fd57d0ec78c604bc768fc5"
},
"status": "active",
"mandate": {
"epoch": 379
}
},
{
"id": "9393c87a66b1f7dd4f9b486a49232de92e39e18b3b20ac4a539b4df2",
"delegate": {
"status": "authorized",
"credential": "670994283668cea40218e0ef33c51aff39ca00a74f68ed428cf305ce"
},
"status": "active",
"mandate": {
"epoch": 379
}
},
{
"id": "b7bfc26ddc6718133a204af6872149b69de83dd3350f60b257e55773",
"delegate": {
"status": "none"
},
"status": "active",
"mandate": {
"epoch": 379
}
}
],
"quorum": "2/3"
},
"id": null
}
{
"jsonrpc": "2.0",
"method": "queryNetwork/blockHeight"
}
{
"jsonrpc": "2.0",
"method": "queryNetwork/genesisConfiguration",
"params": {
"era": "shelley"
}
}
{
"jsonrpc": "2.0",
"method": "queryNetwork/genesisConfiguration",
"params": {
"era": "alonzo"
}
}
{
"jsonrpc": "2.0",
"method": "queryNetwork/startTime"
}
{
"jsonrpc": "2.0",
"method": "queryNetwork/tip"
}
{
"jsonrpc": "2.0",
"method": "queryLedgerState/constitution"
}
{
"jsonrpc": "2.0",
"method": "queryLedgerState/epoch"
}
{
"jsonrpc": "2.0",
"method": "queryLedgerState/eraStart"
}
{
"jsonrpc": "2.0",
"method": "queryLedgerState/eraSummaries"
}
{
"jsonrpc": "2.0",
"method": "queryLedgerState/governanceProposals"
}
{
"jsonrpc": "2.0",
"method": "queryLedgerState/governanceProposals",
"params": {
"proposals": [
{
"transaction": { "id": "ee155ace9c40292074cb6aff8c9ccdd273c81648ff1149ef36bcea6ebb8a3e25" },
"index": 2
}
]
}
}
{
"jsonrpc": "2.0",
"method": "queryLedgerState/liveStakeDistribution"
}
{
"jsonrpc": "2.0",
"method": "queryLedgerState/projectedRewards",
"params": {
"stake": [
1000000
]
}
}
{
"jsonrpc": "2.0",
"method": "queryLedgerState/projectedRewards",
"params": {
"keys": [
"7c16240714ea0e12b41a914f2945784ac494bb19573f0ca61a08afa8",
"stake_vkh10stzgpc5ag8p9dq6j98jj3tcftzffwce2ulsefs6pzh6s39tk6l"
]
}
}
{
"jsonrpc": "2.0",
"method": "queryLedgerState/projectedRewards",
"params": {
"scripts": [
"7c16240714ea0e12b41a914f2945784ac494bb19573f0ca61a08afa8",
"script10stzgpc5ag8p9dq6j98jj3tcftzffwce2ulsefs6pzh6snywdma"
]
}
}
{
"jsonrpc": "2.0",
"method": "queryLedgerState/protocolParameters"
}
{
"jsonrpc": "2.0",
"method": "queryLedgerState/proposedProtocolParameters"
}
{
"jsonrpc": "2.0",
"method": "queryLedgerState/rewardAccountSummaries",
"params": {
"keys": [
"7c16240714ea0e12b41a914f2945784ac494bb19573f0ca61a08afa8",
"stake_vkh10stzgpc5ag8p9dq6j98jj3tcftzffwce2ulsefs6pzh6s39tk6l"
]
}
}
{
"jsonrpc": "2.0",
"method": "queryLedgerState/rewardAccountSummaries",
"params": {
"scripts": [
"7c16240714ea0e12b41a914f2945784ac494bb19573f0ca61a08afa8",
"script10stzgpc5ag8p9dq6j98jj3tcftzffwce2ulsefs6pzh6snywdma"
]
}
}
{
"jsonrpc": "2.0",
"method": "queryLedgerState/rewardsProvenance"
}
{
"jsonrpc": "2.0",
"method": "queryLedgerState/stakePools"
}
{
"jsonrpc": "2.0",
"method": "queryLedgerState/stakePools",
"params": {
"stakePools": [
{ "id": "pool1pk2wzarn9mu64eel89dtg3g8h75c84jsy0q349glpsewgd7sdls" },
{ "id": "4acf2773917c7b547c576a7ff110d2ba5733c1f1ca9cdc659aea3a56" }
]
}
}
{
"jsonrpc": "2.0",
"method": "queryLedgerState/treasuryAndReserves"
}
{
"jsonrpc": "2.0",
"method": "queryLedgerState/tip"
}
{
"jsonrpc": "2.0",
"method": "queryNetwork/tip"
}
{
"jsonrpc": "2.0",
"method": "queryLedgerState/utxo"
}
{
"jsonrpc": "2.0",
"method": "queryLedgerState/utxo",
"params": {
"outputReferences": [
{
"transaction": { "id": "ee155ace9c40292074cb6aff8c9ccdd273c81648ff1149ef36bcea6ebb8a3e25" },
"index": 2
}
]
}
}
Errors from the state query protocol are in the range 2000-2999
and are listed below.
The complete description of the mempool monitoring requests and responses can be found in the API reference.
Plus, test vectors are available on the repository for testing, debugging and to serve as examples.