Embedded Governance Design

We can discuss it here if you want.
Suggestions:

  • Negative: Yield decay increases with each missed vote
  • Positive: Yield growth increases with each cast vote
    or
  • Negative: Vote weight decays with each missed vote
  • Positive: Vote weight grows with each cast vote

Interesting concept. I like how it can deter last-minute swing voting.

I really like this and I think it’s possible.

People with multiple pillars isn’t really an issue to me.
Burn based voting is much more secure and long term aligned than delegation based voting.
Strict Delegation weight based voting gives exponential voting power which is the problem of single coin PoS networks.

The immediate goal of this thread is to come up with the design for the embedded governance contract, which is on-chain. But I think it is worthwhile to discuss the governance needs that can happen off-chain as well. At the very least to be explicit about what we aren’t including.

We need ways for pillars to signal to each other off-chain, more options than just YES, NO, ABSTAIN
Stuff like, “I need more info”, “good work, bad price”, “prioritize this”

And we need ways for delegators to communicate with their pillars.

By momentum I’m assuming you mean, just voting based how others are voting. I would agree that it seems most of our pillars are just doing this.

Preventing this is possible through a commit/reveal scheme.
It’s a 2 round process
Instead of simply posting something like “proposal id, vote” on chain.
The first round you have to post “hash(proposal id, vote, random salt)” on chain. This is committing to your vote on chain without revealing it.
Then after that round is up, you post the unhashed vote data. People can verify that it matches what you posted in the first round.

It’s possible, not everyone will reveal though. But in this case, you could just count the revealed votes.

In oracle systems, the goal is to collectively determine the value of something off-chain.
In a decentralized system, you have to incentive telling the truth.
So they do stuff like rewarding the participants who chose the most popular option.
The idea is that if the participants are decentralized enough, then they can’t collude to make a false option the most chosen, so instead each participant will just choose the true value.
If a false option already has visible momentum however, then they might be incentived to choose the false option. So a commit/reveal scheme can be used.

For AZ though, there isn’t an objective truth. It’s subjective evaluation.

Is this what you are referring to by the way?

Thinking of Polkadot governance. Locking your coins with the vote- longer lock more weight.

I was thinking of a different system involving ephemeral private keys.

  • User submits a poll to the contract
  • The contract generates a pubkey/privatekey pair for each poll and publishes the pubkey
  • Poll participants submit encrypted votes
  • While the poll is active, the contract can only reveal the number of participants
  • When the poll is completed, the private key is revealed, allowing everyone to verify the votes
1 Like

Can you elaborate on how this would work?

This sounds somewhat like Threshold Encryption, which can prevent MEV, but also momentum based voting.

A contract is not it its own entity. It’s logic that runs all nodes. It can’t generate/store a secret on its own. I’m guessing you could do something like, initiate a poll, then have the pillars construct a threshold multisig key for that poll. Then each submit their own contribution to the decryption.

https://docs.skale.network/technology/intro-threshold

1 Like

I wasn’t sure if it was possible, thanks for confirming.
A threshold scheme could work.

I’m wondering if we should defer secret voting and adjusted incentives to a future iteration of the governance module.

1 Like

Yeah definitely not a first iteration kind of thing haha.
First iteration, we really just need to be able to create/activate on our own, and then we can go as fast as we can build+educate the community and pillars.

2 Likes

Okay let’s try and make some progress on an actual interface.

From vm/embedded/definition/spork.go, we can take the ABI

	jsonSpork = `
	[
		{"type":"function","name":"CreateSpork","inputs":[{"name":"name","type":"string"},{"name":"description","type":"string"}]},
		{"type":"function","name":"ActivateSpork","inputs":[{"name":"id","type":"hash"}]},

		{"type":"variable", "name":"sporkInfo", "inputs":[
			{"name":"id", "type":"hash"},
			{"name":"name", "type":"string"},
			{"name":"description", "type":"string"},
			{"name":"activated", "type": "bool"},
			{"name":"enforcementHeight", "type": "uint64"}
		]}
	]`

Let’s use this as a starting point for possible ABIs.

For people who aren’t familiar with this structure, functions determine the transactions that people can make to interact with the contract, and variables are mostly for deciding how data should be stored.

For read-only functions, there the RPC interface. We should design that too, but I think it’s more straightforward and easier once the ABI is there.
There’s also plasma requirements and of course the actual logic, but ABI is a good place to start imo.

