Move docs from book/ to docs/ (#8469)

automerge
This commit is contained in:
Justin Starry
2020-02-26 23:11:38 +08:00
committed by GitHub
parent 8839dbfe5b
commit 021d0a46f8
140 changed files with 56 additions and 58 deletions

View File

@ -0,0 +1,3 @@
# Accepted Design Proposals
The following architectural proposals have been accepted by the Solana team, but are not yet fully implemented. The proposals may be implemented as described, implemented differently as issues in the designs become evident, or not implemented at all. If implemented, the proposal will be moved to [Implemented Proposals](../implemented-proposals/README.md) and the details will be added to relevant sections of the docs.

View File

@ -0,0 +1,90 @@
# Solana ABI management process
This document proposes the Solana ABI management process. The ABI management
process is an engineering practice and a supporting technical framework to avoid
introducing unintended incompatible ABI changes.
# Problem
The Solana ABI (binary interface to the cluster) is currently only defined
implicitly by the implementation and requires a very careful eye to notice
breaking changes. This makes it extremely difficult to upgrade the software
on an existing cluster without rebooting the ledger.
# Requirements and objectives
- Unintended ABI changes can be detected as CI failures mechanically.
- Newer implementation must be able to process the oldest data (since genesis)
once we go mainnet.
- The objective of this proposal is to protect the ABI while sustaining rather
rapid development by opting for a mechanical process rather than a very long
human-driven auditing process.
- Once signed cryptographically, data blob must be identical, so no
in-place data format update is possible regardless of inbound and outbound of
the online system. Also, considering the sheer volume of transactions we're
aiming to handle, retrospective in-place update is undesirable at best.
# Solution
Instead of natural human's eye due-diligence, which should be assumed to fail
regularly, we need a systematic assurance of not breaking the cluster when
changing the source code.
For that purpose, we introduce a mechanism of marking every ABI-related things
in source code (`struct`s, `enum`s) with the new `#[frozen_abi]` attribute. This
takes hard-coded digest value derived from types of its fields via
`ser::Serialize`. And the attribute automatically generates a unit test to try
to detect any unsanctioned changes to the marked ABI-related things.
However, the detection cannot be complete; no matter how hard we statically
analyze the source code, it's still possible to break ABI. For example, this
includes not-`derive`d hand-written `ser::Serialize`, underlying library's
implementation changes (for example `bincode`), CPU architecture differences.
The detection of these possible ABI incompatibilities is out-of-scope for this
ABI management.
# Definitions
ABI item/type: various types to be used for serialization, which collectively
comprises the whole ABI for any system components. For example, those types
include `struct`s and `enum`s.
ABI item digest: Some fixed hash derived from type information of ABI item's
fields.
# Example
```patch
+#[frozen_abi(digest="1c6a53e9")]
#[derive(Serialize, Default, Deserialize, Debug, PartialEq, Eq, Clone)]
pub struct Vote {
/// A stack of votes starting with the oldest vote
pub slots: Vec<Slot>,
/// signature of the bank's state at the last slot
pub hash: Hash,
}
```
# Developer's workflow
To know the digest for new ABI items, developers can add `frozen_abi` with a
random digest value and run the unit tests and replace it with the correct
digest from the assertion test error message.
In general, once we add `frozen_abi` and its change is published in the stable
release channel, its digest should never change. If such a change is needed, we
should opt for defining a new struct like `FooV1`. And special release flow like
hard forks should be approached.
# Implementation remarks
We use some degree of macro machinery to automatically generate unit tests
and calculate a digest from ABI items. This is doable by clever use of
`serde::Serialize` ([1]) and `any::typename` ([2]). For a precedent for similar
implementation, `ink` from the Parity Technologies [3] could be informational.
# References
1. [(De)Serialization with type info · Issue #1095 · serde-rs/serde](https://github.com/serde-rs/serde/issues/1095#issuecomment-345483479)
2. [`std::any::type_name` - Rust](https://doc.rust-lang.org/std/any/fn.type_name.html)
3. [Parity's ink to write smart contracts](https://github.com/paritytech/ink)

View File

@ -0,0 +1,55 @@
# Bankless Leader
A bankless leader does the minimum amount of work to produce a valid block. The leader is tasked with ingress transactions, sorting and filtering valid transactions, arranging them into entries, shredding the entries and broadcasting the shreds. While a validator only needs to reassemble the block and replay execution of well formed entries. The leader does 3x more memory operations before any bank execution than the validator per processed transaction.
## Rationale
Normal bank operation for a spend needs to do 2 loads and 2 stores. With this design leader just does 1 load. so 4x less account\_db work before generating the block. The store operations are likely to be more expensive than reads.
When replay stage starts processing the same transactions, it can assume that PoH is valid, and that all the entries are safe for parallel execution. The fee accounts that have been loaded to produce the block are likely to still be in memory, so the additional load should be warm and the cost is likely to be amortized.
## Fee Account
The [fee account](../terminology.md#fee_account) pays for the transaction to be included in the block. The leader only needs to validate that the fee account has the balance to pay for the fee.
## Balance Cache
For the duration of the leaders consecutive blocks, the leader maintains a temporary balance cache for all the processed fee accounts. The cache is a map of pubkeys to lamports.
At the start of the first block the balance cache is empty. At the end of the last block the cache is destroyed.
The balance cache lookups must reference the same base fork for the entire duration of the cache. At the block boundary, the cache can be reset along with the base fork after replay stage finishes verifying the previous block.
## Balance Check
Prior to the balance check, the leader validates all the signatures in the transaction.
1. Verify the accounts are not in use and BlockHash is valid.
2. Check if the fee account is present in the cache, or load the account from accounts\_db and store the lamport balance in the cache.
3. If the balance is less than the fee, drop the transaction.
4. Subtract the fee from the balance.
5. For all the keys in the transaction that are Credit-Debit and are referenced by an instruction, reduce their balance to 0 in the cache. The account fee is declared as Credit-Debit, but as long as it is not used in any instruction its balance will not be reduced to 0.
## Leader Replay
Leaders will need to replay their blocks as part of the standard replay stage operation.
## Leader Replay With Consecutive Blocks
A leader can be scheduled to produce multiple blocks in a row. In that scenario the leader is likely to be producing the next block while the replay stage for the first block is playing.
When the leader finishes the replay stage it can reset the balance cache by clearing it, and set a new fork as the base for the cache which can become active on the next block.
## Reseting the Balance Cache
1. At the start of the block, if the balance cache is uninitialized, set the base fork for the balance cache to be the parent of the block and create an empty cache.
2. if the cache is initialized, check if block's parents has a new frozen bank that is newer than the current base fork for the balance cache.
3. if a parent newer than the cache's base fork exist, reset the cache to the parent.
## Impact on Clients
The same fee account can be reused many times in the same block until it is used once as Credit-Debit by an instruction.
Clients that transmit a large number of transactions per second should use a dedicated fee account that is not used as Credit-Debit in any instruction.
Once an account fee is used as Credit-Debit, it will fail the balance check until the balance cache is reset.

View File

@ -0,0 +1,83 @@
# Block Confirmation
A validator votes on a PoH hash for two purposes. First, the vote indicates it
believes the ledger is valid up until that point in time. Second, since many
valid forks may exist at a given height, the vote also indicates exclusive
support for the fork. This document describes only the former. The latter is
described in [Tower BFT](tower-bft.md).
## Current Design
To start voting, a validator first registers an account to which it will send
its votes. It then sends votes to that account. The vote contains the tick
height of the block it is voting on. The account stores the 32 highest heights.
### Problems
* Only the validator knows how to find its own votes directly.
Other components, such as the one that calculates confirmation time, needs to
be baked into the validator code. The validator code queries the bank for all
accounts owned by the vote program.
* Voting ballots do not contain a PoH hash. The validator is only voting that
it has observed an arbitrary block at some height.
* Voting ballots do not contain a hash of the bank state. Without that hash,
there is no evidence that the validator executed the transactions and
verified there were no double spends.
## Proposed Design
### No Cross-block State Initially
At the moment a block is produced, the leader shall add a NewBlock transaction
to the ledger with a number of tokens that represents the validation reward.
It is effectively an incremental multisig transaction that sends tokens from
the mining pool to the validators. The account should allocate just enough
space to collect the votes required to achieve a supermajority. When a
validator observes the NewBlock transaction, it has the option to submit a vote
that includes a hash of its ledger state (the bank state). Once the account has
sufficient votes, the vote program should disperse the tokens to the
validators, which causes the account to be deleted.
#### Logging Confirmation Time
The bank will need to be aware of the vote program. After each transaction, it
should check if it is a vote transaction and if so, check the state of that
account. If the transaction caused the supermajority to be achieved, it should
log the time since the NewBlock transaction was submitted.
### Finality and Payouts
[Tower BFT](tower-bft.md) is the proposed fork selection algorithm. It proposes
that payment to miners be postponed until the *stack* of validator votes reaches
a certain depth, at which point rollback is not economically feasible. The vote
program may therefore implement Tower BFT. Vote instructions would need to
reference a global Tower account so that it can track cross-block state.
## Challenges
### On-chain voting
Using programs and accounts to implement this is a bit tedious. The hardest
part is figuring out how much space to allocate in NewBlock. The two variables
are the *active set* and the stakes of those validators. If we calculate the
active set at the time NewBlock is submitted, the number of validators to
allocate space for is known upfront. If, however, we allow new validators to
vote on old blocks, then we'd need a way to allocate space dynamically.
Similar in spirit, if the leader caches stakes at the time of NewBlock, the
vote program doesn't need to interact with the bank when it processes votes. If
we don't, then we have the option to allow stakes to float until a vote is
submitted. A validator could conceivably reference its own staking account, but
that'd be the current account value instead of the account value of the most
recently finalized bank state. The bank currently doesn't offer a means to
reference accounts from particular points in time.
### Voting Implications on Previous Blocks
Does a vote on one height imply a vote on all blocks of lower heights of
that fork? If it does, we'll need a way to lookup the accounts of all
blocks that haven't yet reached supermajority. If not, the validator could
send votes to all blocks explicitly to get the block rewards.

View File

@ -0,0 +1,102 @@
# Cluster Test Framework
This document proposes the Cluster Test Framework \(CTF\). CTF is a test harness that allows tests to execute against a local, in-process cluster or a deployed cluster.
## Motivation
The goal of CTF is to provide a framework for writing tests independent of where and how the cluster is deployed. Regressions can be captured in these tests and the tests can be run against deployed clusters to verify the deployment. The focus of these tests should be on cluster stability, consensus, fault tolerance, API stability.
Tests should verify a single bug or scenario, and should be written with the least amount of internal plumbing exposed to the test.
## Design Overview
Tests are provided an entry point, which is a `contact_info::ContactInfo` structure, and a keypair that has already been funded.
Each node in the cluster is configured with a `validator::ValidatorConfig` at boot time. At boot time this configuration specifies any extra cluster configuration required for the test. The cluster should boot with the configuration when it is run in-process or in a data center.
Once booted, the test will discover the cluster through a gossip entry point and configure any runtime behaviors via validator RPC.
## Test Interface
Each CTF test starts with an opaque entry point and a funded keypair. The test should not depend on how the cluster is deployed, and should be able to exercise all the cluster functionality through the publicly available interfaces.
```text
use crate::contact_info::ContactInfo;
use solana_sdk::signature::{Keypair, Signer};
pub fn test_this_behavior(
entry_point_info: &ContactInfo,
funding_keypair: &Keypair,
num_nodes: usize,
)
```
## Cluster Discovery
At test start, the cluster has already been established and is fully connected. The test can discover most of the available nodes over a few second.
```text
use crate::gossip_service::discover_nodes;
// Discover the cluster over a few seconds.
let cluster_nodes = discover_nodes(&entry_point_info, num_nodes);
```
## Cluster Configuration
To enable specific scenarios, the cluster needs to be booted with special configurations. These configurations can be captured in `validator::ValidatorConfig`.
For example:
```text
let mut validator_config = ValidatorConfig::default();
validator_config.rpc_config.enable_validator_exit = true;
let local = LocalCluster::new_with_config(
num_nodes,
10_000,
100,
&validator_config
);
```
## How to design a new test
For example, there is a bug that shows that the cluster fails when it is flooded with invalid advertised gossip nodes. Our gossip library and protocol may change, but the cluster still needs to stay resilient to floods of invalid advertised gossip nodes.
Configure the RPC service:
```text
let mut validator_config = ValidatorConfig::default();
validator_config.rpc_config.enable_rpc_gossip_push = true;
validator_config.rpc_config.enable_rpc_gossip_refresh_active_set = true;
```
Wire the RPCs and write a new test:
```text
pub fn test_large_invalid_gossip_nodes(
entry_point_info: &ContactInfo,
funding_keypair: &Keypair,
num_nodes: usize,
) {
let cluster = discover_nodes(&entry_point_info, num_nodes);
// Poison the cluster.
let client = create_client(entry_point_info.client_facing_addr(), VALIDATOR_PORT_RANGE);
for _ in 0..(num_nodes * 100) {
client.gossip_push(
cluster_info::invalid_contact_info()
);
}
sleep(Durration::from_millis(1000));
// Force refresh of the active set.
for node in &cluster {
let client = create_client(node.client_facing_addr(), VALIDATOR_PORT_RANGE);
client.gossip_refresh_active_set();
}
// Verify that spends still work.
verify_spends(&cluster);
}
```

View File

@ -0,0 +1,71 @@
# Cross-Program Invocation
## Problem
In today's implementation a client can create a transaction that modifies two accounts, each owned by a separate on-chain program:
```text
let message = Message::new(vec![
token_instruction::pay(&alice_pubkey),
acme_instruction::launch_missiles(&bob_pubkey),
]);
client.send_message(&[&alice_keypair, &bob_keypair], &message);
```
The current implementation does not, however, allow the `acme` program to conveniently invoke `token` instructions on the client's behalf:
```text
let message = Message::new(vec![
acme_instruction::pay_and_launch_missiles(&alice_pubkey, &bob_pubkey),
]);
client.send_message(&[&alice_keypair, &bob_keypair], &message);
```
Currently, there is no way to create instruction `pay_and_launch_missiles` that executes `token_instruction::pay` from the `acme` program. The workaround is to extend the `acme` program with the implementation of the `token` program, and create `token` accounts with `ACME_PROGRAM_ID`, which the `acme` program is permitted to modify. With that workaround, `acme` can modify token-like accounts created by the `acme` program, but not token accounts created by the `token` program.
## Proposed Solution
The goal of this design is to modify Solana's runtime such that an on-chain program can invoke an instruction from another program.
Given two on-chain programs `token` and `acme`, each implementing instructions `pay()` and `launch_missiles()` respectively, we would ideally like to implement the `acme` module with a call to a function defined in the `token` module:
```text
use token;
fn launch_missiles(keyed_accounts: &[KeyedAccount]) -> Result<()> {
...
}
fn pay_and_launch_missiles(keyed_accounts: &[KeyedAccount]) -> Result<()> {
token::pay(&keyed_accounts[1..])?;
launch_missiles(keyed_accounts)?;
}
```
The above code would require that the `token` crate be dynamically linked, so that a custom linker could intercept calls and validate accesses to `keyed_accounts`. That is, even though the client intends to modify both `token` and `acme` accounts, only `token` program is permitted to modify the `token` account, and only the `acme` program is permitted to modify the `acme` account.
Backing off from that ideal cross-program call, a slightly more verbose solution is to expose token's existing `process_instruction()` entrypoint to the acme program:
```text
use token_instruction;
fn launch_missiles(keyed_accounts: &[KeyedAccount]) -> Result<()> {
...
}
fn pay_and_launch_missiles(keyed_accounts: &[KeyedAccount]) -> Result<()> {
let alice_pubkey = keyed_accounts[1].key;
let instruction = token_instruction::pay(&alice_pubkey);
process_instruction(&instruction)?;
launch_missiles(keyed_accounts)?;
}
```
where `process_instruction()` is built into Solana's runtime and responsible for routing the given instruction to the `token` program via the instruction's `program_id` field. Before invoking `pay()`, the runtime must also ensure that `acme` didn't modify any accounts owned by `token`. It does this by calling `runtime::verify_account_changes()` and then afterward updating all the `pre_*` variables to tentatively commit `acme`'s account modifications. After `pay()` completes, the runtime must again ensure that `token` didn't modify any accounts owned by `acme`. It should call `verify_account_changes()` again, but this time with the `token` program ID. Lastly, after `pay_and_launch_missiles()` completes, the runtime must call `verify_account_changes()` one more time, where it normally would, but using all updated `pre_*` variables. If executing `pay_and_launch_missiles()` up to `pay()` made no invalid account changes, `pay()` made no invalid changes, and executing from `pay()` until `pay_and_launch_missiles()` returns made no invalid changes, then the runtime can transitively assume `pay_and_launch_missiles()` as whole made no invalid account changes, and therefore commit all account modifications.
### Setting `KeyedAccount.is_signer`
When `process_instruction()` is invoked, the runtime must create a new `KeyedAccounts` parameter using the signatures from the _original_ transaction data. Since the `token` program is immutable and existed on-chain prior to the `acme` program, the runtime can safely treat the transaction signature as a signature of a transaction with a `token` instruction. When the runtime sees the given instruction references `alice_pubkey`, it looks up the key in the transaction to see if that key corresponds to a transaction signature. In this case it does and so sets `KeyedAccount.is_signer`, thereby authorizing the `token` program to modify Alice's account.

View File

@ -0,0 +1,104 @@
# Inter-chain Transaction Verification
## Problem
Inter-chain applications are not new to the digital asset ecosystem; in fact, even the smaller centralized exchanges still categorically dwarf all single chain applications put together in terms of users and volume. They command massive valuations and have spent years effectively optimizing their core products for a broad range of end users. However, their basic operations center around mechanisms that require their users to unilaterally trust them, typically with little to no recourse or protection from accidental loss. This has led to the broader digital asset ecosystem being fractured along network lines because interoperability solutions typically:
* Are technically complex to fully implement
* Create unstable network scale incentive structures
* Require consistent and high level cooperation between stakeholders
## Proposed Solution
Simple Payment Verification \(SPV\) is a generic term for a range of different methodologies used by light clients on most major blockchain networks to verify aspects of the network state without the burden of fully storing and maintaining the chain itself. In most cases, this means relying on a form of hash tree to supply a proof of the presence of a given transaction in a certain block by comparing against a root hash in that blocks header or equivalent. This allows a light client or wallet to reach a probabilistic level of certainty about on-chain events by itself with a minimum of trust required with regard to network nodes.
Traditionally the process of assembling and validating these proofs is carried out off chain by nodes, wallets, or other clients, but it also offers a potential mechanism for inter-chain state verification. However, by moving the capability to validate SPV proofs on-chain as a smart contract while leveraging the archival properties inherent to the blockchain, it is possible to construct a system for programmatically detecting and verifying transactions on other networks without the involvement of any type of trusted oracle or complex multi-stage consensus mechanism. This concept is broadly generalisable to any network with an SPV mechanism and can even be operated bilaterally on other smart contract platforms, opening up the possibility of cheap, fast, inter-chain transfer of value without relying on collateral, hashlocks, or trusted intermediaries.
Opting to take advantage of well established and developmentally stable mechanisms already common to all major blockchains allows SPV based interoperability solutions to be dramatically simpler than orchestrated multi-stage approaches. As part of this, they dispense with the need for widely agreed upon cross chain communication standards and the large multi-party organizations that write them in favor of a set of discrete contract-based services that can be easily utilized by caller contracts through a common abstraction format. This will set the groundwork for a broad range of applications and contracts able to interoperate across the variegated and every growing platform ecosystem.
## Terminology
SPV Program - Client-facing interface for the inter-chain SPV system, manages participant roles. SPV Engine - Validates transaction proofs, subset of the SPV Program. Client - The caller to the SPV Program, typically another solana contract. Prover - Party who generates proofs for transactions and submits them to the SPV Program. Transaction Proof - Created by Provers, contains a merkle proof, transaction, and blockheader reference. Merkle Proof - Basic SPV proof that validates the presence of a transaction in a certain block. Block Header - Represents the basic parameters and relative position of a given block. Proof Request - An order placed by a client for verification of transaction\(s\) by provers. Header Store - A data structure for storing and referencing ranges of block headers in proofs. Client Request - Transaction from the client to the SPV Program to trigger creation of a Proof Request. Sub-account - A Solana account owned by another contract account, without its own private key.
## Service
SPV Programs run as contracts deployed on the Solana network and maintain a type of public marketplace for SPV proofs that allows any party to submit both requests for proofs as well as proofs themselves for verification in response to requests. There will be multiple SPV Program instances active at any given time, at least one for each connected external network and potentially multiple instances per network. SPV program instances will be relatively consistent in their high level API and feature sets with some variation between currency platforms \(Bitcoin, Litecoin\) and smart contract platforms owing to the potential for verification of network state changes beyond simply transactions. In every case regardless of network, the SPV Program relies on an internal component called an SPV engine to provide stateless verification of the actual SPV proofs upon which the higher level client facing features and api are built. The SPV engine requires a network specific implementation, but allows easy extension of the larger inter-chain ecosystem by any team who chooses to carry out that implementation and drop it into the standard SPV program for deployment.
For purposes of Proof Requests, the requester is referred to as the program client, which in most if not all cases will be another Solana Contract. The client can choose to submit a request pertaining to a specific transaction or to include a broader filter that can apply to any of a range of parameters of a transaction including its inputs, outputs, and amount. For example, A client could submit a request for any transaction sent from a given address A to address B with the amount X after a certain time. This structure can be used in a range of applications, such as verifying a specific intended payment in the case of an atomic swap or detecting the movement of collateral assets for a loan.
Following submission of a Client Request, assuming that it is successfully validated, a proof request account is created by the SPV program to track the progress of the request. Provers use the account to specify the request they intend to fill in the proofs they submit for validation, at which point the SPV program validates those proofs and if successful, saves them to the account data of the request account. Clients can monitor the status of their requests and see any applicable transactions alongside their proofs by querying the account data of the request account. In future iterations when supported by Solana, this process will be simplified by contracts publishing events rather than requiring a polling style process as described.
## Implementation
The Solana Inter-chain SPV mechanism consists of the following components and participants:
### SPV engine
A contract deployed on Solana which statelessly verifies SPV proofs for the caller. It takes as arguments for validation:
* An SPV proof in the correct format of the blockchain associated with the program
* Reference\(s\) to the relevant block headers to compare that proof against
* The necessary parameters of the transaction to verify
If the proof in question is successfully validated, the SPV program saves proof
of that verification to the request account, which can be saved by the caller to
its account data or otherwise handled as necessary. SPV programs also expose
utilities and structs used for representation and validation of headers,
transactions, hashes, etc. on a chain by chain basis.
### SPV program
A contract deployed on Solana which coordinates and intermediates the interaction between Clients and Provers and manages the validation of requests, headers, proofs, etc. It is the primary point of access for Client contracts to access the inter-chain. SPV mechanism. It offers the following core features:
* Submit Proof Request - allows client to place a request for a specific proof or set of proofs
* Cancel Proof Request - allows client to invalidate a pending request
* Fill Proof Request - used by Provers to submit for validation a proof corresponding to a given Proof Request
The SPV program maintains a publicly available listing of valid pending Proof
Requests in its account data for the benefit of the Provers, who monitor it and
enclose references to target requests with their submitted proofs.
### Proof Request
A message sent by the Client to the SPV engine denoting a request for a proof of a specific transaction or set of transactions. Proof Requests can either manually specify a certain transaction by its hash or can elect to submit a filter that matches multiple transactions or classes of transactions. For example, a filter matching “any transaction from address xxx to address yyy” could be used to detect payment of a debt or settlement of an inter-chain swap. Likewise, a filter matching “any transaction from address xxx” could be used by a lending or synthetic token minting contract to monitor and react to changes in collateralization. Proof Requests are sent with a fee, which is disbursed by the SPV engine contract to the appropriate Prover once a proof matching that request is validated.
### Request Book
The public listing of valid, open Proof Requests available to provers to fill or for clients to cancel. Roughly analogous to an orderbook in an exchange, but with a single type of listing rather than two separate sides. It is stored in the account data of the SPV program.
### Proof
A proof of the presence of a given transaction in the blockchain in question. Proofs encompass both the actual merkle proof and reference\(s\) to a chain of valid sequential block headers. They are constructed and submitted by Provers in accordance with the specifications of the publicly available Proof Requests hosted on the request book by the SPV program. Upon Validation, they are saved to the account data of the relevant Proof Request, which can be used by the Client to monitor the state of the request.
### Client
The originator of a request for a transaction proof. Clients will most often be other contracts as parts of applications or specific financial products like loans, swaps, escrow, etc. The client in any given verification process cycle initially submits a ClientRequest which communicates the parameters and fee and if successfully validated, results in the creation of a Proof Request account by the SPV program. The Client may also submit a CancelRequest referencing an active Proof Request in order to denote it as invalid for purposes of proof submission.
### Prover
The submitter of a proof that fills a Proof Request. Provers monitor the request book of the SPV program for outstanding Proof Requests and generate matching proofs, which they submit to the SPV program for validation. If the proof is accepted, the fee associated with the Proof Request in question is disbursed to the Prover. Provers typically operate as Solana Blockstreamer nodes that also have access to a Bitcoin node, which they use for purposes of constructing proofs and accessing block headers.
### Header Store
An account-based data structure used to maintain block headers for the purpose of inclusion in submitted proofs by reference to the header store account. header stores can be maintained by independent entities, since header chain validation is a component of the SPV program proof validation mechanism. Fees that are paid out by Proof Requests to Provers are split between the submitter of the merkle proof itself and the header store that is referenced in the submitted proof. Due to the current inability to grow already allocated account data capacity, the use case necessitates a data structure that can grow indefinitely without rebalancing. Sub-accounts are accounts owned by the SPV program without their own private keys that are used for storage by allocating blockheaders to their account data. Multiple potential approaches to the implementation of the header store system are feasible:
Store Headers in program sub-accounts indexed by Public address:
* Each sub-account holds one header and has a public key matching the blockhash
* Requires same number of account data lookups as confirmations per verification
* Limit on number of confirmations \(15-20\) via max transaction data ceiling
* No network-wide duplication of individual headers
Linked List of multiple sub-accounts storing headers:
* Maintain sequential index of storage accounts, many headers per storage account
* Max 2 account data lookups for &gt;99.9% of verifications \(1 for most\)
* Compact sequential data address format allows any number of confirmations and fast lookups
* Facilitates network-wide header duplication inefficiencies

View File

@ -0,0 +1,137 @@
# Ledger Replication
Replication behavior yet to be implemented.
## Storage epoch
The storage epoch should be the number of slots which results in around 100GB-1TB of ledger to be generated for archivers to store. Archivers will start storing ledger when a given fork has a high probability of not being rolled back.
## Validator behavior
1. Every NUM\_KEY\_ROTATION\_TICKS it also validates samples received from
archivers. It signs the PoH hash at that point and uses the following
algorithm with the signature as the input:
* The low 5 bits of the first byte of the signature creates an index into
another starting byte of the signature.
* The validator then looks at the set of storage proofs where the byte of
the proof's sha state vector starting from the low byte matches exactly
with the chosen byte\(s\) of the signature.
* If the set of proofs is larger than the validator can handle, then it
increases to matching 2 bytes in the signature.
* Validator continues to increase the number of matching bytes until a
workable set is found.
* It then creates a mask of valid proofs and fake proofs and sends it to
the leader. This is a storage proof confirmation transaction.
2. After a lockout period of NUM\_SECONDS\_STORAGE\_LOCKOUT seconds, the
validator then submits a storage proof claim transaction which then causes the
distribution of the storage reward if no challenges were seen for the proof to
the validators and archivers party to the proofs.
## Archiver behavior
1. The archiver then generates another set of offsets which it submits a fake
proof with an incorrect sha state. It can be proven to be fake by providing the
seed for the hash result.
* A fake proof should consist of an archiver hash of a signature of a PoH
value. That way when the archiver reveals the fake proof, it can be
verified on chain.
2. The archiver monitors the ledger, if it sees a fake proof integrated, it
creates a challenge transaction and submits it to the current leader. The
transacation proves the validator incorrectly validated a fake storage proof.
The archiver is rewarded and the validator's staking balance is slashed or
frozen.
## Storage proof contract logic
Each archiver and validator will have their own storage account. The validator's account would be separate from their gossip id similiar to their vote account. These should be implemented as two programs one which handles the validator as the keysigner and one for the archiver. In that way when the programs reference other accounts, they can check the program id to ensure it is a validator or archiver account they are referencing.
### SubmitMiningProof
```text
SubmitMiningProof {
slot: u64,
sha_state: Hash,
signature: Signature,
};
keys = [archiver_keypair]
```
Archivers create these after mining their stored ledger data for a certain hash value. The slot is the end slot of the segment of ledger they are storing, the sha\_state the result of the archiver using the hash function to sample their encrypted ledger segment. The signature is the signature that was created when they signed a PoH value for the current storage epoch. The list of proofs from the current storage epoch should be saved in the account state, and then transfered to a list of proofs for the previous epoch when the epoch passes. In a given storage epoch a given archiver should only submit proofs for one segment.
The program should have a list of slots which are valid storage mining slots. This list should be maintained by keeping track of slots which are rooted slots in which a significant portion of the network has voted on with a high lockout value, maybe 32-votes old. Every SLOTS\_PER\_SEGMENT number of slots would be added to this set. The program should check that the slot is in this set. The set can be maintained by receiving a AdvertiseStorageRecentBlockHash and checking with its bank/Tower BFT state.
The program should do a signature verify check on the signature, public key from the transaction submitter and the message of the previous storage epoch PoH value.
### ProofValidation
```text
ProofValidation {
proof_mask: Vec<ProofStatus>,
}
keys = [validator_keypair, archiver_keypair(s) (unsigned)]
```
A validator will submit this transaction to indicate that a set of proofs for a given segment are valid/not-valid or skipped where the validator did not look at it. The keypairs for the archivers that it looked at should be referenced in the keys so the program logic can go to those accounts and see that the proofs are generated in the previous epoch. The sampling of the storage proofs should be verified ensuring that the correct proofs are skipped by the validator according to the logic outlined in the validator behavior of sampling.
The included archiver keys will indicate the the storage samples which are being referenced; the length of the proof\_mask should be verified against the set of storage proofs in the referenced archiver account\(s\), and should match with the number of proofs submitted in the previous storage epoch in the state of said archiver account.
### ClaimStorageReward
```text
ClaimStorageReward {
}
keys = [validator_keypair or archiver_keypair, validator/archiver_keypairs (unsigned)]
```
Archivers and validators will use this transaction to get paid tokens from a program state where SubmitStorageProof, ProofValidation and ChallengeProofValidations are in a state where proofs have been submitted and validated and there are no ChallengeProofValidations referencing those proofs. For a validator, it should reference the archiver keypairs to which it has validated proofs in the relevant epoch. And for an archiver it should reference validator keypairs for which it has validated and wants to be rewarded.
### ChallengeProofValidation
```text
ChallengeProofValidation {
proof_index: u64,
hash_seed_value: Vec<u8>,
}
keys = [archiver_keypair, validator_keypair]
```
This transaction is for catching lazy validators who are not doing the work to validate proofs. An archiver will submit this transaction when it sees a validator has approved a fake SubmitMiningProof transaction. Since the archiver is a light client not looking at the full chain, it will have to ask a validator or some set of validators for this information maybe via RPC call to obtain all ProofValidations for a certain segment in the previous storage epoch. The program will look in the validator account state see that a ProofValidation is submitted in the previous storage epoch and hash the hash\_seed\_value and see that the hash matches the SubmitMiningProof transaction and that the validator marked it as valid. If so, then it will save the challenge to the list of challenges that it has in its state.
### AdvertiseStorageRecentBlockhash
```text
AdvertiseStorageRecentBlockhash {
hash: Hash,
slot: u64,
}
```
Validators and archivers will submit this to indicate that a new storage epoch has passed and that the storage proofs which are current proofs should now be for the previous epoch. Other transactions should check to see that the epoch that they are referencing is accurate according to current chain state.

View File

@ -0,0 +1,155 @@
# Simple Payment and State Verification
It is often useful to allow low resourced clients to participate in a Solana
cluster. Be this participation economic or contract execution, verification
that a client's activity has been accepted by the network is typically
expensive. This proposal lays out a mechanism for such clients to confirm that
their actions have been committed to the ledger state with minimal resource
expenditure and third-party trust.
## A Naive Approach
Validators store the signatures of recently confirmed transactions for a short
period of time to ensure that they are not processed more than once. Validators
provide a JSON RPC endpoint, which clients can use to query the cluster if a
transaction has been recently processed. Validators also provide a PubSub
notification, whereby a client registers to be notified when a given signature
is observed by the validator. While these two mechanisms allow a client to
verify a payment, they are not a proof and rely on completely trusting a
validator.
We will describe a way to minimize this trust using Merkle Proofs to anchor the
validator's response in the ledger, allowing the client to confirm on their own
that a sufficient number of their preferred validators have confirmed a
transaction. Requiring multiple validator attestations further reduces trust in
the validator, as it increases both the technical and economic difficulty of
compromising several other network participants.
## Light Clients
A 'light client' is a cluster participant that does not itself run a validator.
This light client would provide a level of security greater than trusting a
remote validator, without requiring the light client to spend a lot of resources
verifying the ledger.
Rather than providing transaction signatures directly to a light client, the
validator instead generates a Merkle Proof from the transaction of interest to
the root of a Merkle Tree of all transactions in the including block. This
Merkle Root is stored in a ledger entry which is voted on by validators,
providing it consensus legitimacy. The additional level of security for a light
client depends on an initial canonical set of validators the light client
considers to be the stakeholders of the cluster. As that set is changed, the
client can update its internal set of known validators with
[receipts](simple-payment-and-state-verification.md#receipts). This may become
challenging with a large number of delegated stakes.
Validators themselves may want to use light client APIs for performance reasons.
For example, during the initial launch of a validator, the validator may use a
cluster provided checkpoint of the state and verify it with a receipt.
## Receipts
A receipt is a minimal proof that; a transaction has been included in a block,
that the block has been voted on by the client's preferred set of validators
and that the votes have reached the desired confirmation depth.
### Transaction Inclusion Proof
A transaction inclusion proof is a data structure that contains a Merkle Path
from a transaction, through an Entry-Merkle to a Block-Merkle, which is included
in a Bank-Hash with the required set of validator votes. A chain of PoH Entries
containing subsequent validator votes, deriving from the Bank-Hash, is the proof
of confirmation. Clients can examine this ledger data and compute finality using
Solana's fork selection rules.
An Entry-Merkle is a Merkle Root including all transactions in a given entry,
sorted by signature.
A Block-Merkle is the Merkle Root of all the Entry-Merkles sequenced in the block.
![Block Merkle Diagram](../.gitbook/assets/spv-block-merkle.svg)
A Bank-Hash is the hash of the concatenation of the Block-Merkle and Accounts-Hash
<img alt="Bank Hash Diagram" src="img/spv-bank-hash.svg" class="center"/>
An Accounts-Hash is the hash of the concatentation of the state hashes of each
account modified during the current slot.
Transaction status is necessary for the receipt because the state receipt is
constructed for the block. Two transactions over the same state can appear in
the block, and therefore, there is no way to infer from just the state whether
a transaction that is committed to the ledger has succeeded or failed in
modifying the intended state. It may not be necessary to encode the full status
code, but a single status bit to indicate the transaction's success.
### Account State Verification
An account's state (balance or other data) can be verified by submitting a
transaction with a ___TBD___ Instruction to the cluster. The client can then
use a [Transaction Inclusion Proof](#transaction-inclusion-proof) to verify
whether the cluster agrees that the acount has reached the expected state.
### Validator Votes
Leaders should coalesce the validator votes by stake weight into a single entry.
This will reduce the number of entries necessary to create a receipt.
### Chain of Entries
A receipt has a PoH link from the payment or state Merkle Path root to a list
of consecutive validation votes.
It contains the following:
* Transaction -&gt; Entry-Merkle -&gt; Block-Merkle -&gt; Bank-Hash
And a vector of PoH entries:
* Validator vote entries
* Ticks
* Light entries
```text
/// This Entry definition skips over the transactions and only contains the
/// hash of the transactions used to modify PoH.
LightEntry {
/// The number of hashes since the previous Entry ID.
pub num_hashes: u64,
/// The SHA-256 hash `num_hashes` after the previous Entry ID.
hash: Hash,
/// The Merkle Root of the transactions encoded into the Entry.
entry_hash: Hash,
}
```
The light entries are reconstructed from Entries and simply show the entry
Merkle Root that was mixed in to the PoH hash, instead of the full transaction
set.
Clients do not need the starting vote state. The
[fork selection](../implemented-proposals/tower-bft.md) algorithm is defined
such that only votes that appear after the transaction provide finality for the
transaction, and finality is independent of the starting state.
### Verification
A light client that is aware of the supermajority set validators can verify a
receipt by following the Merkle Path to the PoH chain. The Block-Merkle is the
Merkle Root and will appear in votes included in an Entry. The light client can
simulate [fork selection](../implemented-proposals/tower-bft.md) for the
consecutive votes and verify that the receipt is confirmed at the desired
lockout threshold.
### Synthetic State
Synthetic state should be computed into the Bank-Hash along with the bank
generated state.
For example:
* Epoch validator accounts and their stakes and weights.
* Computed fee rates
These values should have an entry in the Bank-Hash. They should live under known
accounts, and therefore have an index into the hash concatenation.

View File

@ -0,0 +1,58 @@
# Slashing rules
Unlike Proof of Work \(PoW\) where off-chain capital expenses are already
deployed at the time of block construction/voting, PoS systems require
capital-at-risk to prevent a logical/optimal strategy of multiple chain voting.
We intend to implement slashing rules which, if broken, result some amount of
the offending validator's deposited stake to be removed from circulation. Given
the ordering properties of the PoH data structure, we believe we can simplify
our slashing rules to the level of a voting lockout time assigned per vote.
I.e. Each vote has an associated lockout time \(PoH duration\) that represents
a duration by any additional vote from that validator must be in a PoH that
contains the original vote, or a portion of that validator's stake is
slashable. This duration time is a function of the initial vote PoH count and
all additional vote PoH counts. It will likely take the form:
```text
Lockouti\(PoHi, PoHj\) = PoHj + K \* exp\(\(PoHj - PoHi\) / K\)
```
Where PoHi is the height of the vote that the lockout is to be applied to and
PoHj is the height of the current vote on the same fork. If the validator
submits a vote on a different PoH fork on any PoHk where k &gt; j &gt; i and
PoHk &lt; Lockout\(PoHi, PoHj\), then a portion of that validator's stake is at
risk of being slashed.
In addition to the functional form lockout described above, early
implementation may be a numerical approximation based on a First In, First Out
\(FIFO\) data structure and the following logic:
* FIFO queue holding 32 votes per active validator
* new votes are pushed on top of queue \(`push_front`\)
* expired votes are popped off top \(`pop_front`\)
* as votes are pushed into the queue, the lockout of each queued vote doubles
* votes are removed from back of queue if `queue.len() > 32`
* the earliest and latest height that has been removed from the back of the
queue should be stored
It is likely that a reward will be offered as a % of the slashed amount to any
node that submits proof of this slashing condition being violated to the PoH.
### Partial Slashing
In the schema described so far, when a validator votes on a given PoH stream,
they are committing themselves to that fork for a time determined by the vote
lockout. An open question is whether validators will be hesitant to begin
voting on an available fork if the penalties are perceived too harsh for an
honest mistake or flipped bit.
One way to address this concern would be a partial slashing design that results
in a slashable amount as a function of either:
1. the fraction of validators, out of the total validator pool, that were also
slashed during the same time period \(ala Casper\)
2. the amount of time since the vote was cast \(e.g. a linearly increasing % of
total deposited as slashable amount over time\), or both.
This is an area currently under exploration.

View File

@ -0,0 +1,9 @@
# Snapshot Verification
## Problem
Snapshot verification of the account states is implemented, but the bank hash of the snapshot which is used to verify is falsifiable.
## Solution
While a validator is processing transactions to catch up to the cluster from the snapshot, use incoming vote transactions and the commitment calculator to confirm that the cluster is indeed building on the snapshotted bank hash. Once a threshold commitment level is reached, accept the snapshot as valid and start voting.

View File

@ -0,0 +1,69 @@
# Tick Verification
This design the criteria and validation of ticks in a slot. It also describes
error handling and slashing conditions encompassing how the system handles
transmissions that do not meet these requirements.
# Slot structure
Each slot must contain an expected `ticks_per_slot` number of ticks. The last
shred in a slot must contain only the entirety of the last tick, and nothing
else. The leader must also mark this shred containing the last tick with the
`LAST_SHRED_IN_SLOT` flag. Between ticks, there must be `hashes_per_tick`
number of hashes.
# Handling bad transmissions
Malicious transmissions `T` are handled in two ways:
1) If a leader can generate some erronenous transmission `T` and also some
alternate transmission `T'` for the same slot without violating any slashing
rules for duplicate transmissions (for instance if `T'` is a subset of `T`),
then the cluster must handle the possibility of both transmissions being live.
Thus this means we cannot mark the erronenous transmission `T` as dead because
the cluster may have reached consensus on `T'`. These cases necessitate a
slashing proof to punish this bad behavior.
2) Otherwise, we can simply mark the slot as dead and not playable. A slashing
proof may or may not be necessary depending on feasibility.
# Blockstore receiving shreds
When blockstore receives a new shred `s`, there are two cases:
1) `s` is marked as `LAST_SHRED_IN_SLOT`, then check if there exists a shred
`s'` in blockstore for that slot where `s'.index > s.index` If so, together `s`
and `s'` constitute a slashing proof.
2) Blockstore has already received a shred `s'` marked as `LAST_SHRED_IN_SLOT`
with index `i`. If `s.index > i`, then together `s` and `s'`constitute a
slashing proof. In this case, blockstore will also not insert `s`.
3) Duplicate shreds for the same index are ignored. Non-duplicate shreds for
the same index are a slashable condition. Details for this case are covered
in the `Leader Duplicate Block Slashing` section.
# Replaying and validating ticks
1) Replay stage replays entries from blockstore, keeping track of the number of
ticks it has seen per slot, and verifying there are `hashes_per_tick` number of
hashes between ticcks. After the tick from this last shred has been played,
replay stage then checks the total number of ticks.
Failure scenario 1: If ever there are two consecutive ticks between which the
number of hashes is `!= hashes_per_tick`, mark this slot as dead.
Failure scenario 2: If the number of ticks != `ticks_per_slot`, mark slot as
dead.
Failure scenario 3: If the number of ticks reaches `ticks_per_slot`, but we still
haven't seen the `LAST_SHRED_IN_SLOT`, mark this slot as dead.
2) When ReplayStage reaches a shred marked as the last shred, it checks if this
last shred is a tick.
Failure scenario: If the signed shred with the `LAST_SHRED_IN_SLOT` flag cannot
be deserialized into a tick (either fails to deserialize or deserializes into
an entry), mark this slot as dead.

