Layer 0 Governance of Root Nodes
Usually, the Q governance takes place on-chain, but pure on-chain governance allows malicious nodes to gain control over the network in case they receive the on-chain majority. In such a case, maliciously acting validators can avoid loosing their majority by transaction blacklisting and censoring. To avoid this, Q introduces Layer 0 governance (or L0 governance). It's purpose is to allow root nodes to find countermeasures to heal and correct the on-chain governance structures in case it has been compromised by malicious validators.
Currently, L0 governance allows to maintain two kinds of lists:
List name | Description |
---|---|
Root node list | This list contains public root node addresses and determines who can sign L0 governance actions |
Validator exclusion list | This list contains blacklist of public validator addresses. Validators inside exclusion list can’t produce new blocks. For each validator, block number of ban is specified |
Precondition
To perform L0 governance actions you need to understand how to access q-client console
. You can access q-client
console by running
command docker-compose exec node geth attach data/geth.ipc
on the node with unlocked root node account.
Updating the Root Node Lists
First of all it is good to understand how to see current active root node list.
See active Root Node List
Run:
gov.activeRootList()
Example Output:
{
hash: "0xcf6fb61fc8120e3ca17a8de6cfa03b61c0a39f62c12134c9268e43d91149918b",
nodes: ["0x6a39b688d591ea00c9ea69658438794204b5cc62", "0x64d4edefe8ba86d3588b213b0a053e7b910cad68", "0x4a14d788d86d021670ebcece1196631d66595984"],
signers: ["0x6a39b688d591ea00c9ea69658438794204b5cc62", "0x4a14d788d86d021670ebcece1196631d66595984", "0x64d4edefe8ba86d3588b213b0a053e7b910cad68"],
timestamp: 1641484005
}
Description:
- hash - contains unique hash for root nodes and timestamp
- nodes - contains list of public addresses of root nodes in the list
- signers - contains list of public addresses of root nodes that signed this list
- timestamp - contains time when the root node list was proposed (could be specified when proposing new root node list)
Active L0 root node list is not always the same as on-chain. Usually after adding root node using voting, we need to
propagate new root node
list via L0 governance. Let’s say we added address 0xF691ea2E16B1017CE4893C2D2b91e745a3E501ad
as a root node, now
on-chain root node list
contains four addresses instead of three. To check it we can use another L0 governance method.
See current on-chain Root Node List
Run:
gov.onchainRootList()
Example Output:
{
hash: "0xaff40e792ce2205e7c94c5a944aa80b7945036094f3a464e98b15d1bb8f761d7",
nodes: ["0xf691ea2e16b1017ce4893c2d2b91e745a3e501ad", "0x6a39b688d591ea00c9ea69658438794204b5cc62", "0x64d4edefe8ba86d3588b213b0a053e7b910cad68", "0x4a14d788d86d021670ebcece1196631d66595984"],
signers: null,
timestamp: 1641529405
}
As you can see, now we have four addresses in nodes
section. But it would be much easier to see only the difference
between active
and on-chain
root node lists. For this we have another method.
See difference between active and on-chain Root Node Lists
Run:
gov.diffRootList("active", "onchain")
Example Output:
[{
Diff: null,
Name: "active"
}, {
Diff: ["0xf691ea2e16b1017ce4893c2d2b91e745a3e501ad"],
Name: "onchain"
}]
Description:
- first object - contains addresses from active list but not from on-chain list
- second object - contains addresses from on-chain list but not from active list
Output means that if on-chain list is adopted, active list will contain new
address (0xF691ea2E16B1017CE4893C2D2b91e745a3E501ad
).
Now let’s set new active root list. To do so one root node should propose new list and others should accept it.
Propose new Root Node List based on on-chain Root List
Run:
gov.proposeOnchainRootList()
Example Output:
"0x55e82161cb160f29215ee89b4d35f5f0ebeaf83996ebb2f0ef6481087d2507a6"
This proposes a new RootList based on the current on-chain root list. If you want to manually propose a root list,
run:
gov.proposeRootListUpdate({
nodes: [
"0xfd3ba4c7ebda55c038316c776f2479b2909da7a5",
"0xbada551878e60b7d9173452695c1b3d190c3a3dc",
"0x0ab8d42796bc11a0c028a25a79cf31d8eabc65cd"
],
timestamp: 1647852102
})
To verify the proposed root list, use this command:
Run (if another root node has proposed the list):
gov.proposedRootList()
Run (if you have proposed the root list yourself):
gov.desiredRootList()
Example Output:
{
hash: "0xa812273de092fdf8cf27a5311008a0bf714691c38d6737a62ea40c0a76c2532f",
nodes: ["0xf691ea2e16b1017ce4893c2d2b91e745a3e501ad", "0x6a39b688d591ea00c9ea69658438794204b5cc62", "0x64d4edefe8ba86d3588b213b0a053e7b910cad68", "0x4a14d788d86d021670ebcece1196631d66595984"],
signers: ["0x64d4edefe8ba86d3588b213b0a053e7b910cad68"],
timestamp: 1641529675
}
We can see list of new root node addresses and who signed it. But usually to make a decision, we need to check only
if on-chain
list is
the same as proposed
list.
See difference between proposed and on-chain Root Node Lists
Run:
gov.diffRootList("proposed", "onchain")
Example Output:
[{
Diff: null,
Name: "proposed"
}, {
Diff: null,
Name: "onchain"
}]
If Diff
parameter for both objects is null
, then it means that both lists contain the same list of addresses.
Now, we are sure that proposed list contains all addresses from on-chain root node list and we can accept it.
Accept proposed Root Node List
Run:
gov.acceptProposedRootList()
This action will sign the proposed root node list and propagate it to other nodes. Once there is enough
signatures, proposed
root node
list will become active
and will reflect what we have on-chain
.
Updating the Validator Exclusion List
Before updating we can check current exclusion list.
See current Validator Exclusion List
Run:
gov.activeExclusionList()
Example Output: (one validator excluded):
{
hash: "0x5cc23f76da89c61f7b0ffbc09f101b96e02484d6f01689b7e4f31efc5057c31a",
signers: ["0x64d4edefe8ba86d3588b213b0a053e7b910cad68", "0x6a39b688d591ea00c9ea69658438794204b5cc62", "0x4a14d788d86d021670ebcece1196631d66595984"],
timestamp: 1641484876,
validators: [{
address: "0x4a14d788d86d021670ebcece1196631d66595984",
block: 5301,
endBlock:6000
},
validators: [{
address: "0x64d4edefe8ba86d3588b213b0a053e7b910cad68",
block: 4000
}]
}
Example Output: (no validators excluded):
{
hash: "0x7dc4f6d05d3921da619635b412c54c20b73a0725117c1f42d512ac131809afbf",
signers: ["0x4a14d788d86d021670ebcece1196631d66595984", "0x64d4edefe8ba86d3588b213b0a053e7b910cad68", "0x6a39b688d591ea00c9ea69658438794204b5cc62"],
timestamp: 1641561131,
validators: null
}
If we want to update validator exclusion list, we can do it by creating proposal on L0 by root node account.
Propose Validator Exclusion List Update
Run:
gov.proposeExclusionListUpdate({
validators: [
{
"address": "0x4a14d788d86d021670ebcece1196631d66595984",
"block": 5304,
"endBlock":6000
}
{
"address": "0x64d4edefe8ba86d3588b213b0a053e7b910cad68",
"block": 4000
}
],
timestamp: 1641561132
})
Example Output:
"0x7dc4f6d05d3921da619635b412c54c20b73a0725117c1f42d512ac131809afbf"
In this example we proposed to ban validator with address 0x4a14d788d86d021670ebcece1196631d66595984
on block 5304
.
This means that
starting from block 5304
validator won't be able to produce new blocks. However, you can specify the end block of the
ban. In our example
this is endBlock:6000
. End block is optional. If this block is set - validator will be able to produce blocks again
after block 6000
.
In the output we get proposal hash.
After one root node proposed update, this proposal should be available on other nodes. To check it, we can call another method to see proposed root list.
See proposed Validator Exclusion List
Run:
gov.proposedExclusionList()
Example Output:
{
hash: "0xd175110816f3b26afa597d789506f9a36781ff9fb884d8c7df4797c08b9ad0a1",
signers: ["0x64d4edefe8ba86d3588b213b0a053e7b910cad68"],
timestamp: 1641561132,
validators: [{
address: "0x4a14d788d86d021670ebcece1196631d66595984",
block: 5304
}]
}
Now, we are sure that proposed list contains all required addresses and we can accept it.
Accept proposed Exclusion List
Run:
gov.acceptProposedExclusionList()
This action will sign the proposed validator exclusion list and propagate it to other nodes. Once there is enough
signatures, proposed
validator exclusion list will become active
and validators in the list won’t be able to produce blocks.
Human-Friendly Exclusion List Output
For all exclusion list types there is a method that prints more human-friendly representation of the list:
gov.activeExclusionListPrettify()
gov.proposedExclusionListPrettify()
Example Output:
Hash: 0x43b8e41f1850ae7e521bf05d2853285ab0dbe3a2bb425a747f89eb8f580d20b4
Timestamp(created at): 1668164617 (2022-11-11 13:03:37 +0200 EET)
Signers:
0x64D4edeFE8bA86d3588B213b0A053e7B910Cad68
0x4a14D788D86D021670EBcecE1196631d66595984
Validators:
0x6267c22E0fAB9B071a6e9e3910739D6B07DE6a84:
#2115546 (active, open-ended)
0xE3EA34d3Dcc9B8d45B13e117eB13d5B45249E196:
#2115546 (active, open-ended)
0xF55c02b021f91752521C0cB74303565f25BAcCb1:
#2115546 (active, open-ended)
Root node whitelist co-signing
Each time Root node receives the transition block (end of each epoch) it should sign this block and send signature to its peers. No matter if you are a root node, you will get those signatures from other root nodes. To check signatures you can use the follwing methods:
gov.getRootNodeApprovals
Prints rootnode approvals for the latest transition block.
By default, with no params provided - returns signatures of the latest block.
If only one parameter specified (blockNumber
) - returns signatures of specified block.
Second parameter - hash
- hash of the required block.
Usage of both blockNumber
and hash
is forbidden.
Usage example:
gov.getRootNodeApprovals()
Example Output:
[{
blockNumber: 2085347,
hash: "0x8d1d9cb767b87bd97e3639e1558921014e0bb67d567c7a3189dc651caa5a0674",
signature: "zxNakquu/N6zfWgBqqJBiKt+tATpD4PcIBUEL8VvFyMw0s9KDXq/MVc81QfQd2BUkIpbnvG9vYw2JS2L2O0K5AA=",
signer: "0xa713a6d7a695c95eb4eafdd588b170a17bf64a58"
}, {
blockNumber: 2085347,
hash: "0x8d1d9cb767b87bd97e3639e1558921014e0bb67d567c7a3189dc651caa5a0674",
signature: "H7CF0pqD7LywYEKiZ2roNPixz2AfvXWAHPkQRsAS8jUQ+UaUvjRy7GVRVf9NhbbZ4cjuyUb85AFz6LVigPVXygE=",
signer: "0xe3d50388f8136eac453229d0f88e377a2eb5a80b"
}]
With block hash specified:
gov.getRootNodeApprovals(null,"0xba93726efe1681c086f32e242ca39d62b9755599038f80fabe5b56b0b083a3b1")
Example Output:
[{
blockNumber: 2085246,
hash: "0xba93726efe1681c086f32e242ca39d62b9755599038f80fabe5b56b0b083a3b1",
signature: "J1oJyfw6DbQRVUb4wI8iXOUJMd5O2FtwZzZaItWuuwUHmo45qvFD3ccmOKcYPMSQsfHPCdF9lXcZLsDXOGp3UgE=",
signer: "0xe3d50388f8136eac453229d0f88e377a2eb5a80b"
}, {
blockNumber: 2085246,
hash: "0xba93726efe1681c086f32e242ca39d62b9755599038f80fabe5b56b0b083a3b1",
signature: "GHZqFYSD5bSgkLgJvOX03GJ+lOUrP6p6hBySD2lIfENt9oVIfXrcV4LwQiU0OQ7aI0LPDoM3AsBlCIZsRN4CRAE=",
signer: "0xa713a6d7a695c95eb4eafdd588b170a17bf64a58"
}]
With block number specified:
gov.getRootNodeApprovals(2085246)
Example Output:
[{
blockNumber: 2085246,
hash: "0xba93726efe1681c086f32e242ca39d62b9755599038f80fabe5b56b0b083a3b1",
signature: "J1oJyfw6DbQRVUb4wI8iXOUJMd5O2FtwZzZaItWuuwUHmo45qvFD3ccmOKcYPMSQsfHPCdF9lXcZLsDXOGp3UgE=",
signer: "0xe3d50388f8136eac453229d0f88e377a2eb5a80b"
}, {
blockNumber: 2085246,
hash: "0xba93726efe1681c086f32e242ca39d62b9755599038f80fabe5b56b0b083a3b1",
signature: "GHZqFYSD5bSgkLgJvOX03GJ+lOUrP6p6hBySD2lIfENt9oVIfXrcV4LwQiU0OQ7aI0LPDoM3AsBlCIZsRN4CRAE=",
signer: "0xa713a6d7a695c95eb4eafdd588b170a17bf64a58"
}]