Compare commits

..

16 Commits

Author SHA1 Message Date
ff9bd2f512 Fix the output from Gossip Discovery (#4070) 2019-04-29 14:59:01 -07:00
25810ce729 Remove Bench Exchange Contract Execution graph 2019-04-29 14:28:09 -07:00
82c7f0e366 testnet-demo: use more low quota nodes 2019-04-29 12:08:39 -07:00
012d05f10b Increase testnet-demo node count a little 2019-04-29 09:10:18 -07:00
f853595efb testnet-demo now runs across more GCE zones (#4053)
* testnet-demo now runs across more GCE zones

* Save zone info to config file

* Add geoip whitelist for common data centers

* Skip more of start

* Include -x for config

* Fetch private key from first validator node if necessary

* Correct -r propagation
2019-04-28 19:50:02 -07:00
09e4f7e49c Correctly terminate instances across multiple zones 2019-04-28 09:09:34 -07:00
cb37072ed7 Switch testnet-demo to influxcloud 2019-04-27 22:12:30 -07:00
0b109d3340 Correct us-central1-b zone name 2019-04-27 21:43:31 -07:00
dcdc5b8cf7 testnet-demo: skip over validator nodes that fail to boot 2019-04-27 21:34:02 -07:00
1a7c30bb86 Use GPU nodes for blockstreamer as well if rest of testnet has GPUs (#4046) (#4048)
automerge
2019-04-27 21:31:01 -07:00
3ebc14f965 Blockstreamer annotation fix for non buildkite deployments (#4045) (#4047)
automerge
2019-04-27 21:01:26 -07:00
cf589efbbf Performance metrics computation methodology (#4041) (#4044)
automerge
2019-04-27 16:59:45 -07:00
94d5c64281 testnet-demo: add more GCE zones, remove client 2019-04-27 16:53:05 -07:00
566de1fd0e Add DNS resolution for network/drone arguments (#4037)
automerge
2019-04-27 10:00:41 -07:00
cb0f367084 Avoid inaccurate PATH nagging 2019-04-27 15:32:23 +00:00
e08e1fe6ac Add " 2019-04-27 07:41:55 -07:00
391 changed files with 12198 additions and 29391 deletions

View File

@ -10,8 +10,6 @@
set -x
rsync -a --delete --link-dest="$PWD" target "$d"
du -hs "$d"
read -r cacheSizeInGB _ < <(du -s --block-size=1800000000 "$d")
echo "--- ${cacheSizeInGB}GB: $d"
)
#

View File

@ -14,18 +14,14 @@ export PS4="++"
(
set -x
d=$HOME/cargo-target-cache/"$BUILDKITE_LABEL"
MAX_CACHE_SIZE=18 # gigabytes
if [[ -d $d ]]; then
du -hs "$d"
read -r cacheSizeInGB _ < <(du -s --block-size=1800000000 "$d")
echo "--- ${cacheSizeInGB}GB: $d"
if [[ $cacheSizeInGB -gt $MAX_CACHE_SIZE ]]; then
echo "--- $d is too large, removing it"
read -r cacheSizeInGB _ < <(du -s --block-size=1000000000 "$d")
if [[ $cacheSizeInGB -gt 10 ]]; then
echo "$d has gotten too large, removing it"
rm -rf "$d"
fi
else
echo "--- $d not present"
fi
mkdir -p "$d"/target

4
.gitignore vendored
View File

@ -1,11 +1,13 @@
/book/html/
/book/src/img/
/book/src/tests.ok
/core/target/
/farf/
/metrics/scripts/lib/
/ledger-tool/target/
/solana-release/
solana-release.tar.bz2
/target/
/wallet/target/
**/*.rs.bk
.cargo

1127
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,12 +1,11 @@
[workspace]
members = [
".",
"bench-exchange",
"bench-streamer",
"bench-tps",
"client",
"core",
"drone",
"validator",
"fullnode",
"genesis",
"gossip",
"install",
@ -15,7 +14,6 @@ members = [
"ledger-tool",
"logger",
"metrics",
"netutil",
"programs/bpf",
"programs/bpf_loader",
"programs/budget_api",
@ -24,18 +22,17 @@ members = [
"programs/config_program",
"programs/exchange_api",
"programs/exchange_program",
"programs/token_api",
"programs/token_program",
"programs/failure_program",
"programs/noop_program",
"programs/stake_api",
"programs/stake_program",
"programs/storage_api",
"programs/storage_program",
"programs/token_api",
"programs/token_program",
"programs/vote_api",
"programs/vote_program",
"replicator",
"runtime",
"sdk",
"upload-perf",
"vote-signer",

View File

@ -41,7 +41,7 @@ Install rustc, cargo and rustfmt:
```bash
$ curl https://sh.rustup.rs -sSf | sh
$ source $HOME/.cargo/env
$ rustup component add rustfmt
$ rustup component add rustfmt-preview
```
If your rustc version is lower than 1.34.0, please update it:
@ -66,7 +66,7 @@ $ cd solana
Build
```bash
$ cargo build
$ cargo build --all
```
Then to run a minimal local cluster
@ -80,7 +80,7 @@ Testing
Run the test suite:
```bash
$ cargo test
$ cargo test --all
```
Local Testnet

View File

@ -61,7 +61,7 @@ There are three release channels that map to branches as follows:
## Release Steps
### Advance the Channels
### Changing channels
#### Create the new branch
1. Pick your branch point for release on master.
@ -84,7 +84,7 @@ There are three release channels that map to branches as follows:
At this point, `ci/channel-info.sh` should show your freshly cut release branch as
"BETA_CHANNEL" and the previous release branch as "STABLE_CHANNEL".
### Make the Release
### Updating channels (i.e. "making a release")
We use [github's Releases UI](https://github.com/solana-labs/solana/releases) for tagging a release.
@ -99,59 +99,13 @@ We use [github's Releases UI](https://github.com/solana-labs/solana/releases) fo
release should be `<branchname>.X-rc.0`.
1. Verify release automation:
1. [Crates.io](https://crates.io/crates/solana) should have an updated Solana version.
1. ...
1. After testnet deployment, verify that testnets are running correct software.
http://metrics.solana.com should show testnet running on a hash from your
newly created branch.
1. Once the release has been made, update Cargo.toml on the release branch to the next
semantic version (e.g. 0.9.0 -> 0.9.1) by running
`./scripts/increment-cargo-version.sh patch`, then rebuild with `cargo
build` to cause a refresh of `Cargo.lock`.
1. Push your Cargo.toml change and the autogenerated Cargo.lock changes to the
release branch.
### Update software on testnet.solana.com
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.
#### Update testnet schedules
Go to https://buildkite.com/solana-labs and click through: Pipelines ->
testnet-management -> Pipeline Settings -> Schedules
Or just click here:
https://buildkite.com/solana-labs/testnet-management/settings/schedules
There are two scheduled jobs for testnet: a daily restart and an hourly sanity-or-restart. \
https://buildkite.com/solana-labs/testnet-management/settings/schedules/0efd7856-7143-4713-8817-47e6bdb05387
https://buildkite.com/solana-labs/testnet-management/settings/schedules/2a926646-d972-42b5-aeb9-bb6759592a53
On each schedule:
1. Set TESTNET_TAG environment variable to the desired release tag.
1. Example, TESTNET_TAG=v0.13.2
1. Set the Build Branch to the branch that TESTNET_TAG is from.
1. Example: v0.13
#### Restart the testnet
Trigger a TESTNET_OP=create-and-start to refresh the cluster with the new version
1. Go to https://buildkite.com/solana-labs/testnet-management
2. Click "New Build" and use the following settings, then click "Create Build"
1. Commit: HEAD
1. Branch: [channel branch as set in the schedules]
1. Environment Variables:
```
TESTNET=testnet
TESTNET_TAG=[same value as used in TESTNET_TAG in the schedules]
TESTNET_OP=create-and-start
```
#### Update documentation
Document the new recommended version by updating
```export SOLANA_RELEASE=[new scheduled TESTNET_TAG value]```
in book/src/testnet-participation.md for both edge and beta channel branches.
### Alert the community
Notify Discord users on #validator-support that a new release for
testnet.solana.com is available

View File

@ -2,38 +2,36 @@
authors = ["Solana Maintainers <maintainers@solana.com>"]
edition = "2018"
name = "solana-bench-exchange"
version = "0.15.2"
version = "0.14.0"
repository = "https://github.com/solana-labs/solana"
license = "Apache-2.0"
homepage = "https://solana.com/"
publish = false
[dependencies]
bs58 = "0.2.0"
clap = "2.32.0"
bincode = "1.1.4"
bincode = "1.1.2"
env_logger = "0.6.0"
itertools = "0.8.0"
log = "0.4.6"
num-traits = "0.2"
num-derive = "0.2"
rand = "0.6.5"
rayon = "1.0.3"
serde = "1.0.91"
serde_derive = "1.0.91"
serde = "1.0.87"
serde_derive = "1.0.87"
serde_json = "1.0.38"
# solana-runtime = { path = "../solana/runtime"}
solana = { path = "../core", version = "0.15.2" }
solana-client = { path = "../client", version = "0.15.2" }
solana-drone = { path = "../drone", version = "0.15.2" }
solana-exchange-api = { path = "../programs/exchange_api", version = "0.15.2" }
solana-exchange-program = { path = "../programs/exchange_program", version = "0.15.2" }
solana-logger = { path = "../logger", version = "0.15.2" }
solana-metrics = { path = "../metrics", version = "0.15.2" }
solana-netutil = { path = "../netutil", version = "0.15.2" }
solana-runtime = { path = "../runtime", version = "0.15.2" }
solana-sdk = { path = "../sdk", version = "0.15.2" }
ws = "0.8.1"
solana = { path = "../core", version = "0.14.0" }
solana-client = { path = "../client", version = "0.14.0" }
solana-drone = { path = "../drone", version = "0.14.0" }
solana-exchange-api = { path = "../programs/exchange_api", version = "0.14.0" }
solana-exchange-program = { path = "../programs/exchange_program", version = "0.14.0" }
solana-logger = { path = "../logger", version = "0.14.0" }
solana-metrics = { path = "../metrics", version = "0.14.0" }
solana-netutil = { path = "../netutil", version = "0.14.0" }
solana-runtime = { path = "../runtime", version = "0.14.0" }
solana-sdk = { path = "../sdk", version = "0.14.0" }
ws = "0.8.0"
untrusted = "0.6.2"
[features]

View File

@ -23,7 +23,7 @@ demo demonstrates one way to host an exchange on the Solana blockchain by
emulating a currency exchange.
The assets are virtual tokens held by investors who may post trade requests to
the exchange. A Swapper monitors the exchange and posts swap requests for
the exchange. A broker monitors the exchange and posts swap requests for
matching trade orders. All the transactions can execute concurrently.
## Premise
@ -75,7 +75,7 @@ matching trade orders. All the transactions can execute concurrently.
contain the same information as the trade request.
- Price spread
- The difference between the two matching trade orders. The spread is the
profit of the Swapper initiating the swap request.
profit of the broker initiating the swap request.
- Swap requirements
- Policies that result in a successful trade swap.
- Swap request
@ -85,7 +85,7 @@ matching trade orders. All the transactions can execute concurrently.
swap requirements. A trade swap may not wholly satisfy one or both of the
trade orders in which case the trade orders are adjusted appropriately. As
long as the swap requirements are met there will be an exchange of tokens
between accounts. Any price spread is deposited into the Swapper's profit
between accounts. Any price spread is deposited into the broker's profit
account. All trade swaps are recorded in a new account for posterity.
- Investor
- Individual investors who hold a number of tokens and wish to trade them on
@ -93,41 +93,41 @@ matching trade orders. All the transactions can execute concurrently.
accounts containing tokens and/or trade requests. Investors post
transactions to the exchange in order to request tokens and post or cancel
trade requests.
- Swapper
- An agent who facilitates trading between investors. Swappers operate as
- Broker
- An agent who facilitates trading between investors. Brokers operate as
Solana thin clients who monitor all the trade orders looking for a trade
match. Once found, the Swapper issues a swap request to the exchange.
Swappers are the engine of the exchange and are rewarded for their efforts by
accumulating the price spreads of the swaps they initiate. Swappers also
match. Once found, the broker issues a swap request to the exchange.
Brokers are the engine of the exchange and are rewarded for their efforts by
accumulating the price spreads of the swaps they initiate. Brokers also
provide current bid/ask price and OHLCV (Open, High, Low, Close, Volume)
information on demand via a public network port.
- Transaction fees
- Solana transaction fees are paid for by the transaction submitters who are
the Investors and Swappers.
the Investors and Brokers.
## Exchange startup
The exchange is up and running when it reaches a state where it can take
investor's trades and Swapper's swap requests. To achieve this state the
investor's trades and broker's swap requests. To achieve this state the
following must occur in order:
- Start the Solana blockchain
- Start the Swapper thin-client
- The Swapper subscribes to change notifications for all the accounts owned by
- Start the broker thin-client
- The broker subscribes to change notifications for all the accounts owned by
the exchange program id. The subscription is managed via Solana's JSON RPC
interface.
- The Swapper starts responding to queries for bid/ask price and OHLCV
- The broker starts responding to queries for bid/ask price and OHLCV
The Swapper responding successfully to price and OHLCV requests is the signal to
The broker responding successfully to price and OHLCV requests is the signal to
the investors that trades submitted after that point will be analyzed. <!--This
is not ideal, and instead investors should be able to submit trades at any time,
and the Swapper could come and go without missing a trade. One way to achieve
this is for the Swapper to read the current state of all accounts looking for all
and the broker could come and go without missing a trade. One way to achieve
this is for the broker to read the current state of all accounts looking for all
open trade orders.-->
Investors will initially query the exchange to discover their current balance
for each type of token. If the investor does not already have an account for
each type of token, they will submit account requests. Swappers as well will
each type of token, they will submit account requests. Brokers as well will
request accounts to hold the tokens they earn by initiating trade swaps.
```rust
@ -165,7 +165,7 @@ pub struct TokenAccountInfo {
}
```
For this demo investors or Swappers can request more tokens from the exchange at
For this demo investors or brokers can request more tokens from the exchange at
any time by submitting token requests. In non-demos, an exchange of this type
would provide another way to exchange a 3rd party asset into tokens.
@ -269,10 +269,10 @@ pub enum ExchangeInstruction {
## Trade swaps
The Swapper is monitoring the accounts assigned to the exchange program and
The broker is monitoring the accounts assigned to the exchange program and
building a trade-order table. The trade order table is used to identify
matching trade orders which could be fulfilled. When a match is found the
Swapper should issue a swap request. Swap requests may not satisfy the entirety
broker should issue a swap request. Swap requests may not satisfy the entirety
of either order, but the exchange will greedily fulfill it. Any leftover tokens
in either account will keep the trade order valid for further swap requests in
the future.
@ -310,14 +310,14 @@ whole for clarity.
| 5 | 1 T AB 2 10 | 2 F AB 1 5 |
As part of a successful swap request, the exchange will credit tokens to the
Swapper's account equal to the difference in the price ratios or the two orders.
These tokens are considered the Swapper's profit for initiating the trade.
broker's account equal to the difference in the price ratios or the two orders.
These tokens are considered the broker's profit for initiating the trade.
The Swapper would initiate the following swap on the order table above:
The broker would initiate the following swap on the order table above:
- Row 1, To: Investor 1 trades 2 A tokens to 8 B tokens
- Row 1, From: Investor 2 trades 2 A tokens from 8 B tokens
- Swapper takes 8 B tokens as profit
- Broker takes 8 B tokens as profit
Both row 1 trades are fully realized, table becomes:
@ -328,11 +328,11 @@ Both row 1 trades are fully realized, table becomes:
| 3 | 1 T AB 2 8 | 2 F AB 3 6 |
| 4 | 1 T AB 2 10 | 2 F AB 1 5 |
The Swapper would initiate the following swap:
The broker would initiate the following swap:
- Row 1, To: Investor 1 trades 1 A token to 4 B tokens
- Row 1, From: Investor 2 trades 1 A token from 4 B tokens
- Swapper takes 4 B tokens as profit
- Broker takes 4 B tokens as profit
Row 1 From is not fully realized, table becomes:
@ -343,11 +343,11 @@ Row 1 From is not fully realized, table becomes:
| 3 | 1 T AB 2 10 | 2 F AB 3 6 |
| 4 | | 2 F AB 1 5 |
The Swapper would initiate the following swap:
The broker would initiate the following swap:
- Row 1, To: Investor 1 trades 1 A token to 6 B tokens
- Row 1, From: Investor 2 trades 1 A token from 6 B tokens
- Swapper takes 2 B tokens as profit
- Broker takes 2 B tokens as profit
Row 1 To is now fully realized, table becomes:
@ -357,11 +357,11 @@ Row 1 To is now fully realized, table becomes:
| 2 | 1 T AB 2 8 | 2 F AB 3 5 |
| 3 | 1 T AB 2 10 | 2 F AB 1 5 |
The Swapper would initiate the following last swap:
The broker would initiate the following last swap:
- Row 1, To: Investor 1 trades 2 A token to 12 B tokens
- Row 1, From: Investor 2 trades 2 A token from 12 B tokens
- Swapper takes 4 B tokens as profit
- Broker takes 4 B tokens as profit
Table becomes:
@ -383,7 +383,7 @@ pub enum ExchangeInstruction {
/// key 3 - `From` trade order
/// key 4 - Token account associated with the To Trade
/// key 5 - Token account associated with From trade
/// key 6 - Token account in which to deposit the Swappers profit from the swap.
/// key 6 - Token account in which to deposit the brokers profit from the swap.
SwapRequest,
}
@ -442,14 +442,14 @@ pub enum ExchangeInstruction {
/// key 3 - `From` trade order
/// key 4 - Token account associated with the To Trade
/// key 5 - Token account associated with From trade
/// key 6 - Token account in which to deposit the Swappers profit from the swap.
/// key 6 - Token account in which to deposit the brokers profit from the swap.
SwapRequest,
}
```
## Quotes and OHLCV
The Swapper will provide current bid/ask price quotes based on trade actively and
The broker will provide current bid/ask price quotes based on trade actively and
also provide OHLCV based on some time window. The details of how the bid/ask
price quotes are calculated are yet to be decided.

View File

@ -3,21 +3,23 @@
use crate::order_book::*;
use itertools::izip;
use log::*;
use rand::{thread_rng, Rng};
use rayon::prelude::*;
use solana::cluster_info::FULLNODE_PORT_RANGE;
use solana::contact_info::ContactInfo;
use solana::gen_keys::GenKeys;
use solana_client::perf_utils::{sample_txs, SampleStats};
use solana_client::thin_client::create_client;
use solana_client::thin_client::ThinClient;
use solana_drone::drone::request_airdrop_transaction;
use solana_exchange_api::exchange_instruction;
use solana_exchange_api::exchange_state::*;
use solana_exchange_api::id;
use solana_metrics::datapoint_info;
use solana_metrics::influxdb;
use solana_sdk::client::Client;
use solana_sdk::client::SyncClient;
use solana_sdk::pubkey::Pubkey;
use solana_sdk::signature::{Keypair, KeypairUtil};
use solana_sdk::system_instruction;
use solana_sdk::timing::{duration_as_ms, duration_as_s};
use solana_sdk::timing::{duration_as_ms, duration_as_ns, duration_as_s};
use solana_sdk::transaction::Transaction;
use std::cmp;
use std::collections::VecDeque;
@ -65,6 +67,16 @@ impl Default for Config {
}
}
#[derive(Default)]
pub struct SampleStats {
/// Maximum TPS reported by this node
pub tps: f32,
/// Total time taken for those txs
pub elapsed: Duration,
/// Total transactions reported by this node
pub txs: u64,
}
pub fn do_bench_exchange<T>(clients: Vec<T>, config: Config)
where
T: 'static + Client + Send + Sync,
@ -79,18 +91,6 @@ where
chunk_size,
account_groups,
} = config;
info!(
"Exchange client: threads {} duration {} fund_amount {}",
threads,
duration_as_s(&duration),
fund_amount
);
info!(
"Exchange client: transfer delay {} batch size {} chunk size {}",
transfer_delay, batch_size, chunk_size
);
let accounts_in_groups = batch_size * account_groups;
let exit_signal = Arc::new(AtomicBool::new(false));
let clients: Vec<_> = clients.into_iter().map(Arc::new).collect();
@ -168,7 +168,6 @@ where
&shared_txs,
&swapper_signers,
&profit_pubkeys,
transfer_delay,
batch_size,
chunk_size,
account_groups,
@ -238,6 +237,62 @@ where
);
}
fn sample_txs<T>(
exit_signal: &Arc<AtomicBool>,
sample_stats: &Arc<RwLock<Vec<SampleStats>>>,
sample_period: u64,
client: &Arc<T>,
) where
T: Client,
{
let mut max_tps = 0.0;
let mut total_elapsed;
let mut total_txs;
let mut now = Instant::now();
let start_time = now;
let initial_txs = client.get_transaction_count().expect("transaction count");
let mut last_txs = initial_txs;
loop {
total_elapsed = start_time.elapsed();
let elapsed = now.elapsed();
now = Instant::now();
let mut txs = client.get_transaction_count().expect("transaction count");
if txs < last_txs {
error!("expected txs({}) >= last_txs({})", txs, last_txs);
txs = last_txs;
}
total_txs = txs - initial_txs;
let sample_txs = txs - last_txs;
last_txs = txs;
let tps = sample_txs as f32 / duration_as_s(&elapsed);
if tps > max_tps {
max_tps = tps;
}
info!(
"Sampler {:9.2} TPS, Transactions: {:6}, Total transactions: {} over {} s",
tps,
sample_txs,
total_txs,
total_elapsed.as_secs(),
);
if exit_signal.load(Ordering::Relaxed) {
let stats = SampleStats {
tps: max_tps,
elapsed: total_elapsed,
txs: total_txs,
};
sample_stats.write().unwrap().push(stats);
return;
}
sleep(Duration::from_secs(sample_period));
}
}
fn do_tx_transfers<T>(
exit_signal: &Arc<AtomicBool>,
shared_txs: &SharedTransactions,
@ -246,6 +301,7 @@ fn do_tx_transfers<T>(
) where
T: Client,
{
let mut stats = Stats::default();
loop {
let txs;
{
@ -262,18 +318,48 @@ fn do_tx_transfers<T>(
let duration = now.elapsed();
total_txs_sent_count.fetch_add(n, Ordering::Relaxed);
datapoint_info!(
"bench-exchange-do_tx_transfers",
("duration", duration_as_ms(&duration), i64),
("count", n, i64)
stats.total += n as u64;
stats.sent_ns += duration_as_ns(&duration);
let rate = n as f32 / duration_as_s(&duration);
if rate > stats.sent_peak_rate {
stats.sent_peak_rate = rate;
}
trace!(" tx {:?} sent {:.2}/s", n, rate);
solana_metrics::submit(
influxdb::Point::new("bench-exchange")
.add_tag("op", influxdb::Value::String("do_tx_transfers".to_string()))
.add_field(
"duration",
influxdb::Value::Integer(duration_as_ms(&duration) as i64),
)
.add_field("count", influxdb::Value::Integer(n as i64))
.to_owned(),
);
}
if exit_signal.load(Ordering::Relaxed) {
info!(
" Thread Transferred {} Txs, avg {:.2}/s peak {:.2}/s",
stats.total,
(stats.total as f64 / stats.sent_ns as f64) * 1_000_000_000_f64,
stats.sent_peak_rate,
);
return;
}
}
}
#[derive(Default)]
struct Stats {
total: u64,
keygen_ns: u64,
keygen_peak_rate: f32,
sign_ns: u64,
sign_peak_rate: f32,
sent_ns: u64,
sent_peak_rate: f32,
}
struct TradeInfo {
trade_account: Pubkey,
order_info: TradeOrderInfo,
@ -285,7 +371,6 @@ fn swapper<T>(
shared_txs: &SharedTransactions,
signers: &[Arc<Keypair>],
profit_pubkeys: &[Pubkey],
transfer_delay: u64,
batch_size: usize,
chunk_size: usize,
account_groups: usize,
@ -293,57 +378,28 @@ fn swapper<T>(
) where
T: Client,
{
let mut stats = Stats::default();
let mut order_book = OrderBook::default();
let mut account_group: usize = 0;
let mut txs = 0;
let mut total_txs = 0;
let mut now = Instant::now();
let start_time = now;
let mut total_elapsed = start_time.elapsed();
// Chunks may have been dropped and we don't want to wait a long time
// for each time, Back-off each time we fail to confirm a chunk
const CHECK_TX_TIMEOUT_MAX_MS: u64 = 15000;
const CHECK_TX_DELAY_MS: u64 = 100;
let mut max_tries = CHECK_TX_TIMEOUT_MAX_MS / CHECK_TX_DELAY_MS;
// If we dump too many chunks maybe we are just waiting on a back-log
// rather than a series of dropped packets, reset to max waits
const MAX_DUMPS: u64 = 50;
let mut dumps = 0;
'outer: loop {
if let Ok(trade_infos) = receiver.try_recv() {
let mut tries = 0;
let mut trade_index = 0;
while client
.get_balance(&trade_infos[trade_index].trade_account)
.get_balance(&trade_infos[0].trade_account)
.unwrap_or(0)
== 0
{
tries += 1;
if tries >= max_tries {
if tries > 300 {
if exit_signal.load(Ordering::Relaxed) {
break 'outer;
}
error!("Give up and dump batch");
if dumps >= MAX_DUMPS {
error!("Max batches dumped, reset wait back-off");
max_tries = CHECK_TX_TIMEOUT_MAX_MS / CHECK_TX_DELAY_MS;
dumps = 0;
} else {
dumps += 1;
max_tries /= 2;
}
error!("Give up waiting, dump batch");
continue 'outer;
}
debug!("{} waiting for trades batch to clear", tries);
sleep(Duration::from_millis(CHECK_TX_DELAY_MS));
trade_index = thread_rng().gen_range(0, trade_infos.len());
sleep(Duration::from_millis(100));
}
max_tries = CHECK_TX_TIMEOUT_MAX_MS / CHECK_TX_DELAY_MS;
dumps = 0;
trade_infos.iter().for_each(|info| {
order_book
@ -358,6 +414,9 @@ fn swapper<T>(
}
}
let swaps_size = swaps.len();
stats.total += swaps_size as u64;
let now = Instant::now();
let mut to_swap = vec![];
let start = account_group * swaps_size as usize;
@ -370,8 +429,17 @@ fn swapper<T>(
to_swap.push((signer, swap, profit));
}
account_group = (account_group + 1) % account_groups as usize;
let duration = now.elapsed();
let rate = swaps_size as f32 / duration_as_s(&duration);
stats.keygen_ns += duration_as_ns(&duration);
if rate > stats.keygen_peak_rate {
stats.keygen_peak_rate = rate;
}
trace!("sw {:?} keypairs {:.2} /s", swaps_size, rate);
let (blockhash, _fee_calculator) = client
let now = Instant::now();
let blockhash = client
.get_recent_blockhash()
.expect("Failed to get blockhash");
let to_swap_txs: Vec<_> = to_swap
@ -391,25 +459,21 @@ fn swapper<T>(
)
})
.collect();
txs += to_swap_txs.len() as u64;
total_txs += to_swap_txs.len() as u64;
total_elapsed = start_time.elapsed();
let duration = now.elapsed();
if duration_as_s(&duration) >= 1_f32 {
now = Instant::now();
let tps = txs as f32 / duration_as_s(&duration);
info!(
"Swapper {:9.2} TPS, Transactions: {:6}, Total transactions: {} over {} s",
tps,
txs,
total_txs,
total_elapsed.as_secs(),
);
txs = 0;
let n = to_swap_txs.len();
let rate = n as f32 / duration_as_s(&duration);
stats.sign_ns += duration_as_ns(&duration);
if rate > stats.sign_peak_rate {
stats.sign_peak_rate = rate;
}
trace!(" sw {:?} signed {:.2} /s ", n, rate);
datapoint_info!("bench-exchange-swaps", ("count", to_swap_txs.len(), i64));
solana_metrics::submit(
influxdb::Point::new("bench-exchange")
.add_tag("op", influxdb::Value::String("swaps".to_string()))
.add_field("count", influxdb::Value::Integer(to_swap_txs.len() as i64))
.to_owned(),
);
let chunks: Vec<_> = to_swap_txs.chunks(chunk_size).collect();
{
@ -418,8 +482,6 @@ fn swapper<T>(
shared_txs_wl.push_back(chunk.to_vec());
}
}
// Throttle the swapper so it doesn't try to catchup unbridled
sleep(Duration::from_millis(transfer_delay / 2));
}
if exit_signal.load(Ordering::Relaxed) {
@ -427,9 +489,18 @@ fn swapper<T>(
}
}
info!(
"Swapper sent {} at {:9.2} TPS",
total_txs,
total_txs as f32 / duration_as_s(&total_elapsed)
"{} Swaps, batch size {}, chunk size {}",
stats.total, batch_size, chunk_size
);
info!(
" Keygen avg {:.2}/s peak {:.2}/s",
(stats.total as f64 / stats.keygen_ns as f64) * 1_000_000_000_f64,
stats.keygen_peak_rate
);
info!(
" Signed avg {:.2}/s peak {:.2}/s",
(stats.total as f64 / stats.sign_ns as f64) * 1_000_000_000_f64,
stats.sign_peak_rate
);
assert_eq!(
order_book.get_num_outstanding().0 + order_book.get_num_outstanding().1,
@ -444,7 +515,7 @@ fn trader<T>(
shared_txs: &SharedTransactions,
signers: &[Arc<Keypair>],
srcs: &[Pubkey],
transfer_delay: u64,
delay: u64,
batch_size: usize,
chunk_size: usize,
account_groups: usize,
@ -452,19 +523,16 @@ fn trader<T>(
) where
T: Client,
{
let mut stats = Stats::default();
// TODO Hard coded for now
let pair = TokenPair::AB;
let tokens = 1;
let price = 1000;
let mut account_group: usize = 0;
let mut txs = 0;
let mut total_txs = 0;
let mut now = Instant::now();
let start_time = now;
let mut total_elapsed = start_time.elapsed();
loop {
let now = Instant::now();
let trade_keys = generate_keypairs(batch_size as u64);
let mut trades = vec![];
@ -498,12 +566,20 @@ fn trader<T>(
trades.push((signer, trade.pubkey(), direction, src));
}
account_group = (account_group + 1) % account_groups as usize;
let duration = now.elapsed();
let rate = batch_size as f32 / duration_as_s(&duration);
stats.keygen_ns += duration_as_ns(&duration);
if rate > stats.keygen_peak_rate {
stats.keygen_peak_rate = rate;
}
trace!("sw {:?} keypairs {:.2} /s", batch_size, rate);
let (blockhash, _fee_calculator) = client
let blockhash = client
.get_recent_blockhash()
.expect("Failed to get blockhash");
trades.chunks(chunk_size).for_each(|chunk| {
let now = Instant::now();
let trades_txs: Vec<_> = chunk
.par_iter()
.map(|(signer, trade, direction, src)| {
@ -522,52 +598,55 @@ fn trader<T>(
)
})
.collect();
let duration = now.elapsed();
let n = trades_txs.len();
let rate = n as f32 / duration_as_s(&duration);
stats.sign_ns += duration_as_ns(&duration);
if rate > stats.sign_peak_rate {
stats.sign_peak_rate = rate;
}
trace!(" sw {:?} signed {:.2} /s ", n, rate);
solana_metrics::submit(
influxdb::Point::new("bench-exchange")
.add_tag("op", influxdb::Value::String("trades".to_string()))
.add_field("count", influxdb::Value::Integer(trades_txs.len() as i64))
.to_owned(),
);
{
txs += chunk_size as u64;
total_txs += chunk_size as u64;
total_elapsed = start_time.elapsed();
let duration = now.elapsed();
if duration_as_s(&duration) >= 1_f32 {
now = Instant::now();
let tps = txs as f32 / duration_as_s(&duration);
info!(
"Trader {:9.2} TPS, Transactions: {:6}, Total transactions: {} over {} s",
tps,
txs,
total_txs,
total_elapsed.as_secs(),
);
txs = 0;
}
datapoint_info!("bench-exchange-trades", ("count", trades_txs.len(), i64));
{
let mut shared_txs_wl = shared_txs
.write()
.expect("Failed to send tx to transfer threads");
shared_txs_wl.push_back(trades_txs);
}
let mut shared_txs_wl = shared_txs
.write()
.expect("Failed to send tx to transfer threads");
stats.total += chunk_size as u64;
shared_txs_wl.push_back(trades_txs);
}
if transfer_delay > 0 {
sleep(Duration::from_millis(transfer_delay));
if delay > 0 {
sleep(Duration::from_millis(delay));
}
});
if exit_signal.load(Ordering::Relaxed) {
info!(
"Trader sent {} at {:9.2} TPS",
total_txs,
total_txs as f32 / duration_as_s(&total_elapsed)
);
return;
}
// TODO chunk the trade infos and send them when the batch is sent
sender
.send(trade_infos)
.expect("Failed to send trades to swapper");
if exit_signal.load(Ordering::Relaxed) {
info!(
"{} Trades with batch size {} chunk size {}",
stats.total, batch_size, chunk_size
);
info!(
" Keygen avg {:.2}/s peak {:.2}/s",
(stats.total as f64 / stats.keygen_ns as f64) * 1_000_000_000_f64,
stats.keygen_peak_rate
);
info!(
" Signed avg {:.2}/s peak {:.2}/s",
(stats.total as f64 / stats.sign_ns as f64) * 1_000_000_000_f64,
stats.sign_peak_rate
);
break;
}
}
}
@ -663,8 +742,7 @@ pub fn fund_keys(client: &Client, source: &Keypair, dests: &[Arc<Keypair>], lamp
to_fund_txs.len(),
);
let (blockhash, _fee_calculator) =
client.get_recent_blockhash().expect("blockhash");
let blockhash = client.get_recent_blockhash().expect("blockhash");
to_fund_txs.par_iter_mut().for_each(|(k, tx)| {
tx.sign(&[*k], blockhash);
});
@ -715,11 +793,11 @@ pub fn create_token_accounts(client: &Client, signers: &[Arc<Keypair>], accounts
let mut to_create_txs: Vec<_> = chunk
.par_iter()
.map(|(signer, new)| {
let owner_pubkey = &signer.pubkey();
let owner_id = &signer.pubkey();
let space = mem::size_of::<ExchangeState>() as u64;
let create_ix =
system_instruction::create_account(owner_pubkey, new, 1, space, &id());
let request_ix = exchange_instruction::account_request(owner_pubkey, new);
system_instruction::create_account(owner_id, new, 1, space, &id());
let request_ix = exchange_instruction::account_request(owner_id, new);
(
signer,
Transaction::new_unsigned_instructions(vec![create_ix, request_ix]),
@ -739,7 +817,7 @@ pub fn create_token_accounts(client: &Client, signers: &[Arc<Keypair>], accounts
let mut retries = 0;
while !to_create_txs.is_empty() {
let (blockhash, _fee_calculator) = client
let blockhash = client
.get_recent_blockhash()
.expect("Failed to get blockhash");
to_create_txs.par_iter_mut().for_each(|(k, tx)| {
@ -790,13 +868,13 @@ pub fn create_token_accounts(client: &Client, signers: &[Arc<Keypair>], accounts
}
}
fn compute_and_report_stats(maxes: &Arc<RwLock<Vec<(String, SampleStats)>>>, total_txs_sent: u64) {
fn compute_and_report_stats(maxes: &Arc<RwLock<Vec<(SampleStats)>>>, total_txs_sent: u64) {
let mut max_txs = 0;
let mut max_elapsed = Duration::new(0, 0);
info!("| Max TPS | Total Transactions");
info!("+---------------+--------------------");
for (_sock, stats) in maxes.read().unwrap().iter() {
for stats in maxes.read().unwrap().iter() {
let maybe_flag = match stats.txs {
0 => "!!!!!",
_ => "",
@ -855,7 +933,7 @@ pub fn airdrop_lamports(client: &Client, drone_addr: &SocketAddr, id: &Keypair,
let mut tries = 0;
loop {
let (blockhash, _fee_calculator) = client
let blockhash = client
.get_recent_blockhash()
.expect("Failed to get blockhash");
match request_airdrop_transaction(&drone_addr, &id.pubkey(), amount_to_drop, blockhash) {
@ -889,17 +967,35 @@ pub fn airdrop_lamports(client: &Client, drone_addr: &SocketAddr, id: &Keypair,
}
}
pub fn get_clients(nodes: &[ContactInfo]) -> Vec<ThinClient> {
nodes
.iter()
.filter_map(|node| {
let cluster_entrypoint = node;
let cluster_addrs = cluster_entrypoint.client_facing_addr();
if ContactInfo::is_valid_address(&cluster_addrs.0)
&& ContactInfo::is_valid_address(&cluster_addrs.1)
{
let client = create_client(cluster_addrs, FULLNODE_PORT_RANGE);
Some(client)
} else {
None
}
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
use solana::gossip_service::{discover_cluster, get_clients};
use solana::fullnode::FullnodeConfig;
use solana::gossip_service::discover_nodes;
use solana::local_cluster::{ClusterConfig, LocalCluster};
use solana::validator::ValidatorConfig;
use solana_drone::drone::run_local_drone;
use solana_exchange_api::exchange_processor::process_instruction;
use solana_runtime::bank::Bank;
use solana_runtime::bank_client::BankClient;
use solana_sdk::genesis_block::create_genesis_block;
use solana_sdk::genesis_block::GenesisBlock;
use std::sync::mpsc::channel;
#[test]
@ -907,16 +1003,16 @@ mod tests {
solana_logger::setup();
const NUM_NODES: usize = 1;
let validator_config = ValidatorConfig::default();
let fullnode_config = FullnodeConfig::default();
let mut config = Config::default();
config.identity = Keypair::new();
config.threads = 1;
config.duration = Duration::from_secs(1);
config.fund_amount = 100_000;
config.threads = 1;
config.transfer_delay = 20; // 15
config.transfer_delay = 20;
config.batch_size = 100; // 1000;
config.chunk_size = 10; // 200;
config.chunk_size = 10; // 250;
config.account_groups = 1; // 10;
let Config {
fund_amount,
@ -929,8 +1025,8 @@ mod tests {
let cluster = LocalCluster::new(&ClusterConfig {
node_stakes: vec![100_000; NUM_NODES],
cluster_lamports: 100_000_000_000_000,
validator_config,
native_instruction_processors: [solana_exchange_program!()].to_vec(),
fullnode_config,
native_instruction_processors: [("solana_exchange_program".to_string(), id())].to_vec(),
..ClusterConfig::default()
});
@ -946,8 +1042,8 @@ mod tests {
let drone_addr = addr_receiver.recv_timeout(Duration::from_secs(2)).unwrap();
info!("Connecting to the cluster");
let (nodes, _) = discover_cluster(&cluster.entry_point_info.gossip, NUM_NODES)
.unwrap_or_else(|err| {
let nodes =
discover_nodes(&cluster.entry_point_info.gossip, NUM_NODES).unwrap_or_else(|err| {
error!("Failed to discover {} nodes: {:?}", NUM_NODES, err);
exit(1);
});
@ -976,16 +1072,16 @@ mod tests {
#[test]
fn test_exchange_bank_client() {
solana_logger::setup();
let (genesis_block, identity) = create_genesis_block(100_000_000_000_000);
let (genesis_block, identity) = GenesisBlock::new(100_000_000_000_000);
let mut bank = Bank::new(&genesis_block);
bank.add_instruction_processor(id(), process_instruction);
let clients = vec![BankClient::new(bank)];
let mut config = Config::default();
config.identity = identity;
config.threads = 1;
config.duration = Duration::from_secs(1);
config.fund_amount = 100_000;
config.threads = 1;
config.transfer_delay = 20; // 0;
config.batch_size = 100; // 1500;
config.chunk_size = 10; // 1500;

View File

@ -7,7 +7,7 @@ use std::process::exit;
use std::time::Duration;
pub struct Config {
pub entrypoint_addr: SocketAddr,
pub network_addr: SocketAddr,
pub drone_addr: SocketAddr,
pub identity: Keypair,
pub threads: usize,
@ -23,7 +23,7 @@ pub struct Config {
impl Default for Config {
fn default() -> Self {
Self {
entrypoint_addr: SocketAddr::from(([127, 0, 0, 1], 8001)),
network_addr: SocketAddr::from(([127, 0, 0, 1], 8001)),
drone_addr: SocketAddr::from(([127, 0, 0, 1], DRONE_PORT)),
identity: Keypair::new(),
num_nodes: 1,
@ -43,14 +43,14 @@ pub fn build_args<'a, 'b>() -> App<'a, 'b> {
.about(crate_description!())
.version(crate_version!())
.arg(
Arg::with_name("entrypoint")
Arg::with_name("network")
.short("n")
.long("entrypoint")
.long("network")
.value_name("HOST:PORT")
.takes_value(true)
.required(false)
.default_value("127.0.0.1:8001")
.help("Cluster entry point; defaults to 127.0.0.1:8001"),
.help("Network's gossip entry point; defaults to 127.0.0.1:8001"),
)
.arg(
Arg::with_name("drone")
@ -146,9 +146,9 @@ pub fn build_args<'a, 'b>() -> App<'a, 'b> {
pub fn extract_args<'a>(matches: &ArgMatches<'a>) -> Config {
let mut args = Config::default();
args.entrypoint_addr = solana_netutil::parse_host_port(matches.value_of("entrypoint").unwrap())
args.network_addr = solana_netutil::parse_host_port(matches.value_of("network").unwrap())
.unwrap_or_else(|e| {
eprintln!("failed to parse entrypoint address: {}", e);
eprintln!("failed to parse network address: {}", e);
exit(1)
});

View File

@ -2,13 +2,9 @@ pub mod bench;
mod cli;
pub mod order_book;
#[cfg(test)]
#[macro_use]
extern crate solana_exchange_program;
use crate::bench::{airdrop_lamports, do_bench_exchange, Config};
use crate::bench::{airdrop_lamports, do_bench_exchange, get_clients, Config};
use log::*;
use solana::gossip_service::{discover_cluster, get_clients};
use solana::gossip_service::discover_nodes;
use solana_sdk::signature::KeypairUtil;
fn main() {
@ -19,7 +15,7 @@ fn main() {
let cli_config = cli::extract_args(&matches);
let cli::Config {
entrypoint_addr,
network_addr,
drone_addr,
identity,
threads,
@ -34,10 +30,9 @@ fn main() {
} = cli_config;
info!("Connecting to the cluster");
let (nodes, _replicators) =
discover_cluster(&entrypoint_addr, num_nodes).unwrap_or_else(|_| {
panic!("Failed to discover nodes");
});
let nodes = discover_nodes(&network_addr, num_nodes).unwrap_or_else(|_| {
panic!("Failed to discover nodes");
});
let clients = get_clients(&nodes);

View File

@ -1 +0,0 @@
/target/

View File

@ -2,16 +2,16 @@
authors = ["Solana Maintainers <maintainers@solana.com>"]
edition = "2018"
name = "solana-bench-streamer"
version = "0.15.2"
version = "0.14.0"
repository = "https://github.com/solana-labs/solana"
license = "Apache-2.0"
homepage = "https://solana.com/"
[dependencies]
clap = "2.33.0"
solana = { path = "../core", version = "0.15.2" }
solana-logger = { path = "../logger", version = "0.15.2" }
solana-netutil = { path = "../netutil", version = "0.15.2" }
solana = { path = "../core", version = "0.14.0" }
solana-logger = { path = "../logger", version = "0.14.0" }
solana-netutil = { path = "../netutil", version = "0.14.0" }
[features]
cuda = ["solana/cuda"]

View File

@ -20,13 +20,13 @@ fn producer(addr: &SocketAddr, exit: Arc<AtomicBool>) -> JoinHandle<()> {
w.meta.size = PACKET_DATA_SIZE;
w.meta.set_addr(&addr);
}
let msgs = Arc::new(msgs);
let msgs_ = msgs.clone();
spawn(move || loop {
if exit.load(Ordering::Relaxed) {
return;
}
let mut num = 0;
for p in &msgs.packets {
for p in &msgs_.packets {
let a = p.meta.addr();
assert!(p.meta.size < BLOB_SIZE);
send.send_to(&p.data[..p.meta.size], &a).unwrap();

View File

@ -2,24 +2,23 @@
authors = ["Solana Maintainers <maintainers@solana.com>"]
edition = "2018"
name = "solana-bench-tps"
version = "0.15.2"
version = "0.14.0"
repository = "https://github.com/solana-labs/solana"
license = "Apache-2.0"
homepage = "https://solana.com/"
[dependencies]
clap = "2.33.0"
log = "0.4.6"
rayon = "1.0.3"
serde_json = "1.0.39"
solana = { path = "../core", version = "0.15.2" }
solana-client = { path = "../client", version = "0.15.2" }
solana-drone = { path = "../drone", version = "0.15.2" }
solana-logger = { path = "../logger", version = "0.15.2" }
solana-metrics = { path = "../metrics", version = "0.15.2" }
solana-netutil = { path = "../netutil", version = "0.15.2" }
solana-runtime = { path = "../runtime", version = "0.15.2" }
solana-sdk = { path = "../sdk", version = "0.15.2" }
solana = { path = "../core", version = "0.14.0" }
solana-client = { path = "../client", version = "0.14.0" }
solana-drone = { path = "../drone", version = "0.14.0" }
solana-logger = { path = "../logger", version = "0.14.0" }
solana-metrics = { path = "../metrics", version = "0.14.0" }
solana-netutil = { path = "../netutil", version = "0.14.0" }
solana-runtime = { path = "../runtime", version = "0.14.0" }
solana-sdk = { path = "../sdk", version = "0.14.0" }
[features]
cuda = ["solana/cuda"]

View File

@ -1,13 +1,10 @@
use solana_metrics;
use log::*;
use rayon::prelude::*;
use solana::gen_keys::GenKeys;
use solana_client::perf_utils::{sample_txs, SampleStats};
use solana_drone::drone::request_airdrop_transaction;
use solana_metrics::datapoint_info;
use solana_metrics::influxdb;
use solana_sdk::client::Client;
use solana_sdk::hash::Hash;
use solana_sdk::signature::{Keypair, KeypairUtil};
use solana_sdk::system_instruction;
use solana_sdk::system_transaction;
@ -25,6 +22,13 @@ use std::thread::Builder;
use std::time::Duration;
use std::time::Instant;
pub struct NodeStats {
/// Maximum TPS reported by this node
pub tps: f64,
/// Total transactions reported by this node
pub tx: u64,
}
pub const MAX_SPENDS_PER_TX: usize = 4;
pub const NUM_LAMPORTS_PER_ACCOUNT: u64 = 20;
@ -57,8 +61,7 @@ pub fn do_bench_tps<T>(
config: Config,
gen_keypairs: Vec<Keypair>,
keypair0_balance: u64,
) -> u64
where
) where
T: 'static + Client + Send + Sync,
{
let Config {
@ -95,7 +98,7 @@ where
Builder::new()
.name("solana-client-sample".to_string())
.spawn(move || {
sample_txs(&exit_signal, &maxes, sample_period, &client);
sample_tx_count(&exit_signal, &maxes, first_tx_count, sample_period, &client);
})
.unwrap()
})
@ -133,39 +136,28 @@ where
let start = Instant::now();
let mut reclaim_lamports_back_to_source_account = false;
let mut i = keypair0_balance;
let mut blockhash = Hash::default();
let mut blockhash_time = Instant::now();
while start.elapsed() < duration {
let balance = client.get_balance(&id.pubkey()).unwrap_or(0);
metrics_submit_lamport_balance(balance);
// ping-pong between source and destination accounts for each loop iteration
// this seems to be faster than trying to determine the balance of individual
// accounts
let len = tx_count as usize;
if let Ok((new_blockhash, _fee_calculator)) = client.get_new_blockhash(&blockhash) {
blockhash = new_blockhash;
} else {
if blockhash_time.elapsed().as_secs() > 30 {
panic!("Blockhash is not updating");
}
sleep(Duration::from_millis(100));
continue;
}
blockhash_time = Instant::now();
let balance = client.get_balance(&id.pubkey()).unwrap_or(0);
metrics_submit_lamport_balance(balance);
generate_txs(
&shared_txs,
&blockhash,
&keypairs[..len],
&keypairs[len..],
threads,
reclaim_lamports_back_to_source_account,
&client,
);
// In sustained mode overlap the transfers with generation
// this has higher average performance but lower peak performance
// in tested environments.
if !sustained {
while shared_tx_active_thread_count.load(Ordering::Relaxed) > 0 {
sleep(Duration::from_millis(1));
sleep(Duration::from_millis(100));
}
}
@ -202,27 +194,86 @@ where
&start.elapsed(),
total_tx_sent_count.load(Ordering::Relaxed),
);
let r_maxes = maxes.read().unwrap();
r_maxes.first().unwrap().1.txs
}
fn metrics_submit_lamport_balance(lamport_balance: u64) {
println!("Token balance: {}", lamport_balance);
datapoint_info!(
"bench-tps-lamport_balance",
("balance", lamport_balance, i64)
solana_metrics::submit(
influxdb::Point::new("bench-tps")
.add_tag("op", influxdb::Value::String("lamport_balance".to_string()))
.add_field("balance", influxdb::Value::Integer(lamport_balance as i64))
.to_owned(),
);
}
fn generate_txs(
fn sample_tx_count<T: Client>(
exit_signal: &Arc<AtomicBool>,
maxes: &Arc<RwLock<Vec<(String, NodeStats)>>>,
first_tx_count: u64,
sample_period: u64,
client: &Arc<T>,
) {
let mut now = Instant::now();
let mut initial_tx_count = client.get_transaction_count().expect("transaction count");
let mut max_tps = 0.0;
let mut total;
let log_prefix = format!("{:21}:", client.transactions_addr());
loop {
let mut tx_count = client.get_transaction_count().expect("transaction count");
if tx_count < initial_tx_count {
println!(
"expected tx_count({}) >= initial_tx_count({})",
tx_count, initial_tx_count
);
tx_count = initial_tx_count;
}
let duration = now.elapsed();
now = Instant::now();
let sample = tx_count - initial_tx_count;
initial_tx_count = tx_count;
let ns = duration.as_secs() * 1_000_000_000 + u64::from(duration.subsec_nanos());
let tps = (sample * 1_000_000_000) as f64 / ns as f64;
if tps > max_tps {
max_tps = tps;
}
if tx_count > first_tx_count {
total = tx_count - first_tx_count;
} else {
total = 0;
}
println!(
"{} {:9.2} TPS, Transactions: {:6}, Total transactions: {}",
log_prefix, tps, sample, total
);
sleep(Duration::new(sample_period, 0));
if exit_signal.load(Ordering::Relaxed) {
println!("{} Exiting validator thread", log_prefix);
let stats = NodeStats {
tps: max_tps,
tx: total,
};
maxes
.write()
.unwrap()
.push((client.transactions_addr(), stats));
break;
}
}
}
fn generate_txs<T: Client>(
shared_txs: &SharedTransactions,
blockhash: &Hash,
source: &[Keypair],
dest: &[Keypair],
threads: usize,
reclaim: bool,
client: &Arc<T>,
) {
let blockhash = client.get_recent_blockhash().unwrap();
let tx_count = source.len();
println!("Signing transactions... {} (reclaim={})", tx_count, reclaim);
let signing_start = Instant::now();
@ -236,7 +287,7 @@ fn generate_txs(
.par_iter()
.map(|(id, keypair)| {
(
system_transaction::create_user_account(id, &keypair.pubkey(), 1, *blockhash),
system_transaction::create_user_account(id, &keypair.pubkey(), 1, blockhash, 0),
timestamp(),
)
})
@ -253,9 +304,14 @@ fn generate_txs(
duration_as_ms(&duration),
blockhash,
);
datapoint_info!(
"bench-tps-generate_txs",
("duration", duration_as_ms(&duration), i64)
solana_metrics::submit(
influxdb::Point::new("bench-tps")
.add_tag("op", influxdb::Value::String("generate_txs".to_string()))
.add_field(
"duration",
influxdb::Value::Integer(duration_as_ms(&duration) as i64),
)
.to_owned(),
);
let sz = transactions.len() / threads;
@ -282,7 +338,7 @@ fn do_tx_transfers<T: Client>(
}
let txs;
{
let mut shared_txs_wl = shared_txs.write().expect("write lock in do_tx_transfers");
let mut shared_txs_wl = shared_txs.write().unwrap();
txs = shared_txs_wl.pop_front();
}
if let Some(txs0) = txs {
@ -299,9 +355,7 @@ fn do_tx_transfers<T: Client>(
if now > tx.1 && now - tx.1 > 1000 * 30 {
continue;
}
client
.async_send_transaction(tx.0)
.expect("async_send_transaction in do_tx_transfers");
client.async_send_transaction(tx.0).unwrap();
}
shared_tx_thread_count.fetch_add(-1, Ordering::Relaxed);
total_tx_sent_count.fetch_add(tx_len, Ordering::Relaxed);
@ -310,10 +364,15 @@ fn do_tx_transfers<T: Client>(
duration_as_ms(&transfer_start.elapsed()),
tx_len as f32 / duration_as_s(&transfer_start.elapsed()),
);
datapoint_info!(
"bench-tps-do_tx_transfers",
("duration", duration_as_ms(&transfer_start.elapsed()), i64),
("count", tx_len, i64)
solana_metrics::submit(
influxdb::Point::new("bench-tps")
.add_tag("op", influxdb::Value::String("do_tx_transfers".to_string()))
.add_field(
"duration",
influxdb::Value::Integer(duration_as_ms(&transfer_start.elapsed()) as i64),
)
.add_field("count", influxdb::Value::Integer(tx_len as i64))
.to_owned(),
);
}
if exit_signal.load(Ordering::Relaxed) {
@ -406,7 +465,7 @@ pub fn fund_keys<T: Client>(client: &T, source: &Keypair, dests: &[Keypair], lam
to_fund_txs.len(),
);
let (blockhash, _fee_calculator) = client.get_recent_blockhash().unwrap();
let blockhash = client.get_recent_blockhash().unwrap();
// re-sign retained to_fund_txes with updated blockhash
to_fund_txs.par_iter_mut().for_each(|(k, tx)| {
@ -456,7 +515,7 @@ pub fn airdrop_lamports<T: Client>(
id.pubkey(),
);
let (blockhash, _fee_calculator) = client.get_recent_blockhash().unwrap();
let blockhash = client.get_recent_blockhash().unwrap();
match request_airdrop_transaction(&drone_addr, &id.pubkey(), airdrop_amount, blockhash) {
Ok(transaction) => {
let signature = client.async_send_transaction(transaction).unwrap();
@ -497,7 +556,7 @@ pub fn airdrop_lamports<T: Client>(
}
fn compute_and_report_stats(
maxes: &Arc<RwLock<Vec<(String, SampleStats)>>>,
maxes: &Arc<RwLock<Vec<(String, NodeStats)>>>,
sample_period: u64,
tx_send_elapsed: &Duration,
total_tx_send_count: usize,
@ -511,14 +570,14 @@ fn compute_and_report_stats(
println!("---------------------+---------------+--------------------");
for (sock, stats) in maxes.read().unwrap().iter() {
let maybe_flag = match stats.txs {
let maybe_flag = match stats.tx {
0 => "!!!!!",
_ => "",
};
println!(
"{:20} | {:13.2} | {} {}",
sock, stats.tps, stats.txs, maybe_flag
sock, stats.tps, stats.tx, maybe_flag
);
if stats.tps == 0.0 {
@ -529,33 +588,27 @@ fn compute_and_report_stats(
if stats.tps > max_of_maxes {
max_of_maxes = stats.tps;
}
if stats.txs > max_tx_count {
max_tx_count = stats.txs;
if stats.tx > max_tx_count {
max_tx_count = stats.tx;
}
}
if total_maxes > 0.0 {
let num_nodes_with_tps = maxes.read().unwrap().len() - nodes_with_zero_tps;
let average_max = total_maxes / num_nodes_with_tps as f32;
let average_max = total_maxes / num_nodes_with_tps as f64;
println!(
"\nAverage max TPS: {:.2}, {} nodes had 0 TPS",
average_max, nodes_with_zero_tps
);
}
let total_tx_send_count = total_tx_send_count as u64;
let drop_rate = if total_tx_send_count > max_tx_count {
(total_tx_send_count - max_tx_count) as f64 / total_tx_send_count as f64
} else {
0.0
};
println!(
"\nHighest TPS: {:.2} sampling period {}s max transactions: {} clients: {} drop rate: {:.2}",
max_of_maxes,
sample_period,
max_tx_count,
maxes.read().unwrap().len(),
drop_rate,
(total_tx_send_count as u64 - max_tx_count) as f64 / total_tx_send_count as f64,
);
println!(
"\tAverage TPS: {}",
@ -570,67 +623,31 @@ fn should_switch_directions(num_lamports_per_account: u64, i: u64) -> bool {
i % (num_lamports_per_account / 4) == 0 && (i >= (3 * num_lamports_per_account) / 4)
}
pub fn generate_keypairs(seed_keypair: &Keypair, count: usize) -> Vec<Keypair> {
pub fn generate_keypairs(id: &Keypair, tx_count: usize) -> Vec<Keypair> {
let mut seed = [0u8; 32];
seed.copy_from_slice(&seed_keypair.to_bytes()[..32]);
seed.copy_from_slice(&id.to_bytes()[..32]);
let mut rnd = GenKeys::new(seed);
let mut total_keys = 0;
let mut target = count;
while target > 1 {
let mut target = tx_count * 2;
while target > 0 {
total_keys += target;
// Use the upper bound for this division otherwise it may not generate enough keys
target = (target + MAX_SPENDS_PER_TX - 1) / MAX_SPENDS_PER_TX;
target /= MAX_SPENDS_PER_TX;
}
rnd.gen_n_keypairs(total_keys as u64)
}
pub fn generate_and_fund_keypairs<T: Client>(
client: &T,
drone_addr: Option<SocketAddr>,
funding_pubkey: &Keypair,
tx_count: usize,
lamports_per_account: u64,
) -> (Vec<Keypair>, u64) {
info!("Creating {} keypairs...", tx_count * 2);
let mut keypairs = generate_keypairs(funding_pubkey, tx_count * 2);
info!("Get lamports...");
// Sample the first keypair, see if it has lamports, if so then resume.
// This logic is to prevent lamport loss on repeated solana-bench-tps executions
let last_keypair_balance = client
.get_balance(&keypairs[tx_count * 2 - 1].pubkey())
.unwrap_or(0);
if lamports_per_account > last_keypair_balance {
let extra = lamports_per_account - last_keypair_balance;
let total = extra * (keypairs.len() as u64);
if client.get_balance(&funding_pubkey.pubkey()).unwrap_or(0) < total {
airdrop_lamports(client, &drone_addr.unwrap(), funding_pubkey, total);
}
info!("adding more lamports {}", extra);
fund_keys(client, funding_pubkey, &keypairs, extra);
}
// 'generate_keypairs' generates extra keys to be able to have size-aligned funding batches for fund_keys.
keypairs.truncate(2 * tx_count);
(keypairs, last_keypair_balance)
}
#[cfg(test)]
mod tests {
use super::*;
use solana::cluster_info::FULLNODE_PORT_RANGE;
use solana::fullnode::FullnodeConfig;
use solana::local_cluster::{ClusterConfig, LocalCluster};
use solana::validator::ValidatorConfig;
use solana_client::thin_client::create_client;
use solana_drone::drone::run_local_drone;
use solana_runtime::bank::Bank;
use solana_runtime::bank_client::BankClient;
use solana_sdk::client::SyncClient;
use solana_sdk::genesis_block::create_genesis_block;
use solana_sdk::genesis_block::GenesisBlock;
use std::sync::mpsc::channel;
#[test]
@ -649,14 +666,14 @@ mod tests {
}
#[test]
fn test_bench_tps_local_cluster() {
solana_logger::setup();
let validator_config = ValidatorConfig::default();
#[ignore]
fn test_bench_tps() {
let fullnode_config = FullnodeConfig::default();
const NUM_NODES: usize = 1;
let cluster = LocalCluster::new(&ClusterConfig {
node_stakes: vec![999_990; NUM_NODES],
cluster_lamports: 2_000_000,
validator_config,
fullnode_config,
..ClusterConfig::default()
});
@ -671,27 +688,18 @@ mod tests {
config.tx_count = 100;
config.duration = Duration::from_secs(5);
let keypairs = generate_keypairs(&config.id, config.tx_count);
let client = create_client(
(cluster.entry_point_info.rpc, cluster.entry_point_info.tpu),
(cluster.entry_point_info.gossip, drone_addr),
FULLNODE_PORT_RANGE,
);
let lamports_per_account = 100;
let (keypairs, _keypair_balance) = generate_and_fund_keypairs(
&client,
Some(drone_addr),
&config.id,
config.tx_count,
lamports_per_account,
);
let total = do_bench_tps(vec![client], config, keypairs, 0);
assert!(total > 100);
do_bench_tps(vec![client], config, keypairs, 0);
}
#[test]
fn test_bench_tps_bank_client() {
let (genesis_block, id) = create_genesis_block(10_000);
let (genesis_block, id) = GenesisBlock::new(10_000);
let bank = Bank::new(&genesis_block);
let clients = vec![BankClient::new(bank)];
@ -700,26 +708,9 @@ mod tests {
config.tx_count = 10;
config.duration = Duration::from_secs(5);
let (keypairs, _keypair_balance) =
generate_and_fund_keypairs(&clients[0], None, &config.id, config.tx_count, 20);
let keypairs = generate_keypairs(&config.id, config.tx_count);
fund_keys(&clients[0], &config.id, &keypairs, 20);
do_bench_tps(clients, config, keypairs, 0);
}
#[test]
fn test_bench_tps_fund_keys() {
let (genesis_block, id) = create_genesis_block(10_000);
let bank = Bank::new(&genesis_block);
let client = BankClient::new(bank);
let tx_count = 10;
let lamports = 20;
let (keypairs, _keypair_balance) =
generate_and_fund_keypairs(&client, None, &id, tx_count, lamports);
for kp in &keypairs {
// TODO: This should be >= lamports, but fails at the moment
assert_ne!(client.get_balance(&kp.pubkey()).unwrap(), 0);
}
}
}

View File

@ -8,7 +8,7 @@ use solana_sdk::signature::{read_keypair, Keypair, KeypairUtil};
/// Holds the configuration for a single run of the benchmark
pub struct Config {
pub entrypoint_addr: SocketAddr,
pub network_addr: SocketAddr,
pub drone_addr: SocketAddr,
pub id: Keypair,
pub threads: usize,
@ -22,7 +22,7 @@ pub struct Config {
impl Default for Config {
fn default() -> Config {
Config {
entrypoint_addr: SocketAddr::from(([127, 0, 0, 1], 8001)),
network_addr: SocketAddr::from(([127, 0, 0, 1], 8001)),
drone_addr: SocketAddr::from(([127, 0, 0, 1], DRONE_PORT)),
id: Keypair::new(),
threads: 4,
@ -40,12 +40,12 @@ pub fn build_args<'a, 'b>() -> App<'a, 'b> {
App::new(crate_name!()).about(crate_description!())
.version(crate_version!())
.arg(
Arg::with_name("entrypoint")
Arg::with_name("network")
.short("n")
.long("entrypoint")
.long("network")
.value_name("HOST:PORT")
.takes_value(true)
.help("Rendezvous with the cluster at this entry point; defaults to 127.0.0.1:8001"),
.help("Rendezvous with the network at this gossip entry point; defaults to 127.0.0.1:8001"),
)
.arg(
Arg::with_name("drone")
@ -53,7 +53,7 @@ pub fn build_args<'a, 'b>() -> App<'a, 'b> {
.long("drone")
.value_name("HOST:PORT")
.takes_value(true)
.help("Location of the drone; defaults to entrypoint:DRONE_PORT"),
.help("Location of the drone; defaults to network:DRONE_PORT"),
)
.arg(
Arg::with_name("identity")
@ -116,9 +116,9 @@ pub fn build_args<'a, 'b>() -> App<'a, 'b> {
pub fn extract_args<'a>(matches: &ArgMatches<'a>) -> Config {
let mut args = Config::default();
if let Some(addr) = matches.value_of("entrypoint") {
args.entrypoint_addr = solana_netutil::parse_host_port(addr).unwrap_or_else(|e| {
eprintln!("failed to parse entrypoint address: {}", e);
if let Some(addr) = matches.value_of("network") {
args.network_addr = solana_netutil::parse_host_port(addr).unwrap_or_else(|e| {
eprintln!("failed to parse network address: {}", e);
exit(1)
});
}

View File

@ -1,8 +1,15 @@
mod bench;
mod cli;
use crate::bench::{do_bench_tps, generate_and_fund_keypairs, Config, NUM_LAMPORTS_PER_ACCOUNT};
use solana::gossip_service::{discover_cluster, get_clients};
use crate::bench::{
airdrop_lamports, do_bench_tps, fund_keys, generate_keypairs, Config, NUM_LAMPORTS_PER_ACCOUNT,
};
use solana::cluster_info::FULLNODE_PORT_RANGE;
use solana::contact_info::ContactInfo;
use solana::gossip_service::discover_nodes;
use solana_client::thin_client::create_client;
use solana_sdk::client::SyncClient;
use solana_sdk::signature::KeypairUtil;
use std::process::exit;
fn main() {
@ -13,7 +20,7 @@ fn main() {
let cli_config = cli::extract_args(&matches);
let cli::Config {
entrypoint_addr,
network_addr,
drone_addr,
id,
threads,
@ -25,11 +32,10 @@ fn main() {
} = cli_config;
println!("Connecting to the cluster");
let (nodes, _replicators) =
discover_cluster(&entrypoint_addr, num_nodes).unwrap_or_else(|err| {
eprintln!("Failed to discover {} nodes: {:?}", num_nodes, err);
exit(1);
});
let nodes = discover_nodes(&network_addr, num_nodes).unwrap_or_else(|err| {
eprintln!("Failed to discover {} nodes: {:?}", num_nodes, err);
exit(1);
});
if nodes.len() < num_nodes {
eprintln!(
"Error: Insufficient nodes discovered. Expecting {} or more",
@ -37,16 +43,40 @@ fn main() {
);
exit(1);
}
let clients: Vec<_> = nodes
.iter()
.filter_map(|node| {
let cluster_entrypoint = node.clone();
let cluster_addrs = cluster_entrypoint.client_facing_addr();
if ContactInfo::is_valid_address(&cluster_addrs.0)
&& ContactInfo::is_valid_address(&cluster_addrs.1)
{
let client = create_client(cluster_addrs, FULLNODE_PORT_RANGE);
Some(client)
} else {
None
}
})
.collect();
let clients = get_clients(&nodes);
println!("Creating {} keypairs...", tx_count * 2);
let keypairs = generate_keypairs(&id, tx_count);
let (keypairs, keypair_balance) = generate_and_fund_keypairs(
&clients[0],
Some(drone_addr),
&id,
tx_count,
NUM_LAMPORTS_PER_ACCOUNT,
);
println!("Get lamports...");
// Sample the first keypair, see if it has lamports, if so then resume.
// This logic is to prevent lamport loss on repeated solana-bench-tps executions
let keypair0_balance = clients[0]
.get_balance(&keypairs.last().unwrap().pubkey())
.unwrap_or(0);
if NUM_LAMPORTS_PER_ACCOUNT > keypair0_balance {
let extra = NUM_LAMPORTS_PER_ACCOUNT - keypair0_balance;
let total = extra * (keypairs.len() as u64);
airdrop_lamports(&clients[0], &drone_addr, &id, total);
println!("adding more lamports {}", extra);
fund_keys(&clients[0], &id, &keypairs, extra);
}
let config = Config {
id,
@ -57,5 +87,5 @@ fn main() {
sustained,
};
do_bench_tps(clients, config, keypairs, keypair_balance);
do_bench_tps(clients, config, keypairs, keypair0_balance);
}

View File

@ -1,19 +0,0 @@
+------------------------------------------------------------------+
| |
| +-----------------+ Neighborhood 0 +-----------------+ |
| | +--------------------->+ | |
| | Validator 1 | | Validator 2 | |
| | +<---------------------+ | |
| +--------+-+------+ +------+-+--------+ |
| | | | | |
| | +-----------------------------+ | | |
| | +------------------------+------+ | |
| | | | | |
+------------------------------------------------------------------+
| | | |
v v v v
+---------+------+---+ +-+--------+---------+
| | | |
| Neighborhood 1 | | Neighborhood 2 |
| | | |
+--------------------+ +--------------------+

View File

@ -1,15 +0,0 @@
+--------------+
| |
+------------+ Leader +------------+
| | | |
| +--------------+ |
v v
+------------+----------------------------------------+------------+
| |
| +-----------------+ Neighborhood 0 +-----------------+ |
| | +--------------------->+ | |
| | Validator 1 | | Validator 2 | |
| | +<---------------------+ | |
| +-----------------+ +-----------------+ |
| |
+------------------------------------------------------------------+

View File

@ -1,18 +1,28 @@
+--------------------+
| |
+--------+ Neighborhood 0 +----------+
| | | |
| +--------------------+ |
v v
+---------+----------+ +----------+---------+
| | | |
| Neighborhood 1 | | Neighborhood 2 |
| | | |
+---+-----+----------+ +----------+-----+---+
| | | |
v v v v
+------------------+-+ +-+------------------+ +------------------+-+ +-+------------------+
| | | | | | | |
| Neighborhood 3 | | Neighborhood 4 | | Neighborhood 5 | | Neighborhood 6 |
| | | | | | | |
+--------------------+ +--------------------+ +--------------------+ +--------------------+
+--------------+
| |
+------------+ Leader +------------+
| | | |
| +--------------+ |
v v
+--------+--------+ +--------+--------+
| +--------------------->+ |
+-----------------+ Validator 1 | | Validator 2 +-------------+
| | +<---------------------+ | |
| +------+-+-+------+ +---+-+-+---------+ |
| | | | | | | |
| | | | | | | |
| +---------------------------------------------+ | | |
| | | | | | | |
| | | | | +----------------------+ | |
| | | | | | | |
| | | | +--------------------------------------------+ |
| | | | | | | |
| | | +----------------------+ | | |
| | | | | | | |
v v v v v v v v
+--------------------+ +--------------------+ +--------------------+ +--------------------+
| | | | | | | |
| Neighborhood 1 | | Neighborhood 2 | | Neighborhood 3 | | Neighborhood 4 |
| | | | | | | |
+--------------------+ +--------------------+ +--------------------+ +--------------------+

View File

@ -1,5 +1,5 @@
.--------------------------------------.
| Validator |
| Fullnode |
| |
.--------. | .-------------------. |
| |---->| | |

View File

@ -1,60 +0,0 @@
.------------.
| Upstream |
| Validators |
`----+-------`
|
|
.-----------------------------------.
| Validator | |
| v |
| .-----------. .------------. |
.--------. | | Fetch | | Repair | |
| Client +---->| Stage | | Stage | |
`--------` | `---+-------` `----+-------` |
| | | |
| v v |
| .-----------. .------------. |
| | TPU |<-->| Blockstore | |
| | | | | |
| `-----------` `----+-------` |
| | |
| v |
| .------------. |
| | Multicast | |
| | Stage | |
| `----+-------` |
| | |
`-----------------------------------`
|
v
.------------.
| Downstream |
| Validators |
`------------`
.------------.
| PoH |
| Service |
`-------+----`
^ |
| |
.-----------------------------------.
| TPU | | |
| | v |
.-------. | .-----------. .---+--------. | .------------.
| Fetch +---->| SigVerify +--->| Banking |<--->| Blockstore |
| Stage | | | Stage | | Stage | | | |
`-------` | `-----------` `-----+------` | `------------`
| | |
| | |
`-----------------------------------`
|
v
.------------.
| Banktree |
| |
`------------`

View File

@ -3,4 +3,16 @@ set -e
cd "$(dirname "$0")"
cargo_install_unless() {
declare crate=$1
shift
"$@" > /dev/null 2>&1 || \
cargo install "$crate"
}
export PATH=$CARGO_HOME/bin:$PATH
cargo_install_unless mdbook mdbook --help
cargo_install_unless svgbob_cli svgbob --help
make -j"$(nproc)"

View File

@ -1,8 +1,7 @@
BOB_SRCS=$(wildcard art/*.bob)
MSC_SRCS=$(wildcard art/*.msc)
MD_SRCS=$(wildcard src/*.md)
SVG_IMGS=$(BOB_SRCS:art/%.bob=src/img/%.svg) $(MSC_SRCS:art/%.msc=src/img/%.svg)
SVG_IMGS=$(BOB_SRCS:art/%.bob=src/img/%.svg)
all: html/index.html
@ -18,10 +17,6 @@ src/img/%.svg: art/%.bob
@mkdir -p $(@D)
svgbob < $< > $@
src/img/%.svg: art/%.msc
@mkdir -p $(@D)
mscgen -T svg -i $< -o $@
src/%.md: %.md
@mkdir -p $(@D)
@cp $< $@

View File

@ -19,10 +19,10 @@
- [Data Plane Fanout](data-plane-fanout.md)
- [Ledger Replication](ledger-replication.md)
- [Secure Vote Signing](vote-signing.md)
- [Stake Delegation and Rewards](stake-delegation-and-rewards.md)
- [Staking Delegation and Rewards](stake-delegation-and-rewards.md)
- [Performance Metrics](performance-metrics.md)
- [Anatomy of a Validator](validator.md)
- [Anatomy of a Fullnode](fullnode.md)
- [TPU](tpu.md)
- [TVU](tvu.md)
- [Blocktree](blocktree.md)
@ -39,6 +39,9 @@
- [Ledger Replication](ledger-replication-to-implement.md)
- [Secure Vote Signing](vote-signing-to-implement.md)
- [Staking Rewards](staking-rewards.md)
- [Passive Stake Delegation and Rewards](passive-stake-delegation-and-rewards.md)
- [Reliable Vote Transmission](reliable-vote-transmission.md)
- [Persistent Account Storage](persistent-account-storage.md)
- [Cluster Economics](ed_overview.md)
- [Validation-client Economics](ed_validation_client_economics.md)
- [State-validation Protocol-based Rewards](ed_vce_state_validation_protocol_based_rewards.md)
@ -53,17 +56,13 @@
- [Economic Design MVP](ed_mvp.md)
- [References](ed_references.md)
- [Cluster Test Framework](cluster-test-framework.md)
- [Testing Programs](testing-programs.md)
- [Credit-only Accounts](credit-only-credit-debit-accounts.md)
- [Cluster Software Installation and Updates](installer.md)
- [Deterministic Transaction Fees](transaction-fees.md)
- [Validator](validator-proposal.md)
- [Implemented Design Proposals](implemented-proposals.md)
- [Fork Selection](fork-selection.md)
- [Leader-to-Leader Transition](leader-leader-transition.md)
- [Leader-to-Validator Transition](leader-validator-transition.md)
- [Testnet Participation](testnet-participation.md)
- [Testing Programs](testing-programs.md)
- [Reliable Vote Transmission](reliable-vote-transmission.md)
- [Persistent Account Storage](persistent-account-storage.md)
- [Cluster Software Installation and Updates](installer.md)
- [Passive Stake Delegation and Rewards](passive-stake-delegation-and-rewards.md)

View File

@ -12,7 +12,7 @@ To run a blockstreamer, include the argument `no-signer` and (optional)
`blockstream` socket location:
```bash
$ ./multinode-demo/validator-x.sh --no-signer --blockstream <SOCKET>
$ ./multinode-demo/fullnode-x.sh --no-signer --blockstream <SOCKET>
```
The stream will output a series of JSON objects:

View File

@ -20,7 +20,7 @@ least amount of internal plumbing exposed to the test.
Tests are provided an entry point, which is a `contact_info::ContactInfo`
structure, and a keypair that has already been funded.
Each node in the cluster is configured with a `fullnode::ValidatorConfig` at boot
Each node in the cluster is configured with a `fullnode::FullnodeConfig` at boot
time. At boot time this configuration specifies any extra cluster configuration
required for the test. The cluster should boot with the configuration when it
is run in-process or in a data center.
@ -61,18 +61,18 @@ let cluster_nodes = discover_nodes(&entry_point_info, num_nodes);
To enable specific scenarios, the cluster needs to be booted with special
configurations. These configurations can be captured in
`fullnode::ValidatorConfig`.
`fullnode::FullnodeConfig`.
For example:
```rust,ignore
let mut validator_config = ValidatorConfig::default();
validator_config.rpc_config.enable_fullnode_exit = true;
let mut fullnode_config = FullnodeConfig::default();
fullnode_config.rpc_config.enable_fullnode_exit = true;
let local = LocalCluster::new_with_config(
num_nodes,
10_000,
100,
&validator_config
&fullnode_config
);
```
@ -86,9 +86,9 @@ advertised gossip nodes.
Configure the RPC service:
```rust,ignore
let mut validator_config = ValidatorConfig::default();
validator_config.rpc_config.enable_rpc_gossip_push = true;
validator_config.rpc_config.enable_rpc_gossip_refresh_active_set = true;
let mut fullnode_config = FullnodeConfig::default();
fullnode_config.rpc_config.enable_rpc_gossip_push = true;
fullnode_config.rpc_config.enable_rpc_gossip_refresh_active_set = true;
```
Wire the RPCs and write a new test:

View File

@ -28,7 +28,7 @@ its copy.
## Joining a Cluster
Validators and replicators enter the cluster via registration messages sent to
Fullnodes and replicators enter the cluster via registration messages sent to
its *control plane*. The control plane is implemented using a *gossip*
protocol, meaning that a node may register with any existing node, and expect
its registration to propagate to all nodes in the cluster. The time it takes

View File

@ -5,15 +5,16 @@ broadcast transaction blobs to all nodes in a very quick and efficient manner.
In order to establish the fanout, the cluster divides itself into small
collections of nodes, called *neighborhoods*. Each node is responsible for
sharing any data it receives with the other nodes in its neighborhood, as well
as propagating the data on to a small set of nodes in other neighborhoods.
This way each node only has to communicate with a small number of nodes.
as propagating the data on to a small set of nodes in other neighborhoods.
During its slot, the leader node distributes blobs between the validator nodes
in the first neighborhood (layer 0). Each validator shares its data within its
neighborhood, but also retransmits the blobs to one node in some neighborhoods
in the next layer (layer 1). The layer-1 nodes each share their data with their
neighborhood peers, and retransmit to nodes in the next layer, etc, until all
nodes in the cluster have received all the blobs.
in one neighborhood (layer 1). Each validator shares its data within its
neighborhood, but also retransmits the blobs to one node in each of multiple
neighborhoods in the next layer (layer 2). The layer-2 nodes each share their
data with their neighborhood peers, and retransmit to nodes in the next layer,
etc, until all nodes in the cluster have received all the blobs.
<img alt="Two layer cluster" src="img/data-plane.svg" class="center"/>
## Neighborhood Assignment - Weighted Selection
@ -22,50 +23,48 @@ 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.
make up layer 1. These will automatically be the highest stake holders, allowing
the heaviest votes to come back to the leader first. Layer-1 and lower-layer
nodes use the same logic to find their neighbors and lower layer peers.
## Layer and Neighborhood Structure
The current leader makes its initial broadcasts to at most `DATA_PLANE_FANOUT`
nodes. If this layer 0 is smaller than the number of nodes in the cluster, then
nodes. If this layer 1 is smaller than the number of nodes in the cluster, then
the data plane fanout mechanism adds layers below. Subsequent layers follow
these constraints to determine layer-capacity: Each neighborhood contains
`DATA_PLANE_FANOUT` nodes. Layer-0 starts with 1 neighborhood with fanout nodes.
The number of nodes in each additional layer grows by a factor of fanout.
`NEIGHBORHOOD_SIZE` nodes and each layer may have up to `DATA_PLANE_FANOUT/2`
neighborhoods.
As mentioned above, each node in a layer only has to broadcast its blobs to its
neighbors and to exactly 1 node in some next-layer neighborhoods,
instead of to every TVU peer in the cluster. A good way to think about this is,
layer-0 starts with 1 neighborhood with fanout nodes, layer-1 adds "fanout"
neighborhoods, each with fanout nodes and layer-2 will have
`fanout * number of nodes in layer-1` and so on.
This way each node only has to communicate with a maximum of `2 * DATA_PLANE_FANOUT - 1` nodes.
The following diagram shows how the Leader sends blobs with a Fanout of 2 to
Neighborhood 0 in Layer 0 and how the nodes in Neighborhood 0 share their data
with each other.
<img alt="Leader sends blobs to Neighborhood 0 in Layer 0" src="img/data-plane-seeding.svg" class="center"/>
The following diagram shows how Neighborhood 0 fans out to Neighborhoods 1 and 2.
<img alt="Neighborhood 0 Fanout to Neighborhood 1 and 2" src="img/data-plane-fanout.svg" class="center"/>
Finally, the following diagram shows a two layer cluster with a Fanout of 2.
<img alt="Two layer cluster with a Fanout of 2" src="img/data-plane.svg" class="center"/>
neighbors and to exactly 1 node in each next-layer neighborhood, instead of to
every TVU peer in the cluster. In the default mode, each layer contains
`DATA_PLANE_FANOUT/2` neighborhoods. The retransmit mechanism also supports a
second, `grow`, mode of operation that squares the number of neighborhoods
allowed each layer. This dramatically reduces the number of layers needed to
support a large cluster, but can also have a negative impact on the network
pressure on each node in the lower layers. A good way to think of the default
mode (when `grow` is disabled) is to imagine it as chain of layers, where the
leader sends blobs to layer-1 and then layer-1 to layer-2 and so on, the `layer
capacities` remain constant, so all layers past layer-2 will have the same
number of nodes until the whole cluster is covered. When `grow` is enabled, this
becomes a traditional fanout where layer-3 will have the square of the number of
nodes in layer-2 and so on.
#### Configuration Values
`DATA_PLANE_FANOUT` - Determines the size of layer 0. Subsequent
layers grow by a factor of `DATA_PLANE_FANOUT`.
The number of nodes in a neighborhood is equal to the fanout value.
`DATA_PLANE_FANOUT` - Determines the size of layer 1. Subsequent
layers have `DATA_PLANE_FANOUT/2` neighborhoods when `grow` is inactive.
`NEIGHBORHOOD_SIZE` - The number of nodes allowed in a neighborhood.
Neighborhoods will fill to capacity before new ones are added, i.e if a
neighborhood isn't full, it _must_ be the last one.
`GROW_LAYER_CAPACITY` - Whether or not retransmit should be behave like a
_traditional fanout_, i.e if each additional layer should have growing
capacities. When this mode is disabled (default), all layers after layer 1 have
the same capacity, keeping the network pressure on all nodes equal.
Currently, configuration is set when the cluster is launched. In the future,
these parameters may be hosted on-chain, allowing modification on the fly as the
cluster sizes change.
@ -73,10 +72,13 @@ cluster sizes change.
## Neighborhoods
The following diagram shows how two neighborhoods in different layers interact.
To cripple a neighborhood, enough nodes (erasure codes +1) from the neighborhood
above need to fail. Since each neighborhood receives blobs from multiple nodes
in a neighborhood in the upper layer, we'd need a big network failure in the upper
layers to end up with incomplete data.
What this diagram doesn't capture is that each neighbor actually receives
blobs from one validator per neighborhood above it. This means that, to
cripple a neighborhood, enough nodes (erasure codes +1 per neighborhood) from
the layer above need to fail. Since multiple neighborhoods exist in the upper
layer and a node will receive blobs from a node in each of those neighborhoods,
we'd need a big network failure in the upper layers to end up with incomplete
data.
<img alt="Inner workings of a neighborhood"
src="img/data-plane-neighborhood.svg" class="center"/>

View File

@ -1,10 +1,10 @@
# Anatomy of a Validator
# Anatomy of a Fullnode
<img alt="Validator block diagrams" src="img/validator.svg" class="center"/>
<img alt="Fullnode block diagrams" src="img/fullnode.svg" class="center"/>
## Pipelining
The validators make extensive use of an optimization common in CPU design,
The fullnodes make extensive use of an optimization common in CPU design,
called *pipelining*. Pipelining is the right tool for the job when there's a
stream of input data that needs to be processed by a sequence of steps, and
there's different hardware responsible for each. The quintessential example is
@ -19,9 +19,9 @@ dryer and the first is being folded. In this way, one can make progress on
three loads of laundry simultaneously. Given infinite loads, the pipeline will
consistently complete a load at the rate of the slowest stage in the pipeline.
## Pipelining in the Validator
## Pipelining in the Fullnode
The validator contains two pipelined processes, one used in leader mode called
The fullnode contains two pipelined processes, one used in leader mode called
the TPU and one used in validator mode called the TVU. In both cases, the
hardware being pipelined is the same, the network input, the GPU cards, the CPU
cores, writes to disk, and the network output. What it does with that hardware

View File

@ -47,8 +47,8 @@ nodes are started
$ cargo build --all
```
The network is initialized with a genesis ledger generated by running the
following script.
The network is initialized with a genesis ledger and fullnode configuration files.
These files can be generated by running the following script.
```bash
$ ./multinode-demo/setup.sh
@ -69,7 +69,7 @@ $ ./multinode-demo/drone.sh
### Singlenode Testnet
Before you start a validator, make sure you know the IP address of the machine you
Before you start a fullnode, make sure you know the IP address of the machine you
want to be the bootstrap leader for the demo, and make sure that udp ports 8000-10000 are
open on all the machines you want to test with.
@ -86,10 +86,10 @@ The drone does not need to be running for subsequent leader starts.
### Multinode Testnet
To run a multinode testnet, after starting a leader node, spin up some
additional validators in separate shells:
additional full nodes in separate shells:
```bash
$ ./multinode-demo/validator-x.sh
$ ./multinode-demo/fullnode-x.sh
```
To run a performance-enhanced full node on Linux,
@ -99,7 +99,7 @@ your system:
```bash
$ ./fetch-perf-libs.sh
$ SOLANA_CUDA=1 ./multinode-demo/bootstrap-leader.sh
$ SOLANA_CUDA=1 ./multinode-demo/validator.sh
$ SOLANA_CUDA=1 ./multinode-demo/fullnode-x.sh
```
### Testnet Client Demo
@ -145,7 +145,7 @@ Generally we are using `debug` for infrequent debug messages, `trace` for potent
messages and `info` for performance-related logging.
You can also attach to a running process with GDB. The leader's process is named
_solana-validator_:
_solana-fullnode_:
```bash
$ sudo gdb
@ -161,7 +161,7 @@ This will dump all the threads stack traces into gdb.txt
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
$ ./multinode-demo/client.sh --entrypoint testnet.solana.com:8001 --duration 60
$ ./multinode-demo/client.sh --network testnet.solana.com:8001 --duration 60
```
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)

View File

@ -1,6 +1,6 @@
# Gossip Service
The Gossip Service acts as a gateway to nodes in the control plane. Validators
The Gossip Service acts as a gateway to nodes in the control plane. Fullnodes
use the service to ensure information is available to all other nodes in a cluster.
The service broadcasts information using a gossip protocol.
@ -116,8 +116,8 @@ Just like *pull message*, nodes are selected into the active set based on weight
## Notable differences from PlumTree
The active push protocol described here is based on [Plum
Tree](https://haslab.uminho.pt/jop/files/lpr07a.pdf). The main differences are:
The active push protocol described here is based on (Plum
Tree)[https://haslab.uminho.pt/jop/files/lpr07a.pdf]. The main differences are:
* Push messages have a wallclock that is signed by the originator. Once the
wallclock expires the message is dropped. A hop limit is difficult to implement

View File

@ -58,7 +58,7 @@ $ solana-install deploy http://example.com/path/to/solana-release.tar.bz2 update
$ solana-install init --pubkey 92DMonmBYXwEMHJ99c9ceRSpAmk9v6i3RdvDdXaVcrfj # <-- pubkey is obtained from whoever is deploying the updates
$ export PATH=~/.local/share/solana-install/bin:$PATH
$ solana-keygen ... # <-- runs the latest solana-keygen
$ solana-install run solana-validator ... # <-- runs a validator, restarting it as necesary when an update is applied
$ solana-install run solana-fullnode ... # <-- runs a fullnode, restarting it as necesary when an update is applied
```
### On-chain Update Manifest

View File

@ -30,7 +30,6 @@ Methods
* [getSlotLeader](#getslotleader)
* [getNumBlocksSinceSignatureConfirmation](#getnumblockssincesignatureconfirmation)
* [getTransactionCount](#gettransactioncount)
* [getEpochVoteAccounts](#getepochvoteaccounts)
* [requestAirdrop](#requestairdrop)
* [sendTransaction](#sendtransaction)
* [startSubscriptionChannel](#startsubscriptionchannel)
@ -168,16 +167,13 @@ curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "
---
### getRecentBlockhash
Returns a recent block hash from the ledger, and a fee schedule that can be used
to compute the cost of submitting a transaction using it.
Returns a recent block hash from the ledger
##### Parameters:
None
##### Results:
An array consisting of
* `string` - a Hash as base-58 encoded string
* `FeeCalculator object` - the fee schedule for this block hash
##### Example:
```bash
@ -185,7 +181,7 @@ An array consisting of
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getRecentBlockhash"}' http://localhost:8899
// Result
{"jsonrpc":"2.0","result":["GH7ome3EiwEr7tu9JuTh2dpYWBJK3z69Xm1ZE3MEE6JC",{"lamportsPerSignature": 0}],"id":1}
{"jsonrpc":"2.0","result":"GH7ome3EiwEr7tu9JuTh2dpYWBJK3z69Xm1ZE3MEE6JC","id":1}
```
---
@ -275,39 +271,6 @@ curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "m
---
### getEpochVoteAccounts
Returns the account info and associated stake for all the voting accounts in the current epoch.
##### Parameters:
None
##### Results:
An array consisting of vote accounts:
* `string` - the vote account's Pubkey as base-58 encoded string
* `integer` - the stake, in lamports, delegated to this vote account
* `VoteState` - the vote account's state
Each VoteState will be a JSON object with the following sub fields:
* `votes`, array of most recent vote lockouts
* `node_pubkey`, the pubkey of the node that votes using this account
* `authorized_voter_pubkey`, the pubkey of the authorized vote signer for this account
* `commission`, a 32-bit integer used as a fraction (commission/MAX_U32) for rewards payout
* `root_slot`, the most recent slot this account has achieved maximum lockout
* `credits`, credits accrued by this account for reaching lockouts
##### Example:
```bash
// Request
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getEpochVoteAccounts"}' http://localhost:8899
// Result
{"jsonrpc":"2.0","result":[[[84,115,89,23,41,83,221,72,58,23,53,245,195,188,140,161,242,189,200,164,139,214,12,180,84,161,28,151,24,243,159,125],10000000,{"authorized_voter_pubkey":[84,115,89,23,41,83,221,72,58,23,53,245,195,188,140,161,242,189,200,164,139,214,12,180,84,161,28,151,24,243,159,125],"commission":0,"credits":0,"node_pubkey":[49,139,227,211,47,39,69,86,131,244,160,144,228,169,84,143,142,253,83,81,212,110,254,12,242,71,219,135,30,60,157,213],"root_slot":null,"votes":[{"confirmation_count":1,"slot":0}]}]],"id":1}
```
---
### requestAirdrop
Requests an airdrop of lamports to a Pubkey
@ -353,14 +316,6 @@ curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "m
After connect to the RPC PubSub websocket at `ws://<ADDRESS>/`:
- Submit subscription requests to the websocket using the methods below
- Multiple subscriptions may be active at once
- All subscriptions take an optional `confirmations` parameter, which defines
how many confirmed blocks the node should wait before sending a notification.
The greater the number, the more likely the notification is to represent
consensus across the cluster, and the less likely it is to be affected by
forking or rollbacks. If unspecified, the default value is 0; the node will
send a notification as soon as it witnesses the event. The maximum
`confirmations` wait length is the cluster's `MAX_LOCKOUT_HISTORY`, which
represents the economic finality of the chain.
---
@ -370,8 +325,6 @@ for a given account public key changes
##### Parameters:
* `string` - account Pubkey, as base-58 encoded string
* `integer` - optional, number of confirmed blocks to wait before notification.
Default: 0, Max: `MAX_LOCKOUT_HISTORY` (greater integers rounded down)
##### Results:
* `integer` - Subscription id (needed to unsubscribe)
@ -381,8 +334,6 @@ for a given account public key changes
// Request
{"jsonrpc":"2.0", "id":1, "method":"accountSubscribe", "params":["CM78CPUeXjn8o3yroDHxUtKsZZgoy4GPkPPXfouKNH12"]}
{"jsonrpc":"2.0", "id":1, "method":"accountSubscribe", "params":["CM78CPUeXjn8o3yroDHxUtKsZZgoy4GPkPPXfouKNH12", 15]}
// Result
{"jsonrpc": "2.0","result": 0,"id": 1}
```
@ -420,8 +371,6 @@ for a given account owned by the program changes
##### Parameters:
* `string` - program_id Pubkey, as base-58 encoded string
* `integer` - optional, number of confirmed blocks to wait before notification.
Default: 0, Max: `MAX_LOCKOUT_HISTORY` (greater integers rounded down)
##### Results:
* `integer` - Subscription id (needed to unsubscribe)
@ -431,8 +380,6 @@ for a given account owned by the program changes
// Request
{"jsonrpc":"2.0", "id":1, "method":"programSubscribe", "params":["9gZbPtbtHrs6hEWgd6MbVY9VPFtS5Z8xKtnYwA2NynHV"]}
{"jsonrpc":"2.0", "id":1, "method":"programSubscribe", "params":["9gZbPtbtHrs6hEWgd6MbVY9VPFtS5Z8xKtnYwA2NynHV", 15]}
// Result
{"jsonrpc": "2.0","result": 0,"id": 1}
```
@ -472,8 +419,6 @@ On `signatureNotification`, the subscription is automatically cancelled
##### Parameters:
* `string` - Transaction Signature, as base-58 encoded string
* `integer` - optional, number of confirmed blocks to wait before notification.
Default: 0, Max: `MAX_LOCKOUT_HISTORY` (greater integers rounded down)
##### Results:
* `integer` - subscription id (needed to unsubscribe)
@ -483,8 +428,6 @@ On `signatureNotification`, the subscription is automatically cancelled
// Request
{"jsonrpc":"2.0", "id":1, "method":"signatureSubscribe", "params":["2EBVM6cB8vAAD93Ktr6Vd8p67XPbQzCJX47MpReuiCXJAtcjaxpvWpcg9Ege1Nr5Tk3a2GFrByT7WPBjdsTycY9b"]}
{"jsonrpc":"2.0", "id":1, "method":"signatureSubscribe", "params":["2EBVM6cB8vAAD93Ktr6Vd8p67XPbQzCJX47MpReuiCXJAtcjaxpvWpcg9Ege1Nr5Tk3a2GFrByT7WPBjdsTycY9b", 15]}
// Result
{"jsonrpc": "2.0","result": 0,"id": 1}
```

View File

@ -57,7 +57,7 @@ Forwarding is preferred, as it would minimize network congestion, allowing the
cluster to advertise higher TPS capacity.
## Validator Loop
## Fullnode Loop
The PoH Recorder manages the transition between modes. Once a ledger is
replayed, the validator can run until the recorder indicates it should be

View File

@ -2,12 +2,6 @@
Replication behavior yet to be implemented.
### Storage epoch
The storage epoch should be the number of slots which results in around 100GB-1TB of
ledger to be generated for replicators to store. Replicators will start storing ledger
when a given fork has a high probability of not being rolled back.
### Validator behavior
3. Every NUM\_KEY\_ROTATION\_TICKS it also validates samples received from
@ -43,100 +37,3 @@ transacation proves the validator incorrectly validated a fake storage proof.
The replicator is rewarded and the validator's staking balance is slashed or
frozen.
### Storage proof contract logic
Each replicator and validator will have their own storage account. The validator's
account would be separate from their gossip id similiar to their vote account.
These should be implemented as two programs one which handles the validator as the keysigner
and one for the replicator. In that way when the programs reference other accounts, they
can check the program id to ensure it is a validator or replicator account they are
referencing.
#### SubmitMiningProof
```rust,ignore
SubmitMiningProof {
slot: u64,
sha_state: Hash,
signature: Signature,
};
keys = [replicator_keypair]
```
Replicators create these after mining their stored ledger data for a certain hash value.
The slot is the end slot of the segment of ledger they are storing, the sha\_state
the result of the replicator using the hash function to sample their encrypted ledger segment.
The signature is the signature that was created when they signed a PoH value for the
current storage epoch. The list of proofs from the current storage epoch should be saved
in the account state, and then transfered to a list of proofs for the previous epoch when
the epoch passes. In a given storage epoch a given replicator should only submit proofs
for one segment.
The program should have a list of slots which are valid storage mining slots.
This list should be maintained by keeping track of slots which are rooted slots in which a significant
portion of the network has voted on with a high lockout value, maybe 32-votes old. Every SLOTS\_PER\_SEGMENT
number of slots would be added to this set. The program should check that the slot is in this set. The set can
be maintained by receiving a AdvertiseStorageRecentBlockHash and checking with its bank/locktower state.
The program should do a signature verify check on the signature, public key from the transaction submitter and the message of
the previous storage epoch PoH value.
#### ProofValidation
```rust,ignore
ProofValidation {
proof_mask: Vec<ProofStatus>,
}
keys = [validator_keypair, replicator_keypair(s) (unsigned)]
```
A validator will submit this transaction to indicate that a set of proofs for a given
segment are valid/not-valid or skipped where the validator did not look at it. The
keypairs for the replicators that it looked at should be referenced in the keys so the program
logic can go to those accounts and see that the proofs are generated in the previous epoch. The
sampling of the storage proofs should be verified ensuring that the correct proofs are skipped by
the validator according to the logic outlined in the validator behavior of sampling.
The included replicator keys will indicate the the storage samples which are being referenced; the
length of the proof\_mask should be verified against the set of storage proofs in the referenced
replicator account(s), and should match with the number of proofs submitted in the previous storage
epoch in the state of said replicator account.
#### ClaimStorageReward
```rust,ignore
ClaimStorageReward {
}
keys = [validator_keypair or replicator_keypair, validator/replicator_keypairs (unsigned)]
```
Replicators and validators will use this transaction to get paid tokens from a program state
where SubmitStorageProof, ProofValidation and ChallengeProofValidations are in a state where
proofs have been submitted and validated and there are no ChallengeProofValidations referencing
those proofs. For a validator, it should reference the replicator keypairs to which it has validated
proofs in the relevant epoch. And for a replicator it should reference validator keypairs for which it
has validated and wants to be rewarded.
#### ChallengeProofValidation
```rust,ignore
ChallengeProofValidation {
proof_index: u64,
hash_seed_value: Vec<u8>,
}
keys = [replicator_keypair, validator_keypair]
```
This transaction is for catching lazy validators who are not doing the work to validate proofs.
A replicator will submit this transaction when it sees a validator has approved a fake SubmitMiningProof
transaction. Since the replicator is a light client not looking at the full chain, it will have to ask
a validator or some set of validators for this information maybe via RPC call to obtain all ProofValidations for
a certain segment in the previous storage epoch. The program will look in the validator account
state see that a ProofValidation is submitted in the previous storage epoch and hash the hash\_seed\_value and
see that the hash matches the SubmitMiningProof transaction and that the validator marked it as valid. If so,
then it will save the challenge to the list of challenges that it has in its state.
#### AdvertiseStorageRecentBlockhash
```rust,ignore
AdvertiseStorageRecentBlockhash {
hash: Hash,
slot: u64,
}
```
Validators and replicators will submit this to indicate that a new storage epoch has passed and that the
storage proofs which are current proofs should now be for the previous epoch. Other transactions should
check to see that the epoch that they are referencing is accurate according to current chain state.

View File

@ -31,7 +31,7 @@ core. The total space required for verification is `1_ledger_segment +
Validators for PoRep are the same validators that are verifying transactions.
They have some stake that they have put up as collateral that ensures that
their work is honest. If you can prove that a validator verified a fake PoRep,
then the validator will not receive a reward for that storage epoch.
then the validators stake can be slashed.
Replicators are specialized *light clients*. They download a part of the ledger
and store it, and provide PoReps of storing the ledger. For each verified PoRep
@ -53,7 +53,7 @@ changes to determine what rate it can validate storage proofs.
### Constants
1. SLOTS\_PER\_SEGMENT: Number of slots in a segment of ledger data. The
1. NUM\_STORAGE\_ENTRIES: Number of entries in a segment of ledger data. The
unit of storage for a replicator.
2. NUM\_KEY\_ROTATION\_TICKS: Number of ticks to save a PoH value and cause a
key generation for the section of ledger just generated and the rotation of
@ -77,7 +77,7 @@ height.
3. Validator generates a storage proof confirmation transaction.
4. The storage proof confirmation transaction is integrated into the ledger.
6. Validator responds to RPC interfaces for what the last storage epoch PoH
value is and its slot.
value is and its entry\_height.
### Replicator behavior
@ -95,10 +95,10 @@ is:
- (d) replicator can subscribe to an abbreviated transaction stream to
generate the information itself
2. A replicator obtains the PoH hash corresponding to the last key rotation
along with its slot.
along with its entry\_height.
3. The replicator signs the PoH hash with its keypair. That signature is the
seed used to pick the segment to replicate and also the encryption key. The
replicator mods the signature with the slot to get which segment to
replicator mods the signature with the entry\_height to get which segment to
replicate.
4. The replicator retrives the ledger by asking peer validators and
replicators. See 6.5.
@ -118,9 +118,9 @@ current leader and it is put onto the ledger.
### Finding who has a given block of ledger
1. Validators monitor the transaction stream for storage mining proofs, and
keep a mapping of ledger segments by slot to public keys. When it sees
keep a mapping of ledger segments by entry\_height to public keys. When it sees
a storage mining proof it updates this mapping and provides an RPC interface
which takes a slot and hands back a list of public keys. The client
which takes an entry\_height and hands back a list of public keys. The client
then looks up in their cluster\_info table to see which network address that
corresponds to and sends a repair request to retrieve the necessary blocks of
ledger.

View File

@ -85,7 +85,7 @@ contains the following state information:
* Account::lamports - The staked lamports.
* `voter_pubkey` - The pubkey of the VoteState instance the lamports are
* `voter_id` - The pubkey of the VoteState instance the lamports are
delegated to.
* `credits_observed` - The total credits claimed over the lifetime of the
@ -109,7 +109,7 @@ program.
* `account[0]` - RW - The StakeState::Delegate instance.
`StakeState::Delegate::credits_observed` is initialized to `VoteState::credits`.
`StakeState::Delegate::voter_pubkey` is initialized to `account[1]`
`StakeState::Delegate::voter_id` is initialized to `account[1]`
* `account[1]` - R - The VoteState instance.
@ -127,7 +127,7 @@ reward.
* `account[1]` - RW - The StakeState::Delegate instance that is redeeming votes
credits.
* `account[2]` - R - The VoteState instance, must be the same as
`StakeState::voter_pubkey`
`StakeState::voter_id`
Reward is payed out for the difference between `VoteState::credits` to
`StakeState::Delgate.credits_observed`, and `credits_observed` is updated to
@ -181,7 +181,7 @@ the VoteState program or submitting votes to the program.
The total stake allocated to a VoteState program can be calculated by the sum of
all the StakeState programs that have the VoteState pubkey as the
`StakeState::Delegate::voter_pubkey`.
`StakeState::Delegate::voter_id`.
## Example Callflow
@ -194,12 +194,12 @@ nodes since stake is used as weight in the network control and data planes. One
way to implement this would be for the StakeState to delegate to a pool of
validators instead of a single one.
Instead of a single `vote_pubkey` and `credits_observed` entry in the StakeState
Instead of a single `vote_id` and `credits_observed` entry in the StakeState
program, the program can be initialized with a vector of tuples.
```rust,ignore
Voter {
voter_pubkey: Pubkey,
voter_id: Pubkey,
credits_observed: u64,
weight: u8,
}

View File

@ -11,7 +11,7 @@ it on the dashboard.
## TPS
The leader node's banking stage maintains a count of transactions that it recorded.
The leader node's banking stage maintains a count of transactions that it processed.
The dashboard displays the count averaged over 2 second period in the TPS time series
graph. The dashboard also shows per second mean, maximum and total TPS as a running
counter.

View File

@ -1,51 +0,0 @@
# Repair Service
The RepairService is in charge of retrieving missing blobs 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 blobs due to network failures
2) Consider a scenario where blocktree contains the set of slots {1, 3, 5}. Then Blocktree receives blobs for some slot 7, where for each of the blobs b, b.parent == 6, so then the parent-child relation 6 -> 7 is stored in blocktree. However, there is no way to chain these slots to any of the existing banks in Blocktree, and thus the `Blob 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 blobs in these future epochs, this exposes nodes to spam.
# Repair Protocols
The repair protocol makes best attempts to progress the forking structure of Blocktree.
The different protocol strategies to address the above challenges:
1. Blob Repair (Addresses Challenge #1):
This is the most basic repair protocol, with the purpose of detecting and filling "holes" in the ledger. Blocktree tracks the latest root slot. RepairService will then periodically iterate every fork in blocktree starting from the root slot, sending repair requests to validators for any missing blobs. It will send at most some `N` repair reqeusts per iteration.
Note: Validators will only accept blobs 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.
* Blocktree will track the set of "orphan" slots in a separate column family.
* RepairService will periodically make `RequestOrphan` requests for each of the orphans in blocktree.
`RequestOrphan(orphan)` request - `orphan` is the orphan slot that the requestor wants to know the parents of
`RequestOrphan(orphan)` response - The highest blobs for each of the first `N` parents of the requested `orphan`
On receiving the responses `p`, where `p` is some blob in a parent slot, validators will:
* Insert an empty `SlotMeta` in blocktree 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 blocktree, the `Blob Repair` protocol should attempt to fill those slots.
Note: Validators will only accept responses containing blobs 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 blob 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 blobs 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 blob.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 blobs. Both data and coding blobs for missing slots are sent. Repairmen do not send blobs 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 blocktree 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 blobs 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 Blocktree, which holds the latest root.

View File

@ -1,195 +1,68 @@
# 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
Stakers are rewarded for helping validate the ledger. They do it by delegating
their stake to fullnodes. Those fullnodes do the legwork and send votes to the
stakers' staking accounts. The rest of the cluster uses those stake-weighted
votes to select a block when forks arise. Both the fullnode and staker need
some economic incentive to play their part. The fullnode needs to be
compensated for its hardware and the staker needs to be compensated for risking
getting its stake slashed. The economics are covered in [staking
rewards](staking-rewards.md). This chapter, on the other hand, describes the
underlying mechanics of its implementation.
## Basic Besign
## Vote and Rewards accounts
The general idea is that the validator owns a Vote account. The Vote account
tracks validator votes, counts validator generated credits, and provides any
additional validator specific state. The Vote account is not aware of any
stakes delegated to it and has no staking weight.
The rewards process is split into two on-chain programs. The Vote program
solves the problem of making stakes slashable. The Rewards account acts as
custodian of the rewards pool. It is responsible for paying out each staker
once the staker proves to the Rewards program that it participated in
validating the ledger.
A separate Stake account (created by a staker) names a Vote account to which the
stake is delegated. Rewards generated are proportional to the amount of
lamports staked. The Stake account is owned by the staker only. Lamports
stored in this account are the stake.
The Vote account contains the following state information:
## Passive Delegation
* votes - The submitted votes.
Any number of Stake accounts can delegate to a single
Vote account without an interactive action from the identity controlling
the Vote account or submitting votes to the account.
* `delegate_id` - An identity that may operate with the weight of this
account's stake. It is typically the identity of a fullnode, but may be any
identity involved in stake-weighted computations.
The total stake allocated to a Vote account can be calculated by the sum of
all the Stake accounts that have the Vote account pubkey as the
`StakeState::Delegate::voter_pubkey`.
* `authorized_voter_id` - Only this identity is authorized to submit votes.
## Vote and Stake accounts
* `credits` - The amount of unclaimed rewards.
The rewards process is split into two on-chain programs. The Vote program solves
the problem of making stakes slashable. The Stake account acts as custodian of
the rewards pool, and provides passive delegation. The Stake program is
responsible for paying out each staker once the staker proves to the Stake
program that its delegate has participated in validating the ledger.
* `root_slot` - The last slot to reach the full lockout commitment necessary
for rewards.
### VoteState
The Rewards program is stateless and pays out reward when a staker submits its
Vote account to the program. Claiming a reward requires a transaction that
includes the following instructions:
VoteState is the current state of all the votes the validator has submitted to
the network. VoteState contains the following state information:
1. `RewardsInstruction::RedeemVoteCredits`
2. `VoteInstruction::ClearCredits`
* votes - The submitted votes data structure.
* credits - The total number of rewards this vote program has generated over its
lifetime.
* root\_slot - The last slot to reach the full lockout commitment necessary for
rewards.
* commission - The commission taken by this VoteState for any rewards claimed by
staker's Stake accounts. This is the percentage ceiling of the reward.
* Account::lamports - The accumulated lamports from the commission. These do not
count as stakes.
* `authorized_vote_signer` - Only this identity is authorized to submit votes. This field can only modified by this identity.
### VoteInstruction::Initialize
* `account[0]` - RW - The VoteState
`VoteState::authorized_vote_signer` is initialized to `account[0]`
other VoteState members defaulted
### VoteInstruction::AuthorizeVoteSigner(Pubkey)
* `account[0]` - RW - The VoteState
`VoteState::authorized_vote_signer` is set to to `Pubkey`, instruction must by
signed by Pubkey
### VoteInstruction::Vote(Vec<Vote>)
* `account[0]` - RW - The VoteState
`VoteState::lockouts` and `VoteState::credits` are updated according to voting lockout rules see [Fork Selection](fork-selection.md)
The Rewards program transfers lamports from the Rewards account to the Vote
account's public key. The Rewards program also ensures that the `ClearCredits`
instruction follows the `RedeemVoteCredits` instruction, such that a staker may
not claim rewards for the same work more than once.
* `account[1]` - RO - A list of some N most recent slots and their hashes for the vote to be verified against.
### Delegating Stake
`VoteInstruction::DelegateStake` allows the staker to choose a fullnode to
validate the ledger on its behalf. By being a delegate, the fullnode is
entitled to collect transaction fees when its is leader. The larger the stake,
the more often the fullnode will be able to collect those fees.
### StakeState
A StakeState takes one of two forms, StakeState::Delegate and StakeState::MiningPool.
### StakeState::Delegate
StakeState is the current delegation preference of the **staker**. StakeState
contains the following state information:
* Account::lamports - The staked lamports.
* `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.
### StakeState::MiningPool
There are two approaches to the mining pool. The bank could allow the
StakeState program to bypass the token balance check, or a program representing
the mining pool could run on the network. To avoid a single network wide lock,
the pool can be split into several mining pools. This design focuses on using
StakeState::MiningPool instances as the cluster wide mining pools.
* 256 StakeState::MiningPool are initialized, each with 1/256 number of mining pool
tokens stored as `Account::lamports`.
The stakes and the MiningPool are accounts that are owned by the same `Stake`
program.
### StakeInstruction::Initialize
* `account[0]` - RW - The StakeState::Delegate instance.
`StakeState::Delegate::credits_observed` is initialized to `VoteState::credits`.
`StakeState::Delegate::voter_pubkey` is initialized to `account[1]`
* `account[1]` - R - The VoteState instance.
### StakeInstruction::RedeemVoteCredits
The Staker or the owner of the Stake account sends a transaction with this
instruction to claim rewards.
The Vote account and the Stake account pair maintain a lifetime counter
of total rewards generated and claimed. When claiming rewards, the total lamports
deposited into the Stake account and as validator commission is proportional to
`VoteState::credits - StakeState::credits_observed`.
* `account[0]` - RW - The StakeState::MiningPool instance that will fulfill the
reward.
* `account[1]` - RW - The StakeState::Delegate instance that is redeeming votes
credits.
* `account[2]` - R - The VoteState instance, must be the same as
`StakeState::voter_pubkey`
Reward is paid out for the difference between `VoteState::credits` to
`StakeState::Delgate.credits_observed`, and `credits_observed` is updated to
`VoteState::credits`. The commission is deposited into the Vote account token
balance, and the reward is deposited to the Stake account token balance.
The total lamports paid is a percentage-rate of the lamports staked muiltplied by
the ratio of rewards being redeemed to rewards that could have been generated
during the rate period.
Any random MiningPool can be used to redeem the credits.
```rust,ignore
let credits_to_claim = vote_state.credits - stake_state.credits_observed;
stake_state.credits_observed = vote_state.credits;
```
`credits_to_claim` is used to compute the reward and commission, and
`StakeState::Delegate::credits_observed` is updated to the latest
`VoteState::credits` value.
## Collecting network fees into the MiningPool
At the end of the block, before the bank is frozen, but after it processed all
the transactions for the block, a virtual instruction is executed to collect
the transaction fees.
* A portion of the fees are deposited into the leader's account.
* A portion of the fees are deposited into the smallest StakeState::MiningPool
account.
## Authorizing a Vote Signer
### Authorizing a Vote Signer
`VoteInstruction::AuthorizeVoter` allows a staker to choose a signing service
for its votes. That service is responsible for ensuring the vote won't cause
the staker to be slashed.
## Benefits of the design
## Limitations
* Single vote for all the stakers.
* Clearing of the credit variable is not necessary for claiming rewards.
* Each delegated stake can claim its rewards independently.
* Commission for the work is deposited when a reward is claimed by the delegated
stake.
This proposal would benefit from the `read-only` accounts proposal to allow for
many rewards to be claimed concurrently.
## Example Callflow
<img alt="Passive Staking Callflow" src="img/passive-staking-callflow.svg" class="center"/>
Many stakers may delegate their stakes to the same fullnode. The fullnode must
send a separate vote to each staking account. If there are far more stakers
than fullnodes, that's a lot of network traffic. An alternative design might
have fullnodes submit each vote to just one account and then have each staker
submit that account along with their own to collect its reward.

View File

@ -1,8 +1,8 @@
# Staking Rewards
A Proof of Stake (PoS), (i.e. using in-protocol asset, SOL, to provide
secure consensus) design is outlined here. Solana implements a proof of
stake reward/security scheme for validator nodes in the cluster. The purpose is
Initial Proof of Stake (PoS) (i.e. using in-protocol asset, SOL, to provide
secure consensus) design ideas outlined here. Solana will implement a proof of
stake reward/security scheme for node validators in the cluster. The purpose is
threefold:
- Align validator incentives with that of the greater cluster through
@ -64,7 +64,7 @@ capital-at-risk to prevent a logical/optimal strategy of multiple chain voting.
We intend to implement slashing rules which, if broken, result some amount of
the offending validator's deposited stake to be removed from circulation. Given
the ordering properties of the PoH data structure, we believe we can simplify
our slashing rules to the level of a voting lockout time assigned per vote.
our slashing rules to the level of a voting lockout time assigned per vote.
I.e. Each vote has an associated lockout time (PoH duration) that represents a
duration by any additional vote from that validator must be in a PoH that
@ -110,7 +110,7 @@ in a slashable amount as a function of either:
1. the fraction of validators, out of the total validator pool, that were also
slashed during the same time period (ala Casper)
2. the amount of time since the vote was cast (e.g. a linearly increasing % of
total deposited as slashable amount over time), or both.
total deposited as slashable amount over time), or both.
This is an area currently under exploration

View File

@ -15,43 +15,39 @@ reasons:
* The cluster rolled back the ledger
* A validator responded to queries maliciously
### The AsyncClient and SyncClient Traits
### The Transact Trait
To troubleshoot, the application should retarget a lower-level component, where
fewer errors are possible. Retargeting can be done with different
implementations of the AsyncClient and SyncClient traits.
implementations of the Transact trait.
Components implement the following primary methods:
When Futures 0.3.0 is released, the Transact trait may look like this:
```rust,ignore
trait AsyncClient {
fn async_send_transaction(&self, transaction: Transaction) -> io::Result<Signature>;
}
trait SyncClient {
fn get_signature_status(&self, signature: &Signature) -> Result<Option<transaction::Result<()>>>;
trait Transact {
async fn send_transactions(txs: &[Transaction]) -> Vec<Result<(), TransactionError>>;
}
```
Users send transactions and asynchrounously and synchrounously await results.
Users send transactions and asynchrounously await their results.
#### ThinClient for Clusters
#### Transact with Clusters
The highest level implementation, ThinClient, targets a Solana cluster, which
may be a deployed testnet or a local cluster running on a development machine.
The highest level implementation targets a Solana cluster, which may be a
deployed testnet or a local cluster running on a development machine.
#### TpuClient for the TPU
#### Transact with the TPU
The next level is the TPU implementation, which is not yet implemented. At the
TPU level, the application sends transactions over Rust channels, where there
can be no surprises from network queues or dropped packets. The TPU implements
all "normal" transaction errors. It does signature verification, may report
The next level is the TPU implementation of Transact. At the TPU level, the
application sends transactions over Rust channels, where there can be no
surprises from network queues or dropped packets. The TPU implements all
"normal" transaction errors. It does signature verification, may report
account-in-use errors, and otherwise results in the ledger, complete with proof
of history hashes.
### Low-level testing
#### BankClient for the Bank
### Testing with the Bank
Below the TPU level is the Bank. The Bank doesn't do signature verification or
generate a ledger. The Bank is a convenient layer at which to test new on-chain

View File

@ -5,7 +5,7 @@ validator node.
Please note some of the information and instructions described here may change
in future releases.
### Overview
### Beta Testnet Overview
The testnet features a validator running at testnet.solana.com, which
serves as the entrypoint to the cluster for your validator.
@ -16,10 +16,7 @@ The testnet is configured to reset the ledger daily, or sooner
should the hourly automated cluster sanity test fail.
There is a **#validator-support** Discord channel available to reach other
testnet participants, [https://discord.gg/pquxPsq](https://discord.gg/pquxPsq).
Also we'd love it if you choose to register your validator node with us at
[https://forms.gle/LfFscZqJELbuUP139](https://forms.gle/LfFscZqJELbuUP139).
testnet participants, https://discord.gg/pquxPsq.
### Machine Requirements
Since the testnet is not intended for stress testing of max transaction
@ -32,16 +29,6 @@ traversal issues. A cloud-hosted machine works best. **Ensure that IP ports
Prebuilt binaries are available for Linux x86_64 (Ubuntu 18.04 recommended).
MacOS or WSL users may build from source.
For a performance testnet with many transactions we have some preliminary recommended setups:
| | Low end | Medium end | High end | Notes |
| --- | ---------|------------|----------| -- |
| CPU | AMD Threadripper 1900x | AMD Threadripper 2920x | AMD Threadripper 2950x | Consider a 10Gb-capable motherboard with as many PCIe lanes and m.2 slots as possible. |
| RAM | 16GB | 32GB | 64GB | |
| OS Drive | Samsung 860 Evo 2TB | Samsung 860 Evo 4TB | Samsung 860 Evo 4TB | Or equivalent SSD |
| Accounts Drive(s) | None | Samsung 970 Pro 1TB | 2x Samsung 970 Pro 1TB | |
| GPU | 4x Nvidia 1070 or 2x Nvidia 1080 Ti or 2x Nvidia 2070 | 2x Nvidia 2080 Ti | 4x Nvidia 2080 Ti | Any number of cuda-capable GPUs are supported on Linux platforms. |
#### Confirm The Testnet Is Reachable
Before attaching a validator node, sanity check that the cluster is accessible
to your machine by running some simple commands. If any of the commands fail,
@ -67,7 +54,7 @@ The `solana-install` tool can be used to easily install and upgrade the cluster
software on Linux x86_64 systems.
```bash
$ export SOLANA_RELEASE=v0.15.0 # skip this line to install the latest release
$ export SOLANA_RELEASE=v0.14.0 # skip this line to install the latest release
$ curl -sSf https://raw.githubusercontent.com/solana-labs/solana/v0.14.0/install/solana-install-init.sh | sh -s
```
@ -107,90 +94,78 @@ $ export PATH=$PWD/bin:$PATH
Sanity check that you are able to interact with the cluster by receiving a small
airdrop of lamports from the testnet drone:
```bash
$ solana-wallet airdrop 123
$ solana-wallet balance
$ solana-wallet -n testnet.solana.com airdrop 123
$ solana-wallet -n testnet.solana.com balance
```
Also try running following command to join the gossip network and view all the other nodes in the cluster:
```bash
$ solana-gossip --entrypoint testnet.solana.com:8001 spy
$ solana-gossip --network testnet.solana.com:8001 spy
# Press ^C to exit
```
Now configure a key pair for your validator by running:
```bash
$ solana-keygen -o validator-keypair.json
```
Then use one of the following commands, depending on your installation
choice, to start the node:
Then the following command will start a new validator node.
If this is a `solana-install`-installation:
```bash
$ clear-config.sh
$ validator.sh --identity validator-keypair.json --poll-for-new-genesis-block testnet.solana.com
$ clear-fullnode-config.sh
$ fullnode.sh --public-address --poll-for-new-genesis-block testnet.solana.com
```
Alternatively, the `solana-install run` command can be used to run the validator
node while periodically checking for and applying software updates:
```bash
$ clear-config.sh
$ solana-install run validator.sh -- --identity validator-keypair.json --poll-for-new-genesis-block testnet.solana.com
$ clear-fullnode-config.sh
$ solana-install run fullnode.sh -- --public-address --poll-for-new-genesis-block testnet.solana.com
```
If you built from source:
```bash
$ USE_INSTALL=1 ./multinode-demo/clear-config.sh
$ USE_INSTALL=1 ./multinode-demo/validator.sh --identity validator-keypair.json --poll-for-new-genesis-block testnet.solana.com
$ USE_INSTALL=1 ./multinode-demo/clear-fullnode-config.sh
$ USE_INSTALL=1 ./multinode-demo/fullnode.sh --public-address --poll-for-new-genesis-block testnet.solana.com
```
#### Controlling local network port allocation
By default the validator will dynamically select available network ports in the
8000-10000 range, and may be overridden with `--dynamic-port-range`. For
example, `validator.sh --dynamic-port-range 11000-11010 ...` will restrict the
example, `fullnode.sh --dynamic-port-range 11000-11010 ...` will restrict the
validator to ports 11000-11011.
### Validator Monitoring
When `validator.sh` starts, it will output a validator configuration that looks
From another console, confirm the IP address of your validator is visible in the
gossip network by running:
```bash
$ solana-gossip --network edge.testnet.solana.com:8001 spy
```
When `fullnode.sh` starts, it will output a fullnode configuration that looks
similar to:
```bash
======================[ validator configuration ]======================
identity pubkey: 4ceWXsL3UJvn7NYZiRkw7NsryMpviaKBDYr8GK7J61Dm
vote pubkey: 2ozWvfaXQd1X6uKh8jERoRGApDqSqcEy6fF1oN13LL2G
======================[ Fullnode configuration ]======================
node id: 4ceWXsL3UJvn7NYZiRkw7NsryMpviaKBDYr8GK7J61Dm
vote id: 2ozWvfaXQd1X6uKh8jERoRGApDqSqcEy6fF1oN13LL2G
ledger: ...
accounts: ...
======================================================================
```
The **identity pubkey** for your validator can also be found by running:
```bash
$ solana-keygen pubkey validator-keypair.json
```
From another console, confirm the IP address and **identity pubkey** of your validator is visible in the
gossip network by running:
```bash
$ solana-gossip --entrypoint testnet.solana.com:8001 spy
```
Provide the **vote pubkey** to the `solana-wallet show-vote-account` command to view
Provide the **vote id** pubkey to the `solana-wallet show-vote-account` command to view
the recent voting activity from your validator:
```bash
$ solana-wallet show-vote-account 2ozWvfaXQd1X6uKh8jERoRGApDqSqcEy6fF1oN13LL2G
$ solana-wallet -n testnet.solana.com show-vote-account 2ozWvfaXQd1X6uKh8jERoRGApDqSqcEy6fF1oN13LL2G
```
The vote pubkey for the validator can also be found by running:
The vote id for the validator can also be found by running:
```bash
# If this is a `solana-install`-installation run:
$ solana-keygen pubkey ~/.local/share/solana/install/active_release/config-local/validator-vote-keypair.json
$ solana-keygen pubkey ~/.local/share/solana/install/active_release/config-local/fullnode-vote-id.json
# Otherwise run:
$ solana-keygen pubkey ./config-local/validator-vote-keypair.json
$ solana-keygen pubkey ./config-local/fullnode-vote-id.json
```
### Sharing Metrics From Your Validator
If you have obtained a metrics username/password from the Solana maintainers to
help us monitor the health of the testnet, please perform the following steps
before starting the validator to activate metrics reporting:
If you'd like to share metrics perform the following steps before starting the
validator node:
```bash
export u="username obtained from the Solana maintainers"
export p="password obtained from the Solana maintainers"

View File

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

View File

@ -12,12 +12,8 @@ if [[ -d target/perf-libs ]]; then
exit 1
fi
(
set -x
git clone git@github.com:solana-labs/solana-perf-libs.git target/perf-libs
cd target/perf-libs
make -j"$(nproc)"
make DESTDIR=. install
)
./fetch-perf-libs.sh
set -x
git clone git@github.com:solana-labs/solana-perf-libs.git target/perf-libs
cd target/perf-libs
make -j"$(nproc)"
make DESTDIR=. install

20
ci/audit.sh Executable file
View File

@ -0,0 +1,20 @@
#!/usr/bin/env bash
#
# Audits project dependencies for security vulnerabilities
#
set -e
cd "$(dirname "$0")/.."
source ci/_
cargo_install_unless() {
declare crate=$1
shift
"$@" > /dev/null 2>&1 || \
_ cargo install "$crate"
}
cargo_install_unless cargo-audit cargo audit --version
_ cargo audit

View File

@ -14,10 +14,10 @@ steps:
- "queue=cuda"
- command: "ci/test-bench.sh"
name: "bench"
timeout_in_minutes: 60
timeout_in_minutes: 20
- command: ". ci/rust-version.sh; ci/docker-run.sh $$rust_stable_docker_image ci/test-stable.sh"
name: "stable"
timeout_in_minutes: 40
timeout_in_minutes: 30
artifact_paths: "log-*.txt"
- command: ". ci/rust-version.sh; ci/docker-run.sh $$rust_nightly_docker_image ci/test-coverage.sh"
name: "coverage"

View File

@ -4,8 +4,9 @@ ARG date
RUN set -x \
&& rustup install nightly-$date \
&& rustup show \
&& rustup show \
&& rustc --version \
&& cargo --version \
&& cargo install grcov \
&& rustc +nightly-$date --version \
&& cargo +nightly-$date --version

View File

@ -21,11 +21,9 @@ RUN set -x \
rsync \
sudo \
\
&& rm -rf /var/lib/apt/lists/* \
&& rustup component add rustfmt \
&& rustup component add clippy \
&& cargo install cargo-audit \
&& cargo install svgbob_cli \
&& cargo install mdbook \
&& rm -rf /var/lib/apt/lists/* \
&& rustc --version \
&& cargo --version

View File

@ -9,4 +9,3 @@ read -r rustc version _ < <(docker run solanalabs/rust rustc --version)
[[ $rustc = rustc ]]
docker tag solanalabs/rust:latest solanalabs/rust:"$version"
docker push solanalabs/rust:"$version"
docker push solanalabs/rust:latest

View File

@ -25,7 +25,8 @@ fi
build() {
$genPipeline && return
source ci/rust-version.sh stable
source scripts/ulimit-n.sh
_ scripts/ulimit-n.sh
_ cargo +$rust_stable build --all
}
@ -51,11 +52,11 @@ runTest() {
build
runTest "basic" \
runTest "Leader rotation on" \
"ci/localnet-sanity.sh -i 128"
runTest "restart" \
runTest "Leader rotation on, restart" \
"ci/localnet-sanity.sh -i 128 -k 16"
runTest "incremental restart, extra node" \
runTest "Leader rotation on, incremental restart, extra node" \
"ci/localnet-sanity.sh -i 128 -k 16 -R -x"

View File

@ -7,7 +7,7 @@ restartInterval=never
rollingRestart=false
maybeNoLeaderRotation=
extraNodes=0
walletRpcPort=:8899
walletRpcEndpoint=
usage() {
exitcode=0
@ -27,7 +27,7 @@ Start a local cluster and run sanity on it
nodes (at the cadence specified by -k). When disabled all
nodes will be first killed then restarted (default: $rollingRestart)
-b - Disable leader rotation
-x - Add an extra validator (may be supplied multiple times)
-x - Add an extra fullnode (may be supplied multiple times)
-r - Select the RPC endpoint hosted by a node that starts as
a validator node. If unspecified the RPC endpoint hosted by
the bootstrap leader will be used.
@ -61,7 +61,7 @@ while getopts "ch?i:k:brxR" opt; do
extraNodes=$((extraNodes + 1))
;;
r)
walletRpcPort=":18899"
walletRpcEndpoint="--rpc-port 18899"
;;
R)
rollingRestart=true
@ -79,20 +79,17 @@ nodes=(
"multinode-demo/drone.sh"
"multinode-demo/bootstrap-leader.sh \
--enable-rpc-exit \
--no-restart \
--init-complete-file init-complete-node1.log"
"multinode-demo/validator.sh \
"multinode-demo/fullnode.sh \
$maybeNoLeaderRotation \
--enable-rpc-exit \
--no-restart \
--init-complete-file init-complete-node2.log \
--rpc-port 18899"
)
for i in $(seq 1 $extraNodes); do
nodes+=(
"multinode-demo/validator.sh \
--no-restart \
"multinode-demo/fullnode.sh \
--label dyn$i \
--init-complete-file init-complete-node$((2 + i)).log \
$maybeNoLeaderRotation"
@ -169,11 +166,13 @@ startNodes() {
killNode() {
declare pid=$1
echo "kill $pid"
set +e
if kill "$pid"; then
echo "Waiting for $pid to exit..."
wait "$pid"
echo "$pid exited with $?"
else
echo "^^^ +++"
echo "Warning: unable to kill $pid"
fi
set -e
}
@ -197,11 +196,10 @@ killNodes() {
# Give the nodes a splash of time to cleanly exit before killing them
sleep 2
echo "--- Killing nodes: ${pids[*]}"
echo "--- Killing nodes"
for pid in "${pids[@]}"; do
killNode "$pid"
done
echo "done killing nodes"
pids=()
}
@ -256,7 +254,7 @@ rollingNodeRestart() {
}
verifyLedger() {
for ledger in bootstrap-leader validator; do
for ledger in bootstrap-leader fullnode; do
echo "--- $ledger ledger verification"
(
source multinode-demo/common.sh
@ -294,7 +292,7 @@ flag_error() {
}
if ! $skipSetup; then
multinode-demo/setup.sh --hashes-per-tick auto
multinode-demo/setup.sh
else
verifyLedger
fi
@ -306,10 +304,10 @@ while [[ $iteration -le $iterations ]]; do
(
source multinode-demo/common.sh
set -x
client_keypair=/tmp/client-id.json-$$
$solana_keygen -o $client_keypair || exit $?
client_id=/tmp/client-id.json-$$
$solana_keygen -o $client_id || exit $?
$solana_gossip spy --num-nodes-exactly $numNodes || exit $?
rm -rf $client_keypair
rm -rf $client_id
) || flag_error
echo "--- RPC API: bootstrap-leader getTransactionCount ($iteration)"
@ -323,7 +321,7 @@ while [[ $iteration -le $iterations ]]; do
cat log-transactionCount.txt
) || flag_error
echo "--- RPC API: validator getTransactionCount ($iteration)"
echo "--- RPC API: fullnode getTransactionCount ($iteration)"
(
set -x
curl --retry 5 --retry-delay 2 --retry-connrefused \
@ -364,7 +362,8 @@ while [[ $iteration -le $iterations ]]; do
}
(
set -x
timeout 60s scripts/wallet-sanity.sh --url http://127.0.0.1"$walletRpcPort"
# shellcheck disable=SC2086 # Don't want to double quote $walletRpcEndpoint
timeout 60s scripts/wallet-sanity.sh $walletRpcEndpoint
) || flag_error_if_no_leader_rotation
iteration=$((iteration + 1))

View File

@ -13,24 +13,19 @@ declare prints=(
'println!'
'eprint!'
'eprintln!'
'dbg!'
)
# Parts of the tree that are expected to be print free
declare print_free_tree=(
'core/src'
'drone/src'
'metrics/src'
'netutil/src'
'runtime/src'
'sdk/src'
'programs/vote_api/src'
'programs/vote_program/src'
'programs/stake_api/src'
'programs/stake_program/src'
'drone'
'metrics'
'netutil'
'runtime'
'sdk'
)
if _ git --no-pager grep -n --max-depth=0 "${prints[@]/#/-e }" -- "${print_free_tree[@]}"; then
if _ git grep "${prints[@]/#/-e }" -- "${print_free_tree[@]}"; then
exit 1
fi
@ -39,21 +34,7 @@ fi
# Default::default()
#
# Ref: https://github.com/solana-labs/solana/issues/2630
if _ git --no-pager grep -n 'Default::default()' -- '*.rs'; then
if _ git grep 'Default::default()' -- '*.rs'; then
exit 1
fi
# Let's keep a .gitignore for every crate, ensure it's got
# /target/ in it
declare gitignores_ok=true
for i in $(git --no-pager ls-files \*/Cargo.toml ); do
dir=$(dirname "$i")
if [[ ! -f $dir/.gitignore ]]; then
echo 'error: nits.sh .gitnore missing for crate '"$dir" >&2
gitignores_ok=false
elif ! grep -q -e '^/target/$' "$dir"/.gitignore; then
echo 'error: nits.sh "/target/" apparently missing from '"$dir"'/.gitignore' >&2
gitignores_ok=false
fi
done
"$gitignores_ok"

View File

@ -1,65 +0,0 @@
#!/usr/bin/env python
#
# This script figures the order in which workspace crates must be published to
# crates.io. Along the way it also ensures there are no circular dependencies
# that would cause a |cargo publish| to fail.
#
# On success an ordered list of Cargo.toml files is written to stdout
#
import os
import json
import subprocess
import sys;
def load_metadata():
return json.loads(subprocess.Popen(
'cargo metadata --no-deps --format-version=1',
shell=True, stdout=subprocess.PIPE).communicate()[0])
def get_packages():
metadata = load_metadata()
manifest_path = dict()
# Build dictionary of packages and their immediate solana-only dependencies
dependency_graph = dict()
for pkg in metadata['packages']:
manifest_path[pkg['name']] = pkg['manifest_path'];
dependency_graph[pkg['name']] = [x['name'] for x in pkg['dependencies'] if x['name'].startswith('solana')];
# Check for direct circular dependencies
circular_dependencies = set()
for package, dependencies in dependency_graph.items():
for dependency in dependencies:
if dependency in dependency_graph and package in dependency_graph[dependency]:
circular_dependencies.add(' <--> '.join(sorted([package, dependency])))
for dependency in circular_dependencies:
sys.stderr.write('Error: Circular dependency: {}\n'.format(dependency))
if len(circular_dependencies) != 0:
sys.exit(1)
# Order dependencies
sorted_dependency_graph = []
max_iterations = pow(len(dependency_graph),2)
while dependency_graph:
if max_iterations == 0:
# TODO: Be more helpful and find the actual cycle for the user
sys.exit('Error: Circular dependency suspected between these packages: {}\n'.format(' '.join(dependency_graph.keys())))
max_iterations -= 1
for package, dependencies in dependency_graph.items():
for dependency in dependencies:
if dependency in dependency_graph:
break
else:
del dependency_graph[package]
sorted_dependency_graph.append((package, manifest_path[package]))
return sorted_dependency_graph
for package, manifest in get_packages():
print os.path.relpath(manifest)

View File

@ -3,13 +3,35 @@ set -e
cd "$(dirname "$0")/.."
source ci/semver_bash/semver.sh
# shellcheck disable=SC2086
is_crate_version_uploaded() {
name=$1
version=$2
curl https://crates.io/api/v1/crates/${name}/${version} | \
python3 -c "import sys,json; print('version' in json.load(sys.stdin));"
}
# List of internal crates to publish
#
# IMPORTANT: the order of the CRATES *is* significant. Crates must be published
# before the crates that depend on them. Note that this information is already
# expressed in the various Cargo.toml files, and ideally would not be duplicated
# here. (TODO: figure the crate ordering dynamically)
#
CRATES=(
kvstore
logger
netutil
sdk
keygen
metrics
client
drone
programs/{budget_api,config_api,stake_api,storage_api,token_api,vote_api,exchange_api}
programs/{vote_program,budget_program,bpf_loader,config_program,exchange_program,failure_program}
programs/{noop_program,stake_program,storage_program,token_program}
runtime
vote-signer
core
fullnode
genesis
gossip
ledger-tool
wallet
install
)
# Only package/publish if this is a tagged release
[[ -n $TRIGGERED_BUILDKITE_TAG ]] || {
@ -27,37 +49,25 @@ expectedCrateVersion="$MAJOR.$MINOR.$PATCH$SPECIAL"
cargoCommand="cargo publish --token $CRATES_IO_TOKEN"
Cargo_tomls=$(ci/order-crates-for-publishing.py)
for Cargo_toml in $Cargo_tomls; do
echo "-- $Cargo_toml"
grep -q "^version = \"$expectedCrateVersion\"$" "$Cargo_toml" || {
echo "Error: $Cargo_toml version is not $expectedCrateVersion"
for crate in "${CRATES[@]}"; do
if [[ ! -r $crate/Cargo.toml ]]; then
echo "Error: $crate/Cargo.toml does not exist"
exit 1
fi
echo "-- $crate"
grep -q "^version = \"$expectedCrateVersion\"$" "$crate"/Cargo.toml || {
echo "Error: $crate/Cargo.toml version is not $expectedCrateVersion"
exit 1
}
(
set -x
crate=$(dirname "$Cargo_toml")
# TODO: the rocksdb package does not build with the stock rust docker image,
# so use the solana rust docker image until this is resolved upstream
source ci/rust-version.sh
ci/docker-run.sh "$rust_stable_docker_image" bash -exc "cd $crate; $cargoCommand"
) || true # <-- Don't fail. We want to be able to retry the job in cases when a publish fails halfway due to network/cloud issues
# shellcheck disable=SC2086
crate_name=$(grep -m 1 '^name = ' $Cargo_toml | cut -f 3 -d ' ' | tr -d \")
numRetries=30
for ((i = 1 ; i <= numRetries ; i++)); do
echo "Attempt ${i} of ${numRetries}"
# shellcheck disable=SC2086
if [[ $(is_crate_version_uploaded $crate_name $expectedCrateVersion) = True ]] ; then
echo "Found ${crate_name} version ${expectedCrateVersion} on crates.io"
break
fi
echo "Did not find ${crate_name} version ${expectedCrateVersion} on crates.io. Sleeping for 2 seconds."
sleep 2
done
#ci/docker-run.sh rust bash -exc "cd $crate; $cargoCommand"
)
done
exit 0

View File

@ -45,9 +45,7 @@ beta)
CHANNEL_BRANCH=$BETA_CHANNEL
;;
stable)
# Set to whatever branch 'testnet' is on.
# TODO: Revert to $STABLE_CHANNEL for TdS
CHANNEL_BRANCH=$BETA_CHANNEL
CHANNEL_BRANCH=$STABLE_CHANNEL
;;
*)
echo "Error: Invalid PUBLISH_CHANNEL=$PUBLISH_CHANNEL"

View File

@ -48,7 +48,7 @@ echo --- Creating tarball
COMMIT="$(git rev-parse HEAD)"
(
echo "channel: $CHANNEL_OR_TAG"
echo "channel: $CHANNEL"
echo "commit: $COMMIT"
echo "target: $TARGET"
) > solana-release/version.yml
@ -56,41 +56,37 @@ echo --- Creating tarball
source ci/rust-version.sh stable
scripts/cargo-install-all.sh +"$rust_stable" solana-release
rm -rf target/perf-libs
./fetch-perf-libs.sh
mkdir solana-release/target
cp -a target/perf-libs solana-release/target/
# shellcheck source=/dev/null
source ./target/perf-libs/env.sh
(
cd validator
cargo +"$rust_stable" install --path . --features=cuda --root ../solana-release-cuda
cd fullnode
cargo install --path . --features=cuda --root ../solana-release-cuda
)
cp solana-release-cuda/bin/solana-validator solana-release/bin/solana-validator-cuda
cp solana-release-cuda/bin/solana-fullnode solana-release/bin/solana-fullnode-cuda
cp -a scripts multinode-demo solana-release/
# Add a wrapper script for validator.sh
# Add a wrapper script for fullnode.sh
# TODO: Remove multinode/... from tarball
cat > solana-release/bin/validator.sh <<'EOF'
cat > solana-release/bin/fullnode.sh <<'EOF'
#!/usr/bin/env bash
set -e
cd "$(dirname "$0")"/..
export USE_INSTALL=1
exec multinode-demo/validator.sh "$@"
exec multinode-demo/fullnode.sh "$@"
EOF
chmod +x solana-release/bin/validator.sh
chmod +x solana-release/bin/fullnode.sh
# Add a wrapper script for clear-config.sh
# Add a wrapper script for clear-fullnode-config.sh
# TODO: Remove multinode/... from tarball
cat > solana-release/bin/clear-config.sh <<'EOF'
cat > solana-release/bin/clear-fullnode-config.sh <<'EOF'
#!/usr/bin/env bash
set -e
cd "$(dirname "$0")"/..
export USE_INSTALL=1
exec multinode-demo/clear-config.sh "$@"
exec multinode-demo/clear-fullnode-config.sh "$@"
EOF
chmod +x solana-release/bin/clear-config.sh
chmod +x solana-release/bin/clear-fullnode-config.sh
tar jvcf solana-release-$TARGET.tar.bz2 solana-release/
cp solana-release/bin/solana-install solana-install-$TARGET

View File

@ -16,8 +16,8 @@
export rust_stable=1.34.0
export rust_stable_docker_image=solanalabs/rust:1.34.0
export rust_nightly=nightly-2019-05-01
export rust_nightly_docker_image=solanalabs/rust-nightly:2019-05-01
export rust_nightly=nightly-2019-03-14
export rust_nightly_docker_image=solanalabs/rust-nightly:2019-03-14
[[ -z $1 ]] || (

View File

@ -40,10 +40,7 @@ fi
BENCH_FILE=bench_output.log
BENCH_ARTIFACT=current_bench_results.log
# Ensure all dependencies are built
_ cargo +$rust_nightly build --all --release
# Remove "BENCH_FILE", if it exists so that the following commands can append
# First remove "BENCH_FILE", if it exists so that the following commands can append
rm -f "$BENCH_FILE"
# Run sdk benches
@ -62,11 +59,8 @@ _ cargo +$rust_nightly bench --manifest-path core/Cargo.toml ${V:+--verbose} \
_ cargo +$rust_nightly bench --manifest-path programs/bpf/Cargo.toml ${V:+--verbose} --features=bpf_c \
-- -Z unstable-options --format=json --nocapture | tee -a "$BENCH_FILE"
# TODO: debug why solana-upload-perf takes over 30 minutes to complete.
exit 0
_ cargo +$rust_nightly run --release --package solana-upload-perf \
-- "$BENCH_FILE" "$TARGET_BRANCH" "$UPLOAD_METRICS" | tee "$BENCH_ARTIFACT"
-- "$BENCH_FILE" "$TARGET_BRANCH" "$UPLOAD_METRICS" > "$BENCH_ARTIFACT"
upload-ci-artifact "$BENCH_FILE"
upload-ci-artifact "$BENCH_ARTIFACT"

View File

@ -12,9 +12,8 @@ export RUSTFLAGS="-D warnings"
_ cargo +"$rust_stable" fmt --all -- --check
_ cargo +"$rust_stable" clippy --all -- --version
_ cargo +"$rust_stable" clippy --all -- --deny=warnings
_ cargo +"$rust_stable" audit
_ ci/audit.sh
_ ci/nits.sh
_ ci/order-crates-for-publishing.py
_ book/build.sh
echo --- ok

View File

@ -8,7 +8,6 @@ source ci/rust-version.sh stable
export RUST_BACKTRACE=1
rm -rf target/perf-libs
./fetch-perf-libs.sh
export LD_LIBRARY_PATH=$PWD/target/perf-libs:$LD_LIBRARY_PATH

View File

@ -47,6 +47,7 @@ test-stable-perf)
# BPF program tests
_ make -C programs/bpf/c tests
_ programs/bpf/rust/noop/build.sh # Must be built out of band
_ cargo +"$rust_stable" test \
--manifest-path programs/bpf/Cargo.toml \
--no-default-features --features=bpf_c,bpf_rust
@ -61,7 +62,6 @@ test-stable-perf)
# is not yet loaded.
sudo --non-interactive ./net/scripts/enable-nvidia-persistence-mode.sh
rm -rf target/perf-libs
./fetch-perf-libs.sh
# shellcheck source=/dev/null
source ./target/perf-libs/env.sh

View File

@ -52,14 +52,14 @@ launchTestnet() {
declare q_mean_tps='
SELECT round(mean("sum_count")) AS "mean_tps" FROM (
SELECT sum("count") AS "sum_count"
FROM "testnet-automation"."autogen"."banking_stage-record_transactions"
FROM "testnet-automation"."autogen"."counter-banking_stage-process_transactions"
WHERE time > now() - 300s GROUP BY time(1s)
)'
declare q_max_tps='
SELECT round(max("sum_count")) AS "max_tps" FROM (
SELECT sum("count") AS "sum_count"
FROM "testnet-automation"."autogen"."banking_stage-record_transactions"
FROM "testnet-automation"."autogen"."counter-banking_stage-process_transactions"
WHERE time > now() - 300s GROUP BY time(1s)
)'

View File

@ -20,10 +20,8 @@ tarChannelOrTag=edge
delete=false
enableGpu=false
bootDiskType=""
leaderRotation=true
blockstreamer=false
deployUpdateManifest=true
fetchLogs=true
maybeHashesPerTick=
usage() {
exitcode=0
@ -50,8 +48,9 @@ Deploys a CD testnet
-c [number] - Number of client bencher nodes (default: $clientNodeCount)
-u - Include a Blockstreamer (default: $blockstreamer)
-P - Use public network IP addresses (default: $publicNetwork)
-G - Enable GPU, and set count/type of GPUs to use (e.g n1-standard-16 --accelerator count=2,type=nvidia-tesla-v100)
-G - Enable GPU, and set count/type of GPUs to use (e.g n1-standard-16 --accelerator count=4,type=nvidia-tesla-k80)
-g - Enable GPU (default: $enableGpu)
-b - Disable leader rotation
-a [address] - Set the bootstrap fullnode's external IP address to this GCE address
-d [disk-type] - Specify a boot disk type (default None) Use pd-ssd to get ssd on GCE.
-D - Delete the network
@ -62,11 +61,6 @@ Deploys a CD testnet
-s - Skip start. Nodes will still be created or configured, but network software will not be started.
-S - Stop network software without tearing down nodes.
-f - Discard validator nodes that didn't bootup successfully
-w - Skip time-consuming "bells and whistles" that are
unnecessary for a high-node count demo testnet
--hashes-per-tick NUM_HASHES|sleep|auto
- Override the default --hashes-per-tick for the cluster
Note: the SOLANA_METRICS_CONFIG environment variable is used to configure
metrics
@ -76,22 +70,7 @@ EOF
zone=()
shortArgs=()
while [[ -n $1 ]]; do
if [[ ${1:0:2} = -- ]]; then
if [[ $1 = --hashes-per-tick ]]; then
maybeHashesPerTick="$1 $2"
shift 2
else
usage "Unknown long option: $1"
fi
else
shortArgs+=("$1")
shift
fi
done
while getopts "h?p:Pn:c:t:gG:a:Dd:rusxz:p:C:Sfew" opt "${shortArgs[@]}"; do
while getopts "h?p:Pn:c:t:gG:a:Dbd:rusxz:p:C:Sfe" opt; do
case $opt in
h | \?)
usage
@ -124,6 +103,9 @@ while getopts "h?p:Pn:c:t:gG:a:Dd:rusxz:p:C:Sfew" opt "${shortArgs[@]}"; do
;;
esac
;;
b)
leaderRotation=false
;;
g)
enableGpu=true
;;
@ -161,12 +143,8 @@ while getopts "h?p:Pn:c:t:gG:a:Dd:rusxz:p:C:Sfew" opt "${shortArgs[@]}"; do
S)
stopNetwork=true
;;
w)
fetchLogs=false
deployUpdateManifest=false
;;
*)
usage "Unknown option: $opt"
usage "Error: unhandled option: $opt"
;;
esac
done
@ -244,6 +222,10 @@ if ! $skipCreate; then
fi
fi
if ! $leaderRotation; then
create_args+=(-b)
fi
if $publicNetwork; then
create_args+=(-P)
fi
@ -300,46 +282,42 @@ if ! $skipStart; then
op=start
fi
echo "--- net.sh $op"
args=("$op" -t "$tarChannelOrTag")
maybeRejectExtraNodes=
if ! $publicNetwork; then
args+=(-o rejectExtraNodes)
maybeRejectExtraNodes="-o rejectExtraNodes"
fi
maybeNoValidatorSanity=
if [[ -n $NO_VALIDATOR_SANITY ]]; then
args+=(-o noValidatorSanity)
maybeNoValidatorSanity="-o noValidatorSanity"
fi
maybeNoLedgerVerify=
if [[ -n $NO_LEDGER_VERIFY ]]; then
args+=(-o noLedgerVerify)
fi
if [[ -n $NO_INSTALL_CHECK ]]; then
args+=(-o noInstallCheck)
fi
if [[ -n $maybeHashesPerTick ]]; then
# shellcheck disable=SC2206 # Do not want to quote $maybeHashesPerTick
args+=($maybeHashesPerTick)
maybeNoLedgerVerify="-o noLedgerVerify"
fi
maybeReuseLedger=
if $reuseLedger; then
args+=(-r)
fi
if ! $failOnValidatorBootupFailure; then
args+=(-F)
maybeReuseLedger="-r"
fi
maybeUpdateManifestKeypairFile=
# shellcheck disable=SC2154 # SOLANA_INSTALL_UPDATE_MANIFEST_KEYPAIR_x86_64_unknown_linux_gnu comes from .buildkite/env/
if $deployUpdateManifest && [[ -n $SOLANA_INSTALL_UPDATE_MANIFEST_KEYPAIR_x86_64_unknown_linux_gnu ]]; then
if [[ -n $SOLANA_INSTALL_UPDATE_MANIFEST_KEYPAIR_x86_64_unknown_linux_gnu ]]; then
echo "$SOLANA_INSTALL_UPDATE_MANIFEST_KEYPAIR_x86_64_unknown_linux_gnu" > update_manifest_keypair.json
args+=(-i update_manifest_keypair.json)
maybeUpdateManifestKeypairFile="-i update_manifest_keypair.json"
fi
# shellcheck disable=SC2086 # Don't want to double quote the $maybeXYZ variables
time net/net.sh "${args[@]}"
time net/net.sh $op -t "$tarChannelOrTag" \
$maybeUpdateManifestKeypairFile \
$maybeReuseLedger \
$maybeRejectExtraNodes \
$maybeNoValidatorSanity \
$maybeNoLedgerVerify
) || ok=false
if $fetchLogs; then
net/net.sh logs
fi
net/net.sh logs
fi
$ok