From vm/embedded/definition/accelerator.go we can see that items that can be voted on have a status and some timestamps, which i’m guessing are used validate that votes are within voting period

		{"type":"variable","name":"project","inputs":[
			{"name":"id", "type":"hash"},
			{"name":"owner","type":"address"},
			{"name":"name","type":"string"},
			{"name":"description","type":"string"},
			{"name":"url","type":"string"},
			{"name":"znnFundsNeeded","type":"uint256"},
			{"name":"qsrFundsNeeded","type":"uint256"},
			{"name":"creationTimestamp","type":"int64"},
			{"name":"lastUpdateTimestamp","type":"int64"},
			{"name":"status","type":"uint8"},
			{"name":"phaseIds","type":"hash[]"}
		]},

		{"type":"variable","name":"phase","inputs":[
			{"name":"id", "type":"hash"},
			{"name":"projectId", "type":"hash"},
			{"name":"name","type":"string"},
			{"name":"description","type":"string"},
			{"name":"url","type":"string"},
			{"name":"znnFundsNeeded","type":"uint256"},
			{"name":"qsrFundsNeeded","type":"uint256"},
			{"name":"creationTimestamp","type":"int64"},
			{"name":"acceptedTimestamp","type":"int64"},
			{"name":"status","type":"uint8"}
		]}

So if we wanted to start with a very quick passthrough spork contract with pillar voting, it could look something like:

	jsonSpork = `
	[
		{"type":"function","name":"ProposeCreateSpork","inputs":[{"name":"name","type":"string"},{"name":"description","type":"string"}]},
		{"type":"function","name":"ProposeActivateSpork","inputs":[{"name":"id","type":"hash"}]},

		{"type":"variable", "name":"proposedSporkInfo", "inputs":[
			{"name":"id", "type":"hash"},
			{"name":"name", "type":"string"},
			{"name":"description", "type":"string"},
			{"name":"proposalTimestamp","type":"int64"},
			{"name":"creationTimestamp","type":"int64"},
			{"name":"activationTimestamp","type":"int64"},
			{"name":"lastUpdateTimestamp","type":"int64"},
			{"name":"status", "type": "uint8"},
		]}
	]`

status can be an enum, of Proposed, Created, Activated
Need to keep in mind that the id used for the ProposeActivateSpork function will be different that the Proposal Id which will be used for voting.
We might not need all of the timestamps, but just putting them there to start.

From vm/embedded/definition/common.go, we can take the common voting ABI

		{"type":"variable","name":"pillarVote","inputs":[
			{"name":"id","type":"hash"},
			{"name":"name","type":"string"},
			{"name":"vote","type":"uint8"}
		]},
		{"type":"variable","name":"votableHash","inputs":[
			{"name":"exists","type":"bool"}
		]},
		{"type":"variable","name":"pillarVote","inputs":[
			{"name":"id","type":"hash"},
			{"name":"name","type":"string"},
			{"name":"vote","type":"uint8"}
		]},
		{"type":"variable","name":"votableHash","inputs":[
			{"name":"exists","type":"bool"}
		]},

We should break down exactly how these work.
and we likely need Update functions to trigger vote tallying

		{"type":"function","name":"Update", "inputs":[]},
		{"type":"variable","name":"lastUpdate","inputs":[{"name":"height","type":"uint64"}]},
		{"type":"variable","name":"lastEpochUpdate","inputs":[{"name":"lastEpoch", "type": "int64"}]},

Please ask questions if any part of this doesn’t make sense.
We need to work with our pillars who want to understand.

1 Like

Oh I forgot to store the created spork’s id
note the addition of spork id

	jsonSpork = `
	[
		{"type":"function","name":"ProposeCreateSpork","inputs":[{"name":"name","type":"string"},{"name":"description","type":"string"}]},
		{"type":"function","name":"ProposeActivateSpork","inputs":[{"name":"id","type":"hash"}]},

		{"type":"variable", "name":"proposedSporkInfo", "inputs":[
			{"name":"id", "type":"hash"},
			{"name":"sporkId", "type":"hash"},
			{"name":"name", "type":"string"},
			{"name":"description", "type":"string"},
			{"name":"proposalTimestamp","type":"int64"},
			{"name":"creationTimestamp","type":"int64"},
			{"name":"activationTimestamp","type":"int64"},
			{"name":"lastUpdateTimestamp","type":"int64"},
			{"name":"status", "type": "uint8"},
		]}
	]`

So this interface basically implements a state machine for proposedSporkInfo

I think it would be good to design what a transaction focused ABI would look like to compare