View File

@ -0,0 +1,52 @@
# Validator
## History
When we first started Solana, the goal was to de-risk our TPS claims. We knew
that between optimistic concurrency control and sufficiently long leader slots,
that PoS consensus was not the biggest risk to TPS. It was GPU-based signature
verification, software pipelining and concurrent banking. Thus, the TPU was
born. After topping 100k TPS, we split the team into one group working toward
710k TPS and another to flesh out the validator pipeline. Hence, the TVU was
born. The current architecture is a consequence of incremental development with
that ordering and project priorities. It is not a reflection of what we ever
believed was the most technically elegant cross-section of those technologies.
In the context of leader rotation, the strong distinction between leading and
validating is blurred.
## Difference between validating and leading
The fundamental difference between the pipelines is when the PoH is present. In
a leader, we process transactions, removing bad ones, and then tag the result
with a PoH hash. In the validator, we verify that hash, peel it off, and
process the transactions in exactly the same way. The only difference is that
if a validator sees a bad transaction, it can't simply remove it like the
leader does, because that would cause the PoH hash to change. Instead, it
rejects the whole block. The other difference between the pipelines is what
happens _after_ banking. The leader broadcasts entries to downstream validators
whereas the validator will have already done that in RetransmitStage, which is
a confirmation time optimization. The validation pipeline, on the other hand,
has one last step. Any time it finishes processing a block, it needs to weigh
any forks it's observing, possibly cast a vote, and if so, reset its PoH hash
to the block hash it just voted on.
## Proposed Design
We unwrap the many abstraction layers and build a single pipeline that can
toggle leader mode on whenever the validator's ID shows up in the leader
schedule.
![Validator block diagram](../.gitbook/assets/validator-proposal.svg)
## Notable changes
* Hoist FetchStage and BroadcastStage out of TPU
* BankForks renamed to Banktree
* TPU moves to new socket-free crate called solana-tpu.
* TPU's BankingStage absorbs ReplayStage
* TVU goes away
* New RepairStage absorbs Shred Fetch Stage and repair requests
* JSON RPC Service is optional - used for debugging. It should instead be part
of a separate `solana-blockstreamer` executable.
* New MulticastStage absorbs retransmit part of RetransmitStage
* MulticastStage downstream of Blockstore

