4
docs/src/implemented-proposals/README.md
Normal file
4
docs/src/implemented-proposals/README.md
Normal file
@ -0,0 +1,4 @@
|
||||
# Implemented Design Proposals
|
||||
|
||||
The following design proposals are fully implemented.
|
||||
|
89
docs/src/implemented-proposals/commitment.md
Normal file
89
docs/src/implemented-proposals/commitment.md
Normal file
@ -0,0 +1,89 @@
|
||||
# Commitment
|
||||
|
||||
The commitment metric aims to give clients a measure of the network confirmation
|
||||
and stake levels on a particular block. Clients can then use this information to
|
||||
derive their own measures of commitment.
|
||||
|
||||
# Calculation RPC
|
||||
|
||||
Clients can request commitment metrics from a validator for a signature `s`
|
||||
through `get_block_commitment(s: Signature) -> BlockCommitment` over RPC. The
|
||||
`BlockCommitment` struct contains an array of u64 `[u64, MAX_CONFIRMATIONS]`. This
|
||||
array represents the commitment metric for the particular block `N` that
|
||||
contains the signature `s` as of the last block `M` that the validator voted on.
|
||||
|
||||
An entry `s` at index `i` in the `BlockCommitment` array implies that the
|
||||
validator observed `s` total stake in the cluster reaching `i` confirmations on
|
||||
block `N` as observed in some block `M`. There will be `MAX_CONFIRMATIONS` elements in
|
||||
this array, representing all the possible number of confirmations from 1 to
|
||||
`MAX_CONFIRMATIONS`.
|
||||
|
||||
# Computation of commitment metric
|
||||
|
||||
Building this `BlockCommitment` struct leverages the computations already being
|
||||
performed for building consensus. The `collect_vote_lockouts` function in
|
||||
`consensus.rs` builds a HashMap, where each entry is of the form `(b, s)`
|
||||
where `s` is a `StakeLockout` struct representing the amount of stake and
|
||||
lockout on a bank `b`.
|
||||
|
||||
This computation is performed on a votable candidate bank `b` as follows.
|
||||
|
||||
```text
|
||||
let output: HashMap<b, StakeLockout> = HashMap::new();
|
||||
for vote_account in b.vote_accounts {
|
||||
for v in vote_account.vote_stack {
|
||||
for a in ancestors(v) {
|
||||
f(*output.get_mut(a), vote_account, v);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
where `f` is some accumulation function that modifies the `StakeLockout` entry
|
||||
for slot `a` with some data derivable from vote `v` and `vote_account`
|
||||
(stake, lockout, etc.). Note here that the `ancestors` here only includes
|
||||
slots that are present in the current status cache. Signatures for banks earlier
|
||||
than those present in the status cache would not be queryable anyway, so those
|
||||
banks are not included in the commitment calculations here.
|
||||
|
||||
Now we can naturally augment the above computation to also build a
|
||||
`BlockCommitment` array for every bank `b` by:
|
||||
1) Adding a `ForkCommitmentCache` to collect the `BlockCommitment` structs
|
||||
2) Replacing `f` with `f'` such that the above computation also builds this
|
||||
`BlockCommitment` for every bank `b`.
|
||||
|
||||
We will proceed with the details of 2) as 1) is trivial.
|
||||
|
||||
Before continuing, it is noteworthy that for some validator's vote account `a`,
|
||||
the number of local confirmations for that validator on slot `s` is
|
||||
`v.num_confirmations`, where `v` is the smallest vote in the stack of votes
|
||||
`a.votes` such that `v.slot >= s` (i.e. there is no need to look at any
|
||||
votes > v as the number of confirmations will be lower).
|
||||
|
||||
Now more specifically, we augment the above computation to:
|
||||
|
||||
```text
|
||||
let output: HashMap<b, StakeLockout> = HashMap::new();
|
||||
let fork_commitment_cache = ForkCommitmentCache::default();
|
||||
for vote_account in b.vote_accounts {
|
||||
// vote stack is sorted from oldest vote to newest vote
|
||||
for (v1, v2) in vote_account.vote_stack.windows(2) {
|
||||
for a in ancestors(v1).difference(ancestors(v2)) {
|
||||
f'(*output.get_mut(a), *fork_commitment_cache.get_mut(a), vote_account, v);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
where `f'` is defined as:
|
||||
```text
|
||||
fn f`(
|
||||
stake_lockout: &mut StakeLockout,
|
||||
some_ancestor: &mut BlockCommitment,
|
||||
vote_account: VoteAccount,
|
||||
v: Vote, total_stake: u64
|
||||
){
|
||||
f(stake_lockout, vote_account, v);
|
||||
*some_ancestor.commitment[v.num_confirmations] += vote_account.stake;
|
||||
}
|
||||
```
|
133
docs/src/implemented-proposals/durable-tx-nonces.md
Normal file
133
docs/src/implemented-proposals/durable-tx-nonces.md
Normal file
@ -0,0 +1,133 @@
|
||||
# Durable Transaction Nonces
|
||||
|
||||
## Problem
|
||||
|
||||
To prevent replay, Solana transactions contain a nonce field populated with a
|
||||
"recent" blockhash value. A transaction containing a blockhash that is too old
|
||||
(~2min as of this writing) is rejected by the network as invalid. Unfortunately
|
||||
certain use cases, such as custodial services, require more time to produce a
|
||||
signature for the transaction. A mechanism is needed to enable these potentially
|
||||
offline network participants.
|
||||
|
||||
## Requirements
|
||||
|
||||
1) The transaction's signature needs to cover the nonce value
|
||||
2) The nonce must not be reusable, even in the case of signing key disclosure
|
||||
|
||||
## A Contract-based Solution
|
||||
|
||||
Here we describe a contract-based solution to the problem, whereby a client can
|
||||
"stash" a nonce value for future use in a transaction's `recent_blockhash`
|
||||
field. This approach is akin to the Compare and Swap atomic instruction,
|
||||
implemented by some CPU ISAs.
|
||||
|
||||
When making use of a durable nonce, the client must first query its value from
|
||||
account data. A transaction is now constructed in the normal way, but with the
|
||||
following additional requirements:
|
||||
|
||||
1) The durable nonce value is used in the `recent_blockhash` field
|
||||
2) An `AdvanceNonceAccount` instruction is the first issued in the transaction
|
||||
|
||||
### Contract Mechanics
|
||||
|
||||
TODO: svgbob this into a flowchart
|
||||
|
||||
```text
|
||||
Start
|
||||
Create Account
|
||||
state = Uninitialized
|
||||
NonceInstruction
|
||||
if state == Uninitialized
|
||||
if account.balance < rent_exempt
|
||||
error InsufficientFunds
|
||||
state = Initialized
|
||||
elif state != Initialized
|
||||
error BadState
|
||||
if sysvar.recent_blockhashes.is_empty()
|
||||
error EmptyRecentBlockhashes
|
||||
if !sysvar.recent_blockhashes.contains(stored_nonce)
|
||||
error NotReady
|
||||
stored_hash = sysvar.recent_blockhashes[0]
|
||||
success
|
||||
WithdrawInstruction(to, lamports)
|
||||
if state == Uninitialized
|
||||
if !signers.contains(owner)
|
||||
error MissingRequiredSignatures
|
||||
elif state == Initialized
|
||||
if !sysvar.recent_blockhashes.contains(stored_nonce)
|
||||
error NotReady
|
||||
if lamports != account.balance && lamports + rent_exempt > account.balance
|
||||
error InsufficientFunds
|
||||
account.balance -= lamports
|
||||
to.balance += lamports
|
||||
success
|
||||
```
|
||||
|
||||
A client wishing to use this feature starts by creating a nonce account under
|
||||
the system program. This account will be in the `Uninitialized` state with no
|
||||
stored hash, and thus unusable.
|
||||
|
||||
To initialize a newly created account, an `InitializeNonceAccount` instruction must be
|
||||
issued. This instruction takes one parameter, the `Pubkey` of the account's
|
||||
[authority](../offline-signing/durable-nonce.md#nonce-authority). Nonce accounts
|
||||
must be [rent-exempt](rent.md#two-tiered-rent-regime) to meet the data-persistence
|
||||
requirements of the feature, and as such, require that sufficient lamports be
|
||||
deposited before they can be initialized. Upon successful initialization, the
|
||||
cluster's most recent blockhash is stored along with specified nonce authority
|
||||
`Pubkey`.
|
||||
|
||||
The `AdvanceNonceAccount` instruction is used to manage the account's stored nonce
|
||||
value. It stores the cluster's most recent blockhash in the account's state data,
|
||||
failing if that matches the value already stored there. This check prevents
|
||||
replaying transactions within the same block.
|
||||
|
||||
Due to nonce accounts' [rent-exempt](rent.md#two-tiered-rent-regime) requirement,
|
||||
a custom withdraw instruction is used to move funds out of the account.
|
||||
The `WithdrawNonceAccount` instruction takes a single argument, lamports to withdraw,
|
||||
and enforces rent-exemption by preventing the account's balance from falling
|
||||
below the rent-exempt minimum. An exception to this check is if the final balance
|
||||
would be zero lamports, which makes the account eligible for deletion. This
|
||||
account closure detail has an additional requirement that the stored nonce value
|
||||
must not match the cluster's most recent blockhash, as per `AdvanceNonceAccount`.
|
||||
|
||||
The account's [nonce authority](../offline-signing/durable-nonce.md#nonce-authority)
|
||||
can be changed using the `AuthorizeNonceAccount` instruction. It takes one parameter,
|
||||
the `Pubkey` of the new authority. Executing this instruction grants full
|
||||
control over the account and its balance to the new authority.
|
||||
|
||||
{% hint style="info" %}
|
||||
`AdvanceNonceAccount`, `WithdrawNonceAccount` and `AuthorizeNonceAccount` all require the current
|
||||
[nonce authority](../offline-signing/durable-nonce.md#nonce-authority) for the
|
||||
account to sign the transaction.
|
||||
{% endhint %}
|
||||
|
||||
### Runtime Support
|
||||
|
||||
The contract alone is not sufficient for implementing this feature. To enforce
|
||||
an extant `recent_blockhash` on the transaction and prevent fee theft via
|
||||
failed transaction replay, runtime modifications are necessary.
|
||||
|
||||
Any transaction failing the usual `check_hash_age` validation will be tested
|
||||
for a Durable Transaction Nonce. This is signaled by including a `AdvanceNonceAccount`
|
||||
instruction as the first instruction in the transaction.
|
||||
|
||||
If the runtime determines that a Durable Transaction Nonce is in use, it will
|
||||
take the following additional actions to validate the transaction:
|
||||
|
||||
1) The `NonceAccount` specified in the `Nonce` instruction is loaded.
|
||||
2) The `NonceState` is deserialized from the `NonceAccount`'s data field and
|
||||
confirmed to be in the `Initialized` state.
|
||||
3) The nonce value stored in the `NonceAccount` is tested to match against the
|
||||
one specified in the transaction's `recent_blockhash` field.
|
||||
|
||||
If all three of the above checks succeed, the transaction is allowed to continue
|
||||
validation.
|
||||
|
||||
Since transactions that fail with an `InstructionError` are charged a fee and
|
||||
changes to their state rolled back, there is an opportunity for fee theft if an
|
||||
`AdvanceNonceAccount` instruction is reverted. A malicious validator could replay the
|
||||
failed transaction until the stored nonce is successfully advanced. Runtime
|
||||
changes prevent this behavior. When a durable nonce transaction fails with an
|
||||
`InstructionError` aside from the `AdvanceNonceAccount` instruction, the nonce account
|
||||
is rolled back to its pre-execution state as usual. Then the runtime advances
|
||||
its nonce value and the advanced nonce account stored as if it succeeded.
|
15
docs/src/implemented-proposals/ed_overview/README.md
Normal file
15
docs/src/implemented-proposals/ed_overview/README.md
Normal file
@ -0,0 +1,15 @@
|
||||
# Cluster Economics
|
||||
|
||||
**Subject to change.**
|
||||
|
||||
Solana’s crypto-economic system is designed to promote a healthy, long term self-sustaining economy with participant incentives aligned to the security and decentralization of the network. The main participants in this economy are validation-clients and replication-clients. Their contributions to the network, state validation and data storage respectively, and their requisite incentive mechanisms are discussed below.
|
||||
|
||||
The main channels of participant remittances are referred to as protocol-based rewards and transaction fees. Protocol-based rewards are issuances from a global, protocol-defined, inflation rate. These rewards will constitute the total reward delivered to replication and validation clients, the remaining sourced from transaction fees. In the early days of the network, it is likely that protocol-based rewards, deployed based on predefined issuance schedule, will drive the majority of participant incentives to participate in the network.
|
||||
|
||||
These protocol-based rewards, to be distributed to participating validation and replication clients, are to be a result of a global supply inflation rate, calculated per Solana epoch and distributed amongst the active validator set. As discussed further below, the per annum inflation rate is based on a pre-determined disinflationary schedule. This provides the network with monetary supply predictability which supports long term economic stability and security.
|
||||
|
||||
Transaction fees are market-based participant-to-participant transfers, attached to network interactions as a necessary motivation and compensation for the inclusion and execution of a proposed transaction \(be it a state execution or proof-of-replication verification\). A mechanism for long-term economic stability and forking protection through partial burning of each transaction fee is also discussed below.
|
||||
|
||||
A high-level schematic of Solana’s crypto-economic design is shown below in **Figure 1**. The specifics of validation-client economics are described in sections: [Validation-client Economics](ed_validation_client_economics/), [State-validation Protocol-based Rewards](ed_validation_client_economics/ed_vce_state_validation_protocol_based_rewards.md), [State-validation Transaction Fees](ed_validation_client_economics/ed_vce_state_validation_transaction_fees.md) and [Replication-validation Transaction Fees](ed_validation_client_economics/ed_vce_replication_validation_transaction_fees.md). Also, the section titled [Validation Stake Delegation](ed_validation_client_economics/ed_vce_validation_stake_delegation.md) closes with a discussion of validator delegation opportunties and marketplace. Additionally, in [Storage Rent Economics](ed_storage_rent_economics.md), we describe an implementation of storage rent to account for the externality costs of maintaining the active state of the ledger. [Replication-client Economics](ed_replication_client_economics/) will review the Solana network design for global ledger storage/redundancy and archiver-client economics \([Storage-replication rewards](ed_replication_client_economics/ed_rce_storage_replication_rewards.md)\) along with an archiver-to-validator delegation mechanism designed to aide participant on-boarding into the Solana economy discussed in [Replication-client Reward Auto-delegation](ed_replication_client_economics/ed_rce_replication_client_reward_auto_delegation.md). An outline of features for an MVP economic design is discussed in the [Economic Design MVP](ed_mvp.md) section. Finally, in [Attack Vectors](ed_attack_vectors.md), various attack vectors will be described and potential vulnerabilities explored and parameterized.
|
||||
|
||||
**Figure 1**: Schematic overview of Solana economic incentive design.
|
@ -0,0 +1,14 @@
|
||||
# Attack Vectors
|
||||
|
||||
**Subject to change.**
|
||||
|
||||
## Colluding validation and replication clients
|
||||
|
||||
A colluding validation-client, may take the strategy to mark PoReps from non-colluding archiver nodes as invalid as an attempt to maximize the rewards for the colluding archiver nodes. In this case, it isn’t feasible for the offended-against archiver nodes to petition the network for resolution as this would result in a network-wide vote on each offending PoRep and create too much overhead for the network to progress adequately. Also, this mitigation attempt would still be vulnerable to a >= 51% staked colluder.
|
||||
|
||||
Alternatively, transaction fees from submitted PoReps are pooled and distributed across validation-clients in proportion to the number of valid PoReps discounted by the number of invalid PoReps as voted by each validator-client. Thus invalid votes are directly dis-incentivized through this reward channel. Invalid votes that are revealed by archiver nodes as fishing PoReps, will not be discounted from the payout PoRep count.
|
||||
|
||||
Another collusion attack involves a validator-client who may take the strategy to ignore invalid PoReps from colluding archiver and vote them as valid. In this case, colluding archiver-clients would not have to store the data while still receiving rewards for validated PoReps. Additionally, colluding validator nodes would also receive rewards for validating these PoReps. To mitigate this attack, validators must randomly sample PoReps corresponding to the ledger block they are validating and because of this, there will be multiple validators that will receive the colluding archiver’s invalid submissions. These non-colluding validators will be incentivized to mark these PoReps as invalid as they have no way to determine whether the proposed invalid PoRep is actually a fishing PoRep, for which a confirmation vote would result in the validator’s stake being slashed.
|
||||
|
||||
In this case, the proportion of time a colluding pair will be successful has an upper limit determined by the % of stake of the network claimed by the colluding validator. This also sets bounds to the value of such an attack. For example, if a colluding validator controls 10% of the total validator stake, transaction fees will be lost \(likely sent to mining pool\) by the colluding archiver 90% of the time and so the attack vector is only profitable if the per-PoRep reward at least 90% higher than the average PoRep transaction fee. While, probabilistically, some colluding archiver-client PoReps will find their way to colluding validation-clients, the network can also monitor rates of paired \(validator + archiver\) discrepancies in voting patterns and censor identified colluders in these cases.
|
||||
|
@ -0,0 +1,14 @@
|
||||
# Economic Sustainability
|
||||
|
||||
**Subject to change.**
|
||||
|
||||
Long term economic sustainability is one of the guiding principles of Solana’s economic design. While it is impossible to predict how decentralized economies will develop over time, especially economies with flexible decentralized governances, we can arrange economic components such that, under certain conditions, a sustainable economy may take shape in the long term. In the case of Solana’s network, these components take the form of token issuance \(via inflation\) and token burning’.
|
||||
|
||||
The dominant remittances from the Solana mining pool are validator and archiver rewards. The disinflationary mechanism is a flat, protocol-specified and adjusted, % of each transaction fee.
|
||||
|
||||
The Archiver rewards are to be delivered to archivers as a portion of the network inflation after successful PoRep validation. The per-PoRep reward amount is determined as a function of the total network storage redundancy at the time of the PoRep validation and the network goal redundancy. This function is likely to take the form of a discount from a base reward to be delivered when the network has achieved and maintained its goal redundancy. An example of such a reward function is shown in **Figure 3**
|
||||
|
||||
**Figure 3**: Example PoRep reward design as a function of global network storage redundancy.
|
||||
|
||||
In the example shown in Figure 1, multiple per PoRep base rewards are explored \(as a % of Tx Fee\) to be delivered when the global ledger replication redundancy meets 10X. When the global ledger replication redundancy is less than 10X, the base reward is discounted as a function of the square of the ratio of the actual ledger replication redundancy to the goal redundancy \(i.e. 10X\).
|
||||
|
16
docs/src/implemented-proposals/ed_overview/ed_mvp.md
Normal file
16
docs/src/implemented-proposals/ed_overview/ed_mvp.md
Normal file
@ -0,0 +1,16 @@
|
||||
# Economic Design MVP
|
||||
|
||||
**Subject to change.**
|
||||
|
||||
The preceeding sections, outlined in the [Economic Design Overview](./), describe a long-term vision of a sustainable Solana economy. Of course, we don't expect the final implementation to perfectly match what has been described above. We intend to fully engage with network stakeholders throughout the implementation phases \(i.e. pre-testnet, testnet, mainnet\) to ensure the system supports, and is representative of, the various network participants' interests. The first step toward this goal, however, is outlining a some desired MVP economic features to be available for early pre-testnet and testnet participants. Below is a rough sketch outlining basic economic functionality from which a more complete and functional system can be developed.
|
||||
|
||||
## MVP Economic Features
|
||||
|
||||
* Faucet to deliver testnet SOLs to validators for staking and application development.
|
||||
* Mechanism by which validators are rewarded via network inflation.
|
||||
* Ability to delegate tokens to validator nodes
|
||||
* Validator set commission fees on interest from delegated tokens.
|
||||
* Archivers to receive fixed, arbitrary reward for submitting validated PoReps. Reward size mechanism \(i.e. PoRep reward as a function of total ledger redundancy\) to come later.
|
||||
* Pooling of archiver PoRep transaction fees and weighted distribution to validators based on PoRep verification \(see [Replication-validation Transaction Fees](ed_validation_client_economics/ed_vce_replication_validation_transaction_fees.md). It will be useful to test this protection against attacks on testnet.
|
||||
* Nice-to-have: auto-delegation of archiver rewards to validator.
|
||||
|
@ -0,0 +1,6 @@
|
||||
# References
|
||||
|
||||
1. [https://blog.ethereum.org/2016/07/27/inflation-transaction-fees-cryptocurrency-monetary-policy/](https://blog.ethereum.org/2016/07/27/inflation-transaction-fees-cryptocurrency-monetary-policy/)
|
||||
2. [https://medium.com/solana-labs/how-to-create-decentralized-storage-for-a-multi-petabyte-digital-ledger-2499a3a8c281](https://medium.com/solana-labs/how-to-create-decentralized-storage-for-a-multi-petabyte-digital-ledger-2499a3a8c281)
|
||||
3. [https://medium.com/solana-labs/how-to-create-decentralized-storage-for-a-multi-petabyte-digital-ledger-2499a3a8c281](https://medium.com/solana-labs/how-to-create-decentralized-storage-for-a-multi-petabyte-digital-ledger-2499a3a8c281)
|
||||
|
@ -0,0 +1,6 @@
|
||||
# Replication-client Economics
|
||||
|
||||
**Subject to change.**
|
||||
|
||||
Replication-clients should be rewarded for providing the network with storage space. Incentivization of the set of archivers provides data security through redundancy of the historical ledger. Replication nodes are rewarded in proportion to the amount of ledger data storage provided, as proved by successfully submitting Proofs-of-Replication to the cluster.. These rewards are captured by generating and entering Proofs of Replication \(PoReps\) into the PoH stream which can be validated by Validation nodes as described in [Replication-validation Transaction Fees](../ed_validation_client_economics/ed_vce_replication_validation_transaction_fees.md).
|
||||
|
@ -0,0 +1,8 @@
|
||||
# Replication-client Reward Auto-delegation
|
||||
|
||||
**Subject to change.**
|
||||
|
||||
The ability for Solana network participants to earn rewards by providing storage service is a unique on-boarding path that requires little hardware overhead and minimal upfront capital. It offers an avenue for individuals with extra-storage space on their home laptops or PCs to contribute to the security of the network and become integrated into the Solana economy.
|
||||
|
||||
To enhance this on-boarding ramp and facilitate further participation and investment in the Solana economy, replication-clients have the opportunity to auto-delegate their rewards to validation-clients of their choice. Much like the automatic reinvestment of stock dividends, in this scenario, an archiver-client can earn Solana tokens by providing some storage capacity to the network \(i.e. via submitting valid PoReps\), have the protocol-based rewards automatically assigned as delegation to a staked validator node of the archiver's choice and earn interest, less a fee, from the validation-client's network participation.
|
||||
|
@ -0,0 +1,8 @@
|
||||
# Storage-replication Rewards
|
||||
|
||||
**Subject to change.**
|
||||
|
||||
Archiver-clients download, encrypt and submit PoReps for ledger block sections.3 PoReps submitted to the PoH stream, and subsequently validated, function as evidence that the submitting archiver client is indeed storing the assigned ledger block sections on local hard drive space as a service to the network. Therefore, archiver clients should earn protocol rewards proportional to the amount of storage, and the number of successfully validated PoReps, that they are verifiably providing to the network.
|
||||
|
||||
Additionally, archiver clients have the opportunity to capture a portion of slashed bounties \[TBD\] of dishonest validator clients. This can be accomplished by an archiver client submitting a verifiably false PoRep for which a dishonest validator client receives and signs as a valid PoRep. This reward incentive is to prevent lazy validators and minimize validator-archiver collusion attacks, more on this below.
|
||||
|
@ -0,0 +1,18 @@
|
||||
## Storage Rent Economics
|
||||
|
||||
Each transaction that is submitted to the Solana ledger imposes costs. Transaction fees paid by the submitter, and collected by a validator, in theory, account for the acute, transacitonal, costs of validating and adding that data to the ledger. At the same time, our compensation design for archivers (see [Replication-client Economics](ed_replication_client_economics.md)), in theory, accounts for the long term storage of the historical ledger. Unaccounted in this process is the mid-term storage of active ledger state, necessarily maintined by the rotating validator set. This type of storage imposes costs not only to validators but also to the broader network as active state grows so does data transmission and validation overhead. To account for these costs, we describe here our preliminary design and implementation of storage rent.
|
||||
|
||||
Storage rent can be paid via one of two methods:
|
||||
|
||||
Method 1: Set it and forget it
|
||||
|
||||
With this approach, accounts with two-years worth of rent deposits secured are exempt from network rent charges. By maintaining this minimum-balance, the broader network benefits from reduced liquitity and the account holder can trust that their `Account::data` will be retained for continual access/usage.
|
||||
|
||||
Method 2: Pay per byte
|
||||
|
||||
If an account has less than two-years worth of deposited rent the network charges rent on a per-epoch basis, in credit for the next epoch (but in arrears when necessary). This rent is deducted at a rate specified in genesis, in lamports per kilobyte-year.
|
||||
|
||||
For information on the technical implementation details of this design, see the [Rent](rent.md) section.
|
||||
|
||||
|
||||
|
@ -0,0 +1,8 @@
|
||||
# Validation-client Economics
|
||||
|
||||
**Subject to change.**
|
||||
|
||||
Validator-clients are eligible to receive protocol-based \(i.e. inflation-based\) rewards issued via stake-based annual interest rates \(calculated per epoch\) by providing compute \(CPU+GPU\) resources to validate and vote on a given PoH state. These protocol-based rewards are determined through an algorithmic disinflationary schedule as a function of total amount of circulating tokens. The network is expected to launch with an annual inflation rate around 15%, set to decrease by 15% per year until a long-term stable rate of 1-2% is reached. These issuances are to be split and distributed to participating validators and archivers, with around 90% of the issued tokens allocated for validator rewards. Because the network will be distributing a fixed amount of inflation rewards across the stake-weighted valdiator set, any individual validator's interest rate will be a function of the amount of staked SOL in relation to the circulating SOL.
|
||||
|
||||
Additionally, validator clients may earn revenue through fees via state-validation transactions and Proof-of-Replication \(PoRep\) transactions. For clarity, we separately describe the design and motivation of these revenue distriubutions for validation-clients below: state-validation protocol-based rewards, state-validation transaction fees and rent, and PoRep-validation transaction fees.
|
||||
|
@ -0,0 +1,11 @@
|
||||
# Replication-validation Transaction Fees
|
||||
|
||||
**Subject to change.**
|
||||
|
||||
As previously mentioned, validator-clients will also be responsible for validating PoReps submitted into the PoH stream by archiver-clients. In this case, validators are providing compute \(CPU/GPU\) and light storage resources to confirm that these replication proofs could only be generated by a client that is storing the referenced PoH leger block.
|
||||
|
||||
While replication-clients are incentivized and rewarded through protocol-based rewards schedule \(see [Replication-client Economics](../ed_replication_client_economics/README.md)\), validator-clients will be incentivized to include and validate PoReps in PoH through collection of transaction fees associated with the submitted PoReps and distribution of protocol rewards proportional to the validated PoReps. As will be described in detail in the Section 3.1, replication-client rewards are protocol-based and designed to reward based on a global data redundancy factor. I.e. the protocol will incentivize replication-client participation through rewards based on a target ledger redundancy \(e.g. 10x data redundancy\).
|
||||
|
||||
The validation of PoReps by validation-clients is computationally more expensive than state-validation \(detailed in the [Economic Sustainability](../ed_economic_sustainability.md) section\), thus the transaction fees are expected to be proportionally higher.
|
||||
|
||||
There are various attack vectors available for colluding validation and replication clients, also described in detail in [Economic Sustainability](../ed_economic_sustainability.md). To protect against various collusion attack vectors, for a given epoch, validator rewards are distributed across participating validation-clients in proportion to the number of validated PoReps in the epoch less the number of PoReps that mismatch the archivers challenge. The PoRep challenge game is described in [Ledger Replication](../../../cluster/ledger-replication.md#the-porep-game). This design rewards validators proportional to the number of PoReps they process and validate, while providing negative pressure for validation-clients to submit lazy or malicious invalid votes on submitted PoReps \(note that it is computationally prohibitive to determine whether a validator-client has marked a valid PoRep as invalid\).
|
@ -0,0 +1,30 @@
|
||||
# State-validation Protocol-based Rewards
|
||||
|
||||
**Subject to change.**
|
||||
|
||||
Validator-clients have two functional roles in the Solana network:
|
||||
|
||||
* Validate \(vote\) the current global state of that PoH along with any Proofs-of-Replication \(see [Replication Client Economics](../ed_replication_client_economics/)\) that they are eligible to validate.
|
||||
* Be elected as ‘leader’ on a stake-weighted round-robin schedule during which time they are responsible for collecting outstanding transactions and Proofs-of-Replication and incorporating them into the PoH, thus updating the global state of the network and providing chain continuity.
|
||||
|
||||
Validator-client rewards for these services are to be distributed at the end of each Solana epoch. As previously discussed, compensation for validator-clients is provided via a protocol-based annual inflation rate dispersed in proportion to the stake-weight of each validator \(see below\) along with leader-claimed transaction fees available during each leader rotation. I.e. during the time a given validator-client is elected as leader, it has the opportunity to keep a portion of each transaction fee, less a protocol-specified amount that is destroyed \(see [Validation-client State Transaction Fees](ed_vce_state_validation_transaction_fees.md)\). PoRep transaction fees are also collected by the leader client and validator PoRep rewards are distributed in proportion to the number of validated PoReps less the number of PoReps that mismatch an archiver's challenge. \(see [Replication-client Transaction Fees](ed_vce_replication_validation_transaction_fees.md)\)
|
||||
|
||||
The effective protocol-based annual interest rate \(%\) per epoch received by validation-clients is to be a function of:
|
||||
|
||||
* the current global inflation rate, derived from the pre-determined dis-inflationary issuance schedule \(see [Validation-client Economics](.)\)
|
||||
* the fraction of staked SOLs out of the current total circulating supply,
|
||||
* the up-time/participation \[% of available slots that validator had opportunity to vote on\] of a given validator over the previous epoch.
|
||||
|
||||
The first factor is a function of protocol parameters only \(i.e. independent of validator behavior in a given epoch\) and results in a global validation reward schedule designed to incentivize early participation, provide clear montetary stability and provide optimal security in the network.
|
||||
|
||||
At any given point in time, a specific validator's interest rate can be determined based on the porportion of circulating supply that is staked by the network and the validator's uptime/activity in the previous epoch. For example, consider a hypothetical instance of the network with an initial circulating token supply of 250MM tokens with an additional 250MM vesting over 3 years. Additionally an inflation rate is specified at network launch of 7.5%, and a disinflationary schedule of 20% decrease in inflation rate per year \(the actual rates to be implemented are to be worked out during the testnet experimentation phase of mainnet launch\). With these broad assumptions, the 10-year inflation rate \(adjusted daily for this example\) is shown in **Figure 2**, while the total circulating token supply is illustrated in **Figure 3**. Neglected in this toy-model is the inflation supression due to the portion of each transaction fee that is to be destroyed.
|
||||
|
||||
 \*\*Figure 2:\*\* In this example schedule, the annual inflation rate \[%\] reduces at around 20% per year, until it reaches the long-term, fixed, 1.5% rate.
|
||||
|
||||
 \*\*Figure 3:\*\* The total token supply over a 10-year period, based on an initial 250MM tokens with the disinflationary inflation schedule as shown in \*\*Figure 2\*\* Over time, the interest rate, at a fixed network staked percentage, will reduce concordant with network inflation. Validation-client interest rates are designed to be higher in the early days of the network to incentivize participation and jumpstart the network economy. As previously mentioned, the inflation rate is expected to stabalize near 1-2% which also results in a fixed, long-term, interest rate to be provided to validator-clients. This value does not represent the total interest available to validator-clients as transaction fees for state-validation and ledger storage replication \(PoReps\) are not accounted for here. Given these example parameters, annualized validator-specific interest rates can be determined based on the global fraction of tokens bonded as stake, as well as their uptime/activity in the previous epoch. For the purpose of this example, we assume 100% uptime for all validators and a split in interest-based rewards between validators and archiver nodes of 80%/20%. Additionally, the fraction of staked circulating supply is assummed to be constant. Based on these assumptions, an annualized validation-client interest rate schedule as a function of % circulating token supply that is staked is shown in\*\* Figure 4\*\*.
|
||||
|
||||

|
||||
|
||||
**Figure 4:** Shown here are example validator interest rates over time, neglecting transaction fees, segmented by fraction of total circulating supply bonded as stake.
|
||||
|
||||
This epoch-specific protocol-defined interest rate sets an upper limit of _protocol-generated_ annual interest rate \(not absolute total interest rate\) possible to be delivered to any validator-client per epoch. The distributed interest rate per epoch is then discounted from this value based on the participation of the validator-client during the previous epoch.
|
@ -0,0 +1,18 @@
|
||||
# State-validation Transaction Fees
|
||||
|
||||
**Subject to change.**
|
||||
|
||||
Each transaction sent through the network, to be processed by the current leader validation-client and confirmed as a global state transaction, must contain a transaction fee. Transaction fees offer many benefits in the Solana economic design, for example they:
|
||||
|
||||
* provide unit compensation to the validator network for the CPU/GPU resources necessary to process the state transaction,
|
||||
* reduce network spam by introducing real cost to transactions,
|
||||
* open avenues for a transaction market to incentivize validation-client to collect and process submitted transactions in their function as leader,
|
||||
* and provide potential long-term economic stability of the network through a protocol-captured minimum fee amount per transaction, as described below.
|
||||
|
||||
Many current blockchain economies \(e.g. Bitcoin, Ethereum\), rely on protocol-based rewards to support the economy in the short term, with the assumption that the revenue generated through transaction fees will support the economy in the long term, when the protocol derived rewards expire. In an attempt to create a sustainable economy through protocol-based rewards and transaction fees, a fixed portion of each transaction fee is destroyed, with the remaining fee going to the current leader processing the transaction. A scheduled global inflation rate provides a source for rewards distributed to validation-clients, through the process described above, and replication-clients, as discussed below.
|
||||
|
||||
Transaction fees are set by the network cluster based on recent historical throughput, see [Congestion Driven Fees](../../transaction-fees.md#congestion-driven-fees). This minimum portion of each transaction fee can be dynamically adjusted depending on historical gas usage. In this way, the protocol can use the minimum fee to target a desired hardware utilisation. By monitoring a protocol specified gas usage with respect to a desired, target usage amount, the minimum fee can be raised/lowered which should, in turn, lower/raise the actual gas usage per block until it reaches the target amount. This adjustment process can be thought of as similar to the difficulty adjustment algorithm in the Bitcoin protocol, however in this case it is adjusting the minimum transaction fee to guide the transaction processing hardware usage to a desired level.
|
||||
|
||||
As mentioned, a fixed-proportion of each transaction fee is to be destroyed. The intent of this design is to retain leader incentive to include as many transactions as possible within the leader-slot time, while providing an inflation limiting mechansim that protects against "tax evasion" attacks \(i.e. side-channel fee payments\)[1](../ed_references.md).
|
||||
|
||||
Additionally, the burnt fees can be a consideration in fork selection. In the case of a PoH fork with a malicious, censoring leader, we would expect the total fees destroyed to be less than a comparable honest fork, due to the fees lost from censoring. If the censoring leader is to compensate for these lost protocol fees, they would have to replace the burnt fees on their fork themselves, thus potentially reducing the incentive to censor in the first place.
|
@ -0,0 +1,32 @@
|
||||
# Validation Stake Delegation
|
||||
|
||||
**Subject to change.**
|
||||
|
||||
Running a Solana validation-client required relatively modest upfront hardware capital investment. **Table 2** provides an example hardware configuration to support ~1M tx/s with estimated ‘off-the-shelf’ costs:
|
||||
|
||||
| Component | Example | Estimated Cost |
|
||||
| :--- | :--- | :--- |
|
||||
| GPU | 2x 2080 Ti | $2500 |
|
||||
| or | 4x 1080 Ti | $2800 |
|
||||
| OS/Ledger Storage | Samsung 860 Evo 2TB | $370 |
|
||||
| Accounts storage | 2x Samsung 970 Pro M.2 512GB | $340 |
|
||||
| RAM | 32 Gb | $300 |
|
||||
| Motherboard | AMD x399 | $400 |
|
||||
| CPU | AMD Threadripper 2920x | $650 |
|
||||
| Case | | $100 |
|
||||
| Power supply | EVGA 1600W | $300 |
|
||||
| Network | > 500 mbps | |
|
||||
| Network \(1\) | Google webpass business bay area 1gbps unlimited | $5500/mo |
|
||||
| Network \(2\) | Hurricane Electric bay area colo 1gbps | $500/mo |
|
||||
|
||||
**Table 2** example high-end hardware setup for running a Solana client.
|
||||
|
||||
Despite the low-barrier to entry as a validation-client, from a capital investment perspective, as in any developing economy, there will be much opportunity and need for trusted validation services as evidenced by node reliability, UX/UI, APIs and other software accessibility tools. Additionally, although Solana’s validator node startup costs are nominal when compared to similar networks, they may still be somewhat restrictive for some potential participants. In the spirit of developing a true decentralized, permissionless network, these interested parties still have two options to become involved in the Solana network/economy:
|
||||
|
||||
1. Delegation of previously acquired tokens with a reliable validation node to earn a portion of interest generated
|
||||
2. Provide local storage space as a replication-client and receive rewards by submitting Proof-of-Replication \(see [Replication-client Economics](../ed_replication_client_economics/)\).
|
||||
|
||||
a. This participant has the additional option to directly delegate their earned storage rewards \([Replication-client Reward Auto-delegation](../ed_replication_client_economics/ed_rce_replication_client_reward_auto_delegation.md)\)
|
||||
|
||||
Delegation of tokens to validation-clients, via option 1, provides a way for passive Solana token holders to become part of the active Solana economy and earn interest rates proportional to the interest rate generated by the delegated validation-client. Additionally, this feature intends to create a healthy validation-client market, with potential validation-client nodes competing to build reliable, transparent and profitable delegation services.
|
||||
|
36
docs/src/implemented-proposals/embedding-move.md
Normal file
36
docs/src/implemented-proposals/embedding-move.md
Normal file
@ -0,0 +1,36 @@
|
||||
# Embedding the Move Langauge
|
||||
|
||||
## Problem
|
||||
|
||||
Solana enables developers to write on-chain programs in general purpose programming languages such as C or Rust, but those programs contain Solana-specific mechanisms. For example, there isn't another chain that asks developers to create a Rust module with a `process_instruction(KeyedAccounts)` function. Whenever practical, Solana should offer application developers more portable options.
|
||||
|
||||
Until just recently, no popular blockchain offered a language that could expose the value of Solana's massively parallel [runtime](../validator/runtime.md). Solidity contracts, for example, do not separate references to shared data from contract code, and therefore need to be executed serially to ensure deterministic behavior. In practice we see that the most aggressively optimized EVM-based blockchains all seem to peak out around 1,200 TPS - a small fraction of what Solana can do. The Libra project, on the other hand, designed an on-chain programming language called Move that is more suitable for parallel execution. Like Solana's runtime, Move programs depend on accounts for all shared state.
|
||||
|
||||
The biggest design difference between Solana's runtime and Libra's Move VM is how they manage safe invocations between modules. Solana took an operating systems approach and Libra took the domain-specific language approach. In the runtime, a module must trap back into the runtime to ensure the caller's module did not write to data owned by the callee. Likewise, when the callee completes, it must again trap back to the runtime to ensure the callee did not write to data owned by the caller. Move, on the other hand, includes an advanced type system that allows these checks to be run by its bytecode verifier. Because Move bytecode can be verified, the cost of verification is paid just once, at the time the module is loaded on-chain. In the runtime, the cost is paid each time a transaction crosses between modules. The difference is similar in spirit to the difference between a dynamically-typed language like Python versus a statically-typed language like Java. Solana's runtime allows applications to be written in general purpose programming languages, but that comes with the cost of runtime checks when jumping between programs.
|
||||
|
||||
This proposal attempts to define a way to embed the Move VM such that:
|
||||
|
||||
* cross-module invocations within Move do not require the runtime's
|
||||
|
||||
cross-program runtime checks
|
||||
|
||||
* Move programs can leverage functionality in other Solana programs and vice
|
||||
|
||||
versa
|
||||
|
||||
* Solana's runtime parallelism is exposed to batches of Move and non-Move
|
||||
|
||||
transactions
|
||||
|
||||
## Proposed Solution
|
||||
|
||||
### Move VM as a Solana loader
|
||||
|
||||
The Move VM shall be embedded as a Solana loader under the identifier `MOVE_PROGRAM_ID`, so that Move modules can be marked as `executable` with the VM as its `owner`. This will allow modules to load module dependencies, as well as allow for parallel execution of Move scripts.
|
||||
|
||||
All data accounts owned by Move modules must set their owners to the loader, `MOVE_PROGRAM_ID`. Since Move modules encapsulate their account data in the same way Solana programs encapsulate theirs, the Move module owner should be embedded in the account data. The runtime will grant write access to the Move VM, and Move grants access to the module accounts.
|
||||
|
||||
### Interacting with Solana programs
|
||||
|
||||
To invoke instructions in non-Move programs, Solana would need to extend the Move VM with a `process_instruction()` system call. It would work the same as `process_instruction()` Rust BPF programs.
|
||||
|
215
docs/src/implemented-proposals/installer.md
Normal file
215
docs/src/implemented-proposals/installer.md
Normal file
@ -0,0 +1,215 @@
|
||||
# Cluster Software Installation and Updates
|
||||
|
||||
Currently users are required to build the solana cluster software themselves from the git repository and manually update it, which is error prone and inconvenient.
|
||||
|
||||
This document proposes an easy to use software install and updater that can be used to deploy pre-built binaries for supported platforms. Users may elect to use binaries supplied by Solana or any other party they trust. Deployment of updates is managed using an on-chain update manifest program.
|
||||
|
||||
## Motivating Examples
|
||||
|
||||
### Fetch and run a pre-built installer using a bootstrap curl/shell script
|
||||
|
||||
The easiest install method for supported platforms:
|
||||
|
||||
```bash
|
||||
$ curl -sSf https://raw.githubusercontent.com/solana-labs/solana/v0.18.0/install/solana-install-init.sh | sh
|
||||
```
|
||||
|
||||
This script will check github for the latest tagged release and download and run the `solana-install-init` binary from there.
|
||||
|
||||
If additional arguments need to be specified during the installation, the following shell syntax is used:
|
||||
|
||||
```bash
|
||||
$ init_args=.... # arguments for `solana-install-init ...`
|
||||
$ curl -sSf https://raw.githubusercontent.com/solana-labs/solana/v0.18.0/install/solana-install-init.sh | sh -s - ${init_args}
|
||||
```
|
||||
|
||||
### Fetch and run a pre-built installer from a Github release
|
||||
|
||||
With a well-known release URL, a pre-built binary can be obtained for supported platforms:
|
||||
|
||||
```bash
|
||||
$ curl -o solana-install-init https://github.com/solana-labs/solana/releases/download/v0.18.0/solana-install-init-x86_64-apple-darwin
|
||||
$ chmod +x ./solana-install-init
|
||||
$ ./solana-install-init --help
|
||||
```
|
||||
|
||||
### Build and run the installer from source
|
||||
|
||||
If a pre-built binary is not available for a given platform, building the installer from source is always an option:
|
||||
|
||||
```bash
|
||||
$ git clone https://github.com/solana-labs/solana.git
|
||||
$ cd solana/install
|
||||
$ cargo run -- --help
|
||||
```
|
||||
|
||||
### Deploy a new update to a cluster
|
||||
|
||||
Given a solana release tarball \(as created by `ci/publish-tarball.sh`\) that has already been uploaded to a publicly accessible URL, the following commands will deploy the update:
|
||||
|
||||
```bash
|
||||
$ solana-keygen new -o update-manifest.json # <-- only generated once, the public key is shared with users
|
||||
$ solana-install deploy http://example.com/path/to/solana-release.tar.bz2 update-manifest.json
|
||||
```
|
||||
|
||||
### Run a validator node that auto updates itself
|
||||
|
||||
```bash
|
||||
$ solana-install init --pubkey 92DMonmBYXwEMHJ99c9ceRSpAmk9v6i3RdvDdXaVcrfj # <-- pubkey is obtained from whoever is deploying the updates
|
||||
$ export PATH=~/.local/share/solana-install/bin:$PATH
|
||||
$ solana-keygen ... # <-- runs the latest solana-keygen
|
||||
$ solana-install run solana-validator ... # <-- runs a validator, restarting it as necesary when an update is applied
|
||||
```
|
||||
|
||||
## On-chain Update Manifest
|
||||
|
||||
An update manifest is used to advertise the deployment of new release tarballs on a solana cluster. The update manifest is stored using the `config` program, and each update manifest account describes a logical update channel for a given target triple \(eg, `x86_64-apple-darwin`\). The account public key is well-known between the entity deploying new updates and users consuming those updates.
|
||||
|
||||
The update tarball itself is hosted elsewhere, off-chain and can be fetched from the specified `download_url`.
|
||||
|
||||
```text
|
||||
use solana_sdk::signature::Signature;
|
||||
|
||||
/// Information required to download and apply a given update
|
||||
pub struct UpdateManifest {
|
||||
pub timestamp_secs: u64, // When the release was deployed in seconds since UNIX EPOCH
|
||||
pub download_url: String, // Download URL to the release tar.bz2
|
||||
pub download_sha256: String, // SHA256 digest of the release tar.bz2 file
|
||||
}
|
||||
|
||||
/// Data of an Update Manifest program Account.
|
||||
#[derive(Serialize, Deserialize, Default, Debug, PartialEq)]
|
||||
pub struct SignedUpdateManifest {
|
||||
pub manifest: UpdateManifest,
|
||||
pub manifest_signature: Signature,
|
||||
}
|
||||
```
|
||||
|
||||
Note that the `manifest` field itself contains a corresponding signature \(`manifest_signature`\) to guard against man-in-the-middle attacks between the `solana-install` tool and the solana cluster RPC API.
|
||||
|
||||
To guard against rollback attacks, `solana-install` will refuse to install an update with an older `timestamp_secs` than what is currently installed.
|
||||
|
||||
## Release Archive Contents
|
||||
|
||||
A release archive is expected to be a tar file compressed with bzip2 with the following internal structure:
|
||||
|
||||
* `/version.yml` - a simple YAML file containing the field `"target"` - the
|
||||
|
||||
target tuple. Any additional fields are ignored.
|
||||
|
||||
* `/bin/` -- directory containing available programs in the release.
|
||||
|
||||
`solana-install` will symlink this directory to
|
||||
|
||||
`~/.local/share/solana-install/bin` for use by the `PATH` environment
|
||||
|
||||
variable.
|
||||
|
||||
* `...` -- any additional files and directories are permitted
|
||||
|
||||
## solana-install Tool
|
||||
|
||||
The `solana-install` tool is used by the user to install and update their cluster software.
|
||||
|
||||
It manages the following files and directories in the user's home directory:
|
||||
|
||||
* `~/.config/solana/install/config.yml` - user configuration and information about currently installed software version
|
||||
* `~/.local/share/solana/install/bin` - a symlink to the current release. eg, `~/.local/share/solana-update/<update-pubkey>-<manifest_signature>/bin`
|
||||
* `~/.local/share/solana/install/releases/<download_sha256>/` - contents of a release
|
||||
|
||||
### Command-line Interface
|
||||
|
||||
```text
|
||||
solana-install 0.16.0
|
||||
The solana cluster software installer
|
||||
|
||||
USAGE:
|
||||
solana-install [OPTIONS] <SUBCOMMAND>
|
||||
|
||||
FLAGS:
|
||||
-h, --help Prints help information
|
||||
-V, --version Prints version information
|
||||
|
||||
OPTIONS:
|
||||
-c, --config <PATH> Configuration file to use [default: .../Library/Preferences/solana/install.yml]
|
||||
|
||||
SUBCOMMANDS:
|
||||
deploy deploys a new update
|
||||
help Prints this message or the help of the given subcommand(s)
|
||||
info displays information about the current installation
|
||||
init initializes a new installation
|
||||
run Runs a program while periodically checking and applying software updates
|
||||
update checks for an update, and if available downloads and applies it
|
||||
```
|
||||
|
||||
```text
|
||||
solana-install-init
|
||||
initializes a new installation
|
||||
|
||||
USAGE:
|
||||
solana-install init [OPTIONS]
|
||||
|
||||
FLAGS:
|
||||
-h, --help Prints help information
|
||||
|
||||
OPTIONS:
|
||||
-d, --data_dir <PATH> Directory to store install data [default: .../Library/Application Support/solana]
|
||||
-u, --url <URL> JSON RPC URL for the solana cluster [default: http://devnet.solana.com:8899]
|
||||
-p, --pubkey <PUBKEY> Public key of the update manifest [default: 9XX329sPuskWhH4DQh6k16c87dHKhXLBZTL3Gxmve8Gp]
|
||||
```
|
||||
|
||||
```text
|
||||
solana-install-info
|
||||
displays information about the current installation
|
||||
|
||||
USAGE:
|
||||
solana-install info [FLAGS]
|
||||
|
||||
FLAGS:
|
||||
-h, --help Prints help information
|
||||
-l, --local only display local information, don't check the cluster for new updates
|
||||
```
|
||||
|
||||
```text
|
||||
solana-install-deploy
|
||||
deploys a new update
|
||||
|
||||
USAGE:
|
||||
solana-install deploy <download_url> <update_manifest_keypair>
|
||||
|
||||
FLAGS:
|
||||
-h, --help Prints help information
|
||||
|
||||
ARGS:
|
||||
<download_url> URL to the solana release archive
|
||||
<update_manifest_keypair> Keypair file for the update manifest (/path/to/keypair.json)
|
||||
```
|
||||
|
||||
```text
|
||||
solana-install-update
|
||||
checks for an update, and if available downloads and applies it
|
||||
|
||||
USAGE:
|
||||
solana-install update
|
||||
|
||||
FLAGS:
|
||||
-h, --help Prints help information
|
||||
```
|
||||
|
||||
```text
|
||||
solana-install-run
|
||||
Runs a program while periodically checking and applying software updates
|
||||
|
||||
USAGE:
|
||||
solana-install run <program_name> [program_arguments]...
|
||||
|
||||
FLAGS:
|
||||
-h, --help Prints help information
|
||||
|
||||
ARGS:
|
||||
<program_name> program to run
|
||||
<program_arguments>... arguments to supply to the program
|
||||
|
||||
The program will be restarted upon a successful software update
|
||||
```
|
||||
|
54
docs/src/implemented-proposals/leader-leader-transition.md
Normal file
54
docs/src/implemented-proposals/leader-leader-transition.md
Normal file
@ -0,0 +1,54 @@
|
||||
# Leader-to-Leader Transition
|
||||
|
||||
This design describes how leaders transition production of the PoH ledger between each other as each leader generates its own slot.
|
||||
|
||||
## Challenges
|
||||
|
||||
Current leader and the next leader are both racing to generate the final tick for the current slot. The next leader may arrive at that slot while still processing the current leader's entries.
|
||||
|
||||
The ideal scenario would be that the next leader generated its own slot right after it was able to vote for the current leader. It is very likely that the next leader will arrive at their PoH slot height before the current leader finishes broadcasting the entire block.
|
||||
|
||||
The next leader has to make the decision of attaching its own block to the last completed block, or wait to finalize the pending block. It is possible that the next leader will produce a block that proposes that the current leader failed, even though the rest of the network observes that block succeeding.
|
||||
|
||||
The current leader has incentives to start its slot as early as possible to capture economic rewards. Those incentives need to be balanced by the leader's need to attach its block to a block that has the most commitment from the rest of the network.
|
||||
|
||||
## Leader timeout
|
||||
|
||||
While a leader is actively receiving entries for the previous slot, the leader can delay broadcasting the start of its block in real time. The delay is locally configurable by each leader, and can be dynamically based on the previous leader's behavior. If the previous leader's block is confirmed by the leader's TVU before the timeout, the PoH is reset to the start of the slot and this leader produces its block immediately.
|
||||
|
||||
The downsides:
|
||||
|
||||
* Leader delays its own slot, potentially allowing the next leader more time to
|
||||
|
||||
catch up.
|
||||
|
||||
The upsides compared to guards:
|
||||
|
||||
* All the space in a block is used for entries.
|
||||
* The timeout is not fixed.
|
||||
* The timeout is local to the leader, and therefore can be clever. The leader's heuristic can take into account turbine performance.
|
||||
* This design doesn't require a ledger hard fork to update.
|
||||
* The previous leader can redundantly transmit the last entry in the block to the next leader, and the next leader can speculatively decide to trust it to generate its block without verification of the previous block.
|
||||
* The leader can speculatively generate the last tick from the last received entry.
|
||||
* The leader can speculatively process transactions and guess which ones are not going to be encoded by the previous leader. This is also a censorship attack vector. The current leader may withhold transactions that it receives from the clients so it can encode them into its own slot. Once processed, entries can be replayed into PoH quickly.
|
||||
|
||||
## Alternative design options
|
||||
|
||||
### Guard tick at the end of the slot
|
||||
|
||||
A leader does not produce entries in its block after the _penultimate tick_, which is the last tick before the first tick of the next slot. The network votes on the _last tick_, so the time difference between the _penultimate tick_ and the _last tick_ is the forced delay for the entire network, as well as the next leader before a new slot can be generated. The network can produce the _last tick_ from the _penultimate tick_.
|
||||
|
||||
If the next leader receives the _penultimate tick_ before it produces its own _first tick_, it will reset its PoH and produce the _first tick_ from the previous leader's _penultimate tick_. The rest of the network will also reset its PoH to produce the _last tick_ as the id to vote on.
|
||||
|
||||
The downsides:
|
||||
|
||||
* Every vote, and therefore confirmation, is delayed by a fixed timeout. 1 tick, or around 100ms.
|
||||
* Average case confirmation time for a transaction would be at least 50ms worse.
|
||||
* It is part of the ledger definition, so to change this behavior would require a hard fork.
|
||||
* Not all the available space is used for entries.
|
||||
|
||||
The upsides compared to leader timeout:
|
||||
|
||||
* The next leader has received all the previous entries, so it can start processing transactions without recording them into PoH.
|
||||
* The previous leader can redundantly transmit the last entry containing the _penultimate tick_ to the next leader. The next leader can speculatively generate the _last tick_ as soon as it receives the _penultimate tick_, even before verifying it.
|
||||
|
@ -0,0 +1,51 @@
|
||||
# Leader-to-Validator Transition
|
||||
|
||||
A validator typically spends its time validating blocks. If, however, a staker delegates its stake to a validator, it will occasionally be selected as a _slot leader_. As a slot leader, the validator is responsible for producing blocks during an assigned _slot_. A slot has a duration of some number of preconfigured _ticks_. The duration of those ticks are estimated with a _PoH Recorder_ described later in this document.
|
||||
|
||||
## BankFork
|
||||
|
||||
BankFork tracks changes to the bank state over a specific slot. Once the final tick has been registered the state is frozen. Any attempts to write to are rejected.
|
||||
|
||||
## Validator
|
||||
|
||||
A validator operates on many different concurrent forks of the bank state until it generates a PoH hash with a height within its leader slot.
|
||||
|
||||
## Slot Leader
|
||||
|
||||
A slot leader builds blocks on top of only one fork, the one it last voted on.
|
||||
|
||||
## PoH Recorder
|
||||
|
||||
Slot leaders and validators use a PoH Recorder for both estimating slot height and for recording transactions.
|
||||
|
||||
### PoH Recorder when Validating
|
||||
|
||||
The PoH Recorder acts as a simple VDF when validating. It tells the validator when it needs to switch to the slot leader role. Every time the validator votes on a fork, it should use the fork's latest [blockhash](terminology.md#blockhash) to re-seed the VDF. Re-seeding solves two problems. First, it synchronizes its VDF to the leader's, allowing it to more accurately determine when its leader slot begins. Second, if the previous leader goes down, all wallclock time is accounted for in the next leader's PoH stream. For example, if one block is missing when the leader starts, the block it produces should have a PoH duration of two blocks. The longer duration ensures the following leader isn't attempting to snip all the transactions from the previous leader's slot.
|
||||
|
||||
### PoH Recorder when Leading
|
||||
|
||||
A slot leader use the PoH Recorder to record transactions, locking their positions in time. The PoH hash must be derived from a previous leader's last block. If it isn't, its block will fail PoH verification and be rejected by the cluster.
|
||||
|
||||
The PoH Recorder also serves to inform the slot leader when its slot is over. The leader needs to take care not to modify its bank if recording the transaction would generate a PoH height outside its designated slot. The leader, therefore, should not commit account changes until after it generates the entry's PoH hash. When the PoH height falls outside its slot any transactions in its pipeline may be dropped or forwarded to the next leader. Forwarding is preferred, as it would minimize network congestion, allowing the cluster to advertise higher TPS capacity.
|
||||
|
||||
## Validator Loop
|
||||
|
||||
The PoH Recorder manages the transition between modes. Once a ledger is replayed, the validator can run until the recorder indicates it should be the slot leader. As a slot leader, the node can then execute and record transactions.
|
||||
|
||||
The loop is synchronized to PoH and does a synchronous start and stop of the slot leader functionality. After stopping, the validator's TVU should find itself in the same state as if a different leader had sent it the same block. The following is pseudocode for the loop:
|
||||
|
||||
1. Query the LeaderScheduler for the next assigned slot.
|
||||
2. Run the TVU over all the forks. 1. TVU will send votes to what it believes is the "best" fork. 2. After each vote, restart the PoH Recorder to run until the next assigned
|
||||
|
||||
slot.
|
||||
|
||||
3. When time to be a slot leader, start the TPU. Point it to the last fork the
|
||||
|
||||
TVU voted on.
|
||||
|
||||
4. Produce entries until the end of the slot. 1. For the duration of the slot, the TVU must not vote on other forks. 2. After the slot ends, the TPU freezes its BankFork. After freezing,
|
||||
|
||||
the TVU may resume voting.
|
||||
|
||||
5. Goto 1.
|
||||
|
94
docs/src/implemented-proposals/persistent-account-storage.md
Normal file
94
docs/src/implemented-proposals/persistent-account-storage.md
Normal file
@ -0,0 +1,94 @@
|
||||
# Persistent Account Storage
|
||||
|
||||
## Persistent Account Storage
|
||||
|
||||
The set of Accounts represent the current computed state of all the transactions that have been processed by a validator. Each validator needs to maintain this entire set. Each block that is proposed by the network represents a change to this set, and since each block is a potential rollback point the changes need to be reversible.
|
||||
|
||||
Persistent storage like NVMEs are 20 to 40 times cheaper than DDR. The problem with persistent storage is that write and read performance is much slower than DDR and care must be taken in how data is read or written to. Both reads and writes can be split between multiple storage drives and accessed in parallel. This design proposes a data structure that allows for concurrent reads and concurrent writes of storage. Writes are optimized by using an AppendVec data structure, which allows a single writer to append while allowing access to many concurrent readers. The accounts index maintains a pointer to a spot where the account was appended to every fork, thus removing the need for explicit checkpointing of state.
|
||||
|
||||
## AppendVec
|
||||
|
||||
AppendVec is a data structure that allows for random reads concurrent with a single append-only writer. Growing or resizing the capacity of the AppendVec requires exclusive access. This is implemented with an atomic `offset`, which is updated at the end of a completed append.
|
||||
|
||||
The underlying memory for an AppendVec is a memory-mapped file. Memory-mapped files allow for fast random access and paging is handled by the OS.
|
||||
|
||||
## Account Index
|
||||
|
||||
The account index is designed to support a single index for all the currently forked Accounts.
|
||||
|
||||
```text
|
||||
type AppendVecId = usize;
|
||||
|
||||
type Fork = u64;
|
||||
|
||||
struct AccountMap(Hashmap<Fork, (AppendVecId, u64)>);
|
||||
|
||||
type AccountIndex = HashMap<Pubkey, AccountMap>;
|
||||
```
|
||||
|
||||
The index is a map of account Pubkeys to a map of Forks and the location of the Account data in an AppendVec. To get the version of an account for a specific Fork:
|
||||
|
||||
```text
|
||||
/// Load the account for the pubkey.
|
||||
/// This function will load the account from the specified fork, falling back to the fork's parents
|
||||
/// * fork - a virtual Accounts instance, keyed by Fork. Accounts keep track of their parents with Forks,
|
||||
/// the persistent store
|
||||
/// * pubkey - The Account's public key.
|
||||
pub fn load_slow(&self, id: Fork, pubkey: &Pubkey) -> Option<&Account>
|
||||
```
|
||||
|
||||
The read is satisfied by pointing to a memory-mapped location in the `AppendVecId` at the stored offset. A reference can be returned without a copy.
|
||||
|
||||
### Root Forks
|
||||
|
||||
[Tower BFT](tower-bft.md) eventually selects a fork as a root fork and the fork is squashed. A squashed/root fork cannot be rolled back.
|
||||
|
||||
When a fork is squashed, all accounts in its parents not already present in the fork are pulled up into the fork by updating the indexes. Accounts with zero balance in the squashed fork are removed from fork by updating the indexes.
|
||||
|
||||
An account can be _garbage-collected_ when squashing makes it unreachable.
|
||||
|
||||
Three possible options exist:
|
||||
|
||||
* Maintain a HashSet of root forks. One is expected to be created every second. The entire tree can be garbage-collected later. Alternatively, if every fork keeps a reference count of accounts, garbage collection could occur any time an index location is updated.
|
||||
* Remove any pruned forks from the index. Any remaining forks lower in number than the root are can be considered root.
|
||||
* Scan the index, migrate any old roots into the new one. Any remaining forks lower than the new root can be deleted later.
|
||||
|
||||
## Append-only Writes
|
||||
|
||||
All the updates to Accounts occur as append-only updates. For every account update, a new version is stored in the AppendVec.
|
||||
|
||||
It is possible to optimize updates within a single fork by returning a mutable reference to an already stored account in a fork. The Bank already tracks concurrent access of accounts and guarantees that a write to a specific account fork will not be concurrent with a read to an account at that fork. To support this operation, AppendVec should implement this function:
|
||||
|
||||
```text
|
||||
fn get_mut(&self, index: u64) -> &mut T;
|
||||
```
|
||||
|
||||
This API allows for concurrent mutable access to a memory region at `index`. It relies on the Bank to guarantee exclusive access to that index.
|
||||
|
||||
## Garbage collection
|
||||
|
||||
As accounts get updated, they move to the end of the AppendVec. Once capacity has run out, a new AppendVec can be created and updates can be stored there. Eventually references to an older AppendVec will disappear because all the accounts have been updated, and the old AppendVec can be deleted.
|
||||
|
||||
To speed up this process, it's possible to move Accounts that have not been recently updated to the front of a new AppendVec. This form of garbage collection can be done without requiring exclusive locks to any of the data structures except for the index update.
|
||||
|
||||
The initial implementation for garbage collection is that once all the accounts in an AppendVec become stale versions, it gets reused. The accounts are not updated or moved around once appended.
|
||||
|
||||
## Index Recovery
|
||||
|
||||
Each bank thread has exclusive access to the accounts during append, since the accounts locks cannot be released until the data is committed. But there is no explicit order of writes between the separate AppendVec files. To create an ordering, the index maintains an atomic write version counter. Each append to the AppendVec records the index write version number for that append in the entry for the Account in the AppendVec.
|
||||
|
||||
To recover the index, all the AppendVec files can be read in any order, and the latest write version for every fork should be stored in the index.
|
||||
|
||||
## Snapshots
|
||||
|
||||
To snapshot, the underlying memory-mapped files in the AppendVec need to be flushed to disk. The index can be written out to disk as well.
|
||||
|
||||
## Performance
|
||||
|
||||
* Append-only writes are fast. SSDs and NVMEs, as well as all the OS level kernel data structures, allow for appends to run as fast as PCI or NVMe bandwidth will allow \(2,700 MB/s\).
|
||||
* Each replay and banking thread writes concurrently to its own AppendVec.
|
||||
* Each AppendVec could potentially be hosted on a separate NVMe.
|
||||
* Each replay and banking thread has concurrent read access to all the AppendVecs without blocking writes.
|
||||
* Index requires an exclusive write lock for writes. Single-thread performance for HashMap updates is on the order of 10m per second.
|
||||
* Banking and Replay stages should use 32 threads per NVMe. NVMes have optimal performance with 32 concurrent readers or writers.
|
||||
|
23
docs/src/implemented-proposals/readonly-accounts.md
Normal file
23
docs/src/implemented-proposals/readonly-accounts.md
Normal file
@ -0,0 +1,23 @@
|
||||
# Read-Only Accounts
|
||||
|
||||
This design covers the handling of readonly and writable accounts in the [runtime](../validator/runtime.md). Multiple transactions that modify the same account must be processed serially so that they are always replayed in the same order. Otherwise, this could introduce non-determinism to the ledger. Some transactions, however, only need to read, and not modify, the data in particular accounts. Multiple transactions that only read the same account can be processed in parallel, since replay order does not matter, providing a performance benefit.
|
||||
|
||||
In order to identify readonly accounts, the transaction MessageHeader structure contains `num_readonly_signed_accounts` and `num_readonly_unsigned_accounts`. Instruction `program_ids` are included in the account vector as readonly, unsigned accounts, since executable accounts likewise cannot be modified during instruction processing.
|
||||
|
||||
## Runtime handling
|
||||
|
||||
Runtime transaction processing rules need to be updated slightly. Programs still can't write or spend accounts that they do not own. But new runtime rules ensure that readonly accounts cannot be modified, even by the programs that own them.
|
||||
|
||||
Readonly accounts have the following property:
|
||||
|
||||
* Read-only access to all account fields, including lamports (cannot be credited or debited), and account data
|
||||
|
||||
Instructions that credit, debit, or modify the readonly account will fail.
|
||||
|
||||
## Account Lock Optimizations
|
||||
|
||||
The Accounts module keeps track of current locked accounts in the runtime, which separates readonly accounts from the writable accounts. The default account lock gives an account the "writable" designation, and can only be accessed by one processing thread at one time. Readonly accounts are locked by a separate mechanism, allowing for parallel reads.
|
||||
|
||||
Although not yet implemented, readonly accounts could be cached in memory and shared between all the threads executing transactions. An ideal design would hold this cache while a readonly account is referenced by any transaction moving through the runtime, and release the cache when the last transaction exits the runtime.
|
||||
|
||||
Readonly accounts could also be passed into the processor as references, saving an extra copy.
|
59
docs/src/implemented-proposals/reliable-vote-transmission.md
Normal file
59
docs/src/implemented-proposals/reliable-vote-transmission.md
Normal file
@ -0,0 +1,59 @@
|
||||
# Reliable Vote Transmission
|
||||
|
||||
Validator votes are messages that have a critical function for consensus and continuous operation of the network. Therefore it is critical that they are reliably delivered and encoded into the ledger.
|
||||
|
||||
## Challenges
|
||||
|
||||
1. Leader rotation is triggered by PoH, which is clock with high drift. So many nodes are likely to have an incorrect view if the next leader is active in realtime or not.
|
||||
2. The next leader may be easily be flooded. Thus a DDOS would not only prevent delivery of regular transactions, but also consensus messages.
|
||||
3. UDP is unreliable, and our asynchronous protocol requires any message that is transmitted to be retransmitted until it is observed in the ledger. Retransmittion could potentially cause an unintentional _thundering herd_ against the leader with a large number of validators. Worst case flood would be `(num_nodes * num_retransmits)`.
|
||||
4. Tracking if the vote has been transmitted or not via the ledger does not guarantee it will appear in a confirmed block. The current observed block may be unrolled. Validators would need to maintain state for each vote and fork.
|
||||
|
||||
## Design
|
||||
|
||||
1. Send votes as a push message through gossip. This ensures delivery of the vote to all the next leaders, not just the next future one.
|
||||
2. Leaders will read the Crds table for new votes and encode any new received votes into the blocks they propose. This allows for validator votes to be included in rollback forks by all the future leaders.
|
||||
3. Validators that receive votes in the ledger will add them to their local crds table, not as a push request, but simply add them to the table. This shortcuts the push message protocol, so the validation messages do not need to be retransmitted twice around the network.
|
||||
4. CrdsValue for vote should look like this `Votes(Vec<Transaction>)`
|
||||
|
||||
Each vote transaction should maintain a `wallclock` in its data. The merge strategy for Votes will keep the last N set of votes as configured by the local client. For push/pull the vector is traversed recursively and each Transaction is treated as an individual CrdsValue with its own local wallclock and signature.
|
||||
|
||||
Gossip is designed for efficient propagation of state. Messages that are sent through gossip-push are batched and propagated with a minimum spanning tree to the rest of the network. Any partial failures in the tree are actively repaired with the gossip-pull protocol while minimizing the amount of data transfered between any nodes.
|
||||
|
||||
## How this design solves the Challenges
|
||||
|
||||
1. Because there is no easy way for validators to be in sync with leaders on the leader's "active" state, gossip allows for eventual delivery regardless of that state.
|
||||
2. Gossip will deliver the messages to all the subsequent leaders, so if the current leader is flooded the next leader would have already received these votes and is able to encode them.
|
||||
3. Gossip minimizes the number of requests through the network by maintaining an efficient spanning tree, and using bloom filters to repair state. So retransmit back-off is not necessary and messages are batched.
|
||||
4. Leaders that read the crds table for votes will encode all the new valid votes that appear in the table. Even if this leader's block is unrolled, the next leader will try to add the same votes without any additional work done by the validator. Thus ensuring not only eventual delivery, but eventual encoding into the ledger.
|
||||
|
||||
## Performance
|
||||
|
||||
1. Worst case propagation time to the next leader is Log\(N\) hops with a base depending on the fanout. With our current default fanout of 6, it is about 6 hops to 20k nodes.
|
||||
2. The leader should receive 20k validation votes aggregated by gossip-push into MTU-sized shreds. Which would reduce the number of packets for 20k network to 80 shreds.
|
||||
3. Each validators votes is replicated across the entire network. To maintain a queue of 5 previous votes the Crds table would grow by 25 megabytes. `(20,000 nodes * 256 bytes * 5)`.
|
||||
|
||||
## Two step implementation rollout
|
||||
|
||||
Initially the network can perform reliably with just 1 vote transmitted and maintained through the network with the current Vote implementation. For small networks a fanout of 6 is sufficient. With small network the memory and push overhead is minor.
|
||||
|
||||
### Sub 1k validator network
|
||||
|
||||
1. Crds just maintains the validators latest vote.
|
||||
2. Votes are pushed and retransmitted regardless if they are appearing in the ledger.
|
||||
3. Fanout of 6.
|
||||
4. Worst case 256kb memory overhead per node.
|
||||
5. Worst case 4 hops to propagate to every node.
|
||||
6. Leader should receive the entire validator vote set in 4 push message shreds.
|
||||
|
||||
### Sub 20k network
|
||||
|
||||
Everything above plus the following:
|
||||
|
||||
1. CRDS table maintains a vector of 5 latest validator votes.
|
||||
2. Votes encode a wallclock. CrdsValue::Votes is a type that recurses into the transaction vector for all the gossip protocols.
|
||||
3. Increase fanout to 20.
|
||||
4. Worst case 25mb memory overhead per node.
|
||||
5. Sub 4 hops worst case to deliver to the entire network.
|
||||
6. 80 shreds received by the leader for all the validator messages.
|
||||
|
52
docs/src/implemented-proposals/rent.md
Normal file
52
docs/src/implemented-proposals/rent.md
Normal file
@ -0,0 +1,52 @@
|
||||
# Rent
|
||||
|
||||
Accounts on Solana may have owner-controlled state \(`Account::data`\) that's separate from the account's balance \(`Account::lamports`\). Since validators on the network need to maintain a working copy of this state in memory, the network charges a time-and-space based fee for this resource consumption, also known as Rent.
|
||||
|
||||
## Two-tiered rent regime
|
||||
|
||||
Accounts which maintain a minimum balance equivalent to 2 years of rent payments are exempt. Accounts whose balance falls below this threshold are charged rent at a rate specified in genesis, in lamports per kilobyte-year. The network charges rent on a per-epoch basis, in credit for the next epoch \(but in arrears when necessary\), and `Account::rent_epoch` keeps track of the next time rent should be collected from the account.
|
||||
|
||||
## Collecting rent
|
||||
|
||||
Rent is due at account creation time for one epoch's worth of time, and the new account has `Account::rent_epoch` of `current_epoch + 1`. After that, the bank deducts rent from accounts during normal transaction processing as part of the load phase.
|
||||
|
||||
If the account is in the exempt regime, `Account::rent_epoch` is simply pushed to `current_epoch + 1`.
|
||||
|
||||
If the account is non-exempt, the difference between the next epoch and `Account::rent_epoch` is used to calculate the amount of rent owed by this account \(via `Rent::due()`\). Any fractional lamports of the calculation are truncated. Rent due is deducted from `Account::lamports` and `Account::rent_epoch` is updated to the next epoch. If the amount of rent due is less than one lamport, no changes are made to the account.
|
||||
|
||||
Accounts whose balance is insufficient to satisfy the rent that would be due simply fail to load.
|
||||
|
||||
A percentage of the rent collected is destroyed. The rest is distributed to validator accounts by stake weight, a la transaction fees, at the end of every slot.
|
||||
|
||||
## Read-only accounts
|
||||
|
||||
Read-only accounts are not being charged rent in current implementation.
|
||||
|
||||
## Design considerations, others considered
|
||||
|
||||
Under this design, it is possible to have accounts that linger, never get touched, and never have to pay rent. `Noop` instructions that name these accounts can be used to "garbage collect", but it'd also be possible for accounts that never get touched to migrate out of a validator's working set, thereby reducing memory consumption and obviating the need to charge rent.
|
||||
|
||||
### Ad-hoc collection
|
||||
|
||||
Collecting rent on an as-needed basis \(i.e. whenever accounts were loaded/accessed\) was considered. The issues with such an approach are:
|
||||
|
||||
* accounts loaded as "credit only" for a transaction could very reasonably be expected to have rent due,
|
||||
|
||||
but would not be writable during any such transaction
|
||||
|
||||
* a mechanism to "beat the bushes" \(i.e. go find accounts that need to pay rent\) is desirable,
|
||||
|
||||
lest accounts that are loaded infrequently get a free ride
|
||||
|
||||
### System instruction for collecting rent
|
||||
|
||||
Collecting rent via a system instruction was considered, as it would naturally have distributed rent to active and stake-weighted nodes and could have been done incrementally. However:
|
||||
|
||||
* it would have adversely affected network throughput
|
||||
* it would require special-casing by the runtime, as accounts with non-SystemProgram owners may be debited by this instruction
|
||||
* someone would have to issue the transactions
|
||||
|
||||
### Account scans on every epoch
|
||||
|
||||
Scanning the entire Bank for accounts that owe rent at the beginning of each epoch was considered. This would have been an expensive operation, and would require that the entire current state of the network be present on every validator at the beginning of each epoch.
|
||||
|
104
docs/src/implemented-proposals/repair-service.md
Normal file
104
docs/src/implemented-proposals/repair-service.md
Normal file
@ -0,0 +1,104 @@
|
||||
# Repair Service
|
||||
|
||||
## Repair Service
|
||||
|
||||
The RepairService is in charge of retrieving missing shreds that failed to be
|
||||
delivered by primary communication protocols like Turbine. It is in charge of
|
||||
managing the protocols described below in the `Repair Protocols` section below.
|
||||
|
||||
## Challenges:
|
||||
|
||||
1\) Validators can fail to receive particular shreds due to network failures
|
||||
|
||||
2\) Consider a scenario where blockstore contains the set of slots {1, 3, 5}.
|
||||
Then Blockstore receives shreds for some slot 7, where for each of the shreds
|
||||
b, b.parent == 6, so then the parent-child relation 6 -> 7 is stored in
|
||||
blockstore. However, there is no way to chain these slots to any of the
|
||||
existing banks in Blockstore, and thus the `Shred Repair` protocol will not
|
||||
repair these slots. If these slots happen to be part of the main chain, this
|
||||
will halt replay progress on this node.
|
||||
|
||||
## Repair-related primitives
|
||||
Epoch Slots:
|
||||
Each validator advertises separately on gossip thhe various parts of an
|
||||
`Epoch Slots`:
|
||||
* The `stash`: An epoch-long compressed set of all completed slots.
|
||||
* The `cache`: The Run-length Encoding (RLE) of the latest `N` completed
|
||||
slots starting from some some slot `M`, where `N` is the number of slots
|
||||
that will fit in an MTU-sized packet.
|
||||
|
||||
`Epoch Slots` in gossip are updated every time a validator receives a
|
||||
complete slot within the epoch. Completed slots are detected by blockstore
|
||||
and sent over a channel to RepairService. It is important to note that we
|
||||
know that by the time a slot `X` is complete, the epoch schedule must exist
|
||||
for the epoch that contains slot `X` because WindowService will reject
|
||||
shreds for unconfirmed epochs.
|
||||
|
||||
Every `N/2` completed slots, the oldest `N/2` slots are moved from the
|
||||
`cache` into the `stash`. The base value `M` for the RLE should also
|
||||
be updated.
|
||||
|
||||
## Repair Request Protocols
|
||||
|
||||
The repair protocol makes best attempts to progress the forking structure of
|
||||
Blockstore.
|
||||
|
||||
The different protocol strategies to address the above challenges:
|
||||
|
||||
1. Shred Repair \(Addresses Challenge \#1\): This is the most basic repair
|
||||
protocol, with the purpose of detecting and filling "holes" in the ledger.
|
||||
Blockstore tracks the latest root slot. RepairService will then periodically
|
||||
iterate every fork in blockstore starting from the root slot, sending repair
|
||||
requests to validators for any missing shreds. It will send at most some `N`
|
||||
repair reqeusts per iteration. Shred repair should prioritize repairing
|
||||
forks based on the leader's fork weight. Validators should only send repair
|
||||
requests to validators who have marked that slot as completed in their
|
||||
EpochSlots. Validators should prioritize repairing shreds in each slot
|
||||
that they are responsible for retransmitting through turbine. Validators can
|
||||
compute which shreds they are responsible for retransmitting because the
|
||||
seed for turbine is based on leader id, slot, and shred index.
|
||||
|
||||
Note: Validators will only accept shreds within the current verifiable
|
||||
epoch \(epoch the validator has a leader schedule for\).
|
||||
|
||||
2. Preemptive Slot Repair \(Addresses Challenge \#2\): The goal of this
|
||||
protocol is to discover the chaining relationship of "orphan" slots that do not
|
||||
currently chain to any known fork. Shred repair should prioritize repairing
|
||||
orphan slots based on the leader's fork weight.
|
||||
* Blockstore will track the set of "orphan" slots in a separate column family.
|
||||
* RepairService will periodically make `Orphan` requests for each of
|
||||
the orphans in blockstore.
|
||||
|
||||
`Orphan(orphan)` request - `orphan` is the orphan slot that the
|
||||
requestor wants to know the parents of `Orphan(orphan)` response -
|
||||
The highest shreds for each of the first `N` parents of the requested
|
||||
`orphan`
|
||||
|
||||
On receiving the responses `p`, where `p` is some shred in a parent slot,
|
||||
validators will:
|
||||
|
||||
* Insert an empty `SlotMeta` in blockstore for `p.slot` if it doesn't
|
||||
already exist.
|
||||
* If `p.slot` does exist, update the parent of `p` based on `parents`
|
||||
|
||||
Note: that once these empty slots are added to blockstore, the
|
||||
`Shred Repair` protocol should attempt to fill those slots.
|
||||
|
||||
Note: Validators will only accept responses containing shreds within the
|
||||
current verifiable epoch \(epoch the validator has a leader schedule
|
||||
for\).
|
||||
|
||||
Validators should try to send orphan requests to validators who have marked that
|
||||
orphan as completed in their EpochSlots. If no such validators exist, then
|
||||
randomly select a validator in a stake-weighted fashion.
|
||||
|
||||
## Repair Response Protocol
|
||||
|
||||
When a validator receives a request for a shred `S`, they respond with the
|
||||
shred if they have it.
|
||||
|
||||
When a validator receives a shred through a repair response, they check
|
||||
`EpochSlots` to see if <= `1/3` of the network has marked this slot as
|
||||
completed. If so, they resubmit this shred through its associated turbine
|
||||
path, but only if this validator has not retransmitted this shred before.
|
||||
|
55
docs/src/implemented-proposals/snapshot-verification.md
Normal file
55
docs/src/implemented-proposals/snapshot-verification.md
Normal file
@ -0,0 +1,55 @@
|
||||
# Snapshot Verification
|
||||
|
||||
## Problem
|
||||
|
||||
When a validator boots up from a snapshot, it needs a way to verify the account set matches what the rest of the network sees quickly. A potential
|
||||
attacker could give the validator an incorrect state, and then try to convince it to accept a transaction that would otherwise be rejected.
|
||||
|
||||
## Solution
|
||||
|
||||
Currently the bank hash is derived from hashing the delta state of the accounts in a slot which is then combined with the previous bank hash value.
|
||||
The problem with this is that the list of hashes will grow on the order of the number of slots processed by the chain and become a burden to both
|
||||
transmit and verify successfully.
|
||||
|
||||
Another naive method could be to create a merkle tree of the account state. This has the downside that with each account update, the merkle tree
|
||||
would have to be recomputed from the entire account state of all live accounts in the system.
|
||||
|
||||
To verify the snapshot, we do the following:
|
||||
|
||||
On account store of non-zero lamport accounts, we hash the following data:
|
||||
|
||||
* Account owner
|
||||
* Account data
|
||||
* Account pubkey
|
||||
* Account lamports balance
|
||||
* Fork the account is stored on
|
||||
|
||||
Use this resulting hash value as input to an expansion function which expands the hash value into an image value.
|
||||
The function will create a 440 byte block of data where the first 32 bytes are the hash value, and the next 440 - 32 bytes are
|
||||
generated from a Chacha RNG with the hash as the seed.
|
||||
|
||||
The account images are then combined with xor. The previous account value will be xored into the state and the new account value also xored into the state.
|
||||
|
||||
Voting and sysvar hash values occur with the hash of the resulting full image value.
|
||||
|
||||
On validator boot, when it loads from a snapshot, it would verify the hash value with the accounts set. It would then
|
||||
use SPV to display the percentage of the network that voted for the hash value given.
|
||||
|
||||
The resulting value can be verified by a validator to be the result of xoring all current account states together.
|
||||
|
||||
A snapshot must be purged of zero lamport accounts before creation and during verify since the zero lamport accounts do not affect the hash value but may cause
|
||||
a validator bank to read that an account is not present when it really should be.
|
||||
|
||||
An attack on the xor state could be made to influence its value:
|
||||
|
||||
Thus the 440 byte image size comes from this paper, avoiding xor collision with 0 \(or thus any other given bit pattern\): \[[https://link.springer.com/content/pdf/10.1007%2F3-540-45708-9\_19.pdf](https://link.springer.com/content/pdf/10.1007%2F3-540-45708-9_19.pdf)\]
|
||||
|
||||
The math provides 128 bit security in this case:
|
||||
|
||||
```text
|
||||
O(k * 2^(n/(1+lg(k)))
|
||||
k=2^40 accounts
|
||||
n=440
|
||||
2^(40) * 2^(448 * 8 / 41) ~= O(2^(128))
|
||||
```
|
||||
|
56
docs/src/implemented-proposals/staking-rewards.md
Normal file
56
docs/src/implemented-proposals/staking-rewards.md
Normal file
@ -0,0 +1,56 @@
|
||||
# Staking Rewards
|
||||
|
||||
A Proof of Stake \(PoS\), \(i.e. using in-protocol asset, SOL, to provide secure consensus\) design is outlined here. Solana implements a proof of stake reward/security scheme for validator nodes in the cluster. The purpose is threefold:
|
||||
|
||||
* Align validator incentives with that of the greater cluster through
|
||||
|
||||
skin-in-the-game deposits at risk
|
||||
|
||||
* Avoid 'nothing at stake' fork voting issues by implementing slashing rules
|
||||
|
||||
aimed at promoting fork convergence
|
||||
|
||||
* Provide an avenue for validator rewards provided as a function of validator
|
||||
|
||||
participation in the cluster.
|
||||
|
||||
While many of the details of the specific implementation are currently under consideration and are expected to come into focus through specific modeling studies and parameter exploration on the Solana testnet, we outline here our current thinking on the main components of the PoS system. Much of this thinking is based on the current status of Casper FFG, with optimizations and specific attributes to be modified as is allowed by Solana's Proof of History \(PoH\) blockchain data structure.
|
||||
|
||||
## General Overview
|
||||
|
||||
Solana's ledger validation design is based on a rotating, stake-weighted selected leader broadcasting transactions in a PoH data structure to validating nodes. These nodes, upon receiving the leader's broadcast, have the opportunity to vote on the current state and PoH height by signing a transaction into the PoH stream.
|
||||
|
||||
To become a Solana validator, one must deposit/lock-up some amount of SOL in a contract. This SOL will not be accessible for a specific time period. The precise duration of the staking lockup period has not been determined. However we can consider three phases of this time for which specific parameters will be necessary:
|
||||
|
||||
* _Warm-up period_: which SOL is deposited and inaccessible to the node,
|
||||
|
||||
however PoH transaction validation has not begun. Most likely on the order of
|
||||
|
||||
days to weeks
|
||||
|
||||
* _Validation period_: a minimum duration for which the deposited SOL will be
|
||||
|
||||
inaccessible, at risk of slashing \(see slashing rules below\) and earning
|
||||
|
||||
rewards for the validator participation. Likely duration of months to a
|
||||
|
||||
year.
|
||||
|
||||
* _Cool-down period_: a duration of time following the submission of a
|
||||
|
||||
'withdrawal' transaction. During this period validation responsibilities have
|
||||
|
||||
been removed and the funds continue to be inaccessible. Accumulated rewards
|
||||
|
||||
should be delivered at the end of this period, along with the return of the
|
||||
|
||||
initial deposit.
|
||||
|
||||
Solana's trustless sense of time and ordering provided by its PoH data structure, along with its [turbine](https://www.youtube.com/watch?v=qt_gDRXHrHQ&t=1s) data broadcast and transmission design, should provide sub-second transaction confirmation times that scale with the log of the number of nodes in the cluster. This means we shouldn't have to restrict the number of validating nodes with a prohibitive 'minimum deposits' and expect nodes to be able to become validators with nominal amounts of SOL staked. At the same time, Solana's focus on high-throughput should create incentive for validation clients to provide high-performant and reliable hardware. Combined with potential a minimum network speed threshold to join as a validation-client, we expect a healthy validation delegation market to emerge. To this end, Solana's testnet will lead into a "Tour de SOL" validation-client competition, focusing on throughput and uptime to rank and reward testnet validators.
|
||||
|
||||
## Penalties
|
||||
|
||||
As discussed in the [Economic Design](../implemented-proposals/ed_overview/) section, annual validator interest rates are to be specified as a function of total percentage of circulating supply that has been staked. The cluster rewards validators who are online and actively participating in the validation process throughout the entirety of their _validation period_. For validators that go offline/fail to validate transactions during this period, their annual reward is effectively reduced.
|
||||
|
||||
Similarly, we may consider an algorithmic reduction in a validator's active amount staked amount in the case that they are offline. I.e. if a validator is inactive for some amount of time, either due to a partition or otherwise, the amount of their stake that is considered ‘active’ \(eligible to earn rewards\) may be reduced. This design would be structured to help long-lived partitions to eventually reach finality on their respective chains as the % of non-voting total stake is reduced over time until a supermajority can be achieved by the active validators in each partition. Similarly, upon re-engaging, the ‘active’ amount staked will come back online at some defined rate. Different rates of stake reduction may be considered depending on the size of the partition/active set.
|
||||
|
52
docs/src/implemented-proposals/testing-programs.md
Normal file
52
docs/src/implemented-proposals/testing-programs.md
Normal file
@ -0,0 +1,52 @@
|
||||
# Testing Programs
|
||||
|
||||
Applications send transactions to a Solana cluster and query validators to confirm the transactions were processed and to check each transaction's result. When the cluster doesn't behave as anticipated, it could be for a number of reasons:
|
||||
|
||||
* The program is buggy
|
||||
* The BPF loader rejected an unsafe program instruction
|
||||
* The transaction was too big
|
||||
* The transaction was invalid
|
||||
* The Runtime tried to execute the transaction when another one was accessing
|
||||
|
||||
the same account
|
||||
|
||||
* The network dropped the transaction
|
||||
* The cluster rolled back the ledger
|
||||
* A validator responded to queries maliciously
|
||||
|
||||
## The AsyncClient and SyncClient Traits
|
||||
|
||||
To troubleshoot, the application should retarget a lower-level component, where fewer errors are possible. Retargeting can be done with different implementations of the AsyncClient and SyncClient traits.
|
||||
|
||||
Components implement the following primary methods:
|
||||
|
||||
```text
|
||||
trait AsyncClient {
|
||||
fn async_send_transaction(&self, transaction: Transaction) -> io::Result<Signature>;
|
||||
}
|
||||
|
||||
trait SyncClient {
|
||||
fn get_signature_status(&self, signature: &Signature) -> Result<Option<transaction::Result<()>>>;
|
||||
}
|
||||
```
|
||||
|
||||
Users send transactions and asynchrounously and synchrounously await results.
|
||||
|
||||
### ThinClient for Clusters
|
||||
|
||||
The highest level implementation, ThinClient, targets a Solana cluster, which may be a deployed testnet or a local cluster running on a development machine.
|
||||
|
||||
### TpuClient for the TPU
|
||||
|
||||
The next level is the TPU implementation, which is not yet implemented. At the TPU level, the application sends transactions over Rust channels, where there can be no surprises from network queues or dropped packets. The TPU implements all "normal" transaction errors. It does signature verification, may report account-in-use errors, and otherwise results in the ledger, complete with proof of history hashes.
|
||||
|
||||
## Low-level testing
|
||||
|
||||
### BankClient for the Bank
|
||||
|
||||
Below the TPU level is the Bank. The Bank doesn't do signature verification or generate a ledger. The Bank is a convenient layer at which to test new on-chain programs. It allows developers to toggle between native program implementations and BPF-compiled variants. No need for the Transact trait here. The Bank's API is synchronous.
|
||||
|
||||
## Unit-testing with the Runtime
|
||||
|
||||
Below the Bank is the Runtime. The Runtime is the ideal test environment for unit-testing. By statically linking the Runtime into a native program implementation, the developer gains the shortest possible edit-compile-run loop. Without any dynamic linking, stack traces include debug symbols and program errors are straightforward to troubleshoot.
|
||||
|
136
docs/src/implemented-proposals/tower-bft.md
Normal file
136
docs/src/implemented-proposals/tower-bft.md
Normal file
@ -0,0 +1,136 @@
|
||||
# Tower BFT
|
||||
|
||||
This design describes Solana's _Tower BFT_ algorithm. It addresses the following problems:
|
||||
|
||||
* Some forks may not end up accepted by the supermajority of the cluster, and voters need to recover from voting on such forks.
|
||||
* Many forks may be votable by different voters, and each voter may see a different set of votable forks. The selected forks should eventually converge for the cluster.
|
||||
* Reward based votes have an associated risk. Voters should have the ability to configure how much risk they take on.
|
||||
* The [cost of rollback](tower-bft.md#cost-of-rollback) needs to be computable. It is important to clients that rely on some measurable form of Consistency. The costs to break consistency need to be computable, and increase super-linearly for older votes.
|
||||
* ASIC speeds are different between nodes, and attackers could employ Proof of History ASICS that are much faster than the rest of the cluster. Consensus needs to be resistant to attacks that exploit the variability in Proof of History ASIC speed.
|
||||
|
||||
For brevity this design assumes that a single voter with a stake is deployed as an individual validator in the cluster.
|
||||
|
||||
## Time
|
||||
|
||||
The Solana cluster generates a source of time via a Verifiable Delay Function we are calling [Proof of History](../cluster/synchronization.md).
|
||||
|
||||
Proof of History is used to create a deterministic round robin schedule for all the active leaders. At any given time only 1 leader, which can be computed from the ledger itself, can propose a fork. For more details, see [fork generation](../cluster/fork-generation.md) and [leader rotation](../cluster/leader-rotation.md).
|
||||
|
||||
## Lockouts
|
||||
|
||||
The purpose of the lockout is to force a validator to commit opportunity cost to a specific fork. Lockouts are measured in slots, and therefor represent a real-time forced delay that a validator needs to wait before breaking the commitment to a fork.
|
||||
|
||||
Validators that violate the lockouts and vote for a diverging fork within the lockout should be punished. The proposed punishment is to slash the validator stake if a concurrent vote within a lockout for a non-descendant fork can be proven to the cluster.
|
||||
|
||||
## Algorithm
|
||||
|
||||
The basic idea to this approach is to stack consensus votes and double lockouts. Each vote in the stack is a confirmation of a fork. Each confirmed fork is an ancestor of the fork above it. Each vote has a `lockout` in units of slots before the validator can submit a vote that does not contain the confirmed fork as an ancestor.
|
||||
|
||||
When a vote is added to the stack, the lockouts of all the previous votes in the stack are doubled \(more on this in [Rollback](tower-bft.md#Rollback)\). With each new vote, a validator commits the previous votes to an ever-increasing lockout. At 32 votes we can consider the vote to be at `max lockout` any votes with a lockout equal to or above `1<<32` are dequeued \(FIFO\). Dequeuing a vote is the trigger for a reward. If a vote expires before it is dequeued, it and all the votes above it are popped \(LIFO\) from the vote stack. The validator needs to start rebuilding the stack from that point.
|
||||
|
||||
### Rollback
|
||||
|
||||
Before a vote is pushed to the stack, all the votes leading up to vote with a lower lock time than the new vote are popped. After rollback lockouts are not doubled until the validator catches up to the rollback height of votes.
|
||||
|
||||
For example, a vote stack with the following state:
|
||||
|
||||
| vote | vote time | lockout | lock expiration time |
|
||||
| ---: | ---: | ---: | ---: |
|
||||
| 4 | 4 | 2 | 6 |
|
||||
| 3 | 3 | 4 | 7 |
|
||||
| 2 | 2 | 8 | 10 |
|
||||
| 1 | 1 | 16 | 17 |
|
||||
|
||||
_Vote 5_ is at time 9, and the resulting state is
|
||||
|
||||
| vote | vote time | lockout | lock expiration time |
|
||||
| ---: | ---: | ---: | ---: |
|
||||
| 5 | 9 | 2 | 11 |
|
||||
| 2 | 2 | 8 | 10 |
|
||||
| 1 | 1 | 16 | 17 |
|
||||
|
||||
_Vote 6_ is at time 10
|
||||
|
||||
| vote | vote time | lockout | lock expiration time |
|
||||
| ---: | ---: | ---: | ---: |
|
||||
| 6 | 10 | 2 | 12 |
|
||||
| 5 | 9 | 4 | 13 |
|
||||
| 2 | 2 | 8 | 10 |
|
||||
| 1 | 1 | 16 | 17 |
|
||||
|
||||
At time 10 the new votes caught up to the previous votes. But _vote 2_ expires at 10, so the when _vote 7_ at time 11 is applied the votes including and above _vote 2_ will be popped.
|
||||
|
||||
| vote | vote time | lockout | lock expiration time |
|
||||
| ---: | ---: | ---: | ---: |
|
||||
| 7 | 11 | 2 | 13 |
|
||||
| 1 | 1 | 16 | 17 |
|
||||
|
||||
The lockout for vote 1 will not increase from 16 until the stack contains 5 votes.
|
||||
|
||||
### Slashing and Rewards
|
||||
|
||||
Validators should be rewarded for selecting the fork that the rest of the cluster selected as often as possible. This is well-aligned with generating a reward when the vote stack is full and the oldest vote needs to be dequeued. Thus a reward should be generated for each successful dequeue.
|
||||
|
||||
### Cost of Rollback
|
||||
|
||||
Cost of rollback of _fork A_ is defined as the cost in terms of lockout time to the validator to confirm any other fork that does not include _fork A_ as an ancestor.
|
||||
|
||||
The **Economic Finality** of _fork A_ can be calculated as the loss of all the rewards from rollback of _fork A_ and its descendants, plus the opportunity cost of reward due to the exponentially growing lockout of the votes that have confirmed _fork A_.
|
||||
|
||||
### Thresholds
|
||||
|
||||
Each validator can independently set a threshold of cluster commitment to a fork before that validator commits to a fork. For example, at vote stack index 7, the lockout is 256 time units. A validator may withhold votes and let votes 0-7 expire unless the vote at index 7 has at greater than 50% commitment in the cluster. This allows each validator to independently control how much risk to commit to a fork. Committing to forks at a higher frequency would allow the validator to earn more rewards.
|
||||
|
||||
### Algorithm parameters
|
||||
|
||||
The following parameters need to be tuned:
|
||||
|
||||
* Number of votes in the stack before dequeue occurs \(32\).
|
||||
* Rate of growth for lockouts in the stack \(2x\).
|
||||
* Starting default lockout \(2\).
|
||||
* Threshold depth for minimum cluster commitment before committing to the fork \(8\).
|
||||
* Minimum cluster commitment size at threshold depth \(50%+\).
|
||||
|
||||
### Free Choice
|
||||
|
||||
A "Free Choice" is an unenforcible validator action. There is no way for the protocol to encode and enforce these actions since each validator can modify the code and adjust the algorithm. A validator that maximizes self-reward over all possible futures should behave in such a way that the system is stable, and the local greedy choice should result in a greedy choice over all possible futures. A set of validator that are engaging in choices to disrupt the protocol should be bound by their stake weight to the denial of service. Two options exits for validator:
|
||||
|
||||
* a validator can outrun previous validator in virtual generation and submit a concurrent fork
|
||||
* a validator can withhold a vote to observe multiple forks before voting
|
||||
|
||||
In both cases, the validator in the cluster have several forks to pick from concurrently, even though each fork represents a different height. In both cases it is impossible for the protocol to detect if the validator behavior is intentional or not.
|
||||
|
||||
### Greedy Choice for Concurrent Forks
|
||||
|
||||
When evaluating multiple forks, each validator should use the following rules:
|
||||
|
||||
1. Forks must satisfy the _Threshold_ rule.
|
||||
2. Pick the fork that maximizes the total cluster lockout time for all the ancestor forks.
|
||||
3. Pick the fork that has the greatest amount of cluster transaction fees.
|
||||
4. Pick the latest fork in terms of PoH.
|
||||
|
||||
Cluster transaction fees are fees that are deposited to the mining pool as described in the [Staking Rewards](staking-rewards.md) section.
|
||||
|
||||
## PoH ASIC Resistance
|
||||
|
||||
Votes and lockouts grow exponentially while ASIC speed up is linear. There are two possible attack vectors involving a faster ASIC.
|
||||
|
||||
### ASIC censorship
|
||||
|
||||
An attacker generates a concurrent fork that outruns previous leaders in an effort to censor them. A fork proposed by this attacker will be available concurrently with the next available leader. For nodes to pick this fork it must satisfy the _Greedy Choice_ rule.
|
||||
|
||||
1. Fork must have equal number of votes for the ancestor fork.
|
||||
2. Fork cannot be so far a head as to cause expired votes.
|
||||
3. Fork must have a greater amount of cluster transaction fees.
|
||||
|
||||
This attack is then limited to censoring the previous leaders fees, and individual transactions. But it cannot halt the cluster, or reduce the validator set compared to the concurrent fork. Fee censorship is limited to access fees going to the leaders but not the validators.
|
||||
|
||||
### ASIC Rollback
|
||||
|
||||
An attacker generates a concurrent fork from an older block to try to rollback the cluster. In this attack the concurrent fork is competing with forks that have already been voted on. This attack is limited by the exponential growth of the lockouts.
|
||||
|
||||
* 1 vote has a lockout of 2 slots. Concurrent fork must be at least 2 slots ahead, and be produced in 1 slot. Therefore requires an ASIC 2x faster.
|
||||
* 2 votes have a lockout of 4 slots. Concurrent fork must be at least 4 slots ahead and produced in 2 slots. Therefore requires an ASIC 2x faster.
|
||||
* 3 votes have a lockout of 8 slots. Concurrent fork must be at least 8 slots ahead and produced in 3 slots. Therefore requires an ASIC 2.6x faster.
|
||||
* 10 votes have a lockout of 1024 slots. 1024/10, or 102.4x faster ASIC.
|
||||
* 20 votes have a lockout of 2^20 slots. 2^20/20, or 52,428.8x faster ASIC.
|
30
docs/src/implemented-proposals/transaction-fees.md
Normal file
30
docs/src/implemented-proposals/transaction-fees.md
Normal file
@ -0,0 +1,30 @@
|
||||
# Deterministic Transaction Fees
|
||||
|
||||
Transactions currently include a fee field that indicates the maximum fee field a slot leader is permitted to charge to process a transaction. The cluster, on the other hand, agrees on a minimum fee. If the network is congested, the slot leader may prioritize the transactions offering higher fees. That means the client won't know how much was collected until the transaction is confirmed by the cluster and the remaining balance is checked. It smells of exactly what we dislike about Ethereum's "gas", non-determinism.
|
||||
|
||||
## Congestion-driven fees
|
||||
|
||||
Each validator uses _signatures per slot_ \(SPS\) to estimate network congestion and _SPS target_ to estimate the desired processing capacity of the cluster. The validator learns the SPS target from the genesis config, whereas it calculates SPS from recently processed transactions. The genesis config also defines a target `lamports_per_signature`, which is the fee to charge per signature when the cluster is operating at _SPS target_.
|
||||
|
||||
## Calculating fees
|
||||
|
||||
The client uses the JSON RPC API to query the cluster for the current fee parameters. Those parameters are tagged with a blockhash and remain valid until that blockhash is old enough to be rejected by the slot leader.
|
||||
|
||||
Before sending a transaction to the cluster, a client may submit the transaction and fee account data to an SDK module called the _fee calculator_. So long as the client's SDK version matches the slot leader's version, the client is assured that its account will be changed exactly the same number of lamports as returned by the fee calculator.
|
||||
|
||||
## Fee Parameters
|
||||
|
||||
In the first implementation of this design, the only fee parameter is `lamports_per_signature`. The more signatures the cluster needs to verify, the higher the fee. The exact number of lamports is determined by the ratio of SPS to the SPS target. At the end of each slot, the cluster lowers `lamports_per_signature` when SPS is below the target and raises it when above the target. The minimum value for `lamports_per_signature` is 50% of the target `lamports_per_signature` and the maximum value is 10x the target \`lamports\_per\_signature'
|
||||
|
||||
Future parameters might include:
|
||||
|
||||
* `lamports_per_pubkey` - cost to load an account
|
||||
* `lamports_per_slot_distance` - higher cost to load very old accounts
|
||||
* `lamports_per_byte` - cost per size of account loaded
|
||||
* `lamports_per_bpf_instruction` - cost to run a program
|
||||
|
||||
## Attacks
|
||||
|
||||
### Hijacking the SPS Target
|
||||
|
||||
A group of validators can centralize the cluster if they can convince it to raise the SPS Target above a point where the rest of the validators can keep up. Raising the target will cause fees to drop, presumably creating more demand and therefore higher TPS. If the validator doesn't have hardware that can process that many transactions that fast, its confirmation votes will eventually get so long that the cluster will be forced to boot it.
|
113
docs/src/implemented-proposals/validator-timestamp-oracle.md
Normal file
113
docs/src/implemented-proposals/validator-timestamp-oracle.md
Normal file
@ -0,0 +1,113 @@
|
||||
# Validator Timestamp Oracle
|
||||
|
||||
Third-party users of Solana sometimes need to know the real-world time a block
|
||||
was produced, generally to meet compliance requirements for external auditors or
|
||||
law enforcement. This proposal describes a validator timestamp oracle that
|
||||
would allow a Solana cluster to satisfy this need.
|
||||
|
||||
The general outline of the proposed implementation is as follows:
|
||||
|
||||
- At regular intervals, each validator records its observed time for a known slot
|
||||
on-chain (via a Timestamp added to a slot Vote)
|
||||
- A client can request a block time for a rooted block using the `getBlockTime`
|
||||
RPC method. When a client requests a timestamp for block N:
|
||||
|
||||
1. A validator determines a "cluster" timestamp for a recent timestamped slot
|
||||
before block N by observing all the timestamped Vote instructions recorded on
|
||||
the ledger that reference that slot, and determining the stake-weighted mean
|
||||
timestamp.
|
||||
|
||||
2. This recent mean timestamp is then used to calculate the timestamp of
|
||||
block N using the cluster's established slot duration
|
||||
|
||||
Requirements:
|
||||
- Any validator replaying the ledger in the future must come up with the same
|
||||
time for every block since genesis
|
||||
- Estimated block times should not drift more than an hour or so before resolving
|
||||
to real-world (oracle) data
|
||||
- The block times are not controlled by a single centralized oracle, but
|
||||
ideally based on a function that uses inputs from all validators
|
||||
- Each validator must maintain a timestamp oracle
|
||||
|
||||
The same implementation can provide a timestamp estimate for a not-yet-rooted
|
||||
block. However, because the most recent timestamped slot may or may not be
|
||||
rooted yet, this timestamp would be unstable (potentially failing requirement
|
||||
1). Initial implementation will target rooted blocks, but if there is a use case
|
||||
for recent-block timestamping, it will be trivial to add the RPC apis in the
|
||||
future.
|
||||
|
||||
## Recording Time
|
||||
|
||||
At regular intervals as it is voting on a particular slot, each validator
|
||||
records its observed time by including a timestamp in its Vote instruction
|
||||
submission. The corresponding slot for the timestamp is the newest Slot in the
|
||||
Vote vector (`Vote::slots.iter().max()`). It is signed by the validator's
|
||||
identity keypair as a usual Vote. In order to enable this reporting, the Vote
|
||||
struct needs to be extended to include a timestamp field, `timestamp:
|
||||
Option<UnixTimestamp>`, which will be set to `None` in most Votes.
|
||||
|
||||
This proposal suggests that Vote instructions with `Some(timestamp)` be issued
|
||||
every 30min, which should be short enough to prevent block times drifting very
|
||||
much, without adding too much transaction overhead to the cluster. Validators
|
||||
can convert this time to a slot interval using the `slots_per_year` value that
|
||||
is stored in each bank.
|
||||
|
||||
```text
|
||||
let seconds_in_30min = 1800;
|
||||
let timestamp_interval = (slots_per_year / SECONDS_PER_YEAR) * seconds_in_30min;
|
||||
```
|
||||
|
||||
Votes with `Some(timestamp)` should be triggered in `replay_stage::handle_votable_bank()`
|
||||
when `bank.slot() % timestamp_interval == 0`.
|
||||
|
||||
### Vote Accounts
|
||||
|
||||
A validator's vote account will hold its most recent slot-timestamp in VoteState.
|
||||
|
||||
### Vote Program
|
||||
|
||||
The on-chain Vote program needs to be extended to process a timestamp sent with
|
||||
a Vote instruction from validators. In addition to its current process\_vote
|
||||
functionality (including loading the correct Vote account and verifying that the
|
||||
transaction signer is the expected validator), this process needs to compare the
|
||||
timestamp and corresponding slot to the currently stored values to verify that
|
||||
they are both monotonically increasing, and store the new slot and timestamp in
|
||||
the account.
|
||||
|
||||
## Calculating Stake-Weighted Mean Timestamp
|
||||
|
||||
In order to calculate the estimated timestamp for a particular block, a
|
||||
validator first needs to identify the most recently timestamped slot:
|
||||
|
||||
```text
|
||||
let timestamp_slot = floor(current_slot / timestamp_interval);
|
||||
```
|
||||
|
||||
Then the validator needs to gather all Vote WithTimestamp transactions from the
|
||||
ledger that reference that slot, using `Blockstore::get_slot_entries()`. As these
|
||||
transactions could have taken some time to reach and be processed by the leader,
|
||||
the validator needs to scan several completed blocks after the timestamp\_slot to
|
||||
get a reasonable set of Timestamps. The exact number of slots will need to be
|
||||
tuned: More slots will enable greater cluster participation and more timestamp
|
||||
datapoints; fewer slots will speed how long timestamp filtering takes.
|
||||
|
||||
From this collection of transactions, the validator calculates the
|
||||
stake-weighted mean timestamp, cross-referencing the epoch stakes from
|
||||
`staking_utils::staked_nodes_at_epoch()`.
|
||||
|
||||
Any validator replaying the ledger should derive the same stake-weighted mean
|
||||
timestamp by processing the Timestamp transactions from the same number of
|
||||
slots.
|
||||
|
||||
## Calculating Estimated Time for a Particular Block
|
||||
|
||||
Once the mean timestamp for a known slot is calculated, it is trivial to
|
||||
calculate the estimated timestamp for subsequent block N:
|
||||
|
||||
```text
|
||||
let block_n_timestamp = mean_timestamp + (block_n_slot_offset * slot_duration);
|
||||
```
|
||||
|
||||
where `block_n_slot_offset` is the difference between the slot of block N and
|
||||
the timestamp\_slot, and `slot_duration` is derived from the cluster's
|
||||
`slots_per_year` stored in each Bank
|
Reference in New Issue
Block a user