Created
December 5, 2019 23:10
Star
You must be signed in to star a gist
LBFT spec
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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 | |
IwUANAYBNQcCNQcDNgUDNwcDOAAENgUEOQcEOAAFOgUGOgUGOwAAAAAIABoACwATJAENBQYAAAAAAAAA | |
ABQFAQ0CDgILABMfAg0DDgIMABMfAw0EEyUBDQEMBQwBCgoMBAwDBgAAAAAAAAAADAIUAAECAQEBAAME | |
BQAMAAwBEgATAgECAgEBAAYFJQAPARMmAQ0DCwMGAAAAAAAAAAAmIgQKAAYHAAAAAAAAACktDQULBS8A | |
AQ0GDAYQBQsDCwALAhQDARMhAgwALwABDQQLBBABDAETJwEMBBAEDAMMBQwCFAQBEyEDAgMBAQADBgsA | |
CwAuAAEiBAYACwATEAEMAAwBEygBEwEBAgQAAAIHBwAMABABCwETKQENAgwCAgUBAQACCA0ALS8AAQ0B | |
CwERAxYECQAGCwAAAAAAAAApDAEMABMEAQIGAQEAAgkJAAwAEQgWLwABDQIMAgwBEwQBAgcBAQACChMA | |
LQ0ACwAvAAENAQwBEAMNAgsCFgQNAAYLAAAAAAAAACkJDAIXDAAUAQECCAEBAAILCwAMABUBAQ0BDAEv | |
AAENAgoMAhADFwIJAQEABAwMAAsALgABIgQGAAsAExABDAAMARMFAQwCEwIBAgoBAQADBgUADAAMARIA | |
EwkBAgsAAAINBQAMAQwAEAAXAgwBAQACDg0ALS8AAQ0BCwERAhYECQAGCwAAAAAAAAApDAEMABMLAQIN | |
AQEAAg8HAAwAEQkWLwABDAETCwECDgEBAAIQEQAtDQALAC8AARACDQELARYECwAGCwAAAAAAAAApCQwB | |
FwwAFAIBAg8BAQACEQsADAAVAgENAQwBLwABDQIKDAIQAhcCEAEACRIUAAYAAAAAAAAAABQFAQ0BCwAL | |
ABMkARMlAQoKDgELABMfAw4BDAATHwIGAAAAAAAAAAAMARQAARMSAQIRAQEAAwYKAAsAExABCwEGAAAA | |
AAAAAAAmBAkADAAMARMKAQISAgAAAAAAEwAAARMGAAwAEQETJgENAQwBAhQBAQABFAQADAAwAAETEwEC | |
FQAAARUEAAwAEQYWAhYBAQABFAQADAAwAAETFQECFwEBAAEUBQAMADAAARECFgIYAQEAARQFAAwAMAAB | |
EQMWAhkBAAEWAwAMABEIAhoBAAEXAwAMABEJAhsBAAEUAwAMAC4AAQIcAAEAAhhFAC0NAAsALgABDQEM | |
ASIECgAGBQAAAAAAAAApCwAvAAENAjQNBAwEEyoBDQUMBQsCEQAWIyIEGwAGAgAAAAAAAAApKg0GKw0H | |
DAYMBxoNCAsCNQ0DDAMTEwENCQwJDAgoIgQwAAYGAAAAAAAAACkMAhAGFg0KMw0LCwsLCigiBD0ABgMA | |
AAAAAAAAKQwLDAojIgREAAYEAAAAAAAAACkCHQABAAMZMgAtDQALAC8AAQ0BKg0EKw0GLA0FDAQMBgwF | |
GRoNBwsBNQ0DDAMTEwENCAwICwcoIgQeAAYGAAAAAAAAACkLAQwHEwQBDQkzDQoMCgYBAAAAAAAAABgM | |
ARAGFwcBLwABDQIMAhABDAkTJwECHgAAAhoWAAwAEBANAgwBEyQBDQULAhYTKwENAwsCFgYBAAAAAAAA | |
ABgMAhcMAwwFEywBDQQMBAIfAAADGwYABgAAAAAAAAAADAAMARMeARQGHAIgAQEAAg0IAC0vAAENAAwA | |
EActEx8cAiEBAAMdEwALABESFg0DDAAQEQ0CDAMLAhYMARMiHAsCFgYBAAAAAAAAABgMAhcCIgIAAAAA | |
ACMBAAEeBQAMABUGHA0BDQICAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAhAAAAAIuik4Gt | |
4PL1dnwa4dd9RJMdmDeQpbcelrucZbqEDSZoAQAAAEcJAABMSUJSQVZNCgEADAF3AAAACAAAAAJ/AAAA | |
HgAAAAOdAAAAXQAAAAz6AAAAFAAAAA0OAQAAugAAAA7IAQAAdwAAAAU/AgAAEgMAAARRBQAAYAAAAAex | |
BQAAAQAAAAmyBQAAEAAAAArCBQAAJAAAAAvmBQAAYQMAAAAAAAEAAgADAAQCAAAFAgAABgEAAAcBAAMh | |
AgEBASQBAQICLwIAAAgAAAkAAAoBAAsCAAwBAA0BAA4DAA8DABAEABEFABIEABMGABQEABUHABYIABcJ | |
ABgAABkKABoLAykMASoNAysOAywPAi0HAy4QAjARAhwSAh4SAh8SAzETATIUAQQBCAECAQcEAQcAAAEH | |
BQEHAQACAAAAAgEFCAEFBwAAAAIBBQIBBQcAAAACAAQCCAgEAAIBAgAAAgEIAAACAQQAAAIBAQEEAAIA | |
AQQAAgEBAQYHAAAAAgEHAAABAgACAQQBAgACAQcEAQkAAAEBAgEHBQEJAAABAgIBAgEFBwQBCQABAQIB | |
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