Compare commits
10 Commits
Author | SHA1 | Date | |
---|---|---|---|
d9e37eb30c | |||
04d1b35926 | |||
d13d609050 | |||
20426cf251 | |||
4a220d7c8e | |||
436eab41ca | |||
c8472d0a96 | |||
1a7db9c17e | |||
b468d9f17c | |||
41cf1d7d23 |
@ -1,4 +1,4 @@
|
||||
root: ./docs/src
|
||||
root: ./book/src
|
||||
|
||||
structure:
|
||||
readme: introduction.md
|
||||
|
6
.gitignore
vendored
6
.gitignore
vendored
@ -1,6 +1,6 @@
|
||||
/docs/html/
|
||||
/docs/src/tests.ok
|
||||
/docs/src/.gitbook/assets/*.svg
|
||||
/book/html/
|
||||
/book/src/tests.ok
|
||||
/book/src/.gitbook/assets/*.svg
|
||||
/farf/
|
||||
/solana-release/
|
||||
/solana-release.tar.bz2
|
||||
|
30
.mergify.yml
30
.mergify.yml
@ -19,6 +19,14 @@ pull_request_rules:
|
||||
label:
|
||||
add:
|
||||
- automerge
|
||||
- name: v0.22 backport
|
||||
conditions:
|
||||
- base=master
|
||||
- label=v0.22
|
||||
actions:
|
||||
backport:
|
||||
branches:
|
||||
- v0.22
|
||||
- name: v0.23 backport
|
||||
conditions:
|
||||
- base=master
|
||||
@ -27,27 +35,11 @@ pull_request_rules:
|
||||
backport:
|
||||
branches:
|
||||
- v0.23
|
||||
- name: v1.0 backport
|
||||
- name: v0.24 backport
|
||||
conditions:
|
||||
- base=master
|
||||
- label=v1.0
|
||||
- label=v0.24
|
||||
actions:
|
||||
backport:
|
||||
branches:
|
||||
- v1.0
|
||||
- name: v1.1 backport
|
||||
conditions:
|
||||
- base=master
|
||||
- label=v1.1
|
||||
actions:
|
||||
backport:
|
||||
branches:
|
||||
- v1.1
|
||||
- name: v1.2 backport
|
||||
conditions:
|
||||
- base=master
|
||||
- label=v1.2
|
||||
actions:
|
||||
backport:
|
||||
branches:
|
||||
- v1.2
|
||||
- v0.24
|
||||
|
@ -45,7 +45,7 @@ $ git pull --rebase upstream master
|
||||
|
||||
If there are no functional changes, PRs can be very large and that's no
|
||||
problem. If, however, your changes are making meaningful changes or additions,
|
||||
then about 1.0.1 lines of changes is about the most you should ask a Solana
|
||||
then about 1,000 lines of changes is about the most you should ask a Solana
|
||||
maintainer to review.
|
||||
|
||||
### Should I send small PRs as I develop large, new components?
|
||||
@ -224,20 +224,21 @@ Inventing new terms is allowed, but should only be done when the term is widely
|
||||
used and understood. Avoid introducing new 3-letter terms, which can be
|
||||
confused with 3-letter acronyms.
|
||||
|
||||
[Terms currently in use](docs/src/terminology.md)
|
||||
[Terms currently in use](book/src/terminology.md)
|
||||
|
||||
|
||||
## Design Proposals
|
||||
|
||||
Solana's architecture is described by docs generated from markdown files in
|
||||
the `docs/src/` directory, maintained by an *editor* (currently @garious). To
|
||||
add a design proposal, you'll need to include it in the
|
||||
[Accepted Design Proposals](https://docs.solana.com/proposals)
|
||||
section of the Solana docs. Here's the full process:
|
||||
Solana's architecture is described by a book generated from markdown files in
|
||||
the `book/src/` directory, maintained by an *editor* (currently @garious). To
|
||||
add a design proposal, you'll need to at least propose a change the content
|
||||
under the [Accepted Design
|
||||
Proposals](https://docs.solana.com/book/v/master/proposals) chapter. Here's
|
||||
the full process:
|
||||
|
||||
1. Propose a design by creating a PR that adds a markdown document to the
|
||||
`docs/src/proposals` directory and references it from the [table of
|
||||
contents](docs/src/SUMMARY.md). Add any relevant *maintainers* to the PR
|
||||
directory `book/src/` and references it from the [table of
|
||||
contents](book/src/SUMMARY.md). Add any relevant *maintainers* to the PR
|
||||
review.
|
||||
2. The PR being merged indicates your proposed change was accepted and that the
|
||||
maintainers support your plan of attack.
|
||||
|
1581
Cargo.lock
generated
1581
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -7,7 +7,6 @@ members = [
|
||||
"chacha",
|
||||
"chacha-cuda",
|
||||
"chacha-sys",
|
||||
"cli-config",
|
||||
"client",
|
||||
"core",
|
||||
"faucet",
|
||||
@ -43,7 +42,6 @@ members = [
|
||||
"archiver",
|
||||
"archiver-lib",
|
||||
"archiver-utils",
|
||||
"remote-wallet",
|
||||
"runtime",
|
||||
"sdk",
|
||||
"sdk-c",
|
||||
|
16
README.md
16
README.md
@ -23,12 +23,12 @@ It's possible for a centralized database to process 710,000 transactions per sec
|
||||
|
||||
Furthermore, and much to our surprise, it can be implemented using a mechanism that has existed in Bitcoin since day one. The Bitcoin feature is called nLocktime and it can be used to postdate transactions using block height instead of a timestamp. As a Bitcoin client, you'd use block height instead of a timestamp if you don't trust the network. Block height turns out to be an instance of what's being called a Verifiable Delay Function in cryptography circles. It's a cryptographically secure way to say time has passed. In Solana, we use a far more granular verifiable delay function, a SHA 256 hash chain, to checkpoint the ledger and coordinate consensus. With it, we implement Optimistic Concurrency Control and are now well en route towards that theoretical limit of 710,000 transactions per second.
|
||||
|
||||
Documentation
|
||||
Architecture
|
||||
===
|
||||
|
||||
Before you jump into the code, review the documentation [Solana: Blockchain Rebuilt for Scale](https://docs.solana.com).
|
||||
Before you jump into the code, review the online book [Solana: Blockchain Rebuilt for Scale](https://docs.solana.com/book/).
|
||||
|
||||
(The _latest_ development version of the docs is [available here](https://docs.solana.com/v/master).)
|
||||
(The _latest_ development version of the online book is also [available here](https://docs.solana.com/book/v/master/).)
|
||||
|
||||
Release Binaries
|
||||
===
|
||||
@ -87,8 +87,7 @@ $ rustup update
|
||||
On Linux systems you may need to install libssl-dev, pkg-config, zlib1g-dev, etc. On Ubuntu:
|
||||
|
||||
```bash
|
||||
$ sudo apt-get update
|
||||
$ sudo apt-get install libssl-dev libudev-dev pkg-config zlib1g-dev llvm clang
|
||||
$ sudo apt-get install libssl-dev pkg-config zlib1g-dev llvm clang
|
||||
```
|
||||
|
||||
Download the source code:
|
||||
@ -121,13 +120,16 @@ $ cargo test
|
||||
Local Testnet
|
||||
---
|
||||
|
||||
Start your own testnet locally, instructions are in the online docs [Solana: Blockchain Rebuild for Scale: Getting Started](https://docs.solana.com/building-from-source).
|
||||
Start your own testnet locally, instructions are in the book [Solana: Blockchain Rebuild for Scale: Getting Started](https://docs.solana.com/book/getting-started).
|
||||
|
||||
Remote Testnets
|
||||
---
|
||||
|
||||
* `testnet` - public stable testnet accessible via devnet.solana.com. Runs 24/7
|
||||
We maintain several testnets:
|
||||
|
||||
* `testnet` - public stable testnet accessible via testnet.solana.com. Runs 24/7
|
||||
* `testnet-beta` - public beta channel testnet accessible via beta.testnet.solana.com. Runs 24/7
|
||||
* `testnet-edge` - public edge channel testnet accessible via edge.testnet.solana.com. Runs 24/7
|
||||
|
||||
## Deploy process
|
||||
|
||||
|
@ -138,11 +138,11 @@ There are three release channels that map to branches as follows:
|
||||
### Update documentation
|
||||
TODO: Documentation update procedure is WIP as we move to gitbook
|
||||
|
||||
Document the new recommended version by updating `docs/src/running-archiver.md` and `docs/src/validator-testnet.md` on the release (beta) branch to point at the `solana-install` for the upcoming release version.
|
||||
Document the new recommended version by updating `book/src/running-archiver.md` and `book/src/validator-testnet.md` on the release (beta) branch to point at the `solana-install` for the upcoming release version.
|
||||
|
||||
### Update software on devnet.solana.com
|
||||
### Update software on testnet.solana.com
|
||||
|
||||
The testnet running on devnet.solana.com is set to use a fixed release tag
|
||||
The testnet running on testnet.solana.com is set to use a fixed release tag
|
||||
which is set in the Buildkite testnet-management pipeline.
|
||||
This tag needs to be updated and the testnet restarted after a new release
|
||||
tag is created.
|
||||
@ -182,4 +182,4 @@ TESTNET_OP=create-and-start
|
||||
### Alert the community
|
||||
|
||||
Notify Discord users on #validator-support that a new release for
|
||||
devnet.solana.com is available
|
||||
testnet.solana.com is available
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "solana-archiver-lib"
|
||||
version = "1.0.1"
|
||||
version = "0.23.0"
|
||||
description = "Solana Archiver Library"
|
||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
@ -15,22 +15,22 @@ ed25519-dalek = "=1.0.0-pre.1"
|
||||
log = "0.4.8"
|
||||
rand = "0.6.5"
|
||||
rand_chacha = "0.1.1"
|
||||
solana-client = { path = "../client", version = "1.0.1" }
|
||||
solana-storage-program = { path = "../programs/storage", version = "1.0.1" }
|
||||
solana-client = { path = "../client", version = "0.23.0" }
|
||||
solana-storage-program = { path = "../programs/storage", version = "0.23.0" }
|
||||
thiserror = "1.0"
|
||||
serde = "1.0.104"
|
||||
serde_json = "1.0.46"
|
||||
serde_json = "1.0.44"
|
||||
serde_derive = "1.0.103"
|
||||
solana-net-utils = { path = "../net-utils", version = "1.0.1" }
|
||||
solana-chacha = { path = "../chacha", version = "1.0.1" }
|
||||
solana-chacha-sys = { path = "../chacha-sys", version = "1.0.1" }
|
||||
solana-ledger = { path = "../ledger", version = "1.0.1" }
|
||||
solana-logger = { path = "../logger", version = "1.0.1" }
|
||||
solana-perf = { path = "../perf", version = "1.0.1" }
|
||||
solana-sdk = { path = "../sdk", version = "1.0.1" }
|
||||
solana-core = { path = "../core", version = "1.0.1" }
|
||||
solana-archiver-utils = { path = "../archiver-utils", version = "1.0.1" }
|
||||
solana-metrics = { path = "../metrics", version = "1.0.1" }
|
||||
solana-net-utils = { path = "../net-utils", version = "0.23.0" }
|
||||
solana-chacha = { path = "../chacha", version = "0.23.0" }
|
||||
solana-chacha-sys = { path = "../chacha-sys", version = "0.23.0" }
|
||||
solana-ledger = { path = "../ledger", version = "0.23.0" }
|
||||
solana-logger = { path = "../logger", version = "0.23.0" }
|
||||
solana-perf = { path = "../perf", version = "0.23.0" }
|
||||
solana-sdk = { path = "../sdk", version = "0.23.0" }
|
||||
solana-core = { path = "../core", version = "0.23.0" }
|
||||
solana-archiver-utils = { path = "../archiver-utils", version = "0.23.0" }
|
||||
solana-metrics = { path = "../metrics", version = "0.23.0" }
|
||||
|
||||
[dev-dependencies]
|
||||
hex = "0.4.0"
|
||||
|
@ -1,5 +1,6 @@
|
||||
use crate::result::ArchiverError;
|
||||
use crossbeam_channel::unbounded;
|
||||
use ed25519_dalek;
|
||||
use rand::{thread_rng, Rng, SeedableRng};
|
||||
use rand_chacha::ChaChaRng;
|
||||
use solana_archiver_utils::sample_file;
|
||||
@ -15,7 +16,6 @@ use solana_core::{
|
||||
packet::{limited_deserialize, PACKET_DATA_SIZE},
|
||||
repair_service,
|
||||
repair_service::{RepairService, RepairSlotRange, RepairStrategy},
|
||||
serve_repair::ServeRepair,
|
||||
shred_fetch_stage::ShredFetchStage,
|
||||
sigverify_stage::{DisabledSigVerifier, SigVerifyStage},
|
||||
storage_stage::NUM_STORAGE_SAMPLES,
|
||||
@ -36,7 +36,7 @@ use solana_sdk::{
|
||||
commitment_config::CommitmentConfig,
|
||||
hash::Hash,
|
||||
message::Message,
|
||||
signature::{Keypair, Signature, Signer},
|
||||
signature::{Keypair, KeypairUtil, Signature},
|
||||
timing::timestamp,
|
||||
transaction::Transaction,
|
||||
transport::TransportError,
|
||||
@ -87,11 +87,11 @@ struct ArchiverMeta {
|
||||
}
|
||||
|
||||
fn get_slot_from_signature(
|
||||
signature: &Signature,
|
||||
signature: &ed25519_dalek::Signature,
|
||||
storage_turn: u64,
|
||||
slots_per_segment: u64,
|
||||
) -> u64 {
|
||||
let signature_vec = signature.as_ref();
|
||||
let signature_vec = signature.to_bytes();
|
||||
let mut segment_index = u64::from(signature_vec[0])
|
||||
| (u64::from(signature_vec[1]) << 8)
|
||||
| (u64::from(signature_vec[1]) << 16)
|
||||
@ -195,7 +195,13 @@ impl Archiver {
|
||||
Blockstore::open(ledger_path).expect("Expected to be able to open database ledger"),
|
||||
);
|
||||
|
||||
let gossip_service = GossipService::new(&cluster_info, None, node.sockets.gossip, &exit);
|
||||
let gossip_service = GossipService::new(
|
||||
&cluster_info,
|
||||
Some(blockstore.clone()),
|
||||
None,
|
||||
node.sockets.gossip,
|
||||
&exit,
|
||||
);
|
||||
|
||||
info!("Connecting to the cluster via {:?}", cluster_entrypoint);
|
||||
let (nodes, _) =
|
||||
@ -384,7 +390,7 @@ impl Archiver {
|
||||
);
|
||||
let message =
|
||||
Message::new_with_payer(vec![ix], Some(&archiver_keypair.pubkey()));
|
||||
if let Err(e) = client.send_message(&[archiver_keypair.as_ref()], message) {
|
||||
if let Err(e) = client.send_message(&[&archiver_keypair], message) {
|
||||
error!("unable to redeem reward, tx failed: {:?}", e);
|
||||
} else {
|
||||
info!(
|
||||
@ -437,13 +443,13 @@ impl Archiver {
|
||||
return Err(e);
|
||||
}
|
||||
};
|
||||
let signature = storage_keypair.sign_message(segment_blockhash.as_ref());
|
||||
let signature = storage_keypair.sign(segment_blockhash.as_ref());
|
||||
let slot = get_slot_from_signature(&signature, segment_slot, slots_per_segment);
|
||||
info!("replicating slot: {}", slot);
|
||||
slot_sender.send(slot)?;
|
||||
meta.slot = slot;
|
||||
meta.slots_per_segment = slots_per_segment;
|
||||
meta.signature = signature;
|
||||
meta.signature = Signature::new(&signature.to_bytes());
|
||||
meta.blockhash = segment_blockhash;
|
||||
|
||||
let mut repair_slot_range = RepairSlotRange::default();
|
||||
@ -516,8 +522,6 @@ impl Archiver {
|
||||
let mut contact_info = node_info.clone();
|
||||
contact_info.tvu = "0.0.0.0:0".parse().unwrap();
|
||||
contact_info.wallclock = timestamp();
|
||||
// copy over the adopted shred_version from the entrypoint
|
||||
contact_info.shred_version = cluster_info.read().unwrap().my_data().shred_version;
|
||||
{
|
||||
let mut cluster_info_w = cluster_info.write().unwrap();
|
||||
cluster_info_w.insert_self(contact_info);
|
||||
@ -671,7 +675,7 @@ impl Archiver {
|
||||
blockhash,
|
||||
);
|
||||
if let Err(err) = client.send_and_confirm_transaction(
|
||||
&[archiver_keypair.as_ref(), storage_keypair.as_ref()],
|
||||
&[&archiver_keypair, &storage_keypair],
|
||||
&mut transaction,
|
||||
10,
|
||||
0,
|
||||
@ -697,7 +701,7 @@ impl Archiver {
|
||||
) -> Result<u64> {
|
||||
let rpc_peers = {
|
||||
let cluster_info = cluster_info.read().unwrap();
|
||||
cluster_info.all_rpc_peers()
|
||||
cluster_info.rpc_peers()
|
||||
};
|
||||
debug!("rpc peers: {:?}", rpc_peers);
|
||||
if !rpc_peers.is_empty() {
|
||||
@ -753,7 +757,7 @@ impl Archiver {
|
||||
loop {
|
||||
let rpc_peers = {
|
||||
let cluster_info = cluster_info.read().unwrap();
|
||||
cluster_info.all_rpc_peers()
|
||||
cluster_info.rpc_peers()
|
||||
};
|
||||
debug!("rpc peers: {:?}", rpc_peers);
|
||||
if !rpc_peers.is_empty() {
|
||||
@ -808,7 +812,7 @@ impl Archiver {
|
||||
/// It is recommended to use a temporary blockstore for this since the download will not verify
|
||||
/// shreds received and might impact the chaining of shreds across slots
|
||||
pub fn download_from_archiver(
|
||||
serve_repair: &ServeRepair,
|
||||
cluster_info: &Arc<RwLock<ClusterInfo>>,
|
||||
archiver_info: &ContactInfo,
|
||||
blockstore: &Arc<Blockstore>,
|
||||
slots_per_segment: u64,
|
||||
@ -828,10 +832,10 @@ impl Archiver {
|
||||
Recycler::default(),
|
||||
"archiver_reeciver",
|
||||
);
|
||||
let id = serve_repair.keypair().pubkey();
|
||||
let id = cluster_info.read().unwrap().id();
|
||||
info!(
|
||||
"Sending repair requests from: {} to: {}",
|
||||
serve_repair.my_info().id,
|
||||
cluster_info.read().unwrap().my_data().id,
|
||||
archiver_info.gossip
|
||||
);
|
||||
let repair_slot_range = RepairSlotRange {
|
||||
@ -851,7 +855,9 @@ impl Archiver {
|
||||
let reqs: Vec<_> = repairs
|
||||
.into_iter()
|
||||
.filter_map(|repair_request| {
|
||||
serve_repair
|
||||
cluster_info
|
||||
.read()
|
||||
.unwrap()
|
||||
.map_repair_request(&repair_request)
|
||||
.map(|result| ((archiver_info.gossip, result), repair_request))
|
||||
.ok()
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "solana-archiver-utils"
|
||||
version = "1.0.1"
|
||||
version = "0.23.0"
|
||||
description = "Solana Archiver Utils"
|
||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
@ -11,12 +11,13 @@ edition = "2018"
|
||||
[dependencies]
|
||||
log = "0.4.8"
|
||||
rand = "0.6.5"
|
||||
solana-chacha = { path = "../chacha", version = "1.0.1" }
|
||||
solana-chacha-sys = { path = "../chacha-sys", version = "1.0.1" }
|
||||
solana-ledger = { path = "../ledger", version = "1.0.1" }
|
||||
solana-logger = { path = "../logger", version = "1.0.1" }
|
||||
solana-perf = { path = "../perf", version = "1.0.1" }
|
||||
solana-sdk = { path = "../sdk", version = "1.0.1" }
|
||||
rand_chacha = "0.1.1"
|
||||
solana-chacha = { path = "../chacha", version = "0.23.0" }
|
||||
solana-chacha-sys = { path = "../chacha-sys", version = "0.23.0" }
|
||||
solana-ledger = { path = "../ledger", version = "0.23.0" }
|
||||
solana-logger = { path = "../logger", version = "0.23.0" }
|
||||
solana-perf = { path = "../perf", version = "0.23.0" }
|
||||
solana-sdk = { path = "../sdk", version = "0.23.0" }
|
||||
|
||||
[dev-dependencies]
|
||||
hex = "0.4.0"
|
||||
|
@ -2,19 +2,19 @@
|
||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
edition = "2018"
|
||||
name = "solana-archiver"
|
||||
version = "1.0.1"
|
||||
version = "0.23.0"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
|
||||
[dependencies]
|
||||
clap = "2.33.0"
|
||||
console = "0.9.2"
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.0.1" }
|
||||
solana-core = { path = "../core", version = "1.0.1" }
|
||||
solana-logger = { path = "../logger", version = "1.0.1" }
|
||||
solana-metrics = { path = "../metrics", version = "1.0.1" }
|
||||
solana-archiver-lib = { path = "../archiver-lib", version = "1.0.1" }
|
||||
solana-net-utils = { path = "../net-utils", version = "1.0.1" }
|
||||
solana-sdk = { path = "../sdk", version = "1.0.1" }
|
||||
console = "0.9.1"
|
||||
solana-clap-utils = { path = "../clap-utils", version = "0.23.0" }
|
||||
solana-core = { path = "../core", version = "0.23.0" }
|
||||
solana-logger = { path = "../logger", version = "0.23.0" }
|
||||
solana-metrics = { path = "../metrics", version = "0.23.0" }
|
||||
solana-archiver-lib = { path = "../archiver-lib", version = "0.23.0" }
|
||||
solana-net-utils = { path = "../net-utils", version = "0.23.0" }
|
||||
solana-sdk = { path = "../sdk", version = "0.23.0" }
|
||||
|
||||
|
@ -12,7 +12,7 @@ use solana_core::{
|
||||
cluster_info::{Node, VALIDATOR_PORT_RANGE},
|
||||
contact_info::ContactInfo,
|
||||
};
|
||||
use solana_sdk::{commitment_config::CommitmentConfig, signature::Signer};
|
||||
use solana_sdk::{commitment_config::CommitmentConfig, signature::KeypairUtil};
|
||||
use std::{net::SocketAddr, path::PathBuf, process::exit, sync::Arc};
|
||||
|
||||
fn main() {
|
||||
|
@ -2,7 +2,7 @@
|
||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
edition = "2018"
|
||||
name = "solana-banking-bench"
|
||||
version = "1.0.1"
|
||||
version = "0.23.0"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
@ -10,11 +10,11 @@ homepage = "https://solana.com/"
|
||||
[dependencies]
|
||||
log = "0.4.6"
|
||||
rayon = "1.2.0"
|
||||
solana-core = { path = "../core", version = "1.0.1" }
|
||||
solana-ledger = { path = "../ledger", version = "1.0.1" }
|
||||
solana-logger = { path = "../logger", version = "1.0.1" }
|
||||
solana-runtime = { path = "../runtime", version = "1.0.1" }
|
||||
solana-measure = { path = "../measure", version = "1.0.1" }
|
||||
solana-sdk = { path = "../sdk", version = "1.0.1" }
|
||||
solana-core = { path = "../core", version = "0.23.0" }
|
||||
solana-ledger = { path = "../ledger", version = "0.23.0" }
|
||||
solana-logger = { path = "../logger", version = "0.23.0" }
|
||||
solana-runtime = { path = "../runtime", version = "0.23.0" }
|
||||
solana-measure = { path = "../measure", version = "0.23.0" }
|
||||
solana-sdk = { path = "../sdk", version = "0.23.0" }
|
||||
rand = "0.6.5"
|
||||
crossbeam-channel = "0.3"
|
||||
|
@ -2,33 +2,40 @@
|
||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
edition = "2018"
|
||||
name = "solana-bench-exchange"
|
||||
version = "1.0.1"
|
||||
version = "0.23.0"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
publish = false
|
||||
|
||||
[dependencies]
|
||||
bincode = "1.2.1"
|
||||
bs58 = "0.3.0"
|
||||
clap = "2.32.0"
|
||||
env_logger = "0.7.1"
|
||||
itertools = "0.8.2"
|
||||
log = "0.4.8"
|
||||
num-derive = "0.3"
|
||||
num-traits = "0.2"
|
||||
rand = "0.6.5"
|
||||
rayon = "1.2.0"
|
||||
serde_json = "1.0.46"
|
||||
serde = "1.0.104"
|
||||
serde_derive = "1.0.103"
|
||||
serde_json = "1.0.44"
|
||||
serde_yaml = "0.8.11"
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.0.1" }
|
||||
solana-core = { path = "../core", version = "1.0.1" }
|
||||
solana-genesis = { path = "../genesis", version = "1.0.1" }
|
||||
solana-client = { path = "../client", version = "1.0.1" }
|
||||
solana-faucet = { path = "../faucet", version = "1.0.1" }
|
||||
solana-exchange-program = { path = "../programs/exchange", version = "1.0.1" }
|
||||
solana-logger = { path = "../logger", version = "1.0.1" }
|
||||
solana-metrics = { path = "../metrics", version = "1.0.1" }
|
||||
solana-net-utils = { path = "../net-utils", version = "1.0.1" }
|
||||
solana-runtime = { path = "../runtime", version = "1.0.1" }
|
||||
solana-sdk = { path = "../sdk", version = "1.0.1" }
|
||||
solana-clap-utils = { path = "../clap-utils", version = "0.23.0" }
|
||||
solana-core = { path = "../core", version = "0.23.0" }
|
||||
solana-genesis = { path = "../genesis", version = "0.23.0" }
|
||||
solana-client = { path = "../client", version = "0.23.0" }
|
||||
solana-faucet = { path = "../faucet", version = "0.23.0" }
|
||||
solana-exchange-program = { path = "../programs/exchange", version = "0.23.0" }
|
||||
solana-logger = { path = "../logger", version = "0.23.0" }
|
||||
solana-metrics = { path = "../metrics", version = "0.23.0" }
|
||||
solana-net-utils = { path = "../net-utils", version = "0.23.0" }
|
||||
solana-runtime = { path = "../runtime", version = "0.23.0" }
|
||||
solana-sdk = { path = "../sdk", version = "0.23.0" }
|
||||
untrusted = "0.7.0"
|
||||
ws = "0.9.1"
|
||||
|
||||
[dev-dependencies]
|
||||
solana-local-cluster = { path = "../local-cluster", version = "1.0.1" }
|
||||
solana-local-cluster = { path = "../local-cluster", version = "0.23.0" }
|
||||
|
@ -15,7 +15,7 @@ use solana_sdk::{
|
||||
client::{Client, SyncClient},
|
||||
commitment_config::CommitmentConfig,
|
||||
pubkey::Pubkey,
|
||||
signature::{Keypair, Signer},
|
||||
signature::{Keypair, KeypairUtil},
|
||||
timing::{duration_as_ms, duration_as_s},
|
||||
transaction::Transaction,
|
||||
{system_instruction, system_program},
|
||||
@ -701,7 +701,7 @@ fn verify_funding_transfer<T: SyncClient + ?Sized>(
|
||||
false
|
||||
}
|
||||
|
||||
pub fn fund_keys<T: Client>(client: &T, source: &Keypair, dests: &[Arc<Keypair>], lamports: u64) {
|
||||
pub fn fund_keys(client: &dyn Client, source: &Keypair, dests: &[Arc<Keypair>], lamports: u64) {
|
||||
let total = lamports * (dests.len() as u64 + 1);
|
||||
let mut funded: Vec<(&Keypair, u64)> = vec![(source, total)];
|
||||
let mut notfunded: Vec<&Arc<Keypair>> = dests.iter().collect();
|
||||
@ -824,11 +824,7 @@ pub fn fund_keys<T: Client>(client: &T, source: &Keypair, dests: &[Arc<Keypair>]
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_token_accounts<T: Client>(
|
||||
client: &T,
|
||||
signers: &[Arc<Keypair>],
|
||||
accounts: &[Keypair],
|
||||
) {
|
||||
pub fn create_token_accounts(client: &dyn Client, signers: &[Arc<Keypair>], accounts: &[Keypair]) {
|
||||
let mut notfunded: Vec<(&Arc<Keypair>, &Keypair)> = signers.iter().zip(accounts).collect();
|
||||
|
||||
while !notfunded.is_empty() {
|
||||
@ -972,12 +968,7 @@ fn generate_keypairs(num: u64) -> Vec<Keypair> {
|
||||
rnd.gen_n_keypairs(num)
|
||||
}
|
||||
|
||||
pub fn airdrop_lamports<T: Client>(
|
||||
client: &T,
|
||||
faucet_addr: &SocketAddr,
|
||||
id: &Keypair,
|
||||
amount: u64,
|
||||
) {
|
||||
pub fn airdrop_lamports(client: &dyn Client, faucet_addr: &SocketAddr, id: &Keypair, amount: u64) {
|
||||
let balance = client.get_balance_with_commitment(&id.pubkey(), CommitmentConfig::recent());
|
||||
let balance = balance.unwrap_or(0);
|
||||
if balance >= amount {
|
||||
|
@ -1,7 +1,7 @@
|
||||
use clap::{crate_description, crate_name, value_t, App, Arg, ArgMatches};
|
||||
use solana_core::gen_keys::GenKeys;
|
||||
use solana_faucet::faucet::FAUCET_PORT;
|
||||
use solana_sdk::signature::{read_keypair_file, Keypair};
|
||||
use solana_sdk::signature::{read_keypair_file, Keypair, KeypairUtil};
|
||||
use std::net::SocketAddr;
|
||||
use std::process::exit;
|
||||
use std::time::Duration;
|
||||
|
@ -5,7 +5,7 @@ pub mod order_book;
|
||||
use crate::bench::{airdrop_lamports, create_client_accounts_file, do_bench_exchange, Config};
|
||||
use log::*;
|
||||
use solana_core::gossip_service::{discover_cluster, get_multi_client};
|
||||
use solana_sdk::signature::Signer;
|
||||
use solana_sdk::signature::KeypairUtil;
|
||||
|
||||
fn main() {
|
||||
solana_logger::setup();
|
||||
|
@ -10,13 +10,12 @@ use solana_local_cluster::local_cluster::{ClusterConfig, LocalCluster};
|
||||
use solana_runtime::bank::Bank;
|
||||
use solana_runtime::bank_client::BankClient;
|
||||
use solana_sdk::genesis_config::create_genesis_config;
|
||||
use solana_sdk::signature::{Keypair, Signer};
|
||||
use solana_sdk::signature::{Keypair, KeypairUtil};
|
||||
use std::process::exit;
|
||||
use std::sync::mpsc::channel;
|
||||
use std::time::Duration;
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn test_exchange_local_cluster() {
|
||||
solana_logger::setup();
|
||||
|
||||
|
@ -2,14 +2,14 @@
|
||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
edition = "2018"
|
||||
name = "solana-bench-streamer"
|
||||
version = "1.0.1"
|
||||
version = "0.23.0"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
|
||||
[dependencies]
|
||||
clap = "2.33.0"
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.0.1" }
|
||||
solana-core = { path = "../core", version = "1.0.1" }
|
||||
solana-logger = { path = "../logger", version = "1.0.1" }
|
||||
solana-net-utils = { path = "../net-utils", version = "1.0.1" }
|
||||
solana-clap-utils = { path = "../clap-utils", version = "0.23.0" }
|
||||
solana-core = { path = "../core", version = "0.23.0" }
|
||||
solana-logger = { path = "../logger", version = "0.23.0" }
|
||||
solana-net-utils = { path = "../net-utils", version = "0.23.0" }
|
||||
|
@ -2,7 +2,7 @@
|
||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
edition = "2018"
|
||||
name = "solana-bench-tps"
|
||||
version = "1.0.1"
|
||||
version = "0.23.0"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
@ -12,26 +12,28 @@ bincode = "1.2.1"
|
||||
clap = "2.33.0"
|
||||
log = "0.4.8"
|
||||
rayon = "1.2.0"
|
||||
serde_json = "1.0.46"
|
||||
serde = "1.0.104"
|
||||
serde_derive = "1.0.103"
|
||||
serde_json = "1.0.44"
|
||||
serde_yaml = "0.8.11"
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.0.1" }
|
||||
solana-core = { path = "../core", version = "1.0.1" }
|
||||
solana-genesis = { path = "../genesis", version = "1.0.1" }
|
||||
solana-client = { path = "../client", version = "1.0.1" }
|
||||
solana-faucet = { path = "../faucet", version = "1.0.1" }
|
||||
solana-librapay = { path = "../programs/librapay", version = "1.0.1", optional = true }
|
||||
solana-logger = { path = "../logger", version = "1.0.1" }
|
||||
solana-metrics = { path = "../metrics", version = "1.0.1" }
|
||||
solana-measure = { path = "../measure", version = "1.0.1" }
|
||||
solana-net-utils = { path = "../net-utils", version = "1.0.1" }
|
||||
solana-runtime = { path = "../runtime", version = "1.0.1" }
|
||||
solana-sdk = { path = "../sdk", version = "1.0.1" }
|
||||
solana-move-loader-program = { path = "../programs/move_loader", version = "1.0.1", optional = true }
|
||||
solana-clap-utils = { path = "../clap-utils", version = "0.23.0" }
|
||||
solana-core = { path = "../core", version = "0.23.0" }
|
||||
solana-genesis = { path = "../genesis", version = "0.23.0" }
|
||||
solana-client = { path = "../client", version = "0.23.0" }
|
||||
solana-faucet = { path = "../faucet", version = "0.23.0" }
|
||||
solana-librapay = { path = "../programs/librapay", version = "0.23.0", optional = true }
|
||||
solana-logger = { path = "../logger", version = "0.23.0" }
|
||||
solana-metrics = { path = "../metrics", version = "0.23.0" }
|
||||
solana-measure = { path = "../measure", version = "0.23.0" }
|
||||
solana-net-utils = { path = "../net-utils", version = "0.23.0" }
|
||||
solana-runtime = { path = "../runtime", version = "0.23.0" }
|
||||
solana-sdk = { path = "../sdk", version = "0.23.0" }
|
||||
solana-move-loader-program = { path = "../programs/move_loader", version = "0.23.0", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
serial_test = "0.3.2"
|
||||
serial_test_derive = "0.4.0"
|
||||
solana-local-cluster = { path = "../local-cluster", version = "1.0.1" }
|
||||
serial_test_derive = "0.3.1"
|
||||
solana-local-cluster = { path = "../local-cluster", version = "0.23.0" }
|
||||
|
||||
[features]
|
||||
move = ["solana-librapay", "solana-move-loader-program"]
|
||||
|
@ -7,7 +7,7 @@ use solana_faucet::faucet::request_airdrop_transaction;
|
||||
#[cfg(feature = "move")]
|
||||
use solana_librapay::{create_genesis, upload_mint_script, upload_payment_script};
|
||||
use solana_measure::measure::Measure;
|
||||
use solana_metrics::{self, datapoint_info};
|
||||
use solana_metrics::{self, datapoint_debug};
|
||||
use solana_sdk::{
|
||||
client::Client,
|
||||
clock::{DEFAULT_TICKS_PER_SECOND, DEFAULT_TICKS_PER_SLOT, MAX_PROCESSING_AGE},
|
||||
@ -15,7 +15,7 @@ use solana_sdk::{
|
||||
fee_calculator::FeeCalculator,
|
||||
hash::Hash,
|
||||
pubkey::Pubkey,
|
||||
signature::{Keypair, Signer},
|
||||
signature::{Keypair, KeypairUtil},
|
||||
system_instruction, system_transaction,
|
||||
timing::{duration_as_ms, duration_as_s, duration_as_us, timestamp},
|
||||
transaction::Transaction,
|
||||
@ -244,7 +244,7 @@ where
|
||||
|
||||
fn metrics_submit_lamport_balance(lamport_balance: u64) {
|
||||
info!("Token balance: {}", lamport_balance);
|
||||
datapoint_info!(
|
||||
datapoint_debug!(
|
||||
"bench-tps-lamport_balance",
|
||||
("balance", lamport_balance, i64)
|
||||
);
|
||||
@ -375,7 +375,7 @@ fn generate_txs(
|
||||
duration_as_ms(&duration),
|
||||
blockhash,
|
||||
);
|
||||
datapoint_info!(
|
||||
datapoint_debug!(
|
||||
"bench-tps-generate_txs",
|
||||
("duration", duration_as_us(&duration), i64)
|
||||
);
|
||||
@ -481,7 +481,7 @@ fn do_tx_transfers<T: Client>(
|
||||
duration_as_ms(&transfer_start.elapsed()),
|
||||
tx_len as f32 / duration_as_s(&transfer_start.elapsed()),
|
||||
);
|
||||
datapoint_info!(
|
||||
datapoint_debug!(
|
||||
"bench-tps-do_tx_transfers",
|
||||
("duration", duration_as_us(&transfer_start.elapsed()), i64),
|
||||
("count", tx_len, i64)
|
||||
|
@ -1,7 +1,7 @@
|
||||
use clap::{crate_description, crate_name, App, Arg, ArgMatches};
|
||||
use solana_faucet::faucet::FAUCET_PORT;
|
||||
use solana_sdk::fee_calculator::FeeCalculator;
|
||||
use solana_sdk::signature::{read_keypair_file, Keypair};
|
||||
use solana_sdk::signature::{read_keypair_file, Keypair, KeypairUtil};
|
||||
use std::{net::SocketAddr, process::exit, time::Duration};
|
||||
|
||||
const NUM_LAMPORTS_PER_ACCOUNT_DEFAULT: u64 = solana_sdk::native_token::LAMPORTS_PER_SOL;
|
||||
|
@ -4,7 +4,7 @@ use solana_bench_tps::cli;
|
||||
use solana_core::gossip_service::{discover_cluster, get_client, get_multi_client};
|
||||
use solana_genesis::Base64Account;
|
||||
use solana_sdk::fee_calculator::FeeCalculator;
|
||||
use solana_sdk::signature::{Keypair, Signer};
|
||||
use solana_sdk::signature::{Keypair, KeypairUtil};
|
||||
use solana_sdk::system_program;
|
||||
use std::{collections::HashMap, fs::File, io::prelude::*, path::Path, process::exit, sync::Arc};
|
||||
|
||||
|
@ -8,7 +8,7 @@ use solana_faucet::faucet::run_local_faucet;
|
||||
use solana_local_cluster::local_cluster::{ClusterConfig, LocalCluster};
|
||||
#[cfg(feature = "move")]
|
||||
use solana_sdk::move_loader::solana_move_loader_program;
|
||||
use solana_sdk::signature::{Keypair, Signer};
|
||||
use solana_sdk::signature::{Keypair, KeypairUtil};
|
||||
use std::sync::{mpsc::channel, Arc};
|
||||
use std::time::Duration;
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
Building the Solana book
|
||||
---
|
||||
|
||||
Install dependencies, build, and test the docs:
|
||||
Install the book's dependnecies, build, and test the book:
|
||||
|
||||
```bash
|
||||
$ ./build.sh
|
||||
@ -19,7 +19,7 @@ Render markdown as HTML:
|
||||
$ make build
|
||||
```
|
||||
|
||||
Render and view the docs:
|
||||
Render and view the book:
|
||||
|
||||
```bash
|
||||
$ make open
|
Before Width: | Height: | Size: 542 KiB After Width: | Height: | Size: 542 KiB |
Before Width: | Height: | Size: 256 KiB After Width: | Height: | Size: 256 KiB |
Before Width: | Height: | Size: 269 KiB After Width: | Height: | Size: 269 KiB |
@ -3,8 +3,6 @@
|
||||
* [Introduction](introduction.md)
|
||||
* [Using Solana from the Command-line](cli/README.md)
|
||||
* [Command-line Usage](cli/usage.md)
|
||||
* [Remote Wallet](remote-wallet/README.md)
|
||||
* [Ledger Hardware Wallet](remote-wallet/ledger.md)
|
||||
* [Paper Wallet](paper-wallet/README.md)
|
||||
* [Installation](paper-wallet/installation.md)
|
||||
* [Paper Wallet Usage](paper-wallet/usage.md)
|
||||
@ -17,27 +15,6 @@
|
||||
* [Anatomy of a Transaction](transaction.md)
|
||||
* [JSON RPC API](apps/jsonrpc-api.md)
|
||||
* [JavaScript API](apps/javascript-api.md)
|
||||
* [Participating in Tour de SOL](tour-de-sol/README.md)
|
||||
* [Useful Links & Discussion](tour-de-sol/useful-links.md)
|
||||
* [Registration](tour-de-sol/registration/README.md)
|
||||
* [How To Register](tour-de-sol/registration/how-to-register.md)
|
||||
* [Terms of Participation](tour-de-sol/registration/terms-of-participation.md)
|
||||
* [Rewards](tour-de-sol/registration/rewards.md)
|
||||
* [Confidentiality](tour-de-sol/registration/confidentiality.md)
|
||||
* [Registration FAQ](tour-de-sol/registration/validator-registration-and-rewards-faq.md)
|
||||
* [Participation](tour-de-sol/participation/README.md)
|
||||
* [Requirements to run a validator](tour-de-sol/participation/validator-technical-requirements.md)
|
||||
* [Steps to create a validator](tour-de-sol/participation/steps-to-create-a-validator/README.md)
|
||||
* [Install the Solana software](tour-de-sol/participation/steps-to-create-a-validator/install-the-solana-software.md)
|
||||
* [Create a validator public key](tour-de-sol/participation/steps-to-create-a-validator/validator-public-key-registration.md)
|
||||
* [Create and configure a Solana validator](tour-de-sol/participation/steps-to-create-a-validator/connecting-your-validator.md)
|
||||
* [Confirm the Solana network is running](tour-de-sol/participation/steps-to-create-a-validator/confirm-the-solana-network-is-running.md)
|
||||
* [Connect to the Solana network](tour-de-sol/participation/steps-to-create-a-validator/connect-to-the-solana-network.md)
|
||||
* [Validator catch-up](tour-de-sol/participation/steps-to-create-a-validator/monitoring-your-validator.md)
|
||||
* [Staking](tour-de-sol/participation/steps-to-create-a-validator/delegating-stake.md)
|
||||
* [Publish information about your validator](tour-de-sol/participation/steps-to-create-a-validator/publishing-information-about-your-validator.md)
|
||||
* [Dry Run 6](tour-de-sol/participation/dry-run-6.md)
|
||||
* [Submitting Bugs](tour-de-sol/submitting-bugs.md)
|
||||
* [Running a Validator](running-validator/README.md)
|
||||
* [Validator Requirements](running-validator/validator-reqs.md)
|
||||
* [Choosing a Testnet](running-validator/validator-testnet.md)
|
@ -1,6 +1,6 @@
|
||||
# Drones
|
||||
|
||||
This section defines an off-chain service called a _drone_, which acts as custodian of a user's private key. In its simplest form, it can be used to create _airdrop_ transactions, a token transfer from the drone's account to a client's account.
|
||||
This chapter defines an off-chain service called a _drone_, which acts as custodian of a user's private key. In its simplest form, it can be used to create _airdrop_ transactions, a token transfer from the drone's account to a client's account.
|
||||
|
||||
## Signing Service
|
||||
|
@ -303,9 +303,6 @@ The result field will be an object with the following fields:
|
||||
* `fee: <u64>` - fee this transaction was charged, as u64 integer
|
||||
* `preBalances: <array>` - array of u64 account balances from before the transaction was processed
|
||||
* `postBalances: <array>` - array of u64 account balances after the transaction was processed
|
||||
* `rewards: <array>` - an array of JSON objects containing:
|
||||
* `pubkey: <string>` - The public key, as base-58 encoded string, of the account that received the reward
|
||||
* `lamports: <i64>`- number of reward lamports credited or debited by the account, as a i64
|
||||
|
||||
#### Example:
|
||||
|
||||
@ -314,13 +311,13 @@ The result field will be an object with the following fields:
|
||||
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc": "2.0","id":1,"method":"getConfirmedBlock","params":[430, "json"]}' localhost:8899
|
||||
|
||||
// Result
|
||||
{"jsonrpc":"2.0","result":{"blockhash":"Gp3t5bfDsJv1ovP8cB1SuRhXVuoTqDv7p3tymyubYg5","parentSlot":429,"previousBlockhash":"EFejToxii1L5aUF2NrK9dsbAEmZSNyN5nsipmZHQR1eA","transactions":[{"transaction":{"message":{"accountKeys":["6H94zdiaYfRfPfKjYLjyr2VFBg6JHXygy84r3qhc3NsC","39UAy8hsoYPywGPGdmun747omSr79zLSjqvPJN3zetoH","SysvarS1otHashes111111111111111111111111111","SysvarC1ock11111111111111111111111111111111","Vote111111111111111111111111111111111111111"],"header":{"numReadonlySignedAccounts":0,"numReadonlyUnsignedAccounts":3,"numRequiredSignatures":2},"instructions":[{"accounts":[1,2,3],"data":"29z5mr1JoRmJYQ6ynmk3pf31cGFRziAF1M3mT3L6sFXf5cKLdkEaMXMT8AqLpD4CpcupHmuMEmtZHpomrwfdZetSomNy3d","programIdIndex":4}],"recentBlockhash":"EFejToxii1L5aUF2NrK9dsbAEmZSNyN5nsipmZHQR1eA"},"signatures":["35YGay1Lwjwgxe9zaH6APSHbt9gYQUCtBWTNL3aVwVGn9xTFw2fgds7qK5AL29mP63A9j3rh8KpN1TgSR62XCaby","4vANMjSKiwEchGSXwVrQkwHnmsbKQmy9vdrsYxWdCup1bLsFzX8gKrFTSVDCZCae2dbxJB9mPNhqB2sD1vvr4sAD"]},"meta":{"fee":1.0.1,"postBalances":[499999972500,15298080,1,1,1],"preBalances":[499999990500,15298080,1,1,1],"status":{"Ok":null}}}]},"id":1}
|
||||
{"jsonrpc":"2.0","result":{"blockhash":"Gp3t5bfDsJv1ovP8cB1SuRhXVuoTqDv7p3tymyubYg5","parentSlot":429,"previousBlockhash":"EFejToxii1L5aUF2NrK9dsbAEmZSNyN5nsipmZHQR1eA","transactions":[{"transaction":{"message":{"accountKeys":["6H94zdiaYfRfPfKjYLjyr2VFBg6JHXygy84r3qhc3NsC","39UAy8hsoYPywGPGdmun747omSr79zLSjqvPJN3zetoH","SysvarS1otHashes111111111111111111111111111","SysvarC1ock11111111111111111111111111111111","Vote111111111111111111111111111111111111111"],"header":{"numReadonlySignedAccounts":0,"numReadonlyUnsignedAccounts":3,"numRequiredSignatures":2},"instructions":[{"accounts":[1,2,3],"data":"29z5mr1JoRmJYQ6ynmk3pf31cGFRziAF1M3mT3L6sFXf5cKLdkEaMXMT8AqLpD4CpcupHmuMEmtZHpomrwfdZetSomNy3d","programIdIndex":4}],"recentBlockhash":"EFejToxii1L5aUF2NrK9dsbAEmZSNyN5nsipmZHQR1eA"},"signatures":["35YGay1Lwjwgxe9zaH6APSHbt9gYQUCtBWTNL3aVwVGn9xTFw2fgds7qK5AL29mP63A9j3rh8KpN1TgSR62XCaby","4vANMjSKiwEchGSXwVrQkwHnmsbKQmy9vdrsYxWdCup1bLsFzX8gKrFTSVDCZCae2dbxJB9mPNhqB2sD1vvr4sAD"]},"meta":{"fee":18000,"postBalances":[499999972500,15298080,1,1,1],"preBalances":[499999990500,15298080,1,1,1],"status":{"Ok":null}}}]},"id":1}
|
||||
|
||||
// Request
|
||||
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc": "2.0","id":1,"method":"getConfirmedBlock","params":[430, "binary"]}' localhost:8899
|
||||
|
||||
// Result
|
||||
{"jsonrpc":"2.0","result":{"blockhash":"Gp3t5bfDsJv1ovP8cB1SuRhXVuoTqDv7p3tymyubYg5","parentSlot":429,"previousBlockhash":"EFejToxii1L5aUF2NrK9dsbAEmZSNyN5nsipmZHQR1eA","transactions":[{"transaction":"81UZJt4dh4Do66jDhrgkQudS8J2N6iG3jaVav7gJrqJSFY4Ug53iA9JFJZh2gxKWcaFdLJwhHx9mRdg9JwDAWB4ywiu5154CRwXV4FMdnPLg7bhxRLwhhYaLsVgMF5AyNRcTzjCVoBvqFgDU7P8VEKDEiMvD3qxzm1pLZVxDG1LTQpT3Dz4Uviv4KQbFQNuC22KupBoyHFB7Zh6KFdMqux4M9PvhoqcoJsJKwXjWpKu7xmEKnnrSbfLadkgjBmmjhW3fdTrFvnhQdTkhtdJxUL1xS9GMuJQer8YgSKNtUXB1eXZQwXU8bU2BjYkZE6Q5Xww8hu9Z4E4Mo4QsooVtHoP6BM3NKw8zjVbWfoCQqxTrwuSzrNCWCWt58C24LHecH67CTt2uXbYSviixvrYkK7A3t68BxTJcF1dXJitEPTFe2ceTkauLJqrJgnER4iUrsjr26T8YgWvpY9wkkWFSviQW6wV5RASTCUasVEcrDiaKj8EQMkgyDoe9HyKitSVg67vMWJFpUXpQobseWJUs5FTWWzmfHmFp8FZ","meta":{"fee":1.0.1,"postBalances":[499999972500,15298080,1,1,1],"preBalances":[499999990500,15298080,1,1,1],"status":{"Ok":null}}}]},"id":1}
|
||||
{"jsonrpc":"2.0","result":{"blockhash":"Gp3t5bfDsJv1ovP8cB1SuRhXVuoTqDv7p3tymyubYg5","parentSlot":429,"previousBlockhash":"EFejToxii1L5aUF2NrK9dsbAEmZSNyN5nsipmZHQR1eA","transactions":[{"transaction":"81UZJt4dh4Do66jDhrgkQudS8J2N6iG3jaVav7gJrqJSFY4Ug53iA9JFJZh2gxKWcaFdLJwhHx9mRdg9JwDAWB4ywiu5154CRwXV4FMdnPLg7bhxRLwhhYaLsVgMF5AyNRcTzjCVoBvqFgDU7P8VEKDEiMvD3qxzm1pLZVxDG1LTQpT3Dz4Uviv4KQbFQNuC22KupBoyHFB7Zh6KFdMqux4M9PvhoqcoJsJKwXjWpKu7xmEKnnrSbfLadkgjBmmjhW3fdTrFvnhQdTkhtdJxUL1xS9GMuJQer8YgSKNtUXB1eXZQwXU8bU2BjYkZE6Q5Xww8hu9Z4E4Mo4QsooVtHoP6BM3NKw8zjVbWfoCQqxTrwuSzrNCWCWt58C24LHecH67CTt2uXbYSviixvrYkK7A3t68BxTJcF1dXJitEPTFe2ceTkauLJqrJgnER4iUrsjr26T8YgWvpY9wkkWFSviQW6wV5RASTCUasVEcrDiaKj8EQMkgyDoe9HyKitSVg67vMWJFpUXpQobseWJUs5FTWWzmfHmFp8FZ","meta":{"fee":18000,"postBalances":[499999972500,15298080,1,1,1],"preBalances":[499999990500,15298080,1,1,1],"status":{"Ok":null}}}]},"id":1}
|
||||
```
|
||||
|
||||
### getConfirmedBlocks
|
||||
@ -579,7 +576,7 @@ An RpcResponse containing a JSON object consisting of a string blockhash and Fee
|
||||
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getRecentBlockhash"}' http://localhost:8899
|
||||
|
||||
// Result
|
||||
{"jsonrpc":"2.0","result":{"context":{"slot":1},"value":{"blockhash":"CSymwgTNX1j3E4qhKfJAUE41nBWEwXufoYryPbkde5RR","feeCalculator":{"burnPercent":50,"lamportsPerSignature":5000,"maxLamportsPerSignature":1.0.10,"minLamportsPerSignature":5000,"targetLamportsPerSignature":1.0.1,"targetSignaturesPerSlot":20000}}},"id":1}
|
||||
{"jsonrpc":"2.0","result":{"context":{"slot":1},"value":{"blockhash":"CSymwgTNX1j3E4qhKfJAUE41nBWEwXufoYryPbkde5RR","feeCalculator":{"burnPercent":50,"lamportsPerSignature":5000,"maxLamportsPerSignature":100000,"minLamportsPerSignature":5000,"targetLamportsPerSignature":10000,"targetSignaturesPerSlot":20000}}},"id":1}
|
||||
```
|
||||
|
||||
### getSignatureConfirmation
|
||||
@ -830,7 +827,7 @@ The result field will be a JSON object with the following fields:
|
||||
// Request
|
||||
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getVersion"}' http://localhost:8899
|
||||
// Result
|
||||
{"jsonrpc":"2.0","result":{"solana-core": "1.0.1"},"id":1}
|
||||
{"jsonrpc":"2.0","result":{"solana-core": "0.23.0"},"id":1}
|
||||
```
|
||||
|
||||
### getVoteAccounts
|
@ -52,7 +52,7 @@ $ NDEBUG=1 ./multinode-demo/faucet.sh
|
||||
|
||||
### Singlenode Testnet
|
||||
|
||||
Before you start a validator, make sure you know the IP address of the machine you want to be the bootstrap validator for the demo, and make sure that udp ports 8000-1.0.1 are open on all the machines you want to test with.
|
||||
Before you start a validator, make sure you know the IP address of the machine you want to be the bootstrap validator for the demo, and make sure that udp ports 8000-10000 are open on all the machines you want to test with.
|
||||
|
||||
Now start the bootstrap validator in a separate shell:
|
||||
|
||||
@ -123,7 +123,7 @@ This will dump all the threads stack traces into gdb.txt
|
||||
|
||||
### Blockstreamer
|
||||
|
||||
Solana supports a node type called a _blockstreamer_. This validator variation is intended for applications that need to observe the data plane without participating in transaction validation or ledger replication.
|
||||
Solana supports a node type called an _blockstreamer_. This validator variation is intended for applications that need to observe the data plane without participating in transaction validation or ledger replication.
|
||||
|
||||
A blockstreamer runs without a vote signer, and can optionally stream ledger entries out to a Unix domain socket as they are processed. The JSON-RPC service still functions as on any other node.
|
||||
|
||||
@ -151,10 +151,10 @@ The stream will output a series of JSON objects:
|
||||
|
||||
## Public Testnet
|
||||
|
||||
In this example the client connects to our public testnet. To run validators on the testnet you would need to open udp ports `8000-1.0.1`.
|
||||
In this example the client connects to our public testnet. To run validators on the testnet you would need to open udp ports `8000-10000`.
|
||||
|
||||
```bash
|
||||
$ NDEBUG=1 ./multinode-demo/bench-tps.sh --entrypoint devnet.solana.com:8001 --faucet devnet.solana.com:9900 --duration 60 --tx_count 50
|
||||
$ NDEBUG=1 ./multinode-demo/bench-tps.sh --entrypoint testnet.solana.com:8001 --faucet testnet.solana.com:9900 --duration 60 --tx_count 50
|
||||
```
|
||||
|
||||
You can observe the effects of your client's transactions on our [dashboard](https://metrics.solana.com:3000/d/testnet/testnet-hud?orgId=2&from=now-30m&to=now&refresh=5s&var-testnet=testnet)
|
@ -22,6 +22,12 @@ $ solana airdrop 2
|
||||
|
||||
// Return
|
||||
"2.00000000 SOL"
|
||||
|
||||
// Command
|
||||
$ solana airdrop 123 --lamports
|
||||
|
||||
// Return
|
||||
"123 lamports"
|
||||
```
|
||||
|
||||
### Get Balance
|
@ -1,5 +1,5 @@
|
||||
# Using Solana from the Command-line
|
||||
|
||||
This section describes the command-line tools for interacting with Solana. One
|
||||
This chapter describes the command-line tools for interacting with Solana. One
|
||||
could use these tools to send payments, stake validators, and check account
|
||||
balances.
|
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,6 @@
|
||||
# A Solana Cluster
|
||||
|
||||
A Solana cluster is a set of validators working together to serve client transactions and maintain the integrity of the ledger. Many clusters may coexist. When two clusters share a common genesis block, they attempt to converge. Otherwise, they simply ignore the existence of the other. Transactions sent to the wrong one are quietly rejected. In this section, we'll discuss how a cluster is created, how nodes join the cluster, how they share the ledger, how they ensure the ledger is replicated, and how they cope with buggy and malicious nodes.
|
||||
A Solana cluster is a set of validators working together to serve client transactions and maintain the integrity of the ledger. Many clusters may coexist. When two clusters share a common genesis block, they attempt to converge. Otherwise, they simply ignore the existence of the other. Transactions sent to the wrong one are quietly rejected. In this chapter, we'll discuss how a cluster is created, how nodes join the cluster, how they share the ledger, how they ensure the ledger is replicated, and how they cope with buggy and malicious nodes.
|
||||
|
||||
## Creating a Cluster
|
||||
|
@ -1,6 +1,6 @@
|
||||
# Fork Generation
|
||||
|
||||
This section describes how forks naturally occur as a consequence of [leader rotation](leader-rotation.md).
|
||||
The chapter describes how forks naturally occur as a consequence of [leader rotation](leader-rotation.md).
|
||||
|
||||
## Overview
|
||||
|
@ -1,6 +1,6 @@
|
||||
# Stake Delegation and Rewards
|
||||
|
||||
Stakers are rewarded for helping to validate the ledger. They do this by delegating their stake to validator nodes. Those validators do the legwork of replaying the ledger and send votes to a per-node vote account to which stakers can delegate their stakes. The rest of the cluster uses those stake-weighted votes to select a block when forks arise. Both the validator and staker need some economic incentive to play their part. The validator needs to be compensated for its hardware and the staker needs to be compensated for the risk of getting its stake slashed. The economics are covered in [staking rewards](../implemented-proposals/staking-rewards.md). This section, on the other hand, describes the underlying mechanics of its implementation.
|
||||
Stakers are rewarded for helping to validate the ledger. They do this by delegating their stake to validator nodes. Those validators do the legwork of replaying the ledger and send votes to a per-node vote account to which stakers can delegate their stakes. The rest of the cluster uses those stake-weighted votes to select a block when forks arise. Both the validator and staker need some economic incentive to play their part. The validator needs to be compensated for its hardware and the staker needs to be compensated for the risk of getting its stake slashed. The economics are covered in [staking rewards](../implemented-proposals/staking-rewards.md). This chapter, on the other hand, describes the underlying mechanics of its implementation.
|
||||
|
||||
## Basic Design
|
||||
|
||||
@ -29,7 +29,11 @@ VoteState is the current state of all the votes the validator has submitted to t
|
||||
* Account::lamports - The accumulated lamports from the commission. These do not count as stakes.
|
||||
* `authorized_voter` - Only this identity is authorized to submit votes. This field can only modified by this identity.
|
||||
* `node_pubkey` - The Solana node that votes in this account.
|
||||
* `authorized_withdrawer` - the identity of the entity in charge of the lamports of this account, separate from the account's address and the authorized vote signer
|
||||
* `authorized_withdrawer` - the identity of the entity in charge of the lamports of this account, separate from the account's
|
||||
|
||||
```text
|
||||
address and the authorized vote signer
|
||||
```
|
||||
|
||||
### VoteInstruction::Initialize\(VoteInit\)
|
||||
|
||||
@ -44,11 +48,13 @@ VoteState is the current state of all the votes the validator has submitted to t
|
||||
Updates the account with a new authorized voter or withdrawer, according to the VoteAuthorize parameter \(`Voter` or `Withdrawer`\). The transaction must be by signed by the Vote account's current `authorized_voter` or `authorized_withdrawer`.
|
||||
|
||||
* `account[0]` - RW - The VoteState
|
||||
|
||||
`VoteState::authorized_voter` or `authorized_withdrawer` is set to to `Pubkey`.
|
||||
|
||||
### VoteInstruction::Vote\(Vote\)
|
||||
|
||||
* `account[0]` - RW - The VoteState
|
||||
|
||||
`VoteState::lockouts` and `VoteState::credits` are updated according to voting lockout rules see [Tower BFT](../implemented-proposals/tower-bft.md)
|
||||
|
||||
* `account[1]` - RO - `sysvar::slot_hashes` A list of some N most recent slots and their hashes for the vote to be verified against.
|
||||
@ -67,10 +73,18 @@ StakeState::Stake is the current delegation preference of the **staker** and con
|
||||
* `voter_pubkey` - The pubkey of the VoteState instance the lamports are delegated to.
|
||||
* `credits_observed` - The total credits claimed over the lifetime of the program.
|
||||
* `activated` - the epoch at which this stake was activated/delegated. The full stake will be counted after warm up.
|
||||
* `deactivated` - the epoch at which this stake was de-activated, some cool down epochs are required before the account is fully deactivated, and the stake available for withdrawal
|
||||
* `deactivated` - the epoch at which this stake was de-activated, some cool down epochs are required before the account
|
||||
|
||||
```text
|
||||
is fully deactivated, and the stake available for withdrawal
|
||||
```
|
||||
|
||||
* `authorized_staker` - the pubkey of the entity that must sign delegation, activation, and deactivation transactions
|
||||
* `authorized_withdrawer` - the identity of the entity in charge of the lamports of this account, separate from the account's address, and the authorized staker
|
||||
* `authorized_withdrawer` - the identity of the entity in charge of the lamports of this account, separate from the account's
|
||||
|
||||
```text
|
||||
address, and the authorized staker
|
||||
```
|
||||
|
||||
### StakeState::RewardsPool
|
||||
|
||||
@ -80,13 +94,12 @@ The Stakes and the RewardsPool are accounts that are owned by the same `Stake` p
|
||||
|
||||
### StakeInstruction::DelegateStake
|
||||
|
||||
The Stake account is moved from Initialized to StakeState::Stake form, or from a deactivated (i.e. fully cooled-down) StakeState::Stake to activated StakeState::Stake. This is how stakers choose the vote account and validator node to which their stake account lamports are delegated. The transaction must be signed by the stake's `authorized_staker`.
|
||||
The Stake account is moved from Ininitialized to StakeState::Stake form. This is how stakers choose their initial delegate validator node and activate their stake account lamports. The transaction must be signed by the stake's `authorized_staker`. If the stake account is already StakeState::Stake \(i.e. already activated\), the stake is re-delegated. Stakes may be re-delegated at any time, and updated stakes are reflected immediately, but only one re-delegation is permitted per epoch.
|
||||
|
||||
* `account[0]` - RW - The StakeState::Stake instance. `StakeState::Stake::credits_observed` is initialized to `VoteState::credits`, `StakeState::Stake::voter_pubkey` is initialized to `account[1]`. If this is the initial delegation of stake, `StakeState::Stake::stake` is initialized to the account's balance in lamports, `StakeState::Stake::activated` is initialized to the current Bank epoch, and `StakeState::Stake::deactivated` is initialized to std::u64::MAX
|
||||
* `account[1]` - R - The VoteState instance.
|
||||
* `account[2]` - R - sysvar::clock account, carries information about current Bank epoch
|
||||
* `account[3]` - R - sysvar::stakehistory account, carries information about stake history
|
||||
* `account[4]` - R - stake::Config accoount, carries warmup, cooldown, and slashing configuration
|
||||
* `account[3]` - R - stake::Config accoount, carries warmup, cooldown, and slashing configuration
|
||||
|
||||
### StakeInstruction::Authorize\(Pubkey, StakeAuthorize\)
|
||||
|
||||
@ -154,7 +167,7 @@ Stakers who have delegated to that validator earn points in proportion to their
|
||||
|
||||
Stakes, once delegated, do not become effective immediately. They must first pass through a warm up period. During this period some portion of the stake is considered "effective", the rest is considered "activating". Changes occur on epoch boundaries.
|
||||
|
||||
The stake program limits the rate of change to total network stake, reflected in the stake program's `config::warmup_rate` \(set to 25% per epoch in the current implementation\).
|
||||
The stake program limits the rate of change to total network stake, reflected in the stake program's `config::warmup_rate` \(typically 25% per epoch\).
|
||||
|
||||
The amount of stake that can be warmed up each epoch is a function of the previous epoch's total effective stake, total activating stake, and the stake program's configured warmup rate.
|
||||
|
||||
@ -166,14 +179,14 @@ Rewards are paid against the "effective" portion of the stake for that epoch.
|
||||
|
||||
#### Warmup example
|
||||
|
||||
Consider the situation of a single stake of 1.0.1 activated at epoch N, with network warmup rate of 20%, and a quiescent total network stake at epoch N of 2,000.
|
||||
Consider the situation of a single stake of 1,000 activated at epoch N, with network warmup rate of 20%, and a quiescent total network stake at epoch N of 2,000.
|
||||
|
||||
At epoch N+1, the amount available to be activated for the network is 400 \(20% of 200\), and at epoch N, this example stake is the only stake activating, and so is entitled to all of the warmup room available.
|
||||
|
||||
| epoch | effective | activating | total effective | total activating |
|
||||
| :--- | ---: | ---: | ---: | ---: |
|
||||
| N-1 | | | 2,000 | 0 |
|
||||
| N | 0 | 1.0.1 | 2,000 | 1.0.1 |
|
||||
| N | 0 | 1,000 | 2,000 | 1,000 |
|
||||
| N+1 | 400 | 600 | 2,400 | 600 |
|
||||
| N+2 | 880 | 120 | 2,880 | 120 |
|
||||
| N+3 | 1000 | 0 | 3,000 | 0 |
|
||||
@ -183,7 +196,7 @@ Were 2 stakes \(X and Y\) to activate at epoch N, they would be awarded a portio
|
||||
| epoch | X eff | X act | Y eff | Y act | total effective | total activating |
|
||||
| :--- | ---: | ---: | ---: | ---: | ---: | ---: |
|
||||
| N-1 | | | | | 2,000 | 0 |
|
||||
| N | 0 | 1.0.1 | 0 | 200 | 2,000 | 1,200 |
|
||||
| N | 0 | 1,000 | 0 | 200 | 2,000 | 1,200 |
|
||||
| N+1 | 333 | 667 | 67 | 133 | 2,400 | 800 |
|
||||
| N+2 | 733 | 267 | 146 | 54 | 2,880 | 321 |
|
||||
| N+3 | 1000 | 0 | 200 | 0 | 3,200 | 0 |
|
@ -8,7 +8,7 @@ During its slot, the leader node distributes shreds between the validator nodes
|
||||
|
||||
In order for data plane fanout to work, the entire cluster must agree on how the cluster is divided into neighborhoods. To achieve this, all the recognized validator nodes \(the TVU peers\) are sorted by stake and stored in a list. This list is then indexed in different ways to figure out neighborhood boundaries and retransmit peers. For example, the leader will simply select the first nodes to make up layer 0. These will automatically be the highest stake holders, allowing the heaviest votes to come back to the leader first. Layer-0 and lower-layer nodes use the same logic to find their neighbors and next layer peers.
|
||||
|
||||
To reduce the possibility of attack vectors, each shred is transmitted over a random tree of neighborhoods. Each node uses the same set of nodes representing the cluster. A random tree is generated from the set for each shred using a seed derived from the leader id, slot and shred index.
|
||||
To reduce the possibility of attack vectors, each shred is transmitted over a random tree of neighborhoods. Each node uses the same set of nodes representing the cluster. A random tree is generated from the set for each shred using randomness derived from the shred itself. Since the random seed is not known in advance, attacks that try to eclipse neighborhoods from certain leaders or blocks become very difficult, and should require almost complete control of the stake in the cluster.
|
||||
|
||||
## Layer and Neighborhood Structure
|
||||
|
@ -10,8 +10,6 @@ These protocol-based rewards, to be distributed to participating validation and
|
||||
|
||||
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 opportunities 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.
|
||||
|
||||

|
||||
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 chapter 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. The [Replication-client Economics](ed_replication_client_economics/) chapter 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 chapter [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.
|
@ -2,15 +2,13 @@
|
||||
|
||||
**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.
|
||||
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 1**
|
||||
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.
|
||||
|
||||
**Figure 1**: 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\).
|
||||
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\).
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
**Subject to change.**
|
||||
|
||||
The preceding 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.
|
||||
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
|
||||
|
@ -2,5 +2,5 @@
|
||||
|
||||
**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).
|
||||
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 above in the [Replication-validation Transaction Fees](../ed_validation_client_economics/ed_vce_replication_validation_transaction_fees.md) chapter.
|
||||
|
@ -1,12 +1,12 @@
|
||||
## 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, transactional, 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 maintained 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.
|
||||
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 liquidity and the account holder can trust that their `Account::data` will be retained for continual access/usage.
|
||||
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
|
||||
|
@ -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/)\), 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 \(detail in the [Economic Sustainability](../ed_economic_sustainability.md) chapter\), 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 below in [Economic Sustainability](../ed_economic_sustainability/README.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](https://github.com/solana-labs/solana/blob/master/book/src/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\).
|
@ -15,20 +15,16 @@ The effective protocol-based annual interest rate \(%\) per epoch received by va
|
||||
* 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 monetary stability and provide optimal security in the network.
|
||||
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 proportion 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 1**, while the total circulating token supply is illustrated in **Figure 2**. Neglected in this toy-model is the inflation suppression due to the portion of each transaction fee that is to be destroyed.
|
||||
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 1:** 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 2:** The total token supply over a 10-year period, based on an initial 250MM tokens with the disinflationary inflation schedule as shown in **Figure 1**. 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 stabilize 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 assumed 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 3**.
|
||||
|
||||

|
||||
|
||||
**Figure 3:** Shown here are example validator interest rates over time, neglecting transaction fees, segmented by fraction of total circulating supply bonded as stake.
|
||||
**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.
|
@ -11,8 +11,8 @@ Each transaction sent through the network, to be processed by the current leader
|
||||
|
||||
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 utilization. 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.
|
||||
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 mechanism that protects against "tax evasion" attacks \(i.e. side-channel fee payments\)[1](../ed_references.md).
|
||||
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.
|
@ -11,7 +11,7 @@ This document proposes an easy to use software install and updater that can be u
|
||||
The easiest install method for supported platforms:
|
||||
|
||||
```bash
|
||||
$ curl -sSf https://raw.githubusercontent.com/solana-labs/solana/v1.0.0/install/solana-install-init.sh | sh
|
||||
$ 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.
|
||||
@ -20,7 +20,7 @@ If additional arguments need to be specified during the installation, the follow
|
||||
|
||||
```bash
|
||||
$ init_args=.... # arguments for `solana-install-init ...`
|
||||
$ curl -sSf https://raw.githubusercontent.com/solana-labs/solana/v1.0.0/install/solana-install-init.sh | sh -s - ${init_args}
|
||||
$ 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
|
||||
@ -28,7 +28,7 @@ $ curl -sSf https://raw.githubusercontent.com/solana-labs/solana/v1.0.0/install/
|
||||
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/v1.0.0/solana-install-init-x86_64-apple-darwin
|
||||
$ 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
|
||||
```
|
||||
@ -77,7 +77,7 @@ pub struct UpdateManifest {
|
||||
pub download_sha256: String, // SHA256 digest of the release tar.bz2 file
|
||||
}
|
||||
|
||||
/// Data of an Update Manifest program Account.
|
||||
/// Userdata of an Update Manifest program Account.
|
||||
#[derive(Serialize, Deserialize, Default, Debug, PartialEq)]
|
||||
pub struct SignedUpdateManifest {
|
||||
pub manifest: UpdateManifest,
|
||||
@ -154,7 +154,7 @@ FLAGS:
|
||||
|
||||
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]
|
||||
-u, --url <URL> JSON RPC URL for the solana cluster [default: http://testnet.solana.com:8899]
|
||||
-p, --pubkey <PUBKEY> Public key of the update manifest [default: 9XX329sPuskWhH4DQh6k16c87dHKhXLBZTL3Gxmve8Gp]
|
||||
```
|
||||
|
49
book/src/implemented-proposals/repair-service.md
Normal file
49
book/src/implemented-proposals/repair-service.md
Normal file
@ -0,0 +1,49 @@
|
||||
# Repair Service
|
||||
|
||||
## Repair Service
|
||||
|
||||
The RepairService is in charge of retrieving missing shreds that failed to be delivered by primary communication protocols like Avalanche. 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.
|
||||
|
||||
3\) Validators that find themselves behind the cluster by an entire epoch struggle/fail to catch up because they do not have a leader schedule for future epochs. If nodes were to blindly accept repair shreds in these future epochs, this exposes nodes to spam.
|
||||
|
||||
## Repair 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.
|
||||
|
||||
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.
|
||||
* Blockstore will track the set of "orphan" slots in a separate column family.
|
||||
* RepairService will periodically make `RequestOrphan` requests for each of the orphans in blockstore.
|
||||
|
||||
`RequestOrphan(orphan)` request - `orphan` is the orphan slot that the requestor wants to know the parents of `RequestOrphan(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\).
|
||||
3. Repairmen \(Addresses Challenge \#3\): This part of the repair protocol is the primary mechanism by which new nodes joining the cluster catch up after loading a snapshot. This protocol works in a "forward" fashion, so validators can verify every shred that they receive against a known leader schedule.
|
||||
|
||||
Each validator advertises in gossip:
|
||||
|
||||
* Current root
|
||||
* The set of all completed slots in the confirmed epochs \(an epoch that was calculated based on a bank <= current root\) past the current root
|
||||
|
||||
Observers of this gossip message with higher epochs \(repairmen\) send shreds to catch the lagging node up with the rest of the cluster. The repairmen are responsible for sending the slots within the epochs that are confrimed by the advertised `root` in gossip. The repairmen divide the responsibility of sending each of the missing slots in these epochs based on a random seed \(simple shred.index iteration by N, seeded with the repairman's node\_pubkey\). Ideally, each repairman in an N node cluster \(N nodes whose epochs are higher than that of the repairee\) sends 1/N of the missing shreds. Both data and coding shreds for missing slots are sent. Repairmen do not send shreds again to the same validator until they see the message in gossip updated, at which point they perform another iteration of this protocol.
|
||||
|
||||
Gossip messages 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. When a newly completed slot is detected, we also update the current root if it has changed since the last update. The root is made available to RepairService through Blockstore, which holds the latest root.
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user