View File

@ -0,0 +1,111 @@
# Secure Vote Signing
## Secure Vote Signing
This design describes additional vote signing behavior that will make the process more secure.
Currently, Solana implements a vote-signing service that evaluates each vote to ensure it does not violate a slashing condition. The service could potentially have different variations, depending on the hardware platform capabilities. In particular, it could be used in conjunction with a secure enclave \(such as SGX\). The enclave could generate an asymmetric key, exposing an API for user \(untrusted\) code to sign the vote transactions, while keeping the vote-signing private key in its protected memory.
The following sections outline how this architecture would work:
### Message Flow
1. The node initializes the enclave at startup
* The enclave generates an asymmetric key and returns the public key to the
node
* The keypair is ephemeral. A new keypair is generated on node bootup. A
new keypair might also be generated at runtime based on some to be determined
criteria.
* The enclave returns its attestation report to the node
2. The node performs attestation of the enclave \(e.g using Intel's IAS APIs\)
* The node ensures that the Secure Enclave is running on a TPM and is
signed by a trusted party
3. The stakeholder of the node grants ephemeral key permission to use its stake.
This process is to be determined.
4. The node's untrusted, non-enclave software calls trusted enclave software
using its interface to sign transactions and other data.
* In case of vote signing, the node needs to verify the PoH. The PoH
verification is an integral part of signing. The enclave would be
presented with some verifiable data to check before signing the vote.
* The process of generating the verifiable data in untrusted space is to be determined
### PoH Verification
1. When the node votes on an en entry `X`, there's a lockout period `N`, for
which it cannot vote on a fork that does not contain `X` in its history.
2. Every time the node votes on the derivative of `X`, say `X+y`, the lockout
period for `X` increases by a factor `F` \(i.e. the duration node cannot vote on
a fork that does not contain `X` increases\).
* The lockout period for `X+y` is still `N` until the node votes again.
3. The lockout period increment is capped \(e.g. factor `F` applies maximum 32
times\).
4. The signing enclave must not sign a vote that violates this policy. This
means
* Enclave is initialized with `N`, `F` and `Factor cap`
* Enclave stores `Factor cap` number of entry IDs on which the node had
previously voted
* The sign request contains the entry ID for the new vote
* Enclave verifies that new vote's entry ID is on the correct fork
\(following the rules \#1 and \#2 above\)
### Ancestor Verification
This is alternate, albeit, less certain approach to verifying voting fork. 1. The validator maintains an active set of nodes in the cluster 2. It observes the votes from the active set in the last voting period 3. It stores the ancestor/last\_tick at which each node voted 4. It sends new vote request to vote-signing service
* It includes previous votes from nodes in the active set, and their
corresponding ancestors
1. The signer checks if the previous votes contains a vote from the validator,
and the vote ancestor matches with majority of the nodes
* It signs the new vote if the check is successful
* It asserts \(raises an alarm of some sort\) if the check is unsuccessful
The premise is that the validator can be spoofed at most once to vote on incorrect data. If someone hijacks the validator and submits a vote request for bogus data, that vote will not be included in the PoH \(as it'll be rejected by the cluster\). The next time the validator sends a request to sign the vote, the signing service will detect that validator's last vote is missing \(as part of
## 5 above\).
### Fork determination
Due to the fact that the enclave cannot process PoH, it has no direct knowledge of fork history of a submitted validator vote. Each enclave should be initiated with the current _active set_ of public keys. A validator should submit its current vote along with the votes of the active set \(including itself\) that it observed in the slot of its previous vote. In this way, the enclave can surmise the votes accompanying the validator's previous vote and thus the fork being voted on. This is not possible for the validator's initial submitted vote, as it will not have a 'previous' slot to reference. To account for this, a short voting freeze should apply until the second vote is submitted containing the votes within the active set, along with it's own vote, at the height of the initial vote.
### Enclave configuration
A staking client should be configurable to prevent voting on inactive forks. This mechanism should use the client's known active set `N_active` along with a threshold vote `N_vote` and a threshold depth `N_depth` to determine whether or not to continue voting on a submitted fork. This configuration should take the form of a rule such that the client will only vote on a fork if it observes more than `N_vote` at `N_depth`. Practically, this represents the client from confirming that it has observed some probability of economic finality of the submitted fork at a depth where an additional vote would create a lockout for an undesirable amount of time if that fork turns out not to be live.
### Challenges
1. Generation of verifiable data in untrusted space for PoH verification in the
enclave.
2. Need infrastructure for granting stake to an ephemeral key.