Delegated Proof of Stake
The Delegated Proof of Stake algorithm allows token holders to elect witnesses. Witnesses act as validators of the blockchain, proposing blocks and verifying that transactions are correct. These witnesses serve a standard term length before being subject to elections again.
Parameters
Coin contract address - Specifies which ERC20-like coin contract to use to calculate the power of a vote. By default, this is resolved to the address at coin
.
Witness count - The number of witnesses that can be elected.
Witness salary - Optional. The amount that witnesses get paid for validating blocks.
Vote allocation - Number of votes each coin account gets. By default, this is equal to the number of witnesses.
Election cycle length - How long the election cycle is. By default, this is 1 week.
Minimum power fraction - Optional. How much of the coin supply needs to have voted for elections to be considered valid. For example, a value of 5 corresponds to 20% of the coin supply needing to have voted.
Candidate Registration
All candidates must register by specifying the public key matching their address.
Voting
Each coin account has up to a specified number of votes, generally equal to the number of witnesses. However, the power of each vote is proportional to the balance of coins the account holds. This ensures that accounts with more at stake have a greater voice in how the network is run. In the current implementation, votes do not expire. This means that, unless a vote is explicitly changed, it is assumed that the account holder is satisfied with the job of the witness and will receive the account holder's vote again in the next election. Unlike traditional elections, voting can be done any time so there is no "election day". However, votes are not counted until the election time.
Proxying Votes
In addition to voting directly for witness candidates, accounts can also proxy their vote to a trusted party. This means that the proxy ends up with a voting power proportional to proxy balance + sum(balance of principals)
.
Elections
Any account can trigger an election if enough time has passed by sending a transaction to the network. Witnesses are elected by summing up the total voting power given to them and taking the top N candidates where N is the witness count specified in the initial parameters. This means that all witnesses end up with an equal chance of proposing a block, no matter how many votes they received. If the minimum number of power required specified by the minimum power fraction is not reached then the witness set does not change.
Future Improvements
Bonding
In the future, witnesses may lock up a specified number of coins that can be seized for bad behavior. This adds an additional incentive for good behavior beyond the witness salary.
Proof of Authority
Right now, candidates do not have to prove their identity, but in the future, it may be useful to enable on-chain notarization to verify candidates' identities.
Alternating Election Cycle
Currently, all witnesses are up for reelection at every election. It may be better to have an election cycle that differs from the term length.
Vote Expiration
Currently, votes never expire. However, one can imagine a scenario in which votes expire after a certain time period. This would prevent lost or stolen accounts from having undue influence in elections. This can be done either by looking at the time the vote was cast or by looking at the last activity on the account.
Contract Transactions
registerCandidate
Register a candidate to be a witness.
unregisterCandidate
Unregister a candidate to be a witness.
vote
Vote for a particular candidate.
proxyVote
Proxy your votes to another account.
unproxyVote
Unproxy your votes.
elect
Run the election.
Example CLI Usage
To get started, we first need to initialize the blockchain. The DPOS and Coin smart contracts will automatically be added into genesis.json
.
loom init
Next, we generate public/private keys for an example account:
loom genkey -a pubkey -k privkey
Then, we need to make sure some initial coins on the blockchain are given out so that we have some voting power. To do this we need to modify genesis.json
and change the init
section of the Coin contract configuration. In this example, we'll give ourselves 100 coins:
{
"vm": "plugin",
"format": "plugin",
"name": "coin",
"location": "coin:1.0.0",
"init": {
"accounts": [
{
"owner": {
"chain_id": "default",
"local": "<local address in base64 from genkey>"
},
"balance": 100
}
]
}
},
We also need to tweak the DPOS settings for this example so we can run an election right now instead of waiting for a full election cycle for votes to come in. We do this by changing the electionCycleLength
in genesis.json
to 0
. We'll also add a salary of 10 coins for witnesses:
{
"vm": "plugin",
"format": "plugin",
"name": "dpos",
"location": "dpos:1.0.0",
"init": {
"params": {
"witnessCount": "21",
"electionCycleLength": "0",
"minPowerFraction": "5",
"witnessSalary": "10"
},
"validators": [
{
"pubKey": "<your validator public key>",
"power": "10"
}
]
}
}
We then boot the blockchain which will initialize the Coin and DPOS smart contracts.
loom run
To send transactions to the network we can use the example-cli from the go-loom project. This can be built by running:
make example-cli
We can check the witness list at any time by running the list_witnesses
subcommand:
./example-cli call list_witnesses
First, we'll fund the dpos contract so that witnesses can get paid. This is simply a transfer to the dpos
contract:
./example-cli call transfer dpos 90 -p privkey
We can also check our balance and the balance of the dpos contract at any time:
./example-cli call balance <your address>
./example-cli call balance dpos
In order to run for a witness seat, we need to register on the blockchain. For this example, we'll just register ourselves:
./example-cli call register_candidate <public key> -p privkey
Then, we'll vote for ourselves, giving all of our vote allocation, which is 21 votes:
./example-cli call vote <your address> 21 -p privkey
Finally, we'll run the election, which we've rigged 😃:
./example-cli call elect -p privkey
To verify that we've been elected we can check the witness list again to see that it's changed:
./example-cli call list_witnesses
We can run the election again and verify we were paid for our service:
./example-cli call elect -p privkey
./example-cli call balance <your address>