CIP-0094 The beginning of Cardano's decentralized governance
This tutorial explores the on-chain data related to CIP-0094.
Abstract
The Cardano Foundation proposes a mechanism for polling Cardano stake pool operators on specific topics. Polls are done on-chain through transaction metadata and authenticated through stake pool credentials (Ed25519 cold key). The goal is to gather opinions on governance matters such as protocol parameter updates. This standard is an inclusive interim solution while the work on a larger governance framework such as CIP-1694 continues.
To learn more about the details of CIP-0094 please follow this link
Overview
In this tutorial, you will learn how to:
- Set up CardanoBI
- Create a request to get the list of Polls curently defined on chain
- Create a request to get the results of a Poll given its hash
- Understand better the key data attributes at play in CIP-0094
Prerequisites
At the time of writing (19/05/2023), Nodejs being the only CardanoBI SDK available, we will focus on it for now but depending on when you're reading this, others may be available and we will make sure to update this tutorial accordingly.
Step 1 - Setting up CardanoBI
The CardanoBI API endpoints we will be using throughout this tutorial are not restricted, so you don't need an API Key to call them, for that reason, we can move on swiftly to the next step.
Step 2 - Getting the list of Polls defined on chain
To retrieve this information, we will simply go ahead and call this endpoint:
You can view the full description of this endpoint by following this link:
DOCS All polls: /api/core/polls
Now let's get some data out, shall we?
- Node.js
- Shell
Install our Node.js SDK
$ git clone https://github.com/cardanobi/cardanobi-js.git
cd cardanobi-js
npm install
Import CardanoBI.js in your project.
import { CardanoBI } from './cardanobi-js/CardanoBI.js'
Now instantiate a CardanoBI object without parameters, by doing so it will use mainnet
as the default network.
Lastly, call the polls_
method, available in the core
domain object:
const CBI = await new CardanoBI();
const all_polls = await CBI.core.polls_();
console.log(all_polls);
If you want to check polls on preprod
, simply pass a network
parameter when creating a new CardanoBI client instance like so:
const CBI = await new CardanoBI({network: "preprod"});
Open a shell terminal and run the following:
curl https://mainnet.cardanobi.io:4000/api/core/polls | jq .
At this stage you should have been able to retrieve the current list of polls defined on mainnet:
[
{
"poll_hash": "96861fe7da8d45ba5db95071ed3889ed1412929f33610636c072a4b5ab550211",
"tx_hash_hex": "fae7bda85acb99c513aeab5f86986047b6f6cbd33a8e11f11c5005513a054dc8",
"start_epoch_no": 412,
"end_epoch_no": 416,
"json": "{\"0\": [\"Which setup would you prefer to be put in place from Q3 2023 onw\", \"ards?\"], \"1\": [[\"Keep k at 500 and minPoolCost at 340 ada\"], [\"Keep k at 500 and halve minPoolCost to 170 ada\"], [\"Increase k to 1000 and keep minPoolCost at 340 ada\"], [\"Increase k to 1000 and halve minPoolCost to 170 ada\"], [\"I would prefer to abstain\"], [\"None of the provided options\"]]}"
}
]
As you can see in the above json response, at the time of writing (19/05/2023), only one poll was available on mainnet
.
GOING FURTHER
Let's look at the attributes of the response:
poll_hash
is the blake2b-25 hash of the question asked by the poll.tx_hash_hex
is the hexadecimal encoding of the hash of the transaction that carried the metadata label94
and therefore the question of the poll.start_epoch_no
andend_epoch_no
represents the epoch range for the poll, starting from the epoch when votes can be casted, ending with the epoch when a snapshot of votes/stakes are taken to build up the final poll results.json
is the raw on-chain metadata defining both question and response, the response being a multiple choice.
Step 3 - Let's deep dive in the first ever SPO Poll votes!
To retrieve the votes, we will simply go ahead and call this endpoint:
You can view the full description of this endpoint by following this link:
DOCS All polls: /api/core/polls/{poll_hash}
(Drum roll) And the results are...not so fast, a bit more work is required:
- Node.js
- Shell
Call the polls_
method, this time passing as a parameter the poll hash
retrieved previously, in order to get the full detailed breakdown of votes for this particular poll:
const poll_results = await CBI.core.polls_({poll_hash: '96861fe7da8d45ba5db95071ed3889ed1412929f33610636c072a4b5ab550211'});
console.log(poll_results);
Open a shell terminal and run the following:
curl https://mainnet.cardanobi.io:4000/api/core/polls/96861fe7da8d45ba5db95071ed3889ed1412929f33610636c072a4b5ab550211 | jq .
At this stage you should be able to see the current tally for this historical vote or the full result even, if you ran the above past the end date of the vote.
Either way, you should be able to see a json similar to the one depicted in the below snapshot.
PARTIAL SNAPSHOT AS OF 19/05/2023 2.30 BST
{
"poll_hash": "96861fe7da8d45ba5db95071ed3889ed1412929f33610636c072a4b5ab550211",
"question": "Which setup would you prefer to be put in place from Q3 2023 onwards?",
"choices": [
"Keep k at 500 and minPoolCost at 340 ada",
"Keep k at 500 and halve minPoolCost to 170 ada",
"Increase k to 1000 and keep minPoolCost at 340 ada",
"Increase k to 1000 and halve minPoolCost to 170 ada",
"I would prefer to abstain",
"None of the provided options"
],
"tx_hash_hex": "fae7bda85acb99c513aeab5f86986047b6f6cbd33a8e11f11c5005513a054dc8",
"start_epoch_no": 412,
"end_epoch_no": 416,
"json": "{\"0\": [\"Which setup would you prefer to be put in place from Q3 2023 onw\", \"ards?\"], \"1\": [[\"Keep k at 500 and minPoolCost at 340 ada\"], [\"Keep k at 500 and halve minPoolCost to 170 ada\"], [\"Increase k to 1000 and keep minPoolCost at 340 ada\"], [\"Increase k to 1000 and halve minPoolCost to 170 ada\"], [\"I would prefer to abstain\"], [\"None of the provided options\"]]}",
"summary": {
"votes": {
"total": 149,
"counts": [
16,
13,
38,
70,
0,
12
],
"pcts": [
0.10738255,
0.087248325,
0.25503355,
0.46979865,
0,
0.08053691
]
},
"stakes": {
"total": 2433955285288878,
"sums": [
572638208726258,
455418812740480,
346833715874855,
793308915228817,
0,
265755632718468
],
"pcts": [
0.23527063,
0.18711059,
0.14249797,
0.32593405,
0,
0.10918674
]
},
"delegators": {
"total": 151956,
"sums": [
23374,
40087,
15290,
49413,
0,
23792
],
"pcts": [
0.15382084,
0.26380664,
0.10062123,
0.32517967,
0,
0.15657164
]
}
},
"votes": [
{
"ticker_name": "SPIDR",
"pool_name": "SpidarPool",
"pool_id": "pool1tnl3yxmj8848vq6meduhz9n5520a7dwsh05r5gfyvlatj87k3jl",
"tx_hash_hex": "3533cdb973f024c9b3ab1a592c27cb2263ba37c510cb9083a1a791d4d4876194",
"response": "Increase k to 1000 and halve minPoolCost to 170 ada",
"response_json": "{\"2\": \"0x96861fe7da8d45ba5db95071ed3889ed1412929f33610636c072a4b5ab550211\", \"3\": 3}",
"extra_sign_hash": "5cff121b7239ea76035bcb79711674a29fdf35d0bbe83a212467fab9",
"cold_vkey": "5cff121b7239ea76035bcb79711674a29fdf35d0bbe83a212467fab9",
"delegator_count": 54,
"delegated_stakes": 160016196440
},
{
"ticker_name": "PANL",
"pool_name": "PANL Stake Pool",
"pool_id": "pool1qhs3cf9ymc2nvmrd2j8cs36cj9jdqgnqk6s9ngyvy2lz5s8rak8",
"tx_hash_hex": "7e0e501f2f80a6c91287814d8e7f4ce0c8c61902c66921d2b9e72b5364e46a7c",
"response": "Increase k to 1000 and halve minPoolCost to 170 ada",
"response_json": "{\"2\": \"0x96861fe7da8d45ba5db95071ed3889ed1412929f33610636c072a4b5ab550211\", \"3\": 3}",
"extra_sign_hash": "05e11c24a4de15366c6d548f8847589164d02260b6a059a08c22be2a",
"cold_vkey": "05e11c24a4de15366c6d548f8847589164d02260b6a059a08c22be2a",
"delegator_count": 31,
"delegated_stakes": 374170765492
},
...
{
"ticker_name": "ACI",
"pool_name": "Blockademia",
"pool_id": "pool144m894gswuy3se407ma5870nkaw5ylykrak73gf4kpepj50ulfa",
"tx_hash_hex": "65bd01f3d840643a98513f5e1617671ee6ff25069728c44affdc839f5b880ecd",
"response": "Increase k to 1000 and keep minPoolCost at 340 ada",
"response_json": "{\"2\": \"0x96861fe7da8d45ba5db95071ed3889ed1412929f33610636c072a4b5ab550211\", \"3\": 2}",
"extra_sign_hash": "ad7672d51077091866aff6fb43f9f3b75d427c961f6de8a135b07219",
"cold_vkey": "ad7672d51077091866aff6fb43f9f3b75d427c961f6de8a135b07219",
"delegator_count": 223,
"delegated_stakes": 1471160856776
},
{
"ticker_name": "ALKUL",
"pool_name": "Lalkul",
"pool_id": "pool1vuwavngz2hums4ll34z9afdjkn826wnzgy2kpmnynhd0wr5qxkh",
"tx_hash_hex": "96d94a521d373475fed2c7b0bf6e3d6392beea6cbac961fb0b1721215d5ad27c",
"response": "Increase k to 1000 and keep minPoolCost at 340 ada",
"response_json": "{\"2\": \"0x96861fe7da8d45ba5db95071ed3889ed1412929f33610636c072a4b5ab550211\", \"3\": 2}",
"extra_sign_hash": "671dd64d0255f9b857ff8d445ea5b2b4cead3a62411560ee649ddaf7",
"cold_vkey": "671dd64d0255f9b857ff8d445ea5b2b4cead3a62411560ee649ddaf7",
"delegator_count": 37,
"delegated_stakes": 1223644341079
}
]
}
When you look at number of votes, a good majority of votes went to "Increase k to 1000 and keep minPoolCost at 340 ada"
and "Increase k to 1000 and halve minPoolCost to 170 ada"
.
When you look at stakes and delegators, these 2 choices are still in the lead but by a much narrower margin.
This is not surprising, small pools are yearning for more even distribution of stakes whilst larger pools are pretty much happy with a status quo. But how does one creates an insentive for stakes to move around in the first place?
Increasing k
would do that for sure, but with no guarantee that moving stakes would get delegated to small SPO of course.
Now we won't open the Pandora's box here in this tutorial, we will leave it at that for now, but small SPOs we love you 😃!
Step 4 - Understanding better the key data attributes at play in CIP-0094
One of the key elements in understanding CIP-0094's inner workings, that got us baffled for a while, was the ability to link a vote transaction to the stake pool that cast the vote in the first place.
This link is done via the cold verification key that each pool use to sign their vote transaction.
In cardano-db-sync
the cold key hash is stored in the table extra_key_witness
against each vote transaction ID.
And that's great, but how on earth do you link each cold key to its corresponding stake pool, I hear you ask.
Well the answer wasn't to be found in cardano-db-sync
, but on chain only, and to query this information, no other than cardano-cli
was able to get the job done 😃
cardano-cli query pool-params --mainnet --stake-pool-id pool1y24nj4qdkg35nvvnfawukauggsxrxuy74876cplmxsee29w5axc
The above query seeks to retrieve the on-chain pool parameters for a pool, given by its ID pool132jxjzyw4awr3s75ltcdx5tv5ecv6m042306l630wqjckhfm32r
a.k.a. ADADCT
and would return this json on mainnet:
ADACT POOL PARAMS
{
"futurePoolParams": null,
"poolParams": {
"cost": 340000000,
"margin": 0.04,
"metadata": {
"hash": "42771b05b30f180890980613b3147f6bb797fe1f8a83e92d39a3135ec9559ea8",
"url": "https://adacapital.io/adact_mainnet.json"
},
"owners": [
"fa504163dcaa366236a08e9cee1e4be4a609623b72b55be8edae9c1f"
],
"pledge": 100000000000,
"publicKey": "22ab39540db22349b1934f5dcb7788440c33709ea9fdac07fb343395",
"relays": [
{
"single host address": {
"IPv4": "51.105.18.17",
"IPv6": null,
"port": 3001
}
},
{
"single host address": {
"IPv4": "52.182.224.171",
"IPv6": null,
"port": 3001
}
}
],
"rewardAccount": {
"credential": {
"key hash": "fa504163dcaa366236a08e9cee1e4be4a609623b72b55be8edae9c1f"
},
"network": "Mainnet"
},
"vrf": "9be345bcbcb0cf0559b1135467fd2e4c78c741898cdf8bcb737b2dc5122632df"
},
"retiring": null
}
Part of it and essential to tying up stake pools to on-chain votes, is the publickey
which is the hash of the cold verification key of the above pool!
Conclusions
This wraps up our first ever CardanoBI
tutorial.
We hope you found it useful and informative.
There will be plenty more, in the months to come, once we move from Alpha to Beta and we release more platform features.
Please reach out with any comments, questions or suggestions.
Until next time, take care 💚