We could do something like

	jsonSpork = `
	[
		{"type":"function","name":"ProposeCreateSpork","inputs":[{"name":"name","type":"string"},{"name":"description","type":"string"}]},
		{"type":"function","name":"ProposeActivateSpork","inputs":[{"name":"id","type":"hash"}]},

		{"type":"variable", "name":"proposedTransactionInfo", "inputs":[
			{"name":"id", "type":"hash"},
			{"name":"address", "type":"address"},
			{"name":"tokenStandard", "type":"tokenStandard"},
			{"name":"amount", "type":"uint256"},
			{"name":"data", "type":"bytes"},
			{"name":"proposalTimestamp","type":"int64"},
			{"name":"lastUpdateTimestamp","type":"int64"},
			{"name":"status", "type": "uint8"},
		]}
	]`

+ Update Logic
+ Voting Logic

The ProposeCreateSpork and ProposeActivateSpork functions would fill out the ProposedTx fields

And the status might just be a boolean, to indicate if the tx has been sent or not

Storing Proposed Transactions gets us in the direction we want to go I think.
Could add another layer on top so that voting happens on a list of transactions.

And it leaves room to build out a generic templating system. So that Propose* → proposedTransactionInfo mappings could be defined as on-chain data.

I’m not familiar with the process so my question might be odd but will it looks like:

  1. Proposals are made on forums + GitHub then the vote signals opinions before a pull request is accepted on GitHub then merged before we all update?

Or

  1. Proposals include the code that force an update to the variables, all happening onchain without spork and the GitHub process?

So there are a few things to keep in mind when it comes to protocol changes.
Nodes verify that that transactions are valid. When we change the protocol we change the definition of valid transactions.

Nodes need to be able to verify the entire chain, from start to end, for initial block download.
That means that the node software needs to keep around logic to validate transactions based on current and previous definitions of the protocol.

In the future there might be multiple clients for nom. Not just go-zenon.
For example, bitcoin has bitcoin-core and btcd, ethereum has geth and reth now.

No matter what the client is, they all have to activate the logic at the same time. Or else a fork will happen. So this is what the on-chain spork contract is for.

When you create a spork, all you do is get an ID. Then the node software maintainers can use that ID to put logic into the node that once that spork id is activated, to use the new logic.

Right now the spork contract is controlled by an address owned by the founding devs.
What we’re doing right now is making it controlled by the community/pillars.

Not all protocol changes require new code though that has to be merged. If it’s just changing a variable and not the logic that handles it. We’re trying to design a governance contract that can (eventually) accommodate both.

4 Likes

If you change a variable that is not identical in other nodes, the rest of the network will ignore (and drop your node/blacklist it) your change (eg changing Bitcoin’s maxSupply variable).

1 Like

Just playing with some possibilities

	jsonSpork = `
	[
		{"type":"function","name":"TemplateCreateSpork","inputs":[{"name":"name","type":"string"},{"name":"description","type":"string"}]},
		{"type":"function","name":"TemplateActivateSpork","inputs":[{"name":"id","type":"hash"}]},

		{"type":"variable", "name":"templatedTransactionInfo", "inputs":[
			{"name":"id", "type":"hash"},
			{"name":"address", "type":"address"},
			{"name":"tokenStandard", "type":"tokenStandard"},
			{"name":"amount", "type":"uint256"},
			{"name":"data", "type":"bytes"},
		]},

		{"type":"function","name":"ProposeTransactions","inputs":[{"name":"txlist","type":"bytes"}]},

		{"type":"variable", "name":"proposalInfo", "inputs":[
			{"name":"id", "type":"hash"},
			{"name":"txlist", "type":"bytes"},
			{"name":"creationTimestamp","type":"int64"},
			{"name":"status","type":"uint8"},
		]},
+ Voting
+ Update

	]`

Two phase process
First call the relevant Template function and get a templated Tx info Id
Then call ProposeTransactions functions using the id
Pillar votes are on the list of txs

txlist just a list of hash ids appended to each other
we are start getting into the realm of custom abis

Yeah which is why we would move certain variables out of the binary and into on-chain state where a change would have an activation height like a spork.

Interesting idea. That applies only for mutable variables (non-constants)?

Could you give an example of what you mean by mutable vs constant?

I think there are items in both:
https://github.com/zenon-network/go-zenon/blob/master/vm/constants/embedded.go
https://github.com/zenon-network/go-zenon/blob/master/vm/constants/plasma.go
that could make to sense to be changeable via governance.