mirror of
https://github.com/vacp2p/rfc-index.git
synced 2026-01-07 23:04:09 -05:00
VAC-RAW/Consensus-hashgraphlike RFC (#142)
This simple, scalable and decentralized consensus is for using in decentralization MLS RFC. todo: - [x] solve lints - [x] refine the RFC: adding liveness and time expiration section, also default counting silent peers (peers that dont participate the voting) - [x] add references - [x] add license - [x] first round reviewing - [x] second round reviewing - [x] last round review --------- Co-authored-by: Ekaterina Broslavskaya <seemenkina@gmail.com>
This commit is contained in:
252
vac/raw/consensus-hashgraphlike.md
Normal file
252
vac/raw/consensus-hashgraphlike.md
Normal file
@@ -0,0 +1,252 @@
|
|||||||
|
---
|
||||||
|
title: HASHGRAPHLIKE CONSENSUS
|
||||||
|
name: Hashgraphlike Consensus Protocol
|
||||||
|
status: raw
|
||||||
|
category: Standards Track
|
||||||
|
tags:
|
||||||
|
editor: Ugur Sen [ugur@status.im](mailto:ugur@status.im)
|
||||||
|
contributors: seemenkina [ekaterina@status.im](mailto:ekaterina@status.im)
|
||||||
|
---
|
||||||
|
## Abstract
|
||||||
|
|
||||||
|
This document specifies a scalable, decentralized, and Byzantine Fault Tolerant (BFT)
|
||||||
|
consensus mechanism inspired by Hashgraph, designed for binary decision-making in P2P networks.
|
||||||
|
|
||||||
|
## Motivation
|
||||||
|
|
||||||
|
Consensus is one of the essential components of decentralization.
|
||||||
|
In particular, in the decentralized group messaging application is used for
|
||||||
|
binary decision-making to govern the group.
|
||||||
|
Therefore, each user contributes to the decision-making process.
|
||||||
|
Besides achieving decentralization, the consensus mechanism MUST be strong:
|
||||||
|
|
||||||
|
- Under the assumption of at least `2/3` honest users in the network.
|
||||||
|
|
||||||
|
- Each user MUST conclude the same decision and scalability:
|
||||||
|
message propagation in the network MUST occur within `O(log n)` rounds,
|
||||||
|
where `n` is the total number of peers,
|
||||||
|
in order to preserve the scalability of the messaging application.
|
||||||
|
|
||||||
|
## Format Specification
|
||||||
|
|
||||||
|
The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”,
|
||||||
|
“SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document
|
||||||
|
are to be interpreted as described in [2119](https://www.ietf.org/rfc/rfc2119.txt).
|
||||||
|
|
||||||
|
## Flow
|
||||||
|
|
||||||
|
Any user in the group initializes the consensus by creating a proposal.
|
||||||
|
Next, the user broadcasts the proposal to the whole network.
|
||||||
|
Upon each user receives the proposal, validates the proposal,
|
||||||
|
adds its vote as yes or no and with its signature and timestamp.
|
||||||
|
The user then sends the proposal and vote to a random peer in a P2P setup,
|
||||||
|
or to a subscribed gossipsub channel if gossip-based messaging is used.
|
||||||
|
Therefore, each user first validates the signature and then adds its new vote.
|
||||||
|
Each sending message counts as a round.
|
||||||
|
After `log(n)` rounds all users in the network have the others vote
|
||||||
|
if at least `2/3` number of users are honest where honesty follows the protocol.
|
||||||
|
|
||||||
|
In general, the voting-based consensus consists of the following phases:
|
||||||
|
|
||||||
|
1. Initialization of voting
|
||||||
|
2. Exchanging votes across the rounds
|
||||||
|
3. Counting the votes
|
||||||
|
|
||||||
|
### Assumptions
|
||||||
|
|
||||||
|
- The users in the P2P network can discover the nodes or they are subscribing same channel in a gossipsub.
|
||||||
|
- We MAY have non-reliable (silent) nodes.
|
||||||
|
- Proposal owners MUST know the number of voters.
|
||||||
|
|
||||||
|
## 1. Initialization of voting
|
||||||
|
|
||||||
|
A user initializes the voting with the proposal payload which is
|
||||||
|
implemented using [protocol buffers v3](https://protobuf.dev/) as follows:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package vac.voting;
|
||||||
|
|
||||||
|
message Proposal {
|
||||||
|
string name = 10; // Proposal name
|
||||||
|
string payload = 11; // Proposal description
|
||||||
|
uint32 proposal_id = 12; // Unique identifier of the proposal
|
||||||
|
bytes proposal_owner = 13; // Public key of the creator
|
||||||
|
repeated Votes = 14; // Vote list in the proposal
|
||||||
|
uint32 expected_voters_count = 15; // Maximum number of distinct voters
|
||||||
|
uint32 round = 16; // Number of Votes
|
||||||
|
uint64 timestamp = 17; // Creation time of proposal
|
||||||
|
uint64 expiration_time = 18; // The time interval that the proposal is active.
|
||||||
|
bool liveness_criteria_yes = 19; // Shows how managing the silent peers vote
|
||||||
|
}
|
||||||
|
|
||||||
|
message Vote {
|
||||||
|
uint32 vote_id = 20; // Unique identifier of the vote
|
||||||
|
bytes vote_owner = 21; // Voter's public key
|
||||||
|
uint32 proposal_id = 22; // Linking votes and proposals
|
||||||
|
int64 timestamp = 23; // Time when the vote was cast
|
||||||
|
bool vote = 24; // Vote bool value (true/false)
|
||||||
|
bytes parent_hash = 25; // Hash of previous owner's Vote
|
||||||
|
bytes received_hash = 26; // Hash of previous received Vote
|
||||||
|
bytes vote_hash = 27; // Hash of all previously defined fields in Vote
|
||||||
|
bytes signature = 28; // Signature of vote_hash
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
To initiate a consensus for a proposal,
|
||||||
|
a user MUST complete all the fields in the proposal, including attaching its `vote`
|
||||||
|
and the `payload` that shows the purpose of the proposal.
|
||||||
|
Notably, `parent_hash` and `received_hash` are empty strings because there is no previous or received hash.
|
||||||
|
Then the initialization section ends when the user who creates the proposal sends it
|
||||||
|
to the random peer from the network or sends it to the proposal to the specific channel.
|
||||||
|
|
||||||
|
## 2. Exchanging votes across the peers
|
||||||
|
|
||||||
|
Once the peer receives the proposal message `P_1` from a 1-1 or a gossipsub channel does the following checks:
|
||||||
|
|
||||||
|
1. Check the signatures of the each votes in proposal, in particular for proposal `P_1`,
|
||||||
|
verify the signature of `V_1` where `V_1 = P_1.votes[0]` with `V_1.signature` and `V_1.vote_owner`
|
||||||
|
2. Do `parent_hash` check: If there are repeated votes from the same sender,
|
||||||
|
check that the hash of the former vote is equal to the `parent_hash` of the later vote.
|
||||||
|
3. Do `received_hash` check: If there are multiple votes in a proposal, check that the hash of a vote is equal to the `received_hash` of the next one.
|
||||||
|
4. After successful verification of the signature and hashes, the receiving peer proceeds to generate `P_2` containing a new vote `V_2` as following:
|
||||||
|
|
||||||
|
4.1. Add its public key as `P_2.vote_owner`.
|
||||||
|
|
||||||
|
4.2. Set `timestamp`.
|
||||||
|
|
||||||
|
4.3. Set boolean `vote`.
|
||||||
|
|
||||||
|
4.4. Define `V_2.parent_hash = 0` if there is no previous peer's vote, otherwise hash of previous owner's vote.
|
||||||
|
|
||||||
|
4.5. Set `V_2.received_hash = hash(P_1.votes[0])`.
|
||||||
|
|
||||||
|
4.6. Set `proposal_id` for the `vote`.
|
||||||
|
|
||||||
|
4.7. Calculate `vote_hash` by hash of all previously defined fields in Vote:
|
||||||
|
`V_2.vote_hash = hash(vote_id, owner, proposal_id, timestamp, vote, parent_hash, received_hash)`
|
||||||
|
|
||||||
|
4.8. Sign `vote_hash` with its private key corresponding the public key as `vote_owner` component then adds `V_2.vote_hash`.
|
||||||
|
|
||||||
|
5. Create `P_2` with by adding `V_2` as follows:
|
||||||
|
|
||||||
|
5.1. Assign `P_2.name`, `P_2.proposal_id`, and `P_2.proposal_owner` to be identical to those in `P_1`.
|
||||||
|
|
||||||
|
5.2. Add the `V_2` to the `P_2.Votes` list.
|
||||||
|
|
||||||
|
5.3. Increase the round by one, namely `P_2.round = P_1.round + 1`.
|
||||||
|
|
||||||
|
5.4. Verify that the proposal has not expired by checking that: `P_2.timestamp - current_time < P_1.expiration_time`.
|
||||||
|
If this does not hold, other peers ignore the message.
|
||||||
|
|
||||||
|
After the peer creates the proposal `P_2` with its vote `V_2`,
|
||||||
|
sends it to the random peer from the network or
|
||||||
|
sends it to the proposal to the specific channel.
|
||||||
|
|
||||||
|
## 3. Determining the result
|
||||||
|
|
||||||
|
Because consensus depends on meeting a quorum threshold,
|
||||||
|
each peer MUST verify the accumulated votes to determine whether the necessary conditions have been satisfied.
|
||||||
|
The voting result is set YES if the majority of the `2n/3` from the distinct peers vote YES.
|
||||||
|
|
||||||
|
To verify, the `findDistinctVoter` method processes the proposal by traversing its `Votes` list to determine the number of unique voters.
|
||||||
|
|
||||||
|
If this method returns true, the peer proceeds with strong validation,
|
||||||
|
which ensures that if any honest peer reaches a decision,
|
||||||
|
no other honest peer can arrive at a conflicting result.
|
||||||
|
|
||||||
|
1. Check each `signature` in the vote as shown in the [Section 2](#2-exchanging-votes-across-the-peers).
|
||||||
|
|
||||||
|
2. Check the `parent_hash` chain if there are multiple votes from the same owner namely `vote_i` and `vote_i+1` respectively,
|
||||||
|
the parent hash of `vote_i+1` should be the hash of `vote_i`
|
||||||
|
|
||||||
|
3. Check the `previous_hash` chain, each received hash of `vote_i+1` should be equal to the hash of `vote_i`.
|
||||||
|
|
||||||
|
4. Check the `timestamp` against the replay attack.
|
||||||
|
In particular, the `timestamp` cannot be the old in the determined threshold.
|
||||||
|
|
||||||
|
5. Check that the liveness criteria defined in the Liveness section are satisfied.
|
||||||
|
|
||||||
|
If a proposal is verified by all the checks,
|
||||||
|
the `countVote` method counts each YES vote from the list of Votes.
|
||||||
|
|
||||||
|
## 4. Properties
|
||||||
|
|
||||||
|
The consensus mechanism satisfies liveness and security properties as follows:
|
||||||
|
|
||||||
|
### Liveness
|
||||||
|
|
||||||
|
Liveness refers to the ability of the protocol to eventually reach a decision when sufficient honest participation is present.
|
||||||
|
In this protocol, if `n > 2` and more than `n/2` of the votes among at least `2n/3` distinct peers are YES,
|
||||||
|
then the consensus result is defined as YES; otherwise, when `n ≤ 2`, unanimous agreement (100% YES votes) is required.
|
||||||
|
|
||||||
|
The peer calculates the result locally as shown in the [Section 3](#3-determining-the-result).
|
||||||
|
From the [hashgraph property](https://hedera.com/learning/hedera-hashgraph/what-is-hashgraph-consensus),
|
||||||
|
if a node could calculate the result of a proposal,
|
||||||
|
it implies that no peer can calculate the opposite of the result.
|
||||||
|
Still, reliability issues can cause some situations where peers cannot receive enough messages,
|
||||||
|
so they cannot calculate the consensus result.
|
||||||
|
|
||||||
|
Rounds are incremented when a peer adds and sends the new proposal.
|
||||||
|
Calculating the required number of rounds, `2n/3` from the distinct peers' votes is achieved in two ways:
|
||||||
|
|
||||||
|
1. `2n/3` rounds in pure P2P networks
|
||||||
|
2. `2` rounds in gossipsub
|
||||||
|
|
||||||
|
Since the message complexity is `O(1)` in the gossipsub channel,
|
||||||
|
in case the network has reliability issues,
|
||||||
|
the second round is used for the peers cannot receive all the messages from the first round.
|
||||||
|
|
||||||
|
If an honest and online peer has received at least one vote but not enough to reach consensus,
|
||||||
|
it MAY continue to propagate its own vote — and any votes it has received — to support message dissemination.
|
||||||
|
This process can continue beyond the expected round count,
|
||||||
|
as long as it remains within the expiration time defined in the proposal.
|
||||||
|
The expiration time acts as a soft upper bound to ensure that consensus is either reached or aborted within a bounded timeframe.
|
||||||
|
|
||||||
|
#### Equality of votes
|
||||||
|
|
||||||
|
An equality of votes occurs when verifying at least `2n/3` distinct voters and
|
||||||
|
applying `liveness_criteria_yes` the number of YES and NO votes is equal.
|
||||||
|
|
||||||
|
Handling ties is an application-level decision. The application MUST define a deterministic tie policy:
|
||||||
|
|
||||||
|
RETRY: re-run the vote with a new proposal_id, optionally adjusting parameters.
|
||||||
|
|
||||||
|
REJECT: abort the proposal and return voting result as NO.
|
||||||
|
|
||||||
|
The chosen policy SHOULD be consistent for all peers via proposal's `payload` to ensure convergence on the same outcome.
|
||||||
|
|
||||||
|
### Silent Node Management
|
||||||
|
|
||||||
|
Silent nodes are the nodes that not participate the voting as YES or NO.
|
||||||
|
There are two possible counting votes for the silent peers.
|
||||||
|
|
||||||
|
1. **Silent peers means YES:**
|
||||||
|
Silent peers counted as YES vote, if the application prefer the strong rejection for NO votes.
|
||||||
|
2. **Silent peers means NO:**
|
||||||
|
Silent peers counted as NO vote, if the application prefer the strong acception for NO votes.
|
||||||
|
|
||||||
|
The proposal is set to default true, which means silent peers' votes are counted as YES namely `liveness_criteria_yes` is set true by default.
|
||||||
|
|
||||||
|
### Security
|
||||||
|
|
||||||
|
This RFC uses cryptographic primitives to prevent the
|
||||||
|
malicious behaviours as follows:
|
||||||
|
|
||||||
|
- Vote forgery attempt: creating unsigned invalid votes
|
||||||
|
- Inconsistent voting: a malicious peer submits conflicting votes (e.g., YES to some peers and NO to others)
|
||||||
|
in different stages of the protocol, violating vote consistency and attempting to undermine consensus.
|
||||||
|
- Integrity breaking attempt: tampering history by changing previous votes.
|
||||||
|
- Replay attack: storing the old votes to maliciously use in fresh voting.
|
||||||
|
|
||||||
|
## 5. Copyright
|
||||||
|
|
||||||
|
Copyright and related rights waived via [CC0](https://creativecommons.org/publicdomain/zero/1.0/)
|
||||||
|
|
||||||
|
## 6. References
|
||||||
|
|
||||||
|
- [Hedera Hashgraph](https://hedera.com/learning/hedera-hashgraph/what-is-hashgraph-consensus)
|
||||||
|
- [Gossip about gossip](https://docs.hedera.com/hedera/core-concepts/hashgraph-consensus-algorithms/gossip-about-gossip)
|
||||||
|
- [Simple implementation of hashgraph consensus](https://github.com/conanwu777/hashgraph)
|
||||||
Reference in New Issue
Block a user