Skip to content

Instantly share code, notes, and snippets.

@mimoo
Created December 5, 2019 23:10
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save mimoo/7139473ffe3edcaaf67a8c877f942ee8 to your computer and use it in GitHub Desktop.
LBFT spec
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8'>
<title>LibraBFT Specification</title>
<script src='https://www.w3.org/Tools/respec/respec-w3c-common' class='remove'></script>
<script class='remove'>
async function loadRust() {
//this is the function you call in 'preProcess', to load the highlighter
const worker = await new Promise(resolve => {
require(["core/worker"], ({ worker }) => resolve(worker));
});
const action = "highlight-load-lang";
const langURL =
"https://gistcdn.githack.com/mimoo/a9e28fc565dddb369477e8bd34e6a1ae/raw/d8a7057fe9c7140f7ef5972b333716d9933fa29b/hljs-rust.js";
const propName = "hljsDefineRust"; // This funtion is defined in the highlighter being loaded
const lang = "rust"; // this is the class you use to identify the language
worker.postMessage({ action, langURL, propName, lang });
return new Promise(resolve => {
worker.addEventListener("message", function listener({ data }) {
const { action: responseAction, lang: responseLang } = data;
if (responseAction === action && responseLang === lang) {
worker.removeEventListener("message", listener);
resolve();
}
});
});
}
async function loadProto() {
//this is the function you call in 'preProcess', to load the highlighter
const worker = await new Promise(resolve => {
require(["core/worker"], ({ worker }) => resolve(worker));
});
const action = "highlight-load-lang";
const langURL =
"https://gistcdn.githack.com/mimoo/a9e28fc565dddb369477e8bd34e6a1ae/raw/555beea7387129c657c49d8616b778c67629b586/hljs-proto.js";
const propName = "hljsDefineProto"; // This funtion is defined in the highlighter being loaded
const lang = "proto"; // this is the class you use to identify the language
worker.postMessage({ action, langURL, propName, lang });
return new Promise(resolve => {
worker.addEventListener("message", function listener({ data }) {
const { action: responseAction, lang: responseLang } = data;
if (responseAction === action && responseLang === lang) {
worker.removeEventListener("message", listener);
resolve();
}
});
});
}
var respecConfig = {
preProcess: [loadRust, loadProto],
specStatus: "base",
editors: [{
name: "Libra",
url: "https://www.libra.com",
}],
github: "https://github.com/libra/libra",
shortName: "lbft",
format: "markdown",
logos: [{
src: 'https://github.com/libra/libra/raw/master/.assets/libra.png',
href: "https://github.com/libra/libra/",
alt: "Libra",
width: 200,
height: 77,
id: 'libra-logo',
}],
};
</script>
</head>
<body>
<section id="abstract">
LibraBFT is a byzantine fault-tolerant (BFT) protocol.
It allows a set of honest participants (called validators) to agree on a succession of financial transactions, as
long
as if f dishonnest validators are allowed to participate, at least 2f+1 validators must be honest.
The [LibraBFT whitepaper](https://developers.libra.org/docs/state-machine-replication-paper) goes in more depth as
to
the precise definition of an honest node and as to why the protocol works.
This document specifies how to implement the protocol to participate in the consensus protocol as a validator.
</section>
## Introduction
TKTK
### Consensus Overview
TODO: diagrams
### The Modules of Consensus
The consensus specification is separated into several modules:
* block store
* safety
* pacemaker
* epoch manager
* event processor
* proposer election
![modules](modules.png)
In addition, the following modules are not specified in this document, but abstracted:
* cryptography
* block tree
* serialization
* network
* execution
* persistent storage
* state synchronizer
Finally, the BFT state machine replication protocol handles the initialization of all components and the main loop of the program.
others:
* we use protobuf/yamux/noise for p2p communication
* we use LCS for some of the serialization (TODO: all of the serialization)
* diagrams needed
### Data Structures of Consensus
In this section we specify the important structures of consensus.
Note that this section does overlook some other structures used in LibraBFT that are explained later in this document.
#### Proposals and Blocks
A **proposal** in LibraBFT is first and foremost a list of *transactions* (payload) from an *author* (the validator's account address) in a `Proposal`:
<pre class="rust">
enum BlockType {
Proposal {
payload: [u8],
author: [u8; 32],
},
NilBlock,
Genesis,
}
</pre>
There are a few edge-cases where `BlockType` is not a normal proposal, but representing a genesis block (a block created automatically at the start of a new epoch), or a nil block (which is what you can vote for if you do not see a proposal).
For the consensus algorithm to work, the proposed block must contain enough context about its place in the blockchain. For this reason it is included in a `BlockData` structure with the *epoch* and the *round* in which it is being proposed, as well as the *concrete time*. In addition, it must contain a *quorum certificate* of the previous block (or parent block).
<pre class="rust">
struct BlockData {
epoch: u64,
round: u64,
timestamp_usecs: u64,
quorum_cert: QuorumCert,
block_type: BlockType,
}
</pre>
This information is signed and encapsulated in a **Block** with the author's signature:
<pre class="rust">
struct Block {
block_data: BlockData,
signature: Option<[u8; 64]>,
}
</pre>
Such blocks are only seen during a proposal, and are thus encapsulated further in a **ProposalMsg** (which also carries some useful information for validators to catch up if they are lagging behind):
<pre class="rust">struct
pub struct ProposalMsg {
proposal: Block,
sync_info: SyncInfo,
}
</pre>
#### Votes and Ledger Infos
After a validator proposes a block, other validators must *vote* to attest that they have seen the proposed block, and that they have managed to process the transactions it contained. All of this information is contained in a `BlockInfo` structure and is further encapsulated in a `VoteData` structure:
<pre class="rust">
struct BlockInfo {
epoch: u64,
round: u64,
id: [u8; 32], // TODO: this can be set maliciously, we must check it everywhere!
executed_state_id: [u8; 32],
version: u64,
timestamp_usecs: u64,
next_validator_set: Option<ValidatorSet>,
}
struct VoteData {
proposed: BlockInfo,
parent: BlockInfo,
}
</pre>
where:
* **id** is the hash of the proposed block, specifically the hash of the `BlockData` structure that is signed in the proposal.
* **executed_state_id** is the root hash of the new state of the blockchain (which is a giant append-only merkle tree), after having executed all the transactions contained in the proposed block. To reach consensus enough validators must agree on the new state. Failing to do this would show differences in how different validators execute transactions.
While this is all consensus needs, the *epoch*, *round*, and *timestamp_usecs* fields are copy/pasted from the proposed block for performance reasons. (TODO: more about this (and in both proposed and parent))
In addition the structure contains two more of these useful fields to make other parts of the system work more efficiently:
* **version** is a counter starting at 0 and incrementing for each transaction. Here it indicates the number of the last transaction that was processed. (If 3 transactions were processed after genesis, this would be 2.)
* **next_validator_set** indicates (if set) that the proposed block is triggering a change of epoch, which in turn triggers a new validator set.
Next, the vote data contains two `BlockInfo` describing two different blocks:
* **proposed** describes the block observed during the current round, which the validator attest to have seen.
* **parent** describes the parent block. In other words, the block certified by the QC contained in the proposed block.
Only the *proposed* field is required in reality, but the *parent* field is included for performance reasons as well. (TODO: more about this, why an entire BlockInfo as parent?)
This information is finally included into a Vote, which contains a signature and more data required by consensus to make progress.
<pre class="rust">
struct Vote {
vote_data: VoteData,
author: [u8; 32],
ledger_info: LedgerInfo,
signature: [u8; 64],
timeout_signature: Option<[u8; 64]>,
}
</pre>
where:
* **vote_data** is the structure (we've talked about previously) summarizing what we are voting on.
* **ledger_info**: the structure that will be useful later for clients to query the blockchain. It contains a hash of *vote_data*.
* **author** the account address of the voter.
* **signature**: a signature over the *ledger_info*.
* **timeout_signature**: this is set to `None` unless the vote serves to indicate that the validator has not observed a proposal during this round (and has timed out). Timeout signatures can be aggregated to form *Timeout Certificates*. (TODO: more about where TCs are used)
Now is time to talk about the ledger info, and why we are not signing the vote data directly.
A **vote** serves a dual purpose:
1. It allows the consensus to make progress.
2. It creates a structure called a `LedgerInfo` that will later be useful for users of the network to query and verify the blockchain.
Let's see what a *ledger info* is:
<pre class="rust">
pub struct LedgerInfo {
commit_info: BlockInfo,
consensus_data_hash: [u8; 32], // TODO: this represents vote_data.hash(), it must be checked!
}
</pre>
It is just two fields:
* `consensus_data_hash`: the hash of vote data, which a validator wants to sign in order to sign its vote. It is repeated here because LedgerInfo will later be distributed without the actual vote data which will be opaque to the users as it contains unecessary information.
* `commit_info`: information about the block that will be committed if the current vote end up forming a quorum certificate.
When talking about *ledger infos* in the rest of this specification, we most often mean a quorum certificate that contains a ledger info with a `commit_info` that is not empty. (If a vote does not trigger a commit, a *commit info* with default values is used instead.)
This *ledger info* is the only piece of information that will be provided to the end users, and contains enough information to authenticate the whole **committed state** (*commit info*) at this point in time: a timestamp, a version, the root hash of the ledger, etc. as well as useful information on validator set change for a client to maintain synchrony with the blockchain.
Finally, the vote is seen on the wire accompanied with a `SyncInfo`, like proposals, in order to help other validators that are lagging behind:
<pre class="rust">
pub struct VoteMsg {
vote: Vote,
sync_info: SyncInfo,
}
</pre>
#### Quorum Certificates
A **quorum certificate** is an aggregation of votes that gets carried by the next block (as you've seen at the start of this section), which means an aggregation of signatures on the same *vote data*.
Since validators want to avoid signing twice, only signatures for *ledger infos* are provided in a vote, and thus a quorum certificate must also carry the *ledger info*:
<pre class="rust">
struct QuorumCert {
vote_data: VoteData,
signed_ledger_info: LedgerInfoWithSignatures,
}
struct LedgerInfoWithSignatures {
ledger_info: LedgerInfo,
signatures: HashMap<AccountAddress, [u8; 64]>,
}
</pre>
### Epoch Changes and Reconfigurations
![reconfiguration](reconfig.png)
## Wire Format
TODO: this should be moved to a different specification
LibraBFT receives messages serialized with Libra Canonical Serialization (LCS) which is a binary encoding defined in a [separate document](https://developers.libra.org/docs/rustdocs/doc/libra_canonical_serialization/).
In order to describe how different data structures are serialized and deserialized in this specification, this section defines an informal Domain-Specific Language.
### Primitive Types
LCS can serialize the following primitive types:
* `[u8]` represents a variable-length array. It is serialized by prefixing the actual data with a 4-byte little-endian representation of the data length.
* `string` represents a variable-length string. It is represented the same way as a `[u8]` with the exception that it must be UTF-8 valid when deserialized.
* `[u8; x]` represents a fixed-length array or x bytes. For example `[u8; 32]` is serialized as 32-bytes.
* `u8` represents a 8-bit value. It is serialized as a `[u8; 1]`.
* `u32` represents a 32-bit value. It is serialized as its `[u8; 4]` representation in little-endian.
* `u64` represents a 64-bit value. It is serialized as its `[u8; 8]` representation in little-endian.
* `bool` represents a boolean. It is serialized as `0x01` if `true`, and `0x00` if false.
### Structs
**Structs** are defined with the keyword `struct`:
<pre class="rust">
struct Thing {
a_number: u64,
another_struct: Thing2,
}
struct Thing2 {
a_bytearray: [u8],
}
</pre>
A struct can contain multiple fields of the form `name: type,` which are serialized one by one and concatenated together.
A struct can also contain another struct.
### Enum
An **enum** is essentially a value that can contain different types:
<pre class="rust">
enum Thing {
ContainsStruct(Thing),
ContainsNothing,
ContainsBool(bool),
}
</pre>
To serialize an enum, the index (starting from zero) of the enum in its ordered list is serialized as a `u32` primitive value, followed by the serialization of the type it contains. (TODO: add examples?)
### Options
An **option** is a type that can be set to `None` or to some value.
For example here `thing` is a field that is either `None` or a `u64` value:
<pre class="rust">
thing: Option<u64>
</pre>
An option is serialized as a serialized boolean, indicating if the value is set (`true`) or not (`false`), followed by the serialization of the actual value (in the example above a `u64`).
### Maps
A **map** (also called an hashmap, a dictionnary, or an associated array) is written as:
<pre class="rust">
thing: Map<X, Y>
</pre>
where elements of type `X` are mapped to elements of type `Y`. For example the following field is a map from `u64` to fixed-length bytearrays of 32 bytes:
<pre class="rust">
thing: Map<u64, [u8; 32]>
</pre>
TODO: Maps are serialized ...
### Vec
A **vector** (or array):
<pre class="rust">
thing: Vec<Thing2>
</pre>
TODO: how is it serialized?
## Terms and Pseudo-Code
* "ensure" means that you should return to the caller if it's not true. Functions that need to return an error message usually return a true or false in this specification, it is up to the implementor to log errors.
* we use rust inspired pseudo-code.
- `[0u8; 32]` means an array of 32 zero bytes.
* LCS: Libra Canonical Serialization.
* QC: quorum certificate
* LI: ledger info
* TC: timeout certificate
## Constants
(TODO: move these constants to their relevant sections?)
* MAX_TRANSACTION_PER_BLOCK: 1,000,000,000,000 :D
* etc.
## Required Functions
### Serialization
* **`lcs_deserialize(input_bytes: [u8]) -> SomeType`**. This function can be used to deserialize a bytearray into a type. Since LCS is a non-describing encoding, we typically call this function as `let some_var: SomeType = lcs_deserialize(input_bytes)` in this specification in order to indicate the resulting type of the deserialization.
* **`lcs_serialize(SomeType) -> [u8]`**. This function serializes some type into a bytearray.
### Cryptography
Two cryptographic algorithms are used by Consensus: the SHA-3 hash function (specifically, the SHA-3-256 instantiation that outputs 256-bit digests) and the EdDSA signature scheme (specifically the Ed25519 instantiation that uses the Edwards25519 curve).
The following functions must be implemented and available to other parts of consensus:
* `SHA-3-256(input_bytes: [u8]) -> [u8; 32]`. This function hashes the `input_bytes` argument with the SHA-3-256 hash function and returns the 256-bit result.
* `ed25519_verify(public_key: [u8;32], signature: [u8; 64], hashed_message: [u8; 32]) -> bool`. This function verifies an Ed25519 `signature` using a `public_key` and a `hashed_message`. It returns `true` if the signature is valid, `false` otherwise.
* `ed25519_sign(private_key: [u8; 32], hashed_message: [u8; 32]) -> [u8; 64]`. This function signs a `hashed_message` with a `private_key`. The signature obtained is a 64-byte bytearray.
### Time Service
functions:
* **`get_current_timestamp()`**. Returns a [unix timestamp](https://en.wikipedia.org/wiki/Unix_time).
## Abstracted Modules
This document specifies the workings of LibraBFT's consensus protocol, and abstracts other parts of the system. For example, it does not specify how transactions are executed and verified, or how connections to other peers are made and maintained.
Instead, we specified all of these parts as abstractions in this section.
### Network
**TODO: we might have to actually specify this here or in another document**.
The network module's job is to queue received consensus messages and send specific messages to its peers.
The `next_msg()` function pops consensus messages from its internal FIFO queue and returns a `ConsensusMsg`.
This type is a protobuf structure as defined below:
<pre class="rust">
message ConsensusMsg {
oneof message {
Proposal proposal = 1;
Vote vote = 2;
RequestBlock request_block = 3;
RespondBlock respond_block = 4;
SyncInfo sync_info = 6;
}
}
</pre>
It maintains the following values:
* `epoch`.
* `validator_set`.
The following functions are available to send or broadcast messages to peers:
* **`initialize(epoch, validator_set)`**.
- set `state.epoch = epoch`.
- set `state.validator_set = validator_set`.
* **`broadcast(msg)`**. Serializes `msg` with LCS and encapsulates it into a `ConsensusMsg` protobuf message before broadcasting the message to all validators (including itself).
* **`send_to(msg, recipients)`**. This function is in charge of sending a `VoteMsg` as an LCS-serialized and encapsulated `ConsensusMsg` protobuf message to the list of validators referenced by the `recipients` list.
* **`clear_prev_epoch_msgs()`**. This function clears out all consensus messages in its queue that have an epoch smaller than `state.epoch`. (TODO: more details)
* **`send_to_ourselves(msg)`**. This function sends a message to ourselves only.
### Transaction Execution
A proposed block's payload contains a series of transactions, which are opaque to the LibraBFT protocol.
The execution module is in charge of correctly executing the payload contained in a proposed block, and to keep track of the result of these executions.
The execution module exposes the following functions:
* **`compute(block)`**. Execute the transactions in a block and return the execution result which consists of:
- a *hash of the execution state* after executing all transactions.
- a *version*, which is the total number of transactions executed (minus one) since the genesis of epoch 0 (or the index of the last transaction executed).
- an *optional set of validators*, in case the block triggers a new epoch.
* **`commit(blocks, finality_proof)`**. This function executes and commit the blocks to storage. (TODO: do we need a finality_proof here?)
* **`sync_to(target: LedgerInfoWithSignatures)`**. This calls `state_sync.sync_to(target)`.
* **`get_epoch_proof(start_epoch)`**. This calls `state_sync.get_epoch_proof(start_epoch)`.
Note that a block's payload is a vector of SignedTransaction, but this data is opaque to consensus and only used by execution.
How transactions are executed is out-of-scope for this document. (TODO: do we specify this anywhere else?)
### Storage
The role of storage is to persist important data in order to maintain safety and liveness. Storage must be aware of data that is committed, and data that is in a pending and speculative state (for example blocks that haven't been committed).
Safety: last_vote, epoch, round, (TODO: last proposal?, preferred block?)
Liveness: storage helps in scenarios where a large number of validators have to restart (pending blocks and QCs are stored persistently).
For this reason, persistent storage needs not only to maintain values like the highest timeout certificate, but also all speculative blocks up to the preferred block and relevant quorum certificates.
The persistent storage is only really used after a restart.
The following constants are known to the module:
* [Genesis Signed Ledger Info](https://github.com/libra/libra/blob/master/executor/src/block_processor.rs#L113). TODO: not sure how to include this in the spec.
The module exposes the following writing interface:
* `initialize()`. TODO: we can probably get rid of this.
* `save_highest_timeout_cert(timeout_cert: TimeoutCertificate)`. Persists the highest timeout certificate.
* `save_tree(blocks: Vec<Block>, quorum_certs: Vec<QuorumCert>)`. Persists the given blocks and quorum certificates.
* `prune_tree(block_hashes: Vec<[u8; 32]>)`. Deletes blocks associated to the given block hashes. (TODO: do we specify this?)
* **`start() -> (epoch, last_vote, root, blocks, quorum_certs, highest_timeout_certificate, validator_set)`**. Construct necessary data to start consensus.
- obtain `last_vote` from storage.
- obtain `highest_timeout_certificate` from storage.
- obtain `blocks` from storage.
- obtain `quorum_certs` from storage.
- obtain `quorum_certs` from storage.
- obtain (signed) `ledger_info`, (signed) `ledger_info_with_validators` from storage
- compute initial data:
<pre class="rust">
let initial_data = RecoveryData {
epoch: ?,
last_vote: last_vote,
root: ?,
blocks: blocks,
quorum_certs: quorum_certs,
highest_timeout_certificate: highest_timeout_certificate,
validators: ledger_info_with_validators.ledger_info.commit_info.next_validator_set
}
</pre>
- prune all blocks and QCs from persistent storage if they are not in the same branch as the root.
- if last_vote is None, delete last vote from storage
- if highest_timeout_certificate is none, delete highest_timeout_certificate from storage
- return initial_data
* `new(last_vote: Option<Vote>, blocks: Vec<Block, quorum_certs: Vec<QuorumCert>, storage_ledger: &LedgerInfo, highest_timeout_certificate: Option<TimeoutCertificate>, validator_set) -> RecoveryData`
- if `storage_ledger.commit_info.next_validator_set` is not set, set `root_id = storage_ledger.commit_info.id`
- if it is set, create:
+ genesis block for new epoch, and push it to `blocks`
+ QC for that genesis block, and push it to `quorum_certs`.
+ set `root_id = genesis_id`.
- sort blocks by `(epoch, round)`.
- set `root = (root_block, root_quorum_cert, root_ledger_info)` where `root_block` is the block associated to `root_id`, `root_quorum_cert` is the QC certifying this block, and `root_ledger_info` is the QC that triggers a commit to this block.
- if it's a new epoch, set last vote and highest timeout cert to None
+ if `last_vote` is set and `last_vote.epoch != root_block.epoch` then set `last_vote = None`
+ if `highest_timeout_cert` is set and `highest_timeout_cert.epoch != root_block.epoch` set `highest_timeout_cert = None`.
- return
<pre class="rust">
RecoveryData {
epoch: root_block.epoch,
last_vote: last_vote,
root: root,
blocks: blocks,
quorum_certs: quorum_certs,
highest_timeout_cert: highest_timeout_cert,
validators: validator_set,
}
</pre>
### State Synchronizer
* sync_to
* get_epoch_proof
### Transaction Manager
Mempool is a store of pending transactions.
functions:
* `pull_txns()`. this is used to pull a set of transactions when generating a proposal
* `commit_txns()`. this is used to notify mempool that some transactions can be removed, when event processor processes a commit.
## Specified Modules
This section goes over the different modules that Consensus use.
### Block Store
The block store is responsible for maintaining pending blocks (and their associated quorum certificates, pending votes, and the highest time out certificates.
It is also in charge of talking to execution/state computer, fetching dependencies that it does not have, and talking to persistent storage to store all data in case of a crash.
It keeps track of the following values:
* `highest_quorum_cert`. The quorum certificate with the highest certified round it has seen.
* `highest_timeout_cert`. The timeout certificate with the highest timed out round it has seen.
* `highest_commit_cert`. The quorum certificate that carries the signed ledger info for the highest round it has seen.
* `root`. The last committed block.
* `votes`. TKTK
The block store exposes the following functions:
* **`Initialize(epoch, last_vote: Option<Vote>, root_block: Block, root_qc: QuorumCert, root_li: QuorumCert, blocks, quorum_certs, block_to_prune, highest_timeout_certificate, max_pruned_blocks_in_mem)`**.
- stores all of that in a cache?
* **`commit(finality_proof: LedgerInfoWithSignatures)`**. If the block committed by this finality proof is not already committed, execute and commit all blocks from the root (the last committed block) to the block committed by this finality proof.
* **`execute_and_insert_block(block: Block)`**.
- if `block` is already in store, return.
- call `execute_block(block)` and ensure that it does not error.
- save the block to storage.
* **`execute_block(block: Block)`**.
- call `verify_and_get_parent(block)` to obtain the parent block.
- if the parent block's execution yielded an epoch change, ensure that the block's payload is empty and return the result of the parent's execution.
- otherwise execute the block and return the result.
* **`verify_and_get_parent(block: Block)`**.
- ensure the stored root's round is strictly smaller than the block's round (`block.block_data.round`).
- obtain the parent block associated to `block.block_data.quorum_cert.vote_data.proposed.id` from storage or return an error.
- ensure the parent's round is strictly smaller than the block's round.
- if the block is a nil block, or the parent block contains a reconfiguration, ensure that both the block and its parent have the same timestamps.
- otherwise ensure that the block's timestamp is strictly greater than its parent's timestamp.
- return the parent
* **`rebuild(root_block, root_qc, root_li, blocks, quorum_certs)`**. This function commits the root block given.
* **`insert_vote(vote: Vote)`**. This function stores a vote and checks if it creates a QC or TC.
- if we already have a QC for the block id (`vote.vote_data.proposed.id`), return.
- store the pending vote.
- check if the vote creates a quorum (TODO: more on that) on the hash of its ledger_info, if so return it.
<pre class="rust">
QuorumCert {
vote_data: vote.vote_data,
signed_ledger_info: LedgerInfoWithSignatures {
ledger_info: vote.ledger_info,
signatures: signatures,
}
}
</pre>
where `signatures` are at least 2f+1 signatures on the same `LedgerInfo` hash taken from each `vote.signature`.
- if the vote has a timeout signature, check if it creates a TC. If so, return it.
<pre class="rust">
TimeoutCertificate {
timeout: Timeout {
epoch: vote.vote_data.proposed.epoch,
round: vote.vote_data.proposed.round,
},
signatures: signatures,
}
</pre>
where `signatures` is at least 2f+1 signatures on the same `Timeout` taken from `vote.timeout_signature`.
* **`need_sync_for_quorum_cert(committed_block_hash, qc)`**
- returns `true` if we don't have the committed block corresponding to `committed_block_hash`
and the QC `qc` can commit blocks that we haven't already committed:
<pre class="rust">
if qc.vote_data.proposed.round < 2 {
return false;
}
let potentially_committed = qc.vote_data.proposed.round - 2;
if block_exists(committed_block_hash) || state.root.round >= potentially_committed {
return false;
}
return true;
</pre>
* **`need_fetch_for_quorum_cert(qc)`**
- if the QC certifies a block which round is less than the round of our last committed block, we don't need to do anything: return `NeedFetchResult::QCRoundBeforeRoot`.
<pre class="rust">
if qc.vote_data.proposed.round < state.root.round {
return NeedFetchResult::QCRoundBeforeRoot;
}
</pre>
- if we already have the QC in store, we don't need to do anything: return `NeedFetchResult::QCAlreadyExist`.
<pre class="rust">
if get_quorum_cert_for_block(qc.vote_data.proposed.id).is_some()
{
return NeedFetchResult::QCAlreadyExist;
}
</pre>
- if we already have the block certified by that QC, return `NeedFetchResult::QCBlockExist`:
<pre class="rust">
if block_exists(qc.vote_data.proposed.id) {
return NeedFetchResult::QCBlockExist;
}
</pre>
- return `NeedFetchResult::NeedFetch`.
* **`sync_to(sync_info: SyncInfo, deadline: Instant, preferred_peer: [u8; 32])`**.
- `process_highest_commit_cert(highest_quorum_cert, deadline, preferred_peer)`
- call `need_fetch_for_quorum_cert(highest_quorum_cert)`
- if the result is `NeedFetchResult::NeedFetch`, call `fetch_quorum_cert(highest_quorum_cert, deadline, preferred_peer)`
- if the result is `NeedFetchResult::QCBlockExist`, call `insert_single_quorum_cert(highest_quorum_cert)`
* `fetch_quorum_cert(qc, deadline, preferred_peer)`
- retrieve all the blocks between the certified block by the given `qc` and an existing one.
<pre class="rust">
let mut pending = vec![];
loop {
// if the next certified block exist, break
if block_exists(qc.vote_data.proposed.id) {
break;
}
// retrieve the certified block
let mut blocks = retrieve_block_for_qc(qc, 1)
let block = blocks.pop();
pending.push(block);
// continue with the next QC
qc = block.block_data.quorum_cert;
}
</pre>
- For every blocks retrieved, insert them in the store
<pre class="rust">
while let Some(block) = pending.pop() {
insert_single_quorum_cert(block.block_data.quorum_cert);
execute_and_insert_block(block);
}
</pre>
- Insert the last QC retrieved: `insert_single_quorum_cert(qc)`
* retrieve_block_for_qc(qc, num_blocks)
- attempts to retrieve `num_blocks` previous blocks from a QC. This makes calls to `event_processor.request_block(qc.vote_data.proposed.id, num_blocks)`.
* **`process_highest_commit_cert(highest_commit_cert: QuorumCert, deadline, preferred_peer)`**.
- retrieve the block that gets committed by this QC:
<pre class="rust">
committed_block_id = highest_commit_cert.signed_ledger_info.ledger_info.commit_info.id
</pre>
- if it does not trigger a commit, return.
<pre class="rust">
if committed_block_id == [0u8; 32] {
return;
}
</pre>
- if we already have the blocks committed by this QC in store, return.
<pre class="rust">
if !need_sync_for_quorum_cert(committed_block_id, highest_commit_cert) {
return
}
</pre>
- retrieve the 3-chain blocks:
<pre class="rust">
let blocks = retrieve_block_for_qc(highest_commit_cert, 3);
let (grand_parent, parent, certified) = blocks;
</pre>
- call `storage.save_tree()` to store the 3-chain:
<pre class="rust">
storage.save_tree([
certified,
parent,
grand_parent,
], [
highest_commit_cert,
certified.block_data.quorum_cert,
parent.block_data.quorum_cert,
]);
</pre>
- TKTK
<pre class="rust">
state_computer.sync_to(highest_commit_cert.signed_ledger_info);
</pre>
- create the new root
<pre class="rust">
let root = root = (
grand_parent,
parent.block_data.quorum_cert,
highest_commit_cert,
);
</pre>
- rebuild?
<pre class="rust">
rebuild(root, [
grand_parent,
parent,
certified
], [
highest_commit_cert,
certified.block_data.quorum_cert,
parent.block_data.quorum_cert,
]);
</pre>
- if the `highest_commit_cert` is ending an epoch, send a notification to ourself:
<pre class="rust">
let serialized_msg = lcs_serialize(ConsensusMsg{
message: EpochChange {
ValidatorChangeEventWithProof {
ledger_info_with_sigs: highest_commit_cert.signed_ledger_info,
}
}
});
network.send_to_ourselves(serialized_msg);
</pre>
### PaceMaker Module
Pacemaker is a module responsible for generating the new round and local timeout events.
It is completely unaware of the epoch, and resets a timer at the beginning of each round.
Constants:
* `BASE_MS = 1000`. Initial time interval duration after a successful quorum commit
* `EXPONENT_BASE= 1.5`. By how much we increase interval every time
* `MAX_EXPONENT: 6`. Maximum time interval won't exceed base * mul^max_pow. Theoretically, setting it means that we rely on synchrony assumptions when the known max messaging delay is max_interval. Alternatively, we can consider using max_interval to meet partial synchrony assumptions where while delta is unknown, it is <= max_interval.
A pacemaker has the following state:
* `time_interval`. Determines the time interval for a round given the number of non-committed rounds since last commit.
* `highest_committed_round`. Highest known committed round as reported by the caller. The caller might choose not to inform the Pacemaker about certain committed rounds (e.g., NIL blocks): in this case the committed round in Pacemaker might lag behind the committed round of a block tree.
* `current_round`. Current round is max{highest_qc, highest_tc} + 1.
* `current_round_deadline`. The deadline for the next local timeout event. It is reset every time a new round start, or a previous deadline expires.
* `time_service`. Service for timer.
* `timeout_sender`. To send local timeout events to the subscriber (e.g., SMR).
Interface:
* **`initialization()`**.
- set `highest_committed_round = 0`.
- set `current_round = 0`.
- set `current_round_deadline = now()`.
* **`process_local_timeout(round) -> bool`**.
* if round != pacemaker.current_round return false
* otherwise call setup_timeout() and return true
* **`process_certificates(hqc_round=0, htc_round=0, highest_committed_round=None) -> NewRoundEvent`**.
- if `highest_committed_round` is not None, and `highest_committed_round > pacemaker.highest_committed_round`, set `pacemaker.highest_committed_round = highest_committed_round`.
- `new_round = max(hqc_round, htc_round) + 1`
- if `new_round <= pacemaker.current_round` return `None`.
- set `pacemaker.current_round = new_round`
- `timeout = pacemaker.setup_timeout()`
- if `qc_round >= tc_round`, return NewRoundEvent {
round: self.current_round,
reason: NewRoundReason::QCReady,
timeout,
}
- otherwise return NewRoundEvent {
round: self.current_round,
reason: NewRoundReason::Timeout,
timeout,
}
None
Internal Functions:
* **`setup_timeout()`**.
- if `highest_committed_round == 0`
- `timeout = pacemaker.time_interval.get_round_duration(pacemaker.current_round - 1)`
- if `pacemaker.current_round < pacemaker.highest_committed_round + 3`
- `timeout = pacemaker.time_interval.get_round_duration(0)`
- otherwise
- `timeout = pacemaker.time_interval.get_round_duration(pacemaker.current_round - pacemaker.highest_committed_round - 3)`
- set `pacemaker.current_round_deadline = now() + timeout`
- start a timer of `timeout`ms that sends `pacemaker.current_round` to `timeout_sender`.
- return `timeout`
* **`time_interval.get_round_duration(round_index_after_committed_qc)`**.
- pow = min(round_index_after_committed_qc, MAX_EXPONENT)
- base_multiplier = EXPONENT_BASE<sup>pow</sup>
- return ceil(BASE_MS x base_multiplier)
### Proposer Election
The proposer election module is in charge of deciding who are the leaders for a specific round.
The current approach is called **Multi Proposer Election** because not one,
but two validators are elected as proposers/leaders for each round.
Only the first one is considered the round leader, unless it fails to propose.
In this case, after the round times out, the second leader's proposal can be considered valid.
The proposer election module maintains the current values:
* **`epoch`**. The current epoch.
* **`proposers`**. A list of account addresses representing the set of validators at the current epoch.
* **`backup_proposal_round`**. The highest received back-up proposal's round.
* **`backup_proposal`**. The highest received back-up proposal.
The module exposes the following functions:
* **`intialize(epoch, proposers)`**.
- set `state.epoch = epoch`.
- set `state.proposers = proposers`.
- set `state.back_proposal_round = 0`.
- set `state.backup_proposal = None`.
* **`get_candidates(round: u64) -> Vec<[u8; 32]>`**.
- set `idx1 = SHA-3-256(epoch_bytes | round_bytes)` where `epoch_bytes` and `round_bytes` are the little-endian representation of `state.epoch` and `round`.
- set `first_proposer = idx1_bytes % candidates.len()` where `idx1_bytes` is a u64 value converted from the little-endian representation `idx1[0..8]`.
- set `idx2 = SHA-3-256(idx1)`
- set `second_proposer = idx2_bytes % candidates.len()` where `idx2_bytes` is a u64 value converted from the little-endian representation `idx2[0..8]`.
- return the tuple `(first_proposer, second_proposer)`.
* **`is_valid_proposer()`**. this function seems redundant.
* **`get_valid_proposers()`**. this function seems redundant.
* **`process_proposal(proposal: Block) -> Option<Block>`**. This function returns the proposal if the author is the current leader, otherwise it potentially updates its state with a backup proposal.
- obtain the `(first_proposer, second_proposer)` tuple by calling `get_candidates(proposal.round)`.
- if `proposal.author` is the `first_proposer`, return `Some(proposal)`.
- if `proposal.author` is the `second_proposer` and `proposal.round > state.backup_proposal_round` then:
+ set `state.backup_proposal = Some(proposal)`.
+ set `state.backup_proposal_round = proposal.round`.
- return `None`.
* **`take_backup_proposal(round) -> Option<Block>`**. This funtion returns the backup proposal observed for some `round`.
- if `round == state.backup_proposal_round` and `state.backup_proposal` is not `None`, return `state.backup_proposal`.
- return `None`.
### Safety Module
The Safety module enforces:
* voting rules. Rules that dictates how a validator can vote.
* commit rules. Rules that dictates when a validator can commit blocks to storage.
The two **voting rules** of LBFT are:
1. A validator can only vote for round that is higher that the previous round it voted for.
2. A validator can only vote for a proposal which parent's is greater or equal to its *preferred round*.
With a preferred round being the round of the parent block certified by an observed QC.
The sole *commit rule* decides that for a certified block `block`, its grand parent block is committed if we have:
1. `block.round = block.parent.round + 1 = block.grand_parent.round + 2`.
The Safety module maintains the following values:
* **`last_vote_round`**. The last round the validator voted on.
* **`preferred_block_round`**. The current preferred round.
The Safety module exposes the following functions:
* **`initialize()`**.
- set `state.last_vote_round = last_vote_round`.
- set `state.preferred_block_round = preferred_block_round`.
* **`update(qc)`**. This function is in charge of updating the preferred round of the validator.
* If the QC certifies a block at round `r`, such that `r > preferred_block_round`, set `preferred_block_round = r`.
* **`construct_and_sign_vote(block: Block, executed_state_id: [u8; 32], version: u64, next_validator_set: Option<ValidatorSet>)`**. This function is in charge of enforcing the voting rules before creating a signed vote.
* *enforce voting rule 1*: ensure that the block's round is greater than the last round we voted for: `block.block_data.round > last_vote_round`.
* *enforce voting rule 2*: ensure that the round of the block's parent is greater or equal to the preferred round: `block.block_data.quorum_cert.vote_data.proposed.round >= preferred_block_round`.
* update the last round we voted for: `last_vote_round = block.block_data.round`.
* hash the block data `block_hash = SHA-3-256(PREFIX_BLOCK_DATA || "@@$$LIBRA$$@@" || lcs_serialize(block.block_data))`.
* create the vote data struct:
<pre class="rust">
let vote_data = VoteData {
proposed: BlockInfo {
block.block_data.epoch,
block.block_data.round,
block_hash,
executed_state_id,
version,
block.block_data.timestamp_usecs,
next_validator_set,
},
parent: block.block_data.quorum_cert.vote_data.proposed,
};
</pre>
* hash the vote data struct: `vote_data_hash = SHA-3-256(PREFIX_VOTE_DATA || "@@$$LIBRA$$@@" || lcs_serialize(vote_data))`
* *commit rule*: if `grand_parent.round + 1 == parent.round && parent.round + 1 == block.round` for `grand_parent = block.block_data_.quorum_cert.vote_data.parent` and `parent = block.block_data_.quorum_cert.vote_data.proposed` then set
<pre class="rust">
let ledger_info = LedgerInfo {
commit_info: parent,
consensus_data_hash: vote_data_hash,
}
</pre>
* otherwise, set
<pre class="rust">
let empty_commit = BlockInfo {
epoch: 0,
round: 0,
id: [0u8; 32],
executed_state_id: [0u8; 32],
version: 0,
timestamp_usecs: 0,
next_validator_set: None,
}
</pre>
and construct the ledger info as
<pre class="rust">
let ledger_info = LedgerInfo {
commit_info: empty_commit,
consensus_data_hash: vote_data_hash,
}
</pre>
* hash the ledger info `ledger_info_hash = SHA-3-256(PREFIX_LEDGER || "@@$$LIBRA$$@@" || lcs_serialize(ledger_info))`
* sign the `ledger_info_hash`: `signature = ed25519_sign(self.private_key, ledger_info_hash)`
* construct a vote and return it
<pre class="rust">
Vote {
vote_data: vote_data,
author: self_validator_address,
ledger_info: ledger_info,
signature: signature,
timeout_signature: None,
}
</pre>
<!-- (global used: self_validator_address, self.private_key) -->
### Epoch Manager
The epoch manager module is the component that drives epoch changes.
For this reason, it is in charge of initializing, and re-initializing other modules when changes of epoch occur.
It manages one value:
* `epoch`. A value indicating in which epoch the validator is.
It exposes the following functions:
* `initialize(initial_data)`.
- set `state.epoch = initial_data.epoch`
- Initialize other modules by calling `start_epoch(initial_data)`.
* `start_epoch(initial_data)`. This function is in charge of initializing other modules to start a new epoch.
- update the network with the new set of validators by calling `network.update_eligible_nodes(initial_data.validator_set)`
- (re-)initialize the safety rules module by calling `safety_rules.initialize(last_vote.round, preferred_round)` (TODO: where do we get preferred round from, do we really want to reinitialze safety rules here?).
- (re-)initialize the block store by calling `block_store.initialize(initial_data)`.
- (re-) initialize the network by calling `network.initialize(initial_data.epoch, validator_set)`.
- (re-)initialize the proposal generator module by calling `proposal_generator.initialize()`.
- (re-)initialize the pacemaker module by calling `pacemaker.initialize()`.
- (re-)initialize the proposer election module by calling `proposer_election.initialize(validator_set)`.
- (re-)initialize the event processor by calling `event_processor.initialize(initial_data.epoch, last_vote, validator_set)`.
* `start_new_epoch(signed_ledger_info)`. This function is in charge of re-initalizing other modules for a new epoch.
- Synchronize to the new epoch advertised by the ledger info by calling `state_sync(signed_ledger_info)`.
- Obtain information about the new epoch from storage by calling `let initial_data = storage.start()`.
- update our current epoch by setting `state.epoch = genesis_block.block_data.epoch`.
- start the new epoch by calling `start_epoch(initial_data)`.
* `process_different_epoch(different_epoch: u64, peer: [u8; 32])`.
- ensure `different_epoch` is different from `state.epoch`.
- if `different_epoch < self.epoch`, help the peer by calling `process_epoch_retrieval(different_epoch, peer)` and return.
- create a request to fetch the epoch from the peer:
<pre class="rust">
let request = EpochRetrievalRequest {
start_epoch: self.epoch,
target_epoch: different_epoch,
};
</pre>
- serialize the request with LCS, encapsulate it in a `ConsensusMsg` protobuf message `msg`, and send the request to the peer by calling `network.send_to(peer, msg)`.
* `process_epoch_retrieval(start_epoch: u64, peer: [u8; 32])`. This answers a peer's request to get help getting from their `start_epoch` epoch to the epoch we're at.
- obtain the proof from `start_epoch` to our epoch by calling `state_computer.get_epoch_proof(start_epoch)`. If the call returns an error, return.
- serialize the proof with LCS, encapsulate it in a `ConsensusMsg` protobuf message `msg` and send it to the peer by calling `network_sender.send_to(peer, msg)`.
(TODO: should we move the following functions elsewhere? not sure)
* `make_genesis_block()`.
- call `make_genesis_block_from_ledger_info` with the genesis ledger info specified in [Appendix B](#appendix-b-genesis-block)
* `make_genesis_block_from_ledger_info(ledger_info: LedgerInfo)`. This function is called with a ledger info that contains the next set of validators.
- first, create a virtual block that will be the parent of the genesis block. It carries the epoch, the executed state hash, version, and timestamp from the previous epoch:
<pre class="rust">
let ancestor = BlockInfo {
epoch: ledger_info.epoch,
round: 0,
id: [0u8; 32], // TODO: why id is 0 ?
executed_state_id: ledger_info.commit_info.executed_state_id,
version: ledger_info.commit_info.version,
timestamp_usecs: ledger_info.commit_info.timestamp_usecs,
next_validator_set: None,
};
</pre>
- second, a genesis block carries a QC that certifies this virtual parent block, the QC's ledger info carries information about the version of the last ledger info of the previous epoch:`
<pre class="rust">
let genesis_quorum_cert = QuorumCert {
vote_data: VoteData {
proposed: ancestor,
parent: ancestor, // TODO: why is it the parent as well? maybe set that to None instead?
},
signed_ledger_info: LedgerInfoWithSignatures {
LedgerInfo {
commit_info: ancestor,
consensus_data_hash: [0u8; 32], // TODO: why null?
},
Map{}, // TODO: how to define an empty (or non-empty) map in LCS DSL?
},
};
</pre>
- finally, create the genesis block data at the new epoch, at round 0, carrying the same timestamp, a genesis block type and the genesis QC we just created:
<pre class="rust">
let block_data = BlockData {
epoch: genesis_quorum_cert.vote_data.proposed.epoch + 1,
round: 0,
timestamp_usecs: ledger_info.commit_info.timestamp_usecs,
quorum_cert: genesis_quorum_cert,
block_type: BlockType::Genesis,
};
</pre>
- hash the block_data by calling `block_data_hash = SHA-3-256(PREFIX_BLOCK_DATA || "@@$$LIBRA$$@@" || lcs_serialize(block_data))`.
- return the genesis block:
<pre class="rust">
Block {
id: block_data_hash,
block_data: block_data,
signature: None,
}
</pre>
* `certificate_for_genesis_from_ledger_info(ledger_info, genesis_block_hash)`.
- the *certified block* is the *genesis block*, which is:
<pre class="rust">
let ancestor = BlockInfo {
epoch: ledger_info.epoch + 1,
round: 0,
id: genesis_id,
executed_state_id: ledger_info.commit_info.executed_state_id,
version: ledger_info.commit_info.version,
timestamp_usecs: ledger_info.commit_info.timestamp_usecs,
next_validator_set: None,
};
</pre>
- create the *vote data*, which contains the genesis block as certified block, as well as parent (TODO: why??)
<pre class="rust">
let vote_data = VoteData {
proposed: ancestor,
parent: ancestor, // TODO: why ancestor again?
};
</pre>
- hash the vote data by calling `let vote_hash = SHA-3-256(PREFIX_VOTE_DATA || "@@$$LIBRA$$@@" || lcs_serialize(vote_data));`
- return the quorum certificate containing information about the genesis block it certifies, as well as committing the genesis block ()
<pre class="rust">
QuorumCert {
vote_data: vote_data,
signed_ledger_info: LedgerInfoWithSignatures {
LedgerInfo {
commit_info: ancestor, // TODO: why is this set?
consensus_data_hash: vote_hash,
},
Map{}, // TODO: how to define an empty (or non-empty) map in LCS DSL?
},
}
</pre>
### Validator Verifier / Validator Set
values:
* validators_public_keys: (address: [u8; 32]) -> (public_key: [u8; 32])
* quorum_voting_power: u64,
functions:
* initialization(public_keys)
- set `state.quorum_voting_power` to `public_keys.len() * 2 / 3 + 1`
- set `state.validators_public_keys`
- TKTK
### Proposal Generator
has access to:
* block store
* txn manager
values:
* `author`. The account address of this validator.
* `max_block_size`. The maximum number of transactions to be added to a proposed block.
* `last_round_generated`. The last round where we proposed a block as a leader.
interface:
* **`initialize(author, max_block_size)`**.
- set `proposal_generator.author = author`.
- set `proposal_generator.max_block_size = max_block_size`.
- set `proposal_generator.last_round_generated = 0`.
* **`generate_nil_block(round)`**.
- set `hqc = block_store.highest_quorum_cert`.
- ensure that `hqc.vote_data.proposed.round < round`.
- set
<pre class="rust">
let block_data = BlockData {
epoch: hqc.vote_data.proposed.epoch,
round: round,
timestamp_usecs: hqc.vote_data.proposed.timestamp_usecs,
quorum_cert: hqc,
block_type: BlockType::NilBlock,
}
</pre>
- set `block_data_hash = SHA-3-256(PREFIX_BLOCK_DATA || "@@$$LIBRA$$@@" || lcs_serialize(block_data))`.
- return
<pre class="rust">
Block {
id: block_data_hash,
block_data: block_data,
signature: None,
}
</pre>
* **`generate_proposal(round, round_deadline)`**.
- the round must be greater than the last one we proposed for: if `state.last_round_generated <= round` return.
- update the last round we proposed for to the current one: `state.last_round_generated = round`.
- retrieve the highest quorum certificate: `let hqc = ensure_highest_quorum_cert(round)`.
- if `hqc`'s certified block as reconfiguration (`hqc.vote_data.proposed.next_validator_set` is set) then return with the result of `generate_reconfig_empty_suffix(round)`
- retrieve all pending blocks in between the last committed block (non-included) and the last certified block: call `let pending_blocks = block_store.path_from_root(hqc.vote_data.proposed.id)` and ensure that no error is returned.
- collect all the payloads from `pending_blocks` and store them in an `exclude_payload` variable.
- call `wait_if_possible(hqc.vote_data.proposed.timestamp_usecs, round_deadline)` and ensure that it does not return an error.
- obtain transactions from mempool, excluding transactions already proposed: `payload = txn_manager.pull_txns(max_block_size, exclude_payload)`.
- return
<pre class="rust">
BlockData {
epoch: hqc.vote_data.proposed.epoch,
round: round,
timestamp_usecs: time_service.get_current_timestamp(),
quorum_cert: hqc,
block_type: BlockType::Proposal {
payload: payload,
author: state.author,
},
}
</pre>
* **`generate_reconfig_empty_suffix(round)`**.
- retrieve the highest quorum certificate: `let hqc = ensure_highest_quorum_cert(round)`.
- return an empty `BlockData` for a proposal indicating that an epoch change is bound to happen:
<pre class="rust">
return BlockData {
epoch: hqc.vote_data.proposed.epoch,
round: round,
timestamp_usecs: hqc.vote_data.proposed.timestamp_usecs,
quorum_cert: hqc,
block_type: BlockType::Proposal {
payload: [], // empty payload
author: this.validator_address,
},
};
</pre>
* **`ensure_highest_quorum_cert(round) -> QuorumCert`**.
- retrieve the `highest_quorum_cert` from storage.
- ensure that its certified block's round is strictly smaller than `round`.
- ensure that it does not end an epoch (`highest_quorum_cert.signed_ledger_info.ledger_info.commit_info.next_validator_set` is set).
- return the `highest_quorum_cert`.
### Event Processor
The event processor module is in charge of processing consensus messages.
Since it is not aware of epochs, it is created by the epoch manager module at the start of each new epoch.
Event processor maintains the following values:
* **`epoch`**. The current epoch.
* **`validators`**. The set of validators for the current epoch.
* **`last_vote_sent`**. The round of the last vote sent, and the last vote sent. This is useful for safety, in order not to vote twice for the same round.
The following functions are used to initialize and advance the protocol:
* **`initialize(epoch, last_vote: Option<(Vote, Round)>, validator_set)`**.
- set `state.epoch = epoch`.
- set `state.last_vote_sent = last_vote`.
- set `validators = validator_set`.
* **`start()`**.
- set `hqc_round = Some(block_store.highest_quorum_cert.vote_data.proposed.round)`
- if `block_store.highest_timeout_cert` is `None`, set `htc_round = None`, otherwise set `htc_round = block_store.highest_timeout_cert.timeout.round`.
- set `last_committed_round = Some(block_store.root.round)`
- call `new_round_event = pacemaker.process_certificates(hqc_round, htc_round, last_committed_round)` and crash if the result is `None`.
- call `process_new_round_event(new_round_event)`.
* **`process_new_round_event(new_round_event: NewRoundEvent)`**.
- if `proposer_election.proposer_for(new_round_event.round)` is not us, return.
- ensure that `proposal_msg = generate_proposal(new_round_event)` returns no error.
- call `network.broadcast_proposal(proposal_msg)`.
The rest of this section go through:
* being a leader
* processing a syncinfo consensus message
* processing a proposal consensus message
* processing a vote consensus message
#### Being a leader
* **`generate_proposal(new_round_event: NewRoundEvent) -> ProposalMsg`**.
- call `proposal = proposal_generator.generate_proposal(new_round_event.round, pacemaker.current_round_deadline())`
- set `signed_proposal = self.safety_rules.sign_proposal(proposal)` and return an error if it produces an error.
- return `Ok(ProposalMsg::new(signed_proposal, gen_sync_info()))`
* **`gen_sync_info() -> SyncInfo`**.
- set `hqc = block_store.highest_quorum_cert`
- if `block_store.highest_timeout_cert` is `None` or if ``block_store.highest_timeout_cert.timeout.round <= hqc.vote_data.proposed.round` set `htc = None`
- otherwise set `htc = block_store.highest_timeout_cert.timeout`.
- return the following `SyncInfo`:
<pre class="rust">
SyncInfo {
highest_quorum_cert: hqc,
highest_commit_cert: block_store.highest_commit_cert,
highest_timeout_cert: htc,
}
</pre>
#### Processing a Local Timeout.
(TODO: how does this ever get called?)
* **`process_local_timeout(round: u64)`**.
- call `pacemaker.process_local_timeout(round)`. If it returns `false`, return.
- if we already voted in this round (`safety_rules.last_voted_round.vote_round == round`) set `timeout_vote = safety_rules.last_voted_round.vote`. This is important as we do not want to create two different votes for the same round.
- if we voted for the backup proposal in this round instead (`proposer_election.take_backup_proposal(round)` returns a block), call `let timeout_vote = execute_and_vote()` on the block returned. If it errors, return.
- if we did not vote in this round, generate a nil block by calling `let nil_block = proposal_generator.generate_nil_block(round)`, then call `let timeout_vote = executed_and_vote()` and return if it errors.
- if the vote is not a timeout (if `timeout_vote.timeout_signature` is not set (TODO: when is this the case?)),
+ create a timeout struct:
<pre class="rust">
let timeout = Timeout {
epoch: timeout_vote.vote_data.proposed.epoch,
round: timeout_vote.vote_data.proposed.round,
};
</pre>
+ sign it: `let signature = safety_rules.sign_timeout(timeout)`. If the call errors, return.
+ add the signature to the timeout vote: `timeout_vote.timeout_signature = signature`.
- create a vote message:
<pre class="rust">
let vote = VoteMsg {
vote: timeout_vote,
sync_info: gen_sync_info(),
};
</pre>
- serialize the `vote` with LCS, encapsulate it inside a `ConsensusMsg` protobuf message and broadcast it by calling `network.broadcast(timeout_vote_msg)`.
#### Process a SyncInfo Message
<pre class="rust">
struct SyncInfo {
highest_quorum_cert: QuorumCert,
highest_commit_cert: QuorumCert,
highest_timeout_cert: Option<TimeoutCertificate>,
}
</pre>
* **`process_sync_info_msg(sync_info_proto, peer: [u8; 32])`**.
- deserialize the `sync_inf_proto` protobuf message with LCS into `sync_info`.
<pre class="rust">
let sync_info: SyncInfo = lcs_deserialize(sync_info_proto);
</pre>
- if `sync_info.epoch` is not the current epoch `state.epoch`, call `epoch_manager` and return:
<pre class="rust">
epoch_manager.process_different_epoch(peer, sync_info.epoch);
return;
</pre>
- call `sync_up()`:
<pre class="rust">
sync_up(sync_info, peer, false)
</pre>
<!--sync_up is called:
* when processing a real vote
* when processing a proposal
* when processing a syncup message
-->
* **`sync_up(sync_info, peer, help_remote)`**. This function figures out if we are lagging behind or not.
- if `help_remote` is `true`, help the remote peer (if it is lagging behind) by calling `help_remote_if_stale(peer, sync_info.highest_round, sync_info.hqc_round)`.
- if we are up-to-date with the `sync_info`, we can return early. This equivalent to say that if all the following statements are true, return:
+ the sync_info carries a QC that certifies a round is lesser or equal to the proposal's round certified by our highest quorum certificate in store
<pre class="rust">
sync_info.highest_quorum_cert.vote_data.proposed.round >= storage.highest_quorum_cert.vote_data.proposed.round
</pre>
+ the sync_info carries a timeout certificate for a round smaller or equal to the highest timeout certificate we have in store.
+ the sync_info carries a quorum certificate that commits a round smaller or equal to the round of the last block we have committed.
- perform some consistency checks:
+ ensure that the epoch of the highest QC matches the epoch of the highest LI:
<pre class="rust">
ensure!(sync_info.highest_quorum_cert.vote_data.proposed.epoch == sync_info.highest_commit_cert.vote_data.proposed.epoch);
</pre>
+ if the highest TC `sync_info.highest_timeout_cert` is set, ensure that the epoch of the highest QC is the epoch of the highest TC:
<pre class="rust">
ensure!(sync_info.highest_quorum_cert.vote_data.proposed.epoch == sync_info.highest_timeout_cert.timeout.epoch);
</pre>
+ ensure that the highest QC's certified block's round is greater or equal to the highest LI's round:
<pre class="rust">
ensure!(sync_info.highest_quorum_cert.vote_data.proposed.round >= sync_info.highest_commit_cert.vote_data.proposed.round);
</pre>
+ ensure that the highest LI's commit info id is set:
<pre class="rust">
ensure!(sync_info.highest_commit_cert.signed_ledger_info.ledger_info.commit_info.id != [0u8; 32])
</pre>
- perform some more checks:
+ set `epoch = sync_info.highest_quorum_cert.vote_data.proposed.epoch`
+ ensure `epoch == sync_info.highest_commit_cert.vote_data.proposed.epoch` (TODO: what about parent?)
+ if the `highest_timeout_cert` is set, ensure that its epoch is the same `epoch`.
+ ensure that the round of `highest_quorum_cert`'s certified block is greater or equal to the round of `highest_commit_cert`'s certified block.
+ ensure that `highest_commit_cert` does not have an empty BlockInfo, as defined in appendix E.
- verify the signatures over the highest quorum certificate.
<pre class="rust">
ensure!(verify_signatures_QC(sync_info.highest_quorum_certificate))
</pre>
- verify the signatures over the highest ledge info.
<pre class="rust">
ensure!(verify_signatures_LI(sync_info.highest_commit_cert))
</pre>
- verify the signature over the highest timeout certificate (if set).
<pre class="rust">
ensure!(verify_signatures_TC(sync_info.highest_timeout_certificate))
</pre>
- process the received QC and TC.
<pre class="rust">
process_certificates(sync_info.highest_quorum_cert, sync_info.highest_timeout_certificate)
</pre>
* **`help_remote_if_stale(peer, remote_round, remote_hqc_round)`**. This function sends a `SyncInfo` message to the peer if the peer is lagging behind.
- do not help the remote if it has a greater round `remote_round + 1 >= pacemaker.current_round` and a greater quorum certificate than us:
<pre class="rust">
if remote_round + 1 >= pacemaker.current_round
&& remote_hqc_round >= block_store.highest_quorum_cert.vote_data.proposed.round {
return;
}
</pre>
- generate a SyncInfo: `sync_info = gen_sync_info()`
- send the SyncInfo to the peer: `network.send_sync_info(sync_info, peer)`
* **`gen_sync_info()`**. This generate a sync info message with our local data.
- set `let htc = None`.
- if `block_store.highest_timeout_cert` is not empty, and its round is higher than `block_store.highest_quorum_cert.vote_data.proposed.round`, set `htc = block_store.highest_timeout_cert`:
<pre class="rust">
if let Some(tc) = block_store.highest_timeout_cert
&& tc.timeout.round > block_store.highest_quorum_cert.vote_data.proposed.round {
htc = Some(tc);
}
</pre>
- return:
<pre class="rust">
return SyncInfo {
highest_quorum_cert: block_store.highest_quorum_cert,
highest_commit_cert: block_store.highest_commit_cert,
highest_timeout_cert: htc,
};
</pre>
<pre class="rust">
struct Timeout {
epoch: u64,
round: u64,
}
struct TimeoutCertificate {
timeout: Timeout,
signatures: HashMap<[u8; 32], [u8; 64]>,
}
</pre>
**`verify_signatures_LI(li, signatures, validator_set)`**
- if the version of the commit info is 0, skip the signature verification
<pre class="rust">
if li.commit_info.version == 0 {
// skip signature verification
}
</pre>
- get a hash of the LI:
<pre class="rust">
let ledger_info_hash = SHA-3-256(PREFIX_LEDGER || "@@$$LIBRA$$@@" || lcs_serialize(li));
</pre>
- ensure the number of signatures is coherent:
<pre class="rust">
ensure!(validator_set.len() >= li.signatures.len());
</pre>
- sum the voting power contained in the signatures
<pre class="rust">
let voting_power = 0;
for (account_address, _) in signatures {
match validator_set.get_voting_power(account_address) {
Some(validator_power) => voting_power += validator_power,
None => return Err(VerifyError::UnknownAuthor),
}
}
</pre>
- ensure that the voting power forms a QC
<pre class="rust">
if voting_power < self.quorum_voting_power {
return Err(VerifyError::TooLittleVotingPower {
voting_power: voting_power,
quorum_voting_power: self.quorum_voting_power,
});
}
</pre>
- ensure that each signature is valid
<pre class="rust">
for (account_address, signature) in signatures {
public_key = validator_set.get_public_key(account_address);
ensure!(ed25519_verify(public_key, signature, ledger_info_hash));
}
</pre>
process_certificates is called:
* after adding a vote if it creates a QC or a TC
* after syncing up, if the current highest qc's round was strictly smaller than the sync info highest qc's round.
* **`process_certificates(qc: QuorumCert, tc: Option<TimeoutCertificate>)`**.
- potentially update the preferred round by calling `safety_rules.update(qc)`.
- if we have the pending block in store, and its round is strictly greater than the last committed block's round:
+ if the block is not a NIL block, set `highest_committed_proposal_round = block.block_data.round`.
+ call `process_commit(qc.signed_ledger_info)`.
- if the function was provided with a timeout certificate `tc`, store the timeoutcertificate in storage.
- call `pacemaker.process_certificates(Some(qc.vote_data.proposed.round), tc.timeout.round, highest_committed_proposal_round)` where the last two arguments could potentially be `None`.
+ if the response is a `new_round_event`, call `process_new_round_event(new_round_event)`.
#### Processing a Proposal Message
A proposal message is expected to contain a valid proposal (not a nil or a genesis block) as well as a SyncInfo message that contains enough information for the validator to decide what is the current round.
* **`process_proposal_msg(proposal_msg_proto, peer_id)`**:
- deserialize the `proposal_msg_proto` into `proposal_msg` using LCS.
- preliminary checks
+ check for a matching epoch, as we **cannot process a proposal from a different epoch** (the validator set might be different). If `proposal.block_data.epoch != state.epoch`, then call `network.send_to_ourselves(DifferentEpoch(proposal.block_data.epoch, peer_id))` and return.
+ ensure that the proposal **does not contain a genesis block**: if `proposal_msg.proposal.block_data` is of type `BlockType::Genesis`, ignore the proposal.
- signature checks
+ validate the **author's signature on the proposal** if `proposal_msg.proposal.block_data` is of type `BlockType::Proposal` (this will be skipped if the proposal contains a nil block):
+ hash the block_data field:
<pre class="rust">
let block_data_hash = SHA-3-256(PREFIX_BLOCK_DATA || "@@$$LIBRA$$@@" || lcs_serialize(proposal_msg.proposal.block_data));
</pre>
+ obtain the author's public key by calling `let public_key = validator_set.get_public_key(proposal_msg.proposal.block_data.block_type.author)`
+ verify the signature by calling `ed25519_verify(public_key, proposal_msg.proposal.signature, block_data_hash)`. Ignore the proposal if it does not validate.
+ validate the QC by calling `verify_signatures_QC(proposal_msg.proposal.block_data.quorum_cert)`.
+ If the SyncInfo contains a timeout certificate, validate it by calling `verify_signatures_TC(proposal_msg.sync_info.highest_timeout_certificate)` (we delay the verification of the highest ledger info).
- wellformedness checks:
+ ensure that the proposal is not a nil block.
+ ensure the block is not a genesis block (TODO: this is redundant).
+ set `parent_round = proposal_msg.proposal.block_data.quorum_cert.vote_data.proposed.round`.
+ ensure that the parent block happened at a previous round (`parent_round < proposal_msg.proposal.block_data.round`).
+ ensure that the parent block did not end an epoch: verify that `proposal_msg.proposal.block_data.quorum_cert.signed_ledger_info.ledger_info.commit_info.next_validator_set` is not set.
+ ensure `proposal_msg.proposal.block_data.round > 0`.
+ ensure `proposal_msg.proposal.block_data.epoch == proposal_msg.sync_info.highest_quorum_cert.vote_data.proposed.epoch`.
+ ensure `proposal_msg.proposal.block_data.quorum_cert.vote_data.proposed.id == proposal_msg.sync_info.highest_quorum_cert.vote_data.proposed.id`.
+ set `previous_round = proposal_msg.proposal.block_data.round - 1`.
+ set `highest_certified_round = max(parent_round, proposal_msg.sync_info.highest_timeout_certificate.timeout.round)` where `proposal_msg.sync_info.highest_timeout_certificate.timeout.round = 0` if `proposal_msg.sync_info.highest_timeout_certificate` is not set.
+ ensure `previous_round == highest_certified_round`.
+ ensure `proposed_block.proposal_msg.proposal.block_data.block_type.author` is set. (TODO: redundant since we already checked it's not a genesis/nil block).
- context checks:
+ ensure that `proposal_msg.proposal.block_data.round >= pacemaker.current_round()`.
+ ensure that `proposer_election.is_valid_proposer(proposal_msg.proposal.block_data.block_type.author, proposal_msg.proposal.block_data.round)`.
- process the `SyncInfo` message to ensure we are at the correct round.
+ ensure that `sync_up(proposal_msg.sync_info(), proposal_msg.proposer(), help_remote=true)` doesn't return an error.
+ ensure that `proposal_msg.proposal.block_data.round == pacemaker.current_round()`.
- let the proposer election figure out if we can vote for this proposal by calling `let block = proposer_election.process_proposal(proposal_msg.proposal)`. If `block` is not set, ignore the proposal (for now, it could become the back up proposal if the round times out).
- **execute the proposal and vote** by calling `let vote = execute_and_vote(proposal_msg.proposal)`. If the function errors, ignore the proposal.
- **send the created vote to the next leader(s)**:
+ obtain the recipient list by calling `recipients = proposer_election.get_valid_proposers(proposal_msg.proposal.block_data.round + 1)`
+ generate a new vote message:
<pre class="rust">
let vote_msg = VoteMsg {
vote: vote,
sync_info: gen_sync_info(),
}
</pre>
* send the vote to the recipients by calling `network.send_vote(vote_msg, recipients)`.
* **`execute_and_vote(proposed_block: Block)`**. Execute and emit a signed vote for a proposed block.
- execute and save the block to storage by calling `block_store.execute_and_insert_block(proposed_block)`. If it errors, ignore the proposal.
- ensure that the proposed block is at the current round: `proposed_block.block_data.round == pacemaker.current_round()`.
- fetch the parent block: `let parent = block_store.get_block(proposed_block.block_data.quorum_cert.vote_data.proposed.id)`.
- potentially wait by calling `wait_before_vote_if_needed(block.timestamp_usecs())` (TODO: why would we wait here if we're already a timeout) (TODO: this function is not specified)
- retrieve the execution state, the version, and the next validator set in case there is an epoch change after execution of the block: `let (executed_state_id, version, next_validator_set) = execution.get_execution(block)`
- construct a vote by calling `let vote = safety_rules.construct_and_sign_vote(proposed_block, executed_state_id, version, next_validator_set)`
- store the vote by calling `storage.save_state(vote)`.
- update our state, set `state.last_vote_sent = (vote, proposed_block.round)`.
- return `vote` to the caller.
<pre class="rust">
struct LedgerInfo {
commit_info: BlockInfo,
consensus_data_hash: [u8; 32],
}
struct LedgerInfoWithSignatures {
ledger_info: LedgerInfo,
signatures: Map<[u8; 32], [u8; 64]>,
}
</pre>
**`verify_signatures_TC(qc, validator_set)`**
<pre class="rust">
struct VoteData {
proposed: BlockInfo,
parent: BlockInfo,
}
struct QuorumCert {
vote_data: VoteData,
signed_ledger_info: LedgerInfoWithSignatures,
}
</pre>
**`verify_signatures_QC(qc, validator_set)`**
- hash the vote data:
<pre class="rust">
let vote_hash = SHA-3-256(PREFIX_VOTE_DATA || "@@$$LIBRA$$@@" || lcs_serialize(qc.vote_data));
</pre>
- ensure that the consensus data hash matches the calculted hash:
<pre class="rust">
ensure!(qc.signed_ledger_info.ledger_info.consensus_data_hash == vote_hash);
</pre>
- if the QC certifies the genesis block, skip the signature verification:
<pre class="rust">
if qc.vote_data.proposed.round == 0
&& qc.vote_data.proposed.id == GENESIS_BLOCK_ID
&& qc.vote_data.proposed.executed_state_id = ACCUMULATOR_PLACEHOLDER_HASH {
// skip signature verification
}
</pre>
- call `verify_signatures_LI(qc.signed_ledger_info, validator_set)` and ensure it does not return an error
<pre class="rust">
ensure!(verify_signatures_LI(qc.signed_ledger_info.ledger_info, qc.signed_ledger_info.signatures, validator_set));
</pre>
#### Processing a Vote Message
(TODO: what if it's a timeout, what does a vote_data and ledger_info contain?)
(TODO: what if it's a timeout after a reconfig block?)
When receiving a `Vote` protobuf message, the following pseudo-code is executed:
* **`process_vote(vote_msg_proto, peer_id)`**.
- deserialize `vote_msg_proto` into `vote_msg` using LCS.
- ensure that the author of the vote is `peer_id`.
- if the epoch of the vote is not `state.epoch`, call `network.send_to_ourself(DifferentEpoch(vote_msg.vote_data.proposed.epoch))` and return.
- ensure that the vote's epoch is the same as the epoch of `sync_info.highest_quorum_cert`'s certified block.
- ensure that the `consensus_data_hash` of the vote's `ledger_info` is equal to the hash of the `vote_data` field. This hash can be computed as follows:
<pre class="rust">
SHA-3-256(PREFIX_VOTE_DATA || "@@$$LIBRA$$@@" || lcs_serialize(vote_msg.vote.vote_data))
</pre>
- verify that the signature contained in the vote over the hash of `ledger_info` is correct. You can compute this hash as follows:
<pre class="rust">
SHA-3-256(PREFIX_LEDGER || "@@$$LIBRA$$@@" || lcs_serialize(vote_msg.vote.ledger_info))
</pre>
- if a `timeout_signature` is set, verify that it is valid over the hash of the serialized `Timeout` structure:
<pre class="rust">
Timeout {
epoch: vote_msg.vote_data.proposed.epoch,
round: vote_msg.vote_data.proposed.round,
}
</pre>
the hash of the `Timeout` structure can be calculated as follow:
<pre class="rust">
SHA-3-256(PREFIX_TIMEOUT || "@@$$LIBRA$$@@" || lcs_serialize(timeout))
</pre>
- if a `timeout_signature` is set, call `sync_up(vote_msg.sync_info, vote_msg.vote.author, true)` and return. This might help the peer who could be timing out because it is desynchronized with the rest of the network. (TODO: why else could we possibly do this if we receive a timeout vote as a leader?)
- ensure that we are the correct person to process this vote by calling `proposer_election.is_valid_proposer(state.this_validator, next_round)` where `next_round` is the `vote_msg.vote.vote_data.proposed.round + 1`.
- call `block_store.insert_vote(vote_msg.vote)`.
+ if it returns a new quorum certificate `new_qc`, call `block_store.sync_to()` and ensures it does not return an error with the following `SyncInfo`:
<pre class="rust">
SyncInfo {
highest_quorum_cert: new_qc,
highest_commit_cert: block_store.highest_commit_cert(),
highest_timeout_cert: None,
}
</pre>
and with `pacemaker.current_round_deadline()` as deadline, and `vote_msg.vote.author` as preferred_peer.
+ if it returns a new timeout certificate `new_tc`, call `block_store.insert_timeout_certificate(new_tc)` to store the new timeout certificate and ensure that it does not return an error.
- call `process_certificates()` on the either the new QC, or the highest QC we have in store ( `block_store.highest_quorum_cert()`) and the new TC.
#### Request for blocks
* **request_block(block_id, num_blocks, peer, timeout)**
- use LCS to serialize the following `BlockRetrievalRequest` struct into a `serialized` message:
<pre class="rust">
BlockRetrievalRequest {
block_id: block_id,
num_blocks: num_blocks,
}
</pre>
- encapsulate it into a `ConsensusMsg` of type `RequestBlock`:
<pre class="rust">
ConsensusMsg {
message: RequestBlock {
bytes: serialized,
}
}
</pre>
- send the request to the `peer` by calling `network.send_to()`. (TODO: or another API that waits for a response?)
- wait for a `BlockRetrievalResponse` response (TODO: security consideration about DoS here?)
- ensure that the response contains as many blocks as requested, or otherwise that the status is not `BlockRetrievalStatus::Succeeded` (indicating perhaps that less blocks were still received and can be processed).
- verify the signature of every block
+ (TODO: more details?)
- verify that every block is well-formed
+ ensure that the block is not a genesis block
+ ensure that the block's quorum cert certifies a block at a round strictly smaller than the block's round
+ ensure that the quorum certificates does not indicate an epoch ending (ensure `block.block_data.quorum_cert.signed_ledger_info.ledger_info.commit_info.next_validator_set` is not set).
- make sure that the blocks form a linear chain up to the requested block id.
- return the blocks to the caller.
#### Process Block Retrieval
<pre class="rust">
struct BlockRetrievalRequest {
block_id: HashValue,
num_blocks: u64,
}
enum BlockRetrievalStatus {
// Successfully fill in the request.
Succeeded,
// Can not find the block corresponding to block_id.
IdNotFound,
// Can not find enough blocks but find some.
NotEnoughBlocks,
}
struct BlockRetrievalResponse<T> {
status: BlockRetrievalStatus,
blocks: Vec<Block>,
}
</pre>
* **`process_block_retrieval(request: BlockRetrievalRequest, from_peer: address)`**. Responds to a block retrieval request
- retrieve `num_blocks` blocks leading to the block `request.block_id` (included) from the block store.
+ if `request.block_id` could not be found, set `status` to `BlockRetrievalStatus::IdNotFound`
+ if not enough blocks could be retrieved from the block store, set `status` to `BlockRetrievalStatus::NotEnoughBlocks`
+ otherwise set `status` to `BlockRetrievalStatus::Succeeded`
- create a `BlockRetrievalResponse` with the `status` and the `blocks` retrieved from the block store:
<pre class="rust">
BlockRetrievalResponse {
status: status,
blocks: blocks,
}
</pre>
- serialize the response with LCS into a `ConsensusMsg` protobuf and send it to `from_peer`.
## Consensus Protocol (Chained BFT SMR)
At the very beginning, storage must be initialized with a few genesis data structures:
* a `highest_commit_cert`.
* an epoch number.
(TODO: move this to persistent storage module)
The consensus state maintains the following values:
* `private_key`. The private key of this validator.
* `public_key`. The consensus public key for this validator, derived from the private key.
* `address`. The address of this validator, derived from the public key.
They are accessible from other modules via the `state.` prefix.
### Starting Consensus
This section go over initial start and any subsequent restart that could happen.
Initialization happens during the first start, and after any restart.
A validator must be initialized with the following values:
* `private_key` and `public_key`. A consensus keypair.
* A list of configuration: (TODO: should these be constants? Do we care about these values in the spec?)
<pre class="rust">
max_pruned_blocks_in_mem: node_config.consensus.max_pruned_blocks_in_mem | 10000,
pacemaker_initial_timeout: node_config.consensus.pacemaker_initial_timeout_ms | 1000,
max_block_size: node_config.consensus.max_block_size,
</pre>
steps:
* initialize the state of consensus
- set `state.private_key = private_key`
- set `state.public_key = public_key`
- set `state.address = SHA-3-256(public_key)`
* Call `storage.start()` to obtain the following values:
- `epoch`. The current epoch tracked by the storage (initialy 0).
- `last_vote`. The last vote message sent.
- `root_block`. The genesis block for the last epoch tracked.
- `root_qc`. The genesis quorum certificate for the last epoch tracked.
- `root_ledger_info`. The highest ledger info of the last epoch (which is a quorum certificate that contains the ledger info with the commit info that contains the change of validator set).
- `blocks`. A series of pending blocks (not yet committed), sorted by round.
- `quorum_certs`. A series of quorum certificates that certify these pending blocks.
- `blocks_to_prune`. (TODO: do we need this?)
- `highest_timeout_certificate`. If present, the highest timeout certificate we've observed (initialy none).
- `validator_set`. A set of validator's public key. (TODO: actually, it's a validator verified struct)
* Initialize the validator verifier module by calling `validator_verifier.initialization(validator_set)`.
* Initialize epoch manager (and other modules) by calling `epoch_manager.initialize(epoch)`.
* Initialize the network by calling `network.initialize(epoch, validator_set)`.
* Start event processing by calling `event_processor.start()`.
### Running the Consensus Protocol
The Event Processor is where the main loop of consensus happens.
It reads a queue of consensus messages from the network layer, and process them in the order received (TODO: priority queue?).
In addition, it has access to the different modules we described previously in this document.
We specify below the main loop of the event processor in pseudo-code:
<pre class="rust">
loop {
match (msg, from_peer) = network.next_msg() {
// event processor messages
Proposal => event_processor.process_proposal_msg(msg, from_peer),
block_retrieval => event_processor.process_block_retrieval(msg, from_peer),
Vote => event_processor.process_vote(msg, from_peer),
local_timeout_round => event_processor.process_local_timeout(msg, from_peer),
SyncInfo => event_processor.process_sync_info_msg(msg, from_peer),
// epoch manager messages
EpochChange => {
event_processor = epoch_manager.start_new_epoch(msg, from_peer);
network.clear_prev_epoch_msgs();
event_processor.start();
},
EpochRetrieval => epoch_manager.process_epoch_retrieval(epoch, from_peer),
// TODO: this one can only be received internally. Make sure to specify this
DifferentEpoch => epoch_manager.process_different_epoch(epoch, from_peer),
};
}
</pre>
It is good to note that each message we process should contain an indication of the epoch, and if the epoch is different, the epoch manager is called instead of the event processor.
## Security Concerns
* protocol buffer -> everything is optional
* time monocity -> beware with unix timestamps, they are not monotonic
## Appendices
### Appendix A: Data Structures
<pre class="rust">
pub struct ProposalMsg {
proposal: Block,
sync_info: SyncInfo,
}
struct Vote {
vote_data: VoteData,
author: [u8; 32],
ledger_info: LedgerInfo,
signature: [u8; 64],
timeout_signature: Option<[u8; 64]>,
}
struct VoteData {
proposed: BlockInfo,
parent: BlockInfo,
}
struct VoteMsg {
vote: Vote,
sync_info: SyncInfo,
}
struct Timeout {
epoch: u64,
round: u64,
}
struct TimeoutCertificate {
timeout: Timeout,
signatures: HashMap<[u8; 32], [u8; 64]>,
}
struct SyncInfo {
highest_quorum_cert: QuorumCert,
highest_commit_cert: QuorumCert,
highest_timeout_cert: Option<TimeoutCertificate>,
}
struct QuorumCert {
vote_data: VoteData,
signed_ledger_info: LedgerInfoWithSignatures,
}
struct ProposalMsg {
proposal: Block,
sync_info: SyncInfo,
}
struct ExecutedBlock {
block: Block,
output: ProcessedVMOutput,
}
struct EpochRetrievalRequest {
start_epoch: u64,
target_epoch: u64,
}
struct Block {
block_data: BlockData,
signature: Option<[u8; 64]>,
}
struct BlockRetrievalRequest {
block_id: [u8; 32],
num_blocks: u64,
}
enum BlockType {
Proposal {
payload: [u8],
author: [u8; 32],
},
NilBlock,
Genesis,
}
struct BlockData {
epoch: u64,
round: u64,
timestamp_usecs: u64,
quorum_cert: QuorumCert,
block_type: BlockType,
}
struct BlockInfo {
epoch: u64,
round: u64,
id: [u8; 32],
executed_state_id: [u8; 32],
version: u64,
timestamp_usecs: u64,
next_validator_set: Option<ValidatorSet>,
}
struct ValidatorSet(Vec<ValidatorPublicKeys>);
struct ValidatorPublicKeys {
account_address: [u8; 32],
consensus_public_key: Ed25519PublicKey,
consensus_voting_power: u64,
network_signing_public_key: Ed25519PublicKey,
network_identity_public_key: X25519StaticPublicKey,
}
struct QuorumCert {
vote_data: VoteData,
signed_ledger_info: LedgerInfoWithSignatures,
}
struct LedgerInfoWithSignatures {
ledger_info: LedgerInfo,
signatures: HashMap<[u8; 32], [u8; 64]>,
}
struct LedgerInfo {
commit_info: BlockInfo,
consensus_data_hash: [u8; 32],
}
</pre>
### Appendix B: Genesis Block
The genesis block at epoch 0 contains an important genesis transaction that
once executed accounts for:
* The MOVE modules at the core of Libra.
* The initial set of validators.
* The minting public key.
(TODO: define how this genesis transaction is created: https://github.com/libra/libra/blob/c1791032c50e41474d428528923cd5e62350cd8f/language/vm/vm-genesis/src/lib.rs#L201)
The following is the encoding of the genesis transaction using base64:
<pre class="rust">
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
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAApVDBgBAAAAAAAAAAIAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAApVDBghAAAAAXFF1Jjg5/iDhTP2EupmETTBiNlMLX8p1UfTrWcx
cF+qAQAAAGgEAAA1AAAAGwAAAAAAAAABAAAAAAAAABwAAAAAAAAAAQAAAAAAAAAfAAAAAAAAAAEAAAAA
AAAAHQAAAAAAAAABAAAAAAAAAAoAAAAAAAAAAQAAAAAAAAAdAAAAAAAAAAEAAAAAAAAAJAAAAAAAAAAB
AAAAAAAAADQAAAAAAAAAAQAAAAAAAAAdAAAAAAAAAAEAAAAAAAAAHgAAAAAAAAABAAAAAAAAACkAAAAA
AAAAAQAAAAAAAAApAAAAAAAAAAEAAAAAAAAAHAAAAAAAAAABAAAAAAAAAC0AAAAAAAAAAQAAAAAAAAAt
AAAAAAAAAAEAAAAAAAAAOgAAAAAAAAABAAAAAAAAADoAAAAAAAAAAQAAAAAAAAA4AAAAAAAAAAEAAAAA
AAAAxQAAAAAAAAABAAAAAAAAAEkAAAAAAAAAAQAAAAAAAABeAAAAAAAAAAEAAAAAAAAAMwAAAAAAAAAB
AAAAAAAAAEEAAAAAAAAAAQAAAAAAAAAtAAAAAAAAAAEAAAAAAAAALAAAAAAAAAABAAAAAAAAACkAAAAA
AAAAAQAAAAAAAAAqAAAAAAAAAAEAAAAAAAAAKQAAAAAAAAABAAAAAAAAAC0AAAAAAAAAAQAAAAAAAAAs
AAAAAAAAAAEAAAAAAAAALgAAAAAAAAABAAAAAAAAACsAAAAAAAAAAQAAAAAAAAAxAAAAAAAAAAEAAAAA
AAAAIwAAAAAAAAABAAAAAAAAADAAAAAAAAAAAQAAAAAAAAAzAAAAAAAAAAEAAAAAAAAAMQAAAAAAAAAB
AAAAAAAAAC4AAAAAAAAAAQAAAAAAAAAvAAAAAAAAAAEAAAAAAAAALgAAAAAAAAABAAAAAAAAACcAAAAA
AAAAAQAAAAAAAAAdAAAAAAAAAAEAAAAAAAAAIgAAAAAAAAABAAAAAAAAACAAAAAAAAAAAQAAAAAAAAAe
AAAAAAAAAAEAAAAAAAAAWAMAAAAAAAABAAAAAAAAAKEDAAAAAAAAAQAAAAAAAAChAwAAAAAAAAEAAAAA
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
AAAAAAAAACEAAAAAH/b63d2l3kyMm8lcWyBKmZBw8ckMl7gBfUvrelXV+zABAAAAlgMAAExJQlJBVk0K
AQALAW4AAAACAAAAAnAAAAAMAAAAA3wAAAAhAAAADJ0AAAACAAAADZ8AAABmAAAADgUBAABBAAAABUYB
AACdAAAABOMBAABAAAAACSMCAAAMAAAACi8CAAAGAAAACzUCAABhAQAAAAAAAQEAAAIBAAADAQAABAAA
BQEABgIABwMACAQACQUACgYACwcADAgADQkADgoBAgIBBwAAAQIAAgEHAAACAgUHAQAAAgAAAAIBAgAA
AgEHAAAAAAIBAgEFBwAAAAICBwAABwAAAgcAAAIAAgEHAAACBgcAAAIAAgEHAAACBwAABwAAAAIAAgYH
AAAHAAAAAgABBwAAAAMBAgMAAwQCBQcBAAYHAgACAwEFBwAAAwMHAAACBwAAAwMGBwAAAgIDAgcAAAcA
AAMEBgcAAAcAAAICAwIHAAACCUxpYnJhQ29pbgFUDk1pbnRDYXBhYmlsaXR5CU1hcmtldENhcBxtaW50
X3dpdGhfZGVmYXVsdF9jYXBhYmlsaXR5BG1pbnQKaW5pdGlhbGl6ZQptYXJrZXRfY2FwBHplcm8FdmFs
dWUFc3BsaXQId2l0aGRyYXcEam9pbgdkZXBvc2l0DGRlc3Ryb3lfemVybwt0b3RhbF92YWx1ZQAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAApVDBgA
AgEAAQIAAQICAQEACQACDwAAAQIBAgIABQAMAC0wAQETAQECAQEBAgMCGwAMAQELAAYAypo7AAAAAAZA
Qg8AAAAAABonIgQLAAYLAAAAAAAAACkHAS8CAQ0CCwIRARYNAwwDCwAYDAIQARcMABQAAQICAQAEAQ0A
LQcBIyIEBwAGAQAAAAAAAAApFAEBMgEBBgAAAAAAAAAAFAIBMgIBAgMBAQIBAQUABwEwAgERARYCBAEA
AQEDAAYAAAAAAAAAABQAAQIFAQABAwQADAARABYCBgEAAgQHAA4ADAETBwENAgwADAICBwEAAgUUAAsA
EAAWDQILAgsBKCIECwAGCgAAAAAAAAApDAILARkMABAAFwwBFAABAggBAAIGBQAOAAwBEwkBDAACCQEA
AgcOAAsAEAAWDQIMARUAAQ0DDAIMAxgMABAAFwIKAQACCAsADAAVAAENAQwBBgAAAAAAAAAAIyIECgAG
CwAAAAAAAAApAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIQAAAACQlBHMSLdFhHDOanRo
GAHwH4TCqh+YkCRr1E+md+ixRAEAAACUAAAATElCUkFWTQoBAAYBQQAAAAIAAAADQwAAAAYAAAANSQAA
AAYAAAAFTwAAABcAAAAEZgAAACAAAAALhgAAAA4AAAAAAAABAAACAAIBCAEIAARIYXNoCHNoYTJfMjU2
CHNoYTNfMjU2AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAAAAQMAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIQAAAAAvgYs+2wiNRpyR57fH99/1FYHdVMSBq0ZxIUs0
vsX7wQEAAAC6AAAATElCUkFWTQoBAAYBQQAAAAIAAAADQwAAAAYAAAANSQAAABEAAAAFWgAAADIAAAAE
jAAAACAAAAALrAAAAA4AAAAAAAABAAACAQIBAQMICAgAAgECBAgICAgACVNpZ25hdHVyZQ5lZDI1NTE5
X3ZlcmlmeRhlZDI1NTE5X3RocmVzaG9sZF92ZXJpZnkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAADAAAAAAABAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAhAAAAAA1p1IsJ
viY+XFjmSRJKd/meaFE6w2/5DmWaJ7NOEr0wAQAAAIgAAABMSUJSQVZNCgEABgFBAAAAAgAAAANDAAAA
AwAAAA1GAAAABgAAAAVMAAAAFQAAAARhAAAAIAAAAAuBAAAABwAAAAAAAAEAAgEIAQIAB1U2NFV0aWwM
dTY0X3RvX2J5dGVzAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAhAAAAABaVYjITDOvy6SXLyAi4wA6Kb4smWvjuPhurFT6Cmpgh
AQAAAIMDAABMSUJSQVZNCgEACQFcAAAAAgAAAAJeAAAABQAAAANjAAAAKgAAAA2NAAAAtwAAAA5EAQAA
RQAAAAWJAQAAdwAAAAQAAgAAIAAAAAkgAgAABAAAAAskAgAAXwEAAAAAAAECAQEAAgAAAwEABAIABQMA
BgQABwUACAYACQcACggACwkADAoADQsADgwADw0CAQcAAQkAAAEBAgECAQUHAAEJAAEBAgEFCQACBQcA
AQkAAgEBAgACBgcAAQkACQABAQIBBgkAAgYHAAEJAAIBAQIBCQABBgcAAQkAAQECAAEHAAEJAAEBAgAD
BgcAAQkAAgIBAQIAAQYHAAEJAAEBAgACBgcAAQkABwABCQABAQIBAQEFBwABCQABAQIBCQACBQcAAQkA
AgECAgADBgcAAQkAAgkAAQICAQECBQcAAQkABQkAAQEDBAYHAAEJAAICAgMBCQADAgYHAAEJAAcAAQkA
AwEFBwABCQADAgUHAAEJAAIDAwYHAAEJAAIJAAMEBQcAAQkABQkAAgIGVmVjdG9yAVQFZW1wdHkGbGVu
Z3RoBmJvcnJvdwlwdXNoX2JhY2sKYm9ycm93X211dAhwb3BfYmFjaw1kZXN0cm95X2VtcHR5BHN3YXAH
cmV2ZXJzZQZhcHBlbmQIaXNfZW1wdHkDZ2V0A3NldAhjb250YWlucwAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAEAAAADAAAAAAABAwAAAAAAAgMAAAAAAAMDAAAAAAAEAwAAAAAABQMAAAAAAAYD
AAAAAAAHAwAAAAAACAEAAwAoAAsANRMBAQ0DCwMGAAAAAAAAAAAjBAkAAgsDBgAAAAAAAAAAJiIEEAAG
AAAAAAAAAAApBgAAAAAAAAAADQEMAwYBAAAAAAAAABkNAgsBCwIlBCcACwALAQsCEwcBDAEGAQAAAAAA
AAAYDQEMAgYBAAAAAAAAABkNAgUWAAIJAQADAg4ADgETCAEPARMKASIECwALAA4BEwUBEwMBBQIADAET
BgECCgEAAgMFAAwAEwEBBgAAAAAAAAAAIwILAQACBAUADAAMARMCARYCDAEAAwUGAAwCDAAMARMEARcC
DQEAAwYYAAYAAAAAAAAAAA0DCwATAQENAgsDCwIlBBYACwELAAsDEwIBIwQRAAkCDAMGAQAAAAAAAAAY
DQMFBQAKAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIQAAAADL3hwo2r0BusoAqlfY+xzo
k/X4I9AFv0bhIc8Qw/Ys2gEAAAAnAgAATElCUkFWTQoBAAsBbgAAAAIAAAACcAAAAAgAAAADeAAAABUA
AAAMjQAAAAYAAAANkwAAACMAAAAOtgAAACQAAAAF2gAAAJkAAAAEcwEAACAAAAAJkwEAAAgAAAAKmwEA
AAwAAAALpwEAAIAAAAAAAAABAgAAAgEAAAMAAAQBAAUCAAYCAAcCAAgDAAkEAQgBBwAAAgEBAQQAAgEH
AAABBAACAQgBBQcAAAACAAMICAgAAgABCAADAQQDAAMCBAUHAQADAQUHAAADAwgICAMECAYHAQAGBwAA
BggPVmFsaWRhdG9yQ29uZmlnBkNvbmZpZwFUA2hhcwZjb25maWcQY29uc2Vuc3VzX3B1YmtleRduZXR3
b3JrX2lkZW50aXR5X3B1YmtleRZuZXR3b3JrX3NpZ25pbmdfcHVia2V5HHJlZ2lzdGVyX2NhbmRpZGF0
ZV92YWxpZGF0b3IXcm90YXRlX2NvbnNlbnN1c19wdWJrZXkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAACAwABAgEDAAUAAAYAAAcAAQQBAAEAAQADAAwALgEBAgEBAQEBAgcADAAwAQENAQwBEQMW
AgIBAAEDBAAMABEAFgIDAQABAwQADAARARYCBAEAAQMEAAwAEQIWAgUBAAMEBwAMAgwBDAAUAAEUAQEy
AQECBgEBAQIFDQAtLwEBDQEMARADDQIMAhAADQMMAAwDFwIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAACEAAAAAifUNtZxXfIofJSx5dnzuN0nvv0FJXcLZdG/PEaY7UzQBAAAAowkAAExJQlJBVk0K
AQALAW4AAAAEAAAAAnIAAAANAAAAA38AAAAYAAAADJcAAAAJAAAADaAAAAA6AAAADtoAAAAgAAAABfoA
AAC5AAAABLMBAABAAAAACfMBAAAIAAAACvsBAAAMAAAACwcCAACcBwAAAAAAAQACAgAAAwEAAQMCAQEA
BAAABQEABgIABwMACAMBDQQBDgUBDwYBAgEHAgEHAAACAAAAAgEHAAACAgIAAgEBAAACAQIAAAIBBwIB
CQAAAQECAAIGBwIBCQAJAAEBAgECAQUHAgEJAAEBAwIHAgEHAAAHAgEHAAADAQcAAAMAAwICAgMCBQcB
AAILR2FzU2NoZWR1bGUGVmVjdG9yBENvc3QBVAppbml0aWFsaXplCG5ld19jb3N0GWhhc19nYXNfc2No
ZWR1bGVfcmVzb3VyY2UWaW5zdHJ1Y3Rpb25fdGFibGVfc2l6ZRFuYXRpdmVfdGFibGVfc2l6ZQNjcHUH
c3RvcmFnZRRpbnN0cnVjdGlvbl9zY2hlZHVsZQ9uYXRpdmVfc2NoZWR1bGUFZW1wdHkJcHVzaF9iYWNr
Bmxlbmd0aAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAApVDBgAAgIAAQICAgAJAAAKAAELAQEMAQAAAEgAbgEtBwEjIgQHAAYAAAAAAAAAACkTBQEN
ABMFAQ0BDgAGGwAAAAAAAAAGAQAAAAAAAAAUAAITBgEOAAYcAAAAAAAAAAYBAAAAAAAAABQAAhMGAQ4A
Bh8AAAAAAAAABgEAAAAAAAAAFAACEwYBDgAGHQAAAAAAAAAGAQAAAAAAAAAUAAITBgEOAAYKAAAAAAAA
AAYBAAAAAAAAABQAAhMGAQ4ABh0AAAAAAAAABgEAAAAAAAAAFAACEwYBDgAGJAAAAAAAAAAGAQAAAAAA
AAAUAAITBgEOAAY0AAAAAAAAAAYBAAAAAAAAABQAAhMGAQ4ABh0AAAAAAAAABgEAAAAAAAAAFAACEwYB
DgAGHgAAAAAAAAAGAQAAAAAAAAAUAAITBgEOAAYpAAAAAAAAAAYBAAAAAAAAABQAAhMGAQ4ABikAAAAA
AAAABgEAAAAAAAAAFAACEwYBDgAGHAAAAAAAAAAGAQAAAAAAAAAUAAITBgEOAAYtAAAAAAAAAAYBAAAA
AAAAABQAAhMGAQ4ABi0AAAAAAAAABgEAAAAAAAAAFAACEwYBDgAGOgAAAAAAAAAGAQAAAAAAAAAUAAIT
BgEOAAY6AAAAAAAAAAYBAAAAAAAAABQAAhMGAQ4ABjgAAAAAAAAABgEAAAAAAAAAFAACEwYBDgAGxQAA
AAAAAAAGAQAAAAAAAAAUAAITBgEOAAZJAAAAAAAAAAYBAAAAAAAAABQAAhMGAQ4ABl4AAAAAAAAABgEA
AAAAAAAAFAACEwYBDgAGMwAAAAAAAAAGAQAAAAAAAAAUAAITBgEOAAZBAAAAAAAAAAYBAAAAAAAAABQA
AhMGAQ4ABi0AAAAAAAAABgEAAAAAAAAAFAACEwYBDgAGLAAAAAAAAAAGAQAAAAAAAAAUAAITBgEOAAYp
AAAAAAAAAAYBAAAAAAAAABQAAhMGAQ4ABioAAAAAAAAABgEAAAAAAAAAFAACEwYBDgAGKQAAAAAAAAAG
AQAAAAAAAAAUAAITBgEOAAYtAAAAAAAAAAYBAAAAAAAAABQAAhMGAQ4ABiwAAAAAAAAABgEAAAAAAAAA
FAACEwYBDgAGLgAAAAAAAAAGAQAAAAAAAAAUAAITBgEOAAYrAAAAAAAAAAYBAAAAAAAAABQAAhMGAQ4A
BjEAAAAAAAAABgEAAAAAAAAAFAACEwYBDgAGIwAAAAAAAAAGAQAAAAAAAAAUAAITBgEOAAYwAAAAAAAA
AAYBAAAAAAAAABQAAhMGAQ4ABjMAAAAAAAAABgEAAAAAAAAAFAACEwYBDgAGMQAAAAAAAAAGAQAAAAAA
AAAUAAITBgEOAAYuAAAAAAAAAAYBAAAAAAAAABQAAhMGAQ4ABi8AAAAAAAAABgEAAAAAAAAAFAACEwYB
DgAGLgAAAAAAAAAGAQAAAAAAAAAUAAITBgEOAAYnAAAAAAAAAAYBAAAAAAAAABQAAhMGAQ4ABh0AAAAA
AAAABgEAAAAAAAAAFAACEwYBDgAGIgAAAAAAAAAGAQAAAAAAAAAUAAITBgEOAAYgAAAAAAAAAAYBAAAA
AAAAABQAAhMGAQ4ABh4AAAAAAAAABgEAAAAAAAAAFAACEwYBDgAGWAMAAAAAAAAGAQAAAAAAAAAUAAIT
BgEOAAahAwAAAAAAAAYBAAAAAAAAABQAAhMGAQ4ABqEDAAAAAAAABgEAAAAAAAAAFAACEwYBDgAGlQMA
AAAAAAAGAQAAAAAAAAAUAAITBgEOAAYGAwAAAAAAAAYBAAAAAAAAABQAAhMGAQ4ABh0AAAAAAAAABgEA
AAAAAAAAFAACEwYBDgAGKQAAAAAAAAAGAQAAAAAAAAAUAAITBgEOAAYKAAAAAAAAAAYBAAAAAAAAABQA
AhMGAQ4BBh4AAAAAAAAABgEAAAAAAAAAFAACEwYBDgEGHgAAAAAAAAAGAQAAAAAAAAAUAAITBgEOAQYe
AAAAAAAAAAYBAAAAAAAAABQAAhMGAQ4BBh4AAAAAAAAABgEAAAAAAAAAFAACEwYBDgEGHgAAAAAAAAAG
AQAAAAAAAAAUAAITBgEOAQYeAAAAAAAAAAYBAAAAAAAAABQAAhMGAQ4BBh4AAAAAAAAABgEAAAAAAAAA
FAACEwYBDgEGHgAAAAAAAAAGAQAAAAAAAAAUAAITBgEOAQYeAAAAAAAAAAYBAAAAAAAAABQAAhMGAQ4B
Bh4AAAAAAAAABgEAAAAAAAAAFAACEwYBDgEGHgAAAAAAAAAGAQAAAAAAAAAUAAITBgEOAQYeAAAAAAAA
AAYBAAAAAAAAABQAAhMGAQ4BBh4AAAAAAAAABgEAAAAAAAAAFAACEwYBDgEGHgAAAAAAAAAGAQAAAAAA
AAAUAAITBgEOAQYeAAAAAAAAAAYBAAAAAAAAABQAAhMGAQ4BBh4AAAAAAAAABgEAAAAAAAAAFAACEwYB
DgEGHgAAAAAAAAAGAQAAAAAAAAAUAAITBgEMAAwBFAECMgECAgEBAAIDBAAMAAwBFAACAgIBAAECAwAH
AS4BAgIDAQEBAQQJAAcBMAECDQAMABECEwcBDQEMAQIEAQEBAQQJAAcBMAECDQAMABEDEwcBDQEMAQIA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACEAAAAAGbAcLPPCFgpD5NytcOPl0YFRzDjeeh0Q
Z8YDG/oK5NkBAAAA1g0AAExJQlJBVk0KAQAMAXcAAAAMAAAAAoMAAAAhAAAAA6QAAACHAAAADCsBAAAe
AAAADUkBAAA2AQAADn8CAAAQAQAABY8DAACdBAAABCwIAABAAAAAB2wIAAABAAAACW0IAAAcAAAACokI
AAA5AAAAC8IIAAAUBQAAAAAAAQACAAMABAAFAAYBAAAHAQAACAEAAAkCAAAKAgAACwEAAAwBAQIBBgEA
AA0AAA4BAA8CABADABEEABIFABMGABQHABUIABYJABcDABgKABkLABoMABsNABwOAB0PAB4DAB8QACAR
ACESACIRACMSACQTACUTACYUACcVACgTACkWACoWACsXACwYAC0ZAC4aAC8bADAcBDwdAT0eAT4fAQ4g
AT8FAUAhAkEiA0IjBUMkAQgBBwcAAQEBBwYBBwQAAQcGAQcDAAECAQcFAAEEAgEHAAABBAACAAIEBwcA
AAIAAwQHBwAIAAIAAgQCAAIBBwcAAgYHAAACAAIBBwcAAQIAAgEHBwACBQcBAAIAAgEHAQAAAAIAAQcB
AAACAAMEAggAAgACBgcAAAgAAgABCAACAAIFBwIACAACAQcCAAAAAgABBwIAAAIAAQQAAgACBAcAAAAC
AQIBBQcAAAACAQIBBAACAQEBBAACAQUEAQUHAQAAAgEFBAEFBwIAAAIAAAACAQgCBgcFAAQAAgEHBgEJ
AAIGBwUABAECAgEHBgEJAAABAgIAAgYHBgEJAAkAAQICAAMIAgkAAQICAAEHBgEJAAECAgEIAQQAAgEH
BwAAAAIBAgEFBwcAAAIAAgYHBwAHBwAAAgEHBwACBgcHAAIAAgEIAQgAAgEIAQIAAgEIAggIAAMGBAcH
AAcFAAcGAQcDAAcGAQcEAAgDAAMBBwMAAwEHBAADAgQHBwADBwQHBwAIAgYHAAAEBgcAAAMCBAIDAwYH
AAACBwcAAwICBgcAAAMDBQcBAAIGBwAAAwMEBgcAAAYBAwMHAQAEBgcAAAMDBAIIAwIGBwAACAMCCAYH
AAADAgUHAgAIAwIEBgEDAwcCAAQGBwAAAwIEBwUAAwIFBwAAAgMBBAMBBQcAAAMBBQcBAAMBBQcCAAMM
BAEGBwAABQcAAAgIAgICAgICAwsEBgcAAAYHAAAFBwAAAgICAgIHBwACAwYGBwUABAYCCAgIAwIGBwUA
BAMBCQADBAYHBgEJAAkABgIIAwMHBgEJAAgCDExpYnJhQWNjb3VudAlMaWJyYUNvaW4ESGFzaAdVNjRV
dGlsC0FkZHJlc3NVdGlsDUJ5dGVhcnJheVV0aWwBVBRXaXRoZHJhd2FsQ2FwYWJpbGl0eRVLZXlSb3Rh
dGlvbkNhcGFiaWxpdHkQU2VudFBheW1lbnRFdmVudBRSZWNlaXZlZFBheW1lbnRFdmVudBRFdmVudEhh
bmRsZUdlbmVyYXRvcgtFdmVudEhhbmRsZQRtYWtlB2RlcG9zaXQVZGVwb3NpdF93aXRoX21ldGFkYXRh
D21pbnRfdG9fYWRkcmVzcxV3aXRoZHJhd19mcm9tX2FjY291bnQUd2l0aGRyYXdfZnJvbV9zZW5kZXIY
d2l0aGRyYXdfd2l0aF9jYXBhYmlsaXR5JGV4dHJhY3Rfc2VuZGVyX3dpdGhkcmF3YWxfY2FwYWJpbGl0
eR1yZXN0b3JlX3dpdGhkcmF3YWxfY2FwYWJpbGl0eR1wYXlfZnJvbV9zZW5kZXJfd2l0aF9tZXRhZGF0
YQ9wYXlfZnJvbV9zZW5kZXIlcm90YXRlX2F1dGhlbnRpY2F0aW9uX2tleV9mb3JfYWNjb3VudBlyb3Rh
dGVfYXV0aGVudGljYXRpb25fa2V5KXJvdGF0ZV9hdXRoZW50aWNhdGlvbl9rZXlfd2l0aF9jYXBhYmls
aXR5JmV4dHJhY3Rfc2VuZGVyX2tleV9yb3RhdGlvbl9jYXBhYmlsaXR5H3Jlc3RvcmVfa2V5X3JvdGF0
aW9uX2NhcGFiaWxpdHkOY3JlYXRlX2FjY291bnQSY3JlYXRlX25ld19hY2NvdW50DHNhdmVfYWNjb3Vu
dBNiYWxhbmNlX2Zvcl9hY2NvdW50B2JhbGFuY2Ubc2VxdWVuY2VfbnVtYmVyX2Zvcl9hY2NvdW50D3Nl
cXVlbmNlX251bWJlciFkZWxlZ2F0ZWRfa2V5X3JvdGF0aW9uX2NhcGFiaWxpdHkfZGVsZWdhdGVkX3dp
dGhkcmF3YWxfY2FwYWJpbGl0eR13aXRoZHJhd2FsX2NhcGFiaWxpdHlfYWRkcmVzcx9rZXlfcm90YXRp
b25fY2FwYWJpbGl0eV9hZGRyZXNzBmV4aXN0cwhwcm9sb2d1ZQhlcGlsb2d1ZQpmcmVzaF9ndWlkFW5l
d19ldmVudF9oYW5kbGVfaW1wbBBuZXdfZXZlbnRfaGFuZGxlCmVtaXRfZXZlbnQUd3JpdGVfdG9fZXZl
bnRfc3RvcmUOZGVzdHJveV9oYW5kbGUSYXV0aGVudGljYXRpb25fa2V5D3JlY2VpdmVkX2V2ZW50cwtz
ZW50X2V2ZW50cw9ldmVudF9nZW5lcmF0b3IPYWNjb3VudF9hZGRyZXNzBmFtb3VudAVwYXllZQhtZXRh
ZGF0YQVwYXllcgdjb3VudGVyBGd1aWQQYWRkcmVzc190b19ieXRlcwR6ZXJvBXZhbHVlHG1pbnRfd2l0
aF9kZWZhdWx0X2NhcGFiaWxpdHkId2l0aGRyYXcIc2hhM18yNTYMdTY0X3RvX2J5dGVzEGJ5dGVhcnJh
eV9jb25jYXQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAA/uAAACCAABAgEIAgIBCQMCAwoEAgMNBQIBEAYCAhEAMQAAIQEAJAIAJQIAMgMAMwQA
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
BQkAAgUHBAEJAAIBAQIAAgYHBAEJAAkAAQECAQcGAAEEAAIBCAEFBwYAAAIBBgkAAgYHBAEJAAIBAQIA
AgYHBQEJAAkAAQIDAAMBBwAAAwEHAQADAQUHAAADBAIICAQDBQIICAQGBwMAAwEFBwIAAwUEAgIFBwQB
BwAABQcAAAMCBAYHAgADBgYHAAAICAgHBgABAwYGBwIABgcEAQcAAAYHAAACAgEDAgIFBwQBBwAAAwUC
AgUHAgAEBQcAAAtMaWJyYVN5c3RlbQxMaWJyYUFjY291bnQPVmFsaWRhdG9yQ29uZmlnBlZlY3Rvcg1W
YWxpZGF0b3JJbmZvF1ZhbGlkYXRvclNldENoYW5nZUV2ZW50DFZhbGlkYXRvclNldA1CbG9ja01ldGFk
YXRhGGluaXRpYWxpemVfdmFsaWRhdG9yX3NldBlpbml0aWFsaXplX2Jsb2NrX21ldGFkYXRhFGdldF9j
b25zZW5zdXNfcHVia2V5GmdldF9jb25zZW5zdXNfdm90aW5nX3Bvd2VyGmdldF9uZXR3b3JrX3NpZ25p
bmdfcHVia2V5G2dldF9uZXR3b3JrX2lkZW50aXR5X3B1YmtleQ5ibG9ja19wcm9sb2d1ZRZwcm9jZXNz
X2Jsb2NrX3Byb2xvZ3VlGGdldF9jdXJyZW50X2Jsb2NrX2hlaWdodBRnZXRfY3VycmVudF9ibG9ja19p
ZBVnZXRfY3VycmVudF90aW1lc3RhbXAUZ2V0X2N1cnJlbnRfcHJvcG9zZXISdmFsaWRhdG9yX3NldF9z
aXplDGlzX3ZhbGlkYXRvcg1hZGRfdmFsaWRhdG9yE2NvcHlfdmFsaWRhdG9yX2luZm8LcmVjb25maWd1
cmUWZ2V0X2l0aF92YWxpZGF0b3JfaW5mbxlnZXRfaXRoX3ZhbGlkYXRvcl9hZGRyZXNzBGFkZHIQY29u
c2Vuc3VzX3B1YmtleRZjb25zZW5zdXNfdm90aW5nX3Bvd2VyFm5ldHdvcmtfc2lnbmluZ19wdWJrZXkX
bmV0d29ya19pZGVudGl0eV9wdWJrZXkRbmV3X3ZhbGlkYXRvcl9zZXQBVAp2YWxpZGF0b3JzDWNoYW5n
ZV9ldmVudHMLRXZlbnRIYW5kbGUGaGVpZ2h0CXRpbWVzdGFtcAJpZAhwcm9wb3NlcgVlbXB0eRBuZXdf
ZXZlbnRfaGFuZGxlBmxlbmd0aAZib3Jyb3cDaGFzCXB1c2hfYmFjawZDb25maWcGY29uZmlnCmJvcnJv
d19tdXQKZW1pdF9ldmVudAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAdgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAClUMGAAAAgUAAQIB
BQICAgYDAgQIABsAABwBAB0CAB4BAB8BASADAiIDAiMEAyUCAyYCAycBAygAAAEAAgAMAC0HASMiBAcA
BgEAAAAAAAAAKRMTARMUAhQCADICAAIBAQAEAA4ALQcCIyIEBwAGAQAAAAAAAAApBgAAAAAAAAAABgAA
AAAAAAAAEgAHAhQDADIDAAICAQABAwMADAARAQIDAQABAwMADAARAgIEAQABAwMADAARAwIFAQABAwMA
DAARBAIGAQIDAgQEBwAMAAwBDAIMAxMHABMQAAIHAAIDAgIFJwAHAi8DAA0ECwALBBEJFiYiBAwABokT
AAAAAAAAKQsDEw0AIgQSAAaKEwAAAAAAACkMAQsEEAoXDAALBBAJFwwDCwQQCxcLBBEIFgYBAAAAAAAA
ABgMBBAIFwIIAQEDAQAFAAcCMAMAEQgWAgkBAQMBAAUABwIwAwARChYCCgEBAwEABQAHAjADABEJFgIL
AQEDAQAFAAcCMAMAEQsWAgwBAQIBBgcABwEwAgANAAwAEQYTFQECDQEBAgQHKwAHATACABEGDQMLAxMV
AQ0BCwEGAAAAAAAAAAAjBA0ACgIGAAAAAAAAAAANAgsDCwITFgENBAsEEQAWCwAjBBsACQILAgYBAAAA
AAAAABgNAgsCCwEoBCQABSkACwMLAhMWAQ0EBRMACgIOAAECBggTAAsAExcAIgQGAAYRAAAAAAAAACkH
AS8CAA0BDAEQBgwAEgAGAQAAAAAAAAASABIAFAAAExgBAg8BAAIJMwALABEAFhMZAA0EDwQTGgANAQ8E
ExsADQIPBBMcAA0DCg0FDwELABEBJAQbAAwBCwAQARcJDQUPAgsAEQMkBCYADAILABADFwkNBQ8DCwAR
BCQEMQAMAwsAEAQXCQ0FDAUCEAABAgIKLwAHAS8CAA0ACwAQBg0BBgAAAAAAAAAADQMLATUTFQENBAoN
BQsBCwMTHQENAgwCEw8ABBcACQ0FDAMGAQAAAAAAAAAYDQMLAwsEKAQgAAUlAAsBCwMTHQENAgUSAAwF
BC4ADAAQBwwBFhQBABMeAgICEQEBAgILEQAHATACABEGDQELAAsBExUBJSIEDAAGAwAAAAAAAAApCwEM
ABMWARYCEgEBAgIMGQAHATACAA0CCwIRBhMVAQ0BCwAMASUiBA4ABgMAAAAAAAAAKQwCEQYMABMWAQ0E
DAQRABYNAwwDAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIQAAAACS0h6lX+tt2JBxM9RH
xKFpeEogH1FdsgR/bU/9KzPCUQEAAAADBAAATElCUkFWTQoBAAsBbgAAAAgAAAACdgAAAAwAAAADggAA
ACcAAAAMqQAAAAYAAAANrwAAAF4AAAAODQEAAB8AAAAFLAEAAIEBAAAErQIAAEAAAAAJ7QIAAAQAAAAK
8QIAAAYAAAAL9wIAAAwBAAAAAAABAAIAAwAEAQABBAEAAwsBAAAFAAAGAAAHAQAIAgIMAwMNBAMOBQMP
BgIQBwERCAMSCQETCgEUCwECAQcCAAIAAAACAAMHAQACAgACAQICAgIAAgECAAACAQIBBAACAQcBAAIF
BwIAAgACAQcCAAAAAgEEAQIAAgIHAQAHAQACBwEAAgACAAIEBwEAAAIBAgEFBwEAAAIAAQcBAAADBQIC
BwEAAgYHAAADAAMGBwEAAgICBAcBAAMDAgICGlRyYW5zYWN0aW9uRmVlRGlzdHJpYnV0aW9uCUxpYnJh
Q29pbgtMaWJyYVN5c3RlbQxMaWJyYUFjY291bnQBVBtkaXN0cmlidXRlX3RyYW5zYWN0aW9uX2ZlZXMK
aW5pdGlhbGl6ZSRkaXN0cmlidXRlX3RyYW5zYWN0aW9uX2ZlZXNfaW50ZXJuYWwhcGVyX3ZhbGlkYXRv
cl9kaXN0cmlidXRpb25fYW1vdW50D2xhc3RfZXBvY2hfcGFpZBlmZWVfd2l0aGRyYXdhbF9jYXBhYmls
aXR5FFdpdGhkcmF3YWxDYXBhYmlsaXR5EnZhbGlkYXRvcl9zZXRfc2l6ZQdiYWxhbmNlGHdpdGhkcmF3
X3dpdGhfY2FwYWJpbGl0eSRleHRyYWN0X3NlbmRlcl93aXRoZHJhd2FsX2NhcGFiaWxpdHkZZ2V0X2l0
aF92YWxpZGF0b3JfYWRkcmVzcwVzcGxpdAdkZXBvc2l0BXZhbHVlDGRlc3Ryb3lfemVybwAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD+4AAgIA
AAkAAAoBAAEBAAMAFgAHAS8AAQ0EEwQBDQAHARMFAQ0DDAQRAQsDEwYBDQILAwsAEwMBDQEMAgwBDAAT
AgECAQEAAgEMAC0HASMiBAcABgAAAAAAAAAAKQYAAAAAAAAAABMHARQAATIAAQICAAADAiIABgAAAAAA
AAAADQMLAwsCJQQWAAsDEwgBDQQLAwYBAAAAAAAAABgNAwwACwETCQENBQ0ADAQMBRMKAQUCAA8AEwsB
BgAAAAAAAAAAIwQeAAwAEwwBBSEABwEMABMKAQIDAAACAxYACwEGAAAAAAAAAAAkIgQHAAYAAAAAAAAA
ACkLAAsBHA0CCwIMARoMACciBBQABgEAAAAAAAAAKQwCAgMAAAAoAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAClUMGAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAADAAAAExpYnJhQWNjb3VudBAAAABTZW50UGF5bWVudEV2ZW50AAAAACwAAAAAypo7AAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKVQwYAAAAACgAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAKVQwYAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAMAAAATGlicmFBY2NvdW50FAAAAFJlY2VpdmVkUGF5bWVudEV2ZW50AAAAACwAAAAAypo7AAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKVQwYAAAAACgAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAHYAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAALAAAATGlicmFTeXN0ZW0XAAAAVmFsaWRhdG9yU2V0Q2hhbmdlRXZlbnQAAAAAzAUAAAoAAAAO5kn0
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
</pre>
### Appendix C: Error Codes
TKTK
### Appendix D: Hash Prefixes
| prefix name | prefix value |
|----------------------|---------------------------------------------------|
| PREFIX_CONTRACT | libra_types::contract_event::ContractEvent |
| PREFIX_MODULE | libra_types::language_storage::ModuleId |
| PREFIX_STRUCT | libra_types::language_storage::StructTag |
| PREFIX_LEDGER | libra_types::ledger_info::LedgerInfo |
| PREFIX_ADDRESS | libra_types::account_address:AccountAddress |
| PREFIX_STATE | libra_types::account_state_blob::AccountStateBlob |
| PREFIX_RAW_TX | libra_types::transaction::RawTransaction |
| PREFIX_RAW_SIGNED_TX | libra_types::transaction::SignedTransaction |
| PREFIX_TX_INFO | libra_types::transaction::TransactionInfo |
| PREFIX_TX | libra_types::transaction::Transaction |
| PREFIX_VOTE_DATA | consensus_types::vote_data::VoteData |
| PREFIX_BLOCK_DATA | consensus_types::block_data::BlockData |
| PREFIX_TIMEOUT | consensus_types::timeout::Timeout |
| PREFIX_BLOCK_DATA | consensus_types::block_data::BlockData |
| PREFIX_SPARSE_NODE | jellyfish_merkle::node_type::InternalNode |
| PREFIX_SPARSE_LEAF | libra_types::proof::SparseMerkleLeafNode |
| PREFIX | jellyfish_merkle::node_type::LeafNode |
### Appendix E: Constants
GENESIS_BLOCK_ID: [0x5e, 0x10, 0xba, 0xd4, 0x5b, 0x35, 0xed, 0x92, 0x9c, 0xd6, 0xd2, 0xc7, 0x09, 0x8b, 0x13, 0x5d, 0x02, 0xdd, 0x25, 0x9a, 0xe8, 0x8a, 0x8d, 0x09, 0xf4, 0xeb, 0x5f, 0xba, 0xe9, 0xa6, 0xf6, 0xe4]
ACCUMULATOR_PLACEHOLDER_HASH: [b"ACCUMULATOR_PLACEHOLDER_HASH"] + [0u8; 4]
SPARSE_MERKLE_PLACEHOLDER_HASH: [b"SPARSE_MERKLE_PLACEHOLDER_HASH"] + [0u8; 2]
PRE_GENESIS_BLOCK_ID: [b"PRE_GENESIS_BLOCK_ID"] + [0u8; 12]
An empty `BlockInfo`:
<pre class="rust">
BlockInfo {
epoch: 0,
round: 0,
id: [0u8; 32],
executed_state_id: [0u8; 32],
version: 0,
timestamp_usecs: 0,
next_validator_set: None,
}
</pre>
## Changelog
TKTK
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment