Compare commits

..

42 Commits

Author SHA1 Message Date
Michael Vines
f83694f8e5 Lock blockexplorer version 2019-08-24 20:04:36 -07:00
Michael Vines
ae3d0010a3 Revert "Disable osx deploy due to failing macOS CI"
This reverts commit 0a5c54a0ef.
2019-08-20 22:28:52 -07:00
Michael Vines
dcffbab82e Update secure variable 2019-08-20 21:49:04 -07:00
Michael Vines
98bae5ea98 Update secure variables 2019-08-20 21:49:00 -07:00
Michael Vines
fa26cc05c3 Drop os version to resolve Appveyor Server build failure 2019-08-20 21:48:55 -07:00
mergify[bot]
e480e5444d Delete terminated GCP instances (#5490) (#5492)
automerge

(cherry picked from commit 6085109171)
2019-08-12 08:58:26 -07:00
mergify[bot]
cf9e6c9ab7 Increase the amount of lamports a validator starts with (#5468)
automerge
2019-08-08 12:04:48 -07:00
Michael Vines
0a5c54a0ef Disable osx deploy due to failing macOS CI 2019-08-05 17:27:09 -07:00
Michael Vines
7f7a868234 Skip sanity on blockstreamer node at cluster boot.
It may not have caught up to the bootstrap leader yet...
2019-08-05 17:10:36 -07:00
Michael Vines
421ad7a0a1 Remove boot_from_snapshot 2019-08-05 16:43:27 -07:00
Michael Vines
139c490d1d Remove unused var 2019-08-04 21:29:37 -07:00
Michael Vines
8a873365bc Move testnet from ec2 tp gcp 2019-08-04 21:02:25 -07:00
Michael Vines
fc2f922e15 Move edge/beta testnets from ec2 to gcp 2019-08-04 20:42:27 -07:00
Michael Vines
4279847efd Reduce size of cpu-only gcp instances 2019-08-04 20:36:25 -07:00
Michael Vines
bced640541 Reduce AWS node count 2019-08-03 23:51:22 -07:00
mergify[bot]
21e8bbf955 Change bank to not create default (#5409) (#5410)
automerge
2019-08-02 15:27:27 -07:00
mergify[bot]
a7c6067e59 getProgramAccounts to check for existing validator-info (#5404) (#5405)
automerge
2019-08-02 08:15:23 -07:00
Michael Vines
e1475ca74b Remove sdk-c from the virtual manifest temporarily
For an unknown reason |cargo clippy| is getting stuck in CI
intermittently when trying to build this crate.
2019-08-01 21:10:13 -07:00
mergify[bot]
fd48ac1896 fix epoch_stakes again (#5396) (#5398)
automerge
2019-08-01 15:19:28 -07:00
mergify[bot]
2892c36d47 Change default location of solana.h to OUT_DIR (#5389) (#5392)
automerge
2019-08-01 15:02:10 -07:00
mergify[bot]
c2bd971696 Don't rebuild/retest release tags (#5385) (#5394)
(cherry picked from commit 5212b2716c)
2019-08-01 13:41:27 -07:00
Michael Vines
f324099c30 Give crate publishing even more time 2019-08-01 11:18:12 -07:00
Michael Vines
d30326ac5a Cargo.lock 2019-08-01 11:04:14 -07:00
Michael Vines
0cb99e8ab8 Depersonalize paths 2019-08-01 08:34:47 -07:00
Michael Vines
f82f620c7e Bump version to 0.17.2 2019-08-01 08:02:23 -07:00
Michael Vines
beb8e89bf4 Bump version to 0.17.1 2019-08-01 07:48:42 -07:00
mergify[bot]
54ff4529b1 Plumb libra accounts to genesis (bp #5333) (#5379)
automerge
2019-07-31 22:44:14 -07:00
mergify[bot]
0942cbd89b Handle paying for move transactions with unique solana system transactions (#5317) (#5378)
automerge
2019-07-31 22:02:28 -07:00
mergify[bot]
88bbaf7add Teach solana-install about release channels (#5372) (#5375)
automerge
2019-07-31 18:18:25 -07:00
Jack May
d1447b5e52 Synchronize and cleanup instruction processor lists (#5356) 2019-07-31 17:37:52 -07:00
mergify[bot]
c00bb42ecd Add command to create genesis accounts (#5343) (#5371)
automerge
2019-07-31 17:13:40 -07:00
mergify[bot]
1a0003fbcc fix epoch_stakes (#5355) (#5369)
automerge
2019-07-31 15:56:48 -07:00
Michael Vines
a584ce6472 Ignore flaky test_replicator_startup_2_nodes (#5358)
automerge
2019-07-31 11:35:58 -07:00
mergify[bot]
63d1f029a9 Ignore flaky local cluster tests (#5347) (#5349)
* Add logging to local_cluster tests

* Ignore flaky test_leader_failure_4, test_repairman_catchup

And crashing banking benchmarks.

(cherry picked from commit 8d243221f0)
2019-07-30 23:08:59 -07:00
Michael Vines
24ebf70016 Bump timeouts for publish docker/tarball builds 2019-07-30 20:10:09 -07:00
Dan Albert
d18dc94209 Update testnet book source to release 0.17.0 (#5339) 2019-07-29 18:53:00 -06:00
mergify[bot]
8242fd19eb Move coverage back to the default queue (#5318) (#5320)
(cherry picked from commit 506b305959)
2019-07-28 23:17:45 -07:00
mergify[bot]
469e91cd8d Add --use_move mode to bench-tps (#5311) (#5316)
automerge
2019-07-28 14:15:57 -07:00
mergify[bot]
4889c2a29c Add move mode to bench-tps (bp #5250) (#5310)
automerge
2019-07-27 17:51:12 -07:00
mergify[bot]
3c6115c94a Pull all libra crates from crates.io (bp #5306) (#5307)
automerge
2019-07-27 15:48:41 -07:00
mergify[bot]
70b15317a9 Move credit-only and Move proposals to the implemented section of the book (#5308) (#5309)
automerge
2019-07-27 15:41:18 -07:00
mergify[bot]
a834e9ae10 Add libray_api (bp #5304) (#5305)
automerge
2019-07-27 13:30:59 -07:00
383 changed files with 8230 additions and 17369 deletions

View File

@@ -9,8 +9,6 @@ cache:
- '%USERPROFILE%\.cargo'
- '%APPVEYOR_BUILD_FOLDER%\target'
clone_folder: d:\projects\solana
build_script:
- bash ci/publish-tarball.sh

24
.github/stale.yml vendored
View File

@@ -1,24 +0,0 @@
only: pulls
# Number of days of inactivity before a pull request becomes stale
daysUntilStale: 30
# Number of days of inactivity before a stale pull request is closed
daysUntilClose: 7
# Issues with these labels will never be considered stale
exemptLabels:
- security
# Label to use when marking a pull request as stale
staleLabel: stale
# Comment to post when marking a pull request as stale. Set to `false` to disable
markComment: >
This pull request has been automatically marked as stale because it has not had
recent activity. It will be closed if no further activity occurs.
# Comment to post when closing a stale pull request. Set to `false` to disable
closeComment: >
This stale pull request has been automatically closed.
Thank you for your contributions.

3
.gitignore vendored
View File

@@ -11,7 +11,10 @@
**/*.rs.bk
.cargo
# node config that is rsynced
/config/
# node config that remains local
/config-local/
# log files
*.log

View File

@@ -43,35 +43,3 @@ pull_request_rules:
backport:
branches:
- v0.18
- name: v0.19 backport
conditions:
- base=master
- label=v0.19
actions:
backport:
branches:
- v0.19
- name: v0.20 backport
conditions:
- base=master
- label=v0.20
actions:
backport:
branches:
- v0.20
- name: v0.21 backport
conditions:
- base=master
- label=v0.21
actions:
backport:
branches:
- v0.21
- name: v0.22 backport
conditions:
- base=master
- label=v0.22
actions:
backport:
branches:
- v0.22

View File

@@ -2,8 +2,9 @@ os:
- osx
language: rust
cache: cargo
rust:
- 1.37.0
- 1.36.0
install:
- source ci/rust-version.sh

2190
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -15,11 +15,11 @@ members = [
"keygen",
"kvstore",
"ledger-tool",
"local_cluster",
"logger",
"merkle-tree",
"measure",
"metrics",
"netutil",
"programs/bpf",
"programs/bpf_loader_api",
"programs/bpf_loader_program",
@@ -27,7 +27,6 @@ members = [
"programs/budget_program",
"programs/config_api",
"programs/config_program",
"programs/config_tests",
"programs/exchange_api",
"programs/exchange_program",
"programs/failure_program",
@@ -47,13 +46,10 @@ members = [
"replicator",
"runtime",
"sdk",
"sdk-c",
"upload-perf",
"validator-info",
"utils/netutil",
"utils/fixed_buf",
"vote-signer",
"cli",
"wallet",
]
exclude = [

View File

@@ -78,7 +78,7 @@ $ source $HOME/.cargo/env
$ rustup component add rustfmt
```
If your rustc version is lower than 1.37.0, please update it:
If your rustc version is lower than 1.34.0, please update it:
```bash
$ rustup update
@@ -240,3 +240,5 @@ problem is solved by this code?" On the other hand, if a test does fail and you
better way to solve the same problem, a Pull Request with your solution would most certainly be
welcome! Likewise, if rewriting a test can better communicate what code it's protecting, please
send us that patch!

View File

@@ -2,7 +2,7 @@
authors = ["Solana Maintainers <maintainers@solana.com>"]
edition = "2018"
name = "solana-bench-exchange"
version = "0.18.1"
version = "0.17.2"
repository = "https://github.com/solana-labs/solana"
license = "Apache-2.0"
homepage = "https://solana.com/"
@@ -10,34 +10,33 @@ publish = false
[dependencies]
bincode = "1.1.4"
bs58 = "0.2.4"
bs58 = "0.2.0"
clap = "2.32.0"
env_logger = "0.6.2"
itertools = "0.8.0"
log = "0.4.8"
log = "0.4.7"
num-derive = "0.2"
num-traits = "0.2"
rand = "0.6.5"
rayon = "1.1.0"
serde = "1.0.99"
serde_derive = "1.0.99"
serde = "1.0.97"
serde_derive = "1.0.97"
serde_json = "1.0.40"
serde_yaml = "0.8.9"
# solana-runtime = { path = "../solana/runtime"}
solana-core = { path = "../core", version = "0.18.1" }
solana-local-cluster = { path = "../local_cluster", version = "0.18.1" }
solana-client = { path = "../client", version = "0.18.1" }
solana-drone = { path = "../drone", version = "0.18.1" }
solana-exchange-api = { path = "../programs/exchange_api", version = "0.18.1" }
solana-exchange-program = { path = "../programs/exchange_program", version = "0.18.1" }
solana-logger = { path = "../logger", version = "0.18.1" }
solana-metrics = { path = "../metrics", version = "0.18.1" }
solana-netutil = { path = "../utils/netutil", version = "0.18.1" }
solana-runtime = { path = "../runtime", version = "0.18.1" }
solana-sdk = { path = "../sdk", version = "0.18.1" }
solana = { path = "../core", version = "0.17.2" }
solana-client = { path = "../client", version = "0.17.2" }
solana-drone = { path = "../drone", version = "0.17.2" }
solana-exchange-api = { path = "../programs/exchange_api", version = "0.17.2" }
solana-exchange-program = { path = "../programs/exchange_program", version = "0.17.2" }
solana-logger = { path = "../logger", version = "0.17.2" }
solana-metrics = { path = "../metrics", version = "0.17.2" }
solana-netutil = { path = "../netutil", version = "0.17.2" }
solana-runtime = { path = "../runtime", version = "0.17.2" }
solana-sdk = { path = "../sdk", version = "0.17.2" }
untrusted = "0.7.0"
ws = "0.9.0"
ws = "0.8.1"
[features]
cuda = ["solana-core/cuda"]
cuda = ["solana/cuda"]

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 order requests to
the exchange. A Matcher monitors the exchange and posts swap requests for
the exchange. A Swapper monitors the exchange and posts swap requests for
matching orders. All the transactions can execute concurrently.
## Premise
@@ -42,26 +42,30 @@ matching orders. All the transactions can execute concurrently.
- A request to create a token account
- Token request
- A request to deposit tokens of a particular type into a token account.
- Asset pair
- A struct with fields Base and Quote, representing the two assets which make up a
trading pair, which themselves are Tokens. The Base or 'primary' asset is the
numerator and the Quote is the denominator for pricing purposes.
- Order side
- Describes which side of the market an investor wants to place a trade on. Options
are "Bid" or "Ask", where a bid represents an offer to purchase the Base asset of
the AssetPair for a sum of the Quote Asset and an Ask is an offer to sell Base asset
for the Quote asset.
- Token pair
- A unique ordered list of two tokens. For the four types of tokens used in
this demo, the valid pairs are AB, AC, AD, BC, BD, CD.
- Direction of trade
- Describes which token in the pair the investor wants to sell and buy and can
be either "To" or "From". For example, if an investor issues a "To" trade
for "AB" then they which to exchange A tokens to B tokens. A "From" order
would read the other way, A tokens from B tokens.
- Price ratio
- An expression of the relative prices of two tokens. Calculated with the Base
Asset as the numerator and the Quote Asset as the denominator. Ratios are
represented as fixed point numbers. The fixed point scaler is defined in
- An expression of the relative prices of two tokens. They consist of the
price of the primary token and the price of the secondary token. For
simplicity sake, the primary token's price is always 1, which forces the
secondary to be the common denominator. For example, if token A was worth
2 and token B was worth 6, the price ratio would be 1:3 or just 3. Price
ratios are represented as fixed point numbers. The fixed point scaler is
defined in
[exchange_state.rs](https://github.com/solana-labs/solana/blob/c2fdd1362a029dcf89c8907c562d2079d977df11/programs/exchange_api/src/exchange_state.rs#L7)
- Order request
- A Solana transaction sent by a trader to the exchange to submit an order.
Order requests are made up of the token pair, the order side (bid or ask),
quantity of the primary token, the price ratio, and the two token accounts
to be credited/deducted. An example trade request looks like "T AB 5 2"
which reads "Exchange 5 A tokens to B tokens at a price ratio of 1:2" A fulfilled trade would result in 5 A tokens
- A Solana transaction executed by the exchange requesting the trade of one
type of token for another. order requests are made up of the token pair,
the direction of the trade, quantity of the primary token, the price ratio,
and the two token accounts to be credited/deducted. An example trade
request looks like "T AB 5 2" which reads "Exchange 5 A tokens to B tokens
at a price ratio of 1:2" A fulfilled trade would result in 5 A tokens
deducted and 10 B tokens credited to the trade initiator's token accounts.
Successful order requests result in an order.
- Order
@@ -71,62 +75,59 @@ matching orders. All the transactions can execute concurrently.
contain the same information as the order request.
- Price spread
- The difference between the two matching orders. The spread is the
profit of the Matcher initiating the swap request.
- Match requirements
profit of the Swapper initiating the swap request.
- Swap requirements
- Policies that result in a successful trade swap.
- Match request
- A request to fill two complementary orders (bid/ask), resulting if successful,
in a trade being created.
- Trade
- A successful trade is created from two matching orders that meet
swap requirements which are submitted in a Match Request by a Matcher and
executed by the exchange. A trade may not wholly satisfy one or both of the
orders in which case the orders are adjusted appropriately. Upon execution,
tokens are distributed to the traders' accounts and any overlap or
"negative spread" between orders is deposited into the Matcher's profit
account. All successful trades are recorded in the data of a new solana
account for posterity.
- Swap request
- A request to exchange tokens between to orders
- Trade swap
- A successful trade. A swap consists of two matching orders that meet
swap requirements. A trade swap may not wholly satisfy one or both of the
orders in which case the 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
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
the exchange. Investors operate as Solana thin clients who own a set of
accounts containing tokens and/or order requests. Investors post
transactions to the exchange in order to request tokens and post or cancel
order requests.
- Matcher
- An agent who facilitates trading between investors. Matchers operate as
- Swapper
- An agent who facilitates trading between investors. Swappers operate as
Solana thin clients who monitor all the orders looking for a trade
match. Once found, the Matcher issues a swap request to the exchange.
Matchers are the engine of the exchange and are rewarded for their efforts by
accumulating the price spreads of the swaps they initiate. Matchers also
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
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 Matchers.
the Investors and Swappers.
## Exchange startup
The exchange is up and running when it reaches a state where it can take
investors' trades and Matchers' match requests. To achieve this state the
investor's trades and Swapper's swap requests. To achieve this state the
following must occur in order:
- Start the Solana blockchain
- Start the thin-client
- The Matcher subscribes to change notifications for all the accounts owned by
- Start the Swapper thin-client
- The Swapper 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 Matcher starts responding to queries for bid/ask price and OHLCV
- The Swapper starts responding to queries for bid/ask price and OHLCV
The Matcher responding successfully to price and OHLCV requests is the signal to
The Swapper 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 Matcher could come and go without missing a trade. One way to achieve
this is for the Matcher to read the current state of all accounts looking for all
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
open 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. Matcher as well will
each type of token, they will submit account requests. Swappers as well will
request accounts to hold the tokens they earn by initiating trade swaps.
```rust
@@ -164,7 +165,7 @@ pub struct TokenAccountInfo {
}
```
For this demo investors or Matcher can request more tokens from the exchange at
For this demo investors or Swappers 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.
@@ -268,10 +269,10 @@ pub enum ExchangeInstruction {
## Trade swaps
The Matcher is monitoring the accounts assigned to the exchange program and
The Swapper is monitoring the accounts assigned to the exchange program and
building a trade-order table. The order table is used to identify
matching orders which could be fulfilled. When a match is found the
Matcher should issue a swap request. Swap requests may not satisfy the entirety
Swapper 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 order valid for further swap requests in
the future.
@@ -309,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
Matcher's account equal to the difference in the price ratios or the two orders.
These tokens are considered the Matcher's profit for initiating the trade.
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.
The Matcher would initiate the following swap on the order table above:
The Swapper 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
- Matcher takes 8 B tokens as profit
- Swapper takes 8 B tokens as profit
Both row 1 trades are fully realized, table becomes:
@@ -327,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 Matcher would initiate the following swap:
The Swapper 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
- Matcher takes 4 B tokens as profit
- Swapper takes 4 B tokens as profit
Row 1 From is not fully realized, table becomes:
@@ -342,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 Matcher would initiate the following swap:
The Swapper 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
- Matcher takes 2 B tokens as profit
- Swapper takes 2 B tokens as profit
Row 1 To is now fully realized, table becomes:
@@ -356,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 Matcher would initiate the following last swap:
The Swapper 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
- Matcher takes 4 B tokens as profit
- Swapper takes 4 B tokens as profit
Table becomes:
@@ -382,7 +383,7 @@ pub enum ExchangeInstruction {
/// key 3 - `From` 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 Matcher profit from the swap.
/// key 6 - Token account in which to deposit the Swappers profit from the swap.
SwapRequest,
}
@@ -441,14 +442,14 @@ pub enum ExchangeInstruction {
/// key 3 - `From` 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 Matcher profit from the swap.
/// key 6 - Token account in which to deposit the Swappers profit from the swap.
SwapRequest,
}
```
## Quotes and OHLCV
The Matcher will provide current bid/ask price quotes based on trade actively and
The Swapper 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

@@ -5,8 +5,8 @@ use itertools::izip;
use log::*;
use rand::{thread_rng, Rng};
use rayon::prelude::*;
use solana::gen_keys::GenKeys;
use solana_client::perf_utils::{sample_txs, SampleStats};
use solana_core::gen_keys::GenKeys;
use solana_drone::drone::request_airdrop_transaction;
use solana_exchange_api::exchange_instruction;
use solana_exchange_api::exchange_state::*;
@@ -527,21 +527,21 @@ fn trader<T>(
let mut trade_infos = vec![];
let start = account_group * batch_size as usize;
let end = account_group * batch_size as usize + batch_size as usize;
let mut side = OrderSide::Ask;
let mut direction = Direction::To;
for (signer, trade, src) in izip!(
signers[start..end].iter(),
trade_keys,
srcs[start..end].iter(),
) {
side = if side == OrderSide::Ask {
OrderSide::Bid
direction = if direction == Direction::To {
Direction::From
} else {
OrderSide::Ask
Direction::To
};
let order_info = OrderInfo {
/// Owner of the trade order
owner: Pubkey::default(), // don't care
side,
direction,
pair,
tokens,
price,
@@ -551,7 +551,7 @@ fn trader<T>(
trade_account: trade.pubkey(),
order_info,
});
trades.push((signer, trade.pubkey(), side, src));
trades.push((signer, trade.pubkey(), direction, src));
}
account_group = (account_group + 1) % account_groups as usize;
@@ -562,7 +562,7 @@ fn trader<T>(
trades.chunks(chunk_size).for_each(|chunk| {
let trades_txs: Vec<_> = chunk
.par_iter()
.map(|(signer, trade, side, src)| {
.map(|(signer, trade, direction, src)| {
let s: &Keypair = &signer;
let owner = &signer.pubkey();
let space = mem::size_of::<ExchangeState>() as u64;
@@ -571,7 +571,7 @@ fn trader<T>(
vec![
system_instruction::create_account(owner, trade, 1, space, &id()),
exchange_instruction::trade_request(
owner, trade, *side, pair, tokens, price, src,
owner, trade, *direction, pair, tokens, price, src,
),
],
blockhash,
@@ -660,7 +660,7 @@ fn verify_funding_transfer<T: SyncClient + ?Sized>(
false
}
pub fn fund_keys(client: &dyn Client, source: &Keypair, dests: &[Arc<Keypair>], lamports: u64) {
pub fn fund_keys(client: &Client, source: &Keypair, dests: &[Arc<Keypair>], lamports: u64) {
let total = lamports * (dests.len() as u64 + 1);
let mut funded: Vec<(&Keypair, u64)> = vec![(source, total)];
let mut notfunded: Vec<&Arc<Keypair>> = dests.iter().collect();
@@ -778,7 +778,7 @@ pub fn fund_keys(client: &dyn Client, source: &Keypair, dests: &[Arc<Keypair>],
}
}
pub fn create_token_accounts(client: &dyn Client, signers: &[Arc<Keypair>], accounts: &[Pubkey]) {
pub fn create_token_accounts(client: &Client, signers: &[Arc<Keypair>], accounts: &[Pubkey]) {
let mut notfunded: Vec<(&Arc<Keypair>, &Pubkey)> = signers.iter().zip(accounts).collect();
while !notfunded.is_empty() {
@@ -908,7 +908,7 @@ fn generate_keypairs(num: u64) -> Vec<Keypair> {
rnd.gen_n_keypairs(num)
}
pub fn airdrop_lamports(client: &dyn Client, drone_addr: &SocketAddr, id: &Keypair, amount: u64) {
pub fn airdrop_lamports(client: &Client, drone_addr: &SocketAddr, id: &Keypair, amount: u64) {
let balance = client.get_balance(&id.pubkey());
let balance = balance.unwrap_or(0);
if balance >= amount {
@@ -963,11 +963,11 @@ pub fn airdrop_lamports(client: &dyn Client, drone_addr: &SocketAddr, id: &Keypa
#[cfg(test)]
mod tests {
use super::*;
use solana_core::gossip_service::{discover_cluster, get_multi_client};
use solana_core::validator::ValidatorConfig;
use solana::gossip_service::{discover_cluster, get_multi_client};
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_local_cluster::local_cluster::{ClusterConfig, LocalCluster};
use solana_runtime::bank::Bank;
use solana_runtime::bank_client::BankClient;
use solana_sdk::genesis_block::create_genesis_block;

View File

@@ -1,5 +1,5 @@
use clap::{crate_description, crate_name, crate_version, value_t, App, Arg, ArgMatches};
use solana_core::gen_keys::GenKeys;
use solana::gen_keys::GenKeys;
use solana_drone::drone::DRONE_PORT;
use solana_sdk::signature::{read_keypair, Keypair, KeypairUtil};
use std::net::SocketAddr;

View File

@@ -8,7 +8,7 @@ extern crate solana_exchange_program;
use crate::bench::{airdrop_lamports, create_client_accounts_file, do_bench_exchange, Config};
use log::*;
use solana_core::gossip_service::{discover_cluster, get_multi_client};
use solana::gossip_service::{discover_cluster, get_multi_client};
use solana_sdk::signature::KeypairUtil;
fn main() {

View File

@@ -96,12 +96,12 @@ impl OrderBook {
// Ok(())
// }
pub fn push(&mut self, pubkey: Pubkey, info: OrderInfo) -> Result<(), Box<dyn error::Error>> {
check_trade(info.side, info.tokens, info.price)?;
match info.side {
OrderSide::Ask => {
check_trade(info.direction, info.tokens, info.price)?;
match info.direction {
Direction::To => {
self.to_ab.push(ToOrder { pubkey, info });
}
OrderSide::Bid => {
Direction::From => {
self.from_ab.push(FromOrder { pubkey, info });
}
}

View File

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

View File

@@ -1,8 +1,8 @@
use clap::{crate_description, crate_name, crate_version, App, Arg};
use solana_core::packet::PacketsRecycler;
use solana_core::packet::{Packet, Packets, BLOB_SIZE, PACKET_DATA_SIZE};
use solana_core::result::Result;
use solana_core::streamer::{receiver, PacketReceiver};
use solana::packet::PacketsRecycler;
use solana::packet::{Packet, Packets, BLOB_SIZE, PACKET_DATA_SIZE};
use solana::result::Result;
use solana::streamer::{receiver, PacketReceiver};
use std::cmp::max;
use std::net::{IpAddr, Ipv4Addr, SocketAddr, UdpSocket};
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};

View File

@@ -2,7 +2,7 @@
authors = ["Solana Maintainers <maintainers@solana.com>"]
edition = "2018"
name = "solana-bench-tps"
version = "0.18.1"
version = "0.17.2"
repository = "https://github.com/solana-labs/solana"
license = "Apache-2.0"
homepage = "https://solana.com/"
@@ -10,26 +10,25 @@ homepage = "https://solana.com/"
[dependencies]
bincode = "1.1.4"
clap = "2.33.0"
log = "0.4.8"
log = "0.4.7"
rayon = "1.1.0"
serde = "1.0.99"
serde_derive = "1.0.99"
serde = "1.0.97"
serde_derive = "1.0.97"
serde_json = "1.0.40"
serde_yaml = "0.8.9"
solana-core = { path = "../core", version = "0.18.1" }
solana-local-cluster = { path = "../local_cluster", version = "0.18.1" }
solana-client = { path = "../client", version = "0.18.1" }
solana-drone = { path = "../drone", version = "0.18.1" }
solana-librapay-api = { path = "../programs/librapay_api", version = "0.18.1" }
solana-logger = { path = "../logger", version = "0.18.1" }
solana-metrics = { path = "../metrics", version = "0.18.1" }
solana-measure = { path = "../measure", version = "0.18.1" }
solana-netutil = { path = "../utils/netutil", version = "0.18.1" }
solana-runtime = { path = "../runtime", version = "0.18.1" }
solana-sdk = { path = "../sdk", version = "0.18.1" }
solana-move-loader-program = { path = "../programs/move_loader_program", version = "0.18.1" }
solana-move-loader-api = { path = "../programs/move_loader_api", version = "0.18.1" }
solana = { path = "../core", version = "0.17.2" }
solana-client = { path = "../client", version = "0.17.2" }
solana-drone = { path = "../drone", version = "0.17.2" }
solana-librapay-api = { path = "../programs/librapay_api", version = "0.17.2" }
solana-logger = { path = "../logger", version = "0.17.2" }
solana-metrics = { path = "../metrics", version = "0.17.2" }
solana-measure = { path = "../measure", version = "0.17.2" }
solana-netutil = { path = "../netutil", version = "0.17.2" }
solana-runtime = { path = "../runtime", version = "0.17.2" }
solana-sdk = { path = "../sdk", version = "0.17.2" }
solana-move-loader-program = { path = "../programs/move_loader_program", version = "0.17.2" }
solana-move-loader-api = { path = "../programs/move_loader_api", version = "0.17.2" }
[features]
cuda = ["solana-core/cuda"]
cuda = ["solana/cuda"]

View File

@@ -3,8 +3,8 @@ use solana_metrics;
use bincode;
use log::*;
use rayon::prelude::*;
use solana::gen_keys::GenKeys;
use solana_client::perf_utils::{sample_txs, SampleStats};
use solana_core::gen_keys::GenKeys;
use solana_drone::drone::request_airdrop_transaction;
use solana_librapay_api::{create_genesis, upload_mint_program, upload_payment_program};
use solana_measure::measure::Measure;
@@ -440,12 +440,7 @@ pub fn fund_keys<T: Client>(
let mut notfunded: Vec<&Keypair> = dests.iter().collect();
let lamports_per_account = (total - (extra * max_fee)) / (notfunded.len() as u64 + 1);
println!(
"funding keys {} with lamports: {:?} total: {}",
dests.len(),
client.get_balance(&source.pubkey()),
total
);
println!("funding keys {}", dests.len());
while !notfunded.is_empty() {
let mut new_funded: Vec<(&Keypair, u64)> = vec![];
let mut to_fund = vec![];
@@ -710,7 +705,10 @@ fn fund_move_keys<T: Client>(
1,
blockhash,
);
client.send_message(&[funding_key], tx.message).unwrap();
let sig = client
.async_send_transaction(tx)
.expect("create_account in generate_and_fund_keypairs");
client.poll_for_signature(&sig).unwrap();
info!("minting to funding keypair");
let tx = librapay_transaction::mint_tokens(
@@ -721,11 +719,12 @@ fn fund_move_keys<T: Client>(
total,
blockhash,
);
client
.send_message(&[funding_key, libra_mint_key], tx.message)
.unwrap();
let sig = client
.async_send_transaction(tx)
.expect("create_account in generate_and_fund_keypairs");
client.poll_for_signature(&sig).unwrap();
info!("creating {} move accounts...", keypairs.len());
info!("creating move accounts.. {}", keypairs.len());
let create_len = 8;
let mut funding_time = Measure::start("funding_time");
for (i, keys) in keypairs.chunks(create_len).enumerate() {
@@ -734,116 +733,97 @@ fn fund_move_keys<T: Client>(
break;
}
let mut tx_send = Measure::start("poll");
let pubkeys: Vec<_> = keys.iter().map(|k| k.pubkey()).collect();
let tx = librapay_transaction::create_accounts(funding_key, &pubkeys, 1, blockhash);
let ser_size = bincode::serialized_size(&tx).unwrap();
client.send_message(&[funding_key], tx.message).unwrap();
let sig = client
.async_send_transaction(tx)
.expect("create_account in generate_and_fund_keypairs");
tx_send.stop();
let mut poll = Measure::start("poll");
client.poll_for_signature(&sig).unwrap();
poll.stop();
if i % 10 == 0 {
blockhash = client.get_recent_blockhash().unwrap().0;
info!(
"size: {} created {} accounts of {}",
"size: {} created {} accounts of {} sig: {}us send: {}us",
ser_size,
i,
(keypairs.len() / create_len),
poll.as_us(),
tx_send.as_us()
);
}
}
funding_time.stop();
info!("funding accounts {}ms", funding_time.as_ms());
const NUM_FUNDING_KEYS: usize = 4;
let funding_keys: Vec<_> = (0..NUM_FUNDING_KEYS).map(|_| Keypair::new()).collect();
let pubkey_amounts: Vec<_> = funding_keys
.iter()
.map(|key| (key.pubkey(), total / NUM_FUNDING_KEYS as u64))
.collect();
let tx = Transaction::new_signed_instructions(
&[funding_key],
system_instruction::transfer_many(&funding_key.pubkey(), &pubkey_amounts),
blockhash,
);
client.send_message(&[funding_key], tx.message).unwrap();
let mut balance = 0;
for _ in 0..20 {
if let Ok(balance_) = client.get_balance(&funding_keys[0].pubkey()) {
if balance_ > 0 {
balance = balance_;
break;
}
}
sleep(Duration::from_millis(100));
}
assert!(balance > 0);
info!("funded multiple funding accounts.. {:?}", balance);
let libra_funding_keys: Vec<_> = (0..NUM_FUNDING_KEYS).map(|_| Keypair::new()).collect();
for (i, key) in libra_funding_keys.iter().enumerate() {
let tx =
librapay_transaction::create_account(&funding_keys[i], &key.pubkey(), 1, blockhash);
client
.send_message(&[&funding_keys[i]], tx.message)
.unwrap();
let mut sigs = vec![];
let tx_count = keypairs.len();
let amount = total / (tx_count as u64);
for (i, key) in keypairs[..tx_count].iter().enumerate() {
let tx = librapay_transaction::transfer(
libra_pay_program_id,
&libra_mint_key.pubkey(),
&funding_keys[i],
funding_key,
&libra_funding_key,
&key.pubkey(),
total / NUM_FUNDING_KEYS as u64,
amount,
blockhash,
);
client
.send_message(&[&funding_keys[i], &libra_funding_key], tx.message)
.unwrap();
info!("funded libra funding key {}", i);
let sig = client
.async_send_transaction(tx.clone())
.expect("create_account in generate_and_fund_keypairs");
let mut poll_time = Measure::start("poll_start");
let poll_status = client.poll_for_signature(&sig);
poll_time.stop();
info!(
"i: {} poll: {:?} time: {}ms",
i,
poll_status,
poll_time.as_ms()
);
sigs.push((sig, key));
if i % 50 == 0 {
blockhash = client.get_recent_blockhash().unwrap().0;
}
}
let tx_count = keypairs.len();
let amount = total / (tx_count as u64);
for (i, keys) in keypairs[..tx_count].chunks(NUM_FUNDING_KEYS).enumerate() {
for (j, key) in keys.iter().enumerate() {
let tx = librapay_transaction::transfer(
libra_pay_program_id,
&libra_mint_key.pubkey(),
&funding_keys[j],
&libra_funding_keys[j],
&key.pubkey(),
amount,
blockhash,
);
let _sig = client
.async_send_transaction(tx.clone())
.expect("create_account in generate_and_fund_keypairs");
}
info!("sent... checking balance {}", i);
for (j, key) in keys.iter().enumerate() {
let mut times = 0;
loop {
let balance =
librapay_transaction::get_libra_balance(client, &key.pubkey()).unwrap();
if balance >= amount {
for (i, (sig, key)) in sigs.iter().enumerate() {
let mut times = 0;
loop {
match client.poll_for_signature(&sig) {
Ok(_) => {
break;
} else if times > 20 {
info!("timed out.. {} key: {} balance: {}", i, j, balance);
break;
} else {
}
Err(e) => {
info!("e :{:?} waiting times: {} sig: {}", e, times, sig);
times += 1;
sleep(Duration::from_millis(100));
sleep(Duration::from_secs(1));
}
}
}
info!("funded: {} of {}", i, keypairs.len() / NUM_FUNDING_KEYS);
blockhash = client.get_recent_blockhash().unwrap().0;
times = 0;
loop {
let balance = librapay_transaction::get_libra_balance(client, &key.pubkey()).unwrap();
if balance < amount {
info!("i: {} balance: {} times: {}", i, balance, times);
times += 1;
sleep(Duration::from_secs(1));
} else {
break;
}
}
if i % 10 == 0 {
info!("funding {} of {}", i, tx_count);
}
}
info!("done funding keys..");
info!("done..");
}
pub fn generate_and_fund_keypairs<T: Client>(
@@ -873,7 +853,7 @@ pub fn generate_and_fund_keypairs<T: Client>(
let extra_fees = extra * fee_calculator.max_lamports_per_signature;
let mut total = account_desired_balance * (1 + keypairs.len() as u64) + extra_fees;
if use_move {
total *= 3;
total *= 2;
}
println!("Previous key balance: {} max_fee: {} lamports_per_account: {} extra: {} desired_balance: {} total: {}",
@@ -886,7 +866,7 @@ pub fn generate_and_fund_keypairs<T: Client>(
}
if use_move {
let libra_genesis_keypair = create_genesis(&funding_key, client, 10_000_000);
let libra_genesis_keypair = create_genesis(&funding_key, client, 1_000_000);
let libra_mint_program_id = upload_mint_program(&funding_key, client);
let libra_pay_program_id = upload_payment_program(&funding_key, client);
@@ -899,7 +879,7 @@ pub fn generate_and_fund_keypairs<T: Client>(
client,
funding_key,
&move_keypairs,
total / 3,
total / 2,
&libra_pay_program_id,
&libra_mint_program_id,
&libra_genesis_keypair,
@@ -911,8 +891,8 @@ pub fn generate_and_fund_keypairs<T: Client>(
move_keypairs,
));
// Give solana keys 1/3 and move keys 1/3 the lamports. Keep 1/3 for fees.
total /= 3;
// Give solana keys half and move keys half the lamports.
total /= 2;
}
fund_keys(
@@ -935,11 +915,11 @@ pub fn generate_and_fund_keypairs<T: Client>(
mod tests {
use super::*;
use solana::cluster_info::FULLNODE_PORT_RANGE;
use solana::local_cluster::{ClusterConfig, LocalCluster};
use solana::validator::ValidatorConfig;
use solana_client::thin_client::create_client;
use solana_core::cluster_info::FULLNODE_PORT_RANGE;
use solana_core::validator::ValidatorConfig;
use solana_drone::drone::run_local_drone;
use solana_local_cluster::local_cluster::{ClusterConfig, LocalCluster};
use solana_runtime::bank::Bank;
use solana_runtime::bank_client::BankClient;
use solana_sdk::client::SyncClient;

View File

@@ -8,7 +8,7 @@ mod cli;
use crate::bench::{
do_bench_tps, generate_and_fund_keypairs, generate_keypairs, Config, NUM_LAMPORTS_PER_ACCOUNT,
};
use solana_core::gossip_service::{discover_cluster, get_multi_client};
use solana::gossip_service::{discover_cluster, get_multi_client};
use solana_sdk::fee_calculator::FeeCalculator;
use solana_sdk::signature::{Keypair, KeypairUtil};
use std::collections::HashMap;
@@ -21,7 +21,7 @@ use std::process::exit;
pub const NUM_SIGNATURES_FOR_TXS: u64 = 100_000 * 60 * 60 * 24 * 7;
fn main() {
solana_logger::setup_with_filter("solana=info");
solana_logger::setup();
solana_metrics::set_panic_hook("bench-tps");
let matches = cli::build_args().get_matches();

View File

@@ -6,7 +6,8 @@
- [Getting Started](getting-started.md)
- [Testnet Participation](testnet-participation.md)
- [Example Client: Web Wallet](webwallet.md)
- [Testnet Replicator](testnet-replicator.md)
- [Example: Web Wallet](webwallet.md)
- [Programming Model](programs.md)
- [Example: Tic-Tac-Toe](tictactoe.md)
@@ -29,29 +30,16 @@
- [Blocktree](blocktree.md)
- [Gossip Service](gossip.md)
- [The Runtime](runtime.md)
- [Anatomy of a Transaction](transaction.md)
- [Running a Validator](running-validator.md)
- [Hardware Requirements](validator-hardware.md)
- [Choosing a Testnet](validator-testnet.md)
- [Installing the Validator Software](validator-software.md)
- [Starting a Validator](validator-start.md)
- [Staking](validator-stake.md)
- [Monitoring a Validator](validator-monitor.md)
- [Publishing Validator Info](validator-info.md)
- [Troubleshooting](validator-troubleshoot.md)
- [FAQ](validator-faq.md)
- [Running a Replicator](running-replicator.md)
- [API Reference](api-reference.md)
- [Transaction](transaction-api.md)
- [Instruction](instruction-api.md)
- [Blockstreamer](blockstreamer.md)
- [JSON RPC API](jsonrpc-api.md)
- [JavaScript API](javascript-api.md)
- [solana CLI](cli.md)
- [solana-wallet CLI](wallet.md)
- [Accepted Design Proposals](proposals.md)
- [Ledger Replication](ledger-replication-to-implement.md)
@@ -85,7 +73,7 @@
- [Passive Stake Delegation and Rewards](passive-stake-delegation-and-rewards.md)
- [Persistent Account Storage](persistent-account-storage.md)
- [Reliable Vote Transmission](reliable-vote-transmission.md)
- [Repair Service](repair-service.md)
- [Repair Service](repair-service.md)
- [Testing Programs](testing-programs.md)
- [Credit-only Accounts](credit-only-credit-debit-accounts.md)
- [Embedding the Move Langauge](embedding-move.md)

View File

@@ -2,15 +2,15 @@
Solanas crypto-economic system is designed to promote a healthy, long term self-sustaining economy with participant incentives aligned to the security and decentralization of the network. The main participants in this economy are validation-clients and replication-clients. Their contributions to the network, state validation and data storage respectively, and their requisite remittance mechanisms are discussed below.
The main channels of participant remittances are referred to as protocol-based rewards and transaction fees. Protocol-based rewards are protocol-derived issuances from a protocol-defined, global inflation rate. These rewards will constitute the total reward delivered to replication clients and a portion of the total rewards for validation clients, the remaining sourced from transaction fees. In the early days of the network, it is likely that protocol-based rewards, deployed based on predefined issuance schedule, will drive the majority of participant incentives to join the network.
The main channels of participant remittances are referred to as protocol-based rewards and transaction fees. Protocol-based rewards are protocol-derived issuances from a network-controlled reserve of tokens (sometimes referred to as the mining pool). These rewards will constitute the total reward delivered to replication clients and a portion of the total rewards for validation clients, the remaining sourced from transaction fees. In the early days of the network, it is likely that protocol-based rewards, deployed based on predefined issuance schedule, will drive the majority of participant incentives to join the network.
These protocol-based rewards, to be distributed to participating validation and replication clients, are to be a result of a global supply inflation rate, calculated per Solana epoch and distributed amongst the active validator set. As discussed further below, the per annum inflation rate is based on a pre-determined disinflationary schedule. This provides the network with monetary supply predictability which supports long term economic stability and security.
These protocol-based rewards, to be distributed to participating validation and replication clients, are to be specified as annual interest rates calculated per, real-time, Solana epoch [DEFINITION]. As discussed further below, the issuance rates are determined as a function of total network validator staked percentage and total replication provided by replicators in each previous epoch. The choice for validator and replicator client rewards to be based on participation rates, rather than a global fixed inflation or interest rate, emphasizes a protocol priority of overall economic security, rather than monetary supply predictability. Due to Solanas hard total supply cap of 1B tokens and the bounds of client participant rates in the protocol, we believe that global interest, and supply issuance, scenarios should be able to be modeled with reasonable uncertainties.
Transaction fees are market-based participant-to-participant transfers, attached to network interactions as a necessary motivation and compensation for the inclusion and execution of a proposed transaction (be it a state execution or proof-of-replication verification). A mechanism for continuous and long-term economic stability through partial burning of each transaction fee is also discussed below.
Transaction fees are market-based participant-to-participant transfers, attached to network interactions as a necessary motivation and compensation for the inclusion and execution of a proposed transaction (be it a state execution or proof-of-replication verification). A mechanism for continuous and long-term funding of the mining pool through a pre-dedicated portion of transaction fees is also discussed below.
A high-level schematic of Solanas crypto-economic design is shown below in **Figure 1**. The specifics of validation-client economics are described in sections: [Validation-client Economics](ed_validation_client_economics.md), [State-validation Protocol-based Rewards](ed_vce_state_validation_protocol_based_rewards.md), [State-validation Transaction Fees](ed_vce_state_validation_transaction_fees.md) and [Replication-validation Transaction Fees](ed_vce_replication_validation_transaction_fees.md). Also, the chapter titled [Validation Stake Delegation](ed_vce_validation_stake_delegation.md) closes with a discussion of validator delegation opportunties and marketplace. Additionally, in [Storage Rent Economics](ed_storage_rend_economics.md), we describe an implementation of storage rent to account for the externality costs of maintaining the active state of the ledger. The [Replication-client Economics](ed_replication_client_economics.md) chapter will review the Solana network design for global ledger storage/redundancy and replicator-client economics ([Storage-replication rewards](ed_rce_storage_replication_rewards.md)) along with a replicator-to-validator delegation mechanism designed to aide participant on-boarding into the Solana economy discussed in [Replication-client Reward Auto-delegation](ed_rce_replication_client_reward_auto_delegation.md). The [Economic Sustainability](ed_economic_sustainability.md) section dives deeper into Solanas design for long-term economic sustainability and outlines the constraints and conditions for a self-sustaining economy. An outline of features for an MVP economic design is discussed in the [Economic Design MVP](ed_mvp.md) section. Finally, in chapter [Attack Vectors](ed_attack_vectors.md), various attack vectors will be described and potential vulnerabilities explored and parameterized.
A high-level schematic of Solanas crypto-economic design is shown below in **Figure 1**. The specifics of validation-client economics are described in sections: [Validation-client Economics](ed_validation_client_economics.md), [State-validation Protocol-based Rewards](ed_vce_state_validation_protocol_based_rewards.md), [State-validation Transaction Fees](ed_vce_state_validation_transaction_fees.md) and [Replication-validation Transaction Fees](ed_vce_replication_validation_transaction_fees.md). Also, the chapter titled [Validation Stake Delegation](ed_vce_validation_stake_delegation.md) closes with a discussion of validator delegation opportunties and marketplace. The [Replication-client Economics](ed_replication_client_economics.md) chapter will review the Solana network design for global ledger storage/redundancy and replicator-client economics ([Storage-replication rewards](ed_rce_storage_replication_rewards.md)) along with a replicator-to-validator delegation mechanism designed to aide participant on-boarding into the Solana economy discussed in [Replication-client Reward Auto-delegation](ed_rce_replication_client_reward_auto_delegation.md). The [Economic Sustainability](ed_economic_sustainability.md) section dives deeper into Solanas design for long-term economic sustainability and outlines the constraints and conditions for a self-sustaining economy. An outline of features for an MVP economic design is discussed in the [Economic Design MVP](ed_mvp.md) section. Finally, in chapter [Attack Vectors](ed_attack_vectors.md), various attack vectors will be described and potential vulnerabilities explored and parameterized.
<!-- ![img alt text](solana_economic_design.png) -->
<p style="text-align:center;"><img src="img/economic_design_infl_230719.png" alt="== Solana Economic Design Diagram ==" width="800"/></p>
<p style="text-align:center;"><img src="img/solana_economic_design.png" alt="== Solana Economic Design Diagram ==" width="800"/></p>
**Figure 1**: Schematic overview of Solana economic incentive design.

View File

@@ -1,3 +1,3 @@
## Validation-client Economics
Validator-clients are eligible to receive protocol-based (i.e. via inflation) rewards issued via stake-based annual interest rates (calculated per epoch) by providing compute (CPU+GPU) resources to validate and vote on a given PoH state. These protocol-based rewards are determined through an algorithmic disinflationary schedule as a function of total amount of circulating tokens. Additionally, these clients may earn revenue through fees via state-validation transactions and Proof-of-Replication (PoRep) transactions. For clarity, we separately describe the design and motivation of these revenue distriubutions for validation-clients below: state-validation protocol-based rewards, state-validation transaction fees and rent, and PoRep-validation transaction fees.
Validator-clients are eligible to receive protocol-based (i.e. via mining pool) rewards issued via stake-based annual interest rates by providing compute (CPU+GPU) resources to validate and vote on a given PoH state. These protocol-based rewards are determined through an algorithmic schedule as a function of total amount of Solana tokens staked in the system and duration since network launch (genesis block). Additionally, these clients may earn revenue through two types of transaction fees: state-validation transaction fees and pooled Proof-of-Replication (PoRep) transaction fees. The distribution of these two types of transaction fees to the participating validation set are designed independently as economic goals and attack vectors are unique between the state- generation/validation mechanism and the ledger replication/validation mechanism. For clarity, we separately describe the design and motivation of the three types of potential revenue streams for validation-clients below: state-validation protocol-based rewards, state-validation transaction fees and PoRep-validation transaction fees.

View File

@@ -2,8 +2,8 @@
As previously mentioned, validator-clients will also be responsible for validating PoReps submitted into the PoH stream by replicator-clients. In this case, validators are providing compute (CPU/GPU) and light storage resources to confirm that these replication proofs could only be generated by a client that is storing the referenced PoH leger block.2
While replication-clients are incentivized and rewarded through protocol-based rewards schedule (see [Replication-client Economics](ed_replication_client_economics.md)), validator-clients will be incentivized to include and validate PoReps in PoH through collection of transaction fees associated with the submitted PoReps and distribution of protocol rewards proportional to the validated PoReps. As will be described in detail in the Section 3.1, replication-client rewards are protocol-based and designed to reward based on a global data redundancy factor. I.e. the protocol will incentivize replication-client participation through rewards based on a target ledger redundancy (e.g. 10x data redundancy).
While replication-clients are incentivized and rewarded through protocol-based rewards schedule (see [Replication-client Economics](ed_replication_client_economics.md)), validator-clients will be incentivized to include and validate PoReps in PoH through the distribution of the transaction fees associated with the submitted PoRep. As will be described in detail in the Section 3.1, replication-client rewards are protocol-based and designed to reward based on a global data redundancy factor. I.e. the protocol will incentivize replication-client participation through rewards based on a target ledger redundancy (e.g. 10x data redundancy). It was chosen not to include a distribution of these rewards to PoRep validators, and to rely only on the collection of PoRep attached transaction fees, due to the fact that the confluence of two participation incentive modes (state-validation inflation rate via global staked % and replication-validation rewards based on global redundancy factor) on the incentives of a single network participant (a validator-client) potentially opened up a significant incentive-driven attack surface area.
The validation of PoReps by validation-clients is computationally more expensive than state-validation (detail in the [Economic Sustainability](ed_economic_sustainability.md) chapter), thus the transaction fees are expected to be proportionally higher.
The validation of PoReps by validation-clients is computationally more expensive than state-validation (detail in the [Economic Sustainability](ed_economic_sustainability.md) chapter), thus the transaction fees are expected to be proportionally higher. However, because replication-client rewards are distributed in proportion to and only after submitted PoReps are validated, they are uniquely motivated for the inclusion and validation of their proofs. This pressure is expected to generate an adequate market economy between replication-clients and validation-clients. Additionally, transaction fees submitted with PoReps have no minimum amount pre-allocated to the mining pool, as do state-validation transaction fees.
There are various attack vectors available for colluding validation and replication clients, as described in detail below in [Economic Sustainability](ed_economic_sustainability). To protect against various collusion attack vectors, for a given epoch, validator rewards are distributed across participating validation-clients in proportion to the number of validated PoReps in the epoch less the number of PoReps that mismatch the replicators challenge. The PoRep challenge game is described in [Ledger Replication](https://github.com/solana-labs/solana/blob/master/book/src/ledger-replication.md#the-porep-game). This design rewards validators proportional to the number of PoReps they process and validate, while providing negative pressure for validation-clients to submit lazy or malicious invalid votes on submitted PoReps (note that it is computationally prohibitive to determine whether a validator-client has marked a valid PoRep as invalid).
There are various attack vectors available for colluding validation and replication clients, as described in detail below in [Economic Sustainability](ed_economic_sustainability). To protect against various collusion attack vectors, for a given epoch, PoRep transaction fees are pooled, and redistributed across participating validation-clients in proportion to the number of validated PoReps in the epoch less the number of invalidated PoReps [DIAGRAM]. This design rewards validators proportional to the number of PoReps they process and validate, while providing negative pressure for validation-clients to submit lazy or malicious invalid votes on submitted PoReps (note that it is computationally prohibitive to determine whether a validator-client has marked a valid PoRep as invalid).

View File

@@ -1,40 +1,46 @@
### State-validation protocol-based rewards
Validator-clients have two functional roles in the Solana network:
Validator-clients have two functional roles in the Solana network
* Validate (vote) the current global state of that PoH along with any Proofs-of-Replication (see [Replication Client Economics](ed_replication_client_economics.md)) that they are eligible to validate.
* Validate (vote) the current global state of that PoH along with any Proofs-of-Replication (see [Replication Client Economics](ed_replication_client_economics.md)) that they are eligible to validate
* Be elected as leader on a stake-weighted round-robin schedule during which time they are responsible for collecting outstanding transactions and Proofs-of-Replication and incorporating them into the PoH, thus updating the global state of the network and providing chain continuity.
Validator-client rewards for these services are to be distributed at the end of each Solana epoch. Compensation for validator-clients is provided via a protocol-based annual inflation rate dispersed in proportion to the stake-weight of each validator (see below) along with leader-claimed transaction fees available during each leader rotation. I.e. during the time a given validator-client is elected as leader, it has the opportunity to keep a portion of each transaction fee, less a protocol-specified amount that is destroyed (see [Validation-client State Transaction Fees](ed_vce_state_validation_transaction_fees.md)). PoRep transaction fees are also collected by the leader client and validator PoRep rewards are distributed in proportion to the number of validated PoReps less the number of PoReps that mismatch a replicator's challenge. (see [Replication-client Transaction Fees](ed_vce_replication_validation_transaction_fees.md))
Validator-client rewards for these services are to be distributed at the end of each Solana epoch. Compensation for validator-clients is provided via a protocol-based annual interest rate dispersed in proportion to the stake-weight of each validator (see below) along with leader-claimed transaction fees available during each leader rotation. I.e. during the time a given validator-client is elected as leader, it has the opportunity to keep a portion of each non-PoRep transaction fee, less a protocol-specified amount that is returned to the mining pool (see [Validation-client State Transaction Fees](ed_vce_state_validation_transaction_fees.md)). PoRep transaction fees are not collected directly by the leader client but pooled and returned to the validator set in proportion to the number of successfully validated PoReps. (see [Replication-client Transaction Fees](ed_vce_replication_validation_transaction_fees.md))
The effective protocol-based annual interest rate (%) per epoch to be distributed to validation-clients is to be a function of:
The protocol-based annual interest-rate (%) per epoch to be distributed to validation-clients is to be a function of:
* the current global inflation rate, derived from the pre-determined dis-inflationary issuance schedule
* the current fraction of staked SOLs out of the current total circulating supply,
* the fraction of staked SOLs out of the current total circulating supply,
* the global time since the genesis block instantiation
* the up-time/participation [% of available slots that validator had opportunity to vote on] of a given validator over the previous epoch.
* the up-time/participation [% of available slots/blocks that validator had opportunity to vote on?] of a given validator over the previous epoch.
The first factor is a function of protocol parameters only (i.e. independent of validator behavior in a given epoch) and results in a global validation reward schedule designed to incentivize early participation, provide clear montetary stability and provide optimal security in the network.
The first two factors are protocol parameters only (i.e. independent of validator behavior in a given epoch) and describe a global validation reward schedule designed to both incentivize early participation and optimal security in the network. This schedule sets a maximum annual validator-client interest rate per epoch.
At any given point in time, a specific validator's interest rate can be determined based on the porportion of circulating supply that is staked by the network and the validator's uptime/activity in the previous epoch. For an illustrative example, consider a hypothetical instance of the network with an initial circulating token supply of 250MM tokens with an additional 250MM vesting over 3 years. Additionally an inflation rate is specified at network launch of 7.5%, and a disinflationary schedule of 20% decrease in inflation rate per year (the actual rates to be implemented are to be worked out during the testnet experimentation phase of mainnet launch). With these broad assumptions, the 10-year inflation rate (adjusted daily for this example) is shown in **Figure 2**, while the total circulating token supply is illustrated in **Figure 3**. Neglected in this toy-model is the inflation supression due to the portion of each transaction fee that is to be destroyed.
At any given point in time, this interest rate is pegged to a defined value given a specific % staked SOL out of the circulating supply (e.g. 10% interest rate when 66% of circulating SOL is staked). The interest rate adjusts as the square-root [TBD] of the % staked, leading to higher validation-client interest rates as the % staked drops below the targeted goal, thus incentivizing more participation leading to more security in the network. An example of such a schedule, for a specified point in time (e.g. network launch) is shown in **Table 1**.
<p style="text-align:center;"><img src="img/p_ex_schedule.png" alt="drawing" width="800"/></p>
**Figure 2:** In this example schedule, the annual inflation rate [%] reduces at around 20% per year, until it reaches the long-term, fixed, 1.5% rate.
| Percentage circulating supply staked [%] | Annual validator-client interest rate [%] |
| ---: | ---: |
| 5 | 13.87 |
| 15 | 13.31 |
| 25 | 12.73 |
| 35 | 12.12 |
| 45 | 11.48 |
| 55 | 10.80 |
| **66** | **10.00** |
| 75 | 9.29 |
| 85 | 8.44 |
<p style="text-align:center;"><img src="img/p_ex_supply.png" alt="drawing" width="800"/></p>
**Figure 3:** The total token supply over a 10-year period, based on an initial 250MM tokens with the disinflationary inflation schedule as shown in **Figure 2**
**Table 1:** Example interest rate schedule based on % SOL staked out of circulating supply. In this case, interest rates are fixed at 10% for 66% of staked circulating supply
Over time, the interest rate, at a fixed network staked percentage, will reduce concordant with network inflation. Validation-client interest rates are designed to be higher in the early days of the network to incentivize participation and jumpstart the network economy. As previously mentioned, the inflation rate is expected to stabalize near 1-2% which also results in a fixed, long-term, interest rate to be provided to validator-clients. This value does not represent the total interest available to validator-clients as transaction fees for both state-validation and ledger storage replication (PoReps) are not accounted for here.
Given these example parameters, annualized validator-specific interest rates can be determined based on the global fraction of tokens bonded as stake, as well as their uptime/activity in the previous epoch. For the purpose of this example, we assume 100% uptime for all validators and a split in interest-based rewards between validators and replicator nodes of 80%/20%. Additionally, the fraction of staked circulating supply is assummed to be constant. Based on these assumptions, an annualized validation-client interest rate schedule as a function of % circulating token supply that is staked is shown in** Figure 4**.
Over time, the interest rate, at any network staked percentage, will drop as described by an algorithmic schedule. Validation-client interest rates are designed to be higher in the early days of the network to incentivize participation and jumpstart the network economy. This mining-pool provided interest rate will reduce over time until a network-chosen baseline value is reached. This is a fixed, long-term, interest rate to be provided to validator-clients. This value does not represent the total interest available to validator-clients as transaction fees for both state-validation and ledger storage replication (PoReps) are not accounted for here. A validation-client interest rate schedule as a function of % network staked and time is shown in** Figure 2**.
<!-- ![== Validation Client Interest Rates Figure ==](validation_client_interest_rates.png =250x) -->
<p style="text-align:center;"><img src="img/p_ex_interest.png" alt="drawing" width="800"/></p>
<p style="text-align:center;"><img src="img/validation_client_interest_rates.png" alt="drawing" width="800"/></p>
**Figure 4:** Shown here are example validator interest rates over time, neglecting transaction fees, segmented by fraction of total circulating supply bonded as stake.
**Figure 2:** In this example schedule, the annual interest rate [%] reduces at around 16.7% per year, until it reaches the long-term, fixed, 4% rate.
This epoch-specific protocol-defined interest rate sets an upper limit of *protocol-generated* annual interest rate (not absolute total interest rate) possible to be delivered to any validator-client per epoch. The distributed interest rate per epoch is then discounted from this value based on the participation of the validator-client during the previous epoch.
This epoch-specific protocol-defined interest rate sets an upper limit of *protocol-generated* annual interest rate (not absolute total interest rate) possible to be delivered to any validator-client per epoch. The distributed interest rate per epoch is then discounted from this value based on the participation of the validator-client during the previous epoch. Each epoch is comprised of XXX slots. The protocol-defined interest rate is then discounted by the log [TBD] of the % of slots a given validator submitted a vote on a PoH branch during that epoch, see **Figure XX**

View File

@@ -1,6 +1,6 @@
### State-validation Transaction Fees
Each transaction sent through the network, to be processed by the current leader validation-client and confirmed as a global state transaction, must contain a transaction fee. Transaction fees offer many benefits in the Solana economic design, for example they:
Each message sent through the network, to be processed by the current leader validation-client and confirmed as a global state transaction, must contain a transaction fee. Transaction fees offer many benefits in the Solana economic design, for example they:
* provide unit compensation to the validator network for the CPU/GPU resources necessary to process the state transaction,
@@ -10,11 +10,11 @@ Each transaction sent through the network, to be processed by the current leader
* and provide potential long-term economic stability of the network through a protocol-captured minimum fee amount per transaction, as described below.
Many current blockchain economies (e.g. Bitcoin, Ethereum), rely on protocol-based rewards to support the economy in the short term, with the assumption that the revenue generated through transaction fees will support the economy in the long term, when the protocol derived rewards expire. In an attempt to create a sustainable economy through protocol-based rewards and transaction fees, a fixed portion of each transaction fee is destroyed, with the remaining fee going to the current leader processing the transaction. A scheduled global inflation rate provides a source for rewards distributed to validation-clients, through the process described above, and replication-clients, as discussed below.
Many current blockchain economies (e.g. Bitcoin, Ethereum), rely on protocol-based rewards to support the economy in the short term, with the assumption that the revenue generated through transaction fees will support the economy in the long term, when the protocol derived rewards expire. In an attempt to create a sustainable economy through protocol-based rewards and transaction fees, a fixed portion of each transaction fee is sent to the mining pool, with the resulting fee going to the current leader processing the transaction. These pooled fees, then re-enter the system through rewards distributed to validation-clients, through the process described above, and replication-clients, as discussed below.
Transaction fees are set by the network cluster based on recent historical throughput, see [Congestion Driven Fees](transaction-fees.md#congestion-driven-fees). This minimum portion of each transaction fee can be dynamically adjusted depending on historical gas usage. In this way, the protocol can use the minimum fee to target a desired hardware utilisation. By monitoring a protocol specified gas usage with respect to a desired, target usage amount, the minimum fee can be raised/lowered which should, in turn, lower/raise the actual gas usage per block until it reaches the target amount. This adjustment process can be thought of as similar to the difficulty adjustment algorithm in the Bitcoin protocol, however in this case it is adjusting the minimum transaction fee to guide the transaction processing hardware usage to a desired level.
The intent of this design is to retain leader incentive to include as many transactions as possible within the leader-slot time, while providing a redistribution avenue that protects against "tax evasion" attacks (i.e. side-channel fee payments)<sup>[1](ed_referenced.md)</sup>. Constraints on the fixed portion of transaction fees going to the mining pool, to establish long-term economic sustainability, are established and discussed in detail in the [Economic Sustainability](ed_economic_sustainability.md) section.
As mentioned, a fixed-proportion of each transaction fee is to be destroyed. The intent of this design is to retain leader incentive to include as many transactions as possible within the leader-slot time, while providing an inflation limiting mechansim that protects against "tax evasion" attacks (i.e. side-channel fee payments)<sup>[1](ed_referenced.md)</sup>.
This minimum, protocol-earmarked, portion of each transaction fee can be dynamically adjusted depending on historical gas usage. In this way, the protocol can use the minimum fee to target a desired hardware utilisation. By monitoring a protocol specified gas usage with respect to a desired, target usage amount (e.g. 50% of a block's capacity), the minimum fee can be raised/lowered which should, in turn, lower/raise the actual gas usage per block until it reaches the target amount. This adjustment process can be thought of as similar to the difficulty adjustment algorithm in the Bitcoin protocol, however in this case it is adjusting the minimum transaction fee to guide the transaction processing hardware usage to a desired level.
Additionally, the burnt fees can be a consideration in fork selection. In the case of a PoH fork with a malicious, censoring leader, we would expect the total fees destroyed to be less than a comparable honest fork, due to the fees lost from censoring. If the censoring leader is to compensate for these lost protocol fees, they would have to replace the burnt fees on their fork themselves, thus potentially reducing the incentive to censor in the first place.
Additionally, the minimum protocol captured fee can be a consideration in fork selection. In the case of a PoH fork with a malicious, censoring leader, we would expect the total procotol captured fee to be less than a comparable honest fork, due to the fees lost from censoring. If the censoring leader is to compensate for these lost protocol fees, they would have to replace the fees on their fork themselves, thus potentially reducing the incentive to censor in the first place.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 256 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 269 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

View File

@@ -12,7 +12,7 @@ updates is managed using an on-chain update manifest program.
#### Fetch and run a pre-built installer using a bootstrap curl/shell script
The easiest install method for supported platforms:
```bash
$ curl -sSf https://raw.githubusercontent.com/solana-labs/solana/v0.18.0/install/solana-install-init.sh | sh
$ curl -sSf https://raw.githubusercontent.com/solana-labs/solana/v0.16.0/install/solana-install-init.sh | sh
```
This script will check github for the latest tagged release and download and run the
@@ -23,7 +23,7 @@ If additional arguments need to be specified during the installation, the
following shell syntax is used:
```bash
$ init_args=.... # arguments for `solana-install-init ...`
$ curl -sSf https://raw.githubusercontent.com/solana-labs/solana/v0.18.0/install/solana-install-init.sh | sh -s - ${init_args}
$ curl -sSf https://raw.githubusercontent.com/solana-labs/solana/v0.16.0/install/solana-install-init.sh | sh -s - ${init_args}
```
#### Fetch and run a pre-built installer from a Github release
@@ -31,7 +31,7 @@ With a well-known release URL, a pre-built binary can be obtained for supported
platforms:
```bash
$ curl -o solana-install-init https://github.com/solana-labs/solana/releases/download/v0.18.0/solana-install-init-x86_64-apple-darwin
$ curl -o solana-install-init https://github.com/solana-labs/solana/releases/download/v0.16.0/solana-install-init-x86_64-apple-darwin
$ chmod +x ./solana-install-init
$ ./solana-install-init --help
```

View File

@@ -26,12 +26,10 @@ Methods
* [getBalance](#getbalance)
* [getClusterNodes](#getclusternodes)
* [getEpochInfo](#getepochinfo)
* [getGenesisBlockhash](#getgenesisblockhash)
* [getLeaderSchedule](#getleaderschedule)
* [getProgramAccounts](#getprogramaccounts)
* [getRecentBlockhash](#getrecentblockhash)
* [getSignatureStatus](#getsignaturestatus)
* [getSlot](#getslot)
* [getSlotLeader](#getslotleader)
* [getSlotsPerSegment](#getslotspersegment)
* [getStorageTurn](#getstorageturn)
@@ -39,8 +37,7 @@ Methods
* [getNumBlocksSinceSignatureConfirmation](#getnumblockssincesignatureconfirmation)
* [getTransactionCount](#gettransactioncount)
* [getTotalSupply](#gettotalsupply)
* [getVersion](#getversion)
* [getVoteAccounts](#getvoteaccounts)
* [getEpochVoteAccounts](#getepochvoteaccounts)
* [requestAirdrop](#requestairdrop)
* [sendTransaction](#sendtransaction)
* [startSubscriptionChannel](#startsubscriptionchannel)
@@ -198,25 +195,6 @@ curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "m
{"jsonrpc":"2.0","result":{"epoch":3,"slotIndex":126,"slotsInEpoch":256},"id":1}
```
---
### getGenesisBlockhash
Returns the genesis block hash
##### Parameters:
None
##### Results:
* `string` - a Hash as base-58 encoded string
##### Example:
```bash
// Request
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getGenesisBlockhash"}' http://localhost:8899
// Result
{"jsonrpc":"2.0","result":"GH7ome3EiwEr7tu9JuTh2dpYWBJK3z69Xm1ZE3MEE6JC","id":1}
```
---
### getLeaderSchedule
@@ -248,7 +226,7 @@ Returns all accounts owned by the provided program Pubkey
##### Results:
The result field will be an array of arrays. Each sub array will contain:
* `string` - the account Pubkey as base-58 encoded string
* `string` - a the account Pubkey as base-58 encoded string
and a JSON object, with the following sub fields:
* `lamports`, number of lamports assigned to this account, as a signed 64-bit integer
@@ -315,25 +293,6 @@ curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "
-----
### getSlot
Returns the current slot the node is processing
##### Parameters:
None
##### Results:
* `u64` - Current slot
##### Example:
```bash
// Request
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getSlot"}' http://localhost:8899
// Result
{"jsonrpc":"2.0","result":"1234","id":1}
```
-----
### getSlotLeader
Returns the current slot leader
@@ -474,53 +433,31 @@ curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "m
---
### getVersion
Returns the current solana versions running on the node
### getEpochVoteAccounts
Returns the account info and associated stake for all the voting accounts in the current epoch.
##### Parameters:
None
##### Results:
The result field will be a JSON object with the following sub fields:
* `solana-core`, software version of solana-core
##### Example:
```bash
// Request
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getVersion"}' http://localhost:8899
// Result
{"jsonrpc":"2.0","result":{"solana-core": "0.17.2"},"id":1}
```
---
### getVoteAccounts
Returns the account info and associated stake for all the voting accounts in the current bank.
##### Parameters:
None
##### Results:
The result field will be a JSON object of `current` and `delinquent` accounts,
each containing an array of JSON objects with the following sub fields:
The result field will be an array of JSON objects, each with the following sub fields:
* `votePubkey` - Vote account public key, as base-58 encoded string
* `nodePubkey` - Node public key, as base-58 encoded string
* `activatedStake` - the stake, in lamports, delegated to this vote account and active in this epoch
* `epochVoteAccount` - bool, whether the vote account is staked for this epoch
* `commission`, an 8-bit integer used as a fraction (commission/MAX_U8) for rewards payout
* `lastVote` - Most recent slot voted on by this vote account
* `stake` - the stake, in lamports, delegated to this vote account
* `commission`, a 32-bit integer used as a fraction (commission/MAX_U32) for rewards payout
##### Example:
```bash
// Request
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getVoteAccounts"}' http://localhost:8899
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getEpochVoteAccounts"}' http://localhost:8899
// Result
{"jsonrpc":"2.0","result":{"current":[{"commission":0,"epochVoteAccount":true,"nodePubkey":"B97CCUW3AEZFGy6uUg6zUdnNYvnVq5VG8PUtb2HayTDD","lastVote":147,"activatedStake":42,"votePubkey":"3ZT31jkAGhUaw8jsy4bTknwBMP8i4Eueh52By4zXcsVw"}],"delinquent":[{"commission":127,"epochVoteAccount":false,"nodePubkey":"6ZPxeQaDo4bkZLRsdNrCzchNQr5LN9QMc9sipXv9Kw8f","lastVote":0,"activatedStake":0,"votePubkey":"CmgCk4aMS7KW1SHX3s9K5tBJ6Yng2LBaC8MFov4wx9sm"}]},"id":1}
{"jsonrpc":"2.0","result":[{"commission":0,"nodePubkey":"Et2RaZJdJRTzTkodUwiHr4H6sLkVmijBFv8tkd7oSSFY","stake":42,"votePubkey":"B4CdWq3NBSoH2wYsVE1CaZSWPo2ZtopE4SJipQhZ3srF"}],"id":1}
```
---
### requestAirdrop
Requests an airdrop of lamports to a Pubkey

View File

@@ -7,16 +7,14 @@ confirmed by super majority of the cluster (Confirmation Time).
Each cluster node maintains various counters that are incremented on certain events.
These counters are periodically uploaded to a cloud based database. Solana's metrics
dashboard fetches these counters, and computes the performance metrics and displays
it on the dashboard.
it on the dashboard.
## TPS
Each node's bank runtime maintains a count of transactions that it has processed.
The dashboard first calculates the median count of transactions across all metrics
enabled nodes in the cluster. The median cluster transaction count is then averaged
over a 2 second period and displayed in the TPS time series graph. The dashboard also
shows the Mean TPS, Max TPS and Total Transaction Count stats which are all calculated from
the median transaction count.
The leader node's banking stage maintains a count of transactions that it recorded.
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.
## Confirmation Time
@@ -28,4 +26,4 @@ super majority vote, and when one of its children forks is frozen.
The node assigns a timestamp to every new fork, and computes the time it took to confirm
the fork. This time is reflected as validator confirmation time in performance metrics.
The performance dashboard displays the average of each validator node's confirmation time
as a time series graph.
as a time series graph.

View File

@@ -1,35 +0,0 @@
# Running a Validator
This document describes how to participate in the Solana testnet as a
validator node.
Please note some of the information and instructions described here may change
in future releases, and documentation will be updated for mainnet participation.
## Overview
Solana currently maintains several testnets, each featuring a validator that can
serve as the entrypoint to the cluster for your validator.
Current testnet entrypoints:
- Stable, testnet.solana.com
- Beta, beta.testnet.solana.com
- Edge, edge.testnet.solana.com
Solana may launch special testnets for validator participation; we will provide
you with a specific entrypoint URL to use.
Prior to mainnet, the testnets may be running different versions of solana
software, which may feature breaking changes. For information on choosing a
testnet and finding software version info, jump to
[Choosing a Testnet](validator-testnet.md).
The testnets are configured to reset the ledger daily, or sooner,
should the hourly automated cluster sanity test fail.
There is a network explorer that shows the status of solana testnets available
at [http://explorer.solana.com/](https://explorer.solana.com/).
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).

View File

@@ -131,9 +131,7 @@ stake account lamports.
* `account[1]` - R - The VoteState instance.
* `account[2]` - R - sysvar::current account, carries information about current Bank epoch
* `account[3]` - R - stake_api::Config accoount, carries warmup, cooldown, and slashing configuration
* `account[2]` - R - syscall::current account, carries information about current Bank epoch
### StakeInstruction::RedeemVoteCredits
@@ -148,11 +146,10 @@ lamport, rewards paid are proportional to the number of lamports staked.
* `account[0]` - RW - The StakeState::Stake instance that is redeeming rewards.
* `account[1]` - R - The VoteState instance, must be the same as `StakeState::voter_pubkey`
* `account[2]` - RW - The StakeState::RewardsPool instance that will fulfill the request (picked at random).
* `account[3]` - R - sysvar::rewards account from the Bank that carries point value.
* `account[4]` - R - sysvar::stake_history account from the Bank that carries stake warmup/cooldown history
* `account[3]` - R - syscall::rewards account from the Bank that carries point value.
Reward is paid out for the difference between `VoteState::credits` to
`StakeState::Stake::credits_observed`, multiplied by `sysvar::rewards::Rewards::validator_point_value`.
`StakeState::Stake::credits_observed`, multiplied by `syscall::rewards::Rewards::validator_point_value`.
`StakeState::Stake::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.
@@ -170,8 +167,7 @@ stake_state.credits_observed = vote_state.credits;
A staker may wish to withdraw from the network. To do so he must first deactivate his stake, and wait for cool down.
* `account[0]` - RW - The StakeState::Stake instance that is deactivating, the transaction must be signed by this key.
* `account[1]` - R - The VoteState instance to which this stake is delegated, required in case of slashing
* `account[2]` - R - sysvar::current account from the Bank that carries current epoch
* `account[1]` - R - syscall::current account from the Bank that carries current epoch
StakeState::Stake::deactivated is set to the current epoch + cool down. The account's stake will ramp down to zero by
that epoch, and Account::lamports will be available for withdrawal.
@@ -182,8 +178,7 @@ Lamports build up over time in a Stake account and any excess over activated sta
* `account[0]` - RW - The StakeState::Stake from which to withdraw, the transaction must be signed by this key.
* `account[1]` - RW - Account that should be credited with the withdrawn lamports.
* `account[2]` - R - sysvar::current account from the Bank that carries current epoch, to calculate stake.
* `account[3]` - R - sysvar::stake_history account from the Bank that carries stake warmup/cooldown history
* `account[2]` - R - syscall::current account from the Bank that carries current epoch, to calculate stake.
## Benefits of the design
@@ -200,106 +195,3 @@ stake.
## Example Callflow
<img alt="Passive Staking Callflow" src="img/passive-staking-callflow.svg" class="center"/>
## Staking Rewards
The specific mechanics and rules of the validator rewards regime is outlined
here. Rewards are earned by delegating stake to a validator that is voting
correctly. Voting incorrectly exposes that validator's stakes to
[slashing](staking-and-rewards.md).
### Basics
The network pays rewards from a portion of network [inflation](inflation.md).
The number of lamports available to pay rewards for an epoch is fixed and
must be evenly divided among all staked nodes according to their relative stake
weight and participation. The weighting unit is called a
[point](terminology.md#point).
Rewards for an epoch are not available until the end of that epoch.
At the end of each epoch, the total number of points earned during the epoch is
summed and used to divide the rewards portion of epoch inflation to arrive at a
point value. This value is recorded in the bank in a
[sysvar](terminology.md#sysvar) that maps epochs to point values.
During redemption, the stake program counts the points earned by the stake for
each epoch, multiplies that by the epoch's point value, and transfers lamports in
that amount from a rewards account into the stake and vote accounts according to
the vote account's commission setting.
### Economics
Point value for an epoch depends on aggregate network participation. If participation
in an epoch drops off, point values are higher for those that do participate.
### Earning credits
Validators earn one vote credit for every correct vote that exceeds maximum
lockout, i.e. every time the validator's vote account retires a slot from its
lockout list, making that vote a root for the node.
Stakers who have delegated to that validator earn points in proportion to their
stake. Points earned is the product of vote credits and stake.
### Stake warmup, cooldown, withdrawal
Stakes, once delegated, do not become effective immediately. They must first
pass through a warm up period. During this period some portion of the stake is
considered "effective", the rest is considered "activating". Changes occur on
epoch boundaries.
The stake program limits the rate of change to total network stake, reflected
in the stake program's `config::warmup_rate` (typically 15% per epoch).
The amount of stake that can be warmed up each epoch is a function of the
previous epoch's total effective stake, total activating stake, and the stake
program's configured warmup rate.
Cooldown works the same way. Once a stake is deactivated, some part of it
is considered "effective", and also "deactivating". As the stake cools
down, it continues to earn rewards and be exposed to slashing, but it also
becomes available for withdrawal.
Bootstrap stakes are not subject to warmup.
Rewards are paid against the "effective" portion of the stake for that epoch.
#### Warmup example
Consider the situation of a single stake of 1,000 activated at epoch N, with
network warmup rate of 20%, and a quiescent total network stake at epoch N of 2,000.
At epoch N+1, the amount available to be activated for the network is 400 (20%
of 200), and at epoch N, this example stake is the only stake activating, and so
is entitled to all of the warmup room available.
|epoch | effective | activating | total effective | total activating|
|------|----------:|-----------:|----------------:|----------------:|
|N-1 | | | 2,000 | 0 |
|N | 0 | 1,000 | 2,000 | 1,000 |
|N+1 | 400 | 600 | 2,400 | 600 |
|N+2 | 880 | 120 | 2,880 | 120 |
|N+3 | 1000 | 0 | 3,000 | 0 |
Were 2 stakes (X and Y) to activate at epoch N, they would be awarded a portion of the 20%
in proportion to their stakes. At each epoch effective and activating for each stake is
a function of the previous epoch's state.
|epoch | X eff | X act | Y eff | Y act | total effective | total activating|
|------|----------:|-----------:|----------:|-----------:|----------------:|----------------:|
|N-1 | | | | | 2,000 | 0 |
|N | 0 | 1,000 | 0 | 200 | 2,000 | 1,200 |
|N+1 | 320 | 680 | 80 | 120 | 2,400 | 800 |
|N+2 | 728 | 272 | 152 | 48 | 2,880 | 320 |
|N+3 | 1000 | 0 | 200 | 0 | 3,200 | 0 |
### Withdrawal
As rewards are earned lamports can be withdrawn from a stake account. Only
lamports in excess of effective+activating stake may be withdrawn at any time.
This means that during warmup, effectively no stake can be withdrawn. During
cooldown, any tokens in excess of effective stake may be withdrawn (activating == 0);

View File

@@ -58,17 +58,6 @@ with a ledger interpretation that matches the leader's.
A gossip network connecting all [nodes](#node) of a [cluster](#cluster).
#### cooldown period
Some number of epochs after stake has been deactivated while it progressively
becomes available for withdrawal. During this period, the stake is considered to
be "deactivating". More info about:
[warmup and cooldown](stake-delegation-and-rewards.md#stake-warmup-cooldown-withdrawal)
#### credit
See [vote credit](#vote-credit).
#### data plane
A multicast network used to efficiently validate [entries](#entry) and gain
@@ -204,10 +193,6 @@ The number of [fullnodes](#fullnode) participating in a [cluster](#cluster).
See [Proof of History](#proof-of-history).
#### point
A weighted [credit](#credit) in a rewards regime. In the validator [rewards regime](staking-rewards.md), the number of points owed to a stake during redemption is the product of the [vote credits](#vote-credit) earned and the number of lamports staked.
#### program
The code that interprets [instructions](#instruction).
@@ -291,11 +276,6 @@ hash values and a bit which says if this hash is valid or fake.
The number of keys and samples that a validator can verify each storage epoch.
#### sysvar
A synthetic [account](#account) provided by the runtime to allow programs to
access network state such as current tick height, rewards [points](#point) values, etc.
#### thin client
A type of [client](#client) that trusts it is communicating with a valid
@@ -343,15 +323,3 @@ that it ran, which can then be verified in less time than it took to produce.
#### vote
See [ledger vote](#ledger-vote).
#### vote credit
A reward tally for validators. A vote credit is awarded to a validator in its
vote account when the validator reaches a [root](#root).
#### warmup period
Some number of epochs after stake has been delegated while it progressively
becomes effective. During this period, the stake is considered to be
"activating". More info about:
[warmup and cooldown](stake-delegation-and-rewards.md#stake-warmup-cooldown-withdrawal)

View File

@@ -1,5 +1,284 @@
## Testnet Participation
This document describes how to participate in the testnet as a
validator node.
Participate in our testnet:
* [Running a Validator](running-validator.md)
* [Running a Replicator](running-replicator.md)
Please note some of the information and instructions described here may change
in future releases.
### Overview
The testnet features a validator running at testnet.solana.com, which
serves as the entrypoint to the cluster for your validator.
Additionally there is a blockexplorer available at
[http://testnet.solana.com/](http://testnet.solana.com/).
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).
### Machine Requirements
Since the testnet is not intended for stress testing of max transaction
throughput, a higher-end machine with a GPU is not necessary to participate.
However ensure the machine used is not behind a residential NAT to avoid NAT
traversal issues. A cloud-hosted machine works best. **Ensure that IP ports
8000 through 10000 are not blocked for Internet inbound and outbound traffic.**
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. |
#### GPU Requirements
CUDA is required to make use of the GPU on your system. The provided Solana
release binaries are built on Ubuntu 18.04 with <a
href="https://developer.nvidia.com/cuda-toolkit-archive">CUDA Toolkit 10.1
update 1"</a>. If your machine is using a different CUDA version then you will
need to rebuild from source.
#### 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,
please retry 5-10 minutes later to confirm the testnet is not just restarting
itself before debugging further.
Fetch the current transaction count over JSON RPC:
```bash
$ curl -X POST -H 'Content-Type: application/json' -d '{"jsonrpc":"2.0","id":1, "method":"getTransactionCount"}' http://testnet.solana.com:8899
```
Inspect the blockexplorer at [http://testnet.solana.com/](http://testnet.solana.com/) for activity.
View the [metrics dashboard](
https://metrics.solana.com:3000/d/testnet-beta/testnet-monitor-beta?var-testnet=testnet)
for more detail on cluster activity.
### Validator Setup
#### Obtaining The Software
##### Bootstrap with `solana-install`
The `solana-install` tool can be used to easily install and upgrade the cluster
software on Linux x86_64 and mac OS systems.
```bash
$ curl -sSf https://raw.githubusercontent.com/solana-labs/solana/v0.17.0/install/solana-install-init.sh | sh -s
```
Alternatively build the `solana-install` program from source and run the
following command to obtain the same result:
```bash
$ solana-install init
```
After a successful install, `solana-install update` may be used to easily update the cluster
software to a newer version at any time.
##### Download Prebuilt Binaries
If you would rather not use `solana-install` to manage the install, you can manually download and install the binaries.
###### Linux
Download the binaries by navigating to
[https://github.com/solana-labs/solana/releases/latest](https://github.com/solana-labs/solana/releases/latest),
download **solana-release-x86_64-unknown-linux-gnu.tar.bz2**, then extract the
archive:
```bash
$ tar jxf solana-release-x86_64-unknown-linux-gnu.tar.bz2
$ cd solana-release/
$ export PATH=$PWD/bin:$PATH
```
###### mac OS
Download the binaries by navigating to
[https://github.com/solana-labs/solana/releases/latest](https://github.com/solana-labs/solana/releases/latest),
download **solana-release-x86_64-apple-darwin.tar.bz2**, then extract the
archive:
```bash
$ tar jxf solana-release-x86_64-apple-darwin.tar.bz2
$ cd solana-release/
$ export PATH=$PWD/bin:$PATH
```
##### Build From Source
If you are unable to use the prebuilt binaries or prefer to build it yourself
from source, navigate to
[https://github.com/solana-labs/solana/releases/latest](https://github.com/solana-labs/solana/releases/latest),
and download the **Source Code** archive. Extract the code and build the
binaries with:
```bash
$ ./scripts/cargo-install-all.sh .
$ export PATH=$PWD/bin:$PATH
```
If building for CUDA (Linux only), fetch the perf-libs first then include the
`cuda` feature flag when building:
```bash
$ ./fetch-perf-libs.sh
$ source ./target/perf-libs/env.sh
$ ./scripts/cargo-install-all.sh . cuda
$ export PATH=$PWD/bin:$PATH
```
### Starting The Validator
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
```
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
# Press ^C to exit
```
Now configure a key pair for your validator by running:
```bash
$ solana-keygen new -o ~/validator-keypair.json
```
Then use one of the following commands, depending on your installation
choice, to start the node:
If this is a `solana-install`-installation:
```bash
$ validator.sh --identity ~/validator-keypair.json --config-dir ~/validator-config --rpc-port 8899 --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
$ solana-install run validator.sh -- --identity ~/validator-keypair.json --config-dir ~/validator-config --rpc-port 8899 --poll-for-new-genesis-block testnet.solana.com
```
If you built from source:
```bash
$ NDEBUG=1 USE_INSTALL=1 ./multinode-demo/validator.sh --identity ~/validator-keypair.json --rpc-port 8899 --poll-for-new-genesis-block testnet.solana.com
```
#### Enabling CUDA
By default CUDA is disabled. If your machine has a GPU with CUDA installed,
define the SOLANA_CUDA flag in your environment *before* running any of the
previusly mentioned commands
```bash
$ export SOLANA_CUDA=1
```
When your validator is started look for the following log message to indicate that CUDA is enabled:
`"[<timestamp> solana::validator] CUDA is enabled"`
#### 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
validator to ports 11000-11011.
### Validator Monitoring
When `validator.sh` starts, it will output a validator configuration that looks
similar to:
```bash
======================[ validator configuration ]======================
identity pubkey: 4ceWXsL3UJvn7NYZiRkw7NsryMpviaKBDYr8GK7J61Dm
vote pubkey: 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
the recent voting activity from your validator:
```bash
$ solana-wallet show-vote-account 2ozWvfaXQd1X6uKh8jERoRGApDqSqcEy6fF1oN13LL2G
```
The vote pubkey 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
# Otherwise run:
$ solana-keygen pubkey ./config-local/validator-vote-keypair.json
```
#### Validator Metrics
Metrics are available for local monitoring of your validator.
Docker must be installed and the current user added to the docker group. Then
download `solana-metrics.tar.bz2` from the Github Release and run
```bash
$ tar jxf solana-metrics.tar.bz2
$ cd solana-metrics/
$ ./start.sh
```
A local InfluxDB and Grafana instance is now running on your machine. Define
`SOLANA_METRICS_CONFIG` in your environment as described at the end of the
`start.sh` output and restart your validator.
Metrics should now be streaming and visible from your local Grafana dashboard.
#### Timezone For Log Messages
Log messages emitted by your validator include a timestamp. When sharing logs
with others to help triage issues, that timestamp can cause confusion as it does
not contain timezone information.
To make it easier to compare logs between different sources we request that
everybody use Pacific Time on their validator nodes. In Linux this can be
accomplished by running:
```bash
$ sudo ln -sf /usr/share/zoneinfo/America/Los_Angeles /etc/localtime
```
#### Publishing Validator Info
You can publish your validator information to the chain to be publicly visible
to other users.
Run the solana-validator-info CLI to populate a validator-info account:
```bash
$ solana-validator-info publish ~/validator-keypair.json <VALIDATOR_NAME> <VALIDATOR_INFO_ARGS>
```
Optional fields for VALIDATOR_INFO_ARGS:
* Website
* Keybase Username
* Details
##### Keybase
Including a Keybase username allows client applications (like the Solana Network
Explorer) to automatically pull in your validator public profile, including
cryptographic proofs, brand identity, etc. To connect your validator pubkey with
Keybase:
1. Join https://keybase.io/ and complete the profile for your validator
2. Add your validator **identity pubkey** to Keybase:
* Create an empty file on your local computer called `validator-<PUBKEY>`
* In Keybase, navigate to the Files section, and upload your pubkey file to
a `solana` subdirectory in your public folder: `/keybase/public/<KEYBASE_USERNAME>/solana`
* To check your pubkey, ensure you can successfully browse to
`https://keybase.pub/<KEYBASE_USERNAME>/solana/validator-<PUBKEY>`
3. Add or update your `solana-validator-info` with your Keybase username. The
CLI will verify the `validator-<PUBKEY>` file

View File

@@ -1,4 +1,4 @@
## Running a Replicator
## Testnet Replicator
This document describes how to setup a replicator in the testnet
Please note some of the information and instructions described here may change
@@ -53,7 +53,7 @@ software.
##### Linux and mac OS
```bash
$ curl -sSf https://raw.githubusercontent.com/solana-labs/solana/v0.18.1/install/solana-install-init.sh | sh -s
$ curl -sSf https://raw.githubusercontent.com/solana-labs/solana/v0.17.0/install/solana-install-init.sh | sh -s
```
Alternatively build the `solana-install` program from source and run the
@@ -129,10 +129,10 @@ $ export STORAGE_IDENTITY=$(solana-keygen pubkey storage-keypair.json)
```
Then set up the storage accounts for your replicator by running:
```bash
$ solana --keypair replicator-keypair.json airdrop 100000
$ solana --keypair replicator-keypair.json create-replicator-storage-account $REPLICATOR_IDENTITY $STORAGE_IDENTITY
$ solana-wallet --keypair replicator-keypair.json airdrop 100000
$ solana-wallet --keypair replicator-keypair.json create-replicator-storage-account $REPLICATOR_IDENTITY $STORAGE_IDENTITY
```
Note: Every time the testnet restarts, run the steps to setup the replicator accounts again.
Note: Every time the testnet restarts, run the wallet steps to setup the replicator accounts again.
To start the replicator:
```bash
@@ -146,8 +146,8 @@ gossip network by running:
$ solana-gossip --entrypoint testnet.solana.com:8001 spy
```
Provide the **storage account pubkey** to the `solana show-storage-account` command to view
Provide the **storage account pubkey** to the `solana-wallet show-storage-account` command to view
the recent mining activity from your replicator:
```bash
$ solana --keypair storage-keypair.json show-storage-account $STORAGE_IDENTITY
$ solana-wallet --keypair storage-keypair.json show-storage-account $STORAGE_IDENTITY
```

View File

@@ -1,2 +0,0 @@
# Validator FAQ
Coming soon...

View File

@@ -1,28 +0,0 @@
# Validator Hardware Requirements
Since the testnet is not intended for stress testing of max transaction
throughput, a higher-end machine with a GPU is not necessary to participate.
However ensure the machine used is not behind a residential NAT to avoid NAT
traversal issues. A cloud-hosted machine works best. **Ensure that IP ports
8000 through 10000 are not blocked for Internet inbound and outbound traffic.**
Prebuilt binaries are available for Linux x86_64 (Ubuntu 18.04 recommended).
MacOS or WSL users may build from source.
## Recommended Setups
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. |
## GPU Requirements
CUDA is required to make use of the GPU on your system. The provided Solana
release binaries are built on Ubuntu 18.04 with <a
href="https://developer.nvidia.com/cuda-toolkit-archive">CUDA Toolkit 10.1
update 1"</a>. If your machine is using a different CUDA version then you will
need to rebuild from source.

View File

@@ -1,31 +0,0 @@
# Publishing Validator Info
You can publish your validator information to the chain to be publicly visible
to other users.
## Run solana-validator-info
Run the solana-validator-info CLI to populate a validator-info account:
```bash
$ solana-validator-info publish ~/validator-keypair.json <VALIDATOR_NAME> <VALIDATOR_INFO_ARGS>
```
Optional fields for VALIDATOR_INFO_ARGS:
* Website
* Keybase Username
* Details
## Keybase
Including a Keybase username allows client applications (like the Solana Network
Explorer) to automatically pull in your validator public profile, including
cryptographic proofs, brand identity, etc. To connect your validator pubkey with
Keybase:
1. Join https://keybase.io/ and complete the profile for your validator
2. Add your validator **identity pubkey** to Keybase:
* Create an empty file on your local computer called `validator-<PUBKEY>`
* In Keybase, navigate to the Files section, and upload your pubkey file to
a `solana` subdirectory in your public folder: `/keybase/public/<KEYBASE_USERNAME>/solana`
* To check your pubkey, ensure you can successfully browse to
`https://keybase.pub/<KEYBASE_USERNAME>/solana/validator-<PUBKEY>`
3. Add or update your `solana-validator-info` with your Keybase username. The
CLI will verify the `validator-<PUBKEY>` file

View File

@@ -1,106 +0,0 @@
# Validator Monitoring
When `validator.sh` starts, it will output a validator configuration that looks
similar to:
```bash
======================[ validator configuration ]======================
identity pubkey: 4ceWXsL3UJvn7NYZiRkw7NsryMpviaKBDYr8GK7J61Dm
vote pubkey: 2ozWvfaXQd1X6uKh8jERoRGApDqSqcEy6fF1oN13LL2G
ledger: ...
accounts: ...
======================================================================
```
## Check Gossip
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
```
## Check Vote Activity
The vote pubkey for the validator can be found by running:
```bash
$ solana-keygen pubkey ~/validator-vote-keypair.json
```
Provide the **vote pubkey** to the `solana show-vote-account` command to view
the recent voting activity from your validator:
```bash
$ solana show-vote-account 2ozWvfaXQd1X6uKh8jERoRGApDqSqcEy6fF1oN13LL2G
```
## Check Your Balance
Your lamport balance should decrease by the transaction fee amount as your
validator submits votes, and increase after serving as the leader:
```bash
$ solana balance
```
## Check Slot Number
After your validator boots, it may take some time to catch up with the cluster.
Use the `get-slot` command to view the current slot that the cluster is
processing:
```bash
$ solana get-slot
```
The current slot that your validator is processing can then been seen with:
```bash
$ solana --url http://127.0.0.1:8899 get-slot
```
Until your validator has caught up, it will not be able to vote successfully and
stake cannot be delegated to it.
Also if you find the cluster's slot advancing faster than yours, you will likely
never catch up. This typically implies some kind of networking issue between
your validator and the rest of the cluster.
## Get Cluster Info
There are several useful JSON-RPC endpoints for monitoring your validator on the
cluster, as well as the health of the cluster:
```bash
# Similar to solana-gossip, you should see your validator in the list of cluster nodes
$ curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getClusterNodes"}' http://testnet.solana.com:8899
# If your validator is properly voting, it should appear in the list of `current` vote accounts. If staked, `stake` should be > 0
$ curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getVoteAccounts"}' http://testnet.solana.com:8899
# Returns the current leader schedule
$ curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getLeaderSchedule"}' http://testnet.solana.com:8899
# Returns info about the current epoch. slotIndex should progress on subsequent calls.
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getEpochInfo"}' http://testnet.solana.com:8899
```
## Validator Metrics
Metrics are available for local monitoring of your validator.
Docker must be installed and the current user added to the docker group. Then
download `solana-metrics.tar.bz2` from the Github Release and run
```bash
$ tar jxf solana-metrics.tar.bz2
$ cd solana-metrics/
$ ./start.sh
```
A local InfluxDB and Grafana instance is now running on your machine. Define
`SOLANA_METRICS_CONFIG` in your environment as described at the end of the
`start.sh` output and restart your validator.
Metrics should now be streaming and visible from your local Grafana dashboard.
## Timezone For Log Messages
Log messages emitted by your validator include a timestamp. When sharing logs
with others to help triage issues, that timestamp can cause confusion as it does
not contain timezone information.
To make it easier to compare logs between different sources we request that
everybody use Pacific Time on their validator nodes. In Linux this can be
accomplished by running:
```bash
$ sudo ln -sf /usr/share/zoneinfo/America/Los_Angeles /etc/localtime
```

View File

@@ -1,63 +0,0 @@
# Installing the Validator Software
## Bootstrap with `solana-install`
The `solana-install` tool can be used to easily install and upgrade the validator
software on Linux x86_64 and mac OS systems.
```bash
$ curl -sSf https://raw.githubusercontent.com/solana-labs/solana/v0.18.0/install/solana-install-init.sh | sh -s
```
Alternatively build the `solana-install` program from source and run the
following command to obtain the same result:
```bash
$ solana-install init
```
After a successful install, `solana-install update` may be used to easily update the cluster
software to a newer version at any time.
## Download Prebuilt Binaries
If you would rather not use `solana-install` to manage the install, you can manually download and install the binaries.
### Linux
Download the binaries by navigating to
[https://github.com/solana-labs/solana/releases/latest](https://github.com/solana-labs/solana/releases/latest),
download **solana-release-x86_64-unknown-linux-gnu.tar.bz2**, then extract the
archive:
```bash
$ tar jxf solana-release-x86_64-unknown-linux-gnu.tar.bz2
$ cd solana-release/
$ export PATH=$PWD/bin:$PATH
```
### mac OS
Download the binaries by navigating to
[https://github.com/solana-labs/solana/releases/latest](https://github.com/solana-labs/solana/releases/latest),
download **solana-release-x86_64-apple-darwin.tar.bz2**, then extract the
archive:
```bash
$ tar jxf solana-release-x86_64-apple-darwin.tar.bz2
$ cd solana-release/
$ export PATH=$PWD/bin:$PATH
```
## Build From Source
If you are unable to use the prebuilt binaries or prefer to build it yourself
from source, navigate to
[https://github.com/solana-labs/solana/releases/latest](https://github.com/solana-labs/solana/releases/latest),
and download the **Source Code** archive. Extract the code and build the
binaries with:
```bash
$ ./scripts/cargo-install-all.sh .
$ export PATH=$PWD/bin:$PATH
```
If building for CUDA (Linux only), fetch the perf-libs first then include the
`cuda` feature flag when building:
```bash
$ ./fetch-perf-libs.sh
$ source target/perf-libs/env.sh
$ ./scripts/cargo-install-all.sh . cuda
$ export PATH=$PWD/bin:$PATH
```

View File

@@ -1,41 +0,0 @@
## Staking a Validator
When your validator starts, it will have no stake, which means it will be
ineligible to become leader.
Adding stake can be accomplished by using the `solana` CLI
First create a stake account keypair with `solana-keygen`:
```bash
$ solana-keygen new -o ~/validator-config/stake-keypair.json
```
and use the cli's `delegate-stake` command to stake your validator with 42 lamports:
```bash
$ solana delegate-stake ~/validator-config/stake-keypair.json ~/validator-vote-keypair.json 42
```
Note that stakes need to warm up, and warmup increments are applied at Epoch boundaries, so it can take an hour
or more for the change to fully take effect.
Assuming your node is voting, now you're up and running and generating validator rewards. You'll want
to periodically redeem/claim your rewards:
```bash
$ solana-wallet redeem-vote-credits ~/validator-config/stake-keypair.json ~/validator-vote-keypair.json
```
The rewards lamports earned are split between your stake account and the vote account according to the
commission rate set in the vote account.
Stake can be deactivated by running:
```bash
$ solana deactivate-stake ~/validator-config/stake-keypair.json ~/validator-vote-keypair.json
```
The stake will cool down, deactivate over time. While cooling down, your stake will continue to earn
rewards.
Note that a stake account may only be used once, so after deactivation, use the
cli's `withdraw-stake` command to recover the previously staked lamports.
Be sure and redeem your credits before withdrawing all your lamports.
Once the account is fully withdrawn, the account is destroyed.

View File

@@ -1,112 +0,0 @@
# Starting a Validator
## 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,
please retry 5-10 minutes later to confirm the testnet is not just restarting
itself before debugging further.
Fetch the current transaction count over JSON RPC:
```bash
$ curl -X POST -H 'Content-Type: application/json' -d '{"jsonrpc":"2.0","id":1, "method":"getTransactionCount"}' http://testnet.solana.com:8899
```
Inspect the network explorer at
[https://explorer.solana.com/](https://explorer.solana.com/) for activity.
View the [metrics dashboard](
https://metrics.solana.com:3000/d/testnet-beta/testnet-monitor-beta?var-testnet=testnet)
for more detail on cluster activity.
## Confirm your Installation
Sanity check that you are able to interact with the cluster by receiving a small
airdrop of lamports from the testnet drone:
```bash
$ solana set --url http://testnet.solana.com:8899
$ solana get
$ solana airdrop 123
$ solana 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
# Press ^C to exit
```
## Start your Validator
Create an identity keypair for your validator by running:
```bash
$ solana-keygen new -o ~/validator-keypair.json
```
### Wallet Configuration
You can set solana configuration to use your validator keypair for all
following commands:
```bash
$ solana set --keypair ~/validator-keypair.json
```
**All following solana commands assume you have set `--keypair` config to
**your validator identity keypair.**
If you haven't, you will need to add the `--keypair` argument to each command, like:
```bash
$ solana --keypair ~/validator-keypair.json airdrop 1000
```
(You can always override the set configuration by explicitly passing the
`--keypair` argument with a command.)
### Validator Start
Airdrop yourself some lamports to get started:
```bash
$ solana airdrop 1000
```
Your validator will need a vote account. Create it now with the following
commands:
```bash
$ solana-keygen new -o ~/validator-vote-keypair.json
$ solana create-vote-account ~/validator-vote-keypair.json ~/validator-keypair.json 1
```
Then use one of the following commands, depending on your installation
choice, to start the node:
If this is a `solana-install`-installation:
```bash
$ validator.sh --identity ~/validator-keypair.json --voting-keypair ~/validator-vote-keypair.json --ledger ~/validator-config --rpc-port 8899 --poll-for-new-genesis-block --entrypoint 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
$ solana-install run validator.sh -- --identity ~/validator-keypair.json --voting-keypair ~/validator-vote-keypair.json --ledger ~/validator-config --rpc-port 8899 --poll-for-new-genesis-block --entrypoint testnet.solana.com
```
If you built from source:
```bash
$ NDEBUG=1 USE_INSTALL=1 ./multinode-demo/validator.sh --identity ~/validator-keypair.json --voting-keypair ~/validator-vote-keypair.json --rpc-port 8899 --poll-for-new-genesis-block --entrypoint testnet.solana.com
```
### Enabling CUDA
By default CUDA is disabled. If your machine has a GPU with CUDA installed,
define the SOLANA_CUDA flag in your environment *before* running any of the
previusly mentioned commands
```bash
$ export SOLANA_CUDA=1
```
When your validator is started look for the following log message to indicate that CUDA is enabled:
`"[<timestamp> solana::validator] CUDA is enabled"`
### 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
validator to ports 11000-11011.
### Limiting ledger size to conserve disk space
By default the validator will retain the full ledger. To conserve disk space
start the validator with the `--limit-ledger-size`, which will instruct the
validator to only retain the last couple hours of ledger.

View File

@@ -1,72 +0,0 @@
# Choosing a Testnet
As noted in the overview, solana currently maintains several testnets, each featuring a validator that can serve as the entrypoint to the cluster for your validator.
Current testnet entrypoints:
- Stable, testnet.solana.com
- Beta, beta.testnet.solana.com
- Edge, edge.testnet.solana.com
Prior to mainnet, the testnets may be running different versions of solana
software, which may feature breaking changes. Generally, the edge testnet tracks
the tip of master, beta tracks the latest tagged minor release, and stable
tracks the most stable tagged release.
### Get Testnet Version
You can submit a JSON-RPC request to see the specific version of the cluster.
```bash
$ curl -X POST -H 'Content-Type: application/json' -d '{"jsonrpc":"2.0","id":1, "method":"getVersion"}' edge.testnet.solana.com:8899
{"jsonrpc":"2.0","result":{"solana-core":"0.18.0-pre1"},"id":1}
```
## Using a Different Testnet
This guide is written in the context of testnet.solana.com, our most stable
cluster. To participate in another testnet, you will need to modify some of the
commands in the following pages.
### Downloading Software
If you are bootstrapping with `solana-install`, you can specify the release tag or named channel to install to match your desired testnet.
```bash
$ curl -sSf https://raw.githubusercontent.com/solana-labs/solana/v0.18.1/install/solana-install-init.sh | sh -s - 0.18.1
```
```bash
$ curl -sSf https://raw.githubusercontent.com/solana-labs/solana/v0.18.1/install/solana-install-init.sh | sh -s - beta
```
Similarly, you can add this argument to the `solana-install` command if you've built the program from source:
```bash
$ solana-install init 0.18.1
```
If you are downloading pre-compiled binaries or building from source, simply choose the release matching your desired testnet.
### Validator Commands
Solana CLI tools like solana and solana-validator-info point at
testnet.solana.com by default. Include a `--url` argument to point at a
different testnet. For instance:
```bash
$ solana --url http://beta.testnet.solana.com:8899 balance
```
The solana cli includes `get` and `set` configuration commands to automatically
set the `--url` argument for future wallet commands.
For example:
```bash
$ solana set --url http://beta.testnet.solana.com:8899
$ solana balance # Same result as command above
```
(You can always override the set configuration by explicitly passing the `--url`
argument with a command.)
Solana-gossip and solana-validator commands already require an explicit
`--entrypoint` argument. Simply replace testnet.solana.com in the examples with
an alternate url to interact with a different testnet. For example:
```bash
$ validator.sh --identity ~/validator-keypair.json --voting-keypair ~/validator-vote-keypair.json --ledger ~/validator-config --rpc-port 8899 --poll-for-new-genesis-block beta.testnet.solana.com
```
You can also submit JSON-RPC requests to a different testnet, like:
```bash
$ curl -X POST -H 'Content-Type: application/json' -d '{"jsonrpc":"2.0","id":1, "method":"getTransactionCount"}' http://beta.testnet.solana.com:8899
```

View File

@@ -1,2 +0,0 @@
# Troubleshooting Validator Issues
Coming soon...

View File

@@ -1,6 +1,6 @@
## solana CLI
## solana-wallet CLI
The [solana-cli crate](https://crates.io/crates/solana-cli) provides a command-line interface tool for Solana
The [solana crate](https://crates.io/crates/solana) is distributed with a command-line interface tool
### Examples
@@ -8,7 +8,7 @@ The [solana-cli crate](https://crates.io/crates/solana-cli) provides a command-l
```sh
// Command
$ solana address
$ solana-wallet address
// Return
<PUBKEY>
@@ -18,7 +18,7 @@ $ solana address
```sh
// Command
$ solana airdrop 123
$ solana-wallet airdrop 123
// Return
"Your balance is: 123"
@@ -28,7 +28,7 @@ $ solana airdrop 123
```sh
// Command
$ solana balance
$ solana-wallet balance
// Return
"Your balance is: 123"
@@ -38,7 +38,7 @@ $ solana balance
```sh
// Command
$ solana confirm <TX_SIGNATURE>
$ solana-wallet confirm <TX_SIGNATURE>
// Return
"Confirmed" / "Not found" / "Transaction failed with error <ERR>"
@@ -48,7 +48,7 @@ $ solana confirm <TX_SIGNATURE>
```sh
// Command
$ solana deploy <PATH>
$ solana-wallet deploy <PATH>
// Return
<PROGRAM_ID>
@@ -58,7 +58,7 @@ $ solana deploy <PATH>
```sh
// Command
$ solana pay <PUBKEY> 123
$ solana-wallet pay <PUBKEY> 123
// Return
<TX_SIGNATURE>
@@ -68,7 +68,7 @@ $ solana pay <PUBKEY> 123
```sh
// Command
$ solana pay <PUBKEY> 123 \
$ solana-wallet pay <PUBKEY> 123 \
--after 2018-12-24T23:59:00 --require-timestamp-from <PUBKEY>
// Return
@@ -81,7 +81,7 @@ $ solana pay <PUBKEY> 123 \
A third party must send a signature to unlock the lamports.
```sh
// Command
$ solana pay <PUBKEY> 123 \
$ solana-wallet pay <PUBKEY> 123 \
--require-signature-from <PUBKEY>
// Return
@@ -92,7 +92,7 @@ $ solana pay <PUBKEY> 123 \
```sh
// Command
$ solana pay <PUBKEY> 123 \
$ solana-wallet pay <PUBKEY> 123 \
--after 2018-12-24T23:59 --require-timestamp-from <PUBKEY> \
--require-signature-from <PUBKEY>
@@ -104,7 +104,7 @@ $ solana pay <PUBKEY> 123 \
```sh
// Command
$ solana pay <PUBKEY> 123 \
$ solana-wallet pay <PUBKEY> 123 \
--require-signature-from <PUBKEY> \
--require-signature-from <PUBKEY>
@@ -116,7 +116,7 @@ $ solana pay <PUBKEY> 123 \
```sh
// Command
$ solana pay <PUBKEY> 123 \
$ solana-wallet pay <PUBKEY> 123 \
--require-signature-from <PUBKEY> \
--cancelable
@@ -128,7 +128,7 @@ $ solana pay <PUBKEY> 123 \
```sh
// Command
$ solana cancel <PROCESS_ID>
$ solana-wallet cancel <PROCESS_ID>
// Return
<TX_SIGNATURE>
@@ -138,7 +138,7 @@ $ solana cancel <PROCESS_ID>
```sh
// Command
$ solana send-signature <PUBKEY> <PROCESS_ID>
$ solana-wallet send-signature <PUBKEY> <PROCESS_ID>
// Return
<TX_SIGNATURE>
@@ -149,7 +149,7 @@ $ solana send-signature <PUBKEY> <PROCESS_ID>
Use the current system time:
```sh
// Command
$ solana send-timestamp <PUBKEY> <PROCESS_ID>
$ solana-wallet send-timestamp <PUBKEY> <PROCESS_ID>
// Return
<TX_SIGNATURE>
@@ -159,7 +159,7 @@ Or specify some other arbitrary timestamp:
```sh
// Command
$ solana send-timestamp <PUBKEY> <PROCESS_ID> --date 2018-12-24T23:59:00
$ solana-wallet send-timestamp <PUBKEY> <PROCESS_ID> --date 2018-12-24T23:59:00
// Return
<TX_SIGNATURE>
@@ -168,10 +168,10 @@ $ solana send-timestamp <PUBKEY> <PROCESS_ID> --date 2018-12-24T23:59:00
### Usage
```manpage
solana 0.12.0
solana-wallet 0.12.0
USAGE:
solana [FLAGS] [OPTIONS] [SUBCOMMAND]
solana-wallet [FLAGS] [OPTIONS] [SUBCOMMAND]
FLAGS:
-h, --help Prints help information
@@ -201,11 +201,11 @@ SUBCOMMANDS:
```
```manpage
solana-address
solana-wallet-address
Get your public key
USAGE:
solana address
solana-wallet address
FLAGS:
-h, --help Prints help information
@@ -213,11 +213,11 @@ FLAGS:
```
```manpage
solana-airdrop
solana-wallet-airdrop
Request a batch of lamports
USAGE:
solana airdrop <NUM>
solana-wallet airdrop <NUM>
FLAGS:
-h, --help Prints help information
@@ -228,11 +228,11 @@ ARGS:
```
```manpage
solana-balance
solana-wallet-balance
Get your balance
USAGE:
solana balance
solana-wallet balance
FLAGS:
-h, --help Prints help information
@@ -240,11 +240,11 @@ FLAGS:
```
```manpage
solana-cancel
solana-wallet-cancel
Cancel a transfer
USAGE:
solana cancel <PROCESS_ID>
solana-wallet cancel <PROCESS_ID>
FLAGS:
-h, --help Prints help information
@@ -255,11 +255,11 @@ ARGS:
```
```manpage
solana-confirm
solana-wallet-confirm
Confirm transaction by signature
USAGE:
solana confirm <SIGNATURE>
solana-wallet confirm <SIGNATURE>
FLAGS:
-h, --help Prints help information
@@ -270,11 +270,11 @@ ARGS:
```
```manpage
solana-deploy
solana-wallet-deploy
Deploy a program
USAGE:
solana deploy <PATH>
solana-wallet deploy <PATH>
FLAGS:
-h, --help Prints help information
@@ -285,11 +285,11 @@ ARGS:
```
```manpage
solana-fees
solana-wallet-fees
Display current cluster fees
USAGE:
solana fees
solana-wallet fees
FLAGS:
-h, --help Prints help information
@@ -297,11 +297,11 @@ FLAGS:
```
```manpage
solana-get-transaction-count
solana-wallet-get-transaction-count
Get current transaction count
USAGE:
solana get-transaction-count
solana-wallet get-transaction-count
FLAGS:
-h, --help Prints help information
@@ -309,11 +309,11 @@ FLAGS:
```
```manpage
solana-pay
solana-wallet-pay
Send a payment
USAGE:
solana pay [FLAGS] [OPTIONS] <PUBKEY> <NUM>
solana-wallet pay [FLAGS] [OPTIONS] <PUBKEY> <NUM>
FLAGS:
--cancelable
@@ -331,11 +331,11 @@ ARGS:
```
```manpage
solana-send-signature
solana-wallet-send-signature
Send a signature to authorize a transfer
USAGE:
solana send-signature <PUBKEY> <PROCESS_ID>
solana-wallet send-signature <PUBKEY> <PROCESS_ID>
FLAGS:
-h, --help Prints help information
@@ -347,11 +347,11 @@ ARGS:
```
```manpage
solana-send-timestamp
solana-wallet-send-timestamp
Send a timestamp to unlock a transfer
USAGE:
solana send-timestamp [OPTIONS] <PUBKEY> <PROCESS_ID>
solana-wallet send-timestamp [OPTIONS] <PUBKEY> <PROCESS_ID>
FLAGS:
-h, --help Prints help information

View File

@@ -1,6 +1,6 @@
[package]
name = "solana-chacha-sys"
version = "0.18.1"
version = "0.17.2"
description = "Solana chacha-sys"
authors = ["Solana Maintainers <maintainers@solana.com>"]
repository = "https://github.com/solana-labs/solana"
@@ -9,4 +9,4 @@ license = "Apache-2.0"
edition = "2018"
[build-dependencies]
cc = "1.0.40"
cc = "1.0.38"

View File

@@ -1,6 +1,6 @@
# Note: when the rust version is changed also modify
# ci/rust-version.sh to pick up the new image tag
FROM rust:1.37.0
FROM rust:1.36.0
# Add Google Protocol Buffers for Libra's metrics library.
ENV PROTOC_VERSION 3.8.0
@@ -10,7 +10,9 @@ RUN set -x \
&& apt update \
&& apt-get install apt-transport-https \
&& echo deb https://apt.buildkite.com/buildkite-agent stable main > /etc/apt/sources.list.d/buildkite-agent.list \
&& echo deb http://apt.llvm.org/stretch/ llvm-toolchain-stretch-7 main > /etc/apt/sources.list.d/llvm.list \
&& apt-key adv --no-tty --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 32A37959C2FA5C3C99EFBC32A79206696452D198 \
&& wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | apt-key add - \
&& apt update \
&& apt install -y \
buildkite-agent \
@@ -18,6 +20,7 @@ RUN set -x \
cmake \
lcov \
libclang-common-7-dev \
llvm-7 \
mscgen \
rsync \
sudo \

View File

@@ -74,23 +74,20 @@ source scripts/configure-metrics.sh
nodes=(
"multinode-demo/drone.sh"
"multinode-demo/bootstrap-leader.sh \
--init-complete-file init-complete-node1.log \
--dynamic-port-range 8000-8019"
--enable-rpc-exit \
--no-restart \
--init-complete-file init-complete-node1.log"
"multinode-demo/validator.sh \
--enable-rpc-exit \
--no-restart \
--dynamic-port-range 8020-8039
--init-complete-file init-complete-node2.log \
--rpc-port 18899"
)
for i in $(seq 1 $extraNodes); do
portStart=$((8040 + i * 20))
portEnd=$((portStart + 19))
nodes+=(
"multinode-demo/validator.sh \
--no-restart \
--dynamic-port-range $portStart-$portEnd
--label dyn$i \
--init-complete-file init-complete-node$((2 + i)).log"
)
@@ -270,7 +267,7 @@ verifyLedger() {
(
source multinode-demo/common.sh
set -x
$solana_ledger_tool --ledger "$SOLANA_CONFIG_DIR"/$ledger verify
$solana_ledger_tool --ledger "$SOLANA_CONFIG_DIR"/$ledger-ledger verify
) || flag_error
done
}

View File

@@ -9,9 +9,8 @@ if [[ -n $APPVEYOR ]]; then
source ci/rust-version.sh
appveyor DownloadFile https://win.rustup.rs/ -FileName rustup-init.exe
export USERPROFILE="D:\\"
./rustup-init -yv --default-toolchain $rust_stable --default-host x86_64-pc-windows-msvc
export PATH="$PATH:/d/.cargo/bin"
export PATH="$PATH:$USERPROFILE/.cargo/bin"
rustc -vV
cargo -vV
fi
@@ -102,8 +101,7 @@ echo --- Creating tarball
set -e
cd "$(dirname "$0")"/..
export USE_INSTALL=1
export REQUIRE_LEDGER_DIR=1
export REQUIRE_KEYPAIRS=1
export REQUIRE_CONFIG_DIR=1
exec multinode-demo/validator.sh "$@"
EOF
chmod +x solana-release/bin/validator.sh

View File

@@ -13,8 +13,8 @@
# $ source ci/rust-version.sh
#
stable_version=1.37.0
nightly_version=2019-08-21
stable_version=1.36.0
nightly_version=2019-07-19
export rust_stable="$stable_version"
export rust_stable_docker_image=solanalabs/rust:"$stable_version"

View File

@@ -10,30 +10,42 @@ source ci/rust-version.sh nightly
export RUST_BACKTRACE=1
export RUSTFLAGS="-D warnings"
do_bpf_check() {
_ cargo +"$rust_stable" fmt --all -- --check
_ cargo +"$rust_nightly" test --all
_ cargo +"$rust_nightly" clippy --version
_ cargo +"$rust_nightly" clippy --all -- --deny=warnings
_ cargo +"$rust_stable" audit
}
(
(
cd sdk/bpf/rust/rust-no-std
do_bpf_check
)
(
cd sdk/bpf/rust/rust-utils
do_bpf_check
)
(
cd sdk/bpf/rust/rust-test
do_bpf_check
)
for project in programs/bpf/rust/*/ ; do
(
cd "$project"
do_bpf_check
)
done
)
_ cargo +"$rust_stable" fmt --all -- --check
# Clippy gets stuck for unknown reasons if sdk-c is included in the build, so check it separately.
# See https://github.com/solana-labs/solana/issues/5503
_ cargo +"$rust_stable" clippy --version
_ cargo +"$rust_stable" clippy --all --exclude solana-sdk-c -- --deny=warnings
_ cargo +"$rust_stable" clippy --manifest-path sdk-c/Cargo.toml -- --deny=warnings
# _ cargo +"$rust_stable" audit --version ### cargo-audit stopped supporting --version?? https://github.com/RustSec/cargo-audit/issues/100
_ cargo +"$rust_stable" audit --ignore RUSTSEC-2019-0013
_ cargo +"$rust_stable" clippy --all -- --deny=warnings
_ cargo +"$rust_stable" audit --version
_ cargo +"$rust_stable" audit --ignore RUSTSEC-2019-0011 # https://github.com/solana-labs/solana/issues/5207
_ ci/nits.sh
_ ci/order-crates-for-publishing.py
_ book/build.sh
for project in sdk/bpf/rust/{rust-utils,rust-test} programs/bpf/rust/*/ ; do
echo "+++ do_bpf_check $project"
(
cd "$project"
_ cargo +"$rust_stable" fmt --all -- --check
_ cargo +"$rust_nightly" test --all
_ cargo +"$rust_nightly" clippy --version
_ cargo +"$rust_nightly" clippy --all -- --deny=warnings
_ cargo +"$rust_stable" audit
)
done
echo --- ok

View File

@@ -90,7 +90,7 @@ Deploys a CD testnet
- Attempt to generate a TLS certificate using this DNS name
--fullnode-additional-disk-size-gb [number]
- Size of additional disk in GB for all fullnodes
--no-snapshot-fetch
--no-snapshot
- If set, disables booting validators from a snapshot
Note: the SOLANA_METRICS_CONFIG environment variable is used to configure
@@ -137,7 +137,7 @@ while [[ -n $1 ]]; do
elif [[ $1 == --machine-type* ]]; then # Bypass quoted long args for GPUs
shortArgs+=("$1")
shift
elif [[ $1 = --no-snapshot-fetch ]]; then
elif [[ $1 = --no-snapshot ]]; then
maybeNoSnapshot="$1"
shift 1
else

View File

@@ -154,9 +154,8 @@ testnet-demo)
: "${GCE_LOW_QUOTA_NODE_COUNT:=70}"
;;
tds)
: "${TDS_CHANNEL_OR_TAG:=edge}"
CHANNEL_OR_TAG="$TDS_CHANNEL_OR_TAG"
CHANNEL_BRANCH="$CI_BRANCH"
CHANNEL_OR_TAG=beta
CHANNEL_BRANCH=$BETA_CHANNEL
;;
*)
echo "Error: Invalid TESTNET=$TESTNET"
@@ -177,11 +176,11 @@ for val in "${GCE_LOW_QUOTA_ZONES[@]}"; do
GCE_LOW_QUOTA_ZONE_ARGS+=("-z $val")
done
if [[ -z $TESTNET_DB_HOST ]]; then
TESTNET_DB_HOST="https://metrics.solana.com:8086"
if [[ -n $TESTNET_DB_HOST ]]; then
SOLANA_METRICS_PARTIAL_CONFIG="host=$TESTNET_DB_HOST,$SOLANA_METRICS_PARTIAL_CONFIG"
fi
export SOLANA_METRICS_CONFIG="db=$TESTNET,host=$TESTNET_DB_HOST,$SOLANA_METRICS_PARTIAL_CONFIG"
export SOLANA_METRICS_CONFIG="db=$TESTNET,$SOLANA_METRICS_PARTIAL_CONFIG"
echo "SOLANA_METRICS_CONFIG: $SOLANA_METRICS_CONFIG"
source scripts/configure-metrics.sh
@@ -218,7 +217,6 @@ sanity() {
set -x
NO_INSTALL_CHECK=1 \
NO_LEDGER_VERIFY=1 \
NO_VALIDATOR_SANITY=1 \
ci/testnet-sanity.sh edge-testnet-solana-com gce us-west1-b
)
;;
@@ -236,7 +234,6 @@ sanity() {
set -x
NO_INSTALL_CHECK=1 \
NO_LEDGER_VERIFY=1 \
NO_VALIDATOR_SANITY=1 \
ci/testnet-sanity.sh beta-testnet-solana-com gce us-west1-b
)
;;
@@ -353,7 +350,7 @@ deploy() {
NO_VALIDATOR_SANITY=1 \
ci/testnet-deploy.sh -p beta-testnet-solana-com -C gce -z us-west1-b \
-t "$CHANNEL_OR_TAG" -n 2 -c 0 -u -P \
-a beta-testnet-solana-com --letsencrypt beta.testnet.solana.com \
-a beta-testnet-solana-com --letsencrypt beta.testnet.solana.com \
${skipCreate:+-e} \
${skipStart:+-s} \
${maybeStop:+-S} \
@@ -554,7 +551,8 @@ deploy() {
${maybeExternalAccountsFile} \
${maybeLamports} \
${maybeAdditionalDisk} \
--skip-deploy-update
--skip-deploy-update \
--no-snapshot
)
;;
*)

View File

@@ -1,50 +0,0 @@
[package]
authors = ["Solana Maintainers <maintainers@solana.com>"]
edition = "2018"
name = "solana-cli"
description = "Blockchain, Rebuilt for Scale"
version = "0.18.1"
repository = "https://github.com/solana-labs/solana"
license = "Apache-2.0"
homepage = "https://solana.com/"
[dependencies]
bincode = "1.1.4"
bs58 = "0.2.4"
chrono = { version = "0.4.7", features = ["serde"] }
clap = "2.33.0"
criterion-stats = "0.3.0"
ctrlc = { version = "3.1.3", features = ["termination"] }
console = "0.7.7"
dirs = "2.0.2"
lazy_static = "1.3.0"
log = "0.4.8"
num-traits = "0.2"
pretty-hex = "0.1.0"
serde = "1.0.99"
serde_derive = "1.0.99"
serde_json = "1.0.40"
serde_yaml = "0.8.9"
solana-budget-api = { path = "../programs/budget_api", version = "0.18.1" }
solana-client = { path = "../client", version = "0.18.1" }
solana-drone = { path = "../drone", version = "0.18.1" }
solana-logger = { path = "../logger", version = "0.18.1" }
solana-netutil = { path = "../utils/netutil", version = "0.18.1" }
solana-runtime = { path = "../runtime", version = "0.18.1" }
solana-sdk = { path = "../sdk", version = "0.18.1" }
solana-stake-api = { path = "../programs/stake_api", version = "0.18.1" }
solana-storage-api = { path = "../programs/storage_api", version = "0.18.1" }
solana-vote-api = { path = "../programs/vote_api", version = "0.18.1" }
solana-vote-signer = { path = "../vote-signer", version = "0.18.1" }
url = "2.1.0"
[dev-dependencies]
solana-core = { path = "../core", version = "0.18.1" }
solana-budget-program = { path = "../programs/budget_program", version = "0.18.1" }
[features]
cuda = []
[[bin]]
name = "solana"
path = "src/main.rs"

View File

@@ -1,49 +0,0 @@
// Wallet settings that can be configured for long-term use
use serde_derive::{Deserialize, Serialize};
use std::fs::{create_dir_all, File};
use std::io::{self, Write};
use std::path::Path;
lazy_static! {
pub static ref CONFIG_FILE: Option<String> = {
dirs::home_dir().map(|mut path| {
path.extend(&[".config", "solana", "wallet", "config.yml"]);
path.to_str().unwrap().to_string()
})
};
}
#[derive(Serialize, Deserialize, Default, Debug, PartialEq)]
pub struct Config {
pub url: String,
pub keypair: String,
}
impl Config {
pub fn new(url: &str, keypair: &str) -> Self {
Self {
url: url.to_string(),
keypair: keypair.to_string(),
}
}
pub fn load(config_file: &str) -> Result<Self, io::Error> {
let file = File::open(config_file.to_string())?;
let config = serde_yaml::from_reader(file)
.map_err(|err| io::Error::new(io::ErrorKind::Other, format!("{:?}", err)))?;
Ok(config)
}
pub fn save(&self, config_file: &str) -> Result<(), io::Error> {
let serialized = serde_yaml::to_string(self)
.map_err(|err| io::Error::new(io::ErrorKind::Other, format!("{:?}", err)))?;
if let Some(outdir) = Path::new(&config_file).parent() {
create_dir_all(outdir)?;
}
let mut file = File::create(config_file)?;
file.write_all(&serialized.into_bytes())?;
Ok(())
}
}

View File

@@ -1,11 +0,0 @@
use console::style;
// Pretty print a "name value"
pub fn println_name_value(name: &str, value: &str) {
let styled_value = if value == "" {
style("(not set)").italic()
} else {
style(value)
};
println!("{} {}", style(name).bold(), styled_value);
}

View File

@@ -1,6 +0,0 @@
#[macro_use]
extern crate lazy_static;
pub mod config;
pub mod display;
pub mod wallet;

View File

@@ -1,237 +0,0 @@
use clap::{crate_description, crate_name, crate_version, Arg, ArgGroup, ArgMatches, SubCommand};
use console::style;
use solana_cli::config::{self, Config};
use solana_cli::display::println_name_value;
use solana_cli::wallet::{app, parse_command, process_command, WalletConfig, WalletError};
use solana_sdk::signature::{gen_keypair_file, read_keypair, KeypairUtil};
use std::error;
fn parse_settings(matches: &ArgMatches<'_>) -> Result<bool, Box<dyn error::Error>> {
let parse_args = match matches.subcommand() {
("get", Some(subcommand_matches)) => {
if let Some(config_file) = matches.value_of("config_file") {
let config = Config::load(config_file).unwrap_or_default();
if let Some(field) = subcommand_matches.value_of("specific_setting") {
let value = match field {
"url" => config.url,
"keypair" => config.keypair,
_ => unreachable!(),
};
println_name_value(&format!("* {}:", field), &value);
} else {
println_name_value("Wallet Config:", config_file);
println_name_value("* url:", &config.url);
println_name_value("* keypair:", &config.keypair);
}
} else {
println!("{} Either provide the `--config` arg or ensure home directory exists to use the default config location", style("No config file found.").bold());
}
false
}
("set", Some(subcommand_matches)) => {
if let Some(config_file) = matches.value_of("config_file") {
let mut config = Config::load(config_file).unwrap_or_default();
if let Some(url) = subcommand_matches.value_of("url") {
config.url = url.to_string();
}
if let Some(keypair) = subcommand_matches.value_of("keypair") {
config.keypair = keypair.to_string();
}
config.save(config_file)?;
println_name_value("Wallet Config Updated:", config_file);
println_name_value("* url:", &config.url);
println_name_value("* keypair:", &config.keypair);
} else {
println!("{} Either provide the `--config` arg or ensure home directory exists to use the default config location", style("No config file found.").bold());
}
false
}
_ => true,
};
Ok(parse_args)
}
pub fn parse_args(matches: &ArgMatches<'_>) -> Result<WalletConfig, Box<dyn error::Error>> {
let config = if let Some(config_file) = matches.value_of("config_file") {
Config::load(config_file).unwrap_or_default()
} else {
Config::default()
};
let json_rpc_url = if let Some(url) = matches.value_of("json_rpc_url") {
url.to_string()
} else if config.url != "" {
config.url
} else {
let default = WalletConfig::default();
default.json_rpc_url
};
let drone_host = if let Some(drone_host) = matches.value_of("drone_host") {
Some(solana_netutil::parse_host(drone_host).or_else(|err| {
Err(WalletError::BadParameter(format!(
"Invalid drone host: {:?}",
err
)))
})?)
} else {
None
};
let drone_port = matches
.value_of("drone_port")
.unwrap()
.parse()
.or_else(|err| {
Err(WalletError::BadParameter(format!(
"Invalid drone port: {:?}",
err
)))
})?;
let mut path = dirs::home_dir().expect("home directory");
let id_path = if matches.is_present("keypair") {
matches.value_of("keypair").unwrap()
} else if config.keypair != "" {
&config.keypair
} else {
path.extend(&[".config", "solana", "id.json"]);
if !path.exists() {
gen_keypair_file(path.to_str().unwrap())?;
println!("New keypair generated at: {}", path.to_str().unwrap());
}
path.to_str().unwrap()
};
let keypair = read_keypair(id_path).or_else(|err| {
Err(WalletError::BadParameter(format!(
"{}: Unable to open keypair file: {}",
err, id_path
)))
})?;
let command = parse_command(&keypair.pubkey(), &matches)?;
Ok(WalletConfig {
command,
drone_host,
drone_port,
json_rpc_url,
keypair,
rpc_client: None,
})
}
// Return an error if a url cannot be parsed.
fn is_url(string: String) -> Result<(), String> {
match url::Url::parse(&string) {
Ok(url) => {
if url.has_host() {
Ok(())
} else {
Err("no host provided".to_string())
}
}
Err(err) => Err(format!("{:?}", err)),
}
}
fn main() -> Result<(), Box<dyn error::Error>> {
solana_logger::setup();
let default = WalletConfig::default();
let default_drone_port = format!("{}", default.drone_port);
let matches = app(crate_name!(), crate_description!(), crate_version!())
.arg({
let arg = Arg::with_name("config_file")
.short("c")
.long("config")
.value_name("PATH")
.takes_value(true)
.help("Configuration file to use");
if let Some(ref config_file) = *config::CONFIG_FILE {
arg.default_value(&config_file)
} else {
arg
}
})
.arg(
Arg::with_name("json_rpc_url")
.short("u")
.long("url")
.value_name("URL")
.takes_value(true)
.validator(is_url)
.help("JSON RPC URL for the solana cluster"),
)
.arg(
Arg::with_name("drone_host")
.long("drone-host")
.value_name("HOST")
.takes_value(true)
.help("Drone host to use [default: same as the --url host]"),
)
.arg(
Arg::with_name("drone_port")
.long("drone-port")
.value_name("PORT")
.takes_value(true)
.default_value(&default_drone_port)
.help("Drone port to use"),
)
.arg(
Arg::with_name("keypair")
.short("k")
.long("keypair")
.value_name("PATH")
.takes_value(true)
.help("/path/to/id.json"),
)
.subcommand(
SubCommand::with_name("get")
.about("Get wallet config settings")
.arg(
Arg::with_name("specific_setting")
.index(1)
.value_name("CONFIG_FIELD")
.takes_value(true)
.possible_values(&["url", "keypair"])
.help("Return a specific config setting"),
),
)
.subcommand(
SubCommand::with_name("set")
.about("Set a wallet config setting")
.arg(
Arg::with_name("url")
.short("u")
.long("url")
.value_name("URL")
.takes_value(true)
.validator(is_url)
.help("Set default JSON RPC URL to query"),
)
.arg(
Arg::with_name("keypair")
.short("k")
.long("keypair")
.value_name("PATH")
.takes_value(true)
.help("/path/to/id.json"),
)
.group(
ArgGroup::with_name("config_settings")
.args(&["url", "keypair"])
.multiple(true)
.required(true),
),
)
.get_matches();
if parse_settings(&matches)? {
let config = parse_args(&matches)?;
let result = process_command(&config)?;
println!("{}", result);
}
Ok(())
}

View File

@@ -1,6 +1,6 @@
[package]
name = "solana-client"
version = "0.18.1"
version = "0.17.2"
description = "Solana Client"
authors = ["Solana Maintainers <maintainers@solana.com>"]
repository = "https://github.com/solana-labs/solana"
@@ -10,19 +10,19 @@ edition = "2018"
[dependencies]
bincode = "1.1.4"
bs58 = "0.2.4"
jsonrpc-core = "13.0.0"
log = "0.4.8"
bs58 = "0.2.0"
jsonrpc-core = "12.1.0"
log = "0.4.7"
rand = "0.6.5"
rayon = "1.1.0"
reqwest = "0.9.20"
serde = "1.0.99"
serde_derive = "1.0.99"
reqwest = "0.9.19"
serde = "1.0.97"
serde_derive = "1.0.97"
serde_json = "1.0.40"
solana-netutil = { path = "../utils/netutil", version = "0.18.1" }
solana-sdk = { path = "../sdk", version = "0.18.1" }
solana-netutil = { path = "../netutil", version = "0.17.2" }
solana-sdk = { path = "../sdk", version = "0.17.2" }
[dev-dependencies]
jsonrpc-core = "13.0.0"
jsonrpc-http-server = "13.0.0"
solana-logger = { path = "../logger", version = "0.18.1" }
jsonrpc-core = "12.1.0"
jsonrpc-http-server = "12.1.0"
solana-logger = { path = "../logger", version = "0.17.2" }

View File

@@ -9,10 +9,9 @@ use serde_json::{json, Value};
use solana_sdk::account::Account;
use solana_sdk::fee_calculator::FeeCalculator;
use solana_sdk::hash::Hash;
use solana_sdk::inflation::Inflation;
use solana_sdk::pubkey::Pubkey;
use solana_sdk::signature::{KeypairUtil, Signature};
use solana_sdk::timing::{DEFAULT_TICKS_PER_SECOND, DEFAULT_TICKS_PER_SLOT};
use solana_sdk::timing::{DEFAULT_NUM_TICKS_PER_SECOND, DEFAULT_TICKS_PER_SLOT};
use solana_sdk::transaction::{self, Transaction, TransactionError};
use std::error;
use std::io;
@@ -21,7 +20,7 @@ use std::thread::sleep;
use std::time::{Duration, Instant};
pub struct RpcClient {
client: Box<dyn GenericRpcClientRequest + Send + Sync>,
client: Box<GenericRpcClientRequest + Send + Sync>,
}
impl RpcClient {
@@ -95,44 +94,6 @@ impl RpcClient {
})
}
pub fn get_inflation(&self) -> io::Result<Inflation> {
let response = self
.client
.send(&RpcRequest::GetInflation, None, 0)
.map_err(|err| {
io::Error::new(
io::ErrorKind::Other,
format!("GetInflation request failure: {:?}", err),
)
})?;
serde_json::from_value(response).map_err(|err| {
io::Error::new(
io::ErrorKind::Other,
format!("GetInflation parse failure: {}", err),
)
})
}
pub fn get_version(&self) -> io::Result<String> {
let response = self
.client
.send(&RpcRequest::GetVersion, None, 0)
.map_err(|err| {
io::Error::new(
io::ErrorKind::Other,
format!("GetVersion request failure: {:?}", err),
)
})?;
serde_json::to_string(&response).map_err(|err| {
io::Error::new(
io::ErrorKind::Other,
format!("GetVersion parse failure: {}", err),
)
})
}
pub fn send_and_confirm_transaction<T: KeypairUtil>(
&self,
transaction: &mut Transaction,
@@ -155,7 +116,7 @@ impl RpcClient {
if cfg!(not(test)) {
// Retry ~twice during a slot
sleep(Duration::from_millis(
500 * DEFAULT_TICKS_PER_SLOT / DEFAULT_TICKS_PER_SECOND,
500 * DEFAULT_TICKS_PER_SLOT / DEFAULT_NUM_TICKS_PER_SECOND,
));
}
};
@@ -201,7 +162,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_TICKS_PER_SECOND));
sleep(Duration::from_millis(1000 / DEFAULT_NUM_TICKS_PER_SECOND));
}
let signature = self.send_transaction(&transaction).ok();
@@ -215,7 +176,7 @@ impl RpcClient {
if cfg!(not(test)) {
// Retry ~twice during a slot
sleep(Duration::from_millis(
500 * DEFAULT_TICKS_PER_SLOT / DEFAULT_TICKS_PER_SECOND,
500 * DEFAULT_TICKS_PER_SLOT / DEFAULT_NUM_TICKS_PER_SECOND,
));
}
@@ -389,7 +350,7 @@ impl RpcClient {
let blockhash = blockhash.parse().map_err(|err| {
io::Error::new(
io::ErrorKind::Other,
format!("GetRecentBlockhash hash parse failure: {:?}", err),
format!("GetRecentBlockhash parse failure: {:?}", err),
)
})?;
Ok((blockhash, fee_calculator))
@@ -407,7 +368,7 @@ impl RpcClient {
// Retry ~twice during a slot
sleep(Duration::from_millis(
500 * DEFAULT_TICKS_PER_SLOT / DEFAULT_TICKS_PER_SECOND,
500 * DEFAULT_TICKS_PER_SLOT / DEFAULT_NUM_TICKS_PER_SECOND,
));
num_retries -= 1;
}
@@ -417,33 +378,6 @@ impl RpcClient {
))
}
pub fn get_genesis_blockhash(&self) -> io::Result<Hash> {
let response = self
.client
.send(&RpcRequest::GetGenesisBlockhash, None, 0)
.map_err(|err| {
io::Error::new(
io::ErrorKind::Other,
format!("GetGenesisBlockhash request failure: {:?}", err),
)
})?;
let blockhash = serde_json::from_value::<String>(response).map_err(|err| {
io::Error::new(
io::ErrorKind::Other,
format!("GetGenesisBlockhash parse failure: {:?}", err),
)
})?;
let blockhash = blockhash.parse().map_err(|err| {
io::Error::new(
io::ErrorKind::Other,
format!("GetGenesisBlockhash hash parse failure: {:?}", err),
)
})?;
Ok(blockhash)
}
pub fn poll_balance_with_timeout(
&self,
pubkey: &Pubkey,

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_TICKS_PER_SECOND, DEFAULT_TICKS_PER_SLOT};
use solana_sdk::timing::{DEFAULT_NUM_TICKS_PER_SECOND, DEFAULT_TICKS_PER_SLOT};
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_TICKS_PER_SECOND,
500 * DEFAULT_TICKS_PER_SLOT / DEFAULT_NUM_TICKS_PER_SECOND,
));
}
}

View File

@@ -9,21 +9,18 @@ pub enum RpcRequest {
GetAccountInfo,
GetBalance,
GetClusterNodes,
GetGenesisBlockhash,
GetInflation,
GetNumBlocksSinceSignatureConfirmation,
GetProgramAccounts,
GetRecentBlockhash,
GetSignatureStatus,
GetSlot,
GetSlotLeader,
GetEpochVoteAccounts,
GetStorageTurn,
GetStorageTurnRate,
GetSlotsPerSegment,
GetStoragePubkeysForSlot,
GetTransactionCount,
GetVersion,
GetVoteAccounts,
RegisterNode,
RequestAirdrop,
SendTransaction,
@@ -40,8 +37,6 @@ impl RpcRequest {
RpcRequest::GetAccountInfo => "getAccountInfo",
RpcRequest::GetBalance => "getBalance",
RpcRequest::GetClusterNodes => "getClusterNodes",
RpcRequest::GetGenesisBlockhash => "getGenesisBlockhash",
RpcRequest::GetInflation => "getInflation",
RpcRequest::GetNumBlocksSinceSignatureConfirmation => {
"getNumBlocksSinceSignatureConfirmation"
}
@@ -50,13 +45,12 @@ impl RpcRequest {
RpcRequest::GetSignatureStatus => "getSignatureStatus",
RpcRequest::GetSlot => "getSlot",
RpcRequest::GetSlotLeader => "getSlotLeader",
RpcRequest::GetEpochVoteAccounts => "getEpochVoteAccounts",
RpcRequest::GetStorageTurn => "getStorageTurn",
RpcRequest::GetStorageTurnRate => "getStorageTurnRate",
RpcRequest::GetSlotsPerSegment => "getSlotsPerSegment",
RpcRequest::GetStoragePubkeysForSlot => "getStoragePubkeysForSlot",
RpcRequest::GetTransactionCount => "getTransactionCount",
RpcRequest::GetVersion => "getVersion",
RpcRequest::GetVoteAccounts => "getVoteAccounts",
RpcRequest::RegisterNode => "registerNode",
RpcRequest::RequestAirdrop => "requestAirdrop",
RpcRequest::SendTransaction => "sendTransaction",
@@ -112,10 +106,6 @@ mod tests {
let request = test_request.build_request_json(1, Some(addr));
assert_eq!(request["method"], "getBalance");
let test_request = RpcRequest::GetInflation;
let request = test_request.build_request_json(1, None);
assert_eq!(request["method"], "getInflation");
let test_request = RpcRequest::GetRecentBlockhash;
let request = test_request.build_request_json(1, None);
assert_eq!(request["method"], "getRecentBlockhash");

View File

@@ -1,7 +1,7 @@
[package]
name = "solana-core"
name = "solana"
description = "Blockchain, Rebuilt for Scale"
version = "0.18.1"
version = "0.17.2"
documentation = "https://docs.rs/solana"
homepage = "https://solana.com/"
readme = "../README.md"
@@ -19,61 +19,55 @@ kvstore = ["solana-kvstore"]
[dependencies]
bincode = "1.1.4"
bs58 = "0.2.4"
bs58 = "0.2.0"
byteorder = "1.3.2"
bzip2 = "0.3.3"
chrono = { version = "0.4.7", features = ["serde"] }
core_affinity = "0.5.9"
crc = { version = "1.8.1", optional = true }
crossbeam-channel = "0.3"
dir-diff = "0.3.1"
fs_extra = "1.1.0"
hashbrown = "0.2.0"
indexmap = "1.0"
itertools = "0.8.0"
jsonrpc-core = "13.0.0"
jsonrpc-derive = "13.0.0"
jsonrpc-http-server = "13.0.0"
jsonrpc-pubsub = "13.0.0"
jsonrpc-ws-server = "13.0.0"
libc = "0.2.62"
log = "0.4.8"
jsonrpc-core = "12.1.0"
jsonrpc-derive = "12.1.0"
jsonrpc-http-server = "12.1.0"
jsonrpc-pubsub = "12.0.0"
jsonrpc-ws-server = "12.1.0"
libc = "0.2.58"
log = "0.4.7"
memmap = { version = "0.7.0", optional = true }
nix = "0.15.0"
nix = "0.14.1"
num-traits = "0.2"
rand = "0.6.5"
rand_chacha = "0.1.1"
rayon = "1.1.0"
reqwest = "0.9.20"
serde = "1.0.99"
serde_derive = "1.0.99"
reqwest = "0.9.19"
rocksdb = "0.11.0"
serde = "1.0.97"
serde_derive = "1.0.97"
serde_json = "1.0.40"
solana-budget-api = { path = "../programs/budget_api", version = "0.18.1" }
solana-budget-program = { path = "../programs/budget_program", version = "0.18.1" }
solana-chacha-sys = { path = "../chacha-sys", version = "0.18.1" }
solana-client = { path = "../client", version = "0.18.1" }
solana-drone = { path = "../drone", version = "0.18.1" }
solana-budget-api = { path = "../programs/budget_api", version = "0.17.2" }
solana-budget-program = { path = "../programs/budget_program", version = "0.17.2" }
solana-chacha-sys = { path = "../chacha-sys", version = "0.17.2" }
solana-client = { path = "../client", version = "0.17.2" }
solana-drone = { path = "../drone", version = "0.17.2" }
solana-ed25519-dalek = "0.2.0"
solana-kvstore = { path = "../kvstore", version = "0.18.1", optional = true }
solana-logger = { path = "../logger", version = "0.18.1" }
solana-merkle-tree = { path = "../merkle-tree", version = "0.18.1" }
solana-metrics = { path = "../metrics", version = "0.18.1" }
solana-measure = { path = "../measure", version = "0.18.1" }
solana-netutil = { path = "../utils/netutil", version = "0.18.1" }
solana-runtime = { path = "../runtime", version = "0.18.1" }
solana-sdk = { path = "../sdk", version = "0.18.1" }
solana-stake-api = { path = "../programs/stake_api", version = "0.18.1" }
solana-storage-api = { path = "../programs/storage_api", version = "0.18.1" }
solana-storage-program = { path = "../programs/storage_program", version = "0.18.1" }
solana-vote-api = { path = "../programs/vote_api", version = "0.18.1" }
solana-vote-signer = { path = "../vote-signer", version = "0.18.1" }
symlink = "0.1.0"
solana-kvstore = { path = "../kvstore", version = "0.17.2", optional = true }
solana-logger = { path = "../logger", version = "0.17.2" }
solana-merkle-tree = { path = "../merkle-tree", version = "0.17.2" }
solana-metrics = { path = "../metrics", version = "0.17.2" }
solana-measure = { path = "../measure", version = "0.17.2" }
solana-netutil = { path = "../netutil", version = "0.17.2" }
solana-runtime = { path = "../runtime", version = "0.17.2" }
solana-sdk = { path = "../sdk", version = "0.17.2" }
solana-stake-api = { path = "../programs/stake_api", version = "0.17.2" }
solana-storage-api = { path = "../programs/storage_api", version = "0.17.2" }
solana-storage-program = { path = "../programs/storage_program", version = "0.17.2" }
solana-vote-api = { path = "../programs/vote_api", version = "0.17.2" }
solana-vote-signer = { path = "../vote-signer", version = "0.17.2" }
sys-info = "0.5.7"
tar = "0.4.26"
tempfile = "3.1.0"
tokio = "0.1"
tokio-codec = "0.1"
tokio-fs = "0.1"
tokio-io = "0.1"
untrusted = "0.7.0"
# reed-solomon-erasure's simd_c feature fails to build for x86_64-pc-windows-msvc, use pure-rust
@@ -82,13 +76,6 @@ reed-solomon-erasure = { version = "3.1.1", features = ["pure-rust"] }
[target.'cfg(not(windows))'.dependencies]
reed-solomon-erasure = "3.1.1"
[dependencies.rocksdb]
# Avoid the vendored bzip2 within rocksdb-sys that can cause linker conflicts
# when also using the bzip2 crate
version = "0.11.0"
default-features = false
features = ["lz4"]
[dev-dependencies]
hex-literal = "0.2.0"
matches = "0.1.6"

View File

@@ -2,21 +2,21 @@
extern crate test;
#[macro_use]
extern crate solana_core;
extern crate solana;
use crossbeam_channel::unbounded;
use log::*;
use rand::{thread_rng, Rng};
use rayon::prelude::*;
use solana_core::banking_stage::{create_test_recorder, BankingStage};
use solana_core::blocktree::{get_tmp_ledger_path, Blocktree};
use solana_core::cluster_info::ClusterInfo;
use solana_core::cluster_info::Node;
use solana_core::genesis_utils::{create_genesis_block, GenesisBlockInfo};
use solana_core::packet::to_packets_chunked;
use solana_core::poh_recorder::WorkingBankEntries;
use solana_core::service::Service;
use solana_core::test_tx::test_tx;
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::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::hash::Hash;
use solana_sdk::pubkey::Pubkey;

View File

@@ -4,20 +4,19 @@ use rand;
extern crate test;
#[macro_use]
extern crate solana_core;
extern crate solana;
use rand::seq::SliceRandom;
use rand::{thread_rng, Rng};
use solana_core::blocktree::{get_tmp_ledger_path, Blocktree};
use solana_core::entry::{make_large_test_entries, make_tiny_test_entries, EntrySlice};
use solana_core::packet::{Blob, BLOB_HEADER_SIZE};
use std::path::Path;
use solana::blocktree::{get_tmp_ledger_path, Blocktree};
use solana::entry::{make_large_test_entries, make_tiny_test_entries, EntrySlice};
use solana::packet::{Blob, BLOB_HEADER_SIZE};
use test::Bencher;
// Given some blobs and a ledger at ledger_path, benchmark writing the blobs to the ledger
fn bench_write_blobs(bench: &mut Bencher, blobs: &mut Vec<Blob>, ledger_path: &Path) {
fn bench_write_blobs(bench: &mut Bencher, blobs: &mut Vec<Blob>, ledger_path: &str) {
let blocktree =
Blocktree::open(ledger_path).expect("Expected to be able to open database ledger");
Blocktree::open(&ledger_path).expect("Expected to be able to open database ledger");
let num_blobs = blobs.len();
@@ -37,7 +36,7 @@ fn bench_write_blobs(bench: &mut Bencher, blobs: &mut Vec<Blob>, ledger_path: &P
}
});
Blocktree::destroy(ledger_path).expect("Expected successful database destruction");
Blocktree::destroy(&ledger_path).expect("Expected successful database destruction");
}
// Insert some blobs into the ledger in preparation for read benchmarks
@@ -111,7 +110,7 @@ fn bench_read_sequential(bench: &mut Bencher) {
// Generate random starting point in the range [0, total_blobs - 1], read num_reads blobs sequentially
let start_index = rng.gen_range(0, num_small_blobs + num_large_blobs);
for i in start_index..start_index + num_reads {
let _ = blocktree.get_data_shred_as_blob(slot, i as u64 % total_blobs);
let _ = blocktree.get_data_blob(slot, i as u64 % total_blobs);
}
});
@@ -142,7 +141,7 @@ fn bench_read_random(bench: &mut Bencher) {
.collect();
bench.iter(move || {
for i in indexes.iter() {
let _ = blocktree.get_data_shred_as_blob(slot, *i as u64);
let _ = blocktree.get_data_blob(slot, *i as u64);
}
});

View File

@@ -1,9 +1,9 @@
//#![feature(test)]
//
//extern crate solana_core;
//extern crate solana;
//extern crate test;
//
//use solana_core::chacha::chacha_cbc_encrypt_files;
//use solana::chacha::chacha_cbc_encrypt_files;
//use std::fs::remove_file;
//use std::fs::File;
//use std::io::Write;

View File

@@ -2,7 +2,7 @@
extern crate test;
use solana_core::gen_keys::GenKeys;
use solana::gen_keys::GenKeys;
use test::Bencher;
#[bench]

View File

@@ -2,7 +2,7 @@
extern crate test;
use solana_core::entry::{next_entries, reconstruct_entries_from_blobs, EntrySlice};
use solana::entry::{next_entries, reconstruct_entries_from_blobs, EntrySlice};
use solana_sdk::hash::{hash, Hash};
use solana_sdk::signature::{Keypair, KeypairUtil};
use solana_sdk::system_transaction;

View File

@@ -1,10 +1,10 @@
// This bench attempts to justify the value of `solana_core::poh_service::NUM_HASHES_PER_BATCH`
// This bench attempts to justify the value of `solana::poh_service::NUM_HASHES_PER_BATCH`
#![feature(test)]
extern crate test;
use solana_core::poh::Poh;
use solana_core::poh_service::NUM_HASHES_PER_BATCH;
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};

View File

@@ -1,8 +1,8 @@
#![feature(test)]
extern crate test;
use solana_core::entry::EntrySlice;
use solana_core::entry::{next_entry_mut, Entry};
use solana::entry::EntrySlice;
use solana::entry::{next_entry_mut, Entry};
use solana_sdk::hash::{hash, Hash};
use solana_sdk::signature::{Keypair, KeypairUtil};
use solana_sdk::system_transaction;

View File

@@ -2,10 +2,10 @@
extern crate test;
use solana_core::packet::to_packets;
use solana_core::recycler::Recycler;
use solana_core::sigverify;
use solana_core::test_tx::test_tx;
use solana::packet::to_packets;
use solana::recycler::Recycler;
use solana::sigverify;
use solana::test_tx::test_tx;
use test::Bencher;
#[bench]

View File

@@ -1,15 +1,15 @@
#![feature(test)]
extern crate solana_core;
extern crate solana;
extern crate test;
use crossbeam_channel::unbounded;
use log::*;
use rand::{thread_rng, Rng};
use solana_core::packet::to_packets_chunked;
use solana_core::service::Service;
use solana_core::sigverify_stage::SigVerifyStage;
use solana_core::test_tx::test_tx;
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;

View File

@@ -1,37 +1,25 @@
//! The `bank_forks` module implments BankForks a DAG of checkpointed Banks
use crate::result::Result;
use crate::snapshot_package::SnapshotPackageSender;
use crate::snapshot_utils;
use solana_measure::measure::Measure;
use bincode::{deserialize_from, serialize_into};
use solana_metrics::inc_new_counter_info;
use solana_runtime::bank::Bank;
use solana_runtime::status_cache::MAX_CACHE_ENTRIES;
use solana_runtime::bank::{Bank, BankRc, StatusCacheRc};
use solana_sdk::genesis_block::GenesisBlock;
use solana_sdk::timing;
use std::collections::{HashMap, HashSet};
use std::fs;
use std::fs::File;
use std::io::{BufReader, BufWriter, Error, ErrorKind};
use std::ops::Index;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use std::time::Instant;
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct SnapshotConfig {
// Generate a new snapshot every this many slots
pub snapshot_interval_slots: usize,
// Where to store the latest packaged snapshot
pub snapshot_package_output_path: PathBuf,
// Where to place the snapshots for recent slots
pub snapshot_path: PathBuf,
}
pub struct BankForks {
banks: HashMap<u64, Arc<Bank>>,
working_bank: Arc<Bank>,
root: u64,
snapshot_config: Option<SnapshotConfig>,
slots_since_snapshot: Vec<u64>,
slots: HashSet<u64>,
snapshot_path: Option<String>,
confidence: HashMap<u64, Confidence>,
}
@@ -83,8 +71,8 @@ impl BankForks {
banks,
working_bank,
root: 0,
snapshot_config: None,
slots_since_snapshot: vec![bank_slot],
slots: HashSet::new(),
snapshot_path: None,
confidence: HashMap::new(),
}
}
@@ -138,29 +126,18 @@ impl BankForks {
self.banks.get(&bank_slot)
}
pub fn new_from_banks(initial_forks: &[Arc<Bank>], rooted_path: Vec<u64>) -> Self {
pub fn new_from_banks(initial_banks: &[Arc<Bank>], root: u64) -> Self {
let mut banks = HashMap::new();
let working_bank = initial_forks[0].clone();
// Iterate through the heads of all the different forks
for bank in initial_forks {
let working_bank = initial_banks[0].clone();
for bank in initial_banks {
banks.insert(bank.slot(), bank.clone());
let parents = bank.parents();
for parent in parents {
if banks.contains_key(&parent.slot()) {
// All ancestors have already been inserted by another fork
break;
}
banks.insert(parent.slot(), parent.clone());
}
}
Self {
root: *rooted_path.last().unwrap(),
root,
banks,
working_bank,
snapshot_config: None,
slots_since_snapshot: rooted_path,
slots: HashSet::new(),
snapshot_path: None,
confidence: HashMap::new(),
}
}
@@ -179,7 +156,7 @@ impl BankForks {
self.working_bank.clone()
}
pub fn set_root(&mut self, root: u64, snapshot_package_sender: &Option<SnapshotPackageSender>) {
pub fn set_root(&mut self, root: u64) {
self.root = root;
let set_root_start = Instant::now();
let root_bank = self
@@ -191,51 +168,8 @@ impl BankForks {
.last()
.map(|bank| bank.transaction_count())
.unwrap_or(0);
if self.snapshot_config.is_some() && snapshot_package_sender.is_some() {
let new_rooted_path = root_bank
.parents()
.into_iter()
.map(|p| p.slot())
.rev()
.skip(1);
self.slots_since_snapshot.extend(new_rooted_path);
self.slots_since_snapshot.push(root);
if self.slots_since_snapshot.len() > MAX_CACHE_ENTRIES {
let num_to_remove = self.slots_since_snapshot.len() - MAX_CACHE_ENTRIES;
self.slots_since_snapshot.drain(0..num_to_remove);
}
}
root_bank.squash();
let new_tx_count = root_bank.transaction_count();
// Generate a snapshot if snapshots are configured and it's been an appropriate number
// of banks since the last snapshot
if self.snapshot_config.is_some() && snapshot_package_sender.is_some() {
let config = self.snapshot_config.as_ref().unwrap();
info!("setting snapshot root: {}", root);
if root - self.slots_since_snapshot[0] >= config.snapshot_interval_slots as u64 {
let mut snapshot_time = Measure::start("total-snapshot-ms");
let r = self.generate_snapshot(
root,
&self.slots_since_snapshot[1..],
snapshot_package_sender.as_ref().unwrap(),
snapshot_utils::get_snapshot_tar_path(&config.snapshot_package_output_path),
);
if r.is_err() {
warn!("Error generating snapshot for bank: {}, err: {:?}", root, r);
} else {
self.slots_since_snapshot = vec![root];
}
// Cleanup outdated snapshots
self.purge_old_snapshots();
snapshot_time.stop();
inc_new_counter_info!("total-snapshot-setup-ms", snapshot_time.as_ms() as usize);
}
}
self.prune_non_root(root);
inc_new_counter_info!(
@@ -252,65 +186,30 @@ impl BankForks {
self.root
}
pub fn slots_since_snapshot(&self) -> &[u64] {
&self.slots_since_snapshot
}
fn purge_old_snapshots(&self) {
// Remove outdated snapshots
let config = self.snapshot_config.as_ref().unwrap();
let slot_snapshot_paths = snapshot_utils::get_snapshot_paths(&config.snapshot_path);
let num_to_remove = slot_snapshot_paths.len().saturating_sub(MAX_CACHE_ENTRIES);
for slot_files in &slot_snapshot_paths[..num_to_remove] {
let r = snapshot_utils::remove_snapshot(slot_files.slot, &config.snapshot_path);
if r.is_err() {
warn!("Couldn't remove snapshot at: {:?}", config.snapshot_path);
}
}
}
fn generate_snapshot<P: AsRef<Path>>(
&self,
root: u64,
slots_since_snapshot: &[u64],
snapshot_package_sender: &SnapshotPackageSender,
tar_output_file: P,
) -> Result<()> {
let config = self.snapshot_config.as_ref().unwrap();
// Add a snapshot for the new root
let bank = self
.get(root)
.cloned()
.expect("root must exist in BankForks");
snapshot_utils::add_snapshot(&config.snapshot_path, &bank, slots_since_snapshot)?;
// Package the relevant snapshots
let slot_snapshot_paths = snapshot_utils::get_snapshot_paths(&config.snapshot_path);
// We only care about the last MAX_CACHE_ENTRIES snapshots of roots because
// the status cache of anything older is thrown away by the bank in
// status_cache.prune_roots()
let start = slot_snapshot_paths.len().saturating_sub(MAX_CACHE_ENTRIES);
let package = snapshot_utils::package_snapshot(
&bank,
&slot_snapshot_paths[start..],
tar_output_file,
&config.snapshot_path,
)?;
// Send the package to the packaging thread
snapshot_package_sender.send(package)?;
Ok(())
}
fn prune_non_root(&mut self, root: u64) {
let slots: HashSet<u64> = self
.banks
.iter()
.filter(|(_, b)| b.is_frozen())
.map(|(k, _)| *k)
.collect();
let descendants = self.descendants();
self.banks
.retain(|slot, _| slot == &root || descendants[&root].contains(slot));
.retain(|slot, _| descendants[&root].contains(slot));
self.confidence
.retain(|slot, _| slot == &root || descendants[&root].contains(slot));
if self.snapshot_path.is_some() {
let diff: HashSet<_> = slots.symmetric_difference(&self.slots).collect();
trace!("prune non root {} - {:?}", root, diff);
for slot in diff.iter() {
if **slot > root {
let _ = self.add_snapshot(**slot, root);
} else {
BankForks::remove_snapshot(**slot, &self.snapshot_path);
}
}
}
self.slots = slots.clone();
}
pub fn cache_fork_confidence(
@@ -348,12 +247,189 @@ impl BankForks {
self.confidence.get(&fork)
}
pub fn set_snapshot_config(&mut self, snapshot_config: SnapshotConfig) {
self.snapshot_config = Some(snapshot_config);
fn get_io_error(error: &str) -> Error {
warn!("BankForks error: {:?}", error);
Error::new(ErrorKind::Other, error)
}
pub fn snapshot_config(&self) -> &Option<SnapshotConfig> {
&self.snapshot_config
fn get_snapshot_path(path: &Option<String>) -> PathBuf {
Path::new(&path.clone().unwrap()).to_path_buf()
}
pub fn add_snapshot(&self, slot: u64, root: u64) -> Result<(), Error> {
let path = BankForks::get_snapshot_path(&self.snapshot_path);
fs::create_dir_all(path.clone())?;
let bank_file = format!("{}", slot);
let bank_file_path = path.join(bank_file);
trace!("path: {:?}", bank_file_path);
let file = File::create(bank_file_path)?;
let mut stream = BufWriter::new(file);
let bank_slot = self.get(slot);
if bank_slot.is_none() {
return Err(BankForks::get_io_error("bank_forks get error"));
}
let bank = bank_slot.unwrap().clone();
serialize_into(&mut stream, &*bank)
.map_err(|_| BankForks::get_io_error("serialize bank error"))?;
let mut parent_slot: u64 = 0;
if let Some(parent_bank) = bank.parent() {
parent_slot = parent_bank.slot();
}
serialize_into(&mut stream, &parent_slot)
.map_err(|_| BankForks::get_io_error("serialize bank parent error"))?;
serialize_into(&mut stream, &root)
.map_err(|_| BankForks::get_io_error("serialize root error"))?;
serialize_into(&mut stream, &bank.src)
.map_err(|_| BankForks::get_io_error("serialize bank status cache error"))?;
serialize_into(&mut stream, &bank.rc)
.map_err(|_| BankForks::get_io_error("serialize bank accounts error"))?;
Ok(())
}
pub fn remove_snapshot(slot: u64, path: &Option<String>) {
let path = BankForks::get_snapshot_path(path);
let bank_file = format!("{}", slot);
let bank_file_path = path.join(bank_file);
let _ = fs::remove_file(bank_file_path);
}
pub fn set_snapshot_config(&mut self, path: Option<String>) {
self.snapshot_path = path;
}
fn load_snapshots(
names: &[u64],
bank0: &mut Bank,
bank_maps: &mut Vec<(u64, u64, Bank)>,
status_cache_rc: &StatusCacheRc,
snapshot_path: &Option<String>,
) -> Option<u64> {
let path = BankForks::get_snapshot_path(snapshot_path);
let mut bank_root: Option<u64> = None;
for bank_slot in names.iter().rev() {
let bank_path = format!("{}", bank_slot);
let bank_file_path = path.join(bank_path.clone());
info!("Load from {:?}", bank_file_path);
let file = File::open(bank_file_path);
if file.is_err() {
warn!("Snapshot file open failed for {}", bank_slot);
continue;
}
let file = file.unwrap();
let mut stream = BufReader::new(file);
let bank: Result<Bank, std::io::Error> = deserialize_from(&mut stream)
.map_err(|_| BankForks::get_io_error("deserialize bank error"));
let slot: Result<u64, std::io::Error> = deserialize_from(&mut stream)
.map_err(|_| BankForks::get_io_error("deserialize bank parent error"));
let parent_slot = if slot.is_ok() { slot.unwrap() } else { 0 };
let root: Result<u64, std::io::Error> = deserialize_from(&mut stream)
.map_err(|_| BankForks::get_io_error("deserialize root error"));
let status_cache: Result<StatusCacheRc, std::io::Error> = deserialize_from(&mut stream)
.map_err(|_| BankForks::get_io_error("deserialize bank status cache error"));
if bank_root.is_none() && bank0.rc.update_from_stream(&mut stream).is_ok() {
bank_root = Some(root.unwrap());
}
if bank_root.is_some() {
match bank {
Ok(v) => {
if status_cache.is_ok() {
status_cache_rc.append(&status_cache.unwrap());
}
bank_maps.push((*bank_slot, parent_slot, v));
}
Err(_) => warn!("Load snapshot failed for {}", bank_slot),
}
} else {
BankForks::remove_snapshot(*bank_slot, snapshot_path);
warn!("Load snapshot rc failed for {}", bank_slot);
}
}
bank_root
}
fn setup_banks(
bank_maps: &mut Vec<(u64, u64, Bank)>,
bank_rc: &BankRc,
status_cache_rc: &StatusCacheRc,
) -> (HashMap<u64, Arc<Bank>>, HashSet<u64>, u64) {
let mut banks = HashMap::new();
let mut slots = HashSet::new();
let (last_slot, last_parent_slot, mut last_bank) = bank_maps.remove(0);
last_bank.set_bank_rc(&bank_rc, &status_cache_rc);
while let Some((slot, parent_slot, mut bank)) = bank_maps.pop() {
bank.set_bank_rc(&bank_rc, &status_cache_rc);
if parent_slot != 0 {
if let Some(parent) = banks.get(&parent_slot) {
bank.set_parent(parent);
}
}
if slot > 0 {
banks.insert(slot, Arc::new(bank));
slots.insert(slot);
}
}
if last_parent_slot != 0 {
if let Some(parent) = banks.get(&last_parent_slot) {
last_bank.set_parent(parent);
}
}
banks.insert(last_slot, Arc::new(last_bank));
slots.insert(last_slot);
(banks, slots, last_slot)
}
pub fn load_from_snapshot(
genesis_block: &GenesisBlock,
account_paths: Option<String>,
snapshot_path: &Option<String>,
) -> Result<Self, Error> {
let path = BankForks::get_snapshot_path(snapshot_path);
let paths = fs::read_dir(path)?;
let mut names = paths
.filter_map(|entry| {
entry.ok().and_then(|e| {
e.path()
.file_name()
.and_then(|n| n.to_str().map(|s| s.parse::<u64>().unwrap()))
})
})
.collect::<Vec<u64>>();
names.sort();
let mut bank_maps = vec![];
let status_cache_rc = StatusCacheRc::default();
let id = (names[names.len() - 1] + 1) as usize;
let mut bank0 =
Bank::create_with_genesis(&genesis_block, account_paths.clone(), &status_cache_rc, id);
bank0.freeze();
let bank_root = BankForks::load_snapshots(
&names,
&mut bank0,
&mut bank_maps,
&status_cache_rc,
snapshot_path,
);
if bank_maps.is_empty() || bank_root.is_none() {
BankForks::remove_snapshot(0, snapshot_path);
return Err(Error::new(ErrorKind::Other, "no snapshots loaded"));
}
let root = bank_root.unwrap();
let (banks, slots, last_slot) =
BankForks::setup_banks(&mut bank_maps, &bank0.rc, &status_cache_rc);
let working_bank = banks[&last_slot].clone();
Ok(BankForks {
banks,
working_bank,
root,
slots,
snapshot_path: snapshot_path.clone(),
confidence: HashMap::new(),
})
}
}
@@ -361,18 +437,12 @@ impl BankForks {
mod tests {
use super::*;
use crate::genesis_utils::{create_genesis_block, GenesisBlockInfo};
use crate::service::Service;
use crate::snapshot_package::SnapshotPackagerService;
use fs_extra::dir::CopyOptions;
use itertools::Itertools;
use solana_sdk::hash::{hashv, Hash};
use solana_sdk::hash::Hash;
use solana_sdk::pubkey::Pubkey;
use solana_sdk::signature::{Keypair, KeypairUtil};
use solana_sdk::system_transaction;
use std::fs;
use std::sync::atomic::AtomicBool;
use std::sync::mpsc::channel;
use tempfile::TempDir;
use std::env;
use std::fs::remove_dir_all;
#[test]
fn test_bank_forks() {
@@ -482,361 +552,120 @@ mod tests {
);
}
fn restore_from_snapshot(old_bank_forks: &BankForks, account_paths: String) {
let (snapshot_path, snapshot_package_output_path) = old_bank_forks
.snapshot_config
.as_ref()
.map(|c| (&c.snapshot_path, &c.snapshot_package_output_path))
.unwrap();
struct TempPaths {
pub paths: String,
}
let deserialized_bank = snapshot_utils::bank_from_archive(
account_paths,
old_bank_forks.snapshot_config.as_ref().unwrap(),
snapshot_utils::get_snapshot_tar_path(snapshot_package_output_path),
)
.unwrap();
let bank = old_bank_forks
.banks
.get(&deserialized_bank.slot())
.unwrap()
.clone();
bank.compare_bank(&deserialized_bank);
let slot_snapshot_paths = snapshot_utils::get_snapshot_paths(&snapshot_path);
for p in slot_snapshot_paths {
snapshot_utils::remove_snapshot(p.slot, &snapshot_path).unwrap();
impl TempPaths {
fn remove_all(&self) {
let paths: Vec<String> = self.paths.split(',').map(|s| s.to_string()).collect();
paths.iter().for_each(|p| {
let _ignored = remove_dir_all(p);
});
}
}
// creates banks up to "last_slot" and runs the input function `f` on each bank created
// also marks each bank as root and generates snapshots
// finally tries to restore from the last bank's snapshot and compares the restored bank to the
// `last_slot` bank
fn run_bank_forks_snapshot_n<F>(last_slot: u64, f: F, set_root_interval: u64)
where
F: Fn(&mut Bank, &Keypair),
{
solana_logger::setup();
// Set up snapshotting config
let mut snapshot_test_config = setup_snapshot_test(1);
#[macro_export]
macro_rules! tmp_bank_accounts_name {
() => {
&format!("{}-{}", file!(), line!())
};
}
let bank_forks = &mut snapshot_test_config.bank_forks;
let accounts_dir = &snapshot_test_config.accounts_dir;
let snapshot_config = &snapshot_test_config.snapshot_config;
let mint_keypair = &snapshot_test_config.genesis_block_info.mint_keypair;
#[macro_export]
macro_rules! get_tmp_bank_accounts_path {
() => {
get_tmp_bank_accounts_path(tmp_bank_accounts_name!())
};
}
let (s, _r) = channel();
let sender = Some(s);
for slot in 0..last_slot {
let mut bank = Bank::new_from_parent(&bank_forks[slot], &Pubkey::default(), slot + 1);
f(&mut bank, &mint_keypair);
let bank = bank_forks.insert(bank);
// Set root to make sure we don't end up with too many account storage entries
// and to allow snapshotting of bank and the purging logic on status_cache to
// kick in
if slot % set_root_interval == 0 || slot == last_slot - 1 {
bank_forks.set_root(bank.slot(), &sender);
impl Drop for TempPaths {
fn drop(&mut self) {
self.remove_all()
}
}
fn get_paths_vec(paths: &str) -> Vec<String> {
paths.split(',').map(|s| s.to_string()).collect()
}
fn get_tmp_snapshots_path() -> TempPaths {
let out_dir = env::var("FARF_DIR").unwrap_or_else(|_| "farf".to_string());
let path = format!("{}/snapshots", out_dir);
TempPaths {
paths: path.to_string(),
}
}
fn get_tmp_bank_accounts_path(paths: &str) -> TempPaths {
let vpaths = get_paths_vec(paths);
let out_dir = env::var("FARF_DIR").unwrap_or_else(|_| "farf".to_string());
let vpaths: Vec<_> = vpaths
.iter()
.map(|path| format!("{}/{}", out_dir, path))
.collect();
TempPaths {
paths: vpaths.join(","),
}
}
fn restore_from_snapshot(
genesis_block: &GenesisBlock,
bank_forks: BankForks,
account_paths: Option<String>,
last_slot: u64,
) {
let new =
BankForks::load_from_snapshot(&genesis_block, account_paths, &bank_forks.snapshot_path)
.unwrap();
for (slot, _) in new.banks.iter() {
if *slot > 0 {
let bank = bank_forks.banks.get(slot).unwrap().clone();
let new_bank = new.banks.get(slot).unwrap();
bank.compare_bank(&new_bank);
}
}
// Generate a snapshot package for last bank
let last_bank = bank_forks.get(last_slot).unwrap();
let slot_snapshot_paths =
snapshot_utils::get_snapshot_paths(&snapshot_config.snapshot_path);
let snapshot_package = snapshot_utils::package_snapshot(
last_bank,
&slot_snapshot_paths,
snapshot_utils::get_snapshot_tar_path(&snapshot_config.snapshot_package_output_path),
&snapshot_config.snapshot_path,
)
.unwrap();
SnapshotPackagerService::package_snapshots(&snapshot_package).unwrap();
restore_from_snapshot(
bank_forks,
accounts_dir.path().to_str().unwrap().to_string(),
);
assert_eq!(new.working_bank().slot(), last_slot);
for (slot, _) in new.banks.iter() {
BankForks::remove_snapshot(*slot, &bank_forks.snapshot_path);
}
}
#[test]
fn test_bank_forks_snapshot_n() {
// create banks upto slot 4 and create 1 new account in each bank. test that bank 4 snapshots
// and restores correctly
run_bank_forks_snapshot_n(
4,
|bank, mint_keypair| {
solana_logger::setup();
let path = get_tmp_bank_accounts_path!();
let spath = get_tmp_snapshots_path();
let GenesisBlockInfo {
genesis_block,
mint_keypair,
..
} = create_genesis_block(10_000);
path.remove_all();
spath.remove_all();
for index in 0..10 {
let bank0 = Bank::new_with_paths(&genesis_block, Some(path.paths.clone()));
bank0.freeze();
let slot = bank0.slot();
let mut bank_forks = BankForks::new(0, bank0);
bank_forks.set_snapshot_config(Some(spath.paths.clone()));
bank_forks.add_snapshot(slot, 0).unwrap();
for forks in 0..index {
let bank = Bank::new_from_parent(&bank_forks[forks], &Pubkey::default(), forks + 1);
let key1 = Keypair::new().pubkey();
let tx = system_transaction::create_user_account(
&mint_keypair,
&key1,
1,
bank.last_blockhash(),
genesis_block.hash(),
);
assert_eq!(bank.process_transaction(&tx), Ok(()));
bank.freeze();
},
1,
);
}
fn goto_end_of_slot(bank: &mut Bank) {
let mut tick_hash = bank.last_blockhash();
loop {
tick_hash = hashv(&[&tick_hash.as_ref(), &[42]]);
bank.register_tick(&tick_hash);
if tick_hash == bank.last_blockhash() {
bank.freeze();
return;
let slot = bank.slot();
bank_forks.insert(bank);
bank_forks.add_snapshot(slot, 0).unwrap();
}
}
}
#[test]
fn test_bank_forks_status_cache_snapshot_n() {
// create banks upto slot (MAX_CACHE_ENTRIES * 2) + 1 while transferring 1 lamport into 2 different accounts each time
// this is done to ensure the AccountStorageEntries keep getting cleaned up as the root moves
// ahead. Also tests the status_cache purge and status cache snapshotting.
// Makes sure that the last bank is restored correctly
let key1 = Keypair::new().pubkey();
let key2 = Keypair::new().pubkey();
for set_root_interval in &[1, 4] {
run_bank_forks_snapshot_n(
(MAX_CACHE_ENTRIES * 2 + 1) as u64,
|bank, mint_keypair| {
let tx = system_transaction::transfer(
&mint_keypair,
&key1,
1,
bank.parent().unwrap().last_blockhash(),
);
assert_eq!(bank.process_transaction(&tx), Ok(()));
let tx = system_transaction::transfer(
&mint_keypair,
&key2,
1,
bank.parent().unwrap().last_blockhash(),
);
assert_eq!(bank.process_transaction(&tx), Ok(()));
goto_end_of_slot(bank);
},
*set_root_interval,
);
}
}
#[test]
fn test_concurrent_snapshot_packaging() {
solana_logger::setup();
// Set up snapshotting config
let mut snapshot_test_config = setup_snapshot_test(1);
let bank_forks = &mut snapshot_test_config.bank_forks;
let accounts_dir = &snapshot_test_config.accounts_dir;
let snapshots_dir = &snapshot_test_config.snapshot_dir;
let snapshot_config = &snapshot_test_config.snapshot_config;
let mint_keypair = &snapshot_test_config.genesis_block_info.mint_keypair;
let genesis_block = &snapshot_test_config.genesis_block_info.genesis_block;
// Take snapshot of zeroth bank
let bank0 = bank_forks.get(0).unwrap();
snapshot_utils::add_snapshot(&snapshot_config.snapshot_path, bank0, &vec![]).unwrap();
// Set up snapshotting channels
let (sender, receiver) = channel();
let (fake_sender, _fake_receiver) = channel();
// Create next MAX_CACHE_ENTRIES + 2 banks and snapshots. Every bank will get snapshotted
// and the snapshot purging logic will run on every snapshot taken. This means the three
// (including snapshot for bank0 created above) earliest snapshots will get purged by the
// time this loop is done.
// Also, make a saved copy of the state of the snapshot for a bank with
// bank.slot == saved_slot, so we can use it for a correctness check later.
let saved_snapshots_dir = TempDir::new().unwrap();
let saved_accounts_dir = TempDir::new().unwrap();
let saved_slot = 4;
let saved_tar = snapshot_config
.snapshot_package_output_path
.join(saved_slot.to_string());
for forks in 0..MAX_CACHE_ENTRIES + 2 {
let bank = Bank::new_from_parent(
&bank_forks[forks as u64],
&Pubkey::default(),
(forks + 1) as u64,
);
let slot = bank.slot();
let key1 = Keypair::new().pubkey();
let tx = system_transaction::create_user_account(
&mint_keypair,
&key1,
1,
genesis_block.hash(),
);
assert_eq!(bank.process_transaction(&tx), Ok(()));
bank.freeze();
bank_forks.insert(bank);
let package_sender = {
if slot == saved_slot as u64 {
// Only send one package on the real sender so that the packaging service
// doesn't take forever to run the packaging logic on all MAX_CACHE_ENTRIES
// later
&sender
} else {
&fake_sender
}
};
bank_forks
.generate_snapshot(
slot,
&vec![],
&package_sender,
snapshot_config
.snapshot_package_output_path
.join(slot.to_string()),
)
.unwrap();
if slot == saved_slot as u64 {
let options = CopyOptions::new();
fs_extra::dir::copy(accounts_dir, &saved_accounts_dir, &options).unwrap();
let snapshot_paths: Vec<_> = fs::read_dir(&snapshot_config.snapshot_path)
.unwrap()
.filter_map(|entry| {
let e = entry.unwrap();
let file_path = e.path();
let file_name = file_path.file_name().unwrap();
file_name
.to_str()
.map(|s| s.parse::<u64>().ok().map(|_| file_path.clone()))
.unwrap_or(None)
})
.collect();
for snapshot_path in snapshot_paths {
fs_extra::dir::copy(&snapshot_path, &saved_snapshots_dir, &options).unwrap();
}
}
}
// Purge all the outdated snapshots, including the ones needed to generate the package
// currently sitting in the channel
bank_forks.purge_old_snapshots();
let mut snapshot_paths = snapshot_utils::get_snapshot_paths(&snapshots_dir);
snapshot_paths.sort();
assert_eq!(
snapshot_paths.iter().map(|path| path.slot).collect_vec(),
(3..=MAX_CACHE_ENTRIES as u64 + 2).collect_vec()
);
// Create a SnapshotPackagerService to create tarballs from all the pending
// SnapshotPackage's on the channel. By the time this service starts, we have already
// purged the first two snapshots, which are needed by every snapshot other than
// the last two snapshots. However, the packaging service should still be able to
// correctly construct the earlier snapshots because the SnapshotPackage's on the
// channel hold hard links to these deleted snapshots. We verify this is the case below.
let exit = Arc::new(AtomicBool::new(false));
let snapshot_packager_service = SnapshotPackagerService::new(receiver, &exit);
// Close the channel so that the package service will exit after reading all the
// packages off the channel
drop(sender);
// Wait for service to finish
snapshot_packager_service
.join()
.expect("SnapshotPackagerService exited with error");
// Check the tar we cached the state for earlier was generated correctly
snapshot_utils::tests::verify_snapshot_tar(
saved_tar,
saved_snapshots_dir.path(),
saved_accounts_dir
.path()
.join(accounts_dir.path().file_name().unwrap()),
);
}
#[test]
fn test_slots_since_snapshot() {
solana_logger::setup();
for add_root_interval in 1..10 {
let (snapshot_sender, _snapshot_receiver) = channel();
let num_set_roots = MAX_CACHE_ENTRIES * 5;
// Make sure this test never clears bank.slots_since_snapshot
let mut snapshot_test_config =
setup_snapshot_test(add_root_interval * num_set_roots * 2);
let mut current_bank = snapshot_test_config.bank_forks[0].clone();
let snapshot_sender = Some(snapshot_sender);
for _ in 0..num_set_roots {
for _ in 0..add_root_interval {
let new_slot = current_bank.slot() + 1;
let new_bank =
Bank::new_from_parent(&current_bank, &Pubkey::default(), new_slot);
snapshot_test_config.bank_forks.insert(new_bank);
current_bank = snapshot_test_config.bank_forks[new_slot].clone();
}
snapshot_test_config
.bank_forks
.set_root(current_bank.slot(), &snapshot_sender);
let slots_since_snapshot_hashset: HashSet<_> = snapshot_test_config
.bank_forks
.slots_since_snapshot
.iter()
.cloned()
.collect();
assert_eq!(slots_since_snapshot_hashset, current_bank.src.roots());
}
let expected_slots_since_snapshot =
(0..=num_set_roots as u64 * add_root_interval as u64).collect_vec();
let num_old_slots = expected_slots_since_snapshot.len() - MAX_CACHE_ENTRIES;
assert_eq!(
snapshot_test_config.bank_forks.slots_since_snapshot(),
&expected_slots_since_snapshot[num_old_slots..],
);
}
}
struct SnapshotTestConfig {
accounts_dir: TempDir,
snapshot_dir: TempDir,
_snapshot_output_path: TempDir,
snapshot_config: SnapshotConfig,
bank_forks: BankForks,
genesis_block_info: GenesisBlockInfo,
}
fn setup_snapshot_test(snapshot_interval_slots: usize) -> SnapshotTestConfig {
let accounts_dir = TempDir::new().unwrap();
let snapshot_dir = TempDir::new().unwrap();
let snapshot_output_path = TempDir::new().unwrap();
let genesis_block_info = create_genesis_block(10_000);
let bank0 = Bank::new_with_paths(
&genesis_block_info.genesis_block,
Some(accounts_dir.path().to_str().unwrap().to_string()),
);
bank0.freeze();
let mut bank_forks = BankForks::new(0, bank0);
let snapshot_config = SnapshotConfig {
snapshot_interval_slots,
snapshot_package_output_path: PathBuf::from(snapshot_output_path.path()),
snapshot_path: PathBuf::from(snapshot_dir.path()),
};
bank_forks.set_snapshot_config(snapshot_config.clone());
SnapshotTestConfig {
accounts_dir,
snapshot_dir,
_snapshot_output_path: snapshot_output_path,
snapshot_config,
bank_forks,
genesis_block_info,
restore_from_snapshot(&genesis_block, bank_forks, Some(path.paths.clone()), index);
}
}
}

View File

@@ -25,7 +25,7 @@ use solana_runtime::locked_accounts_results::LockedAccountsResults;
use solana_sdk::poh_config::PohConfig;
use solana_sdk::pubkey::Pubkey;
use solana_sdk::timing::{
self, DEFAULT_TICKS_PER_SECOND, DEFAULT_TICKS_PER_SLOT, MAX_PROCESSING_AGE,
self, DEFAULT_NUM_TICKS_PER_SECOND, DEFAULT_TICKS_PER_SLOT, MAX_PROCESSING_AGE,
MAX_TRANSACTION_FORWARDING_DELAY,
};
use solana_sdk::transaction::{self, Transaction, TransactionError};
@@ -134,13 +134,15 @@ impl BankingStage {
fn forward_buffered_packets(
socket: &std::net::UdpSocket,
tpu_forwards: &std::net::SocketAddr,
tpu_via_blobs: &std::net::SocketAddr,
unprocessed_packets: &[PacketsAndOffsets],
) -> std::io::Result<()> {
let packets = Self::filter_valid_packets_for_forwarding(unprocessed_packets);
inc_new_counter_info!("banking_stage-forwarded_packets", packets.len());
for p in packets {
socket.send_to(&p.data[..p.meta.size], &tpu_forwards)?;
let blobs = packet::packets_to_blobs(&packets);
for blob in blobs {
socket.send_to(&blob.data[..blob.meta.size], tpu_via_blobs)?;
}
Ok(())
@@ -198,7 +200,6 @@ impl BankingStage {
if processed < verified_txs_len {
let next_leader = poh_recorder.lock().unwrap().next_slot_leader();
// Walk thru rest of the transactions and filter out the invalid (e.g. too old) ones
#[allow(clippy::while_let_on_iterator)]
while let Some((msgs, unprocessed_indexes)) = buffered_packets_iter.next() {
let unprocessed_indexes = Self::filter_unprocessed_packets(
&bank,
@@ -315,7 +316,7 @@ impl BankingStage {
.read()
.unwrap()
.lookup(&leader_pubkey)
.map(|leader| leader.tpu_forwards)
.map(|leader| leader.tpu_via_blobs)
};
leader_addr.map_or(Ok(()), |leader_addr| {
@@ -427,7 +428,7 @@ impl BankingStage {
txs: &[Transaction],
results: &[transaction::Result<()>],
poh: &Arc<Mutex<PohRecorder>>,
) -> (Result<usize>, Vec<usize>) {
) -> (Result<()>, Vec<usize>) {
let mut processed_generation = Measure::start("record::process_generation");
let (processed_transactions, processed_transactions_indexes): (Vec<_>, Vec<_>) = results
.iter()
@@ -443,11 +444,13 @@ impl BankingStage {
.unzip();
processed_generation.stop();
let num_to_commit = processed_transactions.len();
debug!("num_to_commit: {} ", num_to_commit);
debug!("processed: {} ", processed_transactions.len());
// unlock all the accounts with errors which are filtered by the above `filter_map`
if !processed_transactions.is_empty() {
inc_new_counter_warn!("banking_stage-record_transactions", num_to_commit);
inc_new_counter_warn!(
"banking_stage-record_transactions",
processed_transactions.len()
);
let mut hash_time = Measure::start("record::hash");
let hash = hash_transactions(&processed_transactions[..]);
@@ -465,16 +468,13 @@ impl BankingStage {
Err(Error::PohRecorderError(PohRecorderError::MaxHeightReached)) => {
// If record errors, add all the committable transactions (the ones
// we just attempted to record) as retryable
return (
Err(Error::PohRecorderError(PohRecorderError::MaxHeightReached)),
processed_transactions_indexes,
);
return (res, processed_transactions_indexes);
}
Err(e) => panic!(format!("Poh recorder returned unexpected error: {:?}", e)),
}
poh_record.stop();
}
(Ok(num_to_commit), vec![])
(Ok(()), vec![])
}
fn process_and_record_transactions_locked(
@@ -482,7 +482,7 @@ impl BankingStage {
txs: &[Transaction],
poh: &Arc<Mutex<PohRecorder>>,
lock_results: &LockedAccountsResults,
) -> (Result<usize>, Vec<usize>) {
) -> (Result<()>, Vec<usize>) {
let mut load_execute_time = Measure::start("load_execute_time");
// Use a shorter maximum age when adding transactions into the pipeline. This will reduce
// the likelihood of any single thread getting starved and processing old ids.
@@ -494,20 +494,20 @@ impl BankingStage {
let freeze_lock = bank.freeze_lock();
let mut record_time = Measure::start("record_time");
let (num_to_commit, retryable_record_txs) =
Self::record_transactions(bank.slot(), txs, &results, poh);
retryable_txs.extend(retryable_record_txs);
if num_to_commit.is_err() {
return (num_to_commit, retryable_txs);
}
record_time.stop();
let record_time = {
let mut record_time = Measure::start("record_time");
let (res, retryable_record_txs) =
Self::record_transactions(bank.slot(), txs, &results, poh);
retryable_txs.extend(retryable_record_txs);
if res.is_err() {
return (res, retryable_txs);
}
record_time.stop();
record_time
};
let mut commit_time = Measure::start("commit_time");
let num_to_commit = num_to_commit.unwrap();
if num_to_commit != 0 {
let commit_time = {
let mut commit_time = Measure::start("commit_time");
bank.commit_transactions(
txs,
&mut loaded_accounts,
@@ -515,13 +515,14 @@ impl BankingStage {
tx_count,
signature_count,
);
}
commit_time.stop();
commit_time.stop();
commit_time
};
drop(freeze_lock);
debug!(
"bank: {} process_and_record_locked: {}us record: {}us commit: {}us txs_len: {}",
"bank: {} load_execute: {}us record: {}us commit: {}us txs_len: {}",
bank.slot(),
load_execute_time.as_us(),
record_time.as_us(),
@@ -529,7 +530,7 @@ impl BankingStage {
txs.len(),
);
(Ok(num_to_commit), retryable_txs)
(Ok(()), retryable_txs)
}
pub fn process_and_record_transactions(
@@ -537,7 +538,7 @@ impl BankingStage {
txs: &[Transaction],
poh: &Arc<Mutex<PohRecorder>>,
chunk_offset: usize,
) -> (Result<usize>, Vec<usize>) {
) -> (Result<()>, Vec<usize>) {
let mut lock_time = Measure::start("lock_time");
// Once accounts are locked, other threads cannot encode transactions that will modify the
// same account state
@@ -701,7 +702,7 @@ impl BankingStage {
.saturating_sub(MAX_TRANSACTION_FORWARDING_DELAY)
.saturating_sub(
(FORWARD_TRANSACTIONS_TO_LEADER_AT_SLOT_OFFSET * bank.ticks_per_slot()
/ DEFAULT_TICKS_PER_SECOND) as usize,
/ DEFAULT_NUM_TICKS_PER_SECOND) as usize,
),
&mut error_counters,
);
@@ -850,7 +851,6 @@ impl BankingStage {
if processed < verified_txs_len {
let next_leader = poh.lock().unwrap().next_slot_leader();
// Walk thru rest of the transactions and filter out the invalid (e.g. too old) ones
#[allow(clippy::while_let_on_iterator)]
while let Some((msgs, vers)) = mms_iter.next() {
let packet_indexes = Self::generate_packet_indexes(vers);
let unprocessed_indexes = Self::filter_unprocessed_packets(
@@ -1157,7 +1157,6 @@ mod tests {
}
#[test]
#[ignore]
fn test_banking_stage_entryfication() {
solana_logger::setup();
// In this attack we'll demonstrate that a verifier can interpret the ledger
@@ -1297,12 +1296,7 @@ mod tests {
];
let mut results = vec![Ok(()), Ok(())];
let _ = BankingStage::record_transactions(
bank.slot(),
&transactions,
&results,
&poh_recorder,
);
BankingStage::record_transactions(bank.slot(), &transactions, &results, &poh_recorder);
let (_, entries) = entry_receiver.recv().unwrap();
assert_eq!(entries[0].0.transactions.len(), transactions.len());

View File

@@ -1,15 +1,11 @@
//! The `blob_fetch_stage` pulls blobs from UDP sockets and sends it to a channel.
use crate::recycler::Recycler;
use crate::result;
use crate::result::Error;
use crate::service::Service;
use crate::streamer::{self, BlobSender, PacketReceiver, PacketSender};
use crate::streamer::{self, BlobSender};
use std::net::UdpSocket;
use std::sync::atomic::AtomicBool;
use std::sync::mpsc::{channel, RecvTimeoutError};
use std::sync::Arc;
use std::thread::{self, Builder, JoinHandle};
use std::thread::{self, JoinHandle};
pub struct BlobFetchStage {
thread_hdls: Vec<JoinHandle<()>>,
@@ -31,79 +27,6 @@ impl BlobFetchStage {
Self { thread_hdls }
}
fn handle_forwarded_packets(
recvr: &PacketReceiver,
sendr: &PacketSender,
) -> result::Result<()> {
let msgs = recvr.recv()?;
let mut batch = vec![msgs];
while let Ok(more) = recvr.try_recv() {
batch.push(more);
}
batch
.iter_mut()
.for_each(|b| b.packets.iter_mut().for_each(|p| p.meta.forward = true));
for packets in batch {
if sendr.send(packets).is_err() {
return Err(Error::SendError);
}
}
Ok(())
}
pub fn new_multi_socket_packet(
sockets: Vec<Arc<UdpSocket>>,
forward_sockets: Vec<Arc<UdpSocket>>,
sender: &PacketSender,
exit: &Arc<AtomicBool>,
) -> Self {
let recycler = Recycler::default();
let tvu_threads = sockets.into_iter().map(|socket| {
streamer::receiver(
socket,
&exit,
sender.clone(),
recycler.clone(),
"blob_fetch_stage",
)
});
let (forward_sender, forward_receiver) = channel();
let tvu_forwards_threads = forward_sockets.into_iter().map(|socket| {
streamer::receiver(
socket,
&exit,
forward_sender.clone(),
recycler.clone(),
"blob_fetch_stage",
)
});
let sender = sender.clone();
let fwd_thread_hdl = Builder::new()
.name("solana-tvu-fetch-stage-fwd-rcvr".to_string())
.spawn(move || loop {
if let Err(e) = Self::handle_forwarded_packets(&forward_receiver, &sender) {
match e {
Error::RecvTimeoutError(RecvTimeoutError::Disconnected) => break,
Error::RecvTimeoutError(RecvTimeoutError::Timeout) => (),
Error::RecvError(_) => break,
Error::SendError => break,
_ => error!("{:?}", e),
}
}
})
.unwrap();
let mut thread_hdls: Vec<_> = tvu_threads.chain(tvu_forwards_threads).collect();
thread_hdls.push(fwd_thread_hdl);
Self { thread_hdls }
}
}
impl Service for BlobFetchStage {

View File

@@ -10,7 +10,6 @@ use serde_json::json;
use solana_sdk::hash::Hash;
use solana_sdk::pubkey::Pubkey;
use std::cell::RefCell;
use std::path::{Path, PathBuf};
pub trait EntryWriter: std::fmt::Debug {
fn write(&self, payload: String) -> Result<()>;
@@ -42,7 +41,7 @@ impl EntryVec {
#[derive(Debug)]
pub struct EntrySocket {
unix_socket: PathBuf,
socket: String,
}
impl EntryWriter for EntrySocket {
@@ -51,10 +50,11 @@ impl EntryWriter for EntrySocket {
use std::io::prelude::*;
use std::net::Shutdown;
use std::os::unix::net::UnixStream;
use std::path::Path;
const MESSAGE_TERMINATOR: &str = "\n";
let mut socket = UnixStream::connect(&self.unix_socket)?;
let mut socket = UnixStream::connect(Path::new(&self.socket))?;
socket.write_all(payload.as_bytes())?;
socket.write_all(MESSAGE_TERMINATOR.as_bytes())?;
socket.shutdown(Shutdown::Write)?;
@@ -144,11 +144,9 @@ where
pub type SocketBlockstream = Blockstream<EntrySocket>;
impl SocketBlockstream {
pub fn new(unix_socket: &Path) -> Self {
pub fn new(socket: String) -> Self {
Blockstream {
output: EntrySocket {
unix_socket: unix_socket.to_path_buf(),
},
output: EntrySocket { socket },
}
}
}
@@ -156,7 +154,7 @@ impl SocketBlockstream {
pub type MockBlockstream = Blockstream<EntryVec>;
impl MockBlockstream {
pub fn new(_: &Path) -> Self {
pub fn new(_: String) -> Self {
Blockstream {
output: EntryVec::new(),
}
@@ -185,7 +183,6 @@ mod test {
use solana_sdk::signature::{Keypair, KeypairUtil};
use solana_sdk::system_transaction;
use std::collections::HashSet;
use std::path::PathBuf;
#[test]
fn test_serialize_transactions() {
@@ -208,7 +205,7 @@ mod test {
#[test]
fn test_blockstream() -> () {
let blockstream = MockBlockstream::new(&PathBuf::from("test_stream"));
let blockstream = MockBlockstream::new("test_stream".to_string());
let ticks_per_slot = 5;
let mut blockhash = Hash::default();

View File

@@ -11,7 +11,6 @@ use crate::blocktree::Blocktree;
use crate::result::{Error, Result};
use crate::service::Service;
use solana_sdk::pubkey::Pubkey;
use std::path::Path;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::mpsc::{Receiver, RecvTimeoutError};
use std::sync::Arc;
@@ -27,10 +26,10 @@ impl BlockstreamService {
pub fn new(
slot_full_receiver: Receiver<(u64, Pubkey)>,
blocktree: Arc<Blocktree>,
unix_socket: &Path,
blockstream_socket: String,
exit: &Arc<AtomicBool>,
) -> Self {
let mut blockstream = Blockstream::new(unix_socket);
let mut blockstream = Blockstream::new(blockstream_socket);
let exit = exit.clone();
let t_blockstream = Builder::new()
.name("solana-blockstream".to_string())
@@ -70,7 +69,7 @@ impl BlockstreamService {
.iter()
.filter(|entry| entry.is_tick())
.fold(0, |acc, _| acc + 1);
let mut tick_height = if slot > 0 && ticks_per_slot > 0 {
let mut tick_height = if slot > 0 {
ticks_per_slot * slot - 1
} else {
0
@@ -117,7 +116,6 @@ mod test {
use solana_sdk::hash::Hash;
use solana_sdk::signature::{Keypair, KeypairUtil};
use solana_sdk::system_transaction;
use std::path::PathBuf;
use std::sync::mpsc::channel;
#[test]
@@ -135,7 +133,7 @@ mod test {
let blocktree = Blocktree::open(&ledger_path).unwrap();
// Set up blockstream
let mut blockstream = Blockstream::new(&PathBuf::from("test_stream"));
let mut blockstream = Blockstream::new("test_stream".to_string());
// Set up dummy channel to receive a full-slot notification
let (slot_full_sender, slot_full_receiver) = channel();
@@ -161,16 +159,7 @@ mod test {
let expected_tick_heights = [5, 6, 7, 8, 8, 9];
blocktree
.write_entries_using_shreds(
1,
0,
0,
ticks_per_slot,
None,
true,
&Arc::new(Keypair::new()),
&entries,
)
.write_entries(1, 0, 0, ticks_per_slot, &entries)
.unwrap();
slot_full_sender.send((1, leader_pubkey)).unwrap();

File diff suppressed because it is too large Load Diff

View File

@@ -44,14 +44,6 @@ pub mod columns {
#[derive(Debug)]
/// The index column
pub struct Index;
#[derive(Debug)]
/// The shred data column
pub struct ShredData;
#[derive(Debug)]
/// The shred erasure code column
pub struct ShredCode;
}
pub trait Backend: Sized + Send + Sync {
@@ -439,12 +431,7 @@ where
}
};
if let Err(e) = batch.delete::<C>(index) {
error!(
"Error: {:?} while adding delete from_slot {:?} to batch {:?}",
e,
from,
C::NAME
)
error!("Error: {:?} while adding delete to batch {:?}", e, C::NAME)
}
}
Ok(end)

View File

@@ -1,7 +1,6 @@
use crate::erasure::ErasureConfig;
use solana_metrics::datapoint;
use std::cmp::Ordering;
use std::{collections::BTreeSet, ops::Range, ops::RangeBounds};
use std::{collections::BTreeSet, ops::RangeBounds};
#[derive(Clone, Debug, Default, Deserialize, Serialize, Eq, PartialEq)]
// The Meta column family
@@ -28,51 +27,6 @@ pub struct SlotMeta {
pub is_connected: bool,
}
#[derive(Clone, Debug, Default, Deserialize, Serialize, Eq, PartialEq)]
pub struct ErasureSetRanges {
r: Vec<Range<u64>>,
}
impl ErasureSetRanges {
pub fn insert(&mut self, start: u64, end: u64) -> Result<usize, Range<u64>> {
let range = if start < end {
(start..end)
} else {
(end..start)
};
match self.pos(range.start) {
Ok(pos) => Err(self.r[pos].clone()),
Err(pos) => {
self.r.insert(pos, range);
Ok(pos)
}
}
}
fn pos(&self, seek: u64) -> Result<usize, usize> {
self.r.binary_search_by(|probe| {
if probe.contains(&seek) {
Ordering::Equal
} else {
probe.start.cmp(&seek)
}
})
}
pub fn lookup(&self, seek: u64) -> Result<Range<u64>, usize> {
self.pos(seek)
.map(|pos| self.r[pos].clone())
.or_else(|epos| {
if epos < self.r.len() && self.r[epos].contains(&seek) {
Ok(self.r[epos].clone())
} else {
Err(epos)
}
})
}
}
#[derive(Clone, Debug, Default, Deserialize, Serialize, PartialEq)]
/// Index recording presence/absence of blobs
pub struct Index {
@@ -102,7 +56,7 @@ pub struct ErasureMeta {
/// Size of shards in this erasure set
pub size: usize,
/// Erasure configuration for this erasure set
pub config: ErasureConfig,
config: ErasureConfig,
}
#[derive(Debug, PartialEq)]
@@ -277,7 +231,7 @@ impl ErasureMeta {
}
pub fn start_index(&self) -> u64 {
self.set_index
self.set_index * self.config.num_data() as u64
}
/// returns a tuple of (data_end, coding_end)
@@ -346,50 +300,4 @@ mod test {
assert_eq!(e_meta.status(&index), DataFull);
}
}
#[test]
fn test_erasure_set_ranges() {
let mut ranges = ErasureSetRanges::default();
// Test empty ranges
(0..100 as u64).for_each(|i| {
assert_eq!(ranges.lookup(i), Err(0));
});
// Test adding one range and all boundary condition lookups
assert_eq!(ranges.insert(5, 13), Ok(0));
assert_eq!(ranges.lookup(0), Err(0));
assert_eq!(ranges.lookup(4), Err(0));
assert_eq!(ranges.lookup(5), Ok(5..13));
assert_eq!(ranges.lookup(12), Ok(5..13));
assert_eq!(ranges.lookup(13), Err(1));
assert_eq!(ranges.lookup(100), Err(1));
// Test adding second range (with backwards values) and all boundary condition lookups
assert_eq!(ranges.insert(55, 33), Ok(1));
assert_eq!(ranges.lookup(0), Err(0));
assert_eq!(ranges.lookup(4), Err(0));
assert_eq!(ranges.lookup(5), Ok(5..13));
assert_eq!(ranges.lookup(12), Ok(5..13));
assert_eq!(ranges.lookup(13), Err(1));
assert_eq!(ranges.lookup(32), Err(1));
assert_eq!(ranges.lookup(33), Ok(33..55));
assert_eq!(ranges.lookup(54), Ok(33..55));
assert_eq!(ranges.lookup(55), Err(2));
// Add a third range between previous two ranges
assert_eq!(ranges.insert(23, 30), Ok(1));
assert_eq!(ranges.lookup(0), Err(0));
assert_eq!(ranges.lookup(4), Err(0));
assert_eq!(ranges.lookup(5), Ok(5..13));
assert_eq!(ranges.lookup(12), Ok(5..13));
assert_eq!(ranges.lookup(13), Err(1));
assert_eq!(ranges.lookup(23), Ok(23..30));
assert_eq!(ranges.lookup(29), Ok(23..30));
assert_eq!(ranges.lookup(30), Err(2));
assert_eq!(ranges.lookup(32), Err(2));
assert_eq!(ranges.lookup(33), Ok(33..55));
assert_eq!(ranges.lookup(54), Ok(33..55));
assert_eq!(ranges.lookup(55), Err(3));
}
}

View File

@@ -33,8 +33,7 @@ impl Backend for Rocks {
fn open(path: &Path) -> Result<Rocks> {
use crate::blocktree::db::columns::{
Coding, Data, DeadSlots, ErasureMeta, Index, Orphans, Root, ShredCode, ShredData,
SlotMeta,
Coding, Data, DeadSlots, ErasureMeta, Index, Orphans, Root, SlotMeta,
};
fs::create_dir_all(&path)?;
@@ -59,10 +58,6 @@ impl Backend for Rocks {
ColumnFamilyDescriptor::new(Root::NAME, get_cf_options(Root::NAME));
let index_cf_descriptor =
ColumnFamilyDescriptor::new(Index::NAME, get_cf_options(Index::NAME));
let shred_data_cf_descriptor =
ColumnFamilyDescriptor::new(ShredData::NAME, get_cf_options(ShredData::NAME));
let shred_code_cf_descriptor =
ColumnFamilyDescriptor::new(ShredCode::NAME, get_cf_options(ShredCode::NAME));
let cfs = vec![
meta_cf_descriptor,
@@ -73,8 +68,6 @@ impl Backend for Rocks {
orphans_cf_descriptor,
root_cf_descriptor,
index_cf_descriptor,
shred_data_cf_descriptor,
shred_code_cf_descriptor,
];
// Open the database
@@ -85,8 +78,7 @@ impl Backend for Rocks {
fn columns(&self) -> Vec<&'static str> {
use crate::blocktree::db::columns::{
Coding, Data, DeadSlots, ErasureMeta, Index, Orphans, Root, ShredCode, ShredData,
SlotMeta,
Coding, Data, DeadSlots, ErasureMeta, Index, Orphans, Root, SlotMeta,
};
vec![
@@ -98,8 +90,6 @@ impl Backend for Rocks {
Orphans::NAME,
Root::NAME,
SlotMeta::NAME,
ShredData::NAME,
ShredCode::NAME,
]
}
@@ -206,53 +196,6 @@ impl Column<Rocks> for cf::Data {
}
}
impl Column<Rocks> for cf::ShredCode {
const NAME: &'static str = super::CODE_SHRED_CF;
type Index = (u64, u64);
fn key(index: (u64, u64)) -> Vec<u8> {
cf::ShredData::key(index)
}
fn index(key: &[u8]) -> (u64, u64) {
cf::ShredData::index(key)
}
fn slot(index: Self::Index) -> Slot {
index.0
}
fn as_index(slot: Slot) -> Self::Index {
(slot, 0)
}
}
impl Column<Rocks> for cf::ShredData {
const NAME: &'static str = super::DATA_SHRED_CF;
type Index = (u64, u64);
fn key((slot, index): (u64, u64)) -> Vec<u8> {
let mut key = vec![0; 16];
BigEndian::write_u64(&mut key[..8], slot);
BigEndian::write_u64(&mut key[8..16], index);
key
}
fn index(key: &[u8]) -> (u64, u64) {
let slot = BigEndian::read_u64(&key[..8]);
let index = BigEndian::read_u64(&key[8..16]);
(slot, index)
}
fn slot(index: Self::Index) -> Slot {
index.0
}
fn as_index(slot: Slot) -> Self::Index {
(slot, 0)
}
}
impl Column<Rocks> for cf::Index {
const NAME: &'static str = super::INDEX_CF;
type Index = u64;
@@ -464,11 +407,11 @@ impl std::convert::From<rocksdb::Error> for Error {
}
fn get_cf_options(name: &'static str) -> Options {
use crate::blocktree::db::columns::{Coding, Data, ShredCode, ShredData};
use crate::blocktree::db::columns::{Coding, Data};
let mut options = Options::default();
match name {
Coding::NAME | Data::NAME | ShredCode::NAME | ShredData::NAME => {
Coding::NAME | Data::NAME => {
// 512MB * 8 = 4GB. 2 of these columns should take no more than 8GB of RAM
options.set_max_write_buffer_number(8);
options.set_write_buffer_size(MAX_WRITE_BUFFER_SIZE as usize);

View File

@@ -29,23 +29,16 @@ impl<'a> Iterator for RootedSlotIterator<'a> {
.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");
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");
if slot_meta.is_none() {
warn!("Rooted SlotMeta was deleted in between checking is_root and fetch");
}
slot_meta.map(|slot_meta| {
self.next_slots = slot_meta.next_slots.clone();
(rooted_slot, slot_meta)
})
})
.unwrap_or(None)
self.next_slots = slot_meta.next_slots.clone();
(rooted_slot, slot_meta)
})
}
}

View File

@@ -1,5 +1,5 @@
use crate::bank_forks::BankForks;
use crate::blocktree::{Blocktree, SlotMeta};
use crate::blocktree::Blocktree;
use crate::entry::{Entry, EntrySlice};
use crate::leader_schedule_cache::LeaderScheduleCache;
use rayon::prelude::*;
@@ -8,8 +8,8 @@ use solana_metrics::{datapoint, datapoint_error, inc_new_counter_debug};
use solana_runtime::bank::Bank;
use solana_runtime::locked_accounts_results::LockedAccountsResults;
use solana_sdk::genesis_block::GenesisBlock;
use solana_sdk::hash::Hash;
use solana_sdk::timing::{duration_as_ms, Slot, MAX_RECENT_BLOCKHASHES};
use solana_sdk::timing::duration_as_ms;
use solana_sdk::timing::MAX_RECENT_BLOCKHASHES;
use solana_sdk::transaction::Result;
use std::result;
use std::sync::Arc;
@@ -130,6 +130,7 @@ pub fn process_entries(bank: &Bank, entries: &[Entry]) -> Result<()> {
#[derive(Debug, PartialEq)]
pub struct BankForksInfo {
pub bank_slot: u64,
pub entry_height: u64,
}
#[derive(Debug)]
@@ -142,219 +143,39 @@ pub fn process_blocktree(
blocktree: &Blocktree,
account_paths: Option<String>,
verify_ledger: bool,
dev_halt_at_slot: Option<Slot>,
) -> result::Result<(BankForks, Vec<BankForksInfo>, LeaderScheduleCache), BlocktreeProcessorError> {
info!("processing ledger from bank 0...");
// Setup bank for slot 0
let bank0 = Arc::new(Bank::new_with_paths(&genesis_block, account_paths));
process_bank_0(&bank0, blocktree, verify_ledger)?;
process_blocktree_from_root(blocktree, bank0, verify_ledger, dev_halt_at_slot)
}
// Process blocktree from a known root bank
pub fn process_blocktree_from_root(
blocktree: &Blocktree,
bank: Arc<Bank>,
verify_ledger: bool,
dev_halt_at_slot: Option<Slot>,
) -> result::Result<(BankForks, Vec<BankForksInfo>, LeaderScheduleCache), BlocktreeProcessorError> {
info!("processing ledger from root: {}...", bank.slot());
// Starting slot must be a root, and thus has no parents
assert!(bank.parent().is_none());
let start_slot = bank.slot();
let now = Instant::now();
let mut rooted_path = vec![start_slot];
let dev_halt_at_slot = dev_halt_at_slot.unwrap_or(std::u64::MAX);
info!("processing ledger...");
// Setup bank for slot 0
let mut pending_slots = {
let slot = 0;
let bank = Arc::new(Bank::new_with_paths(&genesis_block, account_paths));
let entry_height = 0;
let last_entry_hash = bank.last_blockhash();
blocktree
.set_roots(&[start_slot])
.expect("Couldn't set root on startup");
let meta = blocktree.meta(start_slot).unwrap();
// Iterate and replay slots from blocktree starting from `start_slot`
let (bank_forks, bank_forks_info, leader_schedule_cache) = {
if let Some(meta) = meta {
let epoch_schedule = bank.epoch_schedule();
let mut leader_schedule_cache = LeaderScheduleCache::new(*epoch_schedule, &bank);
let fork_info = process_pending_slots(
&bank,
&meta,
blocktree,
&mut leader_schedule_cache,
&mut rooted_path,
verify_ledger,
dev_halt_at_slot,
)?;
let (banks, bank_forks_info): (Vec<_>, Vec<_>) = fork_info.into_iter().unzip();
let bank_forks = BankForks::new_from_banks(&banks, rooted_path);
(bank_forks, bank_forks_info, leader_schedule_cache)
} else {
// If there's no meta for the input `start_slot`, then we started from a snapshot
// and there's no point in processing the rest of blocktree and implies blocktree
// should be empty past this point.
let bfi = BankForksInfo {
bank_slot: start_slot,
};
let leader_schedule_cache = LeaderScheduleCache::new_from_bank(&bank);
let bank_forks = BankForks::new_from_banks(&[bank], rooted_path);
(bank_forks, vec![bfi], leader_schedule_cache)
}
};
info!(
"processing ledger...complete in {}ms, forks={}...",
duration_as_ms(&now.elapsed()),
bank_forks_info.len(),
);
Ok((bank_forks, bank_forks_info, leader_schedule_cache))
}
fn verify_and_process_entries(
bank: &Bank,
entries: &[Entry],
verify_ledger: bool,
last_entry_hash: Hash,
) -> result::Result<Hash, BlocktreeProcessorError> {
assert!(!entries.is_empty());
if verify_ledger && !entries.verify(&last_entry_hash) {
warn!("Ledger proof of history failed at slot: {}", bank.slot());
return Err(BlocktreeProcessorError::LedgerVerificationFailed);
}
process_entries(&bank, &entries).map_err(|err| {
warn!(
"Failed to process entries for slot {}: {:?}",
bank.slot(),
err
);
BlocktreeProcessorError::LedgerVerificationFailed
})?;
Ok(entries.last().unwrap().hash)
}
// Special handling required for processing the entries in slot 0
fn process_bank_0(
bank0: &Bank,
blocktree: &Blocktree,
verify_ledger: bool,
) -> result::Result<(), BlocktreeProcessorError> {
assert_eq!(bank0.slot(), 0);
// Fetch all entries for this slot
let mut entries = blocktree.get_slot_entries(0, 0, None).map_err(|err| {
warn!("Failed to load entries for slot 0, err: {:?}", err);
BlocktreeProcessorError::LedgerVerificationFailed
})?;
// The first entry in the ledger is a pseudo-tick used only to ensure the number of ticks
// in slot 0 is the same as the number of ticks in all subsequent slots. It is not
// processed by the bank, skip over it.
if entries.is_empty() {
warn!("entry0 not present");
return Err(BlocktreeProcessorError::LedgerVerificationFailed);
}
let entry0 = entries.remove(0);
if !(entry0.is_tick() && entry0.verify(&bank0.last_blockhash())) {
warn!("Ledger proof of history failed at entry0");
return Err(BlocktreeProcessorError::LedgerVerificationFailed);
}
if !entries.is_empty() {
verify_and_process_entries(bank0, &entries, verify_ledger, entry0.hash)?;
} else {
bank0.register_tick(&entry0.hash);
}
bank0.freeze();
Ok(())
}
// Given a slot, add its children to the pending slots queue if those children slots are
// complete
fn process_next_slots(
bank: &Arc<Bank>,
meta: &SlotMeta,
blocktree: &Blocktree,
leader_schedule_cache: &LeaderScheduleCache,
pending_slots: &mut Vec<(u64, SlotMeta, Arc<Bank>, Hash)>,
fork_info: &mut Vec<(Arc<Bank>, BankForksInfo)>,
) -> result::Result<(), BlocktreeProcessorError> {
if meta.next_slots.is_empty() {
// Reached the end of this fork. Record the final entry height and last entry.hash
let bfi = BankForksInfo {
bank_slot: bank.slot(),
};
fork_info.push((bank.clone(), bfi));
return Ok(());
}
// This is a fork point if there are multiple children, create a new child bank for each fork
for next_slot in &meta.next_slots {
let next_meta = blocktree
.meta(*next_slot)
// Load the metadata for this slot
let meta = blocktree
.meta(slot)
.map_err(|err| {
warn!("Failed to load meta for slot {}: {:?}", next_slot, err);
warn!("Failed to load meta for slot {}: {:?}", slot, err);
BlocktreeProcessorError::LedgerVerificationFailed
})?
.unwrap();
// Only process full slots in blocktree_processor, replay_stage
// 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))
.unwrap(),
*next_slot,
));
trace!("Add child bank {} of slot={}", next_slot, bank.slot());
pending_slots.push((*next_slot, next_meta, next_bank, bank.last_blockhash()));
} else {
let bfi = BankForksInfo {
bank_slot: bank.slot(),
};
fork_info.push((bank.clone(), bfi));
}
}
vec![(slot, meta, bank, entry_height, last_entry_hash)]
};
// Reverse sort by slot, so the next slot to be processed can be popped
// TODO: remove me once leader_scheduler can hang with out-of-order slots?
pending_slots.sort_by(|a, b| b.0.cmp(&a.0));
Ok(())
}
blocktree.set_roots(&[0]).expect("Couldn't set first root");
let leader_schedule_cache =
LeaderScheduleCache::new(*pending_slots[0].2.epoch_schedule(), &pending_slots[0].2);
// Iterate through blocktree processing slots starting from the root slot pointed to by the
// given `meta`
fn process_pending_slots(
root_bank: &Arc<Bank>,
root_meta: &SlotMeta,
blocktree: &Blocktree,
leader_schedule_cache: &mut LeaderScheduleCache,
rooted_path: &mut Vec<u64>,
verify_ledger: bool,
dev_halt_at_slot: Slot,
) -> result::Result<Vec<(Arc<Bank>, BankForksInfo)>, BlocktreeProcessorError> {
let mut fork_info = vec![];
let mut last_status_report = Instant::now();
let mut pending_slots = vec![];
process_next_slots(
root_bank,
root_meta,
blocktree,
leader_schedule_cache,
&mut pending_slots,
&mut fork_info,
)?;
let mut root = 0;
while !pending_slots.is_empty() {
let (slot, meta, bank, last_entry_hash) = pending_slots.pop().unwrap();
let (slot, meta, bank, mut entry_height, mut last_entry_hash) =
pending_slots.pop().unwrap();
if last_status_report.elapsed() > Duration::from_secs(2) {
info!("processing ledger...block {}", slot);
@@ -362,43 +183,118 @@ fn process_pending_slots(
}
// Fetch all entries for this slot
let entries = blocktree.get_slot_entries(slot, 0, None).map_err(|err| {
let mut entries = blocktree.get_slot_entries(slot, 0, None).map_err(|err| {
warn!("Failed to load entries for slot {}: {:?}", slot, err);
BlocktreeProcessorError::LedgerVerificationFailed
})?;
verify_and_process_entries(&bank, &entries, verify_ledger, last_entry_hash)?;
if slot == 0 {
// The first entry in the ledger is a pseudo-tick used only to ensure the number of ticks
// in slot 0 is the same as the number of ticks in all subsequent slots. It is not
// processed by the bank, skip over it.
if entries.is_empty() {
warn!("entry0 not present");
return Err(BlocktreeProcessorError::LedgerVerificationFailed);
}
let entry0 = entries.remove(0);
if !(entry0.is_tick() && entry0.verify(&last_entry_hash)) {
warn!("Ledger proof of history failed at entry0");
return Err(BlocktreeProcessorError::LedgerVerificationFailed);
}
last_entry_hash = entry0.hash;
entry_height += 1;
}
if !entries.is_empty() {
if verify_ledger && !entries.verify(&last_entry_hash) {
warn!(
"Ledger proof of history failed at slot: {}, entry: {}",
slot, entry_height
);
return Err(BlocktreeProcessorError::LedgerVerificationFailed);
}
process_entries(&bank, &entries).map_err(|err| {
warn!("Failed to process entries for slot {}: {:?}", slot, err);
BlocktreeProcessorError::LedgerVerificationFailed
})?;
last_entry_hash = entries.last().unwrap().hash;
entry_height += entries.len() as u64;
}
bank.freeze(); // all banks handled by this routine are created from complete slots
if blocktree.is_root(slot) {
let parents = bank.parents().into_iter().map(|b| b.slot()).rev().skip(1);
let parents: Vec<_> = parents.collect();
rooted_path.extend(parents);
rooted_path.push(slot);
root = slot;
leader_schedule_cache.set_root(&bank);
bank.squash();
pending_slots.clear();
fork_info.clear();
}
if slot >= dev_halt_at_slot {
let bfi = BankForksInfo { bank_slot: slot };
if meta.next_slots.is_empty() {
// Reached the end of this fork. Record the final entry height and last entry.hash
let bfi = BankForksInfo {
bank_slot: slot,
entry_height,
};
fork_info.push((bank, bfi));
break;
continue;
}
process_next_slots(
&bank,
&meta,
blocktree,
leader_schedule_cache,
&mut pending_slots,
&mut fork_info,
)?;
// This is a fork point, create a new child bank for each fork
for next_slot in meta.next_slots {
let next_meta = blocktree
.meta(next_slot)
.map_err(|err| {
warn!("Failed to load meta for slot {}: {:?}", slot, err);
BlocktreeProcessorError::LedgerVerificationFailed
})?
.unwrap();
// only process full slots in blocktree_processor, replay_stage
// 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))
.unwrap(),
next_slot,
));
trace!("Add child bank for slot={}", next_slot);
// bank_forks.insert(*next_slot, child_bank);
pending_slots.push((
next_slot,
next_meta,
next_bank,
entry_height,
last_entry_hash,
));
} else {
let bfi = BankForksInfo {
bank_slot: slot,
entry_height,
};
fork_info.push((bank.clone(), bfi));
}
}
// reverse sort by slot, so the next slot to be processed can be pop()ed
// TODO: remove me once leader_scheduler can hang with out-of-order slots?
pending_slots.sort_by(|a, b| b.0.cmp(&a.0));
}
Ok(fork_info)
let (banks, bank_forks_info): (Vec<_>, Vec<_>) = fork_info.into_iter().unzip();
let bank_forks = BankForks::new_from_banks(&banks, root);
info!(
"processing ledger...complete in {}ms, forks={}...",
duration_as_ms(&now.elapsed()),
bank_forks_info.len(),
);
Ok((bank_forks, bank_forks_info, leader_schedule_cache))
}
#[cfg(test)]
@@ -429,18 +325,8 @@ pub mod tests {
let entries = create_ticks(ticks_per_slot, last_entry_hash);
let last_entry_hash = entries.last().unwrap().hash;
blocktree
.write_entries_using_shreds(
slot,
0,
0,
ticks_per_slot,
Some(parent_slot),
true,
&Arc::new(Keypair::new()),
&entries,
)
.unwrap();
let blobs = entries_to_blobs(&entries, slot, parent_slot, true);
blocktree.insert_data_blobs(blobs.iter()).unwrap();
last_entry_hash
}
@@ -490,13 +376,14 @@ pub mod tests {
fill_blocktree_slot_with_ticks(&blocktree, ticks_per_slot, 2, 1, blockhash);
let (mut _bank_forks, bank_forks_info, _) =
process_blocktree(&genesis_block, &blocktree, None, true, None).unwrap();
process_blocktree(&genesis_block, &blocktree, None, true).unwrap();
assert_eq!(bank_forks_info.len(), 1);
assert_eq!(
bank_forks_info[0],
BankForksInfo {
bank_slot: 0, // slot 1 isn't "full", we stop at slot zero
entry_height: ticks_per_slot,
}
);
}
@@ -548,7 +435,7 @@ pub mod tests {
blocktree.set_roots(&[4, 1, 0]).unwrap();
let (bank_forks, bank_forks_info, _) =
process_blocktree(&genesis_block, &blocktree, None, true, None).unwrap();
process_blocktree(&genesis_block, &blocktree, None, true).unwrap();
assert_eq!(bank_forks_info.len(), 1); // One fork, other one is ignored b/c not a descendant of the root
@@ -556,6 +443,7 @@ pub mod tests {
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]
@@ -566,7 +454,10 @@ pub mod tests {
.is_empty());
// Ensure bank_forks holds the right banks
verify_fork_infos(&bank_forks, &bank_forks_info);
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);
}
@@ -618,13 +509,14 @@ pub mod tests {
blocktree.set_roots(&[0, 1]).unwrap();
let (bank_forks, bank_forks_info, _) =
process_blocktree(&genesis_block, &blocktree, None, true, None).unwrap();
process_blocktree(&genesis_block, &blocktree, None, true).unwrap();
assert_eq!(bank_forks_info.len(), 2); // There are two forks
assert_eq!(
bank_forks_info[0],
BankForksInfo {
bank_slot: 3, // Fork 1's head is slot 3
entry_height: ticks_per_slot * 4,
}
);
assert_eq!(
@@ -639,6 +531,7 @@ pub mod tests {
bank_forks_info[1],
BankForksInfo {
bank_slot: 4, // Fork 2's head is slot 4
entry_height: ticks_per_slot * 3,
}
);
assert_eq!(
@@ -653,7 +546,10 @@ pub mod tests {
assert_eq!(bank_forks.root(), 1);
// Ensure bank_forks holds the right banks
verify_fork_infos(&bank_forks, &bank_forks_info);
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());
}
}
#[test]
@@ -694,13 +590,14 @@ pub mod tests {
// 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, true, None).unwrap();
process_blocktree(&genesis_block, &blocktree, None, true).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),
}
);
@@ -825,23 +722,21 @@ pub mod tests {
let blocktree =
Blocktree::open(&ledger_path).expect("Expected to successfully open database ledger");
blocktree
.write_entries_using_shreds(
1,
0,
0,
genesis_block.ticks_per_slot,
None,
true,
&Arc::new(Keypair::new()),
&entries,
)
.write_entries(1, 0, 0, genesis_block.ticks_per_slot, &entries)
.unwrap();
let entry_height = genesis_block.ticks_per_slot + entries.len() as u64;
let (bank_forks, bank_forks_info, _) =
process_blocktree(&genesis_block, &blocktree, None, true, None).unwrap();
process_blocktree(&genesis_block, &blocktree, None, true).unwrap();
assert_eq!(bank_forks_info.len(), 1);
assert_eq!(bank_forks.root(), 0);
assert_eq!(bank_forks_info[0], BankForksInfo { bank_slot: 1 });
assert_eq!(
bank_forks_info[0],
BankForksInfo {
bank_slot: 1,
entry_height,
}
);
let bank = bank_forks[1].clone();
assert_eq!(
@@ -862,10 +757,16 @@ pub mod tests {
let blocktree = Blocktree::open(&ledger_path).unwrap();
let (bank_forks, bank_forks_info, _) =
process_blocktree(&genesis_block, &blocktree, None, true, None).unwrap();
process_blocktree(&genesis_block, &blocktree, None, true).unwrap();
assert_eq!(bank_forks_info.len(), 1);
assert_eq!(bank_forks_info[0], BankForksInfo { bank_slot: 0 });
assert_eq!(
bank_forks_info[0],
BankForksInfo {
bank_slot: 0,
entry_height: 1,
}
);
let bank = bank_forks[0].clone();
assert_eq!(bank.tick_height(), 0);
}
@@ -1353,83 +1254,6 @@ pub mod tests {
assert_eq!(bank.process_transaction(&fail_tx), Ok(()));
}
#[test]
fn test_process_blocktree_from_root() {
let GenesisBlockInfo {
mut genesis_block, ..
} = create_genesis_block(123);
let ticks_per_slot = 1;
genesis_block.ticks_per_slot = ticks_per_slot;
let (ledger_path, blockhash) = create_new_tmp_ledger!(&genesis_block);
let blocktree = Blocktree::open(&ledger_path).unwrap();
/*
Build a blocktree in the ledger with the following fork structure:
slot 0 (all ticks)
|
slot 1 (all ticks)
|
slot 2 (all ticks)
|
slot 3 (all ticks) -> root
|
slot 4 (all ticks)
|
slot 5 (all ticks) -> root
|
slot 6 (all ticks)
*/
let mut last_hash = blockhash;
for i in 0..6 {
last_hash =
fill_blocktree_slot_with_ticks(&blocktree, ticks_per_slot, i + 1, i, last_hash);
}
blocktree.set_roots(&[3, 5]).unwrap();
// Set up bank1
let bank0 = Arc::new(Bank::new(&genesis_block));
process_bank_0(&bank0, &blocktree, true).unwrap();
let bank1 = Arc::new(Bank::new_from_parent(&bank0, &Pubkey::default(), 1));
bank1.squash();
let slot1_entries = blocktree.get_slot_entries(1, 0, None).unwrap();
verify_and_process_entries(&bank1, &slot1_entries, true, bank0.last_blockhash()).unwrap();
// Test process_blocktree_from_root() from slot 1 onwards
let (bank_forks, bank_forks_info, _) =
process_blocktree_from_root(&blocktree, bank1, true, None).unwrap();
assert_eq!(bank_forks_info.len(), 1); // One fork
assert_eq!(
bank_forks_info[0],
BankForksInfo {
bank_slot: 6, // The head of the fork is slot 6
}
);
// slots_since_snapshot should contain everything on the rooted path
assert_eq!(
bank_forks.slots_since_snapshot().to_vec(),
vec![1, 2, 3, 4, 5]
);
assert_eq!(bank_forks.root(), 5);
// Verify the parents of the head of the fork
assert_eq!(
&bank_forks[6]
.parents()
.iter()
.map(|bank| bank.slot())
.collect::<Vec<_>>(),
&[5]
);
// Check that bank forks has the correct banks
verify_fork_infos(&bank_forks, &bank_forks_info);
}
#[test]
#[ignore]
fn test_process_entries_stress() {
@@ -1523,22 +1347,4 @@ pub mod tests {
let bank = Bank::new_with_paths(&genesis_block, account_paths);
bank.epoch_schedule().clone()
}
// Check that `bank_forks` contains all the ancestors and banks for each fork identified in
// `bank_forks_info`
fn verify_fork_infos(bank_forks: &BankForks, bank_forks_info: &[BankForksInfo]) {
for fork in bank_forks_info {
let head_slot = fork.bank_slot;
let head_bank = &bank_forks[head_slot];
let mut parents = head_bank.parents();
parents.push(head_bank.clone());
// Ensure the tip of each fork and all its parents are in the given bank_forks
for parent in parents {
let parent_bank = &bank_forks[parent.slot()];
assert_eq!(parent_bank.slot(), parent.slot());
assert!(parent_bank.is_frozen());
}
}
}
}

View File

@@ -9,10 +9,12 @@ use crate::erasure::{CodingGenerator, ErasureConfig};
use crate::poh_recorder::WorkingBankEntries;
use crate::result::{Error, Result};
use crate::service::Service;
use crate::shred::Shredder;
use crate::staking_utils;
use rayon::ThreadPool;
use solana_metrics::{datapoint, inc_new_counter_error, inc_new_counter_info};
use solana_metrics::{
datapoint, inc_new_counter_debug, inc_new_counter_error, inc_new_counter_info,
};
use solana_sdk::timing::duration_as_ms;
use std::net::UdpSocket;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::mpsc::{Receiver, RecvTimeoutError};
@@ -22,7 +24,7 @@ use std::time::Instant;
mod broadcast_bad_blob_sizes;
mod broadcast_fake_blobs_run;
pub(crate) mod broadcast_utils;
mod broadcast_utils;
mod fail_entry_verification_broadcast_run;
mod standard_broadcast_run;
@@ -239,7 +241,6 @@ mod test {
use solana_sdk::hash::Hash;
use solana_sdk::pubkey::Pubkey;
use solana_sdk::signature::{Keypair, KeypairUtil};
use std::path::Path;
use std::sync::atomic::AtomicBool;
use std::sync::mpsc::channel;
use std::sync::{Arc, RwLock};
@@ -254,7 +255,7 @@ mod test {
fn setup_dummy_broadcast_service(
leader_pubkey: &Pubkey,
ledger_path: &Path,
ledger_path: &str,
entry_receiver: Receiver<WorkingBankEntries>,
) -> MockBroadcastStage {
// Make the database ledger
@@ -310,23 +311,18 @@ mod test {
&ledger_path,
entry_receiver,
);
let start_tick_height;
let max_tick_height;
let ticks_per_slot;
let slot;
{
let bank = broadcast_service.bank.clone();
start_tick_height = bank.tick_height();
max_tick_height = bank.max_tick_height();
ticks_per_slot = bank.ticks_per_slot();
slot = bank.slot();
let ticks = create_ticks(max_tick_height - start_tick_height, Hash::default());
for (i, tick) in ticks.into_iter().enumerate() {
entry_sender
.send((bank.clone(), vec![(tick, i as u64 + 1)]))
.expect("Expect successful send to broadcast service");
}
let bank = broadcast_service.bank.clone();
let start_tick_height = bank.tick_height();
let max_tick_height = bank.max_tick_height();
let ticks_per_slot = bank.ticks_per_slot();
let ticks = create_ticks(max_tick_height - start_tick_height, Hash::default());
for (i, tick) in ticks.into_iter().enumerate() {
entry_sender
.send((bank.clone(), vec![(tick, i as u64 + 1)]))
.expect("Expect successful send to broadcast service");
}
sleep(Duration::from_millis(2000));
trace!(
@@ -337,10 +333,15 @@ mod test {
);
let blocktree = broadcast_service.blocktree;
let (entries, _) = blocktree
.get_slot_entries_with_shred_count(slot, 0)
.expect("Expect entries to be present");
assert_eq!(entries.len(), max_tick_height as usize);
let mut blob_index = 0;
for i in 0..max_tick_height - start_tick_height {
let slot = (start_tick_height + i + 1) / ticks_per_slot;
let result = blocktree.get_data_blob(slot, blob_index).unwrap();
blob_index += 1;
result.expect("expect blob presence");
}
drop(entry_sender);
broadcast_service

View File

@@ -1,7 +1,5 @@
use super::broadcast_utils;
use super::*;
use crate::shred::Shred;
use solana_sdk::timing::duration_as_ms;
#[derive(Default)]
struct BroadcastStats {
@@ -51,7 +49,7 @@ impl StandardBroadcastRun {
impl BroadcastRun for StandardBroadcastRun {
fn run(
&mut self,
_broadcast: &mut Broadcast,
broadcast: &mut Broadcast,
cluster_info: &Arc<RwLock<ClusterInfo>>,
receiver: &Receiver<WorkingBankEntries>,
sock: &UdpSocket,
@@ -68,59 +66,24 @@ impl BroadcastRun for StandardBroadcastRun {
// 2) Convert entries to blobs + generate coding blobs
let to_blobs_start = Instant::now();
let keypair = &cluster_info.read().unwrap().keypair.clone();
let mut latest_blob_index = blocktree
let latest_blob_index = blocktree
.meta(bank.slot())
.expect("Database error")
.map(|meta| meta.consumed)
.unwrap_or(0);
let parent_slot = if let Some(parent_bank) = bank.parent() {
parent_bank.slot()
} else {
0
};
let mut all_shreds = vec![];
let mut all_seeds = vec![];
let num_ventries = receive_results.ventries.len();
receive_results
.ventries
.into_iter()
.enumerate()
.for_each(|(i, entries_tuple)| {
let (entries, _): (Vec<_>, Vec<_>) = entries_tuple.into_iter().unzip();
//entries
let mut shredder = Shredder::new(
bank.slot(),
Some(parent_slot),
1.0,
keypair,
latest_blob_index as u32,
)
.expect("Expected to create a new shredder");
let (data_blobs, coding_blobs) = broadcast_utils::entries_to_blobs(
receive_results.ventries,
&broadcast.thread_pool,
latest_blob_index,
last_tick,
&bank,
&keypair,
&mut broadcast.coding_generator,
);
bincode::serialize_into(&mut shredder, &entries)
.expect("Expect to write all entries to shreds");
if i == (num_ventries - 1) && last_tick == bank.max_tick_height() {
shredder.finalize_slot();
} else {
shredder.finalize_fec_block();
}
let shreds: Vec<Shred> = shredder
.shreds
.iter()
.map(|s| bincode::deserialize(s).unwrap())
.collect();
let mut seeds: Vec<[u8; 32]> = shreds.iter().map(|s| s.seed()).collect();
trace!("Inserting {:?} shreds in blocktree", shreds.len());
blocktree
.insert_shreds(shreds)
.expect("Failed to insert shreds in blocktree");
latest_blob_index = u64::from(shredder.index);
all_shreds.append(&mut shredder.shreds);
all_seeds.append(&mut seeds);
});
blocktree.write_shared_blobs(data_blobs.iter())?;
blocktree.put_shared_coding_blobs(coding_blobs.iter())?;
let to_blobs_elapsed = to_blobs_start.elapsed();
@@ -129,15 +92,17 @@ impl BroadcastRun for StandardBroadcastRun {
let bank_epoch = bank.get_stakers_epoch(bank.slot());
let stakes = staking_utils::staked_nodes_at_epoch(&bank, bank_epoch);
trace!("Broadcasting {:?} shreds", all_shreds.len());
cluster_info.read().unwrap().broadcast_shreds(
// Broadcast data + erasures
cluster_info.read().unwrap().broadcast(
sock,
&all_shreds,
&all_seeds,
data_blobs.iter().chain(coding_blobs.iter()),
stakes.as_ref(),
)?;
inc_new_counter_debug!("streamer-broadcast-sent", all_shreds.len());
inc_new_counter_debug!(
"streamer-broadcast-sent",
data_blobs.len() + coding_blobs.len()
);
let broadcast_elapsed = broadcast_start.elapsed();
self.update_broadcast_stats(

View File

@@ -12,7 +12,7 @@ pub const CHACHA_KEY_SIZE: usize = 32;
pub fn chacha_cbc_encrypt_ledger(
blocktree: &Arc<Blocktree>,
start_slot: u64,
slice: u64,
slots_per_segment: u64,
out_path: &Path,
ivec: &mut [u8; CHACHA_BLOCK_SIZE],
@@ -23,32 +23,26 @@ pub fn chacha_cbc_encrypt_ledger(
let mut buffer = [0; BUFFER_SIZE];
let mut encrypted_buffer = [0; BUFFER_SIZE];
let key = [0; CHACHA_KEY_SIZE];
let mut total_entries = 0;
let mut total_size = 0;
let mut current_slot = start_slot;
let mut start_index = 0;
loop {
match blocktree.get_data_shreds(current_slot, start_index, &mut buffer) {
Ok((last_index, mut size)) => {
debug!(
"chacha: encrypting slice: {} num_shreds: {} data_len: {}",
current_slot,
last_index.saturating_sub(start_index),
size
);
debug!("read {} bytes", size);
let mut entry = slice;
loop {
match blocktree.read_blobs_bytes(0, slots_per_segment - total_entries, &mut buffer, entry) {
Ok((num_entries, entry_len)) => {
debug!(
"chacha: encrypting slice: {} num_entries: {} entry_len: {}",
slice, num_entries, entry_len
);
debug!("read {} bytes", entry_len);
let mut size = entry_len as usize;
if size == 0 {
if current_slot.saturating_sub(start_slot) < slots_per_segment {
current_slot += 1;
start_index = 0;
continue;
} else {
break;
}
break;
}
if size < BUFFER_SIZE {
// round to the nearest key_size boundary
// We are on the last block, round to the nearest key_size
// boundary
size = (size + CHACHA_KEY_SIZE - 1) & !(CHACHA_KEY_SIZE - 1);
}
total_size += size;
@@ -59,7 +53,8 @@ pub fn chacha_cbc_encrypt_ledger(
return Err(res);
}
start_index = last_index + 1;
total_entries += num_entries;
entry += num_entries;
}
Err(e) => {
info!("Error encrypting file: {:?}", e);
@@ -122,22 +117,9 @@ mod tests {
let blocktree = Arc::new(Blocktree::open(&ledger_path).unwrap());
let out_path = Path::new("test_chacha_encrypt_file_output.txt.enc");
let seed = [2u8; 32];
let mut rnd = GenKeys::new(seed);
let keypair = rnd.gen_keypair();
let entries = make_tiny_deterministic_test_entries(slots_per_segment);
blocktree
.write_entries_using_shreds(
0,
0,
0,
ticks_per_slot,
None,
true,
&Arc::new(keypair),
&entries,
)
.write_entries(0, 0, 0, ticks_per_slot, &entries)
.unwrap();
let mut key = hex!(
@@ -153,7 +135,7 @@ mod tests {
hasher.hash(&buf[..size]);
// golden needs to be updated if blob stuff changes....
let golden: Hash = "EdYYuAuDPVY7DLNeCtPWAKipicx2KjsxqD2PZ7oxVmHE"
let golden: Hash = "7hgFLHveuv9zvHpp6qpco9AHAJKyczdgxiktEMkeghDQ"
.parse()
.unwrap();

View File

@@ -33,62 +33,54 @@ pub fn chacha_cbc_encrypt_file_many_keys(
));
}
const BUFFER_SIZE: usize = 8 * 1024;
let mut buffer = [0; BUFFER_SIZE];
let mut buffer = [0; 8 * 1024];
let num_keys = ivecs.len() / CHACHA_BLOCK_SIZE;
let mut sha_states = vec![0; num_keys * size_of::<Hash>()];
let mut int_sha_states = vec![0; num_keys * 112];
let keys: Vec<u8> = vec![0; num_keys * CHACHA_KEY_SIZE]; // keys not used ATM, uniqueness comes from IV
let mut current_slot = segment * slots_per_segment;
let mut start_index = 0;
let start_slot = current_slot;
let mut total_size = 0;
let mut entry = segment;
let mut total_entries = 0;
let mut total_entry_len = 0;
let mut time: f32 = 0.0;
unsafe {
chacha_init_sha_state(int_sha_states.as_mut_ptr(), num_keys as u32);
}
loop {
match blocktree.get_data_shreds(current_slot, start_index, &mut buffer) {
Ok((last_index, mut size)) => {
match blocktree.read_blobs_bytes(entry, slots_per_segment - total_entries, &mut buffer, 0) {
Ok((num_entries, entry_len)) => {
debug!(
"chacha_cuda: encrypting segment: {} num_shreds: {} data_len: {}",
segment,
last_index.saturating_sub(start_index),
size
"chacha_cuda: encrypting segment: {} num_entries: {} entry_len: {}",
segment, num_entries, entry_len
);
if size == 0 {
if current_slot.saturating_sub(start_slot) < slots_per_segment {
current_slot += 1;
start_index = 0;
continue;
} else {
break;
}
if num_entries == 0 {
break;
}
if size < BUFFER_SIZE {
// round to the nearest key_size boundary
size = (size + CHACHA_KEY_SIZE - 1) & !(CHACHA_KEY_SIZE - 1);
}
let entry_len_usz = entry_len as usize;
unsafe {
chacha_cbc_encrypt_many_sample(
buffer[..size].as_ptr(),
buffer[..entry_len_usz].as_ptr(),
int_sha_states.as_mut_ptr(),
size,
entry_len_usz,
keys.as_ptr(),
ivecs.as_mut_ptr(),
num_keys as u32,
samples.as_ptr(),
samples.len() as u32,
total_size,
total_entry_len,
&mut time,
);
}
total_size += size as u64;
start_index = last_index + 1;
total_entry_len += entry_len;
total_entries += num_entries;
entry += num_entries;
debug!(
"total entries: {} entry: {} segment: {} entries_per_segment: {}",
total_entries, entry, segment, slots_per_segment
);
if (entry - segment) >= slots_per_segment {
break;
}
}
Err(e) => {
info!("Error encrypting file: {:?}", e);
@@ -121,7 +113,6 @@ mod tests {
use crate::entry::make_tiny_test_entries;
use crate::replicator::sample_file;
use solana_sdk::hash::Hash;
use solana_sdk::signature::{Keypair, KeypairUtil};
use solana_sdk::timing::DEFAULT_SLOTS_PER_SEGMENT;
use std::fs::{remove_dir_all, remove_file};
use std::path::Path;
@@ -139,16 +130,7 @@ mod tests {
let blocktree = Arc::new(Blocktree::open(&ledger_path).unwrap());
blocktree
.write_entries_using_shreds(
0,
0,
0,
ticks_per_slot,
Some(0),
true,
&Arc::new(Keypair::new()),
&entries,
)
.write_entries(0, 0, 0, ticks_per_slot, &entries)
.unwrap();
let out_path = Path::new("test_chacha_encrypt_file_many_keys_single_output.txt.enc");
@@ -196,16 +178,7 @@ mod tests {
let ticks_per_slot = 16;
let blocktree = Arc::new(Blocktree::open(&ledger_path).unwrap());
blocktree
.write_entries_using_shreds(
0,
0,
0,
ticks_per_slot,
Some(0),
true,
&Arc::new(Keypair::new()),
&entries,
)
.write_entries(0, 0, 0, ticks_per_slot, &entries)
.unwrap();
let out_path = Path::new("test_chacha_encrypt_file_many_keys_multiple_output.txt.enc");

View File

@@ -1,9 +1,8 @@
use crate::validator::ValidatorConfig;
use solana_client::thin_client::ThinClient;
use solana_sdk::pubkey::Pubkey;
pub trait Cluster {
fn get_node_pubkeys(&self) -> Vec<Pubkey>;
fn get_validator_client(&self, pubkey: &Pubkey) -> Option<ThinClient>;
fn restart_node(&mut self, pubkey: Pubkey, config: &ValidatorConfig);
fn restart_node(&mut self, pubkey: Pubkey);
}

File diff suppressed because it is too large Load Diff

View File

@@ -315,7 +315,7 @@ impl ClusterInfoRepairListener {
// sending the blobs in this slot for repair, we expect these slots
// to be full.
if let Some(blob_data) = blocktree
.get_data_shred_bytes(slot, blob_index as u64)
.get_data_blob_bytes(slot, blob_index as u64)
.expect("Failed to read data blob from blocktree")
{
socket.send_to(&blob_data[..], repairee_tvu)?;
@@ -479,7 +479,7 @@ impl Service for ClusterInfoRepairListener {
mod tests {
use super::*;
use crate::blocktree::get_tmp_ledger_path;
use crate::blocktree::tests::make_many_slot_entries_using_shreds;
use crate::blocktree::tests::make_many_slot_entries;
use crate::cluster_info::Node;
use crate::packet::{Blob, SharedBlob};
use crate::streamer;
@@ -620,14 +620,13 @@ mod tests {
fn test_serve_repairs_to_repairee() {
let blocktree_path = get_tmp_ledger_path!();
let blocktree = Blocktree::open(&blocktree_path).unwrap();
let entries_per_slot = 5;
let blobs_per_slot = 5;
let num_slots = 10;
assert_eq!(num_slots % 2, 0);
let (shreds, _) = make_many_slot_entries_using_shreds(0, num_slots, entries_per_slot);
let num_shreds_per_slot = shreds.len() as u64 / num_slots;
let (blobs, _) = make_many_slot_entries(0, num_slots, blobs_per_slot);
// Write slots in the range [0, num_slots] to blocktree
blocktree.insert_shreds(shreds).unwrap();
blocktree.insert_data_blobs(&blobs).unwrap();
// Write roots so that these slots will qualify to be sent by the repairman
let roots: Vec<_> = (0..=num_slots - 1).collect();
@@ -647,8 +646,8 @@ mod tests {
let repairee_epoch_slots =
EpochSlots::new(mock_repairee.id, repairee_root, repairee_slots, 1);
// Mock out some other repairmen such that each repairman is responsible for 1 shred in a slot
let num_repairmen = entries_per_slot - 1;
// Mock out some other repairmen such that each repairman is responsible for 1 blob in a slot
let num_repairmen = blobs_per_slot - 1;
let mut eligible_repairmen: Vec<_> =
(0..num_repairmen).map(|_| Pubkey::new_rand()).collect();
eligible_repairmen.push(my_pubkey);
@@ -673,19 +672,19 @@ mod tests {
.unwrap();
}
let mut received_shreds: Vec<Arc<RwLock<Blob>>> = vec![];
let mut received_blobs: Vec<Arc<RwLock<Blob>>> = vec![];
// This repairee was missing exactly `num_slots / 2` slots, so we expect to get
// `(num_slots / 2) * num_shreds_per_slot * REPAIR_REDUNDANCY` blobs.
let num_expected_shreds = (num_slots / 2) * num_shreds_per_slot * REPAIR_REDUNDANCY as u64;
while (received_shreds.len() as u64) < num_expected_shreds {
received_shreds.extend(mock_repairee.receiver.recv().unwrap());
// `(num_slots / 2) * blobs_per_slot * REPAIR_REDUNDANCY` blobs.
let num_expected_blobs = (num_slots / 2) * blobs_per_slot * REPAIR_REDUNDANCY as u64;
while (received_blobs.len() as u64) < num_expected_blobs {
received_blobs.extend(mock_repairee.receiver.recv().unwrap());
}
// Make sure no extra blobs get sent
sleep(Duration::from_millis(1000));
assert!(mock_repairee.receiver.try_recv().is_err());
assert_eq!(received_shreds.len() as u64, num_expected_shreds);
assert_eq!(received_blobs.len() as u64, num_expected_blobs);
// Shutdown
mock_repairee.close().unwrap();
@@ -703,8 +702,8 @@ mod tests {
// Create blobs for first two epochs and write them to blocktree
let total_slots = slots_per_epoch * 2;
let (shreds, _) = make_many_slot_entries_using_shreds(0, total_slots, 1);
blocktree.insert_shreds(shreds).unwrap();
let (blobs, _) = make_many_slot_entries(0, total_slots, 1);
blocktree.insert_data_blobs(&blobs).unwrap();
// Write roots so that these slots will qualify to be sent by the repairman
let roots: Vec<_> = (0..=slots_per_epoch * 2 - 1).collect();
@@ -742,7 +741,7 @@ mod tests {
)
.unwrap();
// Make sure no shreds get sent
// Make sure no blobs get sent
sleep(Duration::from_millis(1000));
assert!(mock_repairee.receiver.try_recv().is_err());

View File

@@ -1,39 +1,31 @@
use rand::{thread_rng, Rng};
use solana_client::thin_client::create_client;
use crate::blocktree::Blocktree;
/// Cluster independant integration tests
///
/// All tests must start from an entry point and a funding keypair and
/// discover the rest of the network.
use solana_core::{
blocktree::Blocktree,
cluster_info::FULLNODE_PORT_RANGE,
consensus::VOTE_THRESHOLD_DEPTH,
contact_info::ContactInfo,
entry::{Entry, EntrySlice},
gossip_service::discover_cluster,
};
use crate::cluster_info::FULLNODE_PORT_RANGE;
use crate::consensus::VOTE_THRESHOLD_DEPTH;
use crate::contact_info::ContactInfo;
use crate::entry::{Entry, EntrySlice};
use crate::gossip_service::discover_cluster;
use hashbrown::HashSet;
use solana_client::thin_client::create_client;
use solana_runtime::epoch_schedule::MINIMUM_SLOTS_PER_EPOCH;
use solana_sdk::{
client::SyncClient,
hash::Hash,
poh_config::PohConfig,
pubkey::Pubkey,
signature::{Keypair, KeypairUtil, Signature},
system_transaction,
timing::{
duration_as_ms, DEFAULT_TICKS_PER_SECOND, DEFAULT_TICKS_PER_SLOT,
NUM_CONSECUTIVE_LEADER_SLOTS,
},
transport::TransportError,
};
use std::{
collections::{HashMap, HashSet},
path::Path,
thread::sleep,
time::Duration,
use solana_sdk::client::SyncClient;
use solana_sdk::hash::Hash;
use solana_sdk::poh_config::PohConfig;
use solana_sdk::pubkey::Pubkey;
use solana_sdk::signature::{Keypair, KeypairUtil, Signature};
use solana_sdk::system_transaction;
use solana_sdk::timing::{
duration_as_ms, DEFAULT_NUM_TICKS_PER_SECOND, DEFAULT_TICKS_PER_SLOT,
NUM_CONSECUTIVE_LEADER_SLOTS,
};
use solana_sdk::transport::TransportError;
use std::thread::sleep;
use std::time::Duration;
const DEFAULT_SLOT_MILLIS: u64 = (DEFAULT_TICKS_PER_SLOT * 1000) / DEFAULT_TICKS_PER_SECOND;
const DEFAULT_SLOT_MILLIS: u64 = (DEFAULT_TICKS_PER_SLOT * 1000) / DEFAULT_NUM_TICKS_PER_SECOND;
/// Spend and verify from every node in the network
pub fn spend_and_verify_all_nodes<S: ::std::hash::BuildHasher>(
@@ -71,25 +63,8 @@ pub fn spend_and_verify_all_nodes<S: ::std::hash::BuildHasher>(
}
}
pub fn verify_balances<S: ::std::hash::BuildHasher>(
expected_balances: HashMap<Pubkey, u64, S>,
node: &ContactInfo,
) {
pub fn send_many_transactions(node: &ContactInfo, funding_keypair: &Keypair, num_txs: u64) {
let client = create_client(node.client_facing_addr(), FULLNODE_PORT_RANGE);
for (pk, b) in expected_balances {
let bal = client.poll_get_balance(&pk).expect("balance in source");
assert_eq!(bal, b);
}
}
pub fn send_many_transactions(
node: &ContactInfo,
funding_keypair: &Keypair,
max_tokens_per_transfer: u64,
num_txs: u64,
) -> HashMap<Pubkey, u64> {
let client = create_client(node.client_facing_addr(), FULLNODE_PORT_RANGE);
let mut expected_balances = HashMap::new();
for _ in 0..num_txs {
let random_keypair = Keypair::new();
let bal = client
@@ -97,23 +72,12 @@ pub fn send_many_transactions(
.expect("balance in source");
assert!(bal > 0);
let (blockhash, _fee_calculator) = client.get_recent_blockhash().unwrap();
let transfer_amount = thread_rng().gen_range(1, max_tokens_per_transfer);
let mut transaction = system_transaction::transfer(
&funding_keypair,
&random_keypair.pubkey(),
transfer_amount,
blockhash,
);
let mut transaction =
system_transaction::transfer(&funding_keypair, &random_keypair.pubkey(), 1, blockhash);
client
.retry_transfer(&funding_keypair, &mut transaction, 5)
.unwrap();
expected_balances.insert(random_keypair.pubkey(), transfer_amount);
}
expected_balances
}
pub fn fullnode_exit(entry_point_info: &ContactInfo, nodes: usize) {
@@ -130,7 +94,7 @@ pub fn fullnode_exit(entry_point_info: &ContactInfo, nodes: usize) {
}
}
pub fn verify_ledger_ticks(ledger_path: &Path, ticks_per_slot: usize) {
pub fn verify_ledger_ticks(ledger_path: &str, ticks_per_slot: usize) {
let ledger = Blocktree::open(ledger_path).unwrap();
let zeroth_slot = ledger.get_slot_entries(0, 0, None).unwrap();
let last_id = zeroth_slot.last().unwrap().hash;
@@ -208,7 +172,6 @@ pub fn kill_entry_and_spend_and_verify_rest(
info!("done sleeping for 2 fortnights");
for ingress_node in &cluster_nodes {
if ingress_node.id == entry_point_info.id {
info!("ingress_node.id == entry_point_info.id, continuing...");
continue;
}
@@ -252,14 +215,12 @@ pub fn kill_entry_and_spend_and_verify_rest(
Ok(sig) => sig,
}
};
info!("poll_all_nodes_for_signature()");
match poll_all_nodes_for_signature(&entry_point_info, &cluster_nodes, &sig, confs) {
Err(e) => {
info!("poll_all_nodes_for_signature() failed {:?}", e);
result = Err(e);
}
Ok(()) => {
info!("poll_all_nodes_for_signature() succeeded, done.");
break;
}
}

View File

@@ -1,4 +1,5 @@
use crate::bank_forks::BankForks;
use crate::staking_utils;
use solana_metrics::datapoint_info;
use solana_runtime::bank::Bank;
use solana_sdk::account::Account;
@@ -12,6 +13,15 @@ pub const VOTE_THRESHOLD_DEPTH: usize = 8;
pub const VOTE_THRESHOLD_SIZE: f64 = 2f64 / 3f64;
pub const MAX_RECENT_VOTES: usize = 16;
#[derive(Default)]
pub struct EpochStakes {
epoch: u64,
stakes: HashMap<Pubkey, u64>,
self_staked: u64,
total_staked: u64,
delegate_pubkey: Pubkey,
}
#[derive(Default, Debug)]
pub struct StakeLockout {
lockout: u64,
@@ -29,53 +39,92 @@ impl StakeLockout {
#[derive(Default)]
pub struct Tower {
node_pubkey: Pubkey,
epoch_stakes: EpochStakes,
threshold_depth: usize,
threshold_size: f64,
lockouts: VoteState,
recent_votes: VecDeque<Vote>,
}
impl EpochStakes {
pub fn new(epoch: u64, stakes: HashMap<Pubkey, u64>, delegate_pubkey: &Pubkey) -> Self {
let total_staked = stakes.values().sum();
let self_staked = *stakes.get(&delegate_pubkey).unwrap_or(&0);
Self {
epoch,
stakes,
total_staked,
self_staked,
delegate_pubkey: *delegate_pubkey,
}
}
pub fn new_for_tests(lamports: u64) -> Self {
Self::new(
0,
vec![(Pubkey::default(), lamports)].into_iter().collect(),
&Pubkey::default(),
)
}
pub fn new_from_stakes(epoch: u64, accounts: &[(Pubkey, (u64, Account))]) -> Self {
let stakes = accounts.iter().map(|(k, (v, _))| (*k, *v)).collect();
Self::new(epoch, stakes, &accounts[0].0)
}
pub fn new_from_bank(bank: &Bank, my_pubkey: &Pubkey) -> Self {
let bank_epoch = bank.get_epoch_and_slot_index(bank.slot()).0;
let stakes = staking_utils::vote_account_stakes_at_epoch(bank, bank_epoch)
.expect("voting require a bank with stakes");
Self::new(bank_epoch, stakes, my_pubkey)
}
}
impl Tower {
pub fn new(node_pubkey: &Pubkey, vote_account_pubkey: &Pubkey, bank_forks: &BankForks) -> Self {
pub fn new_from_forks(bank_forks: &BankForks, my_pubkey: &Pubkey) -> Self {
let mut frozen_banks: Vec<_> = bank_forks.frozen_banks().values().cloned().collect();
frozen_banks.sort_by_key(|b| (b.parents().len(), b.slot()));
let epoch_stakes = {
if let Some(bank) = frozen_banks.last() {
EpochStakes::new_from_bank(bank, my_pubkey)
} else {
return Self::default();
}
};
let mut tower = Self {
node_pubkey: *node_pubkey,
epoch_stakes,
threshold_depth: VOTE_THRESHOLD_DEPTH,
threshold_size: VOTE_THRESHOLD_SIZE,
lockouts: VoteState::default(),
recent_votes: VecDeque::default(),
};
tower.initialize_lockouts_from_bank_forks(&bank_forks, vote_account_pubkey);
let bank = tower.find_heaviest_bank(bank_forks).unwrap();
tower.lockouts = Self::initialize_lockouts_from_bank(&bank, tower.epoch_stakes.epoch);
tower
}
#[cfg(test)]
pub fn new_for_tests(threshold_depth: usize, threshold_size: f64) -> Self {
pub fn new(epoch_stakes: EpochStakes, threshold_depth: usize, threshold_size: f64) -> Self {
Self {
epoch_stakes,
threshold_depth,
threshold_size,
..Tower::default()
lockouts: VoteState::default(),
recent_votes: VecDeque::default(),
}
}
pub fn collect_vote_lockouts<F>(
&self,
bank_slot: u64,
vote_accounts: F,
ancestors: &HashMap<u64, HashSet<u64>>,
) -> (HashMap<u64, StakeLockout>, u64)
) -> HashMap<u64, StakeLockout>
where
F: Iterator<Item = (Pubkey, (u64, Account))>,
{
let mut stake_lockouts = HashMap::new();
let mut total_stake = 0;
for (key, (lamports, account)) in vote_accounts {
for (key, (_, account)) in vote_accounts {
let lamports: u64 = *self.epoch_stakes.stakes.get(&key).unwrap_or(&0);
if lamports == 0 {
continue;
}
trace!("{} {} with stake {}", self.node_pubkey, key, lamports);
let vote_state = VoteState::from(&account);
if vote_state.is_none() {
datapoint_warn!(
@@ -90,7 +139,9 @@ impl Tower {
}
let mut vote_state = vote_state.unwrap();
if key == self.node_pubkey || vote_state.node_pubkey == self.node_pubkey {
if key == self.epoch_stakes.delegate_pubkey
|| vote_state.node_pubkey == self.epoch_stakes.delegate_pubkey
{
debug!("vote state {:?}", vote_state);
debug!(
"observed slot {}",
@@ -149,25 +200,51 @@ impl Tower {
// Update all the parents of this last vote with the stake of this vote account
Self::update_ancestor_stakes(&mut stake_lockouts, vote.slot, lamports, ancestors);
}
total_stake += lamports;
}
(stake_lockouts, total_stake)
stake_lockouts
}
pub fn is_slot_confirmed(
&self,
slot: u64,
lockouts: &HashMap<u64, StakeLockout>,
total_staked: u64,
) -> bool {
pub fn is_slot_confirmed(&self, slot: u64, lockouts: &HashMap<u64, StakeLockout>) -> bool {
lockouts
.get(&slot)
.map(|lockout| (lockout.stake as f64 / total_staked as f64) > self.threshold_size)
.map(|lockout| {
(lockout.stake as f64 / self.epoch_stakes.total_staked as f64) > self.threshold_size
})
.unwrap_or(false)
}
pub fn is_recent_epoch(&self, bank: &Bank) -> bool {
bank.epoch() >= self.epoch_stakes.epoch
}
pub fn update_epoch(&mut self, bank: &Bank) {
trace!(
"updating bank epoch slot: {} epoch: {}",
bank.slot(),
self.epoch_stakes.epoch
);
if bank.epoch() != self.epoch_stakes.epoch {
assert!(
self.is_recent_epoch(bank),
"epoch_stakes cannot move backwards"
);
info!(
"Tower updated epoch bank slot: {} epoch: {}",
bank.slot(),
self.epoch_stakes.epoch
);
self.epoch_stakes =
EpochStakes::new_from_bank(bank, &self.epoch_stakes.delegate_pubkey);
datapoint_info!(
"tower-epoch",
("epoch", self.epoch_stakes.epoch, i64),
("self_staked", self.epoch_stakes.self_staked, i64),
("total_staked", self.epoch_stakes.total_staked, i64)
);
}
}
pub fn record_vote(&mut self, slot: u64, hash: Hash) -> Option<u64> {
trace!("{} record_vote for {}", self.node_pubkey, slot);
let root_slot = self.lockouts.root_slot;
let vote = Vote { slot, hash };
self.lockouts.process_vote_unchecked(&vote);
@@ -204,6 +281,10 @@ impl Tower {
self.lockouts.root_slot
}
pub fn total_epoch_stakes(&self) -> u64 {
self.epoch_stakes.total_staked
}
pub fn calculate_weight(&self, stake_lockouts: &HashMap<u64, StakeLockout>) -> u128 {
let mut sum = 0u128;
let root_slot = self.lockouts.root_slot.unwrap_or(0);
@@ -247,14 +328,14 @@ impl Tower {
&self,
slot: u64,
stake_lockouts: &HashMap<u64, StakeLockout>,
total_staked: u64,
) -> bool {
let mut lockouts = self.lockouts.clone();
lockouts.process_slot_vote_unchecked(slot);
let vote = lockouts.nth_recent_vote(self.threshold_depth);
if let Some(vote) = vote {
if let Some(fork_stake) = stake_lockouts.get(&vote.slot) {
(fork_stake.stake as f64 / total_staked as f64) > self.threshold_size
(fork_stake.stake as f64 / self.epoch_stakes.total_staked as f64)
> self.threshold_size
} else {
false
}
@@ -315,7 +396,7 @@ impl Tower {
}
fn bank_weight(&self, bank: &Bank, ancestors: &HashMap<u64, HashSet<u64>>) -> u128 {
let (stake_lockouts, _) =
let stake_lockouts =
self.collect_vote_lockouts(bank.slot(), bank.vote_accounts().into_iter(), ancestors);
self.calculate_weight(&stake_lockouts)
}
@@ -337,28 +418,19 @@ impl Tower {
bank_weights.pop().map(|b| b.2)
}
fn initialize_lockouts_from_bank_forks(
&mut self,
bank_forks: &BankForks,
vote_account_pubkey: &Pubkey,
) {
if let Some(bank) = self.find_heaviest_bank(bank_forks) {
if let Some((_stake, vote_account)) = bank.vote_accounts().get(vote_account_pubkey) {
let vote_state = VoteState::deserialize(&vote_account.data)
.expect("vote_account isn't a VoteState?");
trace!(
"{} lockouts initialized to {:?}",
self.node_pubkey,
vote_state
);
assert_eq!(
vote_state.node_pubkey, self.node_pubkey,
"vote account's node_pubkey doesn't match",
);
self.lockouts = vote_state;
fn initialize_lockouts_from_bank(bank: &Bank, current_epoch: u64) -> VoteState {
let mut lockouts = VoteState::default();
if let Some(iter) = bank.epoch_vote_accounts(current_epoch) {
for (delegate_pubkey, (_, account)) in iter {
if delegate_pubkey == bank.collector_id() {
let state = VoteState::deserialize(&account.data).expect("votes");
if lockouts.votes.len() < state.votes.len() {
lockouts = state;
}
}
}
}
};
lockouts
}
}
@@ -385,18 +457,31 @@ mod test {
}
#[test]
fn test_collect_vote_lockouts_sums() {
//two accounts voting for slot 0 with 1 token staked
let accounts = gen_stakes(&[(1, &[0]), (1, &[0])]);
let tower = Tower::new_for_tests(0, 0.67);
fn test_collect_vote_lockouts_no_epoch_stakes() {
let accounts = gen_stakes(&[(1, &[0])]);
let epoch_stakes = EpochStakes::new_for_tests(2);
let tower = Tower::new(epoch_stakes, 0, 0.67);
let ancestors = vec![(1, vec![0].into_iter().collect()), (0, HashSet::new())]
.into_iter()
.collect();
let (staked_lockouts, total_staked) =
tower.collect_vote_lockouts(1, accounts.into_iter(), &ancestors);
let staked_lockouts = tower.collect_vote_lockouts(1, accounts.into_iter(), &ancestors);
assert!(staked_lockouts.is_empty());
assert_eq!(tower.epoch_stakes.total_staked, 2);
}
#[test]
fn test_collect_vote_lockouts_sums() {
//two accounts voting for slot 0 with 1 token staked
let accounts = gen_stakes(&[(1, &[0]), (1, &[0])]);
let epoch_stakes = EpochStakes::new_from_stakes(0, &accounts);
let tower = Tower::new(epoch_stakes, 0, 0.67);
let ancestors = vec![(1, vec![0].into_iter().collect()), (0, HashSet::new())]
.into_iter()
.collect();
let staked_lockouts = tower.collect_vote_lockouts(1, accounts.into_iter(), &ancestors);
assert_eq!(staked_lockouts[&0].stake, 2);
assert_eq!(staked_lockouts[&0].lockout, 2 + 2 + 4 + 4);
assert_eq!(total_staked, 2);
assert_eq!(tower.epoch_stakes.total_staked, 2);
}
#[test]
@@ -404,14 +489,15 @@ mod test {
let votes: Vec<u64> = (0..MAX_LOCKOUT_HISTORY as u64).into_iter().collect();
//two accounts voting for slot 0 with 1 token staked
let accounts = gen_stakes(&[(1, &votes), (1, &votes)]);
let mut tower = Tower::new_for_tests(0, 0.67);
let epoch_stakes = EpochStakes::new_from_stakes(0, &accounts);
let mut tower = Tower::new(epoch_stakes, 0, 0.67);
let mut ancestors = HashMap::new();
for i in 0..(MAX_LOCKOUT_HISTORY + 1) {
tower.record_vote(i as u64, Hash::default());
ancestors.insert(i as u64, (0..i as u64).into_iter().collect());
}
assert_eq!(tower.lockouts.root_slot, Some(0));
let (staked_lockouts, _total_staked) = tower.collect_vote_lockouts(
let staked_lockouts = tower.collect_vote_lockouts(
MAX_LOCKOUT_HISTORY as u64,
accounts.into_iter(),
&ancestors,
@@ -425,7 +511,7 @@ mod test {
#[test]
fn test_calculate_weight_skips_root() {
let mut tower = Tower::new_for_tests(0, 0.67);
let mut tower = Tower::new(EpochStakes::new_for_tests(2), 0, 0.67);
tower.lockouts.root_slot = Some(1);
let stakes = vec![
(
@@ -450,7 +536,7 @@ mod test {
#[test]
fn test_calculate_weight() {
let tower = Tower::new_for_tests(0, 0.67);
let tower = Tower::new(EpochStakes::new_for_tests(2), 0, 0.67);
let stakes = vec![(
0,
StakeLockout {
@@ -465,7 +551,7 @@ mod test {
#[test]
fn test_check_vote_threshold_without_votes() {
let tower = Tower::new_for_tests(1, 0.67);
let tower = Tower::new(EpochStakes::new_for_tests(2), 1, 0.67);
let stakes = vec![(
0,
StakeLockout {
@@ -475,12 +561,12 @@ mod test {
)]
.into_iter()
.collect();
assert!(tower.check_vote_stake_threshold(0, &stakes, 2));
assert!(tower.check_vote_stake_threshold(0, &stakes));
}
#[test]
fn test_aggregate_stake_lockouts() {
let mut tower = Tower::new_for_tests(0, 0.67);
let mut tower = Tower::new(EpochStakes::new_for_tests(2), 0, 0.67);
tower.lockouts.root_slot = Some(1);
let stakes = vec![
(
@@ -533,7 +619,7 @@ mod test {
#[test]
fn test_is_slot_confirmed_not_enough_stake_failure() {
let tower = Tower::new_for_tests(1, 0.67);
let tower = Tower::new(EpochStakes::new_for_tests(2), 1, 0.67);
let stakes = vec![(
0,
StakeLockout {
@@ -543,19 +629,19 @@ mod test {
)]
.into_iter()
.collect();
assert!(!tower.is_slot_confirmed(0, &stakes, 2));
assert!(!tower.is_slot_confirmed(0, &stakes));
}
#[test]
fn test_is_slot_confirmed_unknown_slot() {
let tower = Tower::new_for_tests(1, 0.67);
let tower = Tower::new(EpochStakes::new_for_tests(2), 1, 0.67);
let stakes = HashMap::new();
assert!(!tower.is_slot_confirmed(0, &stakes, 2));
assert!(!tower.is_slot_confirmed(0, &stakes));
}
#[test]
fn test_is_slot_confirmed_pass() {
let tower = Tower::new_for_tests(1, 0.67);
let tower = Tower::new(EpochStakes::new_for_tests(2), 1, 0.67);
let stakes = vec![(
0,
StakeLockout {
@@ -565,19 +651,19 @@ mod test {
)]
.into_iter()
.collect();
assert!(tower.is_slot_confirmed(0, &stakes, 2));
assert!(tower.is_slot_confirmed(0, &stakes));
}
#[test]
fn test_is_locked_out_empty() {
let tower = Tower::new_for_tests(0, 0.67);
let tower = Tower::new(EpochStakes::new_for_tests(2), 0, 0.67);
let descendants = HashMap::new();
assert!(!tower.is_locked_out(0, &descendants));
}
#[test]
fn test_is_locked_out_root_slot_child_pass() {
let mut tower = Tower::new_for_tests(0, 0.67);
let mut tower = Tower::new(EpochStakes::new_for_tests(2), 0, 0.67);
let descendants = vec![(0, vec![1].into_iter().collect())]
.into_iter()
.collect();
@@ -587,7 +673,7 @@ mod test {
#[test]
fn test_is_locked_out_root_slot_sibling_fail() {
let mut tower = Tower::new_for_tests(0, 0.67);
let mut tower = Tower::new(EpochStakes::new_for_tests(2), 0, 0.67);
let descendants = vec![(0, vec![1].into_iter().collect())]
.into_iter()
.collect();
@@ -597,7 +683,7 @@ mod test {
#[test]
fn test_check_already_voted() {
let mut tower = Tower::new_for_tests(0, 0.67);
let mut tower = Tower::new(EpochStakes::new_for_tests(2), 0, 0.67);
tower.record_vote(0, Hash::default());
assert!(tower.has_voted(0));
assert!(!tower.has_voted(1));
@@ -605,7 +691,7 @@ mod test {
#[test]
fn test_is_locked_out_double_vote() {
let mut tower = Tower::new_for_tests(0, 0.67);
let mut tower = Tower::new(EpochStakes::new_for_tests(2), 0, 0.67);
let descendants = vec![(0, vec![1].into_iter().collect()), (1, HashSet::new())]
.into_iter()
.collect();
@@ -616,7 +702,7 @@ mod test {
#[test]
fn test_is_locked_out_child() {
let mut tower = Tower::new_for_tests(0, 0.67);
let mut tower = Tower::new(EpochStakes::new_for_tests(2), 0, 0.67);
let descendants = vec![(0, vec![1].into_iter().collect())]
.into_iter()
.collect();
@@ -626,7 +712,7 @@ mod test {
#[test]
fn test_is_locked_out_sibling() {
let mut tower = Tower::new_for_tests(0, 0.67);
let mut tower = Tower::new(EpochStakes::new_for_tests(2), 0, 0.67);
let descendants = vec![
(0, vec![1, 2].into_iter().collect()),
(1, HashSet::new()),
@@ -641,7 +727,7 @@ mod test {
#[test]
fn test_is_locked_out_last_vote_expired() {
let mut tower = Tower::new_for_tests(0, 0.67);
let mut tower = Tower::new(EpochStakes::new_for_tests(2), 0, 0.67);
let descendants = vec![(0, vec![1, 4].into_iter().collect()), (1, HashSet::new())]
.into_iter()
.collect();
@@ -657,7 +743,7 @@ mod test {
#[test]
fn test_check_vote_threshold_below_threshold() {
let mut tower = Tower::new_for_tests(1, 0.67);
let mut tower = Tower::new(EpochStakes::new_for_tests(2), 1, 0.67);
let stakes = vec![(
0,
StakeLockout {
@@ -668,11 +754,11 @@ mod test {
.into_iter()
.collect();
tower.record_vote(0, Hash::default());
assert!(!tower.check_vote_stake_threshold(1, &stakes, 2));
assert!(!tower.check_vote_stake_threshold(1, &stakes));
}
#[test]
fn test_check_vote_threshold_above_threshold() {
let mut tower = Tower::new_for_tests(1, 0.67);
let mut tower = Tower::new(EpochStakes::new_for_tests(2), 1, 0.67);
let stakes = vec![(
0,
StakeLockout {
@@ -683,12 +769,12 @@ mod test {
.into_iter()
.collect();
tower.record_vote(0, Hash::default());
assert!(tower.check_vote_stake_threshold(1, &stakes, 2));
assert!(tower.check_vote_stake_threshold(1, &stakes));
}
#[test]
fn test_check_vote_threshold_above_threshold_after_pop() {
let mut tower = Tower::new_for_tests(1, 0.67);
let mut tower = Tower::new(EpochStakes::new_for_tests(2), 1, 0.67);
let stakes = vec![(
0,
StakeLockout {
@@ -701,15 +787,15 @@ mod test {
tower.record_vote(0, Hash::default());
tower.record_vote(1, Hash::default());
tower.record_vote(2, Hash::default());
assert!(tower.check_vote_stake_threshold(6, &stakes, 2));
assert!(tower.check_vote_stake_threshold(6, &stakes));
}
#[test]
fn test_check_vote_threshold_above_threshold_no_stake() {
let mut tower = Tower::new_for_tests(1, 0.67);
let mut tower = Tower::new(EpochStakes::new_for_tests(2), 1, 0.67);
let stakes = HashMap::new();
tower.record_vote(0, Hash::default());
assert!(!tower.check_vote_stake_threshold(1, &stakes, 2));
assert!(!tower.check_vote_stake_threshold(1, &stakes));
}
#[test]
@@ -789,7 +875,9 @@ mod test {
]);
// Initialize tower
let mut tower = Tower::new_for_tests(VOTE_THRESHOLD_DEPTH, threshold_size);
let stakes: HashMap<_, _> = accounts.iter().map(|(pk, (s, _))| (*pk, *s)).collect();
let epoch_stakes = EpochStakes::new(0, stakes, &Pubkey::default());
let mut tower = Tower::new(epoch_stakes, VOTE_THRESHOLD_DEPTH, threshold_size);
// CASE 1: Record the first VOTE_THRESHOLD tower votes for fork 2. We want to
// evaluate a vote on slot VOTE_THRESHOLD_DEPTH. The nth most recent vote should be
@@ -799,25 +887,21 @@ mod test {
for vote in &tower_votes {
tower.record_vote(*vote, Hash::default());
}
let (staked_lockouts, total_staked) =
let staked_lockouts =
tower.collect_vote_lockouts(vote_to_evaluate, accounts.clone().into_iter(), &ancestors);
assert!(tower.check_vote_stake_threshold(vote_to_evaluate, &staked_lockouts, total_staked));
assert!(tower.check_vote_stake_threshold(vote_to_evaluate, &staked_lockouts));
// CASE 2: Now we want to evaluate a vote for slot VOTE_THRESHOLD_DEPTH + 1. This slot
// will expire the vote in one of the vote accounts, so we should have insufficient
// stake to pass the threshold
let vote_to_evaluate = VOTE_THRESHOLD_DEPTH as u64 + 1;
let (staked_lockouts, total_staked) =
let staked_lockouts =
tower.collect_vote_lockouts(vote_to_evaluate, accounts.into_iter(), &ancestors);
assert!(!tower.check_vote_stake_threshold(
vote_to_evaluate,
&staked_lockouts,
total_staked
));
assert!(!tower.check_vote_stake_threshold(vote_to_evaluate, &staked_lockouts));
}
fn vote_and_check_recent(num_votes: usize) {
let mut tower = Tower::new_for_tests(1, 0.67);
let mut tower = Tower::new(EpochStakes::new_for_tests(2), 1, 0.67);
let start = num_votes.saturating_sub(MAX_RECENT_VOTES);
let expected: Vec<_> = (start..num_votes)
.map(|i| Vote::new(i as u64, Hash::default()))

View File

@@ -20,12 +20,10 @@ pub struct ContactInfo {
pub gossip: SocketAddr,
/// address to connect to for replication
pub tvu: SocketAddr,
/// address to forward blobs to
pub tvu_forwards: SocketAddr,
/// transactions address
pub tpu: SocketAddr,
/// address to forward unprocessed transactions to
pub tpu_forwards: SocketAddr,
pub tpu_via_blobs: SocketAddr,
/// storage data address
pub storage_addr: SocketAddr,
/// address to which to send JSON-RPC requests
@@ -79,9 +77,8 @@ impl Default for ContactInfo {
id: Pubkey::default(),
gossip: socketaddr_any!(),
tvu: socketaddr_any!(),
tvu_forwards: socketaddr_any!(),
tpu: socketaddr_any!(),
tpu_forwards: socketaddr_any!(),
tpu_via_blobs: socketaddr_any!(),
storage_addr: socketaddr_any!(),
rpc: socketaddr_any!(),
rpc_pubsub: socketaddr_any!(),
@@ -92,14 +89,12 @@ impl Default for ContactInfo {
}
impl ContactInfo {
#[allow(clippy::too_many_arguments)]
pub fn new(
id: &Pubkey,
gossip: SocketAddr,
tvu: SocketAddr,
tvu_forwards: SocketAddr,
tpu: SocketAddr,
tpu_forwards: SocketAddr,
tpu_via_blobs: SocketAddr,
storage_addr: SocketAddr,
rpc: SocketAddr,
rpc_pubsub: SocketAddr,
@@ -110,9 +105,8 @@ impl ContactInfo {
signature: Signature::default(),
gossip,
tvu,
tvu_forwards,
tpu,
tpu_forwards,
tpu_via_blobs,
storage_addr,
rpc,
rpc_pubsub,
@@ -130,7 +124,6 @@ impl ContactInfo {
socketaddr!("127.0.0.1:1238"),
socketaddr!("127.0.0.1:1239"),
socketaddr!("127.0.0.1:1240"),
socketaddr!("127.0.0.1:1241"),
now,
)
}
@@ -149,7 +142,6 @@ impl ContactInfo {
addr,
addr,
addr,
addr,
0,
)
}
@@ -165,17 +157,15 @@ impl ContactInfo {
let tpu_addr = *bind_addr;
let gossip_addr = next_port(&bind_addr, 1);
let tvu_addr = next_port(&bind_addr, 2);
let tpu_forwards_addr = next_port(&bind_addr, 3);
let tvu_forwards_addr = next_port(&bind_addr, 4);
let tpu_via_blobs_addr = next_port(&bind_addr, 3);
let rpc_addr = SocketAddr::new(bind_addr.ip(), rpc_port::DEFAULT_RPC_PORT);
let rpc_pubsub_addr = SocketAddr::new(bind_addr.ip(), rpc_port::DEFAULT_RPC_PUBSUB_PORT);
Self::new(
pubkey,
gossip_addr,
tvu_addr,
tvu_forwards_addr,
tpu_addr,
tpu_forwards_addr,
tpu_via_blobs_addr,
"0.0.0.0:0".parse().unwrap(),
rpc_addr,
rpc_pubsub_addr,
@@ -201,7 +191,6 @@ impl ContactInfo {
daddr,
daddr,
daddr,
daddr,
timestamp(),
)
}
@@ -244,7 +233,7 @@ impl Signable for ContactInfo {
gossip: SocketAddr,
tvu: SocketAddr,
tpu: SocketAddr,
tpu_forwards: SocketAddr,
tpu_via_blobs: SocketAddr,
storage_addr: SocketAddr,
rpc: SocketAddr,
rpc_pubsub: SocketAddr,
@@ -258,7 +247,7 @@ impl Signable for ContactInfo {
tvu: me.tvu,
tpu: me.tpu,
storage_addr: me.storage_addr,
tpu_forwards: me.tpu_forwards,
tpu_via_blobs: me.tpu_via_blobs,
rpc: me.rpc,
rpc_pubsub: me.rpc_pubsub,
wallclock: me.wallclock,
@@ -298,7 +287,7 @@ mod tests {
let ci = ContactInfo::default();
assert!(ci.gossip.ip().is_unspecified());
assert!(ci.tvu.ip().is_unspecified());
assert!(ci.tpu_forwards.ip().is_unspecified());
assert!(ci.tpu_via_blobs.ip().is_unspecified());
assert!(ci.rpc.ip().is_unspecified());
assert!(ci.rpc_pubsub.ip().is_unspecified());
assert!(ci.tpu.ip().is_unspecified());
@@ -309,7 +298,7 @@ mod tests {
let ci = ContactInfo::new_multicast();
assert!(ci.gossip.ip().is_multicast());
assert!(ci.tvu.ip().is_multicast());
assert!(ci.tpu_forwards.ip().is_multicast());
assert!(ci.tpu_via_blobs.ip().is_multicast());
assert!(ci.rpc.ip().is_multicast());
assert!(ci.rpc_pubsub.ip().is_multicast());
assert!(ci.tpu.ip().is_multicast());
@@ -321,7 +310,7 @@ mod tests {
let ci = ContactInfo::new_gossip_entry_point(&addr);
assert_eq!(ci.gossip, addr);
assert!(ci.tvu.ip().is_unspecified());
assert!(ci.tpu_forwards.ip().is_unspecified());
assert!(ci.tpu_via_blobs.ip().is_unspecified());
assert!(ci.rpc.ip().is_unspecified());
assert!(ci.rpc_pubsub.ip().is_unspecified());
assert!(ci.tpu.ip().is_unspecified());
@@ -334,7 +323,7 @@ mod tests {
assert_eq!(ci.tpu, addr);
assert_eq!(ci.gossip.port(), 11);
assert_eq!(ci.tvu.port(), 12);
assert_eq!(ci.tpu_forwards.port(), 13);
assert_eq!(ci.tpu_via_blobs.port(), 13);
assert_eq!(ci.rpc.port(), 8899);
assert_eq!(ci.rpc_pubsub.port(), 8900);
assert!(ci.storage_addr.ip().is_unspecified());
@@ -349,7 +338,7 @@ mod tests {
assert_eq!(d1.id, keypair.pubkey());
assert_eq!(d1.gossip, socketaddr!("127.0.0.1:1235"));
assert_eq!(d1.tvu, socketaddr!("127.0.0.1:1236"));
assert_eq!(d1.tpu_forwards, socketaddr!("127.0.0.1:1237"));
assert_eq!(d1.tpu_via_blobs, socketaddr!("127.0.0.1:1237"));
assert_eq!(d1.tpu, socketaddr!("127.0.0.1:1234"));
assert_eq!(d1.rpc, socketaddr!("127.0.0.1:8899"));
assert_eq!(d1.rpc_pubsub, socketaddr!("127.0.0.1:8900"));

View File

@@ -5,15 +5,17 @@
use crate::crds::{Crds, VersionedCrdsValue};
use crate::crds_gossip_error::CrdsGossipError;
use crate::crds_gossip_pull::{CrdsFilter, CrdsGossipPull};
use crate::crds_gossip_pull::CrdsGossipPull;
use crate::crds_gossip_push::{CrdsGossipPush, CRDS_GOSSIP_NUM_ACTIVE};
use crate::crds_value::{CrdsValue, CrdsValueLabel};
use solana_runtime::bloom::Bloom;
use solana_sdk::hash::Hash;
use solana_sdk::pubkey::Pubkey;
use solana_sdk::signature::Signable;
use std::collections::{HashMap, HashSet};
///The min size for bloom filters
pub const CRDS_GOSSIP_DEFAULT_BLOOM_ITEMS: usize = 500;
pub const CRDS_GOSSIP_BLOOM_SIZE: usize = 1000;
#[derive(Clone)]
pub struct CrdsGossip {
@@ -131,10 +133,9 @@ impl CrdsGossip {
&self,
now: u64,
stakes: &HashMap<Pubkey, u64>,
bloom_size: usize,
) -> Result<(Pubkey, Vec<CrdsFilter>, CrdsValue), CrdsGossipError> {
) -> Result<(Pubkey, Bloom<Hash>, CrdsValue), CrdsGossipError> {
self.pull
.new_pull_request(&self.crds, &self.id, now, stakes, bloom_size)
.new_pull_request(&self.crds, &self.id, now, stakes)
}
/// time when a request to `from` was initiated
@@ -145,13 +146,14 @@ impl CrdsGossip {
self.pull.mark_pull_request_creation_time(from, now)
}
/// process a pull request and create a response
pub fn process_pull_requests(
pub fn process_pull_request(
&mut self,
filters: Vec<(CrdsValue, CrdsFilter)>,
caller: CrdsValue,
filter: Bloom<Hash>,
now: u64,
) -> Vec<Vec<CrdsValue>> {
) -> Vec<CrdsValue> {
self.pull
.process_pull_requests(&mut self.crds, filters, now)
.process_pull_request(&mut self.crds, caller, filter, now)
}
/// process a pull response
pub fn process_pull_response(

View File

@@ -11,12 +11,13 @@
use crate::contact_info::ContactInfo;
use crate::crds::Crds;
use crate::crds_gossip::{get_stake, get_weight, CRDS_GOSSIP_DEFAULT_BLOOM_ITEMS};
use crate::crds_gossip::{get_stake, get_weight, CRDS_GOSSIP_BLOOM_SIZE};
use crate::crds_gossip_error::CrdsGossipError;
use crate::crds_value::{CrdsValue, CrdsValueLabel};
use crate::packet::BLOB_DATA_SIZE;
use bincode::serialized_size;
use rand;
use rand::distributions::{Distribution, WeightedIndex};
use rand::Rng;
use solana_runtime::bloom::Bloom;
use solana_sdk::hash::Hash;
use solana_sdk::pubkey::Pubkey;
@@ -25,90 +26,6 @@ use std::collections::HashMap;
use std::collections::VecDeque;
pub const CRDS_GOSSIP_PULL_CRDS_TIMEOUT_MS: u64 = 15000;
pub const FALSE_RATE: f64 = 0.1f64;
pub const KEYS: f64 = 8f64;
#[derive(Serialize, Deserialize, Default, Clone, Debug, PartialEq)]
pub struct CrdsFilter {
pub filter: Bloom<Hash>,
mask: u64,
mask_bits: u32,
}
impl CrdsFilter {
pub fn new_rand(num_items: usize, max_bytes: usize) -> Self {
let max_bits = (max_bytes * 8) as f64;
let max_items = Self::max_items(max_bits, FALSE_RATE, KEYS);
let mask_bits = Self::mask_bits(num_items as f64, max_items as f64);
let filter = Bloom::random(max_items as usize, FALSE_RATE, max_bits as usize);
let seed: u64 = rand::thread_rng().gen_range(0, 2u64.pow(mask_bits));
let mask = Self::compute_mask(seed, mask_bits);
CrdsFilter {
filter,
mask,
mask_bits,
}
}
// generates a vec of filters that together hold a complete set of Hashes
pub fn new_complete_set(num_items: usize, max_bytes: usize) -> Vec<Self> {
let max_bits = (max_bytes * 8) as f64;
let max_items = Self::max_items(max_bits, FALSE_RATE, KEYS);
let mask_bits = Self::mask_bits(num_items as f64, max_items as f64);
// for each possible mask combination, generate a new filter.
let mut filters = vec![];
for seed in 0..2u64.pow(mask_bits) {
let filter = Bloom::random(max_items as usize, FALSE_RATE, max_bits as usize);
let mask = Self::compute_mask(seed, mask_bits);
let filter = CrdsFilter {
filter,
mask,
mask_bits,
};
filters.push(filter)
}
filters
}
fn compute_mask(seed: u64, mask_bits: u32) -> u64 {
assert!(seed <= 2u64.pow(mask_bits));
let seed: u64 = seed.checked_shl(64 - mask_bits).unwrap_or(0x0);
seed | (!0u64).checked_shr(mask_bits).unwrap_or(!0x0) as u64
}
pub fn max_items(max_bits: f64, false_rate: f64, num_keys: f64) -> f64 {
let m = max_bits;
let p = false_rate;
let k = num_keys;
(m / (-k / (1f64 - (p.ln() / k).exp()).ln())).ceil()
}
fn mask_bits(num_items: f64, max_items: f64) -> u32 {
// for small ratios this can result in a negative number, ensure it returns 0 instead
((num_items / max_items).log2().ceil()).max(0.0) as u32
}
fn hash_as_u64(item: &Hash) -> u64 {
let arr = item.as_ref();
let mut accum = 0;
for (i, val) in arr.iter().enumerate().take(8) {
accum |= (u64::from(*val)) << (i * 8) as u64;
}
accum
}
pub fn test_mask(&self, item: &Hash) -> bool {
// only consider the highest mask_bits bits from the hash and set the rest to 1.
let ones = (!0u64).checked_shr(self.mask_bits).unwrap_or(!0u64);
let bits = Self::hash_as_u64(item) | ones;
bits == self.mask
}
pub fn add(&mut self, item: &Hash) {
if self.test_mask(item) {
self.filter.add(item);
}
}
pub fn contains(&self, item: &Hash) -> bool {
if !self.test_mask(item) {
return true;
}
self.filter.contains(item)
}
}
#[derive(Clone)]
pub struct CrdsGossipPull {
@@ -116,6 +33,8 @@ pub struct CrdsGossipPull {
pub pull_request_time: HashMap<Pubkey, u64>,
/// hash and insert time
purged_values: VecDeque<(Hash, u64)>,
/// max bytes per message
pub max_bytes: usize,
pub crds_timeout: u64,
}
@@ -124,6 +43,7 @@ impl Default for CrdsGossipPull {
Self {
purged_values: VecDeque::new(),
pull_request_time: HashMap::new(),
max_bytes: BLOB_DATA_SIZE,
crds_timeout: CRDS_GOSSIP_PULL_CRDS_TIMEOUT_MS,
}
}
@@ -136,19 +56,18 @@ impl CrdsGossipPull {
self_id: &Pubkey,
now: u64,
stakes: &HashMap<Pubkey, u64>,
bloom_size: usize,
) -> Result<(Pubkey, Vec<CrdsFilter>, CrdsValue), CrdsGossipError> {
) -> Result<(Pubkey, Bloom<Hash>, CrdsValue), CrdsGossipError> {
let options = self.pull_options(crds, &self_id, now, stakes);
if options.is_empty() {
return Err(CrdsGossipError::NoPeers);
}
let filters = self.build_crds_filters(crds, bloom_size);
let filter = self.build_crds_filter(crds);
let index = WeightedIndex::new(options.iter().map(|weighted| weighted.0)).unwrap();
let random = index.sample(&mut rand::thread_rng());
let self_info = crds
.lookup(&CrdsValueLabel::ContactInfo(*self_id))
.unwrap_or_else(|| panic!("self_id invalid {}", self_id));
Ok((options[random].1.id, filters, self_info.clone()))
Ok((options[random].1.id, filter, self_info.clone()))
}
fn pull_options<'a>(
@@ -187,22 +106,21 @@ impl CrdsGossipPull {
}
/// process a pull request and create a response
pub fn process_pull_requests(
pub fn process_pull_request(
&mut self,
crds: &mut Crds,
requests: Vec<(CrdsValue, CrdsFilter)>,
caller: CrdsValue,
mut filter: Bloom<Hash>,
now: u64,
) -> Vec<Vec<CrdsValue>> {
let rv = self.filter_crds_values(crds, &requests);
requests.into_iter().for_each(|(caller, _)| {
let key = caller.label().pubkey();
let old = crds.insert(caller, now);
if let Some(val) = old.ok().and_then(|opt| opt) {
self.purged_values
.push_back((val.value_hash, val.local_timestamp));
}
crds.update_record_timestamp(&key, now);
});
) -> Vec<CrdsValue> {
let rv = self.filter_crds_values(crds, &mut filter);
let key = caller.label().pubkey();
let old = crds.insert(caller, now);
if let Some(val) = old.ok().and_then(|opt| opt) {
self.purged_values
.push_back((val.value_hash, val.local_timestamp))
}
crds.update_record_timestamp(&key, now);
rv
}
/// process a pull response
@@ -229,37 +147,34 @@ impl CrdsGossipPull {
crds.update_record_timestamp(from, now);
failed
}
// build a set of filters of the current crds table
// num_filters - used to increase the likely hood of a value in crds being added to some filter
pub fn build_crds_filters(&self, crds: &Crds, bloom_size: usize) -> Vec<CrdsFilter> {
/// build a filter of the current crds table
pub fn build_crds_filter(&self, crds: &Crds) -> Bloom<Hash> {
let num = cmp::max(
CRDS_GOSSIP_DEFAULT_BLOOM_ITEMS,
CRDS_GOSSIP_BLOOM_SIZE,
crds.table.values().count() + self.purged_values.len(),
);
let mut filters = CrdsFilter::new_complete_set(num, bloom_size);
let mut bloom = Bloom::random(num, 0.1, 4 * 1024 * 8 - 1);
for v in crds.table.values() {
filters
.iter_mut()
.for_each(|filter| filter.add(&v.value_hash));
bloom.add(&v.value_hash);
}
for (value_hash, _insert_timestamp) in &self.purged_values {
filters.iter_mut().for_each(|filter| filter.add(value_hash));
bloom.add(value_hash);
}
filters
bloom
}
/// filter values that fail the bloom filter up to max_bytes
fn filter_crds_values(
&self,
crds: &Crds,
filters: &[(CrdsValue, CrdsFilter)],
) -> Vec<Vec<CrdsValue>> {
let mut ret = vec![vec![]; filters.len()];
fn filter_crds_values(&self, crds: &Crds, filter: &mut Bloom<Hash>) -> Vec<CrdsValue> {
let mut max_bytes = self.max_bytes as isize;
let mut ret = vec![];
for v in crds.table.values() {
filters.iter().enumerate().for_each(|(i, (_, filter))| {
if !filter.contains(&v.value_hash) {
ret[i].push(v.value.clone());
}
});
if filter.contains(&v.value_hash) {
continue;
}
max_bytes -= serialized_size(&v.value).unwrap() as isize;
if max_bytes < 0 {
break;
}
ret.push(v.value.clone());
}
ret
}
@@ -294,9 +209,6 @@ impl CrdsGossipPull {
mod test {
use super::*;
use crate::contact_info::ContactInfo;
use itertools::Itertools;
use solana_sdk::hash::hash;
use solana_sdk::packet::PACKET_DATA_SIZE;
#[test]
fn test_new_pull_with_stakes() {
@@ -329,19 +241,19 @@ mod test {
let id = entry.label().pubkey();
let node = CrdsGossipPull::default();
assert_eq!(
node.new_pull_request(&crds, &id, 0, &HashMap::new(), PACKET_DATA_SIZE),
node.new_pull_request(&crds, &id, 0, &HashMap::new()),
Err(CrdsGossipError::NoPeers)
);
crds.insert(entry.clone(), 0).unwrap();
assert_eq!(
node.new_pull_request(&crds, &id, 0, &HashMap::new(), PACKET_DATA_SIZE),
node.new_pull_request(&crds, &id, 0, &HashMap::new()),
Err(CrdsGossipError::NoPeers)
);
let new = CrdsValue::ContactInfo(ContactInfo::new_localhost(&Pubkey::new_rand(), 0));
crds.insert(new.clone(), 0).unwrap();
let req = node.new_pull_request(&crds, &id, 0, &HashMap::new(), PACKET_DATA_SIZE);
let req = node.new_pull_request(&crds, &id, 0, &HashMap::new());
let (to, _, self_info) = req.unwrap();
assert_eq!(to, new.label().pubkey());
assert_eq!(self_info, entry);
@@ -364,13 +276,7 @@ mod test {
// odds of getting the other request should be 1 in u64::max_value()
for _ in 0..10 {
let req = node.new_pull_request(
&crds,
&node_pubkey,
u64::max_value(),
&HashMap::new(),
PACKET_DATA_SIZE,
);
let req = node.new_pull_request(&crds, &node_pubkey, u64::max_value(), &HashMap::new());
let (to, _, self_info) = req.unwrap();
assert_eq!(to, old.label().pubkey());
assert_eq!(self_info, entry);
@@ -386,20 +292,13 @@ mod test {
node_crds.insert(entry.clone(), 0).unwrap();
let new = CrdsValue::ContactInfo(ContactInfo::new_localhost(&Pubkey::new_rand(), 0));
node_crds.insert(new.clone(), 0).unwrap();
let req = node.new_pull_request(
&node_crds,
&node_pubkey,
0,
&HashMap::new(),
PACKET_DATA_SIZE,
);
let req = node.new_pull_request(&node_crds, &node_pubkey, 0, &HashMap::new());
let mut dest_crds = Crds::default();
let mut dest = CrdsGossipPull::default();
let (_, filters, caller) = req.unwrap();
let filters = filters.into_iter().map(|f| (caller.clone(), f)).collect();
let rsp = dest.process_pull_requests(&mut dest_crds, filters, 1);
assert!(rsp.iter().all(|rsp| rsp.is_empty()));
let (_, filter, caller) = req.unwrap();
let rsp = dest.process_pull_request(&mut dest_crds, caller.clone(), filter, 1);
assert!(rsp.is_empty());
assert!(dest_crds.lookup(&caller.label()).is_some());
assert_eq!(
dest_crds
@@ -448,28 +347,17 @@ mod test {
let mut done = false;
for _ in 0..30 {
// there is a chance of a false positive with bloom filters
let req = node.new_pull_request(
&node_crds,
&node_pubkey,
0,
&HashMap::new(),
PACKET_DATA_SIZE,
);
let (_, filters, caller) = req.unwrap();
let filters = filters.into_iter().map(|f| (caller.clone(), f)).collect();
let mut rsp = dest.process_pull_requests(&mut dest_crds, filters, 0);
let req = node.new_pull_request(&node_crds, &node_pubkey, 0, &HashMap::new());
let (_, filter, caller) = req.unwrap();
let rsp = dest.process_pull_request(&mut dest_crds, caller, filter, 0);
// if there is a false positive this is empty
// prob should be around 0.1 per iteration
if rsp.is_empty() {
continue;
}
if rsp.is_empty() {
continue;
}
assert_eq!(rsp.len(), 1);
let failed =
node.process_pull_response(&mut node_crds, &node_pubkey, rsp.pop().unwrap(), 1);
let failed = node.process_pull_response(&mut node_crds, &node_pubkey, rsp, 1);
assert_eq!(failed, 0);
assert_eq!(
node_crds
@@ -518,88 +406,12 @@ mod test {
// there is a chance of a false positive with bloom filters
// assert that purged value is still in the set
// chance of 30 consecutive false positives is 0.1^30
let filters = node.build_crds_filters(&node_crds, PACKET_DATA_SIZE);
assert!(filters.iter().any(|filter| filter.contains(&value_hash)));
let filter = node.build_crds_filter(&node_crds);
assert!(filter.contains(&value_hash));
}
// purge the value
node.purge_purged(1);
assert_eq!(node.purged_values.len(), 0);
}
#[test]
fn test_crds_filter_mask() {
let filter = CrdsFilter::new_rand(1, 128);
assert_eq!(filter.mask, !0x0);
assert_eq!(CrdsFilter::max_items(80f64, 0.01, 8f64), 9f64);
//1000/9 = 111, so 7 bits are needed to mask it
assert_eq!(CrdsFilter::mask_bits(1000f64, 9f64), 7u32);
let filter = CrdsFilter::new_rand(1000, 10);
assert_eq!(filter.mask & 0x00ffffffff, 0x00ffffffff);
}
#[test]
fn test_crds_filter_add_no_mask() {
let mut filter = CrdsFilter::new_rand(1, 128);
let h: Hash = hash(Hash::default().as_ref());
assert!(!filter.contains(&h));
filter.add(&h);
assert!(filter.contains(&h));
let h: Hash = hash(h.as_ref());
assert!(!filter.contains(&h));
}
#[test]
fn test_crds_filter_add_mask() {
let mut filter = CrdsFilter::new_rand(1000, 10);
let mut h: Hash = Hash::default();
while !filter.test_mask(&h) {
h = hash(h.as_ref());
}
assert!(filter.test_mask(&h));
//if the mask succeeds, we want the guaranteed negative
assert!(!filter.contains(&h));
filter.add(&h);
assert!(filter.contains(&h));
}
#[test]
fn test_crds_filter_complete_set_add_mask() {
let mut filters = CrdsFilter::new_complete_set(1000, 10);
assert!(filters.iter().all(|f| f.mask_bits > 0));
let mut h: Hash = Hash::default();
// rev to make the hash::default() miss on the first few test_masks
while !filters.iter().rev().any(|f| f.test_mask(&h)) {
h = hash(h.as_ref());
}
let filter = filters.iter_mut().find(|f| f.test_mask(&h)).unwrap();
assert!(filter.test_mask(&h));
//if the mask succeeds, we want the guaranteed negative
assert!(!filter.contains(&h));
filter.add(&h);
assert!(filter.contains(&h));
}
#[test]
fn test_crds_filter_contains_mask() {
let filter = CrdsFilter::new_rand(1000, 10);
assert!(filter.mask_bits > 0);
let mut h: Hash = Hash::default();
while filter.test_mask(&h) {
h = hash(h.as_ref());
}
assert!(!filter.test_mask(&h));
//if the mask fails, the hash is contained in the set, and can be treated as a false
//positive
assert!(filter.contains(&h));
}
#[test]
fn test_mask() {
for i in 0..16 {
run_test_mask(i);
}
}
fn run_test_mask(mask_bits: u32) {
let masks: Vec<_> = (0..2u64.pow(mask_bits))
.into_iter()
.map(|seed| CrdsFilter::compute_mask(seed, mask_bits))
.dedup()
.collect();
assert_eq!(masks.len(), 2u64.pow(mask_bits) as usize)
}
}

View File

@@ -10,7 +10,7 @@
use crate::contact_info::ContactInfo;
use crate::crds::{Crds, VersionedCrdsValue};
use crate::crds_gossip::{get_stake, get_weight, CRDS_GOSSIP_DEFAULT_BLOOM_ITEMS};
use crate::crds_gossip::{get_stake, get_weight, CRDS_GOSSIP_BLOOM_SIZE};
use crate::crds_gossip_error::CrdsGossipError;
use crate::crds_value::{CrdsValue, CrdsValueLabel};
use crate::packet::BLOB_DATA_SIZE;
@@ -258,7 +258,7 @@ impl CrdsGossipPush {
if new_items.get(&item.id).is_some() {
continue;
}
let size = cmp::max(CRDS_GOSSIP_DEFAULT_BLOOM_ITEMS, network_size);
let size = cmp::max(CRDS_GOSSIP_BLOOM_SIZE, network_size);
let mut bloom = Bloom::random(size, 0.1, 1024 * 8 * 4);
bloom.add(&item.id);
new_items.insert(item.id, bloom);

View File

@@ -1,5 +1,5 @@
use crate::contact_info::ContactInfo;
use bincode::{serialize, serialized_size};
use bincode::serialize;
use solana_sdk::pubkey::Pubkey;
use solana_sdk::signature::{Keypair, Signable, Signature};
use solana_sdk::transaction::Transaction;
@@ -8,7 +8,6 @@ use std::collections::BTreeSet;
use std::fmt;
/// CrdsValue that is replicated across the cluster
#[allow(clippy::large_enum_variant)]
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub enum CrdsValue {
/// * Merge Strategy - Latest wallclock is picked
@@ -190,11 +189,6 @@ impl CrdsValue {
CrdsValueLabel::EpochSlots(*key),
]
}
/// Returns the size (in bytes) of a CrdsValue
pub fn size(&self) -> u64 {
serialized_size(&self).expect("unable to serialize contact info")
}
}
impl Signable for CrdsValue {

View File

@@ -334,7 +334,6 @@ pub mod test {
use solana_sdk::signature::Signable;
use solana_sdk::signature::{Keypair, KeypairUtil};
use std::borrow::Borrow;
use std::path::Path;
/// Specifies the contents of a 16-data-blob and 4-coding-blob erasure set
/// Exists to be passed to `generate_blocktree_with_coding`
@@ -544,7 +543,6 @@ pub mod test {
}
#[test]
#[ignore]
fn test_erasure_generate_blocktree_with_coding() {
let cases = vec![
(NUM_DATA, NUM_CODING, 7, 5),
@@ -581,7 +579,7 @@ pub mod test {
);
for idx in start_index..data_end {
let opt_bytes = blocktree.get_data_shred_bytes(slot, idx).unwrap();
let opt_bytes = blocktree.get_data_blob_bytes(slot, idx).unwrap();
assert!(opt_bytes.is_some());
}
@@ -750,7 +748,7 @@ pub mod test {
/// Genarates a ledger according to the given specs.
/// Blocktree should have correct SlotMeta and ErasureMeta and so on but will not have done any
/// possible recovery.
pub fn generate_blocktree_with_coding(ledger_path: &Path, specs: &[SlotSpec]) -> Blocktree {
pub fn generate_blocktree_with_coding(ledger_path: &str, specs: &[SlotSpec]) -> Blocktree {
let blocktree = Blocktree::open(ledger_path).unwrap();
let model = generate_ledger_model(specs);

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