View File

@ -80,20 +80,7 @@ ci/channel-info.sh
eval "$(ci/channel-info.sh)"
EC2_ZONES=(
us-west-1a
us-west-2a
us-east-1a
us-east-2a
sa-east-1a
eu-west-1a
eu-west-2a
eu-central-1a
ap-northeast-2a
ap-southeast-2a
ap-south-1a
ca-central-1a
)
EC2_ZONES=(us-west-1a sa-east-1a ap-northeast-2a eu-central-1a ca-central-1a)
# GCE zones with _lots_ of quota
GCE_ZONES=(
@ -132,16 +119,19 @@ case $TESTNET in
testnet-edge|testnet-edge-perf)
CHANNEL_OR_TAG=edge
CHANNEL_BRANCH=$EDGE_CHANNEL
: "${TESTNET_DB_HOST:=https://clocktower-f1d56615.influxcloud.net:8086}"
;;
testnet-beta|testnet-beta-perf)
CHANNEL_OR_TAG=beta
CHANNEL_BRANCH=$BETA_CHANNEL
: "${TESTNET_DB_HOST:=https://clocktower-f1d56615.influxcloud.net:8086}"
;;
testnet)
CHANNEL_OR_TAG=$STABLE_CHANNEL_LATEST_TAG
CHANNEL_BRANCH=$STABLE_CHANNEL
: "${EC2_NODE_COUNT:=10}"
: "${GCE_NODE_COUNT:=}"
: "${TESTNET_DB_HOST:=https://clocktower-f1d56615.influxcloud.net:8086}"
;;
testnet-perf)
CHANNEL_OR_TAG=$STABLE_CHANNEL_LATEST_TAG
@ -152,6 +142,7 @@ testnet-demo)
CHANNEL_BRANCH=$BETA_CHANNEL
: "${GCE_NODE_COUNT:=150}"
: "${GCE_LOW_QUOTA_NODE_COUNT:=70}"
: "${TESTNET_DB_HOST:=https://clocktower-f1d56615.influxcloud.net:8086}"
;;
*)
echo "Error: Invalid TESTNET=$TESTNET"
@ -212,7 +203,6 @@ sanity() {
testnet-edge)
(
set -x
NO_INSTALL_CHECK=1 \
NO_LEDGER_VERIFY=1 \
ci/testnet-sanity.sh edge-testnet-solana-com ec2 us-west-1a
)
@ -229,7 +219,6 @@ sanity() {
testnet-beta)
(
set -x
NO_INSTALL_CHECK=1 \
NO_LEDGER_VERIFY=1 \
ci/testnet-sanity.sh beta-testnet-solana-com ec2 us-west-1a
)
@ -278,7 +267,6 @@ sanity() {
ok=true
if [[ -n $GCE_NODE_COUNT ]]; then
NO_LEDGER_VERIFY=1 \
NO_VALIDATOR_SANITY=1 \
ci/testnet-sanity.sh demo-testnet-solana-com gce "${GCE_ZONES[0]}" -f || ok=false
else
echo "Error: no GCE nodes"
@ -325,8 +313,7 @@ deploy() {
${skipCreate:+-e} \
${skipStart:+-s} \
${maybeStop:+-S} \
${maybeDelete:+-D} \
--hashes-per-tick auto
${maybeDelete:+-D}
)
;;
testnet-edge-perf)
@ -337,11 +324,11 @@ deploy() {
RUST_LOG=solana=warn \
ci/testnet-deploy.sh -p edge-perf-testnet-solana-com -C ec2 -z us-west-2b \
-g -t "$CHANNEL_OR_TAG" -c 2 \
-b \
${skipCreate:+-e} \
${skipStart:+-s} \
${maybeStop:+-S} \
${maybeDelete:+-D} \
--hashes-per-tick auto
${maybeDelete:+-D}
)
;;
testnet-beta)
@ -350,11 +337,11 @@ deploy() {
NO_VALIDATOR_SANITY=1 \
ci/testnet-deploy.sh -p beta-testnet-solana-com -C ec2 -z us-west-1a \
-t "$CHANNEL_OR_TAG" -n 3 -c 0 -u -P -a eipalloc-0f286cf8a0771ce35 \
-b \
${skipCreate:+-e} \
${skipStart:+-s} \
${maybeStop:+-S} \
${maybeDelete:+-D} \
--hashes-per-tick auto
${maybeDelete:+-D}
)
;;
testnet-beta-perf)
@ -365,11 +352,11 @@ deploy() {
RUST_LOG=solana=warn \
ci/testnet-deploy.sh -p beta-perf-testnet-solana-com -C ec2 -z us-west-2b \
-g -t "$CHANNEL_OR_TAG" -c 2 \
-b \
${skipCreate:+-e} \
${skipStart:+-s} \
${maybeStop:+-S} \
${maybeDelete:+-D} \
--hashes-per-tick auto
${maybeDelete:+-D}
)
;;
testnet)
@ -382,7 +369,7 @@ deploy() {
# shellcheck disable=SC2068
ci/testnet-deploy.sh -p testnet-solana-com -C ec2 ${EC2_ZONE_ARGS[@]} \
-t "$CHANNEL_OR_TAG" -n "$EC2_NODE_COUNT" -c 0 -u -P -f -a eipalloc-0fa502bf95f6f18b2 \
-t "$CHANNEL_OR_TAG" -n "$EC2_NODE_COUNT" -c 0 -u -P -a eipalloc-0fa502bf95f6f18b2 \
${skipCreate:+-e} \
${maybeSkipStart:+-s} \
${maybeStop:+-S} \
@ -391,7 +378,7 @@ deploy() {
if [[ -n $GCE_NODE_COUNT ]]; then
# shellcheck disable=SC2068
ci/testnet-deploy.sh -p testnet-solana-com -C gce ${GCE_ZONE_ARGS[@]} \
-t "$CHANNEL_OR_TAG" -n "$GCE_NODE_COUNT" -c 0 -P -f \
-t "$CHANNEL_OR_TAG" -n "$GCE_NODE_COUNT" -c 0 -P \
${skipCreate:+-e} \
${skipStart:+-s} \
${maybeStop:+-S} \
@ -409,12 +396,12 @@ deploy() {
ci/testnet-deploy.sh -p perf-testnet-solana-com -C gce -z us-west1-b \
-G "--machine-type n1-standard-16 --accelerator count=2,type=nvidia-tesla-v100" \
-t "$CHANNEL_OR_TAG" -c 2 \
-b \
-d pd-ssd \
${skipCreate:+-e} \
${skipStart:+-s} \
${maybeStop:+-S} \
${maybeDelete:+-D} \
--hashes-per-tick auto
${maybeDelete:+-D}
)
;;
testnet-demo)
@ -426,28 +413,22 @@ deploy() {
fi
# shellcheck disable=SC2068
NO_LEDGER_VERIFY=1 \
NO_VALIDATOR_SANITY=1 \
ci/testnet-deploy.sh -p demo-testnet-solana-com -C gce ${GCE_ZONE_ARGS[@]} \
-t "$CHANNEL_OR_TAG" -n "$GCE_NODE_COUNT" -c 0 -P -u -f -w \
-a demo-testnet-solana-com \
${skipCreate:+-e} \
${maybeSkipStart:+-s} \
${maybeStop:+-S} \
${maybeDelete:+-D} \
--hashes-per-tick auto
ci/testnet-deploy.sh -p demo-testnet-solana-com -C gce ${GCE_ZONE_ARGS[@]} \
-t "$CHANNEL_OR_TAG" -n "$GCE_NODE_COUNT" -c 0 -P -u -f \
-a demo-testnet-solana-com \
${skipCreate:+-e} \
${maybeSkipStart:+-s} \
${maybeStop:+-S} \
${maybeDelete:+-D}
if [[ -n $GCE_LOW_QUOTA_NODE_COUNT ]]; then
# shellcheck disable=SC2068
NO_LEDGER_VERIFY=1 \
NO_VALIDATOR_SANITY=1 \
ci/testnet-deploy.sh -p demo-testnet-solana-com2 -C gce ${GCE_LOW_QUOTA_ZONE_ARGS[@]} \
-t "$CHANNEL_OR_TAG" -n "$GCE_LOW_QUOTA_NODE_COUNT" -c 0 -P -f -x -w \
${skipCreate:+-e} \
${skipStart:+-s} \
${maybeStop:+-S} \
${maybeDelete:+-D} \
--hashes-per-tick auto
ci/testnet-deploy.sh -p demo-testnet-solana-com2 -C gce ${GCE_LOW_QUOTA_ZONE_ARGS[@]} \
-t "$CHANNEL_OR_TAG" -n "$GCE_LOW_QUOTA_NODE_COUNT" -c 0 -P -f -x \
${skipCreate:+-e} \
${skipStart:+-s} \
${maybeStop:+-S} \
${maybeDelete:+-D}
fi
)
;;

View File

@ -64,7 +64,6 @@ for zone in "$@"; do
${NO_LEDGER_VERIFY:+-o noLedgerVerify} \
${NO_VALIDATOR_SANITY:+-o noValidatorSanity} \
${REJECT_EXTRA_NODES:+-o rejectExtraNodes} \
${NO_INSTALL_CHECK:+-o noInstallCheck} \
$zone || ok=false
net/net.sh logs

1
client/.gitignore vendored
View File

@ -1 +0,0 @@
/target/

View File

@ -1,6 +1,6 @@
[package]
name = "solana-client"
version = "0.15.2"
version = "0.14.0"
description = "Solana Client"
authors = ["Solana Maintainers <maintainers@solana.com>"]
repository = "https://github.com/solana-labs/solana"
@ -9,18 +9,18 @@ license = "Apache-2.0"
edition = "2018"
[dependencies]
bincode = "1.1.4"
bincode = "1.1.2"
bs58 = "0.2.0"
log = "0.4.2"
jsonrpc-core = "10.1.0"
reqwest = "0.9.17"
reqwest = "0.9.11"
serde = "1.0.89"
serde_derive = "1.0.91"
serde_derive = "1.0.88"
serde_json = "1.0.39"
solana-netutil = { path = "../netutil", version = "0.15.2" }
solana-sdk = { path = "../sdk", version = "0.15.2" }
solana-netutil = { path = "../netutil", version = "0.14.0" }
solana-sdk = { path = "../sdk", version = "0.14.0" }
[dev-dependencies]
jsonrpc-core = "10.1.0"
jsonrpc-http-server = "10.1.0"
solana-logger = { path = "../logger", version = "0.15.2" }
solana-logger = { path = "../logger", version = "0.14.0" }

View File

@ -1,7 +1,6 @@
pub mod client_error;
mod generic_rpc_client_request;
pub mod mock_rpc_client_request;
pub mod perf_utils;
pub mod rpc_client;
pub mod rpc_client_request;
pub mod rpc_request;

View File

@ -2,7 +2,6 @@ use crate::client_error::ClientError;
use crate::generic_rpc_client_request::GenericRpcClientRequest;
use crate::rpc_request::RpcRequest;
use serde_json::{Number, Value};
use solana_sdk::fee_calculator::FeeCalculator;
use solana_sdk::transaction::{self, TransactionError};
pub const PUBKEY: &str = "7RoSF9fUmdphVCpabEoefH81WwrW7orsWonXWqTXkKV8";
@ -45,10 +44,7 @@ impl GenericRpcClientRequest for MockRpcClientRequest {
let n = if self.url == "airdrop" { 0 } else { 50 };
Value::Number(Number::from(n))
}
RpcRequest::GetRecentBlockhash => Value::Array(vec![
Value::String(PUBKEY.to_string()),
serde_json::to_value(FeeCalculator::default()).unwrap(),
]),
RpcRequest::GetRecentBlockhash => Value::String(PUBKEY.to_string()),
RpcRequest::GetSignatureStatus => {
let response: Option<transaction::Result<()>> = if self.url == "account_in_use" {
Some(Err(TransactionError::AccountInUse))

View File

@ -1,76 +0,0 @@
use log::*;
use solana_sdk::client::Client;
use solana_sdk::timing::duration_as_s;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, RwLock};
use std::thread::sleep;
use std::time::{Duration, Instant};
#[derive(Default)]
pub struct SampleStats {
/// Maximum TPS reported by this node
pub tps: f32,
/// Total time taken for those txs
pub elapsed: Duration,
/// Total transactions reported by this node
pub txs: u64,
}
pub fn sample_txs<T>(
exit_signal: &Arc<AtomicBool>,
sample_stats: &Arc<RwLock<Vec<(String, SampleStats)>>>,
sample_period: u64,
client: &Arc<T>,
) where
T: Client,
{
let mut max_tps = 0.0;
let mut total_elapsed;
let mut total_txs;
let mut now = Instant::now();
let start_time = now;
let initial_txs = client.get_transaction_count().expect("transaction count");
let mut last_txs = initial_txs;
loop {
total_elapsed = start_time.elapsed();
let elapsed = now.elapsed();
now = Instant::now();
let mut txs = client.get_transaction_count().expect("transaction count");
if txs < last_txs {
info!("Expected txs({}) >= last_txs({})", txs, last_txs);
txs = last_txs;
}
total_txs = txs - initial_txs;
let sample_txs = txs - last_txs;
last_txs = txs;
let tps = sample_txs as f32 / duration_as_s(&elapsed);
if tps > max_tps {
max_tps = tps;
}
info!(
"Sampler {:9.2} TPS, Transactions: {:6}, Total transactions: {} over {} s",
tps,
sample_txs,
total_txs,
total_elapsed.as_secs(),
);
if exit_signal.load(Ordering::Relaxed) {
let stats = SampleStats {
tps: max_tps,
elapsed: total_elapsed,
txs: total_txs,
};
sample_stats
.write()
.unwrap()
.push((client.transactions_addr(), stats));
return;
}
sleep(Duration::from_secs(sample_period));
}
}

View File

@ -4,14 +4,14 @@ use crate::mock_rpc_client_request::MockRpcClientRequest;
use crate::rpc_client_request::RpcClientRequest;
use crate::rpc_request::RpcRequest;
use bincode::serialize;
use bs58;
use log::*;
use serde_json::{json, Value};
use solana_sdk::account::Account;
use solana_sdk::fee_calculator::FeeCalculator;
use solana_sdk::hash::Hash;
use solana_sdk::pubkey::Pubkey;
use solana_sdk::signature::{KeypairUtil, Signature};
use solana_sdk::timing::{DEFAULT_NUM_TICKS_PER_SECOND, DEFAULT_TICKS_PER_SLOT};
use solana_sdk::signature::{Keypair, KeypairUtil, Signature};
use solana_sdk::timing::{DEFAULT_TICKS_PER_SLOT, NUM_TICKS_PER_SECOND};
use solana_sdk::transaction::{self, Transaction, TransactionError};
use std::error;
use std::io;
@ -78,7 +78,7 @@ impl RpcClient {
pub fn send_and_confirm_transaction<T: KeypairUtil>(
&self,
transaction: &mut Transaction,
signer_keys: &[&T],
signer: &T,
) -> Result<String, ClientError> {
let mut send_retries = 5;
loop {
@ -97,7 +97,7 @@ impl RpcClient {
if cfg!(not(test)) {
// Retry ~twice during a slot
sleep(Duration::from_millis(
500 * DEFAULT_TICKS_PER_SLOT / DEFAULT_NUM_TICKS_PER_SECOND,
500 * DEFAULT_TICKS_PER_SLOT / NUM_TICKS_PER_SECOND,
));
}
};
@ -106,7 +106,7 @@ impl RpcClient {
Ok(_) => return Ok(signature_str),
Err(TransactionError::AccountInUse) => {
// Fetch a new blockhash and re-sign the transaction before sending it again
self.resign_transaction(transaction, signer_keys)?;
self.resign_transaction(transaction, signer)?;
send_retries - 1
}
Err(_) => 0,
@ -127,10 +127,10 @@ impl RpcClient {
}
}
pub fn send_and_confirm_transactions<T: KeypairUtil>(
pub fn send_and_confirm_transactions(
&self,
mut transactions: Vec<Transaction>,
signer_keys: &[&T],
signer: &Keypair,
) -> Result<(), Box<dyn error::Error>> {
let mut send_retries = 5;
loop {
@ -143,7 +143,7 @@ impl RpcClient {
// Delay ~1 tick between write transactions in an attempt to reduce AccountInUse errors
// when all the write transactions modify the same program account (eg, deploying a
// new program)
sleep(Duration::from_millis(1000 / DEFAULT_NUM_TICKS_PER_SECOND));
sleep(Duration::from_millis(1000 / NUM_TICKS_PER_SECOND));
}
let signature = self.send_transaction(&transaction).ok();
@ -157,7 +157,7 @@ impl RpcClient {
if cfg!(not(test)) {
// Retry ~twice during a slot
sleep(Duration::from_millis(
500 * DEFAULT_TICKS_PER_SLOT / DEFAULT_NUM_TICKS_PER_SECOND,
500 * DEFAULT_TICKS_PER_SLOT / NUM_TICKS_PER_SECOND,
));
}
@ -187,12 +187,12 @@ impl RpcClient {
send_retries -= 1;
// Re-sign any failed transactions with a new blockhash and retry
let (blockhash, _fee_calculator) =
let blockhash =
self.get_new_blockhash(&transactions_signatures[0].0.message().recent_blockhash)?;
transactions = transactions_signatures
.into_iter()
.map(|(mut transaction, _)| {
transaction.sign(signer_keys, blockhash);
transaction.sign(&[signer], blockhash);
transaction
})
.collect();
@ -202,11 +202,10 @@ impl RpcClient {
pub fn resign_transaction<T: KeypairUtil>(
&self,
tx: &mut Transaction,
signer_keys: &[&T],
signer_key: &T,
) -> Result<(), ClientError> {
let (blockhash, _fee_calculator) =
self.get_new_blockhash(&tx.message().recent_blockhash)?;
tx.sign(signer_keys, blockhash);
let blockhash = self.get_new_blockhash(&tx.message().recent_blockhash)?;
tx.sign(&[signer_key], blockhash);
Ok(())
}
@ -223,7 +222,31 @@ impl RpcClient {
Ok(res)
}
pub fn get_account(&self, pubkey: &Pubkey) -> io::Result<Account> {
pub fn get_account_data(&self, pubkey: &Pubkey) -> io::Result<Vec<u8>> {
let params = json!([format!("{}", pubkey)]);
let response = self
.client
.send(&RpcRequest::GetAccountInfo, Some(params), 0);
match response {
Ok(account_json) => {
let account: Account =
serde_json::from_value(account_json).expect("deserialize account");
Ok(account.data)
}
Err(error) => {
debug!("get_account_data failed: {:?}", error);
Err(io::Error::new(
io::ErrorKind::Other,
"get_account_data failed",
))
}
}
}
/// Request the balance of the user holding `pubkey`. This method blocks
/// until the server sends a response. If the response packet is dropped
/// by the network, this method will hang indefinitely.
pub fn get_balance(&self, pubkey: &Pubkey) -> io::Result<u64> {
let params = json!([format!("{}", pubkey)]);
let response = self
.client
@ -234,89 +257,80 @@ impl RpcClient {
let account: Account =
serde_json::from_value(account_json).expect("deserialize account");
trace!("Response account {:?} {:?}", pubkey, account);
Ok(account)
trace!("get_balance {:?}", account.lamports);
Ok(account.lamports)
})
.map_err(|err| {
io::Error::new(
io::ErrorKind::Other,
format!("AccountNotFound: pubkey={}: {}", pubkey, err),
)
.map_err(|error| {
debug!("Response account {}: None (error: {:?})", pubkey, error);
io::Error::new(io::ErrorKind::Other, "AccountNotFound")
})
}
pub fn get_account_data(&self, pubkey: &Pubkey) -> io::Result<Vec<u8>> {
self.get_account(pubkey).map(|account| account.data)
}
/// Request the balance of the user holding `pubkey`. This method blocks
/// until the server sends a response. If the response packet is dropped
/// by the network, this method will hang indefinitely.
pub fn get_balance(&self, pubkey: &Pubkey) -> io::Result<u64> {
self.get_account(pubkey).map(|account| account.lamports)
}
/// Request the transaction count. If the response packet is dropped by the network,
/// this method will try again 5 times.
pub fn get_transaction_count(&self) -> io::Result<u64> {
let response = self
.client
.send(&RpcRequest::GetTransactionCount, None, 0)
.map_err(|err| {
io::Error::new(
io::ErrorKind::Other,
format!("GetTransactionCount request failure: {:?}", err),
)
})?;
debug!("get_transaction_count");
serde_json::from_value(response).map_err(|err| {
io::Error::new(
io::ErrorKind::Other,
format!("GetTransactionCount parse failure: {}", err),
)
})
let mut num_retries = 5;
while num_retries > 0 {
let response = self.client.send(&RpcRequest::GetTransactionCount, None, 0);
match response {
Ok(value) => {
debug!("transaction_count response: {:?}", value);
if let Some(transaction_count) = value.as_u64() {
return Ok(transaction_count);
}
}
Err(err) => {
debug!("transaction_count failed: {:?}", err);
}
}
num_retries -= 1;
}
Err(io::Error::new(
io::ErrorKind::Other,
"Unable to get transaction count, too many retries",
))?
}
pub fn get_recent_blockhash(&self) -> io::Result<(Hash, FeeCalculator)> {
let response = self
.client
.send(&RpcRequest::GetRecentBlockhash, None, 0)
.map_err(|err| {
io::Error::new(
io::ErrorKind::Other,
format!("GetRecentBlockhash request failure: {:?}", err),
)
})?;
let (blockhash, fee_calculator) =
serde_json::from_value::<(String, FeeCalculator)>(response).map_err(|err| {
io::Error::new(
io::ErrorKind::Other,
format!("GetRecentBlockhash parse failure: {:?}", err),
)
})?;
let blockhash = blockhash.parse().map_err(|err| {
io::Error::new(
io::ErrorKind::Other,
format!("GetRecentBlockhash parse failure: {:?}", err),
)
})?;
Ok((blockhash, fee_calculator))
pub fn get_recent_blockhash(&self) -> io::Result<Hash> {
let mut num_retries = 5;
while num_retries > 0 {
match self.client.send(&RpcRequest::GetRecentBlockhash, None, 0) {
Ok(value) => {
if let Some(blockhash_str) = value.as_str() {
let blockhash_vec = bs58::decode(blockhash_str)
.into_vec()
.expect("bs58::decode");
return Ok(Hash::new(&blockhash_vec));
}
}
Err(err) => {
debug!("retry_get_recent_blockhash failed: {:?}", err);
}
}
num_retries -= 1;
}
Err(io::Error::new(
io::ErrorKind::Other,
"Unable to get recent blockhash, too many retries",
))
}
pub fn get_new_blockhash(&self, blockhash: &Hash) -> io::Result<(Hash, FeeCalculator)> {
pub fn get_new_blockhash(&self, blockhash: &Hash) -> io::Result<Hash> {
let mut num_retries = 10;
while num_retries > 0 {
if let Ok((new_blockhash, fee_calculator)) = self.get_recent_blockhash() {
if let Ok(new_blockhash) = self.get_recent_blockhash() {
if new_blockhash != *blockhash {
return Ok((new_blockhash, fee_calculator));
return Ok(new_blockhash);
}
}
debug!("Got same blockhash ({:?}), will retry...", blockhash);
// Retry ~twice during a slot
sleep(Duration::from_millis(
500 * DEFAULT_TICKS_PER_SLOT / DEFAULT_NUM_TICKS_PER_SECOND,
500 * DEFAULT_TICKS_PER_SLOT / NUM_TICKS_PER_SECOND,
));
num_retries -= 1;
}
@ -468,22 +482,24 @@ impl RpcClient {
Some(params.clone()),
1,
)
.map_err(|err| {
.map_err(|error| {
debug!(
"Response get_num_blocks_since_signature_confirmation: {:?}",
error
);
io::Error::new(
io::ErrorKind::Other,
format!(
"GetNumBlocksSinceSignatureConfirmation request failure: {}",
err
),
"GetNumBlocksSinceSignatureConfirmation request failure",
)
})?;
serde_json::from_value(response).map_err(|err| {
serde_json::from_value(response).map_err(|error| {
debug!(
"ParseError: get_num_blocks_since_signature_confirmation: {}",
error
);
io::Error::new(
io::ErrorKind::Other,
format!(
"GetNumBlocksSinceSignatureConfirmation parse failure: {}",
err
),
"GetNumBlocksSinceSignatureConfirmation parse failure",
)
})
}
@ -590,7 +606,7 @@ mod tests {
// Send erroneous parameter
let blockhash = rpc_client.retry_make_rpc_request(
&RpcRequest::GetRecentBlockhash,
Some(json!("parameter")),
Some(json!("paramter")),
0,
);
assert_eq!(blockhash.is_err(), true);
@ -641,7 +657,7 @@ mod tests {
let key = Keypair::new();
let to = Pubkey::new_rand();
let blockhash = Hash::default();
let tx = system_transaction::create_user_account(&key, &to, 50, blockhash);
let tx = system_transaction::create_user_account(&key, &to, 50, blockhash, 0);
let signature = rpc_client.send_transaction(&tx);
assert_eq!(signature.unwrap(), SIGNATURE.to_string());
@ -655,14 +671,16 @@ mod tests {
fn test_get_recent_blockhash() {
let rpc_client = RpcClient::new_mock("succeeds".to_string());
let expected_blockhash: Hash = PUBKEY.parse().unwrap();
let vec = bs58::decode(PUBKEY).into_vec().unwrap();
let expected_blockhash = Hash::new(&vec);
let (blockhash, _fee_calculator) = rpc_client.get_recent_blockhash().expect("blockhash ok");
let blockhash = dbg!(rpc_client.get_recent_blockhash()).expect("blockhash ok");
assert_eq!(blockhash, expected_blockhash);
let rpc_client = RpcClient::new_mock("fails".to_string());
assert!(rpc_client.get_recent_blockhash().is_err());
let blockhash = dbg!(rpc_client.get_recent_blockhash());
assert!(blockhash.is_err());
}
#[test]
@ -690,17 +708,17 @@ mod tests {
let key = Keypair::new();
let to = Pubkey::new_rand();
let blockhash = Hash::default();
let mut tx = system_transaction::create_user_account(&key, &to, 50, blockhash);
let mut tx = system_transaction::create_user_account(&key, &to, 50, blockhash, 0);
let result = rpc_client.send_and_confirm_transaction(&mut tx, &[&key]);
let result = rpc_client.send_and_confirm_transaction(&mut tx, &key);
result.unwrap();
let rpc_client = RpcClient::new_mock("account_in_use".to_string());
let result = rpc_client.send_and_confirm_transaction(&mut tx, &[&key]);
let result = rpc_client.send_and_confirm_transaction(&mut tx, &key);
assert!(result.is_err());
let rpc_client = RpcClient::new_mock("fails".to_string());
let result = rpc_client.send_and_confirm_transaction(&mut tx, &[&key]);
let result = rpc_client.send_and_confirm_transaction(&mut tx, &key);
assert!(result.is_err());
}
@ -710,13 +728,14 @@ mod tests {
let key = Keypair::new();
let to = Pubkey::new_rand();
let blockhash: Hash = "HUu3LwEzGRsUkuJS121jzkPJW39Kq62pXCTmTa1F9jDL"
.parse()
let vec = bs58::decode("HUu3LwEzGRsUkuJS121jzkPJW39Kq62pXCTmTa1F9jDL")
.into_vec()
.unwrap();
let prev_tx = system_transaction::create_user_account(&key, &to, 50, blockhash);
let mut tx = system_transaction::create_user_account(&key, &to, 50, blockhash);
let blockhash = Hash::new(&vec);
let prev_tx = system_transaction::create_user_account(&key, &to, 50, blockhash, 0);
let mut tx = system_transaction::create_user_account(&key, &to, 50, blockhash, 0);
rpc_client.resign_transaction(&mut tx, &[&key]).unwrap();
rpc_client.resign_transaction(&mut tx, &key).unwrap();
assert_ne!(prev_tx, tx);
assert_ne!(prev_tx.signatures, tx.signatures);

View File

@ -4,7 +4,7 @@ use crate::rpc_request::{RpcError, RpcRequest};
use log::*;
use reqwest;
use reqwest::header::CONTENT_TYPE;
use solana_sdk::timing::{DEFAULT_NUM_TICKS_PER_SECOND, DEFAULT_TICKS_PER_SLOT};
use solana_sdk::timing::{DEFAULT_TICKS_PER_SLOT, NUM_TICKS_PER_SECOND};
use std::thread::sleep;
use std::time::Duration;
@ -73,7 +73,7 @@ impl GenericRpcClientRequest for RpcClientRequest {
// Sleep for approximately half a slot
sleep(Duration::from_millis(
500 * DEFAULT_TICKS_PER_SLOT / DEFAULT_NUM_TICKS_PER_SECOND,
500 * DEFAULT_TICKS_PER_SLOT / NUM_TICKS_PER_SECOND,
));
}
}

View File

@ -13,10 +13,9 @@ pub enum RpcRequest {
GetRecentBlockhash,
GetSignatureStatus,
GetSlotLeader,
GetEpochVoteAccounts,
GetStorageBlockhash,
GetStorageSlot,
GetStoragePubkeysForSlot,
GetStorageEntryHeight,
GetStoragePubkeysForEntryHeight,
GetTransactionCount,
RegisterNode,
RequestAirdrop,
@ -40,10 +39,9 @@ impl RpcRequest {
RpcRequest::GetRecentBlockhash => "getRecentBlockhash",
RpcRequest::GetSignatureStatus => "getSignatureStatus",
RpcRequest::GetSlotLeader => "getSlotLeader",
RpcRequest::GetEpochVoteAccounts => "getEpochVoteAccounts",
RpcRequest::GetStorageBlockhash => "getStorageBlockhash",
RpcRequest::GetStorageSlot => "getStorageSlot",
RpcRequest::GetStoragePubkeysForSlot => "getStoragePubkeysForSlot",
RpcRequest::GetStorageEntryHeight => "getStorageEntryHeight",
RpcRequest::GetStoragePubkeysForEntryHeight => "getStoragePubkeysForEntryHeight",
RpcRequest::GetTransactionCount => "getTransactionCount",
RpcRequest::RegisterNode => "registerNode",
RpcRequest::RequestAirdrop => "requestAirdrop",

View File

@ -7,7 +7,6 @@ use crate::rpc_client::RpcClient;
use bincode::{serialize_into, serialized_size};
use log::*;
use solana_sdk::client::{AsyncClient, Client, SyncClient};
use solana_sdk::fee_calculator::FeeCalculator;
use solana_sdk::hash::Hash;
use solana_sdk::instruction::Instruction;
use solana_sdk::message::Message;
@ -108,8 +107,7 @@ impl ThinClient {
return Ok(transaction.signatures[0]);
}
info!("{} tries failed transfer to {}", x, self.transactions_addr);
let (blockhash, _fee_calculator) = self.rpc_client.get_recent_blockhash()?;
transaction.sign(keypairs, blockhash);
transaction.sign(keypairs, self.rpc_client.get_recent_blockhash()?);
}
Err(io::Error::new(
io::ErrorKind::Other,
@ -117,6 +115,10 @@ impl ThinClient {
))
}
pub fn get_new_blockhash(&self, blockhash: &Hash) -> io::Result<Hash> {
self.rpc_client.get_new_blockhash(blockhash)
}
pub fn poll_balance_with_timeout(
&self,
pubkey: &Pubkey,
@ -161,7 +163,7 @@ impl Client for ThinClient {
impl SyncClient for ThinClient {
fn send_message(&self, keypairs: &[&Keypair], message: Message) -> TransportResult<Signature> {
let (blockhash, _fee_calculator) = self.get_recent_blockhash()?;
let blockhash = self.get_recent_blockhash()?;
let mut transaction = Transaction::new(&keypairs, message, blockhash);
let signature = self.send_and_confirm_transaction(keypairs, &mut transaction, 5, 0)?;
Ok(signature)
@ -212,8 +214,9 @@ impl SyncClient for ThinClient {
Ok(status)
}
fn get_recent_blockhash(&self) -> TransportResult<(Hash, FeeCalculator)> {
Ok(self.rpc_client.get_recent_blockhash()?)
fn get_recent_blockhash(&self) -> TransportResult<Hash> {
let recent_blockhash = self.rpc_client.get_recent_blockhash()?;
Ok(recent_blockhash)
}
fn get_transaction_count(&self) -> TransportResult<u64> {
@ -235,10 +238,6 @@ impl SyncClient for ThinClient {
fn poll_for_signature(&self, signature: &Signature) -> TransportResult<()> {
Ok(self.rpc_client.poll_for_signature(signature)?)
}
fn get_new_blockhash(&self, blockhash: &Hash) -> TransportResult<(Hash, FeeCalculator)> {
Ok(self.rpc_client.get_new_blockhash(blockhash)?)
}
}
impl AsyncClient for ThinClient {

1
core/.gitignore vendored
View File

@ -1 +0,0 @@
/target/

View File

@ -1,7 +1,7 @@
[package]
name = "solana"
description = "Blockchain, Rebuilt for Scale"
version = "0.15.2"
version = "0.14.0"
documentation = "https://docs.rs/solana"
homepage = "https://solana.com/"
readme = "../README.md"
@ -20,12 +20,12 @@ erasure = []
kvstore = ["solana-kvstore"]
[dependencies]
bincode = "1.1.4"
bincode = "1.1.2"
bs58 = "0.2.0"
byteorder = "1.3.1"
chrono = { version = "0.4.0", features = ["serde"] }
crc = { version = "1.8.1", optional = true }
core_affinity = "0.5.9"
ed25519-dalek = "1.0.0-pre.0"
hashbrown = "0.2.0"
indexmap = "1.0"
itertools = "0.8.0"
@ -34,48 +34,41 @@ jsonrpc-derive = "11.0.0"
jsonrpc-http-server = "11.0.0"
jsonrpc-pubsub = "11.0.0"
jsonrpc-ws-server = "11.0.0"
libc = "0.2.55"
libc = "0.2.50"
log = "0.4.2"
memmap = { version = "0.7.0", optional = true }
nix = "0.14.0"
nix = "0.13.0"
rand = "0.6.5"
rand_chacha = "0.1.1"
rayon = "1.0.0"
reed-solomon-erasure = "3.1.1"
reqwest = "0.9.17"
reqwest = "0.9.11"
rocksdb = "0.11.0"
serde = "1.0.89"
serde_derive = "1.0.91"
serde_derive = "1.0.88"
serde_json = "1.0.39"
solana-budget-api = { path = "../programs/budget_api", version = "0.15.2" }
solana-budget-program = { path = "../programs/budget_program", version = "0.15.2" }
solana-client = { path = "../client", version = "0.15.2" }
solana-drone = { path = "../drone", version = "0.15.2" }
solana-ed25519-dalek = "0.2.0"
solana-kvstore = { path = "../kvstore", version = "0.15.2" , optional = true }
solana-logger = { path = "../logger", version = "0.15.2" }
solana-metrics = { path = "../metrics", version = "0.15.2" }
solana-netutil = { path = "../netutil", version = "0.15.2" }
solana-runtime = { path = "../runtime", version = "0.15.2" }
solana-sdk = { path = "../sdk", version = "0.15.2" }
solana-stake-api = { path = "../programs/stake_api", version = "0.15.2" }
solana-stake-program = { path = "../programs/stake_program", version = "0.15.2" }
solana-storage-api = { path = "../programs/storage_api", version = "0.15.2" }
solana-storage-program = { path = "../programs/storage_program", version = "0.15.2" }
solana-vote-api = { path = "../programs/vote_api", version = "0.15.2" }
solana-vote-program = { path = "../programs/vote_program", version = "0.15.2" }
solana-exchange-program = { path = "../programs/exchange_program", version = "0.15.2" }
solana-config-program = { path = "../programs/config_program", version = "0.15.2" }
solana-vote-signer = { path = "../vote-signer", version = "0.15.2" }
solana-budget-api = { path = "../programs/budget_api", version = "0.14.0" }
solana-client = { path = "../client", version = "0.14.0" }
solana-drone = { path = "../drone", version = "0.14.0" }
solana-kvstore = { path = "../kvstore", version = "0.14.0" , optional = true }
solana-logger = { path = "../logger", version = "0.14.0" }
solana-metrics = { path = "../metrics", version = "0.14.0" }
solana-netutil = { path = "../netutil", version = "0.14.0" }
solana-runtime = { path = "../runtime", version = "0.14.0" }
solana-sdk = { path = "../sdk", version = "0.14.0" }
solana-storage-api = { path = "../programs/storage_api", version = "0.14.0" }
solana-vote-api = { path = "../programs/vote_api", version = "0.14.0" }
solana-vote-signer = { path = "../vote-signer", version = "0.14.0" }
sys-info = "0.5.6"
tokio = "0.1"
tokio-codec = "0.1"
untrusted = "0.6.2"
[dev-dependencies]
hex-literal = "0.2.0"
hex-literal = "0.1.4"
matches = "0.1.6"
solana-vote-program = { path = "../programs/vote_program", version = "0.14.0" }
solana-budget-program = { path = "../programs/budget_program", version = "0.14.0" }
[[bench]]
name = "banking_stage"
@ -92,12 +85,6 @@ name = "gen_keys"
[[bench]]
name = "sigverify"
[[bench]]
name = "sigverify_stage"
[[bench]]
name = "poh"
[[bench]]
required-features = ["chacha"]
name = "chacha"

View File

@ -4,34 +4,31 @@ extern crate test;
#[macro_use]
extern crate solana;
use log::*;
use rand::{thread_rng, Rng};
use rayon::prelude::*;
use solana::banking_stage::{create_test_recorder, BankingStage};
use solana::blocktree::{get_tmp_ledger_path, Blocktree};
use solana::cluster_info::ClusterInfo;
use solana::cluster_info::Node;
use solana::genesis_utils::{create_genesis_block, GenesisBlockInfo};
use solana::leader_schedule_cache::LeaderScheduleCache;
use solana::packet::to_packets_chunked;
use solana::poh_recorder::WorkingBankEntries;
use solana::service::Service;
use solana::test_tx::test_tx;
use solana_runtime::bank::Bank;
use solana_sdk::genesis_block::GenesisBlock;
use solana_sdk::hash::hash;
use solana_sdk::pubkey::Pubkey;
use solana_sdk::signature::Signature;
use solana_sdk::signature::{KeypairUtil, Signature};
use solana_sdk::system_transaction;
use solana_sdk::timing::{
duration_as_ms, timestamp, DEFAULT_TICKS_PER_SLOT, MAX_RECENT_BLOCKHASHES,
};
use solana_sdk::timing::{DEFAULT_TICKS_PER_SLOT, MAX_RECENT_BLOCKHASHES};
use std::iter;
use std::sync::atomic::Ordering;
use std::sync::mpsc::{channel, Receiver};
use std::sync::{Arc, RwLock};
use std::time::{Duration, Instant};
use std::time::Duration;
use test::Bencher;
fn check_txs(receiver: &Arc<Receiver<WorkingBankEntries>>, ref_tx_count: usize) {
fn check_txs(receiver: &Receiver<WorkingBankEntries>, ref_tx_count: usize) {
let mut total = 0;
loop {
let entries = receiver.recv_timeout(Duration::new(1, 0));
@ -50,63 +47,25 @@ fn check_txs(receiver: &Arc<Receiver<WorkingBankEntries>>, ref_tx_count: usize)
}
#[bench]
fn bench_consume_buffered(bencher: &mut Bencher) {
let GenesisBlockInfo { genesis_block, .. } = create_genesis_block(100_000);
let bank = Arc::new(Bank::new(&genesis_block));
let ledger_path = get_tmp_ledger_path!();
let my_pubkey = Pubkey::new_rand();
{
let blocktree = Arc::new(
Blocktree::open(&ledger_path).expect("Expected to be able to open database ledger"),
);
let (exit, poh_recorder, poh_service, _signal_receiver) =
create_test_recorder(&bank, &blocktree);
let tx = test_tx();
let len = 4096;
let chunk_size = 1024;
let batches = to_packets_chunked(&vec![tx; len], chunk_size);
let mut packets = vec![];
for batch in batches {
let batch_len = batch.packets.len();
packets.push((batch, vec![0usize; batch_len]));
}
// This tests the performance of buffering packets.
// If the packet buffers are copied, performance will be poor.
bencher.iter(move || {
let _ignored =
BankingStage::consume_buffered_packets(&my_pubkey, &poh_recorder, &mut packets);
});
exit.store(true, Ordering::Relaxed);
poh_service.join().unwrap();
}
let _unused = Blocktree::destroy(&ledger_path);
}
#[bench]
#[ignore]
fn bench_banking_stage_multi_accounts(bencher: &mut Bencher) {
solana_logger::setup();
let num_threads = BankingStage::num_threads() as usize;
// a multiple of packet chunk 2X duplicates to avoid races
let txes = 192 * num_threads * 2;
let txes = 192 * 50 * num_threads * 2;
let mint_total = 1_000_000_000_000;
let GenesisBlockInfo {
mut genesis_block,
mint_keypair,
..
} = create_genesis_block(mint_total);
// Set a high ticks_per_slot so we don't run out of ticks
// during the benchmark
genesis_block.ticks_per_slot = 10_000;
let (genesis_block, mint_keypair) = GenesisBlock::new(mint_total);
let (verified_sender, verified_receiver) = channel();
let (vote_sender, vote_receiver) = channel();
let bank = Arc::new(Bank::new(&genesis_block));
let to_pubkey = Pubkey::new_rand();
let dummy = system_transaction::transfer(&mint_keypair, &to_pubkey, 1, genesis_block.hash());
trace!("txs: {}", txes);
let leader_schedule_cache = Arc::new(LeaderScheduleCache::new_from_bank(&bank));
let dummy = system_transaction::transfer(
&mint_keypair,
&mint_keypair.pubkey(),
1,
genesis_block.hash(),
0,
);
let transactions: Vec<_> = (0..txes)
.into_par_iter()
.map(|_| {
@ -127,6 +86,7 @@ fn bench_banking_stage_multi_accounts(bencher: &mut Bencher) {
&tx.message.account_keys[0],
mint_total / txes as u64,
genesis_block.hash(),
0,
);
let x = bank.process_transaction(&fund);
x.unwrap();
@ -164,29 +124,25 @@ fn bench_banking_stage_multi_accounts(bencher: &mut Bencher) {
&poh_recorder,
verified_receiver,
vote_receiver,
&leader_schedule_cache,
);
poh_recorder.lock().unwrap().set_bank(&bank);
let mut id = genesis_block.hash();
for _ in 0..(MAX_RECENT_BLOCKHASHES * DEFAULT_TICKS_PER_SLOT as usize) {
id = hash(&id.as_ref());
bank.register_tick(&id);
}
let half_len = verified.len() / 2;
let mut start = 0;
// This is so that the signal_receiver does not go out of scope after the closure.
// If it is dropped before poh_service, then poh_service will error when
// calling send() on the channel.
let signal_receiver = Arc::new(signal_receiver);
let signal_receiver2 = signal_receiver.clone();
bencher.iter(move || {
let now = Instant::now();
// make sure the transactions are still valid
bank.register_tick(&genesis_block.hash());
for v in verified[start..start + half_len].chunks(verified.len() / num_threads) {
trace!("sending... {}..{} {}", start, start + half_len, timestamp());
verified_sender.send(v.to_vec()).unwrap();
}
check_txs(&signal_receiver2, txes / 2);
trace!(
"time: {} checked: {}",
duration_as_ms(&now.elapsed()),
txes / 2
);
check_txs(&signal_receiver, txes / 2);
bank.clear_signatures();
start += half_len;
start %= verified.len();
@ -195,7 +151,7 @@ fn bench_banking_stage_multi_accounts(bencher: &mut Bencher) {
exit.store(true, Ordering::Relaxed);
poh_service.join().unwrap();
}
let _unused = Blocktree::destroy(&ledger_path);
Blocktree::destroy(&ledger_path).unwrap();
}
#[bench]
@ -206,17 +162,19 @@ fn bench_banking_stage_multi_programs(bencher: &mut Bencher) {
// a multiple of packet chunk 2X duplicates to avoid races
let txes = 96 * 100 * num_threads * 2;
let mint_total = 1_000_000_000_000;
let GenesisBlockInfo {
genesis_block,
mint_keypair,
..
} = create_genesis_block(mint_total);
let (genesis_block, mint_keypair) = GenesisBlock::new(mint_total);
let (verified_sender, verified_receiver) = channel();
let (vote_sender, vote_receiver) = channel();
let bank = Arc::new(Bank::new(&genesis_block));
let to_pubkey = Pubkey::new_rand();
let dummy = system_transaction::transfer(&mint_keypair, &to_pubkey, 1, genesis_block.hash());
let leader_schedule_cache = Arc::new(LeaderScheduleCache::new_from_bank(&bank));
let dummy = system_transaction::transfer(
&mint_keypair,
&mint_keypair.pubkey(),
1,
genesis_block.hash(),
0,
);
let transactions: Vec<_> = (0..txes)
.into_par_iter()
.map(|_| {
@ -253,6 +211,7 @@ fn bench_banking_stage_multi_programs(bencher: &mut Bencher) {
&tx.message.account_keys[0],
mint_total / txes as u64,
genesis_block.hash(),
0,
);
bank.process_transaction(&fund).unwrap();
});
@ -290,6 +249,7 @@ fn bench_banking_stage_multi_programs(bencher: &mut Bencher) {
&poh_recorder,
verified_receiver,
vote_receiver,
&leader_schedule_cache,
);
poh_recorder.lock().unwrap().set_bank(&bank);
@ -301,15 +261,13 @@ fn bench_banking_stage_multi_programs(bencher: &mut Bencher) {
let half_len = verified.len() / 2;
let mut start = 0;
let signal_receiver = Arc::new(signal_receiver);
let signal_receiver2 = signal_receiver.clone();
bencher.iter(move || {
// make sure the transactions are still valid
bank.register_tick(&genesis_block.hash());
for v in verified[start..start + half_len].chunks(verified.len() / num_threads) {
verified_sender.send(v.to_vec()).unwrap();
}
check_txs(&signal_receiver2, txes / 2);
check_txs(&signal_receiver, txes / 2);
bank.clear_signatures();
start += half_len;
start %= verified.len();

View File

@ -13,7 +13,7 @@ fn bench_block_to_blobs_to_block(bencher: &mut Bencher) {
let zero = Hash::default();
let one = hash(&zero.as_ref());
let keypair = Keypair::new();
let tx0 = system_transaction::transfer(&keypair, &keypair.pubkey(), 1, one);
let tx0 = system_transaction::transfer(&keypair, &keypair.pubkey(), 1, one, 0);
let transactions = vec![tx0; 10];
let entries = next_entries(&zero, 1, transactions);

View File

@ -1,63 +0,0 @@
// This bench attempts to justify the value of `solana::poh_service::NUM_HASHES_PER_BATCH`
#![feature(test)]
extern crate test;
use solana::poh::Poh;
use solana::poh_service::NUM_HASHES_PER_BATCH;
use solana_sdk::hash::Hash;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex};
use test::Bencher;
const NUM_HASHES: u64 = 30_000; // Should require ~10ms on a 2017 MacBook Pro
#[bench]
// No locking. Fastest.
fn bench_poh_hash(bencher: &mut Bencher) {
let mut poh = Poh::new(Hash::default(), None);
bencher.iter(|| {
poh.hash(NUM_HASHES);
})
}
#[bench]
// Lock on each iteration. Slowest.
fn bench_arc_mutex_poh_hash(bencher: &mut Bencher) {
let poh = Arc::new(Mutex::new(Poh::new(Hash::default(), None)));
bencher.iter(|| {
for _ in 0..NUM_HASHES {
poh.lock().unwrap().hash(1);
}
})
}
#[bench]
// Acquire lock every NUM_HASHES_PER_BATCH iterations.
// Speed should be close to bench_poh_hash() if NUM_HASHES_PER_BATCH is set well.
fn bench_arc_mutex_poh_batched_hash(bencher: &mut Bencher) {
let poh = Arc::new(Mutex::new(Poh::new(Hash::default(), Some(NUM_HASHES))));
//let exit = Arc::new(AtomicBool::new(false));
let exit = Arc::new(AtomicBool::new(true));
bencher.iter(|| {
// NOTE: This block attempts to look as close as possible to `PohService::tick_producer()`
loop {
if poh.lock().unwrap().hash(NUM_HASHES_PER_BATCH) {
poh.lock().unwrap().tick().unwrap();
if exit.load(Ordering::Relaxed) {
break;
}
}
}
})
}
#[bench]
// Worst case transaction record delay due to batch hashing at NUM_HASHES_PER_BATCH
fn bench_poh_lock_time_per_batch(bencher: &mut Bencher) {
let mut poh = Poh::new(Hash::default(), None);
bencher.iter(|| {
poh.hash(NUM_HASHES_PER_BATCH);
})
}

View File

@ -1,83 +0,0 @@
#![feature(test)]
extern crate solana;
extern crate test;
use log::*;
use rand::{thread_rng, Rng};
use solana::packet::to_packets_chunked;
use solana::service::Service;
use solana::sigverify_stage::SigVerifyStage;
use solana::test_tx::test_tx;
use solana_sdk::hash::Hash;
use solana_sdk::signature::{Keypair, KeypairUtil};
use solana_sdk::system_transaction;
use solana_sdk::timing::duration_as_ms;
use std::sync::mpsc::channel;
use std::time::{Duration, Instant};
use test::Bencher;
#[bench]
fn bench_sigverify_stage(bencher: &mut Bencher) {
solana_logger::setup();
let (packet_s, packet_r) = channel();
let (verified_s, verified_r) = channel();
let sigverify_disabled = false;
let stage = SigVerifyStage::new(packet_r, sigverify_disabled, verified_s);
let now = Instant::now();
let len = 4096;
let use_same_tx = true;
let chunk_size = 1024;
let mut batches = if use_same_tx {
let tx = test_tx();
to_packets_chunked(&vec![tx; len], chunk_size)
} else {
let from_keypair = Keypair::new();
let to_keypair = Keypair::new();
let txs: Vec<_> = (0..len)
.into_iter()
.map(|_| {
let amount = thread_rng().gen();
let tx = system_transaction::transfer(
&from_keypair,
&to_keypair.pubkey(),
amount,
Hash::default(),
);
tx
})
.collect();
to_packets_chunked(&txs, chunk_size)
};
trace!(
"starting... generation took: {} ms batches: {}",
duration_as_ms(&now.elapsed()),
batches.len()
);
bencher.iter(move || {
let mut sent_len = 0;
for _ in 0..batches.len() {
if let Some(batch) = batches.pop() {
sent_len += batch.packets.len();
packet_s.send(batch).unwrap();
}
}
let mut received = 0;
trace!("sent: {}", sent_len);
loop {
if let Ok(mut verifieds) = verified_r.recv_timeout(Duration::from_millis(10)) {
while let Some(v) = verifieds.pop() {
received += v.0.packets.len();
batches.push(v.0);
}
if received >= sent_len {
break;
}
}
}
trace!("received: {}", received);
});
stage.join().unwrap();
}

View File

@ -1,7 +1,7 @@
//! The `bank_forks` module implments BankForks a DAG of checkpointed Banks
use hashbrown::{HashMap, HashSet};
use solana_metrics::inc_new_counter_info;
use solana_metrics::counter::Counter;
use solana_runtime::bank::Bank;
use solana_sdk::timing;
use std::ops::Index;
@ -11,7 +11,6 @@ use std::time::Instant;
pub struct BankForks {
banks: HashMap<u64, Arc<Bank>>,
working_bank: Arc<Bank>,
root: u64,
}
impl Index<u64> for BankForks {
@ -29,7 +28,6 @@ impl BankForks {
Self {
banks,
working_bank,
root: 0,
}
}
@ -37,8 +35,7 @@ impl BankForks {
pub fn ancestors(&self) -> HashMap<u64, HashSet<u64>> {
let mut ancestors = HashMap::new();
for bank in self.banks.values() {
let mut set: HashSet<u64> = bank.ancestors.keys().cloned().collect();
set.remove(&bank.slot());
let set = bank.parents().into_iter().map(|b| b.slot()).collect();
ancestors.insert(bank.slot(), set);
}
ancestors
@ -49,11 +46,9 @@ impl BankForks {
let mut descendants = HashMap::new();
for bank in self.banks.values() {
let _ = descendants.entry(bank.slot()).or_insert(HashSet::new());
let mut set: HashSet<u64> = bank.ancestors.keys().cloned().collect();
set.remove(&bank.slot());
for parent in set {
for parent in bank.parents() {
descendants
.entry(parent)
.entry(parent.slot())
.or_insert(HashSet::new())
.insert(bank.slot());
}
@ -81,14 +76,13 @@ impl BankForks {
self.banks.get(&bank_slot)
}
pub fn new_from_banks(initial_banks: &[Arc<Bank>], root: u64) -> Self {
pub fn new_from_banks(initial_banks: &[Arc<Bank>]) -> Self {
let mut banks = HashMap::new();
let working_bank = initial_banks[0].clone();
for bank in initial_banks {
banks.insert(bank.slot(), bank.clone());
}
Self {
root,
banks,
working_bank,
}
@ -108,52 +102,35 @@ impl BankForks {
}
pub fn set_root(&mut self, root: u64) {
self.root = root;
let set_root_start = Instant::now();
let root_bank = self
.banks
.get(&root)
.expect("root bank didn't exist in bank_forks");
let root_tx_count = root_bank
.parents()
.last()
.map(|bank| bank.transaction_count())
.unwrap_or(0);
root_bank.squash();
let new_tx_count = root_bank.transaction_count();
self.prune_non_root(root);
inc_new_counter_info!(
"bank-forks_set_root_ms",
timing::duration_as_ms(&set_root_start.elapsed()) as usize
);
inc_new_counter_info!(
"bank-forks_set_root_tx_count",
(new_tx_count - root_tx_count) as usize
);
}
pub fn root(&self) -> u64 {
self.root
}
fn prune_non_root(&mut self, root: u64) {
let descendants = self.descendants();
self.banks
.retain(|slot, _| descendants[&root].contains(slot))
.retain(|slot, bank| *slot >= root || bank.is_in_subtree_of(root))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::genesis_utils::{create_genesis_block, GenesisBlockInfo};
use solana_sdk::genesis_block::GenesisBlock;
use solana_sdk::hash::Hash;
use solana_sdk::pubkey::Pubkey;
#[test]
fn test_bank_forks() {
let GenesisBlockInfo { genesis_block, .. } = create_genesis_block(10_000);
let (genesis_block, _) = GenesisBlock::new(10_000);
let bank = Bank::new(&genesis_block);
let mut bank_forks = BankForks::new(0, bank);
let child_bank = Bank::new_from_parent(&bank_forks[0u64], &Pubkey::default(), 1);
@ -165,7 +142,7 @@ mod tests {
#[test]
fn test_bank_forks_descendants() {
let GenesisBlockInfo { genesis_block, .. } = create_genesis_block(10_000);
let (genesis_block, _) = GenesisBlock::new(10_000);
let bank = Bank::new(&genesis_block);
let mut bank_forks = BankForks::new(0, bank);
let bank0 = bank_forks[0].clone();
@ -182,7 +159,7 @@ mod tests {
#[test]
fn test_bank_forks_ancestors() {
let GenesisBlockInfo { genesis_block, .. } = create_genesis_block(10_000);
let (genesis_block, _) = GenesisBlock::new(10_000);
let bank = Bank::new(&genesis_block);
let mut bank_forks = BankForks::new(0, bank);
let bank0 = bank_forks[0].clone();
@ -200,7 +177,7 @@ mod tests {
#[test]
fn test_bank_forks_frozen_banks() {
let GenesisBlockInfo { genesis_block, .. } = create_genesis_block(10_000);
let (genesis_block, _) = GenesisBlock::new(10_000);
let bank = Bank::new(&genesis_block);
let mut bank_forks = BankForks::new(0, bank);
let child_bank = Bank::new_from_parent(&bank_forks[0u64], &Pubkey::default(), 1);
@ -211,7 +188,7 @@ mod tests {
#[test]
fn test_bank_forks_active_banks() {
let GenesisBlockInfo { genesis_block, .. } = create_genesis_block(10_000);
let (genesis_block, _) = GenesisBlock::new(10_000);
let bank = Bank::new(&genesis_block);
let mut bank_forks = BankForks::new(0, bank);
let child_bank = Bank::new_from_parent(&bank_forks[0u64], &Pubkey::default(), 1);

File diff suppressed because it is too large Load Diff

View File

@ -65,14 +65,14 @@ pub trait BlockstreamEvents {
&self,
slot: u64,
tick_height: u64,
leader_pubkey: &Pubkey,
leader_id: &Pubkey,
entries: &Entry,
) -> Result<()>;
fn emit_block_event(
&self,
slot: u64,
tick_height: u64,
leader_pubkey: &Pubkey,
leader_id: &Pubkey,
blockhash: Hash,
) -> Result<()>;
}
@ -90,7 +90,7 @@ where
&self,
slot: u64,
tick_height: u64,
leader_pubkey: &Pubkey,
leader_id: &Pubkey,
entry: &Entry,
) -> Result<()> {
let transactions: Vec<Vec<u8>> = serialize_transactions(entry);
@ -105,7 +105,7 @@ where
Utc::now().to_rfc3339_opts(SecondsFormat::Nanos, true),
slot,
tick_height,
leader_pubkey,
leader_id,
json_entry,
);
self.output.write(payload)?;
@ -116,7 +116,7 @@ where
&self,
slot: u64,
tick_height: u64,
leader_pubkey: &Pubkey,
leader_id: &Pubkey,
blockhash: Hash,
) -> Result<()> {
let payload = format!(
@ -124,7 +124,7 @@ where
Utc::now().to_rfc3339_opts(SecondsFormat::Nanos, true),
slot,
tick_height,
leader_pubkey,
leader_id,
blockhash,
);
self.output.write(payload)?;
@ -183,8 +183,10 @@ mod test {
let keypair0 = Keypair::new();
let keypair1 = Keypair::new();
let tx0 = system_transaction::transfer(&keypair0, &keypair1.pubkey(), 1, Hash::default());
let tx1 = system_transaction::transfer(&keypair1, &keypair0.pubkey(), 2, Hash::default());
let tx0 =
system_transaction::transfer(&keypair0, &keypair1.pubkey(), 1, Hash::default(), 0);
let tx1 =
system_transaction::transfer(&keypair1, &keypair0.pubkey(), 2, Hash::default(), 0);
let serialized_tx0 = serialize(&tx0).unwrap();
let serialized_tx1 = serialize(&tx1).unwrap();
let entry = Entry::new(&Hash::default(), 1, vec![tx0, tx1]);
@ -206,19 +208,19 @@ mod test {
let tick_height_initial = 0;
let tick_height_final = tick_height_initial + ticks_per_slot + 2;
let mut curr_slot = 0;
let leader_pubkey = Pubkey::new_rand();
let leader_id = Pubkey::new_rand();
for tick_height in tick_height_initial..=tick_height_final {
if tick_height == 5 {
blockstream
.emit_block_event(curr_slot, tick_height - 1, &leader_pubkey, blockhash)
.emit_block_event(curr_slot, tick_height - 1, &leader_id, blockhash)
.unwrap();
curr_slot += 1;
}
let entry = Entry::new(&mut blockhash, 1, vec![]); // just ticks
blockhash = entry.hash;
blockstream
.emit_entry_event(curr_slot, tick_height, &leader_pubkey, &entry)
.emit_entry_event(curr_slot, tick_height, &leader_id, &entry)
.unwrap();
expected_entries.push(entry.clone());
entries.push(entry);

View File

@ -109,10 +109,10 @@ mod test {
use super::*;
use crate::blocktree::create_new_tmp_ledger;
use crate::entry::{create_ticks, Entry};
use crate::genesis_utils::{create_genesis_block, GenesisBlockInfo};
use bincode::{deserialize, serialize};
use chrono::{DateTime, FixedOffset};
use serde_json::Value;
use solana_sdk::genesis_block::GenesisBlock;
use solana_sdk::hash::Hash;
use solana_sdk::signature::{Keypair, KeypairUtil};
use solana_sdk::system_transaction;
@ -121,12 +121,10 @@ mod test {
#[test]
fn test_blockstream_service_process_entries() {
let ticks_per_slot = 5;
let leader_pubkey = Pubkey::new_rand();
let leader_id = Pubkey::new_rand();
// Set up genesis block and blocktree
let GenesisBlockInfo {
mut genesis_block, ..
} = create_genesis_block(1000);
let (mut genesis_block, _mint_keypair) = GenesisBlock::new(1000);
genesis_block.ticks_per_slot = ticks_per_slot;
let (ledger_path, _blockhash) = create_new_tmp_ledger!(&genesis_block);
@ -148,6 +146,7 @@ mod test {
&keypair.pubkey(),
1,
Hash::default(),
0,
);
let entry = Entry::new(&mut blockhash, 1, vec![tx]);
blockhash = entry.hash;
@ -162,7 +161,7 @@ mod test {
.write_entries(1, 0, 0, ticks_per_slot, &entries)
.unwrap();
slot_full_sender.send((1, leader_pubkey)).unwrap();
slot_full_sender.send((1, leader_id)).unwrap();
BlockstreamService::process_entries(
&slot_full_receiver,
&Arc::new(blocktree),

File diff suppressed because it is too large Load Diff

View File

@ -9,7 +9,6 @@ use std::borrow::Borrow;
use std::collections::HashMap;
use std::marker::PhantomData;
use std::path::Path;
use std::sync::Arc;
pub mod columns {
#[derive(Debug)]
@ -31,10 +30,6 @@ pub mod columns {
#[derive(Debug)]
/// The erasure meta column
pub struct ErasureMeta;
#[derive(Debug)]
/// The root column
pub struct Root;
}
pub trait Backend: Sized + Send + Sync {
@ -60,7 +55,7 @@ pub trait Backend: Sized + Send + Sync {
fn delete_cf(&self, cf: Self::ColumnFamily, key: &Self::Key) -> Result<()>;
fn iterator_cf(&self, cf: Self::ColumnFamily, from: Option<&Self::Key>) -> Result<Self::Iter>;
fn iterator_cf(&self, cf: Self::ColumnFamily) -> Result<Self::Iter>;
fn raw_iterator_cf(&self, cf: Self::ColumnFamily) -> Result<Self::Cursor>;
@ -117,15 +112,7 @@ pub struct Database<B>
where
B: Backend,
{
backend: Arc<B>,
}
#[derive(Debug, Clone)]
pub struct BatchProcessor<B>
where
B: Backend,
{
backend: Arc<B>,
backend: B,
}
#[derive(Debug, Clone)]
@ -145,7 +132,7 @@ where
B: Backend,
C: Column<B>,
{
backend: Arc<B>,
backend: PhantomData<B>,
column: PhantomData<C>,
}
@ -164,7 +151,7 @@ where
B: Backend,
{
pub fn open(path: &Path) -> Result<Self> {
let backend = Arc::new(B::open(path)?);
let backend = B::open(path)?;
Ok(Database { backend })
}
@ -183,7 +170,7 @@ where
.get_cf(self.cf_handle::<C>(), C::key(key).borrow())
}
pub fn put_bytes<C>(&self, key: C::Index, data: &[u8]) -> Result<()>
pub fn put_bytes<C>(&mut self, key: C::Index, data: &[u8]) -> Result<()>
where
C: Column<B>,
{
@ -191,7 +178,7 @@ where
.put_cf(self.cf_handle::<C>(), C::key(key).borrow(), data)
}
pub fn delete<C>(&self, key: C::Index) -> Result<()>
pub fn delete<C>(&mut self, key: C::Index) -> Result<()>
where
C: Column<B>,
{
@ -215,7 +202,7 @@ where
}
}
pub fn put<C>(&self, key: C::Index, value: &C::Type) -> Result<()>
pub fn put<C>(&mut self, key: C::Index, value: &C::Type) -> Result<()>
where
C: TypedColumn<B>,
{
@ -241,60 +228,18 @@ where
})
}
pub fn iter<C>(
&self,
start_from: Option<C::Index>,
) -> Result<impl Iterator<Item = (C::Index, Box<[u8]>)>>
pub fn iter<C>(&self) -> Result<impl Iterator<Item = (C::Index, Vec<u8>)>>
where
C: Column<B>,
{
let iter = {
if let Some(index) = start_from {
let key = C::key(index);
self.backend
.iterator_cf(self.cf_handle::<C>(), Some(key.borrow()))?
} else {
self.backend.iterator_cf(self.cf_handle::<C>(), None)?
}
};
let iter = self
.backend
.iterator_cf(self.cf_handle::<C>())?
.map(|(key, value)| (C::index(&key), value.into()));
Ok(iter.map(|(key, value)| (C::index(&key), value)))
Ok(iter)
}
#[inline]
pub fn cf_handle<C>(&self) -> B::ColumnFamily
where
C: Column<B>,
{
self.backend.cf_handle(C::NAME).clone()
}
pub fn column<C>(&self) -> LedgerColumn<B, C>
where
C: Column<B>,
{
LedgerColumn {
backend: Arc::clone(&self.backend),
column: PhantomData,
}
}
// Note this returns an object that can be used to directly write to multiple column families.
// This circumvents the synchronization around APIs that in Blocktree that use
// blocktree.batch_processor, so this API should only be used if the caller is sure they
// are writing to data in columns that will not be corrupted by any simultaneous blocktree
// operations.
pub unsafe fn batch_processor(&self) -> BatchProcessor<B> {
BatchProcessor {
backend: Arc::clone(&self.backend),
}
}
}
impl<B> BatchProcessor<B>
where
B: Backend,
{
pub fn batch(&mut self) -> Result<WriteBatch<B>> {
let db_write_batch = self.backend.batch()?;
let map = self
@ -314,6 +259,24 @@ where
pub fn write(&mut self, batch: WriteBatch<B>) -> Result<()> {
self.backend.write(batch.write_batch)
}
#[inline]
pub fn cf_handle<C>(&self) -> B::ColumnFamily
where
C: Column<B>,
{
self.backend.cf_handle(C::NAME).clone()
}
pub fn column<C>(&self) -> LedgerColumn<B, C>
where
C: Column<B>,
{
LedgerColumn {
backend: PhantomData,
column: PhantomData,
}
}
}
impl<B, C> Cursor<B, C>
@ -370,55 +333,41 @@ where
B: Backend,
C: Column<B>,
{
pub fn get_bytes(&self, key: C::Index) -> Result<Option<Vec<u8>>> {
self.backend.get_cf(self.handle(), C::key(key).borrow())
pub fn get_bytes(&self, db: &Database<B>, key: C::Index) -> Result<Option<Vec<u8>>> {
db.backend.get_cf(self.handle(db), C::key(key).borrow())
}
pub fn cursor(&self) -> Result<Cursor<B, C>> {
let db_cursor = self.backend.raw_iterator_cf(self.handle())?;
Ok(Cursor {
db_cursor,
column: PhantomData,
backend: PhantomData,
})
pub fn cursor(&self, db: &Database<B>) -> Result<Cursor<B, C>> {
db.cursor()
}
pub fn iter(
&self,
start_from: Option<C::Index>,
) -> Result<impl Iterator<Item = (C::Index, Box<[u8]>)>> {
let iter = {
if let Some(index) = start_from {
let key = C::key(index);
self.backend
.iterator_cf(self.handle(), Some(key.borrow()))?
} else {
self.backend.iterator_cf(self.handle(), None)?
}
};
Ok(iter.map(|(key, value)| (C::index(&key), value)))
pub fn iter(&self, db: &Database<B>) -> Result<impl Iterator<Item = (C::Index, Vec<u8>)>> {
db.iter::<C>()
}
#[inline]
pub fn handle(&self) -> B::ColumnFamily {
self.backend.cf_handle(C::NAME).clone()
pub fn handle(&self, db: &Database<B>) -> B::ColumnFamily {
db.cf_handle::<C>()
}
pub fn is_empty(&self) -> Result<bool> {
let mut cursor = self.cursor()?;
pub fn is_empty(&self, db: &Database<B>) -> Result<bool> {
let mut cursor = self.cursor(db)?;
cursor.seek_to_first();
Ok(!cursor.valid())
}
}
pub fn put_bytes(&self, key: C::Index, value: &[u8]) -> Result<()> {
self.backend
.put_cf(self.handle(), C::key(key).borrow(), value)
impl<B, C> LedgerColumn<B, C>
where
B: Backend,
C: Column<B>,
{
pub fn put_bytes(&self, db: &mut Database<B>, key: C::Index, value: &[u8]) -> Result<()> {
db.backend
.put_cf(self.handle(db), C::key(key).borrow(), value)
}
pub fn delete(&self, key: C::Index) -> Result<()> {
self.backend.delete_cf(self.handle(), C::key(key).borrow())
pub fn delete(&self, db: &mut Database<B>, key: C::Index) -> Result<()> {
db.backend.delete_cf(self.handle(db), C::key(key).borrow())
}
}
@ -427,21 +376,18 @@ where
B: Backend,
C: TypedColumn<B>,
{
pub fn get(&self, key: C::Index) -> Result<Option<C::Type>> {
if let Some(serialized_value) = self.backend.get_cf(self.handle(), C::key(key).borrow())? {
let value = deserialize(&serialized_value)?;
Ok(Some(value))
} else {
Ok(None)
}
pub fn get(&self, db: &Database<B>, key: C::Index) -> Result<Option<C::Type>> {
db.get::<C>(key)
}
}
pub fn put(&self, key: C::Index, value: &C::Type) -> Result<()> {
let serialized_value = serialize(value)?;
self.backend
.put_cf(self.handle(), C::key(key).borrow(), &serialized_value)
impl<B, C> LedgerColumn<B, C>
where
B: Backend,
C: TypedColumn<B>,
{
pub fn put(&self, db: &mut Database<B>, key: C::Index, value: &C::Type) -> Result<()> {
db.put::<C>(key, value)
}
}

View File

@ -119,40 +119,6 @@ impl TypedColumn<Kvs> for cf::Orphans {
type Type = bool;
}
impl Column<Kvs> for cf::Root {
const NAME: &'static str = super::ROOT_CF;
type Index = u64;
fn key(slot: u64) -> Key {
let mut key = Key::default();
BigEndian::write_u64(&mut key.0[8..16], slot);
key
}
fn index(key: &Key) -> u64 {
BigEndian::read_u64(&key.0[8..16])
}
}
impl TypedColumn<Kvs> for cf::Root {
type Type = bool;
}
impl Column<Kvs> for cf::SlotMeta {
const NAME: &'static str = super::META_CF;
type Index = u64;
fn key(slot: u64) -> Key {
let mut key = Key::default();
BigEndian::write_u64(&mut key.0[8..16], slot);
key
}
fn index(key: &Key) -> u64 {
BigEndian::read_u64(&key.0[8..16])
}
}
impl Column<Kvs> for cf::SlotMeta {
const NAME: &'static str = super::META_CF;
type Index = u64;

View File

@ -1,6 +1,4 @@
use crate::erasure::{NUM_CODING, NUM_DATA};
use solana_metrics::datapoint;
use std::borrow::Borrow;
#[derive(Clone, Debug, Default, Deserialize, Serialize, Eq, PartialEq)]
// The Meta column family
@ -25,6 +23,8 @@ pub struct SlotMeta {
// True if this slot is full (consumed == last_index + 1) and if every
// slot that is a parent of this slot is also connected.
pub is_connected: bool,
// True if this slot is a root
pub is_root: bool,
}
impl SlotMeta {
@ -38,17 +38,17 @@ impl SlotMeta {
// Should never happen
if self.consumed > self.last_index + 1 {
datapoint!(
"blocktree_error",
(
"error",
format!(
"Observed a slot meta with consumed: {} > meta.last_index + 1: {}",
self.consumed,
self.last_index + 1
),
String
)
solana_metrics::submit(
solana_metrics::influxdb::Point::new("blocktree_error")
.add_field(
"error",
solana_metrics::influxdb::Value::String(format!(
"Observed a slot meta with consumed: {} > meta.last_index + 1: {}",
self.consumed,
self.last_index + 1
)),
)
.to_owned(),
);
}
@ -67,6 +67,7 @@ impl SlotMeta {
parent_slot,
next_slots: vec![],
is_connected: slot == 0,
is_root: false,
last_index: std::u64::MAX,
}
}
@ -80,9 +81,9 @@ pub struct ErasureMeta {
/// Size of shards in this erasure set
pub size: usize,
/// Bitfield representing presence/absence of data blobs
data: u64,
pub data: u64,
/// Bitfield representing presence/absence of coding blobs
coding: u64,
pub coding: u64,
}
#[derive(Debug, PartialEq)]
@ -103,8 +104,10 @@ impl ErasureMeta {
}
pub fn status(&self) -> ErasureMetaStatus {
let (data_missing, coding_missing) =
(NUM_DATA - self.num_data(), NUM_CODING - self.num_coding());
let (data_missing, coding_missing) = (
NUM_DATA - self.data.count_ones() as usize,
NUM_CODING - self.coding.count_ones() as usize,
);
if data_missing > 0 && data_missing + coding_missing <= NUM_CODING {
assert!(self.size != 0);
ErasureMetaStatus::CanRecover
@ -115,14 +118,6 @@ impl ErasureMeta {
}
}
pub fn num_coding(&self) -> usize {
self.coding.count_ones() as usize
}
pub fn num_data(&self) -> usize {
self.data.count_ones() as usize
}
pub fn is_coding_present(&self, index: u64) -> bool {
if let Some(position) = self.data_index_in_set(index) {
self.coding & (1 << position) != 0
@ -167,26 +162,6 @@ impl ErasureMeta {
}
}
pub fn set_data_multi<I, Idx>(&mut self, indexes: I, present: bool)
where
I: IntoIterator<Item = Idx>,
Idx: Borrow<u64>,
{
for index in indexes.into_iter() {
self.set_data_present(*index.borrow(), present);
}
}
pub fn set_coding_multi<I, Idx>(&mut self, indexes: I, present: bool)
where
I: IntoIterator<Item = Idx>,
Idx: Borrow<u64>,
{
for index in indexes.into_iter() {
self.set_coding_present(*index.borrow(), present);
}
}
pub fn set_index_for(index: u64) -> u64 {
index / NUM_DATA as u64
}
@ -226,7 +201,7 @@ fn test_meta_indexes() {
for _ in 0..100 {
let set_index = rng.gen_range(0, 1_000);
let blob_index = (set_index * NUM_DATA) + rng.gen_range(0, NUM_DATA);
let blob_index = (set_index * NUM_DATA) + rng.gen_range(0, 16);
assert_eq!(set_index, ErasureMeta::set_index_for(blob_index));
let e_meta = ErasureMeta::new(set_index);
@ -261,8 +236,8 @@ fn test_meta_indexes() {
fn test_meta_coding_present() {
let mut e_meta = ErasureMeta::default();
e_meta.set_coding_multi(0..NUM_CODING as u64, true);
for i in 0..NUM_CODING as u64 {
e_meta.set_coding_present(i, true);
assert_eq!(e_meta.is_coding_present(i), true);
}
for i in NUM_CODING as u64..NUM_DATA as u64 {
@ -270,62 +245,60 @@ fn test_meta_coding_present() {
}
e_meta.set_index = ErasureMeta::set_index_for((NUM_DATA * 17) as u64);
let start_idx = e_meta.start_index();
e_meta.set_coding_multi(start_idx..start_idx + NUM_CODING as u64, true);
for i in start_idx..start_idx + NUM_CODING as u64 {
for i in (NUM_DATA * 17) as u64..((NUM_DATA * 17) + NUM_CODING) as u64 {
e_meta.set_coding_present(i, true);
assert_eq!(e_meta.is_coding_present(i), true);
}
for i in start_idx + NUM_CODING as u64..start_idx + NUM_DATA as u64 {
for i in (NUM_DATA * 17 + NUM_CODING) as u64..((NUM_DATA * 17) + NUM_DATA) as u64 {
assert_eq!(e_meta.is_coding_present(i), false);
}
}
#[test]
fn test_erasure_meta_status() {
use rand::{seq::SliceRandom, thread_rng};
// Local constansts just used to avoid repetitive casts
const N_DATA: u64 = crate::erasure::NUM_DATA as u64;
const N_CODING: u64 = crate::erasure::NUM_CODING as u64;
let mut e_meta = ErasureMeta::default();
let mut rng = thread_rng();
let data_indexes: Vec<u64> = (0..N_DATA).collect();
let coding_indexes: Vec<u64> = (0..N_CODING).collect();
assert_eq!(e_meta.status(), ErasureMetaStatus::StillNeed(NUM_DATA));
e_meta.set_data_multi(0..N_DATA, true);
e_meta.data = 0b1111_1111_1111_1111;
e_meta.coding = 0x00;
assert_eq!(e_meta.status(), ErasureMetaStatus::DataFull);
e_meta.coding = 0x0e;
e_meta.size = 1;
e_meta.set_coding_multi(0..N_CODING, true);
assert_eq!(e_meta.status(), ErasureMetaStatus::DataFull);
for &idx in data_indexes.choose_multiple(&mut rng, NUM_CODING) {
e_meta.set_data_present(idx, false);
e_meta.data = 0b0111_1111_1111_1111;
assert_eq!(e_meta.status(), ErasureMetaStatus::CanRecover);
assert_eq!(e_meta.status(), ErasureMetaStatus::CanRecover);
}
e_meta.data = 0b0111_1111_1111_1110;
assert_eq!(e_meta.status(), ErasureMetaStatus::CanRecover);
e_meta.set_data_multi(0..N_DATA, true);
e_meta.data = 0b0111_1111_1011_1110;
assert_eq!(e_meta.status(), ErasureMetaStatus::CanRecover);
for &idx in coding_indexes.choose_multiple(&mut rng, NUM_CODING) {
e_meta.set_coding_present(idx, false);
e_meta.data = 0b0111_1011_1011_1110;
assert_eq!(e_meta.status(), ErasureMetaStatus::StillNeed(1));
assert_eq!(e_meta.status(), ErasureMetaStatus::DataFull);
}
e_meta.data = 0b0111_1011_1011_1110;
assert_eq!(e_meta.status(), ErasureMetaStatus::StillNeed(1));
e_meta.coding = 0b0000_1110;
e_meta.data = 0b1111_1111_1111_1100;
assert_eq!(e_meta.status(), ErasureMetaStatus::CanRecover);
e_meta.data = 0b1111_1111_1111_1000;
assert_eq!(e_meta.status(), ErasureMetaStatus::CanRecover);
}
#[test]
fn test_meta_data_present() {
let mut e_meta = ErasureMeta::default();
e_meta.set_data_multi(0..NUM_DATA as u64, true);
for i in 0..NUM_DATA as u64 {
e_meta.set_data_present(i, true);
assert_eq!(e_meta.is_data_present(i), true);
}
for i in NUM_DATA as u64..2 * NUM_DATA as u64 {
@ -333,13 +306,12 @@ fn test_meta_data_present() {
}
e_meta.set_index = ErasureMeta::set_index_for((NUM_DATA * 23) as u64);
let start_idx = e_meta.start_index();
e_meta.set_data_multi(start_idx..start_idx + NUM_DATA as u64, true);
for i in start_idx..start_idx + NUM_DATA as u64 {
for i in (NUM_DATA * 23) as u64..(NUM_DATA * 24) as u64 {
e_meta.set_data_present(i, true);
assert_eq!(e_meta.is_data_present(i), true);
}
for i in start_idx - NUM_DATA as u64..start_idx {
for i in (NUM_DATA * 22) as u64..(NUM_DATA * 23) as u64 {
assert_eq!(e_meta.is_data_present(i), false);
}
}

View File

@ -6,8 +6,8 @@ use crate::result::{Error, Result};
use byteorder::{BigEndian, ByteOrder};
use rocksdb::{
self, ColumnFamily, ColumnFamilyDescriptor, DBIterator, DBRawIterator, Direction, IteratorMode,
Options, WriteBatch as RWriteBatch, DB,
self, ColumnFamily, ColumnFamilyDescriptor, DBIterator, DBRawIterator, IteratorMode, Options,
WriteBatch as RWriteBatch, DB,
};
use std::fs;
@ -30,7 +30,7 @@ impl Backend for Rocks {
type Error = rocksdb::Error;
fn open(path: &Path) -> Result<Rocks> {
use crate::blocktree::db::columns::{Coding, Data, ErasureMeta, Orphans, Root, SlotMeta};
use crate::blocktree::db::columns::{Coding, Data, ErasureMeta, Orphans, SlotMeta};
fs::create_dir_all(&path)?;
@ -44,7 +44,6 @@ impl Backend for Rocks {
let erasure_meta_cf_descriptor =
ColumnFamilyDescriptor::new(ErasureMeta::NAME, get_cf_options());
let orphans_cf_descriptor = ColumnFamilyDescriptor::new(Orphans::NAME, get_cf_options());
let root_cf_descriptor = ColumnFamilyDescriptor::new(Root::NAME, get_cf_options());
let cfs = vec![
meta_cf_descriptor,
@ -52,7 +51,6 @@ impl Backend for Rocks {
erasure_cf_descriptor,
erasure_meta_cf_descriptor,
orphans_cf_descriptor,
root_cf_descriptor,
];
// Open the database
@ -62,14 +60,13 @@ impl Backend for Rocks {
}
fn columns(&self) -> Vec<&'static str> {
use crate::blocktree::db::columns::{Coding, Data, ErasureMeta, Orphans, Root, SlotMeta};
use crate::blocktree::db::columns::{Coding, Data, ErasureMeta, Orphans, SlotMeta};
vec![
Coding::NAME,
ErasureMeta::NAME,
Data::NAME,
Orphans::NAME,
Root::NAME,
SlotMeta::NAME,
]
}
@ -101,17 +98,10 @@ impl Backend for Rocks {
Ok(())
}
fn iterator_cf(&self, cf: ColumnFamily, start_from: Option<&[u8]>) -> Result<DBIterator> {
let iter = {
if let Some(start_from) = start_from {
self.0
.iterator_cf(cf, IteratorMode::From(start_from, Direction::Forward))?
} else {
self.0.iterator_cf(cf, IteratorMode::Start)?
}
};
fn iterator_cf(&self, cf: ColumnFamily) -> Result<DBIterator> {
let raw_iter = self.0.iterator_cf(cf, IteratorMode::Start)?;
Ok(iter)
Ok(raw_iter)
}
fn raw_iterator_cf(&self, cf: ColumnFamily) -> Result<DBRawIterator> {
@ -180,25 +170,6 @@ impl TypedColumn<Rocks> for cf::Orphans {
type Type = bool;
}
impl Column<Rocks> for cf::Root {
const NAME: &'static str = super::ROOT_CF;
type Index = u64;
fn key(slot: u64) -> Vec<u8> {
let mut key = vec![0; 8];
BigEndian::write_u64(&mut key[..], slot);
key
}
fn index(key: &[u8]) -> u64 {
BigEndian::read_u64(&key[..8])
}
}
impl TypedColumn<Rocks> for cf::Root {
type Type = bool;
}
impl Column<Rocks> for cf::SlotMeta {
const NAME: &'static str = super::META_CF;
type Index = u64;

View File

@ -1,126 +0,0 @@
use super::*;
pub struct RootedSlotIterator<'a> {
next_slots: Vec<u64>,
blocktree: &'a super::Blocktree,
}
impl<'a> RootedSlotIterator<'a> {
pub fn new(start_slot: u64, blocktree: &'a super::Blocktree) -> Result<Self> {
if blocktree.is_root(start_slot) {
Ok(Self {
next_slots: vec![start_slot],
blocktree,
})
} else {
Err(Error::BlocktreeError(BlocktreeError::SlotNotRooted))
}
}
}
impl<'a> Iterator for RootedSlotIterator<'a> {
type Item = (u64, super::SlotMeta);
fn next(&mut self) -> Option<Self::Item> {
// Clone b/c passing the closure to the map below requires exclusive access to
// `self`, which is borrowed here if we don't clone.
let rooted_slot = self
.next_slots
.iter()
.find(|x| self.blocktree.is_root(**x))
.cloned();
rooted_slot.map(|rooted_slot| {
let slot_meta = self
.blocktree
.meta(rooted_slot)
.expect("Database failure, couldnt fetch SlotMeta")
.expect("SlotMeta in iterator didn't exist");
self.next_slots = slot_meta.next_slots.clone();
(rooted_slot, slot_meta)
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::blocktree_processor::tests::fill_blocktree_slot_with_ticks;
#[test]
fn test_rooted_slot_iterator() {
let blocktree_path = get_tmp_ledger_path("test_rooted_slot_iterator");
let blocktree = Blocktree::open(&blocktree_path).unwrap();
blocktree.set_root(0, 0).unwrap();
let ticks_per_slot = 5;
/*
Build a blocktree in the ledger with the following fork structure:
slot 0
|
slot 1 <-- set_root(true)
/ \
slot 2 |
/ |
slot 3 |
|
slot 4
*/
// Fork 1, ending at slot 3
let last_entry_hash = Hash::default();
let fork_point = 1;
let mut fork_hash = Hash::default();
for slot in 0..=3 {
let parent = {
if slot == 0 {
0
} else {
slot - 1
}
};
let last_entry_hash = fill_blocktree_slot_with_ticks(
&blocktree,
ticks_per_slot,
slot,
parent,
last_entry_hash,
);
if slot == fork_point {
fork_hash = last_entry_hash;
}
}
// Fork 2, ending at slot 4
let _ =
fill_blocktree_slot_with_ticks(&blocktree, ticks_per_slot, 4, fork_point, fork_hash);
// Set a root
blocktree.set_root(3, 0).unwrap();
// Trying to get an iterator on a different fork will error
assert!(RootedSlotIterator::new(4, &blocktree).is_err());
// Trying to get an iterator on any slot on the root fork should succeed
let result: Vec<_> = RootedSlotIterator::new(3, &blocktree)
.unwrap()
.into_iter()
.map(|(slot, _)| slot)
.collect();
let expected = vec![3];
assert_eq!(result, expected);
let result: Vec<_> = RootedSlotIterator::new(0, &blocktree)
.unwrap()
.into_iter()
.map(|(slot, _)| slot)
.collect();
let expected = vec![0, 1, 2, 3];
assert_eq!(result, expected);
drop(blocktree);
Blocktree::destroy(&blocktree_path).expect("Expected successful database destruction");
}
}

View File

@ -3,32 +3,26 @@ use crate::blocktree::Blocktree;
use crate::entry::{Entry, EntrySlice};
use crate::leader_schedule_cache::LeaderScheduleCache;
use rayon::prelude::*;
use solana_metrics::{datapoint, datapoint_error, inc_new_counter_debug};
use solana_metrics::counter::Counter;
use solana_runtime::bank::Bank;
use solana_runtime::locked_accounts_results::LockedAccountsResults;
use solana_sdk::genesis_block::GenesisBlock;
use solana_sdk::timing::duration_as_ms;
use solana_sdk::timing::MAX_RECENT_BLOCKHASHES;
use solana_sdk::transaction::Result;
use solana_sdk::transaction::Transaction;
use std::result;
use std::sync::Arc;
use std::time::{Duration, Instant};
fn first_err(results: &[Result<()>]) -> Result<()> {
for r in results {
if r.is_err() {
return r.clone();
}
r.clone()?;
}
Ok(())
}
fn par_execute_entries(
bank: &Bank,
entries: &[(&Entry, LockedAccountsResults<Transaction>)],
) -> Result<()> {
inc_new_counter_debug!("bank-par_execute_entries-count", entries.len());
fn par_execute_entries(bank: &Bank, entries: &[(&Entry, LockedAccountsResults)]) -> Result<()> {
inc_new_counter_info!("bank-par_execute_entries-count", entries.len());
let results: Vec<Result<()>> = entries
.into_par_iter()
.map(|(e, locked_accounts)| {
@ -38,17 +32,21 @@ fn par_execute_entries(
MAX_RECENT_BLOCKHASHES,
);
let mut first_err = None;
for (r, tx) in results.iter().zip(e.transactions.iter()) {
for r in results {
if let Err(ref e) = r {
if first_err.is_none() {
first_err = Some(r.clone());
}
if !Bank::can_commit(&r) {
warn!("Unexpected validator error: {:?}, tx: {:?}", e, tx);
datapoint_error!(
"validator_process_entry_error",
("error", format!("error: {:?}, tx: {:?}", e, tx), String)
);
warn!("Unexpected validator error: {:?}", e);
solana_metrics::submit(
solana_metrics::influxdb::Point::new("validator_process_entry_error")
.add_field(
"error",
solana_metrics::influxdb::Value::String(format!("{:?}", e)),
)
.to_owned(),
)
}
}
}
@ -71,47 +69,25 @@ pub fn process_entries(bank: &Bank, entries: &[Entry]) -> Result<()> {
if entry.is_tick() {
// if its a tick, execute the group and register the tick
par_execute_entries(bank, &mt_group)?;
mt_group = vec![];
bank.register_tick(&entry.hash);
mt_group = vec![];
continue;
}
// else loop on processing the entry
loop {
// try to lock the accounts
// try to lock the accounts
let lock_results = bank.lock_accounts(&entry.transactions);
// if any of the locks error out
// execute the current group
if first_err(lock_results.locked_accounts_results()).is_err() {
par_execute_entries(bank, &mt_group)?;
// Drop all the locks on accounts by clearing the LockedAccountsFinalizer's in the
// mt_group
mt_group = vec![];
drop(lock_results);
let lock_results = bank.lock_accounts(&entry.transactions);
let first_lock_err = first_err(lock_results.locked_accounts_results());
// if locking worked
if first_lock_err.is_ok() {
// push the entry to the mt_group
mt_group.push((entry, lock_results));
// done with this entry
break;
}
// else we failed to lock, 2 possible reasons
if mt_group.is_empty() {
// An entry has account lock conflicts with *itself*, which should not happen
// if generated by a properly functioning leader
datapoint!(
"validator_process_entry_error",
(
"error",
format!(
"Lock accounts error, entry conflicts with itself, txs: {:?}",
entry.transactions
),
String
)
);
// bail
first_lock_err?;
} else {
// else we have an entry that conflicts with a prior entry
// execute the current queue and try to process this entry again
par_execute_entries(bank, &mt_group)?;
mt_group = vec![];
}
mt_group.push((entry, lock_results));
} else {
// push the entry to the mt_group
mt_group.push((entry, lock_results));
}
}
par_execute_entries(bank, &mt_group)?;
@ -155,13 +131,10 @@ pub fn process_blocktree(
vec![(slot, meta, bank, entry_height, last_entry_hash)]
};
blocktree.set_root(0, 0).expect("Couldn't set first root");
let leader_schedule_cache = LeaderScheduleCache::new(*pending_slots[0].2.epoch_schedule(), 0);
let leader_schedule_cache = LeaderScheduleCache::new(*pending_slots[0].2.epoch_schedule());
let mut fork_info = vec![];
let mut last_status_report = Instant::now();
let mut root = 0;
while !pending_slots.is_empty() {
let (slot, meta, bank, mut entry_height, mut last_entry_hash) =
pending_slots.pop().unwrap();
@ -215,11 +188,7 @@ pub fn process_blocktree(
bank.freeze(); // all banks handled by this routine are created from complete slots
if blocktree.is_root(slot) {
root = slot;
leader_schedule_cache.set_root(slot);
bank.squash();
pending_slots.clear();
fork_info.clear();
}
if meta.next_slots.is_empty() {
@ -243,12 +212,12 @@ pub fn process_blocktree(
.unwrap();
// only process full slots in blocktree_processor, replay_stage
// handles any partials
// handles any partials
if next_meta.is_full() {
let next_bank = Arc::new(Bank::new_from_parent(
&bank,
&leader_schedule_cache
.slot_leader_at(next_slot, Some(&bank))
.slot_leader_at_else_compute(next_slot, &bank)
.unwrap(),
next_slot,
));
@ -276,7 +245,7 @@ pub fn process_blocktree(
}
let (banks, bank_forks_info): (Vec<_>, Vec<_>) = fork_info.into_iter().unzip();
let bank_forks = BankForks::new_from_banks(&banks, root);
let bank_forks = BankForks::new_from_banks(&banks);
info!(
"processing ledger...complete in {}ms, forks={}...",
duration_as_ms(&now.elapsed()),
@ -287,15 +256,12 @@ pub fn process_blocktree(
}
#[cfg(test)]
pub mod tests {
mod tests {
use super::*;
use crate::blocktree::create_new_tmp_ledger;
use crate::blocktree::tests::entries_to_blobs;
use crate::entry::{create_ticks, next_entry, next_entry_mut, Entry};
use crate::genesis_utils::{
create_genesis_block, create_genesis_block_with_leader, GenesisBlockInfo,
};
use solana_runtime::epoch_schedule::EpochSchedule;
use crate::entry::{create_ticks, next_entry, Entry};
use solana_sdk::genesis_block::GenesisBlock;
use solana_sdk::hash::Hash;
use solana_sdk::instruction::InstructionError;
use solana_sdk::pubkey::Pubkey;
@ -303,7 +269,7 @@ pub mod tests {
use solana_sdk::system_transaction;
use solana_sdk::transaction::TransactionError;
pub fn fill_blocktree_slot_with_ticks(
fn fill_blocktree_slot_with_ticks(
blocktree: &Blocktree,
ticks_per_slot: u64,
slot: u64,
@ -323,7 +289,7 @@ pub mod tests {
fn test_process_blocktree_with_incomplete_slot() {
solana_logger::setup();
let GenesisBlockInfo { genesis_block, .. } = create_genesis_block(10_000);
let (genesis_block, _mint_keypair) = GenesisBlock::new(10_000);
let ticks_per_slot = genesis_block.ticks_per_slot;
/*
@ -376,85 +342,11 @@ pub mod tests {
);
}
#[test]
fn test_process_blocktree_with_two_forks_and_squash() {
solana_logger::setup();
let GenesisBlockInfo { genesis_block, .. } = create_genesis_block(10_000);
let ticks_per_slot = genesis_block.ticks_per_slot;
// Create a new ledger with slot 0 full of ticks
let (ledger_path, blockhash) = create_new_tmp_ledger!(&genesis_block);
debug!("ledger_path: {:?}", ledger_path);
let mut last_entry_hash = blockhash;
/*
Build a blocktree in the ledger with the following fork structure:
slot 0
|
slot 1
/ \
slot 2 |
/ |
slot 3 |
|
slot 4 <-- set_root(true)
*/
let blocktree =
Blocktree::open(&ledger_path).expect("Expected to successfully open database ledger");
// Fork 1, ending at slot 3
let last_slot1_entry_hash =
fill_blocktree_slot_with_ticks(&blocktree, ticks_per_slot, 1, 0, last_entry_hash);
last_entry_hash =
fill_blocktree_slot_with_ticks(&blocktree, ticks_per_slot, 2, 1, last_slot1_entry_hash);
let last_fork1_entry_hash =
fill_blocktree_slot_with_ticks(&blocktree, ticks_per_slot, 3, 2, last_entry_hash);
// Fork 2, ending at slot 4
let last_fork2_entry_hash =
fill_blocktree_slot_with_ticks(&blocktree, ticks_per_slot, 4, 1, last_slot1_entry_hash);
info!("last_fork1_entry.hash: {:?}", last_fork1_entry_hash);
info!("last_fork2_entry.hash: {:?}", last_fork2_entry_hash);
blocktree.set_root(4, 0).unwrap();
let (bank_forks, bank_forks_info, _) =
process_blocktree(&genesis_block, &blocktree, None).unwrap();
assert_eq!(bank_forks_info.len(), 1); // One fork, other one is ignored b/c not a descendant of the root
assert_eq!(
bank_forks_info[0],
BankForksInfo {
bank_slot: 4, // Fork 2's head is slot 4
entry_height: ticks_per_slot * 3,
}
);
assert!(&bank_forks[4]
.parents()
.iter()
.map(|bank| bank.slot())
.collect::<Vec<_>>()
.is_empty());
// Ensure bank_forks holds the right banks
for info in bank_forks_info {
assert_eq!(bank_forks[info.bank_slot].slot(), info.bank_slot);
assert!(bank_forks[info.bank_slot].is_frozen());
}
assert_eq!(bank_forks.root(), 4);
}
#[test]
fn test_process_blocktree_with_two_forks() {
solana_logger::setup();
let GenesisBlockInfo { genesis_block, .. } = create_genesis_block(10_000);
let (genesis_block, _mint_keypair) = GenesisBlock::new(10_000);
let ticks_per_slot = genesis_block.ticks_per_slot;
// Create a new ledger with slot 0 full of ticks
@ -494,8 +386,8 @@ pub mod tests {
info!("last_fork1_entry.hash: {:?}", last_fork1_entry_hash);
info!("last_fork2_entry.hash: {:?}", last_fork2_entry_hash);
blocktree.set_root(0, 0).unwrap();
blocktree.set_root(1, 0).unwrap();
blocktree.set_root(0).unwrap();
blocktree.set_root(1).unwrap();
let (bank_forks, bank_forks_info, _) =
process_blocktree(&genesis_block, &blocktree, None).unwrap();
@ -532,8 +424,6 @@ pub mod tests {
&[1]
);
assert_eq!(bank_forks.root(), 1);
// Ensure bank_forks holds the right banks
for info in bank_forks_info {
assert_eq!(bank_forks[info.bank_slot].slot(), info.bank_slot);
@ -541,63 +431,6 @@ pub mod tests {
}
}
#[test]
fn test_process_blocktree_epoch_boundary_root() {
solana_logger::setup();
let GenesisBlockInfo { genesis_block, .. } = create_genesis_block(10_000);
let ticks_per_slot = genesis_block.ticks_per_slot;
// Create a new ledger with slot 0 full of ticks
let (ledger_path, blockhash) = create_new_tmp_ledger!(&genesis_block);
let mut last_entry_hash = blockhash;
let blocktree =
Blocktree::open(&ledger_path).expect("Expected to successfully open database ledger");
// Let last_slot be the number of slots in the first two epochs
let epoch_schedule = get_epoch_schedule(&genesis_block, None);
let last_slot = epoch_schedule.get_last_slot_in_epoch(1);
// Create a single chain of slots with all indexes in the range [0, last_slot + 1]
for i in 1..=last_slot + 1 {
last_entry_hash = fill_blocktree_slot_with_ticks(
&blocktree,
ticks_per_slot,
i,
i - 1,
last_entry_hash,
);
}
// Set a root on the last slot of the last confirmed epoch
blocktree.set_root(last_slot, 0).unwrap();
// Set a root on the next slot of the confrimed epoch
blocktree.set_root(last_slot + 1, last_slot).unwrap();
// Check that we can properly restart the ledger / leader scheduler doesn't fail
let (bank_forks, bank_forks_info, _) =
process_blocktree(&genesis_block, &blocktree, None).unwrap();
assert_eq!(bank_forks_info.len(), 1); // There is one fork
assert_eq!(
bank_forks_info[0],
BankForksInfo {
bank_slot: last_slot + 1, // Head is last_slot + 1
entry_height: ticks_per_slot * (last_slot + 2),
}
);
// The latest root should have purged all its parents
assert!(&bank_forks[last_slot + 1]
.parents()
.iter()
.map(|bank| bank.slot())
.collect::<Vec<_>>()
.is_empty());
}
#[test]
fn test_first_err() {
assert_eq!(first_err(&[Ok(())]), Ok(()));
@ -635,11 +468,7 @@ pub mod tests {
fn test_process_empty_entry_is_registered() {
solana_logger::setup();
let GenesisBlockInfo {
genesis_block,
mint_keypair,
..
} = create_genesis_block(2);
let (genesis_block, mint_keypair) = GenesisBlock::new(2);
let bank = Bank::new(&genesis_block);
let keypair = Keypair::new();
let slot_entries = create_ticks(genesis_block.ticks_per_slot - 1, genesis_block.hash());
@ -648,6 +477,7 @@ pub mod tests {
&keypair.pubkey(),
1,
slot_entries.last().unwrap().hash,
0,
);
// First, ensure the TX is rejected because of the unregistered last ID
@ -665,19 +495,13 @@ pub mod tests {
fn test_process_ledger_simple() {
solana_logger::setup();
let leader_pubkey = Pubkey::new_rand();
let mint = 100;
let GenesisBlockInfo {
genesis_block,
mint_keypair,
..
} = create_genesis_block_with_leader(mint, &leader_pubkey, 50);
let (genesis_block, mint_keypair) = GenesisBlock::new_with_leader(100, &leader_pubkey, 50);
let (ledger_path, mut last_entry_hash) = create_new_tmp_ledger!(&genesis_block);
debug!("ledger_path: {:?}", ledger_path);
let deducted_from_mint = 3;
let mut entries = vec![];
let blockhash = genesis_block.hash();
for _ in 0..deducted_from_mint {
for _ in 0..3 {
// Transfer one token from the mint to a random account
let keypair = Keypair::new();
let tx = system_transaction::create_user_account(
@ -685,6 +509,7 @@ pub mod tests {
&keypair.pubkey(),
1,
blockhash,
0,
);
let entry = Entry::new(&last_entry_hash, 1, vec![tx]);
last_entry_hash = entry.hash;
@ -698,6 +523,7 @@ pub mod tests {
&keypair2.pubkey(),
42,
blockhash,
0,
);
let entry = Entry::new(&last_entry_hash, 1, vec![tx]);
last_entry_hash = entry.hash;
@ -717,7 +543,6 @@ pub mod tests {
process_blocktree(&genesis_block, &blocktree, None).unwrap();
assert_eq!(bank_forks_info.len(), 1);
assert_eq!(bank_forks.root(), 0);
assert_eq!(
bank_forks_info[0],
BankForksInfo {
@ -727,19 +552,14 @@ pub mod tests {
);
let bank = bank_forks[1].clone();
assert_eq!(
bank.get_balance(&mint_keypair.pubkey()),
mint - deducted_from_mint
);
assert_eq!(bank.get_balance(&mint_keypair.pubkey()), 50 - 3);
assert_eq!(bank.tick_height(), 2 * genesis_block.ticks_per_slot - 1);
assert_eq!(bank.last_blockhash(), entries.last().unwrap().hash);
}
#[test]
fn test_process_ledger_with_one_tick_per_slot() {
let GenesisBlockInfo {
mut genesis_block, ..
} = create_genesis_block(123);
let (mut genesis_block, _mint_keypair) = GenesisBlock::new(123);
genesis_block.ticks_per_slot = 1;
let (ledger_path, _blockhash) = create_new_tmp_ledger!(&genesis_block);
@ -761,7 +581,7 @@ pub mod tests {
#[test]
fn test_process_entries_tick() {
let GenesisBlockInfo { genesis_block, .. } = create_genesis_block(1000);
let (genesis_block, _mint_keypair) = GenesisBlock::new(1000);
let bank = Bank::new(&genesis_block);
// ensure bank can process a tick
@ -773,11 +593,7 @@ pub mod tests {
#[test]
fn test_process_entries_2_entries_collision() {
let GenesisBlockInfo {
genesis_block,
mint_keypair,
..
} = create_genesis_block(1000);
let (genesis_block, mint_keypair) = GenesisBlock::new(1000);
let bank = Bank::new(&genesis_block);
let keypair1 = Keypair::new();
let keypair2 = Keypair::new();
@ -790,6 +606,7 @@ pub mod tests {
&keypair1.pubkey(),
2,
bank.last_blockhash(),
0,
);
let entry_1 = next_entry(&blockhash, 1, vec![tx]);
let tx = system_transaction::create_user_account(
@ -797,6 +614,7 @@ pub mod tests {
&keypair2.pubkey(),
2,
bank.last_blockhash(),
0,
);
let entry_2 = next_entry(&entry_1.hash, 1, vec![tx]);
assert_eq!(process_entries(&bank, &[entry_1, entry_2]), Ok(()));
@ -807,11 +625,7 @@ pub mod tests {
#[test]
fn test_process_entries_2_txes_collision() {
let GenesisBlockInfo {
genesis_block,
mint_keypair,
..
} = create_genesis_block(1000);
let (genesis_block, mint_keypair) = GenesisBlock::new(1000);
let bank = Bank::new(&genesis_block);
let keypair1 = Keypair::new();
let keypair2 = Keypair::new();
@ -830,6 +644,7 @@ pub mod tests {
&mint_keypair.pubkey(),
1,
bank.last_blockhash(),
0,
)],
);
@ -842,12 +657,14 @@ pub mod tests {
&keypair3.pubkey(),
2,
bank.last_blockhash(),
0,
), // should be fine
system_transaction::create_user_account(
&keypair1,
&mint_keypair.pubkey(),
2,
bank.last_blockhash(),
0,
), // will collide
],
);
@ -864,11 +681,7 @@ pub mod tests {
#[test]
fn test_process_entries_2_txes_collision_and_error() {
let GenesisBlockInfo {
genesis_block,
mint_keypair,
..
} = create_genesis_block(1000);
let (genesis_block, mint_keypair) = GenesisBlock::new(1000);
let bank = Bank::new(&genesis_block);
let keypair1 = Keypair::new();
let keypair2 = Keypair::new();
@ -890,12 +703,14 @@ pub mod tests {
&mint_keypair.pubkey(),
1,
bank.last_blockhash(),
0,
),
system_transaction::transfer(
&keypair4,
&keypair4.pubkey(),
1,
Hash::default(), // Should cause a transaction failure with BlockhashNotFound
0,
),
],
);
@ -909,12 +724,14 @@ pub mod tests {
&keypair3.pubkey(),
2,
bank.last_blockhash(),
0,
), // should be fine
system_transaction::create_user_account(
&keypair1,
&mint_keypair.pubkey(),
2,
bank.last_blockhash(),
0,
), // will collide
],
);
@ -944,109 +761,9 @@ pub mod tests {
}
}
#[test]
fn test_process_entries_2nd_entry_collision_with_self_and_error() {
solana_logger::setup();
let GenesisBlockInfo {
genesis_block,
mint_keypair,
..
} = create_genesis_block(1000);
let bank = Bank::new(&genesis_block);
let keypair1 = Keypair::new();
let keypair2 = Keypair::new();
let keypair3 = Keypair::new();
// fund: put some money in each of 1 and 2
assert_matches!(bank.transfer(5, &mint_keypair, &keypair1.pubkey()), Ok(_));
assert_matches!(bank.transfer(4, &mint_keypair, &keypair2.pubkey()), Ok(_));
// 3 entries: first has a transfer, 2nd has a conflict with 1st, 3rd has a conflict with itself
let entry_1_to_mint = next_entry(
&bank.last_blockhash(),
1,
vec![system_transaction::transfer(
&keypair1,
&mint_keypair.pubkey(),
1,
bank.last_blockhash(),
)],
);
// should now be:
// keypair1=4
// keypair2=4
// keypair3=0
let entry_2_to_3_and_1_to_mint = next_entry(
&entry_1_to_mint.hash,
1,
vec![
system_transaction::create_user_account(
&keypair2,
&keypair3.pubkey(),
2,
bank.last_blockhash(),
), // should be fine
system_transaction::transfer(
&keypair1,
&mint_keypair.pubkey(),
2,
bank.last_blockhash(),
), // will collide with predecessor
],
);
// should now be:
// keypair1=2
// keypair2=2
// keypair3=2
let entry_conflict_itself = next_entry(
&entry_2_to_3_and_1_to_mint.hash,
1,
vec![
system_transaction::transfer(
&keypair1,
&keypair3.pubkey(),
1,
bank.last_blockhash(),
),
system_transaction::transfer(
&keypair1,
&keypair2.pubkey(),
1,
bank.last_blockhash(),
), // should be fine
],
);
// would now be:
// keypair1=0
// keypair2=3
// keypair3=3
assert!(process_entries(
&bank,
&[
entry_1_to_mint.clone(),
entry_2_to_3_and_1_to_mint.clone(),
entry_conflict_itself.clone()
]
)
.is_err());
// last entry should have been aborted before par_execute_entries
assert_eq!(bank.get_balance(&keypair1.pubkey()), 2);
assert_eq!(bank.get_balance(&keypair2.pubkey()), 2);
assert_eq!(bank.get_balance(&keypair3.pubkey()), 2);
}
#[test]
fn test_process_entries_2_entries_par() {
let GenesisBlockInfo {
genesis_block,
mint_keypair,
..
} = create_genesis_block(1000);
let (genesis_block, mint_keypair) = GenesisBlock::new(1000);
let bank = Bank::new(&genesis_block);
let keypair1 = Keypair::new();
let keypair2 = Keypair::new();
@ -1059,6 +776,7 @@ pub mod tests {
&keypair1.pubkey(),
1,
bank.last_blockhash(),
0,
);
assert_eq!(bank.process_transaction(&tx), Ok(()));
let tx = system_transaction::create_user_account(
@ -1066,6 +784,7 @@ pub mod tests {
&keypair2.pubkey(),
1,
bank.last_blockhash(),
0,
);
assert_eq!(bank.process_transaction(&tx), Ok(()));
@ -1076,6 +795,7 @@ pub mod tests {
&keypair3.pubkey(),
1,
bank.last_blockhash(),
0,
);
let entry_1 = next_entry(&blockhash, 1, vec![tx]);
let tx = system_transaction::create_user_account(
@ -1083,6 +803,7 @@ pub mod tests {
&keypair4.pubkey(),
1,
bank.last_blockhash(),
0,
);
let entry_2 = next_entry(&entry_1.hash, 1, vec![tx]);
assert_eq!(process_entries(&bank, &[entry_1, entry_2]), Ok(()));
@ -1093,11 +814,7 @@ pub mod tests {
#[test]
fn test_process_entries_2_entries_tick() {
let GenesisBlockInfo {
genesis_block,
mint_keypair,
..
} = create_genesis_block(1000);
let (genesis_block, mint_keypair) = GenesisBlock::new(1000);
let bank = Bank::new(&genesis_block);
let keypair1 = Keypair::new();
let keypair2 = Keypair::new();
@ -1110,6 +827,7 @@ pub mod tests {
&keypair1.pubkey(),
1,
bank.last_blockhash(),
0,
);
assert_eq!(bank.process_transaction(&tx), Ok(()));
let tx = system_transaction::create_user_account(
@ -1117,6 +835,7 @@ pub mod tests {
&keypair2.pubkey(),
1,
bank.last_blockhash(),
0,
);
assert_eq!(bank.process_transaction(&tx), Ok(()));
@ -1127,7 +846,7 @@ pub mod tests {
// ensure bank can process 2 entries that do not have a common account and tick is registered
let tx =
system_transaction::create_user_account(&keypair2, &keypair3.pubkey(), 1, blockhash);
system_transaction::create_user_account(&keypair2, &keypair3.pubkey(), 1, blockhash, 0);
let entry_1 = next_entry(&blockhash, 1, vec![tx]);
let tick = next_entry(&entry_1.hash, 1, vec![]);
let tx = system_transaction::create_user_account(
@ -1135,6 +854,7 @@ pub mod tests {
&keypair4.pubkey(),
1,
bank.last_blockhash(),
0,
);
let entry_2 = next_entry(&tick.hash, 1, vec![tx]);
assert_eq!(
@ -1150,6 +870,7 @@ pub mod tests {
&keypair3.pubkey(),
1,
bank.last_blockhash(),
0,
);
let entry_3 = next_entry(&entry_2.hash, 1, vec![tx]);
assert_eq!(
@ -1161,11 +882,7 @@ pub mod tests {
#[test]
fn test_update_transaction_statuses() {
// Make sure instruction errors still update the signature cache
let GenesisBlockInfo {
genesis_block,
mint_keypair,
..
} = create_genesis_block(11_000);
let (genesis_block, mint_keypair) = GenesisBlock::new(11_000);
let bank = Bank::new(&genesis_block);
let pubkey = Pubkey::new_rand();
bank.transfer(1_000, &mint_keypair, &pubkey).unwrap();
@ -1184,8 +901,13 @@ pub mod tests {
);
// Make sure other errors don't update the signature cache
let tx =
system_transaction::create_user_account(&mint_keypair, &pubkey, 1000, Hash::default());
let tx = system_transaction::create_user_account(
&mint_keypair,
&pubkey,
1000,
Hash::default(),
0,
);
let signature = tx.signatures[0];
// Should fail with blockhash not found
@ -1203,11 +925,7 @@ pub mod tests {
#[test]
fn test_update_transaction_statuses_fail() {
let GenesisBlockInfo {
genesis_block,
mint_keypair,
..
} = create_genesis_block(11_000);
let (genesis_block, mint_keypair) = GenesisBlock::new(11_000);
let bank = Bank::new(&genesis_block);
let keypair1 = Keypair::new();
let keypair2 = Keypair::new();
@ -1216,12 +934,14 @@ pub mod tests {
&keypair1.pubkey(),
1,
bank.last_blockhash(),
0,
);
let fail_tx = system_transaction::create_user_account(
&mint_keypair,
&keypair2.pubkey(),
2,
bank.last_blockhash(),
0,
);
let entry_1_to_mint = next_entry(
@ -1241,87 +961,4 @@ pub mod tests {
// Should not see duplicate signature error
assert_eq!(bank.process_transaction(&fail_tx), Ok(()));
}
#[test]
#[ignore]
fn test_process_entries_stress() {
// this test throws lots of rayon threads at process_entries()
// finds bugs in very low-layer stuff
solana_logger::setup();
let GenesisBlockInfo {
genesis_block,
mint_keypair,
..
} = create_genesis_block(1_000_000_000);
let mut bank = Bank::new(&genesis_block);
const NUM_TRANSFERS: usize = 100;
let keypairs: Vec<_> = (0..NUM_TRANSFERS * 2).map(|_| Keypair::new()).collect();
// give everybody one lamport
for keypair in &keypairs {
bank.transfer(1, &mint_keypair, &keypair.pubkey())
.expect("funding failed");
}
let mut i = 0;
let mut hash = bank.last_blockhash();
loop {
let entries: Vec<_> = (0..NUM_TRANSFERS)
.map(|i| {
next_entry_mut(
&mut hash,
0,
vec![system_transaction::transfer(
&keypairs[i],
&keypairs[i + NUM_TRANSFERS].pubkey(),
1,
bank.last_blockhash(),
)],
)
})
.collect();
info!("paying iteration {}", i);
process_entries(&bank, &entries).expect("paying failed");
let entries: Vec<_> = (0..NUM_TRANSFERS)
.map(|i| {
next_entry_mut(
&mut hash,
0,
vec![system_transaction::transfer(
&keypairs[i + NUM_TRANSFERS],
&keypairs[i].pubkey(),
1,
bank.last_blockhash(),
)],
)
})
.collect();
info!("refunding iteration {}", i);
process_entries(&bank, &entries).expect("refunding failed");
// advance to next block
process_entries(
&bank,
&(0..bank.ticks_per_slot())
.map(|_| next_entry_mut(&mut hash, 1, vec![]))
.collect::<Vec<_>>(),
)
.expect("process ticks failed");
i += 1;
bank = Bank::new_from_parent(&Arc::new(bank), &Pubkey::default(), i as u64);
bank.squash();
}
}
fn get_epoch_schedule(
genesis_block: &GenesisBlock,
account_paths: Option<String>,
) -> EpochSchedule {
let bank = Bank::new_with_paths(&genesis_block, account_paths);
bank.epoch_schedule().clone()
}
}

View File

@ -1,8 +1,8 @@
//! A stage to broadcast data from a leader node to validators
//!
use crate::blocktree::Blocktree;
use crate::cluster_info::{ClusterInfo, ClusterInfoError, DATA_PLANE_FANOUT};
use crate::entry::EntrySlice;
use crate::cluster_info::{ClusterInfo, ClusterInfoError, NEIGHBORHOOD_SIZE};
use crate::entry::{EntrySender, EntrySlice};
use crate::erasure::CodingGenerator;
use crate::packet::index_blobs_with_genesis;
use crate::poh_recorder::WorkingBankEntries;
@ -10,10 +10,8 @@ use crate::result::{Error, Result};
use crate::service::Service;
use crate::staking_utils;
use rayon::prelude::*;
use solana_metrics::{
datapoint, inc_new_counter_debug, inc_new_counter_error, inc_new_counter_info,
inc_new_counter_warn,
};
use solana_metrics::counter::Counter;
use solana_metrics::{influxdb, submit};
use solana_sdk::hash::Hash;
use solana_sdk::pubkey::Pubkey;
use solana_sdk::timing::duration_as_ms;
@ -29,17 +27,9 @@ pub enum BroadcastStageReturnType {
ChannelDisconnected,
}
#[derive(Default)]
struct BroadcastStats {
num_entries: Vec<usize>,
run_elapsed: Vec<u64>,
to_blobs_elapsed: Vec<u64>,
}
struct Broadcast {
id: Pubkey,
coding_generator: CodingGenerator,
stats: BroadcastStats,
}
impl Broadcast {
@ -49,13 +39,14 @@ impl Broadcast {
receiver: &Receiver<WorkingBankEntries>,
sock: &UdpSocket,
blocktree: &Arc<Blocktree>,
storage_entry_sender: &EntrySender,
genesis_blockhash: &Hash,
) -> Result<()> {
let timer = Duration::new(1, 0);
let (mut bank, entries) = receiver.recv_timeout(timer)?;
let mut max_tick_height = bank.max_tick_height();
let run_start = Instant::now();
let now = Instant::now();
let mut num_entries = entries.len();
let mut ventries = Vec::new();
let mut last_tick = entries.last().map(|v| v.1).unwrap_or(0);
@ -83,14 +74,12 @@ impl Broadcast {
}
let bank_epoch = bank.get_stakers_epoch(bank.slot());
let mut broadcast_table = cluster_info
.read()
.unwrap()
.sorted_tvu_peers(staking_utils::staked_nodes_at_epoch(&bank, bank_epoch).as_ref());
inc_new_counter_warn!("broadcast_service-num_peers", broadcast_table.len() + 1);
let mut broadcast_table = cluster_info.read().unwrap().sorted_tvu_peers(
&staking_utils::delegated_stakes_at_epoch(&bank, bank_epoch).unwrap(),
);
inc_new_counter_info!("broadcast_service-num_peers", broadcast_table.len() + 1);
// Layer 1, leader nodes are limited to the fanout size.
broadcast_table.truncate(DATA_PLANE_FANOUT);
broadcast_table.truncate(NEIGHBORHOOD_SIZE);
inc_new_counter_info!("broadcast_service-entries_received", num_entries);
@ -98,9 +87,11 @@ impl Broadcast {
let blobs: Vec<_> = ventries
.into_par_iter()
.map(|p| {
.map_with(storage_entry_sender.clone(), |s, p| {
let entries: Vec<_> = p.into_iter().map(|e| e.0).collect();
entries.to_shared_blobs()
let blobs = entries.to_shared_blobs();
let _ignored = s.send(entries);
blobs
})
.flatten()
.collect();
@ -137,47 +128,33 @@ impl Broadcast {
// Send out data
ClusterInfo::broadcast(&self.id, contains_last_tick, &broadcast_table, sock, &blobs)?;
inc_new_counter_debug!("streamer-broadcast-sent", blobs.len());
inc_new_counter_info!("streamer-broadcast-sent", blobs.len());
// send out erasures
ClusterInfo::broadcast(&self.id, false, &broadcast_table, sock, &coding)?;
self.update_broadcast_stats(
duration_as_ms(&broadcast_start.elapsed()),
duration_as_ms(&run_start.elapsed()),
num_entries,
to_blobs_elapsed,
blob_index,
let broadcast_elapsed = duration_as_ms(&broadcast_start.elapsed());
inc_new_counter_info!(
"broadcast_service-time_ms",
duration_as_ms(&now.elapsed()) as usize
);
info!(
"broadcast: {} entries, blob time {} broadcast time {}",
num_entries, to_blobs_elapsed, broadcast_elapsed
);
submit(
influxdb::Point::new("broadcast-service")
.add_field(
"transmit-index",
influxdb::Value::Integer(blob_index as i64),
)
.to_owned(),
);
Ok(())
}
fn update_broadcast_stats(
&mut self,
broadcast_elapsed: u64,
run_elapsed: u64,
num_entries: usize,
to_blobs_elapsed: u64,
blob_index: u64,
) {
inc_new_counter_info!("broadcast_service-time_ms", broadcast_elapsed as usize);
self.stats.num_entries.push(num_entries);
self.stats.to_blobs_elapsed.push(to_blobs_elapsed);
self.stats.run_elapsed.push(run_elapsed);
if self.stats.num_entries.len() >= 16 {
info!(
"broadcast: entries: {:?} blob times ms: {:?} broadcast times ms: {:?}",
self.stats.num_entries, self.stats.to_blobs_elapsed, self.stats.run_elapsed
);
self.stats.num_entries.clear();
self.stats.to_blobs_elapsed.clear();
self.stats.run_elapsed.clear();
}
datapoint!("broadcast-service", ("transmit-index", blob_index, i64));
}
}
// Implement a destructor for the BroadcastStage thread to signal it exited
@ -209,6 +186,7 @@ impl BroadcastStage {
cluster_info: &Arc<RwLock<ClusterInfo>>,
receiver: &Receiver<WorkingBankEntries>,
blocktree: &Arc<Blocktree>,
storage_entry_sender: EntrySender,
genesis_blockhash: &Hash,
) -> BroadcastStageReturnType {
let me = cluster_info.read().unwrap().my_data().clone();
@ -217,13 +195,17 @@ impl BroadcastStage {
let mut broadcast = Broadcast {
id: me.id,
coding_generator,
stats: BroadcastStats::default(),
};
loop {
if let Err(e) =
broadcast.run(&cluster_info, receiver, sock, blocktree, genesis_blockhash)
{
if let Err(e) = broadcast.run(
&cluster_info,
receiver,
sock,
blocktree,
&storage_entry_sender,
genesis_blockhash,
) {
match e {
Error::RecvTimeoutError(RecvTimeoutError::Disconnected) | Error::SendError => {
return BroadcastStageReturnType::ChannelDisconnected;
@ -231,7 +213,7 @@ impl BroadcastStage {
Error::RecvTimeoutError(RecvTimeoutError::Timeout) => (),
Error::ClusterInfoError(ClusterInfoError::NoPeers) => (), // TODO: Why are the unit-tests throwing hundreds of these?
_ => {
inc_new_counter_error!("streamer-broadcaster-error", 1, 1);
inc_new_counter_info!("streamer-broadcaster-error", 1, 1);
error!("broadcaster error: {:?}", e);
}
}
@ -261,6 +243,7 @@ impl BroadcastStage {
receiver: Receiver<WorkingBankEntries>,
exit_sender: &Arc<AtomicBool>,
blocktree: &Arc<Blocktree>,
storage_entry_sender: EntrySender,
genesis_blockhash: &Hash,
) -> Self {
let blocktree = blocktree.clone();
@ -275,6 +258,7 @@ impl BroadcastStage {
&cluster_info,
&receiver,
&blocktree,
storage_entry_sender,
&genesis_blockhash,
)
})
@ -298,9 +282,9 @@ mod test {
use crate::blocktree::{get_tmp_ledger_path, Blocktree};
use crate::cluster_info::{ClusterInfo, Node};
use crate::entry::create_ticks;
use crate::genesis_utils::{create_genesis_block, GenesisBlockInfo};
use crate::service::Service;
use solana_runtime::bank::Bank;
use solana_sdk::genesis_block::GenesisBlock;
use solana_sdk::hash::Hash;
use solana_sdk::signature::{Keypair, KeypairUtil};
use std::sync::atomic::AtomicBool;
@ -336,8 +320,9 @@ mod test {
let cluster_info = Arc::new(RwLock::new(cluster_info));
let exit_sender = Arc::new(AtomicBool::new(false));
let (storage_sender, _receiver) = channel();
let GenesisBlockInfo { genesis_block, .. } = create_genesis_block(10_000);
let (genesis_block, _) = GenesisBlock::new(10_000);
let bank = Arc::new(Bank::new(&genesis_block));
// Start up the broadcast stage
@ -347,6 +332,7 @@ mod test {
entry_receiver,
&exit_sender,
&blocktree,
storage_sender,
&Hash::default(),
);

View File

@ -1,11 +1,12 @@
use crate::blocktree::Blocktree;
use solana_storage_api::SLOTS_PER_SEGMENT;
use std::fs::File;
use std::io;
use std::io::{BufWriter, Write};
use std::path::Path;
use std::sync::Arc;
use crate::storage_stage::ENTRIES_PER_SEGMENT;
pub const CHACHA_BLOCK_SIZE: usize = 64;
pub const CHACHA_KEY_SIZE: usize = 32;
@ -49,7 +50,8 @@ pub fn chacha_cbc_encrypt_ledger(
let mut entry = slice;
loop {
match blocktree.read_blobs_bytes(0, SLOTS_PER_SEGMENT - total_entries, &mut buffer, entry) {
match blocktree.read_blobs_bytes(entry, ENTRIES_PER_SEGMENT - total_entries, &mut buffer, 0)
{
Ok((num_entries, entry_len)) => {
debug!(
"chacha: encrypting slice: {} num_entries: {} entry_len: {}",
@ -122,6 +124,7 @@ mod tests {
&keypair.pubkey(),
1,
one,
0,
)],
)
})
@ -153,11 +156,13 @@ mod tests {
let mut hasher = Hasher::default();
hasher.hash(&buf[..size]);
use bs58;
// golden needs to be updated if blob stuff changes....
let golden: Hash = "9xb2Asf7UK5G8WqPwsvzo5xwLi4dixBSDiYKCtYRikA"
.parse()
.unwrap();
let golden = Hash::new(
&bs58::decode("5Pz5KQyNht2nqkJhVd8F9zTFxzoDvbQSzaxQbtCPiyCo")
.into_vec()
.unwrap(),
);
assert_eq!(hasher.result(), golden);
remove_file(out_path).unwrap();
}

View File

@ -7,11 +7,12 @@ use crate::sigverify::{
chacha_cbc_encrypt_many_sample, chacha_end_sha_state, chacha_init_sha_state,
};
use solana_sdk::hash::Hash;
use solana_storage_api::SLOTS_PER_SEGMENT;
use std::io;
use std::mem::size_of;
use std::sync::Arc;
use crate::storage_stage::ENTRIES_PER_SEGMENT;
// Encrypt a file with multiple starting IV states, determined by ivecs.len()
//
// Then sample each block at the offsets provided by samples argument with sha256
@ -46,7 +47,8 @@ pub fn chacha_cbc_encrypt_file_many_keys(
chacha_init_sha_state(int_sha_states.as_mut_ptr(), num_keys as u32);
}
loop {
match blocktree.read_blobs_bytes(entry, SLOTS_PER_SEGMENT - total_entries, &mut buffer, 0) {
match blocktree.read_blobs_bytes(entry, ENTRIES_PER_SEGMENT - total_entries, &mut buffer, 0)
{
Ok((num_entries, entry_len)) => {
debug!(
"chacha_cuda: encrypting segment: {} num_entries: {} entry_len: {}",
@ -76,9 +78,9 @@ pub fn chacha_cbc_encrypt_file_many_keys(
entry += num_entries;
debug!(
"total entries: {} entry: {} segment: {} entries_per_segment: {}",
total_entries, entry, segment, SLOTS_PER_SEGMENT
total_entries, entry, segment, ENTRIES_PER_SEGMENT
);
if (entry - segment) >= SLOTS_PER_SEGMENT {
if (entry - segment) >= ENTRIES_PER_SEGMENT {
break;
}
}

View File

@ -1,6 +1,6 @@
use solana_sdk::pubkey::Pubkey;
pub trait Cluster {
fn get_node_pubkeys(&self) -> Vec<Pubkey>;
fn get_node_ids(&self) -> Vec<Pubkey>;
fn restart_node(&mut self, pubkey: Pubkey);
}

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More