Compare commits
454 Commits
document-r
...
v1.7.14
Author | SHA1 | Date | |
---|---|---|---|
5bdb824267 | |||
474f2bcdf4 | |||
2302211963 | |||
8178db52a5 | |||
5d8429d953 | |||
fec15f69f4 | |||
257ddbeee1 | |||
47c1730808 | |||
a005a6b816 | |||
2f2948f998 | |||
55ccff7604 | |||
1bf88556ee | |||
4c4f183515 | |||
282322cbe8 | |||
2dc00d0e13 | |||
a90c338982 | |||
36c283026f | |||
a1a0c63862 | |||
e20fdde0a4 | |||
5b52ac8990 | |||
502ae8b319 | |||
30f0b3cf53 | |||
2975dc5c1a | |||
d68377e927 | |||
cc1a3d6645 | |||
e9a993fb59 | |||
88177d33fd | |||
0ec301f1c3 | |||
34665571fa | |||
5dd1c2191e | |||
aacb5e58ad | |||
8d2dce6f6b | |||
e50a26a493 | |||
302887da67 | |||
597c504c27 | |||
b7af118091 | |||
9e392687eb | |||
bee923ca6c | |||
879df38059 | |||
3e776267b5 | |||
0bfb466184 | |||
71bb3bf6aa | |||
99c74c8902 | |||
085f5f945d | |||
e757e51ddc | |||
458ccecb5d | |||
7f21a55a32 | |||
b112e4a8aa | |||
63b24d9577 | |||
49a6acffca | |||
7b4638aa0b | |||
f51ee23837 | |||
55f27d5c26 | |||
f33c651114 | |||
a71ebcc9f3 | |||
cd575945b6 | |||
6b24dd1c6a | |||
03da3eaa81 | |||
07b71329a7 | |||
53b387f113 | |||
d0143dad8f | |||
3e870a40f2 | |||
707302d9f2 | |||
b8ab6a46a8 | |||
9ad801a52c | |||
c85816c44e | |||
70d556782b | |||
ca83167cfc | |||
54ad080bf2 | |||
80e239550b | |||
992b313941 | |||
97bd521725 | |||
3ae2917603 | |||
e51c2d1a84 | |||
a24b0dc81c | |||
6e856cd468 | |||
0dde54b95b | |||
1fa863e4b2 | |||
73e97aab5b | |||
8f082a239a | |||
7e5026bde2 | |||
7ac0ea0885 | |||
dc06d3dee5 | |||
0b1aadf446 | |||
b9a0156a93 | |||
f786d1d0f3 | |||
b360c90d21 | |||
da801b753b | |||
8f168a610e | |||
d7e6ab58c4 | |||
a0e6a7c73b | |||
ed4e7c0c87 | |||
584f8deae3 | |||
e9c3f11d24 | |||
57e87a09c0 | |||
a2b435916d | |||
ad66189463 | |||
9a280426da | |||
864006a78a | |||
e37e46cfc2 | |||
376b21c6e7 | |||
fcda5d4a7d | |||
2e4a2c15be | |||
a8ec380c56 | |||
afb87a386a | |||
2d060fd2d9 | |||
c180f4c84e | |||
105a89175c | |||
959334d2e3 | |||
aa2098d115 | |||
37ee47c3e6 | |||
4fb43bbd90 | |||
5fbcb10e6f | |||
6f86abf551 | |||
744a69f818 | |||
1c6cac2054 | |||
0c64bd0938 | |||
f73a61d2ec | |||
bdb77b0c98 | |||
58bef3a94b | |||
52dfb4a09c | |||
c734db59cb | |||
3b36e8e285 | |||
cdbc77bf97 | |||
8ab358ce78 | |||
5193ba2062 | |||
7e5767f926 | |||
e5e6829d20 | |||
3976816b79 | |||
9732157f0c | |||
ed18d6d38d | |||
ef336a44a1 | |||
de5f503a76 | |||
b5b1ed2a55 | |||
caea9c99cd | |||
1495b94f7a | |||
f55bb78307 | |||
fa5a71dbf0 | |||
80a6479c47 | |||
a492de1bea | |||
43b414b0df | |||
6f31882260 | |||
5042808ecf | |||
1c9d0521ca | |||
ddda94e486 | |||
73ed9f56d7 | |||
ab5d032634 | |||
03b930515b | |||
c9f763ea6e | |||
422044f375 | |||
7584262f47 | |||
894f121d0e | |||
4b133509d9 | |||
e8040a828d | |||
78086329be | |||
6deeedd886 | |||
1a0146f21d | |||
7d0a9e0381 | |||
0f7b84197f | |||
bb06502d24 | |||
b7f1f19d8e | |||
0707290bbf | |||
8934a3961f | |||
b142ef5f8b | |||
f2dc9dd96e | |||
20ad3005b5 | |||
eacc69efba | |||
3c200ae45a | |||
74e7c7cbd0 | |||
6bd6d6212c | |||
4d2e66cf9c | |||
65fe00b7ad | |||
02c509390a | |||
49b0d1792b | |||
9511031490 | |||
04ee86e93c | |||
82f25f982e | |||
1cc32c9cda | |||
aedcab846c | |||
548ddff7ed | |||
7aced9e772 | |||
1cc8de0fed | |||
f08c7b2294 | |||
cc58d36de6 | |||
128393da54 | |||
e7964a0b89 | |||
77dc3746a3 | |||
e88f4d689c | |||
f1858c74a4 | |||
7427dafc36 | |||
1cdcabf7cf | |||
ea192b3c83 | |||
927057df26 | |||
19049ca91b | |||
3542348c1e | |||
df9061b933 | |||
98658ebed5 | |||
2a93147b1b | |||
4145c629c0 | |||
551dc0a74c | |||
85bbcdad9a | |||
336c1c1d37 | |||
1570afe493 | |||
c7c650fccc | |||
9b7fba69f4 | |||
0bd355c166 | |||
0d8c8d013d | |||
7a57f7b8cc | |||
940c4731c4 | |||
4332f0ca05 | |||
e0e6e20e02 | |||
0fdaa1438e | |||
8a111229f7 | |||
e3eb9c195a | |||
8dd5ec6fbd | |||
e5d60bc56d | |||
cba97d576a | |||
0670213365 | |||
ed5c11b3aa | |||
4b8c4704b0 | |||
c5374095e6 | |||
6abd5fbc3e | |||
a6a302f41f | |||
4c764829da | |||
9900c1ad8a | |||
3fabbab417 | |||
6c99c1ae13 | |||
c2320fceab | |||
8b87d86358 | |||
5d0e1d62d5 | |||
7e613c7a78 | |||
2a30436e45 | |||
5315a89e7d | |||
8c328316ae | |||
030a97d098 | |||
c40e71dc03 | |||
2f633cdfb7 | |||
edd6ae588a | |||
2a73ba2fbb | |||
5eadb86500 | |||
c98ab6c6dc | |||
5321463892 | |||
d668a7694f | |||
9bb482e46f | |||
c534c928a7 | |||
0d0478c4a4 | |||
cda681a2f0 | |||
72ed4f28b1 | |||
6afeaac7a5 | |||
75a2b66206 | |||
5966053a6d | |||
e500b79858 | |||
428c20c79f | |||
bf84bc17ea | |||
30fa9cbee7 | |||
eefca613ad | |||
f6d943aec7 | |||
88462e67b5 | |||
eb683dd402 | |||
c7d0aea5f4 | |||
8f08953100 | |||
09b009abd9 | |||
c37e481a43 | |||
72cf55b8c3 | |||
6cb24ae7b6 | |||
eeaf0234f0 | |||
0200740d70 | |||
1268eef3b2 | |||
b433048003 | |||
daf2c3c155 | |||
03d213d764 | |||
99f9481b5d | |||
10bd14bca6 | |||
d26533e370 | |||
820abacf49 | |||
4466aa39c4 | |||
f4e43731ef | |||
884ef211f7 | |||
896ef5a15f | |||
4ff482cd47 | |||
6e27360fbc | |||
a10a0824eb | |||
2fdda2ec1b | |||
57f76a2111 | |||
287daa9248 | |||
7e40a051a4 | |||
bd0a7730b6 | |||
0d1c87e650 | |||
56cd963fd7 | |||
d1b26cb267 | |||
6a0a03415c | |||
41d50adbf3 | |||
328b52c4d6 | |||
b7d04cf7b9 | |||
7ed2cf30a5 | |||
78fe5576a9 | |||
41179b1282 | |||
f82d99b4c2 | |||
967f0d07f2 | |||
940dbe99e9 | |||
98e1c68a70 | |||
d8e250e9b0 | |||
c8be8510ba | |||
c99460ba15 | |||
769fcb7f50 | |||
60812790e1 | |||
a01e44f3b9 | |||
0917370bd5 | |||
09b612b130 | |||
26fdf3fb07 | |||
f41f3f6b51 | |||
677664e71d | |||
a8071f1039 | |||
bc08351a0a | |||
088b3893c3 | |||
d9d6dd9ba6 | |||
aca66674d3 | |||
597429ab3e | |||
715360c1e7 | |||
fcabaa7eff | |||
b14af989b8 | |||
1a919e0c3e | |||
9c1a6bed7b | |||
c48ec02f42 | |||
6f376489a5 | |||
0cbf7bef1e | |||
0f87e598f6 | |||
363b75619f | |||
090c801cc6 | |||
b711778d4a | |||
d52569d66f | |||
c676b7a473 | |||
71c49bc8cd | |||
97a7f747fb | |||
cef9e0de0c | |||
5637acb799 | |||
3d3bdcb966 | |||
c9f02ae020 | |||
0e7512a225 | |||
c330016109 | |||
cb13cdec85 | |||
c65c580b20 | |||
d159ae9342 | |||
a540af1ca7 | |||
e9c234d89f | |||
b472dac6b3 | |||
523dac1be3 | |||
962a2126b5 | |||
5c495ad1b0 | |||
f633f34e43 | |||
bacf1b9acc | |||
4df9da5c48 | |||
30bbc1350d | |||
2f0f1fd5f5 | |||
c28e6ebc4c | |||
6479c11e9a | |||
4ed0fcdde6 | |||
296a8ade63 | |||
a84953ccfd | |||
8492031fd0 | |||
bff7259111 | |||
67e1814581 | |||
12e92dd59d | |||
4ee366edfa | |||
61573756f8 | |||
fe5fed1182 | |||
0c90307677 | |||
cdd2a51f1f | |||
0dbe3434f0 | |||
ef205593c5 | |||
8b5ba771ad | |||
991f99b239 | |||
d6f17517cb | |||
15b2f280e3 | |||
60b43a1ddf | |||
d6b83e3b0a | |||
0446f89d22 | |||
54bc3e606e | |||
fed90cfbe8 | |||
e2e41a29eb | |||
3b813db42f | |||
16b1a4d003 | |||
b51ea3ca0c | |||
dc76675644 | |||
274a238a00 | |||
10507f0ade | |||
af2a6106da | |||
120a7e433f | |||
98e34f07df | |||
738df79394 | |||
98e9b6e70b | |||
c9318f8dc2 | |||
78f3606e30 | |||
97f4d098e1 | |||
144a13b096 | |||
af0869c66c | |||
48e565038a | |||
cd6e1d921c | |||
fb767f4612 | |||
9f35db28e5 | |||
ab19543dff | |||
3d5f333a3b | |||
d06ca605cf | |||
334e11e4b9 | |||
fd68b4e7a8 | |||
e5ea16fad8 | |||
6d5a4b5cce | |||
ffb6b5a23b | |||
e247625025 | |||
67c07aa5d3 | |||
0de1ce0c2c | |||
59f4fba05c | |||
c0c764377c | |||
c9bc059637 | |||
78147d48e4 | |||
5e7db52087 | |||
ae42413d57 | |||
d433bd3d84 | |||
58dd6dc227 | |||
893df9b277 | |||
17dc13760b | |||
498bf911eb | |||
96de58b3a4 | |||
9b61fa24c7 | |||
e9be3cf6bc | |||
ea44a71914 | |||
a00fbbf5ca | |||
eb1a04af65 | |||
d1fbf77f3f | |||
04fbf73a29 | |||
db70eb3160 | |||
cd974c26b6 | |||
53e0f5d61c | |||
e864bf4898 | |||
81d12b7644 | |||
309fcd6270 | |||
938112e449 | |||
3e012ea69e | |||
975c942ea7 | |||
2798271da0 | |||
dc258cebab | |||
4b8c5194c7 | |||
3c7c6dacfb | |||
e36337a764 | |||
a49856b898 | |||
8ca2f52041 | |||
2f7f243022 | |||
7e443770d7 | |||
8ec09884b8 | |||
88c7e636d6 | |||
add3fd479d | |||
70410536b9 | |||
6f3f9b485c | |||
bd9ce3590d |
@ -38,6 +38,7 @@ jobs:
|
||||
- readlink -f .
|
||||
script:
|
||||
- source ci/env.sh
|
||||
- rustup set profile default
|
||||
- ci/publish-tarball.sh
|
||||
deploy:
|
||||
- provider: s3
|
||||
|
1979
Cargo.lock
generated
1979
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
11
Cargo.toml
11
Cargo.toml
@ -39,19 +39,20 @@ members = [
|
||||
"metrics",
|
||||
"net-shaper",
|
||||
"notifier",
|
||||
"poh",
|
||||
"poh-bench",
|
||||
"program-test",
|
||||
"programs/secp256k1",
|
||||
"programs/bpf_loader",
|
||||
"programs/budget",
|
||||
"programs/config",
|
||||
"programs/exchange",
|
||||
"programs/failure",
|
||||
"programs/noop",
|
||||
"programs/ownable",
|
||||
"programs/secp256k1",
|
||||
"programs/stake",
|
||||
"programs/vote",
|
||||
"remote-wallet",
|
||||
"rpc",
|
||||
"runtime",
|
||||
"runtime/store-tool",
|
||||
"sdk",
|
||||
@ -59,7 +60,6 @@ members = [
|
||||
"sdk/cargo-test-bpf",
|
||||
"scripts",
|
||||
"stake-accounts",
|
||||
"stake-monitor",
|
||||
"sys-tuner",
|
||||
"tokens",
|
||||
"transaction-status",
|
||||
@ -76,5 +76,10 @@ exclude = [
|
||||
"programs/bpf",
|
||||
]
|
||||
|
||||
# TODO: Remove once the "simd-accel" feature from the reed-solomon-erasure
|
||||
# dependency is supported on Apple M1. v2 of the feature resolver is needed to
|
||||
# specify arch-specific features.
|
||||
resolver = "2"
|
||||
|
||||
[profile.dev]
|
||||
split-debuginfo = "unpacked"
|
||||
|
@ -1,6 +1,6 @@
|
||||
<p align="center">
|
||||
<a href="https://solana.com">
|
||||
<img alt="Solana" src="https://i.imgur.com/uBVzyX3.png" width="250" />
|
||||
<img alt="Solana" src="https://i.imgur.com/IKyzQ6T.png" width="250" />
|
||||
</a>
|
||||
</p>
|
||||
|
||||
@ -19,7 +19,7 @@ $ source $HOME/.cargo/env
|
||||
$ rustup component add rustfmt
|
||||
```
|
||||
|
||||
Please sure you are always using the latest stable rust version by running:
|
||||
Please make sure you are always using the latest stable rust version by running:
|
||||
|
||||
```bash
|
||||
$ rustup update
|
||||
@ -51,11 +51,6 @@ $ cd solana
|
||||
$ cargo build
|
||||
```
|
||||
|
||||
## **4. Run a minimal local cluster.**
|
||||
```bash
|
||||
$ ./run.sh
|
||||
```
|
||||
|
||||
# Testing
|
||||
|
||||
**Run the test suite:**
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "solana-account-decoder"
|
||||
version = "1.7.0"
|
||||
version = "1.7.14"
|
||||
description = "Solana account decoder"
|
||||
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
@ -19,11 +19,10 @@ lazy_static = "1.4.0"
|
||||
serde = "1.0.122"
|
||||
serde_derive = "1.0.103"
|
||||
serde_json = "1.0.56"
|
||||
solana-config-program = { path = "../programs/config", version = "=1.7.0" }
|
||||
solana-sdk = { path = "../sdk", version = "=1.7.0" }
|
||||
solana-stake-program = { path = "../programs/stake", version = "=1.7.0" }
|
||||
solana-vote-program = { path = "../programs/vote", version = "=1.7.0" }
|
||||
spl-token-v2-0 = { package = "spl-token", version = "=3.1.0", features = ["no-entrypoint"] }
|
||||
solana-config-program = { path = "../programs/config", version = "=1.7.14" }
|
||||
solana-sdk = { path = "../sdk", version = "=1.7.14" }
|
||||
solana-vote-program = { path = "../programs/vote", version = "=1.7.14" }
|
||||
spl-token-v2-0 = { package = "spl-token", version = "=3.2.0", features = ["no-entrypoint"] }
|
||||
thiserror = "1.0"
|
||||
zstd = "0.5.1"
|
||||
|
||||
|
@ -28,6 +28,7 @@ use {
|
||||
|
||||
pub type StringAmount = String;
|
||||
pub type StringDecimals = String;
|
||||
pub const MAX_BASE58_BYTES: usize = 128;
|
||||
|
||||
/// A duplicate representation of an Account for pretty JSON serialization
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
@ -48,7 +49,7 @@ pub enum UiAccountData {
|
||||
Binary(String, UiAccountEncoding),
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq)]
|
||||
#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum UiAccountEncoding {
|
||||
Binary, // Legacy. Retained for RPC backwards compatibility
|
||||
@ -60,6 +61,17 @@ pub enum UiAccountEncoding {
|
||||
}
|
||||
|
||||
impl UiAccount {
|
||||
fn encode_bs58<T: ReadableAccount>(
|
||||
account: &T,
|
||||
data_slice_config: Option<UiDataSliceConfig>,
|
||||
) -> String {
|
||||
if account.data().len() <= MAX_BASE58_BYTES {
|
||||
bs58::encode(slice_data(account.data(), data_slice_config)).into_string()
|
||||
} else {
|
||||
"error: data too large for bs58 encoding".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn encode<T: ReadableAccount>(
|
||||
pubkey: &Pubkey,
|
||||
account: &T,
|
||||
@ -68,33 +80,34 @@ impl UiAccount {
|
||||
data_slice_config: Option<UiDataSliceConfig>,
|
||||
) -> Self {
|
||||
let data = match encoding {
|
||||
UiAccountEncoding::Binary => UiAccountData::LegacyBinary(
|
||||
bs58::encode(slice_data(&account.data(), data_slice_config)).into_string(),
|
||||
),
|
||||
UiAccountEncoding::Base58 => UiAccountData::Binary(
|
||||
bs58::encode(slice_data(&account.data(), data_slice_config)).into_string(),
|
||||
encoding,
|
||||
),
|
||||
UiAccountEncoding::Binary => {
|
||||
let data = Self::encode_bs58(account, data_slice_config);
|
||||
UiAccountData::LegacyBinary(data)
|
||||
}
|
||||
UiAccountEncoding::Base58 => {
|
||||
let data = Self::encode_bs58(account, data_slice_config);
|
||||
UiAccountData::Binary(data, encoding)
|
||||
}
|
||||
UiAccountEncoding::Base64 => UiAccountData::Binary(
|
||||
base64::encode(slice_data(&account.data(), data_slice_config)),
|
||||
base64::encode(slice_data(account.data(), data_slice_config)),
|
||||
encoding,
|
||||
),
|
||||
UiAccountEncoding::Base64Zstd => {
|
||||
let mut encoder = zstd::stream::write::Encoder::new(Vec::new(), 0).unwrap();
|
||||
match encoder
|
||||
.write_all(slice_data(&account.data(), data_slice_config))
|
||||
.write_all(slice_data(account.data(), data_slice_config))
|
||||
.and_then(|()| encoder.finish())
|
||||
{
|
||||
Ok(zstd_data) => UiAccountData::Binary(base64::encode(zstd_data), encoding),
|
||||
Err(_) => UiAccountData::Binary(
|
||||
base64::encode(slice_data(&account.data(), data_slice_config)),
|
||||
base64::encode(slice_data(account.data(), data_slice_config)),
|
||||
UiAccountEncoding::Base64,
|
||||
),
|
||||
}
|
||||
}
|
||||
UiAccountEncoding::JsonParsed => {
|
||||
if let Ok(parsed_data) =
|
||||
parse_account_data(pubkey, &account.owner(), &account.data(), additional_data)
|
||||
parse_account_data(pubkey, account.owner(), account.data(), additional_data)
|
||||
{
|
||||
UiAccountData::Json(parsed_data)
|
||||
} else {
|
||||
@ -166,7 +179,7 @@ impl Default for UiFeeCalculator {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct UiDataSliceConfig {
|
||||
pub offset: usize,
|
||||
|
@ -9,14 +9,14 @@ use crate::{
|
||||
};
|
||||
use inflector::Inflector;
|
||||
use serde_json::Value;
|
||||
use solana_sdk::{instruction::InstructionError, pubkey::Pubkey, system_program, sysvar};
|
||||
use solana_sdk::{instruction::InstructionError, pubkey::Pubkey, stake, system_program, sysvar};
|
||||
use std::collections::HashMap;
|
||||
use thiserror::Error;
|
||||
|
||||
lazy_static! {
|
||||
static ref BPF_UPGRADEABLE_LOADER_PROGRAM_ID: Pubkey = solana_sdk::bpf_loader_upgradeable::id();
|
||||
static ref CONFIG_PROGRAM_ID: Pubkey = solana_config_program::id();
|
||||
static ref STAKE_PROGRAM_ID: Pubkey = solana_stake_program::id();
|
||||
static ref STAKE_PROGRAM_ID: Pubkey = stake::program::id();
|
||||
static ref SYSTEM_PROGRAM_ID: Pubkey = system_program::id();
|
||||
static ref SYSVAR_PROGRAM_ID: Pubkey = sysvar::id();
|
||||
static ref TOKEN_PROGRAM_ID: Pubkey = spl_token_id_v2_0();
|
||||
|
@ -6,10 +6,10 @@ use bincode::deserialize;
|
||||
use serde_json::Value;
|
||||
use solana_config_program::{get_config_data, ConfigKeys};
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
use solana_stake_program::config::Config as StakeConfig;
|
||||
use solana_sdk::stake::config::{self as stake_config, Config as StakeConfig};
|
||||
|
||||
pub fn parse_config(data: &[u8], pubkey: &Pubkey) -> Result<ConfigAccountType, ParseAccountError> {
|
||||
let parsed_account = if pubkey == &solana_stake_program::config::id() {
|
||||
let parsed_account = if pubkey == &stake_config::id() {
|
||||
get_config_data(data)
|
||||
.ok()
|
||||
.and_then(|data| deserialize::<StakeConfig>(data).ok())
|
||||
@ -37,7 +37,7 @@ fn parse_config_data<T>(data: &[u8], keys: Vec<(Pubkey, bool)>) -> Option<UiConf
|
||||
where
|
||||
T: serde::de::DeserializeOwned,
|
||||
{
|
||||
let config_data: T = deserialize(&get_config_data(data).ok()?).ok()?;
|
||||
let config_data: T = deserialize(get_config_data(data).ok()?).ok()?;
|
||||
let keys = keys
|
||||
.iter()
|
||||
.map(|key| UiConfigKey {
|
||||
@ -101,11 +101,7 @@ mod test {
|
||||
};
|
||||
let stake_config_account = create_config_account(vec![], &stake_config, 10);
|
||||
assert_eq!(
|
||||
parse_config(
|
||||
&stake_config_account.data(),
|
||||
&solana_stake_program::config::id()
|
||||
)
|
||||
.unwrap(),
|
||||
parse_config(stake_config_account.data(), &stake_config::id()).unwrap(),
|
||||
ConfigAccountType::StakeConfig(UiStakeConfig {
|
||||
warmup_cooldown_rate: 0.25,
|
||||
slash_penalty: 50,
|
||||
@ -125,7 +121,7 @@ mod test {
|
||||
10,
|
||||
);
|
||||
assert_eq!(
|
||||
parse_config(&validator_info_config_account.data(), &info_pubkey).unwrap(),
|
||||
parse_config(validator_info_config_account.data(), &info_pubkey).unwrap(),
|
||||
ConfigAccountType::ValidatorInfo(UiConfig {
|
||||
keys: vec![
|
||||
UiConfigKey {
|
||||
|
@ -4,7 +4,7 @@ use crate::{
|
||||
};
|
||||
use bincode::deserialize;
|
||||
use solana_sdk::clock::{Epoch, UnixTimestamp};
|
||||
use solana_stake_program::stake_state::{Authorized, Delegation, Lockup, Meta, Stake, StakeState};
|
||||
use solana_sdk::stake::state::{Authorized, Delegation, Lockup, Meta, Stake, StakeState};
|
||||
|
||||
pub fn parse_stake(data: &[u8]) -> Result<StakeAccountType, ParseAccountError> {
|
||||
let stake_state: StakeState = deserialize(data)
|
||||
|
@ -2,7 +2,7 @@
|
||||
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
|
||||
edition = "2018"
|
||||
name = "solana-accounts-bench"
|
||||
version = "1.7.0"
|
||||
version = "1.7.14"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
@ -11,11 +11,11 @@ publish = false
|
||||
[dependencies]
|
||||
log = "0.4.11"
|
||||
rayon = "1.5.0"
|
||||
solana-logger = { path = "../logger", version = "=1.7.0" }
|
||||
solana-runtime = { path = "../runtime", version = "=1.7.0" }
|
||||
solana-measure = { path = "../measure", version = "=1.7.0" }
|
||||
solana-sdk = { path = "../sdk", version = "=1.7.0" }
|
||||
solana-version = { path = "../version", version = "=1.7.0" }
|
||||
solana-logger = { path = "../logger", version = "=1.7.14" }
|
||||
solana-runtime = { path = "../runtime", version = "=1.7.14" }
|
||||
solana-measure = { path = "../measure", version = "=1.7.14" }
|
||||
solana-sdk = { path = "../sdk", version = "=1.7.14" }
|
||||
solana-version = { path = "../version", version = "=1.7.14" }
|
||||
rand = "0.7.0"
|
||||
clap = "2.33.1"
|
||||
crossbeam-channel = "0.4"
|
||||
|
@ -6,6 +6,7 @@ use rayon::prelude::*;
|
||||
use solana_measure::measure::Measure;
|
||||
use solana_runtime::{
|
||||
accounts::{create_test_accounts, update_accounts_bench, Accounts},
|
||||
accounts_db::AccountShrinkThreshold,
|
||||
accounts_index::AccountSecondaryIndexes,
|
||||
ancestors::Ancestors,
|
||||
};
|
||||
@ -64,6 +65,7 @@ fn main() {
|
||||
&ClusterType::Testnet,
|
||||
AccountSecondaryIndexes::default(),
|
||||
false,
|
||||
AccountShrinkThreshold::default(),
|
||||
);
|
||||
println!("Creating {} accounts", num_accounts);
|
||||
let mut create_time = Measure::start("create accounts");
|
||||
@ -119,6 +121,8 @@ fn main() {
|
||||
solana_sdk::clock::Slot::default(),
|
||||
&ancestors,
|
||||
None,
|
||||
false,
|
||||
None,
|
||||
);
|
||||
time_store.stop();
|
||||
if results != results_store {
|
||||
|
@ -2,7 +2,7 @@
|
||||
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
|
||||
edition = "2018"
|
||||
name = "solana-accounts-cluster-bench"
|
||||
version = "1.7.0"
|
||||
version = "1.7.14"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
@ -13,23 +13,24 @@ clap = "2.33.1"
|
||||
log = "0.4.11"
|
||||
rand = "0.7.0"
|
||||
rayon = "1.4.1"
|
||||
solana-account-decoder = { path = "../account-decoder", version = "=1.7.0" }
|
||||
solana-clap-utils = { path = "../clap-utils", version = "=1.7.0" }
|
||||
solana-client = { path = "../client", version = "=1.7.0" }
|
||||
solana-core = { path = "../core", version = "=1.7.0" }
|
||||
solana-faucet = { path = "../faucet", version = "=1.7.0" }
|
||||
solana-gossip = { path = "../gossip", version = "=1.7.0" }
|
||||
solana-logger = { path = "../logger", version = "=1.7.0" }
|
||||
solana-measure = { path = "../measure", version = "=1.7.0" }
|
||||
solana-net-utils = { path = "../net-utils", version = "=1.7.0" }
|
||||
solana-runtime = { path = "../runtime", version = "=1.7.0" }
|
||||
solana-sdk = { path = "../sdk", version = "=1.7.0" }
|
||||
solana-transaction-status = { path = "../transaction-status", version = "=1.7.0" }
|
||||
solana-version = { path = "../version", version = "=1.7.0" }
|
||||
spl-token-v2-0 = { package = "spl-token", version = "=3.1.0", features = ["no-entrypoint"] }
|
||||
solana-account-decoder = { path = "../account-decoder", version = "=1.7.14" }
|
||||
solana-clap-utils = { path = "../clap-utils", version = "=1.7.14" }
|
||||
solana-client = { path = "../client", version = "=1.7.14" }
|
||||
solana-core = { path = "../core", version = "=1.7.14" }
|
||||
solana-faucet = { path = "../faucet", version = "=1.7.14" }
|
||||
solana-gossip = { path = "../gossip", version = "=1.7.14" }
|
||||
solana-logger = { path = "../logger", version = "=1.7.14" }
|
||||
solana-measure = { path = "../measure", version = "=1.7.14" }
|
||||
solana-net-utils = { path = "../net-utils", version = "=1.7.14" }
|
||||
solana-runtime = { path = "../runtime", version = "=1.7.14" }
|
||||
solana-sdk = { path = "../sdk", version = "=1.7.14" }
|
||||
solana-streamer = { path = "../streamer", version = "=1.7.14" }
|
||||
solana-transaction-status = { path = "../transaction-status", version = "=1.7.14" }
|
||||
solana-version = { path = "../version", version = "=1.7.14" }
|
||||
spl-token-v2-0 = { package = "spl-token", version = "=3.2.0", features = ["no-entrypoint"] }
|
||||
|
||||
[dev-dependencies]
|
||||
solana-local-cluster = { path = "../local-cluster", version = "=1.7.0" }
|
||||
solana-local-cluster = { path = "../local-cluster", version = "=1.7.14" }
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
@ -20,6 +20,7 @@ use solana_sdk::{
|
||||
timing::timestamp,
|
||||
transaction::Transaction,
|
||||
};
|
||||
use solana_streamer::socket::SocketAddrSpace;
|
||||
use solana_transaction_status::parse_token::spl_token_v2_0_instruction;
|
||||
use std::{
|
||||
net::SocketAddr,
|
||||
@ -55,7 +56,7 @@ pub fn airdrop_lamports(
|
||||
);
|
||||
|
||||
let (blockhash, _fee_calculator) = client.get_recent_blockhash().unwrap();
|
||||
match request_airdrop_transaction(&faucet_addr, &id.pubkey(), airdrop_amount, blockhash) {
|
||||
match request_airdrop_transaction(faucet_addr, &id.pubkey(), airdrop_amount, blockhash) {
|
||||
Ok(transaction) => {
|
||||
let mut tries = 0;
|
||||
loop {
|
||||
@ -363,7 +364,7 @@ fn run_accounts_bench(
|
||||
iterations: usize,
|
||||
maybe_space: Option<u64>,
|
||||
batch_size: usize,
|
||||
close_nth: u64,
|
||||
close_nth_batch: u64,
|
||||
maybe_lamports: Option<u64>,
|
||||
num_instructions: usize,
|
||||
mint: Option<Pubkey>,
|
||||
@ -431,7 +432,7 @@ fn run_accounts_bench(
|
||||
if !airdrop_lamports(
|
||||
&client,
|
||||
&faucet_addr,
|
||||
&payer_keypairs[i],
|
||||
payer_keypairs[i],
|
||||
lamports * 100_000,
|
||||
) {
|
||||
warn!("failed airdrop, exiting");
|
||||
@ -441,6 +442,7 @@ fn run_accounts_bench(
|
||||
}
|
||||
}
|
||||
|
||||
// Create accounts
|
||||
let sigs_len = executor.num_outstanding();
|
||||
if sigs_len < batch_size {
|
||||
let num_to_create = batch_size - sigs_len;
|
||||
@ -475,21 +477,25 @@ fn run_accounts_bench(
|
||||
}
|
||||
}
|
||||
|
||||
if close_nth > 0 {
|
||||
let expected_closed = total_accounts_created as u64 / close_nth;
|
||||
if expected_closed > total_accounts_closed {
|
||||
let txs: Vec<_> = (0..expected_closed - total_accounts_closed)
|
||||
if close_nth_batch > 0 {
|
||||
let num_batches_to_close =
|
||||
total_accounts_created as u64 / (close_nth_batch * batch_size as u64);
|
||||
let expected_closed = num_batches_to_close * batch_size as u64;
|
||||
let max_closed_seed = seed_tracker.max_closed.load(Ordering::Relaxed);
|
||||
// Close every account we've created with seed between max_closed_seed..expected_closed
|
||||
if max_closed_seed < expected_closed {
|
||||
let txs: Vec<_> = (0..expected_closed - max_closed_seed)
|
||||
.into_par_iter()
|
||||
.map(|_| {
|
||||
let message = make_close_message(
|
||||
&payer_keypairs[0],
|
||||
payer_keypairs[0],
|
||||
&base_keypair,
|
||||
seed_tracker.max_closed.clone(),
|
||||
1,
|
||||
min_balance,
|
||||
mint.is_some(),
|
||||
);
|
||||
let signers: Vec<&Keypair> = vec![&payer_keypairs[0], &base_keypair];
|
||||
let signers: Vec<&Keypair> = vec![payer_keypairs[0], &base_keypair];
|
||||
Transaction::new(&signers, message, recent_blockhash.0)
|
||||
})
|
||||
.collect();
|
||||
@ -572,14 +578,14 @@ fn main() {
|
||||
.help("Number of transactions to send per batch"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("close_nth")
|
||||
Arg::with_name("close_nth_batch")
|
||||
.long("close-frequency")
|
||||
.takes_value(true)
|
||||
.value_name("BYTES")
|
||||
.help(
|
||||
"Send close transactions after this many accounts created. \
|
||||
Note: a `close-frequency` value near or below `batch-size` \
|
||||
may result in transaction-simulation errors, as the close \
|
||||
"Every `n` batches, create a batch of close transactions for
|
||||
the earliest remaining batch of accounts created.
|
||||
Note: Should be > 1 to avoid situations where the close \
|
||||
transactions will be submitted before the corresponding \
|
||||
create transactions have been confirmed",
|
||||
),
|
||||
@ -632,7 +638,7 @@ fn main() {
|
||||
let space = value_t!(matches, "space", u64).ok();
|
||||
let lamports = value_t!(matches, "lamports", u64).ok();
|
||||
let batch_size = value_t!(matches, "batch_size", usize).unwrap_or(4);
|
||||
let close_nth = value_t!(matches, "close_nth", u64).unwrap_or(0);
|
||||
let close_nth_batch = value_t!(matches, "close_nth_batch", u64).unwrap_or(0);
|
||||
let iterations = value_t!(matches, "iterations", usize).unwrap_or(10);
|
||||
let num_instructions = value_t!(matches, "num_instructions", usize).unwrap_or(1);
|
||||
if num_instructions == 0 || num_instructions > 500 {
|
||||
@ -665,6 +671,7 @@ fn main() {
|
||||
Some(&entrypoint_addr), // find_node_by_gossip_addr
|
||||
None, // my_gossip_addr
|
||||
0, // my_shred_version
|
||||
SocketAddrSpace::Unspecified,
|
||||
)
|
||||
.unwrap_or_else(|err| {
|
||||
eprintln!("Failed to discover {} node: {:?}", entrypoint_addr, err);
|
||||
@ -685,7 +692,7 @@ fn main() {
|
||||
iterations,
|
||||
space,
|
||||
batch_size,
|
||||
close_nth,
|
||||
close_nth_batch,
|
||||
lamports,
|
||||
num_instructions,
|
||||
mint,
|
||||
@ -716,11 +723,11 @@ pub mod test {
|
||||
};
|
||||
|
||||
let faucet_addr = SocketAddr::from(([127, 0, 0, 1], 9900));
|
||||
let cluster = LocalCluster::new(&mut config);
|
||||
let cluster = LocalCluster::new(&mut config, SocketAddrSpace::Unspecified);
|
||||
let iterations = 10;
|
||||
let maybe_space = None;
|
||||
let batch_size = 100;
|
||||
let close_nth = 100;
|
||||
let close_nth_batch = 100;
|
||||
let maybe_lamports = None;
|
||||
let num_instructions = 2;
|
||||
let mut start = Measure::start("total accounts run");
|
||||
@ -731,7 +738,7 @@ pub mod test {
|
||||
iterations,
|
||||
maybe_space,
|
||||
batch_size,
|
||||
close_nth,
|
||||
close_nth_batch,
|
||||
maybe_lamports,
|
||||
num_instructions,
|
||||
None,
|
||||
|
@ -2,7 +2,7 @@
|
||||
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
|
||||
edition = "2018"
|
||||
name = "solana-banking-bench"
|
||||
version = "1.7.0"
|
||||
version = "1.7.14"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
@ -14,17 +14,18 @@ crossbeam-channel = "0.4"
|
||||
log = "0.4.11"
|
||||
rand = "0.7.0"
|
||||
rayon = "1.5.0"
|
||||
solana-core = { path = "../core", version = "=1.7.0" }
|
||||
solana-clap-utils = { path = "../clap-utils", version = "=1.7.0" }
|
||||
solana-gossip = { path = "../gossip", version = "=1.7.0" }
|
||||
solana-ledger = { path = "../ledger", version = "=1.7.0" }
|
||||
solana-logger = { path = "../logger", version = "=1.7.0" }
|
||||
solana-measure = { path = "../measure", version = "=1.7.0" }
|
||||
solana-perf = { path = "../perf", version = "=1.7.0" }
|
||||
solana-runtime = { path = "../runtime", version = "=1.7.0" }
|
||||
solana-streamer = { path = "../streamer", version = "=1.7.0" }
|
||||
solana-sdk = { path = "../sdk", version = "=1.7.0" }
|
||||
solana-version = { path = "../version", version = "=1.7.0" }
|
||||
solana-core = { path = "../core", version = "=1.7.14" }
|
||||
solana-clap-utils = { path = "../clap-utils", version = "=1.7.14" }
|
||||
solana-gossip = { path = "../gossip", version = "=1.7.14" }
|
||||
solana-ledger = { path = "../ledger", version = "=1.7.14" }
|
||||
solana-logger = { path = "../logger", version = "=1.7.14" }
|
||||
solana-measure = { path = "../measure", version = "=1.7.14" }
|
||||
solana-perf = { path = "../perf", version = "=1.7.14" }
|
||||
solana-poh = { path = "../poh", version = "=1.7.14" }
|
||||
solana-runtime = { path = "../runtime", version = "=1.7.14" }
|
||||
solana-streamer = { path = "../streamer", version = "=1.7.14" }
|
||||
solana-sdk = { path = "../sdk", version = "=1.7.14" }
|
||||
solana-version = { path = "../version", version = "=1.7.14" }
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
@ -4,11 +4,7 @@ use crossbeam_channel::unbounded;
|
||||
use log::*;
|
||||
use rand::{thread_rng, Rng};
|
||||
use rayon::prelude::*;
|
||||
use solana_core::{
|
||||
banking_stage::{create_test_recorder, BankingStage},
|
||||
poh_recorder::PohRecorder,
|
||||
poh_recorder::WorkingBankEntry,
|
||||
};
|
||||
use solana_core::banking_stage::BankingStage;
|
||||
use solana_gossip::{cluster_info::ClusterInfo, cluster_info::Node};
|
||||
use solana_ledger::{
|
||||
blockstore::Blockstore,
|
||||
@ -17,6 +13,7 @@ use solana_ledger::{
|
||||
};
|
||||
use solana_measure::measure::Measure;
|
||||
use solana_perf::packet::to_packets_chunked;
|
||||
use solana_poh::poh_recorder::{create_test_recorder, PohRecorder, WorkingBankEntry};
|
||||
use solana_runtime::{
|
||||
accounts_background_service::AbsRequestSender, bank::Bank, bank_forks::BankForks,
|
||||
};
|
||||
@ -28,6 +25,7 @@ use solana_sdk::{
|
||||
timing::{duration_as_us, timestamp},
|
||||
transaction::Transaction,
|
||||
};
|
||||
use solana_streamer::socket::SocketAddrSpace;
|
||||
use std::{
|
||||
sync::{atomic::Ordering, mpsc::Receiver, Arc, Mutex},
|
||||
thread::sleep,
|
||||
@ -77,7 +75,7 @@ fn make_accounts_txs(
|
||||
.into_par_iter()
|
||||
.map(|_| {
|
||||
let mut new = dummy.clone();
|
||||
let sig: Vec<u8> = (0..64).map(|_| thread_rng().gen()).collect();
|
||||
let sig: Vec<u8> = (0..64).map(|_| thread_rng().gen::<u8>()).collect();
|
||||
if !same_payer {
|
||||
new.message.account_keys[0] = solana_sdk::pubkey::new_rand();
|
||||
}
|
||||
@ -168,6 +166,7 @@ fn main() {
|
||||
|
||||
let (verified_sender, verified_receiver) = unbounded();
|
||||
let (vote_sender, vote_receiver) = unbounded();
|
||||
let (tpu_vote_sender, tpu_vote_receiver) = unbounded();
|
||||
let (replay_vote_sender, _replay_vote_receiver) = unbounded();
|
||||
let bank0 = Bank::new(&genesis_config);
|
||||
let mut bank_forks = BankForks::new(bank0);
|
||||
@ -188,7 +187,7 @@ fn main() {
|
||||
genesis_config.hash(),
|
||||
);
|
||||
// Ignore any pesky duplicate signature errors in the case we are using single-payer
|
||||
let sig: Vec<u8> = (0..64).map(|_| thread_rng().gen()).collect();
|
||||
let sig: Vec<u8> = (0..64).map(|_| thread_rng().gen::<u8>()).collect();
|
||||
fund.signatures = vec![Signature::new(&sig[0..64])];
|
||||
let x = bank.process_transaction(&fund);
|
||||
x.unwrap();
|
||||
@ -198,7 +197,7 @@ fn main() {
|
||||
if !skip_sanity {
|
||||
//sanity check, make sure all the transactions can execute sequentially
|
||||
transactions.iter().for_each(|tx| {
|
||||
let res = bank.process_transaction(&tx);
|
||||
let res = bank.process_transaction(tx);
|
||||
assert!(res.is_ok(), "sanity test transactions error: {:?}", res);
|
||||
});
|
||||
bank.clear_signatures();
|
||||
@ -218,12 +217,17 @@ fn main() {
|
||||
);
|
||||
let (exit, poh_recorder, poh_service, signal_receiver) =
|
||||
create_test_recorder(&bank, &blockstore, None);
|
||||
let cluster_info = ClusterInfo::new_with_invalid_keypair(Node::new_localhost().info);
|
||||
let cluster_info = ClusterInfo::new(
|
||||
Node::new_localhost().info,
|
||||
Arc::new(Keypair::new()),
|
||||
SocketAddrSpace::Unspecified,
|
||||
);
|
||||
let cluster_info = Arc::new(cluster_info);
|
||||
let banking_stage = BankingStage::new(
|
||||
&cluster_info,
|
||||
&poh_recorder,
|
||||
verified_receiver,
|
||||
tpu_vote_receiver,
|
||||
vote_receiver,
|
||||
None,
|
||||
replay_vote_sender,
|
||||
@ -354,7 +358,7 @@ fn main() {
|
||||
if bank.slot() > 0 && bank.slot() % 16 == 0 {
|
||||
for tx in transactions.iter_mut() {
|
||||
tx.message.recent_blockhash = bank.last_blockhash();
|
||||
let sig: Vec<u8> = (0..64).map(|_| thread_rng().gen()).collect();
|
||||
let sig: Vec<u8> = (0..64).map(|_| thread_rng().gen::<u8>()).collect();
|
||||
tx.signatures[0] = Signature::new(&sig[0..64]);
|
||||
}
|
||||
verified = to_packets_chunked(&transactions.clone(), packets_per_chunk);
|
||||
@ -379,6 +383,7 @@ fn main() {
|
||||
);
|
||||
|
||||
drop(verified_sender);
|
||||
drop(tpu_vote_sender);
|
||||
drop(vote_sender);
|
||||
exit.store(true, Ordering::Relaxed);
|
||||
banking_stage.join().unwrap();
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "solana-banks-client"
|
||||
version = "1.7.0"
|
||||
version = "1.7.14"
|
||||
description = "Solana banks client"
|
||||
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
@ -11,20 +11,20 @@ edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
bincode = "1.3.1"
|
||||
borsh = "0.8.1"
|
||||
borsh-derive = "0.8.1"
|
||||
borsh = "0.9.0"
|
||||
borsh-derive = "0.9.0"
|
||||
futures = "0.3"
|
||||
mio = "0.7.6"
|
||||
solana-banks-interface = { path = "../banks-interface", version = "=1.7.0" }
|
||||
solana-program = { path = "../sdk/program", version = "=1.7.0" }
|
||||
solana-sdk = { path = "../sdk", version = "=1.7.0" }
|
||||
solana-banks-interface = { path = "../banks-interface", version = "=1.7.14" }
|
||||
solana-program = { path = "../sdk/program", version = "=1.7.14" }
|
||||
solana-sdk = { path = "../sdk", version = "=1.7.14" }
|
||||
tarpc = { version = "0.24.1", features = ["full"] }
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
tokio-serde = { version = "0.8", features = ["bincode"] }
|
||||
|
||||
[dev-dependencies]
|
||||
solana-runtime = { path = "../runtime", version = "=1.7.0" }
|
||||
solana-banks-server = { path = "../banks-server", version = "=1.7.0" }
|
||||
solana-runtime = { path = "../runtime", version = "=1.7.14" }
|
||||
solana-banks-server = { path = "../banks-server", version = "=1.7.14" }
|
||||
|
||||
[lib]
|
||||
crate-type = ["lib"]
|
||||
|
@ -10,8 +10,14 @@ use futures::{future::join_all, Future, FutureExt};
|
||||
pub use solana_banks_interface::{BanksClient as TarpcClient, TransactionStatus};
|
||||
use solana_banks_interface::{BanksRequest, BanksResponse};
|
||||
use solana_program::{
|
||||
clock::Slot, fee_calculator::FeeCalculator, hash::Hash, program_pack::Pack, pubkey::Pubkey,
|
||||
rent::Rent, sysvar,
|
||||
clock::Clock,
|
||||
clock::Slot,
|
||||
fee_calculator::FeeCalculator,
|
||||
hash::Hash,
|
||||
program_pack::Pack,
|
||||
pubkey::Pubkey,
|
||||
rent::Rent,
|
||||
sysvar::{self, Sysvar},
|
||||
};
|
||||
use solana_sdk::{
|
||||
account::{from_account, Account},
|
||||
@ -63,7 +69,7 @@ impl BanksClient {
|
||||
&mut self,
|
||||
ctx: Context,
|
||||
commitment: CommitmentLevel,
|
||||
) -> impl Future<Output = io::Result<(FeeCalculator, Hash, Slot)>> + '_ {
|
||||
) -> impl Future<Output = io::Result<(FeeCalculator, Hash, u64)>> + '_ {
|
||||
self.inner
|
||||
.get_fees_with_commitment_and_context(ctx, commitment)
|
||||
}
|
||||
@ -85,6 +91,14 @@ impl BanksClient {
|
||||
self.inner.get_slot_with_context(ctx, commitment)
|
||||
}
|
||||
|
||||
pub fn get_block_height_with_context(
|
||||
&mut self,
|
||||
ctx: Context,
|
||||
commitment: CommitmentLevel,
|
||||
) -> impl Future<Output = io::Result<Slot>> + '_ {
|
||||
self.inner.get_block_height_with_context(ctx, commitment)
|
||||
}
|
||||
|
||||
pub fn process_transaction_with_commitment_and_context(
|
||||
&mut self,
|
||||
ctx: Context,
|
||||
@ -115,24 +129,39 @@ impl BanksClient {
|
||||
self.send_transaction_with_context(context::current(), transaction)
|
||||
}
|
||||
|
||||
/// Return the cluster clock
|
||||
pub fn get_clock(&mut self) -> impl Future<Output = io::Result<Clock>> + '_ {
|
||||
self.get_account(sysvar::clock::id()).map(|result| {
|
||||
let clock_sysvar = result?
|
||||
.ok_or_else(|| io::Error::new(io::ErrorKind::Other, "Clock sysvar not present"))?;
|
||||
from_account::<Clock, _>(&clock_sysvar).ok_or_else(|| {
|
||||
io::Error::new(io::ErrorKind::Other, "Failed to deserialize Clock sysvar")
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/// Return the fee parameters associated with a recent, rooted blockhash. The cluster
|
||||
/// will use the transaction's blockhash to look up these same fee parameters and
|
||||
/// use them to calculate the transaction fee.
|
||||
pub fn get_fees(
|
||||
&mut self,
|
||||
) -> impl Future<Output = io::Result<(FeeCalculator, Hash, Slot)>> + '_ {
|
||||
) -> impl Future<Output = io::Result<(FeeCalculator, Hash, u64)>> + '_ {
|
||||
self.get_fees_with_commitment_and_context(context::current(), CommitmentLevel::default())
|
||||
}
|
||||
|
||||
/// Return the cluster Sysvar
|
||||
pub fn get_sysvar<T: Sysvar>(&mut self) -> impl Future<Output = io::Result<T>> + '_ {
|
||||
self.get_account(T::id()).map(|result| {
|
||||
let sysvar = result?
|
||||
.ok_or_else(|| io::Error::new(io::ErrorKind::Other, "Sysvar not present"))?;
|
||||
from_account::<T, _>(&sysvar)
|
||||
.ok_or_else(|| io::Error::new(io::ErrorKind::Other, "Failed to deserialize sysvar"))
|
||||
})
|
||||
}
|
||||
|
||||
/// Return the cluster rent
|
||||
pub fn get_rent(&mut self) -> impl Future<Output = io::Result<Rent>> + '_ {
|
||||
self.get_account(sysvar::rent::id()).map(|result| {
|
||||
let rent_sysvar = result?
|
||||
.ok_or_else(|| io::Error::new(io::ErrorKind::Other, "Rent sysvar not present"))?;
|
||||
from_account::<Rent, _>(&rent_sysvar).ok_or_else(|| {
|
||||
io::Error::new(io::ErrorKind::Other, "Failed to deserialize Rent sysvar")
|
||||
})
|
||||
})
|
||||
self.get_sysvar::<Rent>()
|
||||
}
|
||||
|
||||
/// Return a recent, rooted blockhash from the server. The cluster will only accept
|
||||
@ -192,12 +221,18 @@ impl BanksClient {
|
||||
self.process_transactions_with_commitment(transactions, CommitmentLevel::default())
|
||||
}
|
||||
|
||||
/// Return the most recent rooted slot height. All transactions at or below this height
|
||||
/// are said to be finalized. The cluster will not fork to a higher slot height.
|
||||
/// Return the most recent rooted slot. All transactions at or below this slot
|
||||
/// are said to be finalized. The cluster will not fork to a higher slot.
|
||||
pub fn get_root_slot(&mut self) -> impl Future<Output = io::Result<Slot>> + '_ {
|
||||
self.get_slot_with_context(context::current(), CommitmentLevel::default())
|
||||
}
|
||||
|
||||
/// Return the most recent rooted block height. All transactions at or below this height
|
||||
/// are said to be finalized. The cluster will not fork to a higher block height.
|
||||
pub fn get_root_block_height(&mut self) -> impl Future<Output = io::Result<Slot>> + '_ {
|
||||
self.get_block_height_with_context(context::current(), CommitmentLevel::default())
|
||||
}
|
||||
|
||||
/// Return the account at the given address at the slot corresponding to the given
|
||||
/// commitment level. If the account is not found, None is returned.
|
||||
pub fn get_account_with_commitment(
|
||||
@ -377,13 +412,13 @@ mod tests {
|
||||
|
||||
let mint_pubkey = &genesis.mint_keypair.pubkey();
|
||||
let bob_pubkey = solana_sdk::pubkey::new_rand();
|
||||
let instruction = system_instruction::transfer(&mint_pubkey, &bob_pubkey, 1);
|
||||
let message = Message::new(&[instruction], Some(&mint_pubkey));
|
||||
let instruction = system_instruction::transfer(mint_pubkey, &bob_pubkey, 1);
|
||||
let message = Message::new(&[instruction], Some(mint_pubkey));
|
||||
|
||||
Runtime::new()?.block_on(async {
|
||||
let client_transport = start_local_server(bank_forks, block_commitment_cache).await;
|
||||
let mut banks_client = start_client(client_transport).await?;
|
||||
let (_, recent_blockhash, last_valid_slot) = banks_client.get_fees().await?;
|
||||
let (_, recent_blockhash, last_valid_block_height) = banks_client.get_fees().await?;
|
||||
let transaction = Transaction::new(&[&genesis.mint_keypair], message, recent_blockhash);
|
||||
let signature = transaction.signatures[0];
|
||||
banks_client.send_transaction(transaction).await?;
|
||||
@ -391,8 +426,8 @@ mod tests {
|
||||
let mut status = banks_client.get_transaction_status(signature).await?;
|
||||
|
||||
while status.is_none() {
|
||||
let root_slot = banks_client.get_root_slot().await?;
|
||||
if root_slot > last_valid_slot {
|
||||
let root_block_height = banks_client.get_root_block_height().await?;
|
||||
if root_block_height > last_valid_block_height {
|
||||
break;
|
||||
}
|
||||
sleep(Duration::from_millis(100)).await;
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "solana-banks-interface"
|
||||
version = "1.7.0"
|
||||
version = "1.7.14"
|
||||
description = "Solana banks RPC interface"
|
||||
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
@ -12,7 +12,7 @@ edition = "2018"
|
||||
[dependencies]
|
||||
mio = "0.7.6"
|
||||
serde = { version = "1.0.122", features = ["derive"] }
|
||||
solana-sdk = { path = "../sdk", version = "=1.7.0" }
|
||||
solana-sdk = { path = "../sdk", version = "=1.7.14" }
|
||||
tarpc = { version = "0.24.1", features = ["full"] }
|
||||
|
||||
[dev-dependencies]
|
||||
|
@ -34,6 +34,7 @@ pub trait Banks {
|
||||
async fn get_transaction_status_with_context(signature: Signature)
|
||||
-> Option<TransactionStatus>;
|
||||
async fn get_slot_with_context(commitment: CommitmentLevel) -> Slot;
|
||||
async fn get_block_height_with_context(commitment: CommitmentLevel) -> u64;
|
||||
async fn process_transaction_with_commitment_and_context(
|
||||
transaction: Transaction,
|
||||
commitment: CommitmentLevel,
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "solana-banks-server"
|
||||
version = "1.7.0"
|
||||
version = "1.7.14"
|
||||
description = "Solana banks server"
|
||||
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
@ -14,10 +14,10 @@ bincode = "1.3.1"
|
||||
futures = "0.3"
|
||||
log = "0.4.11"
|
||||
mio = "0.7.6"
|
||||
solana-banks-interface = { path = "../banks-interface", version = "=1.7.0" }
|
||||
solana-runtime = { path = "../runtime", version = "=1.7.0" }
|
||||
solana-sdk = { path = "../sdk", version = "=1.7.0" }
|
||||
solana-metrics = { path = "../metrics", version = "=1.7.0" }
|
||||
solana-banks-interface = { path = "../banks-interface", version = "=1.7.14" }
|
||||
solana-runtime = { path = "../runtime", version = "=1.7.14" }
|
||||
solana-sdk = { path = "../sdk", version = "=1.7.14" }
|
||||
solana-metrics = { path = "../metrics", version = "=1.7.14" }
|
||||
tarpc = { version = "0.24.1", features = ["full"] }
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
tokio-serde = { version = "0.8", features = ["bincode"] }
|
||||
|
@ -113,7 +113,7 @@ impl BanksServer {
|
||||
self,
|
||||
signature: &Signature,
|
||||
blockhash: &Hash,
|
||||
last_valid_slot: Slot,
|
||||
last_valid_block_height: u64,
|
||||
commitment: CommitmentLevel,
|
||||
) -> Option<transaction::Result<()>> {
|
||||
let mut status = self
|
||||
@ -122,7 +122,7 @@ impl BanksServer {
|
||||
while status.is_none() {
|
||||
sleep(Duration::from_millis(200)).await;
|
||||
let bank = self.bank(commitment);
|
||||
if bank.slot() > last_valid_slot {
|
||||
if bank.block_height() > last_valid_block_height {
|
||||
break;
|
||||
}
|
||||
status = bank.get_signature_status_with_blockhash(signature, blockhash);
|
||||
@ -131,10 +131,13 @@ impl BanksServer {
|
||||
}
|
||||
}
|
||||
|
||||
fn verify_transaction(transaction: &Transaction) -> transaction::Result<()> {
|
||||
fn verify_transaction(
|
||||
transaction: &Transaction,
|
||||
libsecp256k1_0_5_upgrade_enabled: bool,
|
||||
) -> transaction::Result<()> {
|
||||
if let Err(err) = transaction.verify() {
|
||||
Err(err)
|
||||
} else if let Err(err) = transaction.verify_precompiles() {
|
||||
} else if let Err(err) = transaction.verify_precompiles(libsecp256k1_0_5_upgrade_enabled) {
|
||||
Err(err)
|
||||
} else {
|
||||
Ok(())
|
||||
@ -145,16 +148,19 @@ fn verify_transaction(transaction: &Transaction) -> transaction::Result<()> {
|
||||
impl Banks for BanksServer {
|
||||
async fn send_transaction_with_context(self, _: Context, transaction: Transaction) {
|
||||
let blockhash = &transaction.message.recent_blockhash;
|
||||
let last_valid_slot = self
|
||||
let last_valid_block_height = self
|
||||
.bank_forks
|
||||
.read()
|
||||
.unwrap()
|
||||
.root_bank()
|
||||
.get_blockhash_last_valid_slot(&blockhash)
|
||||
.get_blockhash_last_valid_block_height(blockhash)
|
||||
.unwrap();
|
||||
let signature = transaction.signatures.get(0).cloned().unwrap_or_default();
|
||||
let info =
|
||||
TransactionInfo::new(signature, serialize(&transaction).unwrap(), last_valid_slot);
|
||||
let info = TransactionInfo::new(
|
||||
signature,
|
||||
serialize(&transaction).unwrap(),
|
||||
last_valid_block_height,
|
||||
);
|
||||
self.transaction_sender.send(info).unwrap();
|
||||
}
|
||||
|
||||
@ -162,11 +168,13 @@ impl Banks for BanksServer {
|
||||
self,
|
||||
_: Context,
|
||||
commitment: CommitmentLevel,
|
||||
) -> (FeeCalculator, Hash, Slot) {
|
||||
) -> (FeeCalculator, Hash, u64) {
|
||||
let bank = self.bank(commitment);
|
||||
let (blockhash, fee_calculator) = bank.last_blockhash_with_fee_calculator();
|
||||
let last_valid_slot = bank.get_blockhash_last_valid_slot(&blockhash).unwrap();
|
||||
(fee_calculator, blockhash, last_valid_slot)
|
||||
let last_valid_block_height = bank
|
||||
.get_blockhash_last_valid_block_height(&blockhash)
|
||||
.unwrap();
|
||||
(fee_calculator, blockhash, last_valid_block_height)
|
||||
}
|
||||
|
||||
async fn get_transaction_status_with_context(
|
||||
@ -209,29 +217,39 @@ impl Banks for BanksServer {
|
||||
self.slot(commitment)
|
||||
}
|
||||
|
||||
async fn get_block_height_with_context(self, _: Context, commitment: CommitmentLevel) -> u64 {
|
||||
self.bank(commitment).block_height()
|
||||
}
|
||||
|
||||
async fn process_transaction_with_commitment_and_context(
|
||||
self,
|
||||
_: Context,
|
||||
transaction: Transaction,
|
||||
commitment: CommitmentLevel,
|
||||
) -> Option<transaction::Result<()>> {
|
||||
if let Err(err) = verify_transaction(&transaction) {
|
||||
if let Err(err) = verify_transaction(
|
||||
&transaction,
|
||||
self.bank(commitment).libsecp256k1_0_5_upgrade_enabled(),
|
||||
) {
|
||||
return Some(Err(err));
|
||||
}
|
||||
|
||||
let blockhash = &transaction.message.recent_blockhash;
|
||||
let last_valid_slot = self
|
||||
let last_valid_block_height = self
|
||||
.bank_forks
|
||||
.read()
|
||||
.unwrap()
|
||||
.root_bank()
|
||||
.get_blockhash_last_valid_slot(blockhash)
|
||||
.get_blockhash_last_valid_block_height(blockhash)
|
||||
.unwrap();
|
||||
let signature = transaction.signatures.get(0).cloned().unwrap_or_default();
|
||||
let info =
|
||||
TransactionInfo::new(signature, serialize(&transaction).unwrap(), last_valid_slot);
|
||||
let info = TransactionInfo::new(
|
||||
signature,
|
||||
serialize(&transaction).unwrap(),
|
||||
last_valid_block_height,
|
||||
);
|
||||
self.transaction_sender.send(info).unwrap();
|
||||
self.poll_signature_status(&signature, blockhash, last_valid_slot, commitment)
|
||||
self.poll_signature_status(&signature, blockhash, last_valid_block_height, commitment)
|
||||
.await
|
||||
}
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
use log::*;
|
||||
use solana_metrics::{datapoint_warn, inc_new_counter_info};
|
||||
use solana_runtime::{bank::Bank, bank_forks::BankForks};
|
||||
use solana_sdk::{clock::Slot, signature::Signature};
|
||||
use solana_sdk::signature::Signature;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
net::{SocketAddr, UdpSocket},
|
||||
@ -24,15 +24,19 @@ pub struct SendTransactionService {
|
||||
pub struct TransactionInfo {
|
||||
pub signature: Signature,
|
||||
pub wire_transaction: Vec<u8>,
|
||||
pub last_valid_slot: Slot,
|
||||
pub last_valid_block_height: u64,
|
||||
}
|
||||
|
||||
impl TransactionInfo {
|
||||
pub fn new(signature: Signature, wire_transaction: Vec<u8>, last_valid_slot: Slot) -> Self {
|
||||
pub fn new(
|
||||
signature: Signature,
|
||||
wire_transaction: Vec<u8>,
|
||||
last_valid_block_height: u64,
|
||||
) -> Self {
|
||||
Self {
|
||||
signature,
|
||||
wire_transaction,
|
||||
last_valid_slot,
|
||||
last_valid_block_height,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -124,7 +128,7 @@ impl SendTransactionService {
|
||||
result.rooted += 1;
|
||||
inc_new_counter_info!("send_transaction_service-rooted", 1);
|
||||
false
|
||||
} else if transaction_info.last_valid_slot < root_bank.slot() {
|
||||
} else if transaction_info.last_valid_block_height < root_bank.block_height() {
|
||||
info!("Dropping expired transaction: {}", signature);
|
||||
result.expired += 1;
|
||||
inc_new_counter_info!("send_transaction_service-expired", 1);
|
||||
@ -138,8 +142,8 @@ impl SendTransactionService {
|
||||
result.retried += 1;
|
||||
inc_new_counter_info!("send_transaction_service-retry", 1);
|
||||
Self::send_transaction(
|
||||
&send_socket,
|
||||
&tpu_address,
|
||||
send_socket,
|
||||
tpu_address,
|
||||
&transaction_info.wire_transaction,
|
||||
);
|
||||
true
|
||||
|
@ -2,7 +2,7 @@
|
||||
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
|
||||
edition = "2018"
|
||||
name = "solana-bench-exchange"
|
||||
version = "1.7.0"
|
||||
version = "1.7.14"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
@ -18,22 +18,23 @@ rand = "0.7.0"
|
||||
rayon = "1.5.0"
|
||||
serde_json = "1.0.56"
|
||||
serde_yaml = "0.8.13"
|
||||
solana-clap-utils = { path = "../clap-utils", version = "=1.7.0" }
|
||||
solana-core = { path = "../core", version = "=1.7.0" }
|
||||
solana-genesis = { path = "../genesis", version = "=1.7.0" }
|
||||
solana-client = { path = "../client", version = "=1.7.0" }
|
||||
solana-exchange-program = { path = "../programs/exchange", version = "=1.7.0" }
|
||||
solana-faucet = { path = "../faucet", version = "=1.7.0" }
|
||||
solana-gossip = { path = "../gossip", version = "=1.7.0" }
|
||||
solana-logger = { path = "../logger", version = "=1.7.0" }
|
||||
solana-metrics = { path = "../metrics", version = "=1.7.0" }
|
||||
solana-net-utils = { path = "../net-utils", version = "=1.7.0" }
|
||||
solana-runtime = { path = "../runtime", version = "=1.7.0" }
|
||||
solana-sdk = { path = "../sdk", version = "=1.7.0" }
|
||||
solana-version = { path = "../version", version = "=1.7.0" }
|
||||
solana-clap-utils = { path = "../clap-utils", version = "=1.7.14" }
|
||||
solana-core = { path = "../core", version = "=1.7.14" }
|
||||
solana-genesis = { path = "../genesis", version = "=1.7.14" }
|
||||
solana-client = { path = "../client", version = "=1.7.14" }
|
||||
solana-exchange-program = { path = "../programs/exchange", version = "=1.7.14" }
|
||||
solana-faucet = { path = "../faucet", version = "=1.7.14" }
|
||||
solana-gossip = { path = "../gossip", version = "=1.7.14" }
|
||||
solana-logger = { path = "../logger", version = "=1.7.14" }
|
||||
solana-metrics = { path = "../metrics", version = "=1.7.14" }
|
||||
solana-net-utils = { path = "../net-utils", version = "=1.7.14" }
|
||||
solana-runtime = { path = "../runtime", version = "=1.7.14" }
|
||||
solana-sdk = { path = "../sdk", version = "=1.7.14" }
|
||||
solana-streamer = { path = "../streamer", version = "=1.7.14" }
|
||||
solana-version = { path = "../version", version = "=1.7.14" }
|
||||
|
||||
[dev-dependencies]
|
||||
solana-local-cluster = { path = "../local-cluster", version = "=1.7.0" }
|
||||
solana-local-cluster = { path = "../local-cluster", version = "=1.7.14" }
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
@ -451,13 +451,13 @@ fn swapper<T>(
|
||||
let to_swap_txs: Vec<_> = to_swap
|
||||
.par_iter()
|
||||
.map(|(signer, swap, profit)| {
|
||||
let s: &Keypair = &signer;
|
||||
let s: &Keypair = signer;
|
||||
let owner = &signer.pubkey();
|
||||
let instruction = exchange_instruction::swap_request(
|
||||
owner,
|
||||
&swap.0.pubkey,
|
||||
&swap.1.pubkey,
|
||||
&profit,
|
||||
profit,
|
||||
);
|
||||
let message = Message::new(&[instruction], Some(&s.pubkey()));
|
||||
Transaction::new(&[s], message, blockhash)
|
||||
@ -600,7 +600,7 @@ fn trader<T>(
|
||||
src,
|
||||
),
|
||||
];
|
||||
let message = Message::new(&instructions, Some(&owner_pubkey));
|
||||
let message = Message::new(&instructions, Some(owner_pubkey));
|
||||
Transaction::new(&[owner.as_ref(), trade], message, blockhash)
|
||||
})
|
||||
.collect();
|
||||
@ -739,7 +739,7 @@ pub fn fund_keys<T: Client>(client: &T, source: &Keypair, dests: &[Arc<Keypair>]
|
||||
let mut to_fund_txs: Vec<_> = chunk
|
||||
.par_iter()
|
||||
.map(|(k, m)| {
|
||||
let instructions = system_instruction::transfer_many(&k.pubkey(), &m);
|
||||
let instructions = system_instruction::transfer_many(&k.pubkey(), m);
|
||||
let message = Message::new(&instructions, Some(&k.pubkey()));
|
||||
(k.clone(), Transaction::new_unsigned(message))
|
||||
})
|
||||
@ -777,7 +777,7 @@ pub fn fund_keys<T: Client>(client: &T, source: &Keypair, dests: &[Arc<Keypair>]
|
||||
let mut waits = 0;
|
||||
loop {
|
||||
sleep(Duration::from_millis(200));
|
||||
to_fund_txs.retain(|(_, tx)| !verify_funding_transfer(client, &tx, amount));
|
||||
to_fund_txs.retain(|(_, tx)| !verify_funding_transfer(client, tx, amount));
|
||||
if to_fund_txs.is_empty() {
|
||||
break;
|
||||
}
|
||||
@ -836,7 +836,7 @@ pub fn create_token_accounts<T: Client>(
|
||||
);
|
||||
let request_ix =
|
||||
exchange_instruction::account_request(owner_pubkey, &new_keypair.pubkey());
|
||||
let message = Message::new(&[create_ix, request_ix], Some(&owner_pubkey));
|
||||
let message = Message::new(&[create_ix, request_ix], Some(owner_pubkey));
|
||||
(
|
||||
(from_keypair, new_keypair),
|
||||
Transaction::new_unsigned(message),
|
||||
@ -872,7 +872,7 @@ pub fn create_token_accounts<T: Client>(
|
||||
let mut waits = 0;
|
||||
while !to_create_txs.is_empty() {
|
||||
sleep(Duration::from_millis(200));
|
||||
to_create_txs.retain(|(_, tx)| !verify_transaction(client, &tx));
|
||||
to_create_txs.retain(|(_, tx)| !verify_transaction(client, tx));
|
||||
if to_create_txs.is_empty() {
|
||||
break;
|
||||
}
|
||||
@ -958,7 +958,7 @@ fn compute_and_report_stats(maxes: &Arc<RwLock<Vec<(String, SampleStats)>>>, tot
|
||||
|
||||
fn generate_keypairs(num: u64) -> Vec<Keypair> {
|
||||
let mut seed = [0_u8; 32];
|
||||
seed.copy_from_slice(&Keypair::new().pubkey().as_ref());
|
||||
seed.copy_from_slice(Keypair::new().pubkey().as_ref());
|
||||
let mut rnd = GenKeys::new(seed);
|
||||
rnd.gen_n_keypairs(num)
|
||||
}
|
||||
@ -989,7 +989,7 @@ pub fn airdrop_lamports<T: Client>(
|
||||
let (blockhash, _fee_calculator, _last_valid_slot) = client
|
||||
.get_recent_blockhash_with_commitment(CommitmentConfig::processed())
|
||||
.expect("Failed to get blockhash");
|
||||
match request_airdrop_transaction(&faucet_addr, &id.pubkey(), amount_to_drop, blockhash) {
|
||||
match request_airdrop_transaction(faucet_addr, &id.pubkey(), amount_to_drop, blockhash) {
|
||||
Ok(transaction) => {
|
||||
let signature = client.async_send_transaction(transaction).unwrap();
|
||||
|
||||
|
@ -7,6 +7,7 @@ use crate::bench::{airdrop_lamports, create_client_accounts_file, do_bench_excha
|
||||
use log::*;
|
||||
use solana_gossip::gossip_service::{discover_cluster, get_multi_client};
|
||||
use solana_sdk::signature::Signer;
|
||||
use solana_streamer::socket::SocketAddrSpace;
|
||||
|
||||
fn main() {
|
||||
solana_logger::setup();
|
||||
@ -55,11 +56,12 @@ fn main() {
|
||||
);
|
||||
} else {
|
||||
info!("Connecting to the cluster");
|
||||
let nodes = discover_cluster(&entrypoint_addr, num_nodes).unwrap_or_else(|_| {
|
||||
panic!("Failed to discover nodes");
|
||||
});
|
||||
let nodes = discover_cluster(&entrypoint_addr, num_nodes, SocketAddrSpace::Unspecified)
|
||||
.unwrap_or_else(|_| {
|
||||
panic!("Failed to discover nodes");
|
||||
});
|
||||
|
||||
let (client, num_clients) = get_multi_client(&nodes);
|
||||
let (client, num_clients) = get_multi_client(&nodes, &SocketAddrSpace::Unspecified);
|
||||
|
||||
info!("{} nodes found", num_clients);
|
||||
if num_clients < num_nodes {
|
||||
|
@ -15,6 +15,7 @@ use solana_sdk::{
|
||||
genesis_config::create_genesis_config,
|
||||
signature::{Keypair, Signer},
|
||||
};
|
||||
use solana_streamer::socket::SocketAddrSpace;
|
||||
use std::{process::exit, sync::mpsc::channel, time::Duration};
|
||||
|
||||
#[test]
|
||||
@ -43,13 +44,19 @@ fn test_exchange_local_cluster() {
|
||||
} = config;
|
||||
let accounts_in_groups = batch_size * account_groups;
|
||||
|
||||
let cluster = LocalCluster::new(&mut ClusterConfig {
|
||||
node_stakes: vec![100_000; NUM_NODES],
|
||||
cluster_lamports: 100_000_000_000_000,
|
||||
validator_configs: make_identical_validator_configs(&ValidatorConfig::default(), NUM_NODES),
|
||||
native_instruction_processors: [solana_exchange_program!()].to_vec(),
|
||||
..ClusterConfig::default()
|
||||
});
|
||||
let cluster = LocalCluster::new(
|
||||
&mut ClusterConfig {
|
||||
node_stakes: vec![100_000; NUM_NODES],
|
||||
cluster_lamports: 100_000_000_000_000,
|
||||
validator_configs: make_identical_validator_configs(
|
||||
&ValidatorConfig::default(),
|
||||
NUM_NODES,
|
||||
),
|
||||
native_instruction_processors: [solana_exchange_program!()].to_vec(),
|
||||
..ClusterConfig::default()
|
||||
},
|
||||
SocketAddrSpace::Unspecified,
|
||||
);
|
||||
|
||||
let faucet_keypair = Keypair::new();
|
||||
cluster.transfer(
|
||||
@ -66,13 +73,17 @@ fn test_exchange_local_cluster() {
|
||||
.expect("faucet_addr");
|
||||
|
||||
info!("Connecting to the cluster");
|
||||
let nodes =
|
||||
discover_cluster(&cluster.entry_point_info.gossip, NUM_NODES).unwrap_or_else(|err| {
|
||||
error!("Failed to discover {} nodes: {:?}", NUM_NODES, err);
|
||||
exit(1);
|
||||
});
|
||||
let nodes = discover_cluster(
|
||||
&cluster.entry_point_info.gossip,
|
||||
NUM_NODES,
|
||||
SocketAddrSpace::Unspecified,
|
||||
)
|
||||
.unwrap_or_else(|err| {
|
||||
error!("Failed to discover {} nodes: {:?}", NUM_NODES, err);
|
||||
exit(1);
|
||||
});
|
||||
|
||||
let (client, num_clients) = get_multi_client(&nodes);
|
||||
let (client, num_clients) = get_multi_client(&nodes, &SocketAddrSpace::Unspecified);
|
||||
|
||||
info!("clients: {}", num_clients);
|
||||
assert!(num_clients >= NUM_NODES);
|
||||
|
@ -2,7 +2,7 @@
|
||||
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
|
||||
edition = "2018"
|
||||
name = "solana-bench-streamer"
|
||||
version = "1.7.0"
|
||||
version = "1.7.14"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
@ -10,11 +10,11 @@ publish = false
|
||||
|
||||
[dependencies]
|
||||
clap = "2.33.1"
|
||||
solana-clap-utils = { path = "../clap-utils", version = "=1.7.0" }
|
||||
solana-streamer = { path = "../streamer", version = "=1.7.0" }
|
||||
solana-logger = { path = "../logger", version = "=1.7.0" }
|
||||
solana-net-utils = { path = "../net-utils", version = "=1.7.0" }
|
||||
solana-version = { path = "../version", version = "=1.7.0" }
|
||||
solana-clap-utils = { path = "../clap-utils", version = "=1.7.14" }
|
||||
solana-streamer = { path = "../streamer", version = "=1.7.14" }
|
||||
solana-logger = { path = "../logger", version = "=1.7.14" }
|
||||
solana-net-utils = { path = "../net-utils", version = "=1.7.14" }
|
||||
solana-version = { path = "../version", version = "=1.7.14" }
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
@ -18,7 +18,7 @@ fn producer(addr: &SocketAddr, exit: Arc<AtomicBool>) -> JoinHandle<()> {
|
||||
msgs.packets.resize(10, Packet::default());
|
||||
for w in msgs.packets.iter_mut() {
|
||||
w.meta.size = PACKET_DATA_SIZE;
|
||||
w.meta.set_addr(&addr);
|
||||
w.meta.set_addr(addr);
|
||||
}
|
||||
let msgs = Arc::new(msgs);
|
||||
spawn(move || loop {
|
||||
@ -92,6 +92,7 @@ fn main() -> Result<()> {
|
||||
recycler.clone(),
|
||||
"bench-streamer-test",
|
||||
1,
|
||||
true,
|
||||
));
|
||||
}
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
|
||||
edition = "2018"
|
||||
name = "solana-bench-tps"
|
||||
version = "1.7.0"
|
||||
version = "1.7.14"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
@ -15,23 +15,24 @@ log = "0.4.11"
|
||||
rayon = "1.5.0"
|
||||
serde_json = "1.0.56"
|
||||
serde_yaml = "0.8.13"
|
||||
solana-clap-utils = { path = "../clap-utils", version = "=1.7.0" }
|
||||
solana-core = { path = "../core", version = "=1.7.0" }
|
||||
solana-genesis = { path = "../genesis", version = "=1.7.0" }
|
||||
solana-client = { path = "../client", version = "=1.7.0" }
|
||||
solana-faucet = { path = "../faucet", version = "=1.7.0" }
|
||||
solana-gossip = { path = "../gossip", version = "=1.7.0" }
|
||||
solana-logger = { path = "../logger", version = "=1.7.0" }
|
||||
solana-metrics = { path = "../metrics", version = "=1.7.0" }
|
||||
solana-measure = { path = "../measure", version = "=1.7.0" }
|
||||
solana-net-utils = { path = "../net-utils", version = "=1.7.0" }
|
||||
solana-runtime = { path = "../runtime", version = "=1.7.0" }
|
||||
solana-sdk = { path = "../sdk", version = "=1.7.0" }
|
||||
solana-version = { path = "../version", version = "=1.7.0" }
|
||||
solana-clap-utils = { path = "../clap-utils", version = "=1.7.14" }
|
||||
solana-core = { path = "../core", version = "=1.7.14" }
|
||||
solana-genesis = { path = "../genesis", version = "=1.7.14" }
|
||||
solana-client = { path = "../client", version = "=1.7.14" }
|
||||
solana-faucet = { path = "../faucet", version = "=1.7.14" }
|
||||
solana-gossip = { path = "../gossip", version = "=1.7.14" }
|
||||
solana-logger = { path = "../logger", version = "=1.7.14" }
|
||||
solana-metrics = { path = "../metrics", version = "=1.7.14" }
|
||||
solana-measure = { path = "../measure", version = "=1.7.14" }
|
||||
solana-net-utils = { path = "../net-utils", version = "=1.7.14" }
|
||||
solana-runtime = { path = "../runtime", version = "=1.7.14" }
|
||||
solana-sdk = { path = "../sdk", version = "=1.7.14" }
|
||||
solana-streamer = { path = "../streamer", version = "=1.7.14" }
|
||||
solana-version = { path = "../version", version = "=1.7.14" }
|
||||
|
||||
[dev-dependencies]
|
||||
serial_test = "0.4.0"
|
||||
solana-local-cluster = { path = "../local-cluster", version = "=1.7.0" }
|
||||
solana-local-cluster = { path = "../local-cluster", version = "=1.7.14" }
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
@ -544,12 +544,12 @@ impl<'a> FundingTransactions<'a> for Vec<(&'a Keypair, Transaction)> {
|
||||
|
||||
// re-sign retained to_fund_txes with updated blockhash
|
||||
self.sign(blockhash);
|
||||
self.send(&client);
|
||||
self.send(client);
|
||||
|
||||
// Sleep a few slots to allow transactions to process
|
||||
sleep(Duration::from_secs(1));
|
||||
|
||||
self.verify(&client, to_lamports);
|
||||
self.verify(client, to_lamports);
|
||||
|
||||
// retry anything that seems to have dropped through cracks
|
||||
// again since these txs are all or nothing, they're fine to
|
||||
@ -564,7 +564,7 @@ impl<'a> FundingTransactions<'a> for Vec<(&'a Keypair, Transaction)> {
|
||||
let to_fund_txs: Vec<(&Keypair, Transaction)> = to_fund
|
||||
.par_iter()
|
||||
.map(|(k, t)| {
|
||||
let instructions = system_instruction::transfer_many(&k.pubkey(), &t);
|
||||
let instructions = system_instruction::transfer_many(&k.pubkey(), t);
|
||||
let message = Message::new(&instructions, Some(&k.pubkey()));
|
||||
(*k, Transaction::new_unsigned(message))
|
||||
})
|
||||
@ -617,7 +617,7 @@ impl<'a> FundingTransactions<'a> for Vec<(&'a Keypair, Transaction)> {
|
||||
return None;
|
||||
}
|
||||
|
||||
let verified = if verify_funding_transfer(&client, &tx, to_lamports) {
|
||||
let verified = if verify_funding_transfer(&client, tx, to_lamports) {
|
||||
verified_txs.fetch_add(1, Ordering::Relaxed);
|
||||
Some(k.pubkey())
|
||||
} else {
|
||||
@ -733,7 +733,7 @@ pub fn airdrop_lamports<T: Client>(
|
||||
);
|
||||
|
||||
let (blockhash, _fee_calculator) = get_recent_blockhash(client);
|
||||
match request_airdrop_transaction(&faucet_addr, &id.pubkey(), airdrop_amount, blockhash) {
|
||||
match request_airdrop_transaction(faucet_addr, &id.pubkey(), airdrop_amount, blockhash) {
|
||||
Ok(transaction) => {
|
||||
let mut tries = 0;
|
||||
loop {
|
||||
|
@ -7,6 +7,7 @@ use solana_gossip::gossip_service::{discover_cluster, get_client, get_multi_clie
|
||||
use solana_sdk::fee_calculator::FeeRateGovernor;
|
||||
use solana_sdk::signature::{Keypair, Signer};
|
||||
use solana_sdk::system_program;
|
||||
use solana_streamer::socket::SocketAddrSpace;
|
||||
use std::{collections::HashMap, fs::File, io::prelude::*, path::Path, process::exit, sync::Arc};
|
||||
|
||||
/// Number of signatures for all transactions in ~1 week at ~100K TPS
|
||||
@ -39,7 +40,7 @@ fn main() {
|
||||
let keypair_count = *tx_count * keypair_multiplier;
|
||||
if *write_to_client_file {
|
||||
info!("Generating {} keypairs", keypair_count);
|
||||
let (keypairs, _) = generate_keypairs(&id, keypair_count as u64);
|
||||
let (keypairs, _) = generate_keypairs(id, keypair_count as u64);
|
||||
let num_accounts = keypairs.len() as u64;
|
||||
let max_fee =
|
||||
FeeRateGovernor::new(*target_lamports_per_signature, 0).max_lamports_per_signature;
|
||||
@ -68,13 +69,14 @@ fn main() {
|
||||
}
|
||||
|
||||
info!("Connecting to the cluster");
|
||||
let nodes = discover_cluster(&entrypoint_addr, *num_nodes).unwrap_or_else(|err| {
|
||||
eprintln!("Failed to discover {} nodes: {:?}", num_nodes, err);
|
||||
exit(1);
|
||||
});
|
||||
let nodes = discover_cluster(entrypoint_addr, *num_nodes, SocketAddrSpace::Unspecified)
|
||||
.unwrap_or_else(|err| {
|
||||
eprintln!("Failed to discover {} nodes: {:?}", num_nodes, err);
|
||||
exit(1);
|
||||
});
|
||||
|
||||
let client = if *multi_client {
|
||||
let (client, num_clients) = get_multi_client(&nodes);
|
||||
let (client, num_clients) = get_multi_client(&nodes, &SocketAddrSpace::Unspecified);
|
||||
if nodes.len() < num_clients {
|
||||
eprintln!(
|
||||
"Error: Insufficient nodes discovered. Expecting {} or more",
|
||||
@ -88,7 +90,7 @@ fn main() {
|
||||
let mut target_client = None;
|
||||
for node in nodes {
|
||||
if node.id == *target_node {
|
||||
target_client = Some(Arc::new(get_client(&[node])));
|
||||
target_client = Some(Arc::new(get_client(&[node], &SocketAddrSpace::Unspecified)));
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -97,7 +99,7 @@ fn main() {
|
||||
exit(1);
|
||||
})
|
||||
} else {
|
||||
Arc::new(get_client(&nodes))
|
||||
Arc::new(get_client(&nodes, &SocketAddrSpace::Unspecified))
|
||||
};
|
||||
|
||||
let keypairs = if *read_from_client_file {
|
||||
@ -135,7 +137,7 @@ fn main() {
|
||||
generate_and_fund_keypairs(
|
||||
client.clone(),
|
||||
Some(*faucet_addr),
|
||||
&id,
|
||||
id,
|
||||
keypair_count,
|
||||
*num_lamports_per_account,
|
||||
)
|
||||
|
@ -13,6 +13,7 @@ use solana_local_cluster::{
|
||||
validator_configs::make_identical_validator_configs,
|
||||
};
|
||||
use solana_sdk::signature::{Keypair, Signer};
|
||||
use solana_streamer::socket::SocketAddrSpace;
|
||||
use std::{
|
||||
sync::{mpsc::channel, Arc},
|
||||
time::Duration,
|
||||
@ -23,13 +24,19 @@ fn test_bench_tps_local_cluster(config: Config) {
|
||||
|
||||
solana_logger::setup();
|
||||
const NUM_NODES: usize = 1;
|
||||
let cluster = LocalCluster::new(&mut ClusterConfig {
|
||||
node_stakes: vec![999_990; NUM_NODES],
|
||||
cluster_lamports: 200_000_000,
|
||||
validator_configs: make_identical_validator_configs(&ValidatorConfig::default(), NUM_NODES),
|
||||
native_instruction_processors,
|
||||
..ClusterConfig::default()
|
||||
});
|
||||
let cluster = LocalCluster::new(
|
||||
&mut ClusterConfig {
|
||||
node_stakes: vec![999_990; NUM_NODES],
|
||||
cluster_lamports: 200_000_000,
|
||||
validator_configs: make_identical_validator_configs(
|
||||
&ValidatorConfig::default(),
|
||||
NUM_NODES,
|
||||
),
|
||||
native_instruction_processors,
|
||||
..ClusterConfig::default()
|
||||
},
|
||||
SocketAddrSpace::Unspecified,
|
||||
);
|
||||
|
||||
let faucet_keypair = Keypair::new();
|
||||
cluster.transfer(
|
||||
|
@ -148,6 +148,33 @@ all_test_steps() {
|
||||
command_step stable ". ci/rust-version.sh; ci/docker-run.sh \$\$rust_stable_docker_image ci/test-stable.sh" 60
|
||||
wait_step
|
||||
|
||||
# BPF test suite
|
||||
if affects \
|
||||
.rs$ \
|
||||
Cargo.lock$ \
|
||||
Cargo.toml$ \
|
||||
^ci/rust-version.sh \
|
||||
^ci/test-stable-bpf.sh \
|
||||
^ci/test-stable.sh \
|
||||
^ci/test-local-cluster.sh \
|
||||
^core/build.rs \
|
||||
^fetch-perf-libs.sh \
|
||||
^programs/ \
|
||||
^sdk/ \
|
||||
; then
|
||||
cat >> "$output_file" <<"EOF"
|
||||
- command: "ci/test-stable-bpf.sh"
|
||||
name: "stable-bpf"
|
||||
timeout_in_minutes: 20
|
||||
artifact_paths: "bpf-dumps.tar.bz2"
|
||||
agents:
|
||||
- "queue=default"
|
||||
EOF
|
||||
else
|
||||
annotate --style info \
|
||||
"Stable-BPF skipped as no relevant files were modified"
|
||||
fi
|
||||
|
||||
# Perf test suite
|
||||
if affects \
|
||||
.rs$ \
|
||||
@ -165,7 +192,7 @@ all_test_steps() {
|
||||
cat >> "$output_file" <<"EOF"
|
||||
- command: "ci/test-stable-perf.sh"
|
||||
name: "stable-perf"
|
||||
timeout_in_minutes: 40
|
||||
timeout_in_minutes: 20
|
||||
artifact_paths: "log-*.txt"
|
||||
agents:
|
||||
- "queue=cuda"
|
||||
|
@ -3,13 +3,19 @@
|
||||
# Pull requests to not run these steps.
|
||||
steps:
|
||||
- command: "ci/publish-tarball.sh"
|
||||
agents:
|
||||
- "queue=release-build"
|
||||
timeout_in_minutes: 60
|
||||
name: "publish tarball"
|
||||
- wait
|
||||
- command: "sdk/docker-solana/build.sh"
|
||||
agents:
|
||||
- "queue=release-build"
|
||||
timeout_in_minutes: 60
|
||||
name: "publish docker"
|
||||
- command: "ci/publish-crate.sh"
|
||||
agents:
|
||||
- "queue=release-build"
|
||||
timeout_in_minutes: 240
|
||||
name: "publish crate"
|
||||
branches: "!master"
|
||||
|
@ -28,16 +28,22 @@ cargo_audit_ignores=(
|
||||
# Blocked on multiple crates updating `time` to >= 0.2.23
|
||||
--ignore RUSTSEC-2020-0071
|
||||
|
||||
# difference is unmaintained
|
||||
#
|
||||
# Blocked on predicates v1.0.6 removing its dependency on `difference`
|
||||
--ignore RUSTSEC-2020-0095
|
||||
|
||||
# generic-array: arr! macro erases lifetimes
|
||||
#
|
||||
# Blocked on libsecp256k1 releasing with upgraded dependencies
|
||||
# https://github.com/paritytech/libsecp256k1/issues/66
|
||||
--ignore RUSTSEC-2020-0146
|
||||
|
||||
# hyper: Lenient `hyper` header parsing of `Content-Length` could allow request smuggling
|
||||
#
|
||||
# Blocked on jsonrpc removing dependency on unmaintained `websocket`
|
||||
# https://github.com/paritytech/jsonrpc/issues/605
|
||||
--ignore RUSTSEC-2021-0078
|
||||
|
||||
# hyper: Integer overflow in `hyper`'s parsing of the `Transfer-Encoding` header leads to data loss
|
||||
#
|
||||
# Blocked on jsonrpc removing dependency on unmaintained `websocket`
|
||||
# https://github.com/paritytech/jsonrpc/issues/605
|
||||
--ignore RUSTSEC-2021-0079
|
||||
)
|
||||
scripts/cargo-for-all-lock-files.sh stable audit "${cargo_audit_ignores[@]}"
|
||||
|
@ -74,10 +74,13 @@ else
|
||||
export CI_BUILD_ID=
|
||||
export CI_COMMIT=
|
||||
export CI_JOB_ID=
|
||||
export CI_OS_NAME=
|
||||
export CI_PULL_REQUEST=
|
||||
export CI_REPO_SLUG=
|
||||
export CI_TAG=
|
||||
# Don't override ci/run-local.sh
|
||||
if [[ -z $CI_LOCAL_RUN ]]; then
|
||||
export CI_OS_NAME=
|
||||
fi
|
||||
fi
|
||||
|
||||
cat <<EOF
|
||||
|
@ -127,7 +127,7 @@ startNode() {
|
||||
waitForNodeToInit() {
|
||||
declare initCompleteFile=$1
|
||||
while [[ ! -r $initCompleteFile ]]; do
|
||||
if [[ $SECONDS -ge 240 ]]; then
|
||||
if [[ $SECONDS -ge 300 ]]; then
|
||||
echo "^^^ +++"
|
||||
echo "Error: $initCompleteFile not found in $SECONDS seconds"
|
||||
exit 1
|
||||
|
@ -12,10 +12,14 @@ import json
|
||||
import subprocess
|
||||
import sys;
|
||||
|
||||
real_file = os.path.realpath(__file__)
|
||||
ci_path = os.path.dirname(real_file)
|
||||
src_root = os.path.dirname(ci_path)
|
||||
|
||||
def load_metadata():
|
||||
cmd = f'{src_root}/cargo metadata --no-deps --format-version=1'
|
||||
return json.loads(subprocess.Popen(
|
||||
'cargo metadata --no-deps --format-version=1',
|
||||
shell=True, stdout=subprocess.PIPE).communicate()[0])
|
||||
cmd, shell=True, stdout=subprocess.PIPE).communicate()[0])
|
||||
|
||||
def get_packages():
|
||||
metadata = load_metadata()
|
||||
|
55
ci/run-local.sh
Executable file
55
ci/run-local.sh
Executable file
@ -0,0 +1,55 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
export CI_LOCAL_RUN=true
|
||||
|
||||
set -e
|
||||
|
||||
case $(uname -o) in
|
||||
*/Linux)
|
||||
export CI_OS_NAME=linux
|
||||
;;
|
||||
*)
|
||||
echo "local CI runs are only supported on Linux" 1>&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
steps=()
|
||||
steps+=(test-sanity)
|
||||
steps+=(shellcheck)
|
||||
steps+=(test-checks)
|
||||
steps+=(test-coverage)
|
||||
steps+=(test-stable)
|
||||
steps+=(test-stable-bpf)
|
||||
steps+=(test-stable-perf)
|
||||
steps+=(test-downstream-builds)
|
||||
steps+=(test-bench)
|
||||
steps+=(test-local-cluster)
|
||||
|
||||
step_index=0
|
||||
if [[ -n "$1" ]]; then
|
||||
start_step="$1"
|
||||
while [[ $step_index -lt ${#steps[@]} ]]; do
|
||||
step="${steps[$step_index]}"
|
||||
if [[ "$step" = "$start_step" ]]; then
|
||||
break
|
||||
fi
|
||||
step_index=$((step_index + 1))
|
||||
done
|
||||
if [[ $step_index -eq ${#steps[@]} ]]; then
|
||||
echo "unexpected start step: \"$start_step\"" 1>&2
|
||||
exit 1
|
||||
else
|
||||
echo "** starting at step: \"$start_step\" **"
|
||||
echo
|
||||
fi
|
||||
fi
|
||||
|
||||
while [[ $step_index -lt ${#steps[@]} ]]; do
|
||||
step="${steps[$step_index]}"
|
||||
cmd="ci/${step}.sh"
|
||||
$cmd
|
||||
step_index=$((step_index + 1))
|
||||
done
|
@ -7,7 +7,7 @@ source multinode-demo/common.sh
|
||||
|
||||
rm -rf config/run/init-completed config/ledger config/snapshot-ledger
|
||||
|
||||
SOLANA_RUN_SH_VALIDATOR_ARGS="--snapshot-interval-slots 200" timeout 120 ./run.sh &
|
||||
SOLANA_RUN_SH_VALIDATOR_ARGS="--snapshot-interval-slots 200" timeout 120 ./scripts/run.sh &
|
||||
pid=$!
|
||||
|
||||
attempts=20
|
||||
|
@ -76,7 +76,7 @@ RestartForceExitStatus=SIGPIPE
|
||||
TimeoutStartSec=10
|
||||
TimeoutStopSec=0
|
||||
KillMode=process
|
||||
LimitNOFILE=700000
|
||||
LimitNOFILE=1000000
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
@ -8,5 +8,5 @@ source "$HERE"/utils.sh
|
||||
ensure_env || exit 1
|
||||
|
||||
# Allow more files to be opened by a user
|
||||
echo "* - nofile 700000" > /etc/security/limits.d/90-solana-nofiles.conf
|
||||
echo "* - nofile 1000000" > /etc/security/limits.d/90-solana-nofiles.conf
|
||||
|
||||
|
@ -49,6 +49,10 @@ _ "$cargo" nightly bench --manifest-path runtime/Cargo.toml ${V:+--verbose} \
|
||||
_ "$cargo" nightly bench --manifest-path gossip/Cargo.toml ${V:+--verbose} \
|
||||
-- -Z unstable-options --format=json | tee -a "$BENCH_FILE"
|
||||
|
||||
# Run poh benches
|
||||
_ "$cargo" nightly bench --manifest-path poh/Cargo.toml ${V:+--verbose} \
|
||||
-- -Z unstable-options --format=json | tee -a "$BENCH_FILE"
|
||||
|
||||
# Run core benches
|
||||
_ "$cargo" nightly bench --manifest-path core/Cargo.toml ${V:+--verbose} \
|
||||
-- -Z unstable-options --format=json | tee -a "$BENCH_FILE"
|
||||
|
@ -14,7 +14,7 @@ scripts/increment-cargo-version.sh check
|
||||
|
||||
# Disallow uncommitted Cargo.lock changes
|
||||
(
|
||||
_ scripts/cargo-for-all-lock-files.sh tree
|
||||
_ scripts/cargo-for-all-lock-files.sh tree >/dev/null
|
||||
set +e
|
||||
if ! _ git diff --exit-code; then
|
||||
echo -e "\nError: Uncommitted Cargo.lock changes" 1>&2
|
||||
@ -35,8 +35,10 @@ echo --- build environment
|
||||
"$cargo" stable clippy --version --verbose
|
||||
"$cargo" nightly clippy --version --verbose
|
||||
|
||||
# audit is done only with stable
|
||||
# audit is done only with "$cargo stable"
|
||||
"$cargo" stable audit --version
|
||||
|
||||
grcov --version
|
||||
)
|
||||
|
||||
export RUST_BACKTRACE=1
|
||||
@ -65,7 +67,8 @@ _ ci/order-crates-for-publishing.py
|
||||
|
||||
# -Z... is needed because of clippy bug: https://github.com/rust-lang/rust-clippy/issues/4612
|
||||
# run nightly clippy for `sdk/` as there's a moderate amount of nightly-only code there
|
||||
_ "$cargo" nightly clippy -Zunstable-options --workspace --all-targets -- --deny=warnings --deny=clippy::integer_arithmetic
|
||||
_ "$cargo" nightly clippy -Zunstable-options --workspace --all-targets -- \
|
||||
--deny=warnings --deny=clippy::integer_arithmetic --allow=clippy::inconsistent_struct_constructor
|
||||
|
||||
_ "$cargo" stable fmt --all -- --check
|
||||
|
||||
|
9
ci/test-downstream-builds.sh
Executable file
9
ci/test-downstream-builds.sh
Executable file
@ -0,0 +1,9 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
export CI_LOCAL_RUN=true
|
||||
|
||||
set -ex
|
||||
|
||||
scripts/build-downstream-projects.sh
|
1
ci/test-stable-bpf.sh
Symbolic link
1
ci/test-stable-bpf.sh
Symbolic link
@ -0,0 +1 @@
|
||||
test-stable.sh
|
@ -21,10 +21,6 @@ export RUST_BACKTRACE=1
|
||||
export RUSTFLAGS="-D warnings"
|
||||
source scripts/ulimit-n.sh
|
||||
|
||||
# Clear the C dependency files, if dependency moves these files are not regenerated
|
||||
test -d target/debug/bpf && find target/debug/bpf -name '*.d' -delete
|
||||
test -d target/release/bpf && find target/release/bpf -name '*.d' -delete
|
||||
|
||||
# Limit compiler jobs to reduce memory usage
|
||||
# on machines with 2gb/thread of memory
|
||||
NPROC=$(nproc)
|
||||
@ -35,17 +31,25 @@ case $testName in
|
||||
test-stable)
|
||||
_ "$cargo" stable test --jobs "$NPROC" --all --exclude solana-local-cluster ${V:+--verbose} -- --nocapture
|
||||
;;
|
||||
test-stable-perf)
|
||||
test-stable-bpf)
|
||||
# Clear the C dependency files, if dependency moves these files are not regenerated
|
||||
test -d target/debug/bpf && find target/debug/bpf -name '*.d' -delete
|
||||
test -d target/release/bpf && find target/release/bpf -name '*.d' -delete
|
||||
|
||||
# rustfilt required for dumping BPF assembly listings
|
||||
"$cargo" install rustfilt
|
||||
|
||||
# solana-keygen required when building C programs
|
||||
_ "$cargo" build --manifest-path=keygen/Cargo.toml
|
||||
export PATH="$PWD/target/debug":$PATH
|
||||
cargo_build_bpf="$(realpath ./cargo-build-bpf)"
|
||||
|
||||
# BPF solana-sdk legacy compile test
|
||||
./cargo-build-bpf --manifest-path sdk/Cargo.toml
|
||||
"$cargo_build_bpf" --manifest-path sdk/Cargo.toml
|
||||
|
||||
# BPF Program unit tests
|
||||
"$cargo" test --manifest-path programs/bpf/Cargo.toml
|
||||
cargo-build-bpf --manifest-path programs/bpf/Cargo.toml --bpf-sdk sdk/bpf
|
||||
"$cargo_build_bpf" --manifest-path programs/bpf/Cargo.toml --bpf-sdk sdk/bpf
|
||||
|
||||
# BPF program system tests
|
||||
_ make -C programs/bpf/c tests
|
||||
@ -53,11 +57,32 @@ test-stable-perf)
|
||||
--manifest-path programs/bpf/Cargo.toml \
|
||||
--no-default-features --features=bpf_c,bpf_rust -- --nocapture
|
||||
|
||||
# Dump BPF program assembly listings
|
||||
for bpf_test in programs/bpf/rust/*; do
|
||||
if pushd "$bpf_test"; then
|
||||
"$cargo_build_bpf" --dump
|
||||
popd
|
||||
fi
|
||||
done
|
||||
|
||||
# BPF program instruction count assertion
|
||||
bpf_target_path=programs/bpf/target
|
||||
_ "$cargo" stable test \
|
||||
--manifest-path programs/bpf/Cargo.toml \
|
||||
--no-default-features --features=bpf_c,bpf_rust assert_instruction_count \
|
||||
-- --nocapture &> "${bpf_target_path}"/deploy/instuction_counts.txt
|
||||
|
||||
bpf_dump_archive="bpf-dumps.tar.bz2"
|
||||
rm -f "$bpf_dump_archive"
|
||||
tar cjvf "$bpf_dump_archive" "${bpf_target_path}"/{deploy/*.txt,bpfel-unknown-unknown/release/*.so}
|
||||
exit 0
|
||||
;;
|
||||
test-stable-perf)
|
||||
if [[ $(uname) = Linux ]]; then
|
||||
# Enable persistence mode to keep the CUDA kernel driver loaded, avoiding a
|
||||
# lengthy and unexpected delay the first time CUDA is involved when the driver
|
||||
# is not yet loaded.
|
||||
sudo --non-interactive ./net/scripts/enable-nvidia-persistence-mode.sh
|
||||
sudo --non-interactive ./net/scripts/enable-nvidia-persistence-mode.sh || true
|
||||
|
||||
rm -rf target/perf-libs
|
||||
./fetch-perf-libs.sh
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "solana-clap-utils"
|
||||
version = "1.7.0"
|
||||
version = "1.7.14"
|
||||
description = "Solana utilities for the clap"
|
||||
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
@ -12,10 +12,10 @@ edition = "2018"
|
||||
[dependencies]
|
||||
clap = "2.33.0"
|
||||
rpassword = "4.0"
|
||||
solana-remote-wallet = { path = "../remote-wallet", version = "=1.7.0" }
|
||||
solana-sdk = { path = "../sdk", version = "=1.7.0" }
|
||||
solana-remote-wallet = { path = "../remote-wallet", version = "=1.7.14" }
|
||||
solana-sdk = { path = "../sdk", version = "=1.7.14" }
|
||||
thiserror = "1.0.21"
|
||||
tiny-bip39 = "0.8.0"
|
||||
tiny-bip39 = "0.8.1"
|
||||
uriparse = "0.6.3"
|
||||
url = "2.1.0"
|
||||
chrono = "0.4"
|
||||
|
@ -24,9 +24,11 @@ use {
|
||||
},
|
||||
},
|
||||
std::{
|
||||
cell::RefCell,
|
||||
convert::TryFrom,
|
||||
error,
|
||||
io::{stdin, stdout, Write},
|
||||
ops::Deref,
|
||||
process::exit,
|
||||
str::FromStr,
|
||||
sync::Arc,
|
||||
@ -89,33 +91,49 @@ impl CliSignerInfo {
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
#[derive(Debug)]
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct DefaultSigner {
|
||||
pub arg_name: String,
|
||||
pub path: String,
|
||||
is_path_checked: RefCell<bool>,
|
||||
}
|
||||
|
||||
impl DefaultSigner {
|
||||
pub fn new(path: String) -> Self {
|
||||
pub fn new<AN: AsRef<str>, P: AsRef<str>>(arg_name: AN, path: P) -> Self {
|
||||
let arg_name = arg_name.as_ref().to_string();
|
||||
let path = path.as_ref().to_string();
|
||||
Self {
|
||||
arg_name: "keypair".to_string(),
|
||||
arg_name,
|
||||
path,
|
||||
..Self::default()
|
||||
}
|
||||
}
|
||||
pub fn from_path(path: String) -> Result<Self, Box<dyn error::Error>> {
|
||||
std::fs::metadata(&path)
|
||||
.map_err(|_| {
|
||||
std::io::Error::new(
|
||||
std::io::ErrorKind::Other,
|
||||
format!(
|
||||
"No default signer found, run \"solana-keygen new -o {}\" to create a new one",
|
||||
path
|
||||
),
|
||||
)
|
||||
.into()
|
||||
})
|
||||
.map(|_| Self::new(path))
|
||||
|
||||
fn path(&self) -> Result<&str, Box<dyn std::error::Error>> {
|
||||
if !self.is_path_checked.borrow().deref() {
|
||||
parse_signer_source(&self.path)
|
||||
.and_then(|s| {
|
||||
if let SignerSourceKind::Filepath(path) = &s.kind {
|
||||
std::fs::metadata(path).map(|_| ()).map_err(|e| e.into())
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
})
|
||||
.map_err(|_| {
|
||||
std::io::Error::new(
|
||||
std::io::ErrorKind::Other,
|
||||
format!(
|
||||
"No default signer found, run \"solana-keygen new -o {}\" to create a new one",
|
||||
self.path
|
||||
),
|
||||
)
|
||||
})?;
|
||||
*self.is_path_checked.borrow_mut() = true;
|
||||
}
|
||||
Ok(&self.path)
|
||||
}
|
||||
|
||||
pub fn generate_unique_signers(
|
||||
&self,
|
||||
bulk_signers: Vec<Option<Box<dyn Signer>>>,
|
||||
@ -145,7 +163,7 @@ impl DefaultSigner {
|
||||
matches: &ArgMatches,
|
||||
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
|
||||
) -> Result<Box<dyn Signer>, Box<dyn std::error::Error>> {
|
||||
signer_from_path(matches, &self.path, &self.arg_name, wallet_manager)
|
||||
signer_from_path(matches, self.path()?, &self.arg_name, wallet_manager)
|
||||
}
|
||||
|
||||
pub fn signer_from_path_with_config(
|
||||
@ -154,7 +172,13 @@ impl DefaultSigner {
|
||||
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
|
||||
config: &SignerFromPathConfig,
|
||||
) -> Result<Box<dyn Signer>, Box<dyn std::error::Error>> {
|
||||
signer_from_path_with_config(matches, &self.path, &self.arg_name, wallet_manager, config)
|
||||
signer_from_path_with_config(
|
||||
matches,
|
||||
self.path()?,
|
||||
&self.arg_name,
|
||||
wallet_manager,
|
||||
config,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -277,7 +301,9 @@ pub(crate) fn parse_signer_source<S: AsRef<str>>(
|
||||
ASK_KEYWORD => Ok(SignerSource::new_legacy(SignerSourceKind::Prompt)),
|
||||
_ => match Pubkey::from_str(source.as_str()) {
|
||||
Ok(pubkey) => Ok(SignerSource::new(SignerSourceKind::Pubkey(pubkey))),
|
||||
Err(_) => Ok(SignerSource::new(SignerSourceKind::Filepath(source))),
|
||||
Err(_) => std::fs::metadata(source.as_str())
|
||||
.map(|_| SignerSource::new(SignerSourceKind::Filepath(source)))
|
||||
.map_err(|err| err.into()),
|
||||
},
|
||||
}
|
||||
}
|
||||
@ -480,7 +506,7 @@ pub const SKIP_SEED_PHRASE_VALIDATION_ARG: ArgConstant<'static> = ArgConstant {
|
||||
|
||||
/// Prompts user for a passphrase and then asks for confirmirmation to check for mistakes
|
||||
pub fn prompt_passphrase(prompt: &str) -> Result<String, Box<dyn error::Error>> {
|
||||
let passphrase = prompt_password_stderr(&prompt)?;
|
||||
let passphrase = prompt_password_stderr(prompt)?;
|
||||
if !passphrase.is_empty() {
|
||||
let confirmed = rpassword::prompt_password_stderr("Enter same passphrase again: ")?;
|
||||
if confirmed != passphrase {
|
||||
@ -560,9 +586,9 @@ pub fn keypair_from_seed_phrase(
|
||||
let keypair = if skip_validation {
|
||||
let passphrase = prompt_passphrase(&passphrase_prompt)?;
|
||||
if legacy {
|
||||
keypair_from_seed_phrase_and_passphrase(&seed_phrase, &passphrase)?
|
||||
keypair_from_seed_phrase_and_passphrase(seed_phrase, &passphrase)?
|
||||
} else {
|
||||
let seed = generate_seed_from_seed_phrase_and_passphrase(&seed_phrase, &passphrase);
|
||||
let seed = generate_seed_from_seed_phrase_and_passphrase(seed_phrase, &passphrase);
|
||||
keypair_from_seed_and_derivation_path(&seed, derivation_path)?
|
||||
}
|
||||
} else {
|
||||
@ -590,7 +616,7 @@ pub fn keypair_from_seed_phrase(
|
||||
if legacy {
|
||||
keypair_from_seed(seed.as_bytes())?
|
||||
} else {
|
||||
keypair_from_seed_and_derivation_path(&seed.as_bytes(), derivation_path)?
|
||||
keypair_from_seed_and_derivation_path(seed.as_bytes(), derivation_path)?
|
||||
}
|
||||
};
|
||||
|
||||
@ -751,6 +777,10 @@ mod tests {
|
||||
// Catchall into SignerSource::Filepath fails
|
||||
let junk = "sometextthatisnotapubkeyorfile".to_string();
|
||||
assert!(Pubkey::from_str(&junk).is_err());
|
||||
assert!(matches!(
|
||||
parse_signer_source(&junk),
|
||||
Err(SignerSourceError::IoError(_))
|
||||
));
|
||||
|
||||
let prompt = "prompt:".to_string();
|
||||
assert!(matches!(
|
||||
|
@ -3,7 +3,7 @@ authors = ["Solana Maintainers <maintainers@solana.foundation>"]
|
||||
edition = "2018"
|
||||
name = "solana-cli-config"
|
||||
description = "Blockchain, Rebuilt for Scale"
|
||||
version = "1.7.0"
|
||||
version = "1.7.14"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
|
@ -107,24 +107,24 @@ mod test {
|
||||
#[test]
|
||||
fn compute_websocket_url() {
|
||||
assert_eq!(
|
||||
Config::compute_websocket_url(&"http://api.devnet.solana.com"),
|
||||
Config::compute_websocket_url("http://api.devnet.solana.com"),
|
||||
"ws://api.devnet.solana.com/".to_string()
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
Config::compute_websocket_url(&"https://api.devnet.solana.com"),
|
||||
Config::compute_websocket_url("https://api.devnet.solana.com"),
|
||||
"wss://api.devnet.solana.com/".to_string()
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
Config::compute_websocket_url(&"http://example.com:8899"),
|
||||
Config::compute_websocket_url("http://example.com:8899"),
|
||||
"ws://example.com:8900/".to_string()
|
||||
);
|
||||
assert_eq!(
|
||||
Config::compute_websocket_url(&"https://example.com:1234"),
|
||||
Config::compute_websocket_url("https://example.com:1234"),
|
||||
"wss://example.com:1235/".to_string()
|
||||
);
|
||||
|
||||
assert_eq!(Config::compute_websocket_url(&"garbage"), String::new());
|
||||
assert_eq!(Config::compute_websocket_url("garbage"), String::new());
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ authors = ["Solana Maintainers <maintainers@solana.foundation>"]
|
||||
edition = "2018"
|
||||
name = "solana-cli-output"
|
||||
description = "Blockchain, Rebuilt for Scale"
|
||||
version = "1.7.0"
|
||||
version = "1.7.14"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
@ -12,20 +12,20 @@ documentation = "https://docs.rs/solana-cli-output"
|
||||
[dependencies]
|
||||
base64 = "0.13.0"
|
||||
chrono = { version = "0.4.11", features = ["serde"] }
|
||||
console = "0.11.3"
|
||||
clap = "2.33.0"
|
||||
console = "0.14.1"
|
||||
humantime = "2.0.1"
|
||||
Inflector = "0.11.4"
|
||||
indicatif = "0.15.0"
|
||||
serde = "1.0.122"
|
||||
serde_derive = "1.0.103"
|
||||
serde_json = "1.0.56"
|
||||
solana-account-decoder = { path = "../account-decoder", version = "=1.7.0" }
|
||||
solana-clap-utils = { path = "../clap-utils", version = "=1.7.0" }
|
||||
solana-client = { path = "../client", version = "=1.7.0" }
|
||||
solana-sdk = { path = "../sdk", version = "=1.7.0" }
|
||||
solana-stake-program = { path = "../programs/stake", version = "=1.7.0" }
|
||||
solana-transaction-status = { path = "../transaction-status", version = "=1.7.0" }
|
||||
solana-vote-program = { path = "../programs/vote", version = "=1.7.0" }
|
||||
solana-account-decoder = { path = "../account-decoder", version = "=1.7.14" }
|
||||
solana-clap-utils = { path = "../clap-utils", version = "=1.7.14" }
|
||||
solana-client = { path = "../client", version = "=1.7.14" }
|
||||
solana-sdk = { path = "../sdk", version = "=1.7.14" }
|
||||
solana-transaction-status = { path = "../transaction-status", version = "=1.7.14" }
|
||||
solana-vote-program = { path = "../programs/vote", version = "=1.7.14" }
|
||||
spl-memo = { version = "=3.0.1", features = ["no-entrypoint"] }
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
|
@ -8,6 +8,7 @@ use {
|
||||
QuietDisplay, VerboseDisplay,
|
||||
},
|
||||
chrono::{Local, TimeZone},
|
||||
clap::ArgMatches,
|
||||
console::{style, Emoji},
|
||||
inflector::cases::titlecase::to_title_case,
|
||||
serde::{Deserialize, Serialize},
|
||||
@ -25,10 +26,10 @@ use {
|
||||
native_token::lamports_to_sol,
|
||||
pubkey::Pubkey,
|
||||
signature::Signature,
|
||||
stake::state::{Authorized, Lockup},
|
||||
stake_history::StakeHistoryEntry,
|
||||
transaction::{Transaction, TransactionError},
|
||||
},
|
||||
solana_stake_program::stake_state::{Authorized, Lockup},
|
||||
solana_transaction_status::{
|
||||
EncodedConfirmedBlock, EncodedTransaction, TransactionConfirmationStatus,
|
||||
UiTransactionStatusMeta,
|
||||
@ -47,7 +48,7 @@ use {
|
||||
|
||||
static WARNING: Emoji = Emoji("⚠️", "!");
|
||||
|
||||
#[derive(PartialEq)]
|
||||
#[derive(PartialEq, Debug)]
|
||||
pub enum OutputFormat {
|
||||
Display,
|
||||
Json,
|
||||
@ -77,6 +78,21 @@ impl OutputFormat {
|
||||
OutputFormat::JsonCompact => serde_json::to_value(item).unwrap().to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_matches(matches: &ArgMatches<'_>, output_name: &str, verbose: bool) -> Self {
|
||||
matches
|
||||
.value_of(output_name)
|
||||
.map(|value| match value {
|
||||
"json" => OutputFormat::Json,
|
||||
"json-compact" => OutputFormat::JsonCompact,
|
||||
_ => unreachable!(),
|
||||
})
|
||||
.unwrap_or(if verbose {
|
||||
OutputFormat::DisplayVerbose
|
||||
} else {
|
||||
OutputFormat::Display
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
@ -233,6 +249,10 @@ pub struct CliEpochInfo {
|
||||
pub epoch_info: EpochInfo,
|
||||
#[serde(skip)]
|
||||
pub average_slot_time_ms: u64,
|
||||
#[serde(skip)]
|
||||
pub start_block_time: Option<UnixTimestamp>,
|
||||
#[serde(skip)]
|
||||
pub current_block_time: Option<UnixTimestamp>,
|
||||
}
|
||||
|
||||
impl QuietDisplay for CliEpochInfo {}
|
||||
@ -277,21 +297,41 @@ impl fmt::Display for CliEpochInfo {
|
||||
remaining_slots_in_epoch
|
||||
),
|
||||
)?;
|
||||
let (time_elapsed, annotation) = if let (Some(start_block_time), Some(current_block_time)) =
|
||||
(self.start_block_time, self.current_block_time)
|
||||
{
|
||||
(
|
||||
Duration::from_secs((current_block_time - start_block_time) as u64),
|
||||
None,
|
||||
)
|
||||
} else {
|
||||
(
|
||||
slot_to_duration(self.epoch_info.slot_index, self.average_slot_time_ms),
|
||||
Some("* estimated based on current slot durations"),
|
||||
)
|
||||
};
|
||||
let time_remaining = slot_to_duration(remaining_slots_in_epoch, self.average_slot_time_ms);
|
||||
writeln_name_value(
|
||||
f,
|
||||
"Epoch Completed Time:",
|
||||
&format!(
|
||||
"{}/{} ({} remaining)",
|
||||
slot_to_human_time(self.epoch_info.slot_index, self.average_slot_time_ms),
|
||||
slot_to_human_time(self.epoch_info.slots_in_epoch, self.average_slot_time_ms),
|
||||
slot_to_human_time(remaining_slots_in_epoch, self.average_slot_time_ms)
|
||||
"{}{}/{} ({} remaining)",
|
||||
humantime::format_duration(time_elapsed).to_string(),
|
||||
if annotation.is_some() { "*" } else { "" },
|
||||
humantime::format_duration(time_elapsed + time_remaining).to_string(),
|
||||
humantime::format_duration(time_remaining).to_string(),
|
||||
),
|
||||
)
|
||||
)?;
|
||||
if let Some(annotation) = annotation {
|
||||
writeln!(f)?;
|
||||
writeln!(f, "{}", annotation)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn slot_to_human_time(slot: Slot, slot_time_ms: u64) -> String {
|
||||
humantime::format_duration(Duration::from_secs((slot * slot_time_ms) / 1000)).to_string()
|
||||
fn slot_to_duration(slot: Slot, slot_time_ms: u64) -> Duration {
|
||||
Duration::from_secs((slot * slot_time_ms) / 1000)
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Default)]
|
||||
@ -323,6 +363,8 @@ pub struct CliValidators {
|
||||
pub total_current_stake: u64,
|
||||
pub total_delinquent_stake: u64,
|
||||
pub validators: Vec<CliValidator>,
|
||||
pub average_skip_rate: f64,
|
||||
pub average_stake_weighted_skip_rate: f64,
|
||||
#[serde(skip_serializing)]
|
||||
pub validators_sort_order: CliValidatorsSortOrder,
|
||||
#[serde(skip_serializing)]
|
||||
@ -486,6 +528,18 @@ impl fmt::Display for CliValidators {
|
||||
writeln!(f, "{}", header)?;
|
||||
}
|
||||
|
||||
writeln!(f)?;
|
||||
writeln_name_value(
|
||||
f,
|
||||
"Average Stake-Weighted Skip Rate:",
|
||||
&format!("{:.2}%", self.average_stake_weighted_skip_rate,),
|
||||
)?;
|
||||
writeln_name_value(
|
||||
f,
|
||||
"Average Unweighted Skip Rate: ",
|
||||
&format!("{:.2}%", self.average_skip_rate),
|
||||
)?;
|
||||
|
||||
writeln!(f)?;
|
||||
writeln_name_value(
|
||||
f,
|
||||
@ -733,6 +787,7 @@ pub struct CliEpochReward {
|
||||
pub post_balance: u64, // lamports
|
||||
pub percent_change: f64,
|
||||
pub apr: Option<f64>,
|
||||
pub commission: Option<u8>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
@ -777,23 +832,27 @@ impl fmt::Display for CliKeyedEpochRewards {
|
||||
writeln!(f, "Epoch Rewards:")?;
|
||||
writeln!(
|
||||
f,
|
||||
" {:<44} {:<18} {:<18} {:>14} {:>14}",
|
||||
"Address", "Amount", "New Balance", "Percent Change", "APR"
|
||||
" {:<44} {:<18} {:<18} {:>14} {:>14} {:>10}",
|
||||
"Address", "Amount", "New Balance", "Percent Change", "APR", "Commission"
|
||||
)?;
|
||||
for keyed_reward in &self.rewards {
|
||||
match &keyed_reward.reward {
|
||||
Some(reward) => {
|
||||
writeln!(
|
||||
f,
|
||||
" {:<44} ◎{:<17.9} ◎{:<17.9} {:>13.2}% {}",
|
||||
" {:<44} ◎{:<17.9} ◎{:<17.9} {:>13.9}% {:>14} {:>10}",
|
||||
keyed_reward.address,
|
||||
lamports_to_sol(reward.amount),
|
||||
lamports_to_sol(reward.post_balance),
|
||||
reward.percent_change,
|
||||
reward
|
||||
.apr
|
||||
.map(|apr| format!("{:>13.2}%", apr))
|
||||
.map(|apr| format!("{:.2}%", apr))
|
||||
.unwrap_or_default(),
|
||||
reward
|
||||
.commission
|
||||
.map(|commission| format!("{}%", commission))
|
||||
.unwrap_or_else(|| "-".to_string())
|
||||
)?;
|
||||
}
|
||||
None => {
|
||||
@ -910,13 +969,13 @@ fn show_epoch_rewards(
|
||||
writeln!(f, "Epoch Rewards:")?;
|
||||
writeln!(
|
||||
f,
|
||||
" {:<6} {:<11} {:<18} {:<18} {:>14} {:>14}",
|
||||
"Epoch", "Reward Slot", "Amount", "New Balance", "Percent Change", "APR"
|
||||
" {:<6} {:<11} {:<18} {:<18} {:>14} {:>14} {:>10}",
|
||||
"Epoch", "Reward Slot", "Amount", "New Balance", "Percent Change", "APR", "Commission"
|
||||
)?;
|
||||
for reward in epoch_rewards {
|
||||
writeln!(
|
||||
f,
|
||||
" {:<6} {:<11} ◎{:<17.9} ◎{:<17.9} {:>13.2}% {}",
|
||||
" {:<6} {:<11} ◎{:<17.9} ◎{:<17.9} {:>13.9}% {:>14} {:>10}",
|
||||
reward.epoch,
|
||||
reward.effective_slot,
|
||||
lamports_to_sol(reward.amount),
|
||||
@ -924,8 +983,12 @@ fn show_epoch_rewards(
|
||||
reward.percent_change,
|
||||
reward
|
||||
.apr
|
||||
.map(|apr| format!("{:>13.2}%", apr))
|
||||
.map(|apr| format!("{:.2}%", apr))
|
||||
.unwrap_or_default(),
|
||||
reward
|
||||
.commission
|
||||
.map(|commission| format!("{}%", commission))
|
||||
.unwrap_or_else(|| "-".to_string())
|
||||
)?;
|
||||
}
|
||||
}
|
||||
@ -1287,7 +1350,7 @@ impl fmt::Display for CliValidatorInfo {
|
||||
writeln_name_value(
|
||||
f,
|
||||
&format!(" {}:", to_title_case(key)),
|
||||
&value.as_str().unwrap_or("?"),
|
||||
value.as_str().unwrap_or("?"),
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
@ -1325,8 +1388,8 @@ impl fmt::Display for CliVoteAccount {
|
||||
build_balance_message(self.account_balance, self.use_lamports_unit, true)
|
||||
)?;
|
||||
writeln!(f, "Validator Identity: {}", self.validator_identity)?;
|
||||
writeln!(f, "Authorized Voters: {}", self.authorized_voters)?;
|
||||
writeln!(f, "Authorized Withdrawer: {}", self.authorized_withdrawer)?;
|
||||
writeln!(f, "Vote Authority: {}", self.authorized_voters)?;
|
||||
writeln!(f, "Withdraw Authority: {}", self.authorized_withdrawer)?;
|
||||
writeln!(f, "Credits: {}", self.credits)?;
|
||||
writeln!(f, "Commission: {}%", self.commission)?;
|
||||
writeln!(
|
||||
@ -1513,15 +1576,19 @@ impl fmt::Display for CliInflation {
|
||||
"Staking rate: {:>5.2}%",
|
||||
self.current_rate.validator * 100.
|
||||
)?;
|
||||
writeln!(
|
||||
f,
|
||||
"Foundation rate: {:>5.2}%",
|
||||
self.current_rate.foundation * 100.
|
||||
)
|
||||
|
||||
if self.current_rate.foundation > 0. {
|
||||
writeln!(
|
||||
f,
|
||||
"Foundation rate: {:>5.2}%",
|
||||
self.current_rate.foundation * 100.
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Default)]
|
||||
#[derive(Serialize, Deserialize, Default, Debug, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CliSignOnlyData {
|
||||
pub blockhash: String,
|
||||
@ -1669,6 +1736,7 @@ pub struct CliFeesInner {
|
||||
pub blockhash: String,
|
||||
pub lamports_per_signature: u64,
|
||||
pub last_valid_slot: Option<Slot>,
|
||||
pub last_valid_block_height: Option<Slot>,
|
||||
}
|
||||
|
||||
impl QuietDisplay for CliFeesInner {}
|
||||
@ -1682,11 +1750,11 @@ impl fmt::Display for CliFeesInner {
|
||||
"Lamports per signature:",
|
||||
&self.lamports_per_signature.to_string(),
|
||||
)?;
|
||||
let last_valid_slot = self
|
||||
.last_valid_slot
|
||||
let last_valid_block_height = self
|
||||
.last_valid_block_height
|
||||
.map(|s| s.to_string())
|
||||
.unwrap_or_default();
|
||||
writeln_name_value(f, "Last valid slot:", &last_valid_slot)
|
||||
writeln_name_value(f, "Last valid block height:", &last_valid_block_height)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1715,6 +1783,7 @@ impl CliFees {
|
||||
blockhash: Hash,
|
||||
lamports_per_signature: u64,
|
||||
last_valid_slot: Option<Slot>,
|
||||
last_valid_block_height: Option<Slot>,
|
||||
) -> Self {
|
||||
Self {
|
||||
inner: Some(CliFeesInner {
|
||||
@ -1722,6 +1791,7 @@ impl CliFees {
|
||||
blockhash: blockhash.to_string(),
|
||||
lamports_per_signature,
|
||||
last_valid_slot,
|
||||
last_valid_block_height,
|
||||
}),
|
||||
}
|
||||
}
|
||||
@ -1768,7 +1838,7 @@ impl fmt::Display for CliTokenAccount {
|
||||
writeln_name_value(
|
||||
f,
|
||||
"Close authority:",
|
||||
&account.close_authority.as_ref().unwrap_or(&String::new()),
|
||||
account.close_authority.as_ref().unwrap_or(&String::new()),
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
@ -1860,6 +1930,9 @@ pub struct CliUpgradeableProgram {
|
||||
pub authority: String,
|
||||
pub last_deploy_slot: u64,
|
||||
pub data_len: usize,
|
||||
pub lamports: u64,
|
||||
#[serde(skip_serializing)]
|
||||
pub use_lamports_unit: bool,
|
||||
}
|
||||
impl QuietDisplay for CliUpgradeableProgram {}
|
||||
impl VerboseDisplay for CliUpgradeableProgram {}
|
||||
@ -1880,12 +1953,78 @@ impl fmt::Display for CliUpgradeableProgram {
|
||||
"Data Length:",
|
||||
&format!("{:?} ({:#x?}) bytes", self.data_len, self.data_len),
|
||||
)?;
|
||||
writeln_name_value(
|
||||
f,
|
||||
"Balance:",
|
||||
&build_balance_message(self.lamports, self.use_lamports_unit, true),
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CliUpgradeablePrograms {
|
||||
pub programs: Vec<CliUpgradeableProgram>,
|
||||
#[serde(skip_serializing)]
|
||||
pub use_lamports_unit: bool,
|
||||
}
|
||||
impl QuietDisplay for CliUpgradeablePrograms {}
|
||||
impl VerboseDisplay for CliUpgradeablePrograms {}
|
||||
impl fmt::Display for CliUpgradeablePrograms {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
writeln!(f)?;
|
||||
writeln!(
|
||||
f,
|
||||
"{}",
|
||||
style(format!(
|
||||
"{:<44} | {:<9} | {:<44} | {}",
|
||||
"Program Id", "Slot", "Authority", "Balance"
|
||||
))
|
||||
.bold()
|
||||
)?;
|
||||
for program in self.programs.iter() {
|
||||
writeln!(
|
||||
f,
|
||||
"{}",
|
||||
&format!(
|
||||
"{:<44} | {:<9} | {:<44} | {}",
|
||||
program.program_id,
|
||||
program.last_deploy_slot,
|
||||
program.authority,
|
||||
build_balance_message(program.lamports, self.use_lamports_unit, true)
|
||||
)
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CliUpgradeableProgramClosed {
|
||||
pub program_id: String,
|
||||
pub lamports: u64,
|
||||
#[serde(skip_serializing)]
|
||||
pub use_lamports_unit: bool,
|
||||
}
|
||||
impl QuietDisplay for CliUpgradeableProgramClosed {}
|
||||
impl VerboseDisplay for CliUpgradeableProgramClosed {}
|
||||
impl fmt::Display for CliUpgradeableProgramClosed {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
writeln!(f)?;
|
||||
writeln!(
|
||||
f,
|
||||
"Closed Program Id {}, {} reclaimed",
|
||||
&self.program_id,
|
||||
&build_balance_message(self.lamports, self.use_lamports_unit, true)
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CliUpgradeableBuffer {
|
||||
pub address: String,
|
||||
pub authority: String,
|
||||
@ -1970,6 +2109,11 @@ pub fn return_signers_with_config(
|
||||
output_format: &OutputFormat,
|
||||
config: &ReturnSignersConfig,
|
||||
) -> Result<String, Box<dyn std::error::Error>> {
|
||||
let cli_command = return_signers_data(tx, config);
|
||||
Ok(output_format.formatted_string(&cli_command))
|
||||
}
|
||||
|
||||
pub fn return_signers_data(tx: &Transaction, config: &ReturnSignersConfig) -> CliSignOnlyData {
|
||||
let verify_results = tx.verify_with_results();
|
||||
let mut signers = Vec::new();
|
||||
let mut absent = Vec::new();
|
||||
@ -1994,19 +2138,17 @@ pub fn return_signers_with_config(
|
||||
None
|
||||
};
|
||||
|
||||
let cli_command = CliSignOnlyData {
|
||||
CliSignOnlyData {
|
||||
blockhash: tx.message.recent_blockhash.to_string(),
|
||||
message,
|
||||
signers,
|
||||
absent,
|
||||
bad_sig,
|
||||
};
|
||||
|
||||
Ok(output_format.formatted_string(&cli_command))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_sign_only_reply_string(reply: &str) -> SignOnly {
|
||||
let object: Value = serde_json::from_str(&reply).unwrap();
|
||||
let object: Value = serde_json::from_str(reply).unwrap();
|
||||
let blockhash_str = object.get("blockhash").unwrap().as_str().unwrap();
|
||||
let blockhash = blockhash_str.parse::<Hash>().unwrap();
|
||||
let mut present_signers: Vec<(Pubkey, Signature)> = Vec::new();
|
||||
@ -2136,8 +2278,8 @@ impl fmt::Display for CliBlock {
|
||||
writeln!(f, "Rewards:")?;
|
||||
writeln!(
|
||||
f,
|
||||
" {:<44} {:^15} {:<15} {:<20} {:>14}",
|
||||
"Address", "Type", "Amount", "New Balance", "Percent Change"
|
||||
" {:<44} {:^15} {:<15} {:<20} {:>14} {:>10}",
|
||||
"Address", "Type", "Amount", "New Balance", "Percent Change", "Commission"
|
||||
)?;
|
||||
for reward in rewards {
|
||||
let sign = if reward.lamports < 0 { "-" } else { "" };
|
||||
@ -2145,7 +2287,7 @@ impl fmt::Display for CliBlock {
|
||||
total_rewards += reward.lamports;
|
||||
writeln!(
|
||||
f,
|
||||
" {:<44} {:^15} {:>15} {}",
|
||||
" {:<44} {:^15} {:>15} {} {}",
|
||||
reward.pubkey,
|
||||
if let Some(reward_type) = reward.reward_type {
|
||||
format!("{}", reward_type)
|
||||
@ -2167,7 +2309,11 @@ impl fmt::Display for CliBlock {
|
||||
/ (reward.post_balance as f64 - reward.lamports as f64))
|
||||
* 100.0
|
||||
)
|
||||
}
|
||||
},
|
||||
reward
|
||||
.commission
|
||||
.map(|commission| format!("{:>9}%", commission))
|
||||
.unwrap_or_else(|| " -".to_string())
|
||||
)?;
|
||||
}
|
||||
|
||||
@ -2380,6 +2526,7 @@ impl VerboseDisplay for CliGossipNodes {}
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use clap::{App, Arg};
|
||||
use solana_sdk::{
|
||||
message::Message,
|
||||
pubkey::Pubkey,
|
||||
@ -2408,6 +2555,10 @@ mod tests {
|
||||
fn try_sign_message(&self, _message: &[u8]) -> Result<Signature, SignerError> {
|
||||
Ok(Signature::new(&[1u8; 64]))
|
||||
}
|
||||
|
||||
fn is_interactive(&self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
let present: Box<dyn Signer> = Box::new(keypair_from_seed(&[2u8; 32]).unwrap());
|
||||
@ -2436,6 +2587,22 @@ mod tests {
|
||||
assert_eq!(sign_only.absent_signers[0], absent.pubkey());
|
||||
assert_eq!(sign_only.bad_signers[0], bad.pubkey());
|
||||
|
||||
let res_data = return_signers_data(&tx, &ReturnSignersConfig::default());
|
||||
assert_eq!(
|
||||
res_data,
|
||||
CliSignOnlyData {
|
||||
blockhash: blockhash.to_string(),
|
||||
message: None,
|
||||
signers: vec![format!(
|
||||
"{}={}",
|
||||
present.pubkey().to_string(),
|
||||
tx.signatures[1]
|
||||
)],
|
||||
absent: vec![absent.pubkey().to_string()],
|
||||
bad_sig: vec![bad.pubkey().to_string()],
|
||||
}
|
||||
);
|
||||
|
||||
let expected_msg = "AwECBwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDgTl3Dqh9\
|
||||
F19Wo1Rmw0x+zMuNipG07jeiXfYPW4/Js5QEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQE\
|
||||
BAQEBAYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBgYGBQUFBQUFBQUFBQUFBQUFBQUF\
|
||||
@ -2449,10 +2616,26 @@ mod tests {
|
||||
let res = return_signers_with_config(&tx, &OutputFormat::JsonCompact, &config).unwrap();
|
||||
let sign_only = parse_sign_only_reply_string(&res);
|
||||
assert_eq!(sign_only.blockhash, blockhash);
|
||||
assert_eq!(sign_only.message, Some(expected_msg));
|
||||
assert_eq!(sign_only.message, Some(expected_msg.clone()));
|
||||
assert_eq!(sign_only.present_signers[0].0, present.pubkey());
|
||||
assert_eq!(sign_only.absent_signers[0], absent.pubkey());
|
||||
assert_eq!(sign_only.bad_signers[0], bad.pubkey());
|
||||
|
||||
let res_data = return_signers_data(&tx, &config);
|
||||
assert_eq!(
|
||||
res_data,
|
||||
CliSignOnlyData {
|
||||
blockhash: blockhash.to_string(),
|
||||
message: Some(expected_msg),
|
||||
signers: vec![format!(
|
||||
"{}={}",
|
||||
present.pubkey().to_string(),
|
||||
tx.signatures[1]
|
||||
)],
|
||||
absent: vec![absent.pubkey().to_string()],
|
||||
bad_sig: vec![bad.pubkey().to_string()],
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -2501,4 +2684,50 @@ mod tests {
|
||||
"verbose"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_output_format_from_matches() {
|
||||
let app = App::new("test").arg(
|
||||
Arg::with_name("output_format")
|
||||
.long("output")
|
||||
.value_name("FORMAT")
|
||||
.global(true)
|
||||
.takes_value(true)
|
||||
.possible_values(&["json", "json-compact"])
|
||||
.help("Return information in specified output format"),
|
||||
);
|
||||
let matches = app
|
||||
.clone()
|
||||
.get_matches_from(vec!["test", "--output", "json"]);
|
||||
assert_eq!(
|
||||
OutputFormat::from_matches(&matches, "output_format", false),
|
||||
OutputFormat::Json
|
||||
);
|
||||
assert_eq!(
|
||||
OutputFormat::from_matches(&matches, "output_format", true),
|
||||
OutputFormat::Json
|
||||
);
|
||||
|
||||
let matches = app
|
||||
.clone()
|
||||
.get_matches_from(vec!["test", "--output", "json-compact"]);
|
||||
assert_eq!(
|
||||
OutputFormat::from_matches(&matches, "output_format", false),
|
||||
OutputFormat::JsonCompact
|
||||
);
|
||||
assert_eq!(
|
||||
OutputFormat::from_matches(&matches, "output_format", true),
|
||||
OutputFormat::JsonCompact
|
||||
);
|
||||
|
||||
let matches = app.clone().get_matches_from(vec!["test"]);
|
||||
assert_eq!(
|
||||
OutputFormat::from_matches(&matches, "output_format", false),
|
||||
OutputFormat::Display
|
||||
);
|
||||
assert_eq!(
|
||||
OutputFormat::from_matches(&matches, "output_format", true),
|
||||
OutputFormat::DisplayVerbose
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ use {
|
||||
indicatif::{ProgressBar, ProgressStyle},
|
||||
solana_sdk::{
|
||||
clock::UnixTimestamp, hash::Hash, message::Message, native_token::lamports_to_sol,
|
||||
program_utils::limited_deserialize, pubkey::Pubkey, transaction::Transaction,
|
||||
program_utils::limited_deserialize, pubkey::Pubkey, stake, transaction::Transaction,
|
||||
},
|
||||
solana_transaction_status::UiTransactionStatusMeta,
|
||||
spl_memo::id as spl_memo_id,
|
||||
@ -140,7 +140,7 @@ fn format_account_mode(message: &Message, index: usize) -> String {
|
||||
} else {
|
||||
"-"
|
||||
},
|
||||
if message.is_writable(index, /*demote_sysvar_write_locks=*/ true) {
|
||||
if message.is_writable(index, /*demote_program_write_locks=*/ true) {
|
||||
"w" // comment for consistent rust fmt (no joking; lol)
|
||||
} else {
|
||||
"-"
|
||||
@ -244,10 +244,9 @@ pub fn write_transaction<W: io::Write>(
|
||||
writeln!(w, "{} {:?}", prefix, vote_instruction)?;
|
||||
raw = false;
|
||||
}
|
||||
} else if program_pubkey == solana_stake_program::id() {
|
||||
if let Ok(stake_instruction) = limited_deserialize::<
|
||||
solana_stake_program::stake_instruction::StakeInstruction,
|
||||
>(&instruction.data)
|
||||
} else if program_pubkey == stake::program::id() {
|
||||
if let Ok(stake_instruction) =
|
||||
limited_deserialize::<stake::instruction::StakeInstruction>(&instruction.data)
|
||||
{
|
||||
writeln!(w, "{} {:?}", prefix, stake_instruction)?;
|
||||
raw = false;
|
||||
|
@ -3,7 +3,7 @@ authors = ["Solana Maintainers <maintainers@solana.foundation>"]
|
||||
edition = "2018"
|
||||
name = "solana-cli"
|
||||
description = "Blockchain, Rebuilt for Scale"
|
||||
version = "1.7.0"
|
||||
version = "1.7.14"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
@ -16,7 +16,8 @@ chrono = { version = "0.4.11", features = ["serde"] }
|
||||
clap = "2.33.1"
|
||||
criterion-stats = "0.3.0"
|
||||
ctrlc = { version = "3.1.5", features = ["termination"] }
|
||||
console = "0.11.3"
|
||||
console = "0.14.1"
|
||||
const_format = "0.2.14"
|
||||
dirs-next = "2.0.0"
|
||||
log = "0.4.11"
|
||||
Inflector = "0.11.4"
|
||||
@ -28,30 +29,30 @@ reqwest = { version = "0.11.2", default-features = false, features = ["blocking"
|
||||
serde = "1.0.122"
|
||||
serde_derive = "1.0.103"
|
||||
serde_json = "1.0.56"
|
||||
solana-account-decoder = { path = "../account-decoder", version = "=1.7.0" }
|
||||
solana-bpf-loader-program = { path = "../programs/bpf_loader", version = "=1.7.0" }
|
||||
solana-clap-utils = { path = "../clap-utils", version = "=1.7.0" }
|
||||
solana-cli-config = { path = "../cli-config", version = "=1.7.0" }
|
||||
solana-cli-output = { path = "../cli-output", version = "=1.7.0" }
|
||||
solana-client = { path = "../client", version = "=1.7.0" }
|
||||
solana-config-program = { path = "../programs/config", version = "=1.7.0" }
|
||||
solana-faucet = { path = "../faucet", version = "=1.7.0" }
|
||||
solana-logger = { path = "../logger", version = "=1.7.0" }
|
||||
solana-net-utils = { path = "../net-utils", version = "=1.7.0" }
|
||||
solana-account-decoder = { path = "../account-decoder", version = "=1.7.14" }
|
||||
solana-bpf-loader-program = { path = "../programs/bpf_loader", version = "=1.7.14" }
|
||||
solana-clap-utils = { path = "../clap-utils", version = "=1.7.14" }
|
||||
solana-cli-config = { path = "../cli-config", version = "=1.7.14" }
|
||||
solana-cli-output = { path = "../cli-output", version = "=1.7.14" }
|
||||
solana-client = { path = "../client", version = "=1.7.14" }
|
||||
solana-config-program = { path = "../programs/config", version = "=1.7.14" }
|
||||
solana-faucet = { path = "../faucet", version = "=1.7.14" }
|
||||
solana-logger = { path = "../logger", version = "=1.7.14" }
|
||||
solana-net-utils = { path = "../net-utils", version = "=1.7.14" }
|
||||
solana_rbpf = "=0.2.11"
|
||||
solana-remote-wallet = { path = "../remote-wallet", version = "=1.7.0" }
|
||||
solana-sdk = { path = "../sdk", version = "=1.7.0" }
|
||||
solana-stake-program = { path = "../programs/stake", version = "=1.7.0" }
|
||||
solana-transaction-status = { path = "../transaction-status", version = "=1.7.0" }
|
||||
solana-version = { path = "../version", version = "=1.7.0" }
|
||||
solana-vote-program = { path = "../programs/vote", version = "=1.7.0" }
|
||||
solana-remote-wallet = { path = "../remote-wallet", version = "=1.7.14" }
|
||||
solana-sdk = { path = "../sdk", version = "=1.7.14" }
|
||||
solana-transaction-status = { path = "../transaction-status", version = "=1.7.14" }
|
||||
solana-version = { path = "../version", version = "=1.7.14" }
|
||||
solana-vote-program = { path = "../programs/vote", version = "=1.7.14" }
|
||||
spl-memo = { version = "=3.0.1", features = ["no-entrypoint"] }
|
||||
thiserror = "1.0.21"
|
||||
tiny-bip39 = "0.7.0"
|
||||
tiny-bip39 = "0.8.1"
|
||||
url = "2.1.1"
|
||||
|
||||
[dev-dependencies]
|
||||
solana-core = { path = "../core", version = "=1.7.0" }
|
||||
solana-core = { path = "../core", version = "=1.7.14" }
|
||||
solana-streamer = { path = "../streamer", version = "=1.7.14" }
|
||||
tempfile = "3.1.0"
|
||||
|
||||
[[bin]]
|
||||
|
202
cli/src/clap_app.rs
Normal file
202
cli/src/clap_app.rs
Normal file
@ -0,0 +1,202 @@
|
||||
use crate::{
|
||||
cli::*, cluster_query::*, feature::*, inflation::*, nonce::*, program::*, stake::*,
|
||||
validator_info::*, vote::*, wallet::*,
|
||||
};
|
||||
use clap::{App, AppSettings, Arg, ArgGroup, SubCommand};
|
||||
use solana_clap_utils::{self, input_validators::*, keypair::*};
|
||||
use solana_cli_config::CONFIG_FILE;
|
||||
|
||||
pub fn get_clap_app<'ab, 'v>(name: &str, about: &'ab str, version: &'v str) -> App<'ab, 'v> {
|
||||
App::new(name)
|
||||
.about(about)
|
||||
.version(version)
|
||||
.setting(AppSettings::SubcommandRequiredElseHelp)
|
||||
.arg({
|
||||
let arg = Arg::with_name("config_file")
|
||||
.short("C")
|
||||
.long("config")
|
||||
.value_name("FILEPATH")
|
||||
.takes_value(true)
|
||||
.global(true)
|
||||
.help("Configuration file to use");
|
||||
if let Some(ref config_file) = *CONFIG_FILE {
|
||||
arg.default_value(config_file)
|
||||
} else {
|
||||
arg
|
||||
}
|
||||
})
|
||||
.arg(
|
||||
Arg::with_name("json_rpc_url")
|
||||
.short("u")
|
||||
.long("url")
|
||||
.value_name("URL_OR_MONIKER")
|
||||
.takes_value(true)
|
||||
.global(true)
|
||||
.validator(is_url_or_moniker)
|
||||
.help(
|
||||
"URL for Solana's JSON RPC or moniker (or their first letter): \
|
||||
[mainnet-beta, testnet, devnet, localhost]",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("websocket_url")
|
||||
.long("ws")
|
||||
.value_name("URL")
|
||||
.takes_value(true)
|
||||
.global(true)
|
||||
.validator(is_url)
|
||||
.help("WebSocket URL for the solana cluster"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("keypair")
|
||||
.short("k")
|
||||
.long("keypair")
|
||||
.value_name("KEYPAIR")
|
||||
.global(true)
|
||||
.takes_value(true)
|
||||
.help("Filepath or URL to a keypair"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("commitment")
|
||||
.long("commitment")
|
||||
.takes_value(true)
|
||||
.possible_values(&[
|
||||
"processed",
|
||||
"confirmed",
|
||||
"finalized",
|
||||
"recent", // Deprecated as of v1.5.5
|
||||
"single", // Deprecated as of v1.5.5
|
||||
"singleGossip", // Deprecated as of v1.5.5
|
||||
"root", // Deprecated as of v1.5.5
|
||||
"max", // Deprecated as of v1.5.5
|
||||
])
|
||||
.value_name("COMMITMENT_LEVEL")
|
||||
.hide_possible_values(true)
|
||||
.global(true)
|
||||
.help("Return information at the selected commitment level [possible values: processed, confirmed, finalized]"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("verbose")
|
||||
.long("verbose")
|
||||
.short("v")
|
||||
.global(true)
|
||||
.help("Show additional information"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("no_address_labels")
|
||||
.long("no-address-labels")
|
||||
.global(true)
|
||||
.help("Do not use address labels in the output"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("output_format")
|
||||
.long("output")
|
||||
.value_name("FORMAT")
|
||||
.global(true)
|
||||
.takes_value(true)
|
||||
.possible_values(&["json", "json-compact"])
|
||||
.help("Return information in specified output format"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(SKIP_SEED_PHRASE_VALIDATION_ARG.name)
|
||||
.long(SKIP_SEED_PHRASE_VALIDATION_ARG.long)
|
||||
.global(true)
|
||||
.help(SKIP_SEED_PHRASE_VALIDATION_ARG.help),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("rpc_timeout")
|
||||
.long("rpc-timeout")
|
||||
.value_name("SECONDS")
|
||||
.takes_value(true)
|
||||
.default_value(DEFAULT_RPC_TIMEOUT_SECONDS)
|
||||
.global(true)
|
||||
.hidden(true)
|
||||
.help("Timeout value for RPC requests"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("confirm_transaction_initial_timeout")
|
||||
.long("confirm-timeout")
|
||||
.value_name("SECONDS")
|
||||
.takes_value(true)
|
||||
.default_value(DEFAULT_CONFIRM_TX_TIMEOUT_SECONDS)
|
||||
.global(true)
|
||||
.hidden(true)
|
||||
.help("Timeout value for initial transaction status"),
|
||||
)
|
||||
.cluster_query_subcommands()
|
||||
.feature_subcommands()
|
||||
.inflation_subcommands()
|
||||
.nonce_subcommands()
|
||||
.program_subcommands()
|
||||
.stake_subcommands()
|
||||
.validator_info_subcommands()
|
||||
.vote_subcommands()
|
||||
.wallet_subcommands()
|
||||
.subcommand(
|
||||
SubCommand::with_name("config")
|
||||
.about("Solana command-line tool configuration settings")
|
||||
.aliases(&["get", "set"])
|
||||
.setting(AppSettings::SubcommandRequiredElseHelp)
|
||||
.subcommand(
|
||||
SubCommand::with_name("get")
|
||||
.about("Get current config settings")
|
||||
.arg(
|
||||
Arg::with_name("specific_setting")
|
||||
.index(1)
|
||||
.value_name("CONFIG_FIELD")
|
||||
.takes_value(true)
|
||||
.possible_values(&[
|
||||
"json_rpc_url",
|
||||
"websocket_url",
|
||||
"keypair",
|
||||
"commitment",
|
||||
])
|
||||
.help("Return a specific config setting"),
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("set")
|
||||
.about("Set a config setting")
|
||||
.group(
|
||||
ArgGroup::with_name("config_settings")
|
||||
.args(&["json_rpc_url", "websocket_url", "keypair", "commitment"])
|
||||
.multiple(true)
|
||||
.required(true),
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("import-address-labels")
|
||||
.about("Import a list of address labels")
|
||||
.arg(
|
||||
Arg::with_name("filename")
|
||||
.index(1)
|
||||
.value_name("FILENAME")
|
||||
.takes_value(true)
|
||||
.help("YAML file of address labels"),
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("export-address-labels")
|
||||
.about("Export the current address labels")
|
||||
.arg(
|
||||
Arg::with_name("filename")
|
||||
.index(1)
|
||||
.value_name("FILENAME")
|
||||
.takes_value(true)
|
||||
.help("YAML file to receive the current address labels"),
|
||||
),
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("completion")
|
||||
.about("Generate completion scripts for various shells")
|
||||
.arg(
|
||||
Arg::with_name("shell")
|
||||
.long("shell")
|
||||
.short("s")
|
||||
.takes_value(true)
|
||||
.possible_values(&["bash", "fish", "zsh", "powershell", "elvish"])
|
||||
.default_value("bash")
|
||||
)
|
||||
)
|
||||
}
|
1095
cli/src/cli.rs
1095
cli/src/cli.rs
File diff suppressed because it is too large
Load Diff
@ -1,7 +1,6 @@
|
||||
use crate::{
|
||||
cli::{CliCommand, CliCommandInfo, CliConfig, CliError, ProcessResult},
|
||||
spend_utils::{resolve_spend_tx_and_check_account_balance, SpendAmount},
|
||||
stake::is_stake_program_v2_enabled,
|
||||
};
|
||||
use clap::{value_t, value_t_or_exit, App, AppSettings, Arg, ArgMatches, SubCommand};
|
||||
use console::{style, Emoji};
|
||||
@ -24,11 +23,12 @@ use solana_client::{
|
||||
pubsub_client::PubsubClient,
|
||||
rpc_client::{GetConfirmedSignaturesForAddress2Config, RpcClient},
|
||||
rpc_config::{
|
||||
RpcAccountInfoConfig, RpcBlockConfig, RpcLargestAccountsConfig, RpcLargestAccountsFilter,
|
||||
RpcProgramAccountsConfig, RpcTransactionConfig, RpcTransactionLogsConfig,
|
||||
RpcTransactionLogsFilter,
|
||||
RpcAccountInfoConfig, RpcBlockConfig, RpcGetVoteAccountsConfig, RpcLargestAccountsConfig,
|
||||
RpcLargestAccountsFilter, RpcProgramAccountsConfig, RpcTransactionConfig,
|
||||
RpcTransactionLogsConfig, RpcTransactionLogsFilter,
|
||||
},
|
||||
rpc_filter,
|
||||
rpc_request::DELINQUENT_VALIDATOR_SLOT_DISTANCE,
|
||||
rpc_response::SlotInfo,
|
||||
};
|
||||
use solana_remote_wallet::remote_wallet::RemoteWalletManager;
|
||||
@ -46,7 +46,9 @@ use solana_sdk::{
|
||||
rent::Rent,
|
||||
rpc_port::DEFAULT_RPC_PORT_STR,
|
||||
signature::Signature,
|
||||
slot_history, system_instruction, system_program,
|
||||
slot_history,
|
||||
stake::{self, state::StakeState},
|
||||
system_instruction, system_program,
|
||||
sysvar::{
|
||||
self,
|
||||
slot_history::SlotHistory,
|
||||
@ -55,7 +57,6 @@ use solana_sdk::{
|
||||
timing,
|
||||
transaction::Transaction,
|
||||
};
|
||||
use solana_stake_program::stake_state::StakeState;
|
||||
use solana_transaction_status::UiTransactionEncoding;
|
||||
use solana_vote_program::vote_state::VoteState;
|
||||
use std::{
|
||||
@ -121,7 +122,7 @@ impl ClusterQuerySubCommands for App<'_, '_> {
|
||||
.long("our-localhost")
|
||||
.takes_value(false)
|
||||
.value_name("PORT")
|
||||
.default_value(&DEFAULT_RPC_PORT_STR)
|
||||
.default_value(DEFAULT_RPC_PORT_STR)
|
||||
.validator(is_port)
|
||||
.help("Guess Identity pubkey and validator rpc node assuming local (possibly private) validator"),
|
||||
)
|
||||
@ -175,7 +176,7 @@ impl ClusterQuerySubCommands for App<'_, '_> {
|
||||
.takes_value(true)
|
||||
.value_name("EPOCH")
|
||||
.validator(is_epoch)
|
||||
.help("Epoch to show leader schedule for. (default: current)")
|
||||
.help("Epoch to show leader schedule for. [default: current]")
|
||||
)
|
||||
)
|
||||
.subcommand(
|
||||
@ -381,6 +382,25 @@ impl ClusterQuerySubCommands for App<'_, '_> {
|
||||
])
|
||||
.default_value("stake")
|
||||
.help("Sort order (does not affect JSON output)"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("keep_unstaked_delinquents")
|
||||
.long("keep-unstaked-delinquents")
|
||||
.takes_value(false)
|
||||
.help("Don't discard unstaked, delinquent validators")
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("delinquent_slot_distance")
|
||||
.long("delinquent-slot-distance")
|
||||
.takes_value(true)
|
||||
.value_name("SLOT_DISTANCE")
|
||||
.validator(is_slot)
|
||||
.help(
|
||||
concatcp!(
|
||||
"Minimum slot distance from the tip to consider a validator delinquent. [default: ",
|
||||
DELINQUENT_VALIDATOR_SLOT_DISTANCE,
|
||||
"]",
|
||||
))
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
@ -616,6 +636,8 @@ pub fn parse_show_validators(matches: &ArgMatches<'_>) -> Result<CliCommandInfo,
|
||||
let use_lamports_unit = matches.is_present("lamports");
|
||||
let number_validators = matches.is_present("number");
|
||||
let reverse_sort = matches.is_present("reverse");
|
||||
let keep_unstaked_delinquents = matches.is_present("keep_unstaked_delinquents");
|
||||
let delinquent_slot_distance = value_of(matches, "delinquent_slot_distance");
|
||||
|
||||
let sort_order = match value_t_or_exit!(matches, "sort", String).as_str() {
|
||||
"delinquent" => CliValidatorsSortOrder::Delinquent,
|
||||
@ -636,6 +658,8 @@ pub fn parse_show_validators(matches: &ArgMatches<'_>) -> Result<CliCommandInfo,
|
||||
sort_order,
|
||||
reverse_sort,
|
||||
number_validators,
|
||||
keep_unstaked_delinquents,
|
||||
delinquent_slot_distance,
|
||||
},
|
||||
signers: vec![],
|
||||
})
|
||||
@ -937,18 +961,19 @@ pub fn process_fees(
|
||||
*recent_blockhash,
|
||||
fee_calculator.lamports_per_signature,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
} else {
|
||||
CliFees::none()
|
||||
}
|
||||
} else {
|
||||
let result = rpc_client.get_recent_blockhash_with_commitment(config.commitment)?;
|
||||
let (recent_blockhash, fee_calculator, last_valid_slot) = result.value;
|
||||
let result = rpc_client.get_fees_with_commitment(config.commitment)?;
|
||||
CliFees::some(
|
||||
result.context.slot,
|
||||
recent_blockhash,
|
||||
fee_calculator.lamports_per_signature,
|
||||
Some(last_valid_slot),
|
||||
result.value.blockhash,
|
||||
result.value.fee_calculator.lamports_per_signature,
|
||||
None,
|
||||
Some(result.value.last_valid_block_height),
|
||||
)
|
||||
};
|
||||
Ok(config.output_format.formatted_string(&fees))
|
||||
@ -1075,9 +1100,15 @@ pub fn process_get_epoch_info(rpc_client: &RpcClient, config: &CliConfig) -> Pro
|
||||
(secs as u64).saturating_mul(1000).checked_div(slots)
|
||||
})
|
||||
.unwrap_or(clock::DEFAULT_MS_PER_SLOT);
|
||||
let start_block_time = rpc_client
|
||||
.get_block_time(epoch_info.absolute_slot - epoch_info.slot_index)
|
||||
.ok();
|
||||
let current_block_time = rpc_client.get_block_time(epoch_info.absolute_slot).ok();
|
||||
let epoch_info = CliEpochInfo {
|
||||
epoch_info,
|
||||
average_slot_time_ms,
|
||||
start_block_time,
|
||||
current_block_time,
|
||||
};
|
||||
Ok(config.output_format.formatted_string(&epoch_info))
|
||||
}
|
||||
@ -1707,7 +1738,7 @@ pub fn process_show_stakes(
|
||||
}
|
||||
}
|
||||
let all_stake_accounts = rpc_client
|
||||
.get_program_accounts_with_config(&solana_stake_program::id(), program_accounts_config)?;
|
||||
.get_program_accounts_with_config(&stake::program::id(), program_accounts_config)?;
|
||||
let stake_history_account = rpc_client.get_account(&stake_history::id())?;
|
||||
let clock_account = rpc_client.get_account(&sysvar::clock::id())?;
|
||||
let clock: Clock = from_account(&clock_account).ok_or_else(|| {
|
||||
@ -1718,8 +1749,6 @@ pub fn process_show_stakes(
|
||||
let stake_history = from_account(&stake_history_account).ok_or_else(|| {
|
||||
CliError::RpcRequestError("Failed to deserialize stake history".to_string())
|
||||
})?;
|
||||
// At v1.6, this check can be removed and simply passed as `true`
|
||||
let stake_program_v2_enabled = is_stake_program_v2_enabled(rpc_client)?;
|
||||
|
||||
let mut stake_accounts: Vec<CliKeyedStakeState> = vec![];
|
||||
for (stake_pubkey, stake_account) in all_stake_accounts {
|
||||
@ -1735,7 +1764,6 @@ pub fn process_show_stakes(
|
||||
use_lamports_unit,
|
||||
&stake_history,
|
||||
&clock,
|
||||
stake_program_v2_enabled,
|
||||
),
|
||||
});
|
||||
}
|
||||
@ -1754,7 +1782,6 @@ pub fn process_show_stakes(
|
||||
use_lamports_unit,
|
||||
&stake_history,
|
||||
&clock,
|
||||
stake_program_v2_enabled,
|
||||
),
|
||||
});
|
||||
}
|
||||
@ -1785,11 +1812,17 @@ pub fn process_show_validators(
|
||||
validators_sort_order: CliValidatorsSortOrder,
|
||||
validators_reverse_sort: bool,
|
||||
number_validators: bool,
|
||||
keep_unstaked_delinquents: bool,
|
||||
delinquent_slot_distance: Option<Slot>,
|
||||
) -> ProcessResult {
|
||||
let progress_bar = new_spinner_progress_bar();
|
||||
progress_bar.set_message("Fetching vote accounts...");
|
||||
let epoch_info = rpc_client.get_epoch_info()?;
|
||||
let vote_accounts = rpc_client.get_vote_accounts()?;
|
||||
let vote_accounts = rpc_client.get_vote_accounts_with_config(RpcGetVoteAccountsConfig {
|
||||
keep_unstaked_delinquents: Some(keep_unstaked_delinquents),
|
||||
delinquent_slot_distance,
|
||||
..RpcGetVoteAccountsConfig::default()
|
||||
})?;
|
||||
|
||||
progress_bar.set_message("Fetching block production...");
|
||||
let skip_rate: HashMap<_, _> = rpc_client
|
||||
@ -1888,14 +1921,40 @@ pub fn process_show_validators(
|
||||
entry.delinquent_active_stake += validator.activated_stake;
|
||||
}
|
||||
|
||||
let validators: Vec<_> = current_validators
|
||||
.into_iter()
|
||||
.chain(delinquent_validators.into_iter())
|
||||
.collect();
|
||||
|
||||
let (average_skip_rate, average_stake_weighted_skip_rate) = {
|
||||
let mut skip_rate_len = 0;
|
||||
let mut skip_rate_sum = 0.;
|
||||
let mut skip_rate_weighted_sum = 0.;
|
||||
for validator in validators.iter() {
|
||||
if let Some(skip_rate) = validator.skip_rate {
|
||||
skip_rate_sum += skip_rate;
|
||||
skip_rate_len += 1;
|
||||
skip_rate_weighted_sum += skip_rate * validator.activated_stake as f64;
|
||||
}
|
||||
}
|
||||
|
||||
if skip_rate_len > 0 && total_active_stake > 0 {
|
||||
(
|
||||
skip_rate_sum / skip_rate_len as f64,
|
||||
skip_rate_weighted_sum / total_active_stake as f64,
|
||||
)
|
||||
} else {
|
||||
(100., 100.) // Impossible?
|
||||
}
|
||||
};
|
||||
|
||||
let cli_validators = CliValidators {
|
||||
total_active_stake,
|
||||
total_current_stake,
|
||||
total_delinquent_stake,
|
||||
validators: current_validators
|
||||
.into_iter()
|
||||
.chain(delinquent_validators.into_iter())
|
||||
.collect(),
|
||||
validators,
|
||||
average_skip_rate,
|
||||
average_stake_weighted_skip_rate,
|
||||
validators_sort_order,
|
||||
validators_reverse_sort,
|
||||
number_validators,
|
||||
@ -2082,7 +2141,7 @@ pub fn process_calculate_rent(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::cli::{app, parse_command};
|
||||
use crate::{clap_app::get_clap_app, cli::parse_command};
|
||||
use solana_sdk::signature::{write_keypair, Keypair};
|
||||
use std::str::FromStr;
|
||||
use tempfile::NamedTempFile;
|
||||
@ -2094,11 +2153,11 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_parse_command() {
|
||||
let test_commands = app("test", "desc", "version");
|
||||
let test_commands = get_clap_app("test", "desc", "version");
|
||||
let default_keypair = Keypair::new();
|
||||
let (default_keypair_file, mut tmp_file) = make_tmp_file();
|
||||
write_keypair(&default_keypair, tmp_file.as_file_mut()).unwrap();
|
||||
let default_signer = DefaultSigner::new(default_keypair_file);
|
||||
let default_signer = DefaultSigner::new("", &default_keypair_file);
|
||||
|
||||
let test_cluster_version = test_commands
|
||||
.clone()
|
||||
|
@ -10,6 +10,7 @@ use solana_cli_output::{QuietDisplay, VerboseDisplay};
|
||||
use solana_client::{client_error::ClientError, rpc_client::RpcClient};
|
||||
use solana_remote_wallet::remote_wallet::RemoteWalletManager;
|
||||
use solana_sdk::{
|
||||
account::Account,
|
||||
clock::Slot,
|
||||
feature::{self, Feature},
|
||||
feature_set::FEATURE_NAMES,
|
||||
@ -312,6 +313,31 @@ fn feature_activation_allowed(rpc_client: &RpcClient, quiet: bool) -> Result<boo
|
||||
Ok(feature_activation_allowed)
|
||||
}
|
||||
|
||||
fn status_from_account(account: Account) -> Option<CliFeatureStatus> {
|
||||
feature::from_account(&account).map(|feature| match feature.activated_at {
|
||||
None => CliFeatureStatus::Pending,
|
||||
Some(activation_slot) => CliFeatureStatus::Active(activation_slot),
|
||||
})
|
||||
}
|
||||
|
||||
fn get_feature_status(
|
||||
rpc_client: &RpcClient,
|
||||
feature_id: &Pubkey,
|
||||
) -> Result<Option<CliFeatureStatus>, Box<dyn std::error::Error>> {
|
||||
rpc_client
|
||||
.get_account(feature_id)
|
||||
.map(status_from_account)
|
||||
.map_err(|e| e.into())
|
||||
}
|
||||
|
||||
pub fn get_feature_is_active(
|
||||
rpc_client: &RpcClient,
|
||||
feature_id: &Pubkey,
|
||||
) -> Result<bool, Box<dyn std::error::Error>> {
|
||||
get_feature_status(rpc_client, feature_id)
|
||||
.map(|status| matches!(status, Some(CliFeatureStatus::Active(_))))
|
||||
}
|
||||
|
||||
fn process_status(
|
||||
rpc_client: &RpcClient,
|
||||
config: &CliConfig,
|
||||
@ -327,11 +353,7 @@ fn process_status(
|
||||
let feature_id = &feature_ids[i];
|
||||
let feature_name = FEATURE_NAMES.get(feature_id).unwrap();
|
||||
if let Some(account) = account {
|
||||
if let Some(feature) = feature::from_account(&account) {
|
||||
let feature_status = match feature.activated_at {
|
||||
None => CliFeatureStatus::Pending,
|
||||
Some(activation_slot) => CliFeatureStatus::Active(activation_slot),
|
||||
};
|
||||
if let Some(feature_status) = status_from_account(account) {
|
||||
features.push(CliFeature {
|
||||
id: feature_id.to_string(),
|
||||
description: feature_name.to_string(),
|
||||
|
@ -102,7 +102,7 @@ fn process_rewards(
|
||||
rewards_epoch: Option<Epoch>,
|
||||
) -> ProcessResult {
|
||||
let rewards = rpc_client
|
||||
.get_inflation_reward(&addresses, rewards_epoch)
|
||||
.get_inflation_reward(addresses, rewards_epoch)
|
||||
.map_err(|err| {
|
||||
if let Some(epoch) = rewards_epoch {
|
||||
format!("Rewards not available for epoch {}", epoch)
|
||||
|
@ -18,9 +18,13 @@ macro_rules! pubkey {
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_use]
|
||||
extern crate const_format;
|
||||
|
||||
extern crate serde_derive;
|
||||
|
||||
pub mod checks;
|
||||
pub mod clap_app;
|
||||
pub mod cli;
|
||||
pub mod cluster_query;
|
||||
pub mod feature;
|
||||
@ -33,3 +37,4 @@ pub mod stake;
|
||||
pub mod test_utils;
|
||||
pub mod validator_info;
|
||||
pub mod vote;
|
||||
pub mod wallet;
|
||||
|
206
cli/src/main.rs
206
cli/src/main.rs
@ -1,18 +1,15 @@
|
||||
use clap::{
|
||||
crate_description, crate_name, value_t_or_exit, AppSettings, Arg, ArgGroup, ArgMatches,
|
||||
SubCommand,
|
||||
};
|
||||
use clap::{crate_description, crate_name, value_t_or_exit, ArgMatches};
|
||||
use console::style;
|
||||
use solana_clap_utils::{
|
||||
input_validators::{is_url, is_url_or_moniker, normalize_to_url_if_moniker},
|
||||
keypair::{CliSigners, DefaultSigner, SKIP_SEED_PHRASE_VALIDATION_ARG},
|
||||
input_validators::normalize_to_url_if_moniker,
|
||||
keypair::{CliSigners, DefaultSigner},
|
||||
DisplayError,
|
||||
};
|
||||
use solana_cli::cli::{
|
||||
app, parse_command, process_command, CliCommandInfo, CliConfig, SettingType,
|
||||
DEFAULT_RPC_TIMEOUT_SECONDS,
|
||||
use solana_cli::{
|
||||
clap_app::get_clap_app,
|
||||
cli::{parse_command, process_command, CliCommandInfo, CliConfig, SettingType},
|
||||
};
|
||||
use solana_cli_config::{Config, CONFIG_FILE};
|
||||
use solana_cli_config::Config;
|
||||
use solana_cli_output::{display::println_name_value, OutputFormat};
|
||||
use solana_client::rpc_config::RpcSendTransactionConfig;
|
||||
use solana_remote_wallet::remote_wallet::RemoteWalletManager;
|
||||
@ -167,23 +164,29 @@ pub fn parse_args<'a>(
|
||||
let rpc_timeout = value_t_or_exit!(matches, "rpc_timeout", u64);
|
||||
let rpc_timeout = Duration::from_secs(rpc_timeout);
|
||||
|
||||
let confirm_transaction_initial_timeout =
|
||||
value_t_or_exit!(matches, "confirm_transaction_initial_timeout", u64);
|
||||
let confirm_transaction_initial_timeout =
|
||||
Duration::from_secs(confirm_transaction_initial_timeout);
|
||||
|
||||
let (_, websocket_url) = CliConfig::compute_websocket_url_setting(
|
||||
matches.value_of("websocket_url").unwrap_or(""),
|
||||
&config.websocket_url,
|
||||
matches.value_of("json_rpc_url").unwrap_or(""),
|
||||
&config.json_rpc_url,
|
||||
);
|
||||
let default_signer_arg_name = "keypair".to_string();
|
||||
let (_, default_signer_path) = CliConfig::compute_keypair_path_setting(
|
||||
matches.value_of("keypair").unwrap_or(""),
|
||||
matches.value_of(&default_signer_arg_name).unwrap_or(""),
|
||||
&config.keypair_path,
|
||||
);
|
||||
|
||||
let default_signer = DefaultSigner::from_path(default_signer_path.clone())?;
|
||||
let default_signer = DefaultSigner::new(default_signer_arg_name, &default_signer_path);
|
||||
|
||||
let CliCommandInfo {
|
||||
command,
|
||||
mut signers,
|
||||
} = parse_command(&matches, &default_signer, &mut wallet_manager)?;
|
||||
} = parse_command(matches, &default_signer, &mut wallet_manager)?;
|
||||
|
||||
if signers.is_empty() {
|
||||
if let Ok(signer_info) =
|
||||
@ -194,18 +197,7 @@ pub fn parse_args<'a>(
|
||||
}
|
||||
|
||||
let verbose = matches.is_present("verbose");
|
||||
let output_format = matches
|
||||
.value_of("output_format")
|
||||
.map(|value| match value {
|
||||
"json" => OutputFormat::Json,
|
||||
"json-compact" => OutputFormat::JsonCompact,
|
||||
_ => unreachable!(),
|
||||
})
|
||||
.unwrap_or(if verbose {
|
||||
OutputFormat::DisplayVerbose
|
||||
} else {
|
||||
OutputFormat::Display
|
||||
});
|
||||
let output_format = OutputFormat::from_matches(matches, "output_format", verbose);
|
||||
|
||||
let (_, commitment) = CliConfig::compute_commitment_config(
|
||||
matches.value_of("commitment").unwrap_or(""),
|
||||
@ -234,6 +226,7 @@ pub fn parse_args<'a>(
|
||||
preflight_commitment: Some(commitment.commitment),
|
||||
..RpcSendTransactionConfig::default()
|
||||
},
|
||||
confirm_transaction_initial_timeout,
|
||||
address_labels,
|
||||
},
|
||||
signers,
|
||||
@ -242,178 +235,21 @@ pub fn parse_args<'a>(
|
||||
|
||||
fn main() -> Result<(), Box<dyn error::Error>> {
|
||||
solana_logger::setup_with_default("off");
|
||||
let matches = app(
|
||||
let matches = get_clap_app(
|
||||
crate_name!(),
|
||||
crate_description!(),
|
||||
solana_version::version!(),
|
||||
)
|
||||
.arg({
|
||||
let arg = Arg::with_name("config_file")
|
||||
.short("C")
|
||||
.long("config")
|
||||
.value_name("FILEPATH")
|
||||
.takes_value(true)
|
||||
.global(true)
|
||||
.help("Configuration file to use");
|
||||
if let Some(ref config_file) = *CONFIG_FILE {
|
||||
arg.default_value(&config_file)
|
||||
} else {
|
||||
arg
|
||||
}
|
||||
})
|
||||
.arg(
|
||||
Arg::with_name("json_rpc_url")
|
||||
.short("u")
|
||||
.long("url")
|
||||
.value_name("URL_OR_MONIKER")
|
||||
.takes_value(true)
|
||||
.global(true)
|
||||
.validator(is_url_or_moniker)
|
||||
.help(
|
||||
"URL for Solana's JSON RPC or moniker (or their first letter): \
|
||||
[mainnet-beta, testnet, devnet, localhost]",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("websocket_url")
|
||||
.long("ws")
|
||||
.value_name("URL")
|
||||
.takes_value(true)
|
||||
.global(true)
|
||||
.validator(is_url)
|
||||
.help("WebSocket URL for the solana cluster"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("keypair")
|
||||
.short("k")
|
||||
.long("keypair")
|
||||
.value_name("KEYPAIR")
|
||||
.global(true)
|
||||
.takes_value(true)
|
||||
.help("Filepath or URL to a keypair"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("commitment")
|
||||
.long("commitment")
|
||||
.takes_value(true)
|
||||
.possible_values(&[
|
||||
"processed",
|
||||
"confirmed",
|
||||
"finalized",
|
||||
"recent", // Deprecated as of v1.5.5
|
||||
"single", // Deprecated as of v1.5.5
|
||||
"singleGossip", // Deprecated as of v1.5.5
|
||||
"root", // Deprecated as of v1.5.5
|
||||
"max", // Deprecated as of v1.5.5
|
||||
])
|
||||
.value_name("COMMITMENT_LEVEL")
|
||||
.hide_possible_values(true)
|
||||
.global(true)
|
||||
.help("Return information at the selected commitment level [possible values: processed, confirmed, finalized]"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("verbose")
|
||||
.long("verbose")
|
||||
.short("v")
|
||||
.global(true)
|
||||
.help("Show additional information"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("no_address_labels")
|
||||
.long("no-address-labels")
|
||||
.global(true)
|
||||
.help("Do not use address labels in the output"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("output_format")
|
||||
.long("output")
|
||||
.value_name("FORMAT")
|
||||
.global(true)
|
||||
.takes_value(true)
|
||||
.possible_values(&["json", "json-compact"])
|
||||
.help("Return information in specified output format"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(SKIP_SEED_PHRASE_VALIDATION_ARG.name)
|
||||
.long(SKIP_SEED_PHRASE_VALIDATION_ARG.long)
|
||||
.global(true)
|
||||
.help(SKIP_SEED_PHRASE_VALIDATION_ARG.help),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("rpc_timeout")
|
||||
.long("rpc-timeout")
|
||||
.value_name("SECONDS")
|
||||
.takes_value(true)
|
||||
.default_value(DEFAULT_RPC_TIMEOUT_SECONDS)
|
||||
.global(true)
|
||||
.hidden(true)
|
||||
.help("Timeout value for RPC requests"),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("config")
|
||||
.about("Solana command-line tool configuration settings")
|
||||
.aliases(&["get", "set"])
|
||||
.setting(AppSettings::SubcommandRequiredElseHelp)
|
||||
.subcommand(
|
||||
SubCommand::with_name("get")
|
||||
.about("Get current config settings")
|
||||
.arg(
|
||||
Arg::with_name("specific_setting")
|
||||
.index(1)
|
||||
.value_name("CONFIG_FIELD")
|
||||
.takes_value(true)
|
||||
.possible_values(&[
|
||||
"json_rpc_url",
|
||||
"websocket_url",
|
||||
"keypair",
|
||||
"commitment",
|
||||
])
|
||||
.help("Return a specific config setting"),
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("set")
|
||||
.about("Set a config setting")
|
||||
.group(
|
||||
ArgGroup::with_name("config_settings")
|
||||
.args(&["json_rpc_url", "websocket_url", "keypair", "commitment"])
|
||||
.multiple(true)
|
||||
.required(true),
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("import-address-labels")
|
||||
.about("Import a list of address labels")
|
||||
.arg(
|
||||
Arg::with_name("filename")
|
||||
.index(1)
|
||||
.value_name("FILENAME")
|
||||
.takes_value(true)
|
||||
.help("YAML file of address labels"),
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("export-address-labels")
|
||||
.about("Export the current address labels")
|
||||
.arg(
|
||||
Arg::with_name("filename")
|
||||
.index(1)
|
||||
.value_name("FILENAME")
|
||||
.takes_value(true)
|
||||
.help("YAML file to receive the current address labels"),
|
||||
),
|
||||
),
|
||||
)
|
||||
.get_matches();
|
||||
|
||||
do_main(&matches).map_err(|err| DisplayError::new_as_boxed(err).into())
|
||||
}
|
||||
|
||||
fn do_main(matches: &ArgMatches<'_>) -> Result<(), Box<dyn error::Error>> {
|
||||
if parse_settings(&matches)? {
|
||||
if parse_settings(matches)? {
|
||||
let mut wallet_manager = None;
|
||||
|
||||
let (mut config, signers) = parse_args(&matches, &mut wallet_manager)?;
|
||||
let (mut config, signers) = parse_args(matches, &mut wallet_manager)?;
|
||||
config.signers = signers.iter().map(|s| s.as_ref()).collect();
|
||||
let result = process_command(&config)?;
|
||||
println!("{}", result);
|
||||
|
119
cli/src/nonce.rs
119
cli/src/nonce.rs
@ -1,9 +1,10 @@
|
||||
use crate::{
|
||||
checks::{check_account_for_fee_with_commitment, check_unique_pubkeys},
|
||||
cli::{
|
||||
log_instruction_custom_error, CliCommand, CliCommandInfo, CliConfig, CliError,
|
||||
ProcessResult,
|
||||
log_instruction_custom_error, log_instruction_custom_error_ex, CliCommand, CliCommandInfo,
|
||||
CliConfig, CliError, ProcessResult,
|
||||
},
|
||||
feature::get_feature_is_active,
|
||||
memo::WithMemo,
|
||||
spend_utils::{resolve_spend_tx_and_check_account_balance, SpendAmount},
|
||||
};
|
||||
@ -12,7 +13,7 @@ use solana_clap_utils::{
|
||||
input_parsers::*,
|
||||
input_validators::*,
|
||||
keypair::{DefaultSigner, SignerIndex},
|
||||
memo::MEMO_ARG,
|
||||
memo::{memo_arg, MEMO_ARG},
|
||||
nonce::*,
|
||||
};
|
||||
use solana_cli_output::CliNonceAccount;
|
||||
@ -20,16 +21,19 @@ use solana_client::{nonce_utils::*, rpc_client::RpcClient};
|
||||
use solana_remote_wallet::remote_wallet::RemoteWalletManager;
|
||||
use solana_sdk::{
|
||||
account::Account,
|
||||
feature_set::merge_nonce_error_into_system_error,
|
||||
hash::Hash,
|
||||
instruction::InstructionError,
|
||||
message::Message,
|
||||
nonce::{self, State},
|
||||
pubkey::Pubkey,
|
||||
system_instruction::{
|
||||
advance_nonce_account, authorize_nonce_account, create_nonce_account,
|
||||
create_nonce_account_with_seed, withdraw_nonce_account, NonceError, SystemError,
|
||||
create_nonce_account_with_seed, instruction_to_nonce_error, withdraw_nonce_account,
|
||||
NonceError, SystemError,
|
||||
},
|
||||
system_program,
|
||||
transaction::Transaction,
|
||||
transaction::{Transaction, TransactionError},
|
||||
};
|
||||
use std::sync::Arc;
|
||||
|
||||
@ -56,7 +60,8 @@ impl NonceSubCommands for App<'_, '_> {
|
||||
.required(true),
|
||||
"Account to be granted authority of the nonce account. "),
|
||||
)
|
||||
.arg(nonce_authority_arg()),
|
||||
.arg(nonce_authority_arg())
|
||||
.arg(memo_arg()),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("create-nonce-account")
|
||||
@ -91,7 +96,8 @@ impl NonceSubCommands for App<'_, '_> {
|
||||
.value_name("STRING")
|
||||
.takes_value(true)
|
||||
.help("Seed for address generation; if specified, the resulting account will be at a derived address of the NONCE_ACCOUNT pubkey")
|
||||
),
|
||||
)
|
||||
.arg(memo_arg()),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("nonce")
|
||||
@ -115,7 +121,8 @@ impl NonceSubCommands for App<'_, '_> {
|
||||
.required(true),
|
||||
"Address of the nonce account. "),
|
||||
)
|
||||
.arg(nonce_authority_arg()),
|
||||
.arg(nonce_authority_arg())
|
||||
.arg(memo_arg()),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("nonce-account")
|
||||
@ -161,7 +168,8 @@ impl NonceSubCommands for App<'_, '_> {
|
||||
.validator(is_amount)
|
||||
.help("The amount to withdraw from the nonce account, in SOL"),
|
||||
)
|
||||
.arg(nonce_authority_arg()),
|
||||
.arg(nonce_authority_arg())
|
||||
.arg(memo_arg()),
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -363,8 +371,21 @@ pub fn process_authorize_nonce_account(
|
||||
&tx.message,
|
||||
config.commitment,
|
||||
)?;
|
||||
let merge_errors =
|
||||
get_feature_is_active(rpc_client, &merge_nonce_error_into_system_error::id())?;
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner(&tx);
|
||||
log_instruction_custom_error::<NonceError>(result, &config)
|
||||
|
||||
if merge_errors {
|
||||
log_instruction_custom_error::<SystemError>(result, config)
|
||||
} else {
|
||||
log_instruction_custom_error_ex::<NonceError, _>(result, config, |ix_error| {
|
||||
if let InstructionError::Custom(_) = ix_error {
|
||||
instruction_to_nonce_error(ix_error, merge_errors)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn process_create_nonce_account(
|
||||
@ -448,8 +469,40 @@ pub fn process_create_nonce_account(
|
||||
|
||||
let mut tx = Transaction::new_unsigned(message);
|
||||
tx.try_sign(&config.signers, recent_blockhash)?;
|
||||
let merge_errors =
|
||||
get_feature_is_active(rpc_client, &merge_nonce_error_into_system_error::id())?;
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner(&tx);
|
||||
log_instruction_custom_error::<SystemError>(result, &config)
|
||||
|
||||
let err_ix_index = if let Err(err) = &result {
|
||||
err.get_transaction_error().and_then(|tx_err| {
|
||||
if let TransactionError::InstructionError(ix_index, _) = tx_err {
|
||||
Some(ix_index)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
match err_ix_index {
|
||||
// SystemInstruction::InitializeNonceAccount failed
|
||||
Some(1) => {
|
||||
if merge_errors {
|
||||
log_instruction_custom_error::<SystemError>(result, config)
|
||||
} else {
|
||||
log_instruction_custom_error_ex::<NonceError, _>(result, config, |ix_error| {
|
||||
if let InstructionError::Custom(_) = ix_error {
|
||||
instruction_to_nonce_error(ix_error, merge_errors)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
// SystemInstruction::CreateAccount{,WithSeed} failed
|
||||
_ => log_instruction_custom_error::<SystemError>(result, config),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn process_get_nonce(
|
||||
@ -474,10 +527,10 @@ pub fn process_new_nonce(
|
||||
) -> ProcessResult {
|
||||
check_unique_pubkeys(
|
||||
(&config.signers[0].pubkey(), "cli keypair".to_string()),
|
||||
(&nonce_account, "nonce_account_pubkey".to_string()),
|
||||
(nonce_account, "nonce_account_pubkey".to_string()),
|
||||
)?;
|
||||
|
||||
if let Err(err) = rpc_client.get_account(&nonce_account) {
|
||||
if let Err(err) = rpc_client.get_account(nonce_account) {
|
||||
return Err(CliError::BadParameter(format!(
|
||||
"Unable to advance nonce account {}. error: {}",
|
||||
nonce_account, err
|
||||
@ -487,7 +540,7 @@ pub fn process_new_nonce(
|
||||
|
||||
let nonce_authority = config.signers[nonce_authority];
|
||||
let ixs = vec![advance_nonce_account(
|
||||
&nonce_account,
|
||||
nonce_account,
|
||||
&nonce_authority.pubkey(),
|
||||
)]
|
||||
.with_memo(memo);
|
||||
@ -502,8 +555,21 @@ pub fn process_new_nonce(
|
||||
&tx.message,
|
||||
config.commitment,
|
||||
)?;
|
||||
let merge_errors =
|
||||
get_feature_is_active(rpc_client, &merge_nonce_error_into_system_error::id())?;
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner(&tx);
|
||||
log_instruction_custom_error::<SystemError>(result, &config)
|
||||
|
||||
if merge_errors {
|
||||
log_instruction_custom_error::<SystemError>(result, config)
|
||||
} else {
|
||||
log_instruction_custom_error_ex::<NonceError, _>(result, config, |ix_error| {
|
||||
if let InstructionError::Custom(_) = ix_error {
|
||||
instruction_to_nonce_error(ix_error, merge_errors)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn process_show_nonce_account(
|
||||
@ -522,7 +588,7 @@ pub fn process_show_nonce_account(
|
||||
use_lamports_unit,
|
||||
..CliNonceAccount::default()
|
||||
};
|
||||
if let Some(ref data) = data {
|
||||
if let Some(data) = data {
|
||||
nonce_account.nonce = Some(data.blockhash.to_string());
|
||||
nonce_account.lamports_per_signature = Some(data.fee_calculator.lamports_per_signature);
|
||||
nonce_account.authority = Some(data.authority.to_string());
|
||||
@ -565,14 +631,27 @@ pub fn process_withdraw_from_nonce_account(
|
||||
&tx.message,
|
||||
config.commitment,
|
||||
)?;
|
||||
let merge_errors =
|
||||
get_feature_is_active(rpc_client, &merge_nonce_error_into_system_error::id())?;
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner(&tx);
|
||||
log_instruction_custom_error::<NonceError>(result, &config)
|
||||
|
||||
if merge_errors {
|
||||
log_instruction_custom_error::<SystemError>(result, config)
|
||||
} else {
|
||||
log_instruction_custom_error_ex::<NonceError, _>(result, config, |ix_error| {
|
||||
if let InstructionError::Custom(_) = ix_error {
|
||||
instruction_to_nonce_error(ix_error, merge_errors)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::cli::{app, parse_command};
|
||||
use crate::{clap_app::get_clap_app, cli::parse_command};
|
||||
use solana_sdk::{
|
||||
account::Account,
|
||||
account_utils::StateMut,
|
||||
@ -592,11 +671,11 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_parse_command() {
|
||||
let test_commands = app("test", "desc", "version");
|
||||
let test_commands = get_clap_app("test", "desc", "version");
|
||||
let default_keypair = Keypair::new();
|
||||
let (default_keypair_file, mut tmp_file) = make_tmp_file();
|
||||
write_keypair(&default_keypair, tmp_file.as_file_mut()).unwrap();
|
||||
let default_signer = DefaultSigner::new(default_keypair_file.clone());
|
||||
let default_signer = DefaultSigner::new("", &default_keypair_file);
|
||||
let (keypair_file, mut tmp_file) = make_tmp_file();
|
||||
let nonce_account_keypair = Keypair::new();
|
||||
write_keypair(&nonce_account_keypair, tmp_file.as_file_mut()).unwrap();
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -92,7 +92,7 @@ where
|
||||
Ok((message, spend))
|
||||
} else {
|
||||
let from_balance = rpc_client
|
||||
.get_balance_with_commitment(&from_pubkey, commitment)?
|
||||
.get_balance_with_commitment(from_pubkey, commitment)?
|
||||
.value;
|
||||
let (message, SpendAndFee { spend, fee }) = resolve_spend_message(
|
||||
amount,
|
||||
|
1117
cli/src/stake.rs
1117
cli/src/stake.rs
File diff suppressed because it is too large
Load Diff
@ -119,7 +119,7 @@ fn parse_validator_info(
|
||||
let key_list: ConfigKeys = deserialize(&account.data)?;
|
||||
if !key_list.keys.is_empty() {
|
||||
let (validator_pubkey, _) = key_list.keys[1];
|
||||
let validator_info_string: String = deserialize(&get_config_data(&account.data)?)?;
|
||||
let validator_info_string: String = deserialize(get_config_data(&account.data)?)?;
|
||||
let validator_info: Map<_, _> = serde_json::from_str(&validator_info_string)?;
|
||||
Ok((validator_pubkey, validator_info))
|
||||
} else {
|
||||
@ -246,7 +246,7 @@ pub fn process_set_validator_info(
|
||||
) -> ProcessResult {
|
||||
// Validate keybase username
|
||||
if let Some(string) = validator_info.get("keybaseUsername") {
|
||||
let result = verify_keybase(&config.signers[0].pubkey(), &string);
|
||||
let result = verify_keybase(&config.signers[0].pubkey(), string);
|
||||
if result.is_err() {
|
||||
if force_keybase {
|
||||
println!("--force supplied, ignoring: {:?}", result);
|
||||
@ -272,7 +272,7 @@ pub fn process_set_validator_info(
|
||||
},
|
||||
)
|
||||
.find(|(pubkey, account)| {
|
||||
let (validator_pubkey, _) = parse_validator_info(&pubkey, &account).unwrap();
|
||||
let (validator_pubkey, _) = parse_validator_info(pubkey, account).unwrap();
|
||||
validator_pubkey == config.signers[0].pubkey()
|
||||
});
|
||||
|
||||
@ -393,7 +393,7 @@ pub fn process_get_validator_info(
|
||||
}
|
||||
for (validator_info_pubkey, validator_info_account) in validator_info.iter() {
|
||||
let (validator_pubkey, validator_info) =
|
||||
parse_validator_info(&validator_info_pubkey, &validator_info_account)?;
|
||||
parse_validator_info(validator_info_pubkey, validator_info_account)?;
|
||||
validator_info_list.push(CliValidatorInfo {
|
||||
identity_pubkey: validator_pubkey.to_string(),
|
||||
info_pubkey: validator_info_pubkey.to_string(),
|
||||
@ -408,7 +408,7 @@ pub fn process_get_validator_info(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::cli::app;
|
||||
use crate::clap_app::get_clap_app;
|
||||
use bincode::{serialize, serialized_size};
|
||||
use serde_json::json;
|
||||
|
||||
@ -432,7 +432,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_parse_args() {
|
||||
let matches = app("test", "desc", "version").get_matches_from(vec![
|
||||
let matches = get_clap_app("test", "desc", "version").get_matches_from(vec![
|
||||
"test",
|
||||
"validator-info",
|
||||
"publish",
|
||||
@ -451,7 +451,7 @@ mod tests {
|
||||
"name": "Alice",
|
||||
"keybaseUsername": "alice_keybase",
|
||||
});
|
||||
assert_eq!(parse_args(&matches), expected);
|
||||
assert_eq!(parse_args(matches), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
303
cli/src/vote.rs
303
cli/src/vote.rs
@ -6,6 +6,7 @@ use crate::{
|
||||
},
|
||||
memo::WithMemo,
|
||||
spend_utils::{resolve_spend_tx_and_check_account_balance, SpendAmount},
|
||||
stake::check_current_authority,
|
||||
};
|
||||
use clap::{value_t_or_exit, App, Arg, ArgMatches, SubCommand};
|
||||
use solana_clap_utils::{
|
||||
@ -55,6 +56,15 @@ impl VoteSubCommands for App<'_, '_> {
|
||||
.validator(is_valid_signer)
|
||||
.help("Keypair of validator that will vote with this account"),
|
||||
)
|
||||
.arg(
|
||||
pubkey!(Arg::with_name("authorized_withdrawer")
|
||||
.index(3)
|
||||
.value_name("WITHDRAWER_PUBKEY")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.long("authorized-withdrawer"),
|
||||
"Public key of the authorized withdrawer")
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("commission")
|
||||
.long("commission")
|
||||
@ -70,10 +80,12 @@ impl VoteSubCommands for App<'_, '_> {
|
||||
"Public key of the authorized voter [default: validator identity pubkey]. "),
|
||||
)
|
||||
.arg(
|
||||
pubkey!(Arg::with_name("authorized_withdrawer")
|
||||
.long("authorized-withdrawer")
|
||||
.value_name("WITHDRAWER_PUBKEY"),
|
||||
"Public key of the authorized withdrawer [default: validator identity pubkey]. "),
|
||||
Arg::with_name("allow_unsafe_authorized_withdrawer")
|
||||
.long("allow-unsafe-authorized-withdrawer")
|
||||
.takes_value(false)
|
||||
.help("Allow an authorized withdrawer pubkey to be identical to the validator identity \
|
||||
account pubkey or vote account pubkey, which is normally an unsafe \
|
||||
configuration and should be avoided."),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("seed")
|
||||
@ -82,7 +94,7 @@ impl VoteSubCommands for App<'_, '_> {
|
||||
.takes_value(true)
|
||||
.help("Seed for address generation; if specified, the resulting account will be at a derived address of the VOTE ACCOUNT pubkey")
|
||||
)
|
||||
.arg(memo_arg())
|
||||
.arg(memo_arg())
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("vote-authorize-voter")
|
||||
@ -109,7 +121,7 @@ impl VoteSubCommands for App<'_, '_> {
|
||||
.required(true),
|
||||
"New authorized vote signer. "),
|
||||
)
|
||||
.arg(memo_arg())
|
||||
.arg(memo_arg())
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("vote-authorize-withdrawer")
|
||||
@ -136,7 +148,65 @@ impl VoteSubCommands for App<'_, '_> {
|
||||
.required(true),
|
||||
"New authorized withdrawer. "),
|
||||
)
|
||||
.arg(memo_arg())
|
||||
.arg(memo_arg())
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("vote-authorize-voter-checked")
|
||||
.about("Authorize a new vote signing keypair for the given vote account, \
|
||||
checking the new authority as a signer")
|
||||
.arg(
|
||||
pubkey!(Arg::with_name("vote_account_pubkey")
|
||||
.index(1)
|
||||
.value_name("VOTE_ACCOUNT_ADDRESS")
|
||||
.required(true),
|
||||
"Vote account in which to set the authorized voter. "),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("authorized")
|
||||
.index(2)
|
||||
.value_name("AUTHORIZED_KEYPAIR")
|
||||
.required(true)
|
||||
.validator(is_valid_signer)
|
||||
.help("Current authorized vote signer."),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("new_authorized")
|
||||
.index(3)
|
||||
.value_name("NEW_AUTHORIZED_KEYPAIR")
|
||||
.required(true)
|
||||
.validator(is_valid_signer)
|
||||
.help("New authorized vote signer."),
|
||||
)
|
||||
.arg(memo_arg())
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("vote-authorize-withdrawer-checked")
|
||||
.about("Authorize a new withdraw signing keypair for the given vote account, \
|
||||
checking the new authority as a signer")
|
||||
.arg(
|
||||
pubkey!(Arg::with_name("vote_account_pubkey")
|
||||
.index(1)
|
||||
.value_name("VOTE_ACCOUNT_ADDRESS")
|
||||
.required(true),
|
||||
"Vote account in which to set the authorized withdrawer. "),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("authorized")
|
||||
.index(2)
|
||||
.value_name("AUTHORIZED_KEYPAIR")
|
||||
.required(true)
|
||||
.validator(is_valid_signer)
|
||||
.help("Current authorized withdrawer."),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("new_authorized")
|
||||
.index(3)
|
||||
.value_name("NEW_AUTHORIZED_KEYPAIR")
|
||||
.required(true)
|
||||
.validator(is_valid_signer)
|
||||
.help("New authorized withdrawer."),
|
||||
)
|
||||
.arg(memo_arg())
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("vote-update-validator")
|
||||
@ -166,7 +236,7 @@ impl VoteSubCommands for App<'_, '_> {
|
||||
.validator(is_valid_signer)
|
||||
.help("Authorized withdrawer keypair"),
|
||||
)
|
||||
.arg(memo_arg())
|
||||
.arg(memo_arg())
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("vote-update-commission")
|
||||
@ -196,7 +266,7 @@ impl VoteSubCommands for App<'_, '_> {
|
||||
.validator(is_valid_signer)
|
||||
.help("Authorized withdrawer keypair"),
|
||||
)
|
||||
.arg(memo_arg())
|
||||
.arg(memo_arg())
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("vote-account")
|
||||
@ -266,7 +336,7 @@ impl VoteSubCommands for App<'_, '_> {
|
||||
.validator(is_valid_signer)
|
||||
.help("Authorized withdrawer [default: cli config keypair]"),
|
||||
)
|
||||
.arg(memo_arg())
|
||||
.arg(memo_arg())
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -282,9 +352,28 @@ pub fn parse_create_vote_account(
|
||||
signer_of(matches, "identity_account", wallet_manager)?;
|
||||
let commission = value_t_or_exit!(matches, "commission", u8);
|
||||
let authorized_voter = pubkey_of_signer(matches, "authorized_voter", wallet_manager)?;
|
||||
let authorized_withdrawer = pubkey_of_signer(matches, "authorized_withdrawer", wallet_manager)?;
|
||||
let authorized_withdrawer =
|
||||
pubkey_of_signer(matches, "authorized_withdrawer", wallet_manager)?.unwrap();
|
||||
let allow_unsafe = matches.is_present("allow_unsafe_authorized_withdrawer");
|
||||
let memo = matches.value_of(MEMO_ARG.name).map(String::from);
|
||||
|
||||
if !allow_unsafe {
|
||||
if authorized_withdrawer == vote_account_pubkey.unwrap() {
|
||||
return Err(CliError::BadParameter(
|
||||
"Authorized withdrawer pubkey is identical to vote \
|
||||
account pubkey, an unsafe configuration"
|
||||
.to_owned(),
|
||||
));
|
||||
}
|
||||
if authorized_withdrawer == identity_pubkey.unwrap() {
|
||||
return Err(CliError::BadParameter(
|
||||
"Authorized withdrawer pubkey is identical to identity \
|
||||
account pubkey, an unsafe configuration"
|
||||
.to_owned(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
let payer_provided = None;
|
||||
let signer_info = default_signer.generate_unique_signers(
|
||||
vec![payer_provided, vote_account, identity_account],
|
||||
@ -311,19 +400,25 @@ pub fn parse_vote_authorize(
|
||||
default_signer: &DefaultSigner,
|
||||
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
|
||||
vote_authorize: VoteAuthorize,
|
||||
checked: bool,
|
||||
) -> Result<CliCommandInfo, CliError> {
|
||||
let vote_account_pubkey =
|
||||
pubkey_of_signer(matches, "vote_account_pubkey", wallet_manager)?.unwrap();
|
||||
let new_authorized_pubkey =
|
||||
pubkey_of_signer(matches, "new_authorized_pubkey", wallet_manager)?.unwrap();
|
||||
let (authorized, _) = signer_of(matches, "authorized", wallet_manager)?;
|
||||
let (authorized, authorized_pubkey) = signer_of(matches, "authorized", wallet_manager)?;
|
||||
|
||||
let payer_provided = None;
|
||||
let signer_info = default_signer.generate_unique_signers(
|
||||
vec![payer_provided, authorized],
|
||||
matches,
|
||||
wallet_manager,
|
||||
)?;
|
||||
let mut signers = vec![payer_provided, authorized];
|
||||
|
||||
let new_authorized_pubkey = if checked {
|
||||
let (new_authorized_signer, new_authorized_pubkey) =
|
||||
signer_of(matches, "new_authorized", wallet_manager)?;
|
||||
signers.push(new_authorized_signer);
|
||||
new_authorized_pubkey.unwrap()
|
||||
} else {
|
||||
pubkey_of_signer(matches, "new_authorized_pubkey", wallet_manager)?.unwrap()
|
||||
};
|
||||
|
||||
let signer_info = default_signer.generate_unique_signers(signers, matches, wallet_manager)?;
|
||||
let memo = matches.value_of(MEMO_ARG.name).map(String::from);
|
||||
|
||||
Ok(CliCommandInfo {
|
||||
@ -332,6 +427,12 @@ pub fn parse_vote_authorize(
|
||||
new_authorized_pubkey,
|
||||
vote_authorize,
|
||||
memo,
|
||||
authorized: signer_info.index_of(authorized_pubkey).unwrap(),
|
||||
new_authorized: if checked {
|
||||
signer_info.index_of(Some(new_authorized_pubkey))
|
||||
} else {
|
||||
None
|
||||
},
|
||||
},
|
||||
signers: signer_info.signers,
|
||||
})
|
||||
@ -461,14 +562,14 @@ pub fn process_create_vote_account(
|
||||
seed: &Option<String>,
|
||||
identity_account: SignerIndex,
|
||||
authorized_voter: &Option<Pubkey>,
|
||||
authorized_withdrawer: &Option<Pubkey>,
|
||||
authorized_withdrawer: Pubkey,
|
||||
commission: u8,
|
||||
memo: Option<&String>,
|
||||
) -> ProcessResult {
|
||||
let vote_account = config.signers[vote_account];
|
||||
let vote_account_pubkey = vote_account.pubkey();
|
||||
let vote_account_address = if let Some(seed) = seed {
|
||||
Pubkey::create_with_seed(&vote_account_pubkey, &seed, &solana_vote_program::id())?
|
||||
Pubkey::create_with_seed(&vote_account_pubkey, seed, &solana_vote_program::id())?
|
||||
} else {
|
||||
vote_account_pubkey
|
||||
};
|
||||
@ -493,7 +594,7 @@ pub fn process_create_vote_account(
|
||||
let vote_init = VoteInit {
|
||||
node_pubkey: identity_pubkey,
|
||||
authorized_voter: authorized_voter.unwrap_or(identity_pubkey),
|
||||
authorized_withdrawer: authorized_withdrawer.unwrap_or(identity_pubkey),
|
||||
authorized_withdrawer,
|
||||
commission,
|
||||
};
|
||||
|
||||
@ -549,7 +650,7 @@ pub fn process_create_vote_account(
|
||||
let mut tx = Transaction::new_unsigned(message);
|
||||
tx.try_sign(&config.signers, recent_blockhash)?;
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner(&tx);
|
||||
log_instruction_custom_error::<SystemError>(result, &config)
|
||||
log_instruction_custom_error::<SystemError>(result, config)
|
||||
}
|
||||
|
||||
pub fn process_vote_authorize(
|
||||
@ -558,28 +659,54 @@ pub fn process_vote_authorize(
|
||||
vote_account_pubkey: &Pubkey,
|
||||
new_authorized_pubkey: &Pubkey,
|
||||
vote_authorize: VoteAuthorize,
|
||||
authorized: SignerIndex,
|
||||
new_authorized: Option<SignerIndex>,
|
||||
memo: Option<&String>,
|
||||
) -> ProcessResult {
|
||||
// If the `authorized_account` is also the fee payer, `config.signers` will only have one
|
||||
// keypair in it
|
||||
let authorized = if config.signers.len() == 2 {
|
||||
config.signers[1]
|
||||
} else {
|
||||
config.signers[0]
|
||||
};
|
||||
let authorized = config.signers[authorized];
|
||||
let new_authorized_signer = new_authorized.map(|index| config.signers[index]);
|
||||
|
||||
check_unique_pubkeys(
|
||||
(&authorized.pubkey(), "authorized_account".to_string()),
|
||||
(new_authorized_pubkey, "new_authorized_pubkey".to_string()),
|
||||
)?;
|
||||
|
||||
let (_, vote_state) = get_vote_account(rpc_client, vote_account_pubkey, config.commitment)?;
|
||||
match vote_authorize {
|
||||
VoteAuthorize::Voter => {
|
||||
let current_authorized_voter = vote_state
|
||||
.authorized_voters()
|
||||
.last()
|
||||
.ok_or_else(|| {
|
||||
CliError::RpcRequestError(
|
||||
"Invalid vote account state; no authorized voters found".to_string(),
|
||||
)
|
||||
})?
|
||||
.1;
|
||||
check_current_authority(current_authorized_voter, &authorized.pubkey())?
|
||||
}
|
||||
VoteAuthorize::Withdrawer => {
|
||||
check_current_authority(&vote_state.authorized_withdrawer, &authorized.pubkey())?
|
||||
}
|
||||
}
|
||||
|
||||
let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?;
|
||||
let ixs = vec![vote_instruction::authorize(
|
||||
vote_account_pubkey, // vote account to update
|
||||
&authorized.pubkey(), // current authorized
|
||||
new_authorized_pubkey, // new vote signer/withdrawer
|
||||
vote_authorize, // vote or withdraw
|
||||
)]
|
||||
.with_memo(memo);
|
||||
let vote_ix = if new_authorized_signer.is_some() {
|
||||
vote_instruction::authorize_checked(
|
||||
vote_account_pubkey, // vote account to update
|
||||
&authorized.pubkey(), // current authorized
|
||||
new_authorized_pubkey, // new vote signer/withdrawer
|
||||
vote_authorize, // vote or withdraw
|
||||
)
|
||||
} else {
|
||||
vote_instruction::authorize(
|
||||
vote_account_pubkey, // vote account to update
|
||||
&authorized.pubkey(), // current authorized
|
||||
new_authorized_pubkey, // new vote signer/withdrawer
|
||||
vote_authorize, // vote or withdraw
|
||||
)
|
||||
};
|
||||
let ixs = vec![vote_ix].with_memo(memo);
|
||||
|
||||
let message = Message::new(&ixs, Some(&config.signers[0].pubkey()));
|
||||
let mut tx = Transaction::new_unsigned(message);
|
||||
@ -592,7 +719,7 @@ pub fn process_vote_authorize(
|
||||
config.commitment,
|
||||
)?;
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner(&tx);
|
||||
log_instruction_custom_error::<VoteError>(result, &config)
|
||||
log_instruction_custom_error::<VoteError>(result, config)
|
||||
}
|
||||
|
||||
pub fn process_vote_update_validator(
|
||||
@ -629,7 +756,7 @@ pub fn process_vote_update_validator(
|
||||
config.commitment,
|
||||
)?;
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner(&tx);
|
||||
log_instruction_custom_error::<VoteError>(result, &config)
|
||||
log_instruction_custom_error::<VoteError>(result, config)
|
||||
}
|
||||
|
||||
pub fn process_vote_update_commission(
|
||||
@ -660,7 +787,7 @@ pub fn process_vote_update_commission(
|
||||
config.commitment,
|
||||
)?;
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner(&tx);
|
||||
log_instruction_custom_error::<VoteError>(result, &config)
|
||||
log_instruction_custom_error::<VoteError>(result, config)
|
||||
}
|
||||
|
||||
fn get_vote_account(
|
||||
@ -763,7 +890,7 @@ pub fn process_withdraw_from_vote_account(
|
||||
let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?;
|
||||
let withdraw_authority = config.signers[withdraw_authority];
|
||||
|
||||
let current_balance = rpc_client.get_balance(&vote_account_pubkey)?;
|
||||
let current_balance = rpc_client.get_balance(vote_account_pubkey)?;
|
||||
let minimum_balance = rpc_client.get_minimum_balance_for_rent_exemption(VoteState::size_of())?;
|
||||
|
||||
let lamports = match withdraw_amount {
|
||||
@ -798,13 +925,13 @@ pub fn process_withdraw_from_vote_account(
|
||||
config.commitment,
|
||||
)?;
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner(&transaction);
|
||||
log_instruction_custom_error::<VoteError>(result, &config)
|
||||
log_instruction_custom_error::<VoteError>(result, config)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::cli::{app, parse_command};
|
||||
use crate::{clap_app::get_clap_app, cli::parse_command};
|
||||
use solana_sdk::signature::{read_keypair_file, write_keypair, Keypair, Signer};
|
||||
use tempfile::NamedTempFile;
|
||||
|
||||
@ -815,7 +942,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_parse_command() {
|
||||
let test_commands = app("test", "desc", "version");
|
||||
let test_commands = get_clap_app("test", "desc", "version");
|
||||
let keypair = Keypair::new();
|
||||
let pubkey = keypair.pubkey();
|
||||
let pubkey_string = pubkey.to_string();
|
||||
@ -826,7 +953,7 @@ mod tests {
|
||||
let default_keypair = Keypair::new();
|
||||
let (default_keypair_file, mut tmp_file) = make_tmp_file();
|
||||
write_keypair(&default_keypair, tmp_file.as_file_mut()).unwrap();
|
||||
let default_signer = DefaultSigner::new(default_keypair_file.clone());
|
||||
let default_signer = DefaultSigner::new("", &default_keypair_file);
|
||||
|
||||
let test_authorize_voter = test_commands.clone().get_matches_from(vec![
|
||||
"test",
|
||||
@ -843,6 +970,8 @@ mod tests {
|
||||
new_authorized_pubkey: pubkey2,
|
||||
vote_authorize: VoteAuthorize::Voter,
|
||||
memo: None,
|
||||
authorized: 0,
|
||||
new_authorized: None,
|
||||
},
|
||||
signers: vec![read_keypair_file(&default_keypair_file).unwrap().into()],
|
||||
}
|
||||
@ -867,6 +996,8 @@ mod tests {
|
||||
new_authorized_pubkey: pubkey2,
|
||||
vote_authorize: VoteAuthorize::Voter,
|
||||
memo: None,
|
||||
authorized: 1,
|
||||
new_authorized: None,
|
||||
},
|
||||
signers: vec![
|
||||
read_keypair_file(&default_keypair_file).unwrap().into(),
|
||||
@ -875,18 +1006,84 @@ mod tests {
|
||||
}
|
||||
);
|
||||
|
||||
let (voter_keypair_file, mut tmp_file) = make_tmp_file();
|
||||
let voter_keypair = Keypair::new();
|
||||
write_keypair(&voter_keypair, tmp_file.as_file_mut()).unwrap();
|
||||
|
||||
let test_authorize_voter = test_commands.clone().get_matches_from(vec![
|
||||
"test",
|
||||
"vote-authorize-voter-checked",
|
||||
&pubkey_string,
|
||||
&default_keypair_file,
|
||||
&voter_keypair_file,
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&test_authorize_voter, &default_signer, &mut None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::VoteAuthorize {
|
||||
vote_account_pubkey: pubkey,
|
||||
new_authorized_pubkey: voter_keypair.pubkey(),
|
||||
vote_authorize: VoteAuthorize::Voter,
|
||||
memo: None,
|
||||
authorized: 0,
|
||||
new_authorized: Some(1),
|
||||
},
|
||||
signers: vec![
|
||||
read_keypair_file(&default_keypair_file).unwrap().into(),
|
||||
read_keypair_file(&voter_keypair_file).unwrap().into()
|
||||
],
|
||||
}
|
||||
);
|
||||
|
||||
let test_authorize_voter = test_commands.clone().get_matches_from(vec![
|
||||
"test",
|
||||
"vote-authorize-voter-checked",
|
||||
&pubkey_string,
|
||||
&authorized_keypair_file,
|
||||
&voter_keypair_file,
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&test_authorize_voter, &default_signer, &mut None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::VoteAuthorize {
|
||||
vote_account_pubkey: pubkey,
|
||||
new_authorized_pubkey: voter_keypair.pubkey(),
|
||||
vote_authorize: VoteAuthorize::Voter,
|
||||
memo: None,
|
||||
authorized: 1,
|
||||
new_authorized: Some(2),
|
||||
},
|
||||
signers: vec![
|
||||
read_keypair_file(&default_keypair_file).unwrap().into(),
|
||||
read_keypair_file(&authorized_keypair_file).unwrap().into(),
|
||||
read_keypair_file(&voter_keypair_file).unwrap().into(),
|
||||
],
|
||||
}
|
||||
);
|
||||
|
||||
let test_authorize_voter = test_commands.clone().get_matches_from(vec![
|
||||
"test",
|
||||
"vote-authorize-voter-checked",
|
||||
&pubkey_string,
|
||||
&authorized_keypair_file,
|
||||
&pubkey2_string,
|
||||
]);
|
||||
assert!(parse_command(&test_authorize_voter, &default_signer, &mut None).is_err());
|
||||
|
||||
let (keypair_file, mut tmp_file) = make_tmp_file();
|
||||
let keypair = Keypair::new();
|
||||
write_keypair(&keypair, tmp_file.as_file_mut()).unwrap();
|
||||
// Test CreateVoteAccount SubCommand
|
||||
let (identity_keypair_file, mut tmp_file) = make_tmp_file();
|
||||
let identity_keypair = Keypair::new();
|
||||
let authorized_withdrawer = Keypair::new().pubkey();
|
||||
write_keypair(&identity_keypair, tmp_file.as_file_mut()).unwrap();
|
||||
let test_create_vote_account = test_commands.clone().get_matches_from(vec![
|
||||
"test",
|
||||
"create-vote-account",
|
||||
&keypair_file,
|
||||
&identity_keypair_file,
|
||||
&authorized_withdrawer.to_string(),
|
||||
"--commission",
|
||||
"10",
|
||||
]);
|
||||
@ -898,7 +1095,7 @@ mod tests {
|
||||
seed: None,
|
||||
identity_account: 2,
|
||||
authorized_voter: None,
|
||||
authorized_withdrawer: None,
|
||||
authorized_withdrawer,
|
||||
commission: 10,
|
||||
memo: None,
|
||||
},
|
||||
@ -919,6 +1116,7 @@ mod tests {
|
||||
"create-vote-account",
|
||||
&keypair_file,
|
||||
&identity_keypair_file,
|
||||
&authorized_withdrawer.to_string(),
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&test_create_vote_account2, &default_signer, &mut None).unwrap(),
|
||||
@ -928,7 +1126,7 @@ mod tests {
|
||||
seed: None,
|
||||
identity_account: 2,
|
||||
authorized_voter: None,
|
||||
authorized_withdrawer: None,
|
||||
authorized_withdrawer,
|
||||
commission: 100,
|
||||
memo: None,
|
||||
},
|
||||
@ -951,6 +1149,7 @@ mod tests {
|
||||
"create-vote-account",
|
||||
&keypair_file,
|
||||
&identity_keypair_file,
|
||||
&authorized_withdrawer.to_string(),
|
||||
"--authorized-voter",
|
||||
&authed.to_string(),
|
||||
]);
|
||||
@ -962,7 +1161,7 @@ mod tests {
|
||||
seed: None,
|
||||
identity_account: 2,
|
||||
authorized_voter: Some(authed),
|
||||
authorized_withdrawer: None,
|
||||
authorized_withdrawer,
|
||||
commission: 100,
|
||||
memo: None,
|
||||
},
|
||||
@ -977,14 +1176,14 @@ mod tests {
|
||||
let (keypair_file, mut tmp_file) = make_tmp_file();
|
||||
let keypair = Keypair::new();
|
||||
write_keypair(&keypair, tmp_file.as_file_mut()).unwrap();
|
||||
// test init with an authed withdrawer
|
||||
// succeed even though withdrawer unsafe (because forcefully allowed)
|
||||
let test_create_vote_account4 = test_commands.clone().get_matches_from(vec![
|
||||
"test",
|
||||
"create-vote-account",
|
||||
&keypair_file,
|
||||
&identity_keypair_file,
|
||||
"--authorized-withdrawer",
|
||||
&authed.to_string(),
|
||||
&identity_keypair_file,
|
||||
"--allow-unsafe-authorized-withdrawer",
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&test_create_vote_account4, &default_signer, &mut None).unwrap(),
|
||||
@ -994,7 +1193,7 @@ mod tests {
|
||||
seed: None,
|
||||
identity_account: 2,
|
||||
authorized_voter: None,
|
||||
authorized_withdrawer: Some(authed),
|
||||
authorized_withdrawer: identity_keypair.pubkey(),
|
||||
commission: 100,
|
||||
memo: None,
|
||||
},
|
||||
|
744
cli/src/wallet.rs
Normal file
744
cli/src/wallet.rs
Normal file
@ -0,0 +1,744 @@
|
||||
use crate::{
|
||||
cli::{
|
||||
log_instruction_custom_error, request_and_confirm_airdrop, CliCommand, CliCommandInfo,
|
||||
CliConfig, CliError, ProcessResult,
|
||||
},
|
||||
memo::WithMemo,
|
||||
nonce::check_nonce_account,
|
||||
spend_utils::{resolve_spend_tx_and_check_account_balances, SpendAmount},
|
||||
};
|
||||
use clap::{value_t_or_exit, App, Arg, ArgMatches, SubCommand};
|
||||
use solana_account_decoder::{UiAccount, UiAccountEncoding};
|
||||
use solana_clap_utils::{
|
||||
fee_payer::*,
|
||||
input_parsers::*,
|
||||
input_validators::*,
|
||||
keypair::{DefaultSigner, SignerIndex},
|
||||
memo::*,
|
||||
nonce::*,
|
||||
offline::*,
|
||||
};
|
||||
use solana_cli_output::{
|
||||
display::build_balance_message, return_signers_with_config, CliAccount,
|
||||
CliSignatureVerificationStatus, CliTransaction, CliTransactionConfirmation, OutputFormat,
|
||||
ReturnSignersConfig,
|
||||
};
|
||||
use solana_client::{
|
||||
blockhash_query::BlockhashQuery, nonce_utils, rpc_client::RpcClient,
|
||||
rpc_config::RpcTransactionConfig, rpc_response::RpcKeyedAccount,
|
||||
};
|
||||
use solana_remote_wallet::remote_wallet::RemoteWalletManager;
|
||||
use solana_sdk::{
|
||||
commitment_config::CommitmentConfig,
|
||||
message::Message,
|
||||
pubkey::Pubkey,
|
||||
signature::Signature,
|
||||
stake,
|
||||
system_instruction::{self, SystemError},
|
||||
system_program,
|
||||
transaction::Transaction,
|
||||
};
|
||||
use solana_transaction_status::{EncodedTransaction, UiTransactionEncoding};
|
||||
use std::{fmt::Write as FmtWrite, fs::File, io::Write, sync::Arc};
|
||||
|
||||
pub trait WalletSubCommands {
|
||||
fn wallet_subcommands(self) -> Self;
|
||||
}
|
||||
|
||||
impl WalletSubCommands for App<'_, '_> {
|
||||
fn wallet_subcommands(self) -> Self {
|
||||
self.subcommand(
|
||||
SubCommand::with_name("account")
|
||||
.about("Show the contents of an account")
|
||||
.alias("account")
|
||||
.arg(
|
||||
pubkey!(Arg::with_name("account_pubkey")
|
||||
.index(1)
|
||||
.value_name("ACCOUNT_ADDRESS")
|
||||
.required(true),
|
||||
"Account key URI. ")
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("output_file")
|
||||
.long("output-file")
|
||||
.short("o")
|
||||
.value_name("FILEPATH")
|
||||
.takes_value(true)
|
||||
.help("Write the account data to this file"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("lamports")
|
||||
.long("lamports")
|
||||
.takes_value(false)
|
||||
.help("Display balance in lamports instead of SOL"),
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("address")
|
||||
.about("Get your public key")
|
||||
.arg(
|
||||
Arg::with_name("confirm_key")
|
||||
.long("confirm-key")
|
||||
.takes_value(false)
|
||||
.help("Confirm key on device; only relevant if using remote wallet"),
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("airdrop")
|
||||
.about("Request SOL from a faucet")
|
||||
.arg(
|
||||
Arg::with_name("amount")
|
||||
.index(1)
|
||||
.value_name("AMOUNT")
|
||||
.takes_value(true)
|
||||
.validator(is_amount)
|
||||
.required(true)
|
||||
.help("The airdrop amount to request, in SOL"),
|
||||
)
|
||||
.arg(
|
||||
pubkey!(Arg::with_name("to")
|
||||
.index(2)
|
||||
.value_name("RECIPIENT_ADDRESS"),
|
||||
"The account address of airdrop recipient. "),
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("balance")
|
||||
.about("Get your balance")
|
||||
.arg(
|
||||
pubkey!(Arg::with_name("pubkey")
|
||||
.index(1)
|
||||
.value_name("ACCOUNT_ADDRESS"),
|
||||
"The account address of the balance to check. ")
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("lamports")
|
||||
.long("lamports")
|
||||
.takes_value(false)
|
||||
.help("Display balance in lamports instead of SOL"),
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("confirm")
|
||||
.about("Confirm transaction by signature")
|
||||
.arg(
|
||||
Arg::with_name("signature")
|
||||
.index(1)
|
||||
.value_name("TRANSACTION_SIGNATURE")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.help("The transaction signature to confirm"),
|
||||
)
|
||||
.after_help(// Formatted specifically for the manually-indented heredoc string
|
||||
"Note: This will show more detailed information for finalized transactions with verbose mode (-v/--verbose).\
|
||||
\n\
|
||||
\nAccount modes:\
|
||||
\n |srwx|\
|
||||
\n s: signed\
|
||||
\n r: readable (always true)\
|
||||
\n w: writable\
|
||||
\n x: program account (inner instructions excluded)\
|
||||
"
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("create-address-with-seed")
|
||||
.about("Generate a derived account address with a seed")
|
||||
.arg(
|
||||
Arg::with_name("seed")
|
||||
.index(1)
|
||||
.value_name("SEED_STRING")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.validator(is_derived_address_seed)
|
||||
.help("The seed. Must not take more than 32 bytes to encode as utf-8"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("program_id")
|
||||
.index(2)
|
||||
.value_name("PROGRAM_ID")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.help(
|
||||
"The program_id that the address will ultimately be used for, \n\
|
||||
or one of NONCE, STAKE, and VOTE keywords",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
pubkey!(Arg::with_name("from")
|
||||
.long("from")
|
||||
.value_name("FROM_PUBKEY")
|
||||
.required(false),
|
||||
"From (base) key, [default: cli config keypair]. "),
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("decode-transaction")
|
||||
.about("Decode a serialized transaction")
|
||||
.arg(
|
||||
Arg::with_name("transaction")
|
||||
.index(1)
|
||||
.value_name("TRANSACTION")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.help("transaction to decode"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("encoding")
|
||||
.index(2)
|
||||
.value_name("ENCODING")
|
||||
.possible_values(&["base58", "base64"]) // Subset of `UiTransactionEncoding` enum
|
||||
.default_value("base58")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.help("transaction encoding"),
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("resolve-signer")
|
||||
.about("Checks that a signer is valid, and returns its specific path; useful for signers that may be specified generally, eg. usb://ledger")
|
||||
.arg(
|
||||
Arg::with_name("signer")
|
||||
.index(1)
|
||||
.value_name("SIGNER_KEYPAIR")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.validator(is_valid_signer)
|
||||
.help("The signer path to resolve")
|
||||
)
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("transfer")
|
||||
.about("Transfer funds between system accounts")
|
||||
.alias("pay")
|
||||
.arg(
|
||||
pubkey!(Arg::with_name("to")
|
||||
.index(1)
|
||||
.value_name("RECIPIENT_ADDRESS")
|
||||
.required(true),
|
||||
"The account address of recipient. "),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("amount")
|
||||
.index(2)
|
||||
.value_name("AMOUNT")
|
||||
.takes_value(true)
|
||||
.validator(is_amount_or_all)
|
||||
.required(true)
|
||||
.help("The amount to send, in SOL; accepts keyword ALL"),
|
||||
)
|
||||
.arg(
|
||||
pubkey!(Arg::with_name("from")
|
||||
.long("from")
|
||||
.value_name("FROM_ADDRESS"),
|
||||
"Source account of funds (if different from client local account). "),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("no_wait")
|
||||
.long("no-wait")
|
||||
.takes_value(false)
|
||||
.help("Return signature immediately after submitting the transaction, instead of waiting for confirmations"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("derived_address_seed")
|
||||
.long("derived-address-seed")
|
||||
.takes_value(true)
|
||||
.value_name("SEED_STRING")
|
||||
.requires("derived_address_program_id")
|
||||
.validator(is_derived_address_seed)
|
||||
.hidden(true)
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("derived_address_program_id")
|
||||
.long("derived-address-program-id")
|
||||
.takes_value(true)
|
||||
.value_name("PROGRAM_ID")
|
||||
.requires("derived_address_seed")
|
||||
.hidden(true)
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("allow_unfunded_recipient")
|
||||
.long("allow-unfunded-recipient")
|
||||
.takes_value(false)
|
||||
.help("Complete the transfer even if the recipient address is not funded")
|
||||
)
|
||||
.offline_args()
|
||||
.nonce_args(false)
|
||||
.arg(memo_arg())
|
||||
.arg(fee_payer_arg()),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn resolve_derived_address_program_id(matches: &ArgMatches<'_>, arg_name: &str) -> Option<Pubkey> {
|
||||
matches.value_of(arg_name).and_then(|v| match v {
|
||||
"NONCE" => Some(system_program::id()),
|
||||
"STAKE" => Some(stake::program::id()),
|
||||
"VOTE" => Some(solana_vote_program::id()),
|
||||
_ => pubkey_of(matches, arg_name),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn parse_account(
|
||||
matches: &ArgMatches<'_>,
|
||||
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
|
||||
) -> Result<CliCommandInfo, CliError> {
|
||||
let account_pubkey = pubkey_of_signer(matches, "account_pubkey", wallet_manager)?.unwrap();
|
||||
let output_file = matches.value_of("output_file");
|
||||
let use_lamports_unit = matches.is_present("lamports");
|
||||
Ok(CliCommandInfo {
|
||||
command: CliCommand::ShowAccount {
|
||||
pubkey: account_pubkey,
|
||||
output_file: output_file.map(ToString::to_string),
|
||||
use_lamports_unit,
|
||||
},
|
||||
signers: vec![],
|
||||
})
|
||||
}
|
||||
|
||||
pub fn parse_airdrop(
|
||||
matches: &ArgMatches<'_>,
|
||||
default_signer: &DefaultSigner,
|
||||
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
|
||||
) -> Result<CliCommandInfo, CliError> {
|
||||
let pubkey = pubkey_of_signer(matches, "to", wallet_manager)?;
|
||||
let signers = if pubkey.is_some() {
|
||||
vec![]
|
||||
} else {
|
||||
vec![default_signer.signer_from_path(matches, wallet_manager)?]
|
||||
};
|
||||
let lamports = lamports_of_sol(matches, "amount").unwrap();
|
||||
Ok(CliCommandInfo {
|
||||
command: CliCommand::Airdrop { pubkey, lamports },
|
||||
signers,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn parse_balance(
|
||||
matches: &ArgMatches<'_>,
|
||||
default_signer: &DefaultSigner,
|
||||
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
|
||||
) -> Result<CliCommandInfo, CliError> {
|
||||
let pubkey = pubkey_of_signer(matches, "pubkey", wallet_manager)?;
|
||||
let signers = if pubkey.is_some() {
|
||||
vec![]
|
||||
} else {
|
||||
vec![default_signer.signer_from_path(matches, wallet_manager)?]
|
||||
};
|
||||
Ok(CliCommandInfo {
|
||||
command: CliCommand::Balance {
|
||||
pubkey,
|
||||
use_lamports_unit: matches.is_present("lamports"),
|
||||
},
|
||||
signers,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn parse_decode_transaction(matches: &ArgMatches<'_>) -> Result<CliCommandInfo, CliError> {
|
||||
let blob = value_t_or_exit!(matches, "transaction", String);
|
||||
let encoding = match matches.value_of("encoding").unwrap() {
|
||||
"base58" => UiTransactionEncoding::Base58,
|
||||
"base64" => UiTransactionEncoding::Base64,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
let encoded_transaction = EncodedTransaction::Binary(blob, encoding);
|
||||
if let Some(transaction) = encoded_transaction.decode() {
|
||||
Ok(CliCommandInfo {
|
||||
command: CliCommand::DecodeTransaction(transaction),
|
||||
signers: vec![],
|
||||
})
|
||||
} else {
|
||||
Err(CliError::BadParameter(
|
||||
"Unable to decode transaction".to_string(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_create_address_with_seed(
|
||||
matches: &ArgMatches<'_>,
|
||||
default_signer: &DefaultSigner,
|
||||
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
|
||||
) -> Result<CliCommandInfo, CliError> {
|
||||
let from_pubkey = pubkey_of_signer(matches, "from", wallet_manager)?;
|
||||
let signers = if from_pubkey.is_some() {
|
||||
vec![]
|
||||
} else {
|
||||
vec![default_signer.signer_from_path(matches, wallet_manager)?]
|
||||
};
|
||||
|
||||
let program_id = resolve_derived_address_program_id(matches, "program_id").unwrap();
|
||||
|
||||
let seed = matches.value_of("seed").unwrap().to_string();
|
||||
|
||||
Ok(CliCommandInfo {
|
||||
command: CliCommand::CreateAddressWithSeed {
|
||||
from_pubkey,
|
||||
seed,
|
||||
program_id,
|
||||
},
|
||||
signers,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn parse_transfer(
|
||||
matches: &ArgMatches<'_>,
|
||||
default_signer: &DefaultSigner,
|
||||
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
|
||||
) -> Result<CliCommandInfo, CliError> {
|
||||
let amount = SpendAmount::new_from_matches(matches, "amount");
|
||||
let to = pubkey_of_signer(matches, "to", wallet_manager)?.unwrap();
|
||||
let sign_only = matches.is_present(SIGN_ONLY_ARG.name);
|
||||
let dump_transaction_message = matches.is_present(DUMP_TRANSACTION_MESSAGE.name);
|
||||
let no_wait = matches.is_present("no_wait");
|
||||
let blockhash_query = BlockhashQuery::new_from_matches(matches);
|
||||
let nonce_account = pubkey_of_signer(matches, NONCE_ARG.name, wallet_manager)?;
|
||||
let (nonce_authority, nonce_authority_pubkey) =
|
||||
signer_of(matches, NONCE_AUTHORITY_ARG.name, wallet_manager)?;
|
||||
let memo = matches.value_of(MEMO_ARG.name).map(String::from);
|
||||
let (fee_payer, fee_payer_pubkey) = signer_of(matches, FEE_PAYER_ARG.name, wallet_manager)?;
|
||||
let (from, from_pubkey) = signer_of(matches, "from", wallet_manager)?;
|
||||
let allow_unfunded_recipient = matches.is_present("allow_unfunded_recipient");
|
||||
|
||||
let mut bulk_signers = vec![fee_payer, from];
|
||||
if nonce_account.is_some() {
|
||||
bulk_signers.push(nonce_authority);
|
||||
}
|
||||
|
||||
let signer_info =
|
||||
default_signer.generate_unique_signers(bulk_signers, matches, wallet_manager)?;
|
||||
|
||||
let derived_address_seed = matches
|
||||
.value_of("derived_address_seed")
|
||||
.map(|s| s.to_string());
|
||||
let derived_address_program_id =
|
||||
resolve_derived_address_program_id(matches, "derived_address_program_id");
|
||||
|
||||
Ok(CliCommandInfo {
|
||||
command: CliCommand::Transfer {
|
||||
amount,
|
||||
to,
|
||||
sign_only,
|
||||
dump_transaction_message,
|
||||
allow_unfunded_recipient,
|
||||
no_wait,
|
||||
blockhash_query,
|
||||
nonce_account,
|
||||
nonce_authority: signer_info.index_of(nonce_authority_pubkey).unwrap(),
|
||||
memo,
|
||||
fee_payer: signer_info.index_of(fee_payer_pubkey).unwrap(),
|
||||
from: signer_info.index_of(from_pubkey).unwrap(),
|
||||
derived_address_seed,
|
||||
derived_address_program_id,
|
||||
},
|
||||
signers: signer_info.signers,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn process_show_account(
|
||||
rpc_client: &RpcClient,
|
||||
config: &CliConfig,
|
||||
account_pubkey: &Pubkey,
|
||||
output_file: &Option<String>,
|
||||
use_lamports_unit: bool,
|
||||
) -> ProcessResult {
|
||||
let account = rpc_client.get_account(account_pubkey)?;
|
||||
let data = account.data.clone();
|
||||
let cli_account = CliAccount {
|
||||
keyed_account: RpcKeyedAccount {
|
||||
pubkey: account_pubkey.to_string(),
|
||||
account: UiAccount::encode(
|
||||
account_pubkey,
|
||||
&account,
|
||||
UiAccountEncoding::Base64,
|
||||
None,
|
||||
None,
|
||||
),
|
||||
},
|
||||
use_lamports_unit,
|
||||
};
|
||||
|
||||
let mut account_string = config.output_format.formatted_string(&cli_account);
|
||||
|
||||
if config.output_format == OutputFormat::Display
|
||||
|| config.output_format == OutputFormat::DisplayVerbose
|
||||
{
|
||||
if let Some(output_file) = output_file {
|
||||
let mut f = File::create(output_file)?;
|
||||
f.write_all(&data)?;
|
||||
writeln!(&mut account_string)?;
|
||||
writeln!(&mut account_string, "Wrote account data to {}", output_file)?;
|
||||
} else if !data.is_empty() {
|
||||
use pretty_hex::*;
|
||||
writeln!(&mut account_string, "{:?}", data.hex_dump())?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(account_string)
|
||||
}
|
||||
|
||||
pub fn process_airdrop(
|
||||
rpc_client: &RpcClient,
|
||||
config: &CliConfig,
|
||||
pubkey: &Option<Pubkey>,
|
||||
lamports: u64,
|
||||
) -> ProcessResult {
|
||||
let pubkey = if let Some(pubkey) = pubkey {
|
||||
*pubkey
|
||||
} else {
|
||||
config.pubkey()?
|
||||
};
|
||||
println!(
|
||||
"Requesting airdrop of {}",
|
||||
build_balance_message(lamports, false, true),
|
||||
);
|
||||
|
||||
let pre_balance = rpc_client.get_balance(&pubkey)?;
|
||||
|
||||
let result = request_and_confirm_airdrop(rpc_client, config, &pubkey, lamports);
|
||||
if let Ok(signature) = result {
|
||||
let signature_cli_message = log_instruction_custom_error::<SystemError>(result, config)?;
|
||||
println!("{}", signature_cli_message);
|
||||
|
||||
let current_balance = rpc_client.get_balance(&pubkey)?;
|
||||
|
||||
if current_balance < pre_balance.saturating_add(lamports) {
|
||||
println!("Balance unchanged");
|
||||
println!("Run `solana confirm -v {:?}` for more info", signature);
|
||||
Ok("".to_string())
|
||||
} else {
|
||||
Ok(build_balance_message(current_balance, false, true))
|
||||
}
|
||||
} else {
|
||||
log_instruction_custom_error::<SystemError>(result, config)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn process_balance(
|
||||
rpc_client: &RpcClient,
|
||||
config: &CliConfig,
|
||||
pubkey: &Option<Pubkey>,
|
||||
use_lamports_unit: bool,
|
||||
) -> ProcessResult {
|
||||
let pubkey = if let Some(pubkey) = pubkey {
|
||||
*pubkey
|
||||
} else {
|
||||
config.pubkey()?
|
||||
};
|
||||
let balance = rpc_client.get_balance(&pubkey)?;
|
||||
Ok(build_balance_message(balance, use_lamports_unit, true))
|
||||
}
|
||||
|
||||
pub fn process_confirm(
|
||||
rpc_client: &RpcClient,
|
||||
config: &CliConfig,
|
||||
signature: &Signature,
|
||||
) -> ProcessResult {
|
||||
match rpc_client.get_signature_statuses_with_history(&[*signature]) {
|
||||
Ok(status) => {
|
||||
let cli_transaction = if let Some(transaction_status) = &status.value[0] {
|
||||
let mut transaction = None;
|
||||
let mut get_transaction_error = None;
|
||||
if config.verbose {
|
||||
match rpc_client.get_transaction_with_config(
|
||||
signature,
|
||||
RpcTransactionConfig {
|
||||
encoding: Some(UiTransactionEncoding::Base64),
|
||||
commitment: Some(CommitmentConfig::confirmed()),
|
||||
},
|
||||
) {
|
||||
Ok(confirmed_transaction) => {
|
||||
let decoded_transaction = confirmed_transaction
|
||||
.transaction
|
||||
.transaction
|
||||
.decode()
|
||||
.expect("Successful decode");
|
||||
let json_transaction = EncodedTransaction::encode(
|
||||
decoded_transaction.clone(),
|
||||
UiTransactionEncoding::Json,
|
||||
);
|
||||
|
||||
transaction = Some(CliTransaction {
|
||||
transaction: json_transaction,
|
||||
meta: confirmed_transaction.transaction.meta,
|
||||
block_time: confirmed_transaction.block_time,
|
||||
slot: Some(confirmed_transaction.slot),
|
||||
decoded_transaction,
|
||||
prefix: " ".to_string(),
|
||||
sigverify_status: vec![],
|
||||
});
|
||||
}
|
||||
Err(err) => {
|
||||
get_transaction_error = Some(format!("{:?}", err));
|
||||
}
|
||||
}
|
||||
}
|
||||
CliTransactionConfirmation {
|
||||
confirmation_status: Some(transaction_status.confirmation_status()),
|
||||
transaction,
|
||||
get_transaction_error,
|
||||
err: transaction_status.err.clone(),
|
||||
}
|
||||
} else {
|
||||
CliTransactionConfirmation {
|
||||
confirmation_status: None,
|
||||
transaction: None,
|
||||
get_transaction_error: None,
|
||||
err: None,
|
||||
}
|
||||
};
|
||||
Ok(config.output_format.formatted_string(&cli_transaction))
|
||||
}
|
||||
Err(err) => Err(CliError::RpcRequestError(format!("Unable to confirm: {}", err)).into()),
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::unnecessary_wraps)]
|
||||
pub fn process_decode_transaction(config: &CliConfig, transaction: &Transaction) -> ProcessResult {
|
||||
let sigverify_status = CliSignatureVerificationStatus::verify_transaction(transaction);
|
||||
let decode_transaction = CliTransaction {
|
||||
decoded_transaction: transaction.clone(),
|
||||
transaction: EncodedTransaction::encode(transaction.clone(), UiTransactionEncoding::Json),
|
||||
meta: None,
|
||||
block_time: None,
|
||||
slot: None,
|
||||
prefix: "".to_string(),
|
||||
sigverify_status,
|
||||
};
|
||||
Ok(config.output_format.formatted_string(&decode_transaction))
|
||||
}
|
||||
|
||||
pub fn process_create_address_with_seed(
|
||||
config: &CliConfig,
|
||||
from_pubkey: Option<&Pubkey>,
|
||||
seed: &str,
|
||||
program_id: &Pubkey,
|
||||
) -> ProcessResult {
|
||||
let from_pubkey = if let Some(pubkey) = from_pubkey {
|
||||
*pubkey
|
||||
} else {
|
||||
config.pubkey()?
|
||||
};
|
||||
let address = Pubkey::create_with_seed(&from_pubkey, seed, program_id)?;
|
||||
Ok(address.to_string())
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn process_transfer(
|
||||
rpc_client: &RpcClient,
|
||||
config: &CliConfig,
|
||||
amount: SpendAmount,
|
||||
to: &Pubkey,
|
||||
from: SignerIndex,
|
||||
sign_only: bool,
|
||||
dump_transaction_message: bool,
|
||||
allow_unfunded_recipient: bool,
|
||||
no_wait: bool,
|
||||
blockhash_query: &BlockhashQuery,
|
||||
nonce_account: Option<&Pubkey>,
|
||||
nonce_authority: SignerIndex,
|
||||
memo: Option<&String>,
|
||||
fee_payer: SignerIndex,
|
||||
derived_address_seed: Option<String>,
|
||||
derived_address_program_id: Option<&Pubkey>,
|
||||
) -> ProcessResult {
|
||||
let from = config.signers[from];
|
||||
let mut from_pubkey = from.pubkey();
|
||||
|
||||
let (recent_blockhash, fee_calculator) =
|
||||
blockhash_query.get_blockhash_and_fee_calculator(rpc_client, config.commitment)?;
|
||||
|
||||
if !sign_only && !allow_unfunded_recipient {
|
||||
let recipient_balance = rpc_client
|
||||
.get_balance_with_commitment(to, config.commitment)?
|
||||
.value;
|
||||
if recipient_balance == 0 {
|
||||
return Err(format!(
|
||||
"The recipient address ({}) is not funded. \
|
||||
Add `--allow-unfunded-recipient` to complete the transfer \
|
||||
",
|
||||
to
|
||||
)
|
||||
.into());
|
||||
}
|
||||
}
|
||||
|
||||
let nonce_authority = config.signers[nonce_authority];
|
||||
let fee_payer = config.signers[fee_payer];
|
||||
|
||||
let derived_parts = derived_address_seed.zip(derived_address_program_id);
|
||||
let with_seed = if let Some((seed, program_id)) = derived_parts {
|
||||
let base_pubkey = from_pubkey;
|
||||
from_pubkey = Pubkey::create_with_seed(&base_pubkey, &seed, program_id)?;
|
||||
Some((base_pubkey, seed, program_id, from_pubkey))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let build_message = |lamports| {
|
||||
let ixs = if let Some((base_pubkey, seed, program_id, from_pubkey)) = with_seed.as_ref() {
|
||||
vec![system_instruction::transfer_with_seed(
|
||||
from_pubkey,
|
||||
base_pubkey,
|
||||
seed.clone(),
|
||||
program_id,
|
||||
to,
|
||||
lamports,
|
||||
)]
|
||||
.with_memo(memo)
|
||||
} else {
|
||||
vec![system_instruction::transfer(&from_pubkey, to, lamports)].with_memo(memo)
|
||||
};
|
||||
|
||||
if let Some(nonce_account) = &nonce_account {
|
||||
Message::new_with_nonce(
|
||||
ixs,
|
||||
Some(&fee_payer.pubkey()),
|
||||
nonce_account,
|
||||
&nonce_authority.pubkey(),
|
||||
)
|
||||
} else {
|
||||
Message::new(&ixs, Some(&fee_payer.pubkey()))
|
||||
}
|
||||
};
|
||||
|
||||
let (message, _) = resolve_spend_tx_and_check_account_balances(
|
||||
rpc_client,
|
||||
sign_only,
|
||||
amount,
|
||||
&fee_calculator,
|
||||
&from_pubkey,
|
||||
&fee_payer.pubkey(),
|
||||
build_message,
|
||||
config.commitment,
|
||||
)?;
|
||||
let mut tx = Transaction::new_unsigned(message);
|
||||
|
||||
if sign_only {
|
||||
tx.try_partial_sign(&config.signers, recent_blockhash)?;
|
||||
return_signers_with_config(
|
||||
&tx,
|
||||
&config.output_format,
|
||||
&ReturnSignersConfig {
|
||||
dump_transaction_message,
|
||||
},
|
||||
)
|
||||
} else {
|
||||
if let Some(nonce_account) = &nonce_account {
|
||||
let nonce_account = nonce_utils::get_account_with_commitment(
|
||||
rpc_client,
|
||||
nonce_account,
|
||||
config.commitment,
|
||||
)?;
|
||||
check_nonce_account(&nonce_account, &nonce_authority.pubkey(), &recent_blockhash)?;
|
||||
}
|
||||
|
||||
tx.try_sign(&config.signers, recent_blockhash)?;
|
||||
let result = if no_wait {
|
||||
rpc_client.send_transaction(&tx)
|
||||
} else {
|
||||
rpc_client.send_and_confirm_transaction_with_spinner(&tx)
|
||||
};
|
||||
log_instruction_custom_error::<SystemError>(result, config)
|
||||
}
|
||||
}
|
@ -18,13 +18,15 @@ use solana_sdk::{
|
||||
signature::{keypair_from_seed, Keypair, Signer},
|
||||
system_program,
|
||||
};
|
||||
use solana_streamer::socket::SocketAddrSpace;
|
||||
|
||||
#[test]
|
||||
fn test_nonce() {
|
||||
let mint_keypair = Keypair::new();
|
||||
let mint_pubkey = mint_keypair.pubkey();
|
||||
let faucet_addr = run_local_faucet(mint_keypair, None);
|
||||
let test_validator = TestValidator::with_no_fees(mint_pubkey, Some(faucet_addr));
|
||||
let test_validator =
|
||||
TestValidator::with_no_fees(mint_pubkey, Some(faucet_addr), SocketAddrSpace::Unspecified);
|
||||
|
||||
full_battery_tests(test_validator, None, false);
|
||||
}
|
||||
@ -34,7 +36,8 @@ fn test_nonce_with_seed() {
|
||||
let mint_keypair = Keypair::new();
|
||||
let mint_pubkey = mint_keypair.pubkey();
|
||||
let faucet_addr = run_local_faucet(mint_keypair, None);
|
||||
let test_validator = TestValidator::with_no_fees(mint_pubkey, Some(faucet_addr));
|
||||
let test_validator =
|
||||
TestValidator::with_no_fees(mint_pubkey, Some(faucet_addr), SocketAddrSpace::Unspecified);
|
||||
|
||||
full_battery_tests(test_validator, Some(String::from("seed")), false);
|
||||
}
|
||||
@ -44,7 +47,8 @@ fn test_nonce_with_authority() {
|
||||
let mint_keypair = Keypair::new();
|
||||
let mint_pubkey = mint_keypair.pubkey();
|
||||
let faucet_addr = run_local_faucet(mint_keypair, None);
|
||||
let test_validator = TestValidator::with_no_fees(mint_pubkey, Some(faucet_addr));
|
||||
let test_validator =
|
||||
TestValidator::with_no_fees(mint_pubkey, Some(faucet_addr), SocketAddrSpace::Unspecified);
|
||||
|
||||
full_battery_tests(test_validator, None, true);
|
||||
}
|
||||
@ -216,7 +220,12 @@ fn test_create_account_with_seed() {
|
||||
let mint_keypair = Keypair::new();
|
||||
let mint_pubkey = mint_keypair.pubkey();
|
||||
let faucet_addr = run_local_faucet(mint_keypair, None);
|
||||
let test_validator = TestValidator::with_custom_fees(mint_pubkey, 1, Some(faucet_addr));
|
||||
let test_validator = TestValidator::with_custom_fees(
|
||||
mint_pubkey,
|
||||
1,
|
||||
Some(faucet_addr),
|
||||
SocketAddrSpace::Unspecified,
|
||||
);
|
||||
|
||||
let offline_nonce_authority_signer = keypair_from_seed(&[1u8; 32]).unwrap();
|
||||
let online_nonce_creator_signer = keypair_from_seed(&[2u8; 32]).unwrap();
|
||||
|
@ -15,6 +15,7 @@ use solana_sdk::{
|
||||
pubkey::Pubkey,
|
||||
signature::{Keypair, Signer},
|
||||
};
|
||||
use solana_streamer::socket::SocketAddrSpace;
|
||||
use std::{env, fs::File, io::Read, path::PathBuf, str::FromStr};
|
||||
|
||||
#[test]
|
||||
@ -30,7 +31,8 @@ fn test_cli_program_deploy_non_upgradeable() {
|
||||
let mint_keypair = Keypair::new();
|
||||
let mint_pubkey = mint_keypair.pubkey();
|
||||
let faucet_addr = run_local_faucet(mint_keypair, None);
|
||||
let test_validator = TestValidator::with_no_fees(mint_pubkey, Some(faucet_addr));
|
||||
let test_validator =
|
||||
TestValidator::with_no_fees(mint_pubkey, Some(faucet_addr), SocketAddrSpace::Unspecified);
|
||||
|
||||
let rpc_client =
|
||||
RpcClient::new_with_commitment(test_validator.rpc_url(), CommitmentConfig::processed());
|
||||
@ -68,7 +70,7 @@ fn test_cli_program_deploy_non_upgradeable() {
|
||||
.unwrap()
|
||||
.as_str()
|
||||
.unwrap();
|
||||
let program_id = Pubkey::from_str(&program_id_str).unwrap();
|
||||
let program_id = Pubkey::from_str(program_id_str).unwrap();
|
||||
let account0 = rpc_client.get_account(&program_id).unwrap();
|
||||
assert_eq!(account0.lamports, minimum_balance_for_rent_exemption);
|
||||
assert_eq!(account0.owner, bpf_loader::id());
|
||||
@ -146,7 +148,8 @@ fn test_cli_program_deploy_no_authority() {
|
||||
let mint_keypair = Keypair::new();
|
||||
let mint_pubkey = mint_keypair.pubkey();
|
||||
let faucet_addr = run_local_faucet(mint_keypair, None);
|
||||
let test_validator = TestValidator::with_no_fees(mint_pubkey, Some(faucet_addr));
|
||||
let test_validator =
|
||||
TestValidator::with_no_fees(mint_pubkey, Some(faucet_addr), SocketAddrSpace::Unspecified);
|
||||
|
||||
let rpc_client =
|
||||
RpcClient::new_with_commitment(test_validator.rpc_url(), CommitmentConfig::processed());
|
||||
@ -198,7 +201,7 @@ fn test_cli_program_deploy_no_authority() {
|
||||
.unwrap()
|
||||
.as_str()
|
||||
.unwrap();
|
||||
let program_id = Pubkey::from_str(&program_id_str).unwrap();
|
||||
let program_id = Pubkey::from_str(program_id_str).unwrap();
|
||||
|
||||
// Attempt to upgrade the program
|
||||
config.signers = vec![&keypair, &upgrade_authority];
|
||||
@ -229,7 +232,8 @@ fn test_cli_program_deploy_with_authority() {
|
||||
let mint_keypair = Keypair::new();
|
||||
let mint_pubkey = mint_keypair.pubkey();
|
||||
let faucet_addr = run_local_faucet(mint_keypair, None);
|
||||
let test_validator = TestValidator::with_no_fees(mint_pubkey, Some(faucet_addr));
|
||||
let test_validator =
|
||||
TestValidator::with_no_fees(mint_pubkey, Some(faucet_addr), SocketAddrSpace::Unspecified);
|
||||
|
||||
let rpc_client =
|
||||
RpcClient::new_with_commitment(test_validator.rpc_url(), CommitmentConfig::processed());
|
||||
@ -284,7 +288,7 @@ fn test_cli_program_deploy_with_authority() {
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
program_keypair.pubkey(),
|
||||
Pubkey::from_str(&program_pubkey_str).unwrap()
|
||||
Pubkey::from_str(program_pubkey_str).unwrap()
|
||||
);
|
||||
let program_account = rpc_client.get_account(&program_keypair.pubkey()).unwrap();
|
||||
assert_eq!(program_account.lamports, minimum_balance_for_program);
|
||||
@ -328,7 +332,7 @@ fn test_cli_program_deploy_with_authority() {
|
||||
.unwrap()
|
||||
.as_str()
|
||||
.unwrap();
|
||||
let program_pubkey = Pubkey::from_str(&program_pubkey_str).unwrap();
|
||||
let program_pubkey = Pubkey::from_str(program_pubkey_str).unwrap();
|
||||
let program_account = rpc_client.get_account(&program_pubkey).unwrap();
|
||||
assert_eq!(program_account.lamports, minimum_balance_for_program);
|
||||
assert_eq!(program_account.owner, bpf_loader_upgradeable::id());
|
||||
@ -397,7 +401,7 @@ fn test_cli_program_deploy_with_authority() {
|
||||
.as_str()
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
Pubkey::from_str(&new_upgrade_authority_str).unwrap(),
|
||||
Pubkey::from_str(new_upgrade_authority_str).unwrap(),
|
||||
new_upgrade_authority.pubkey()
|
||||
);
|
||||
|
||||
@ -438,6 +442,8 @@ fn test_cli_program_deploy_with_authority() {
|
||||
config.command = CliCommand::Program(ProgramCliCommand::Show {
|
||||
account_pubkey: Some(program_pubkey),
|
||||
authority_pubkey: keypair.pubkey(),
|
||||
get_programs: false,
|
||||
get_buffers: false,
|
||||
all: false,
|
||||
use_lamports_unit: false,
|
||||
});
|
||||
@ -452,7 +458,7 @@ fn test_cli_program_deploy_with_authority() {
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
new_upgrade_authority.pubkey(),
|
||||
Pubkey::from_str(&authority_pubkey_str).unwrap()
|
||||
Pubkey::from_str(authority_pubkey_str).unwrap()
|
||||
);
|
||||
|
||||
// Set no authority
|
||||
@ -510,7 +516,7 @@ fn test_cli_program_deploy_with_authority() {
|
||||
.unwrap()
|
||||
.as_str()
|
||||
.unwrap();
|
||||
let program_pubkey = Pubkey::from_str(&program_pubkey_str).unwrap();
|
||||
let program_pubkey = Pubkey::from_str(program_pubkey_str).unwrap();
|
||||
let (programdata_pubkey, _) =
|
||||
Pubkey::find_program_address(&[program_pubkey.as_ref()], &bpf_loader_upgradeable::id());
|
||||
let programdata_account = rpc_client.get_account(&programdata_pubkey).unwrap();
|
||||
@ -521,7 +527,7 @@ fn test_cli_program_deploy_with_authority() {
|
||||
{
|
||||
assert_eq!(upgrade_authority_address, None);
|
||||
} else {
|
||||
panic!("not a buffer account");
|
||||
panic!("not a ProgramData account");
|
||||
}
|
||||
|
||||
// Get buffer authority
|
||||
@ -529,6 +535,8 @@ fn test_cli_program_deploy_with_authority() {
|
||||
config.command = CliCommand::Program(ProgramCliCommand::Show {
|
||||
account_pubkey: Some(program_pubkey),
|
||||
authority_pubkey: keypair.pubkey(),
|
||||
get_programs: false,
|
||||
get_buffers: false,
|
||||
all: false,
|
||||
use_lamports_unit: false,
|
||||
});
|
||||
@ -544,6 +552,88 @@ fn test_cli_program_deploy_with_authority() {
|
||||
assert_eq!("none", authority_pubkey_str);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cli_program_close_program() {
|
||||
solana_logger::setup();
|
||||
|
||||
let mut pathbuf = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
||||
pathbuf.push("tests");
|
||||
pathbuf.push("fixtures");
|
||||
pathbuf.push("noop");
|
||||
pathbuf.set_extension("so");
|
||||
|
||||
let mint_keypair = Keypair::new();
|
||||
let mint_pubkey = mint_keypair.pubkey();
|
||||
let faucet_addr = run_local_faucet(mint_keypair, None);
|
||||
let test_validator =
|
||||
TestValidator::with_no_fees(mint_pubkey, Some(faucet_addr), SocketAddrSpace::Unspecified);
|
||||
|
||||
let rpc_client =
|
||||
RpcClient::new_with_commitment(test_validator.rpc_url(), CommitmentConfig::processed());
|
||||
|
||||
let mut file = File::open(pathbuf.to_str().unwrap()).unwrap();
|
||||
let mut program_data = Vec::new();
|
||||
file.read_to_end(&mut program_data).unwrap();
|
||||
let max_len = program_data.len();
|
||||
let minimum_balance_for_programdata = rpc_client
|
||||
.get_minimum_balance_for_rent_exemption(
|
||||
UpgradeableLoaderState::programdata_len(max_len).unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
let minimum_balance_for_program = rpc_client
|
||||
.get_minimum_balance_for_rent_exemption(UpgradeableLoaderState::program_len().unwrap())
|
||||
.unwrap();
|
||||
let upgrade_authority = Keypair::new();
|
||||
|
||||
let mut config = CliConfig::recent_for_tests();
|
||||
let keypair = Keypair::new();
|
||||
config.json_rpc_url = test_validator.rpc_url();
|
||||
config.signers = vec![&keypair];
|
||||
config.command = CliCommand::Airdrop {
|
||||
pubkey: None,
|
||||
lamports: 100 * minimum_balance_for_programdata + minimum_balance_for_program,
|
||||
};
|
||||
process_command(&config).unwrap();
|
||||
|
||||
// Deploy the upgradeable program
|
||||
let program_keypair = Keypair::new();
|
||||
config.signers = vec![&keypair, &upgrade_authority, &program_keypair];
|
||||
config.command = CliCommand::Program(ProgramCliCommand::Deploy {
|
||||
program_location: Some(pathbuf.to_str().unwrap().to_string()),
|
||||
program_signer_index: Some(2),
|
||||
program_pubkey: Some(program_keypair.pubkey()),
|
||||
buffer_signer_index: None,
|
||||
buffer_pubkey: None,
|
||||
allow_excessive_balance: false,
|
||||
upgrade_authority_signer_index: 1,
|
||||
is_final: false,
|
||||
max_len: Some(max_len),
|
||||
});
|
||||
config.output_format = OutputFormat::JsonCompact;
|
||||
process_command(&config).unwrap();
|
||||
|
||||
let (programdata_pubkey, _) = Pubkey::find_program_address(
|
||||
&[program_keypair.pubkey().as_ref()],
|
||||
&bpf_loader_upgradeable::id(),
|
||||
);
|
||||
|
||||
// Close program
|
||||
let close_account = rpc_client.get_account(&programdata_pubkey).unwrap();
|
||||
let programdata_lamports = close_account.lamports;
|
||||
let recipient_pubkey = Pubkey::new_unique();
|
||||
config.signers = vec![&keypair, &upgrade_authority];
|
||||
config.command = CliCommand::Program(ProgramCliCommand::Close {
|
||||
account_pubkey: Some(program_keypair.pubkey()),
|
||||
recipient_pubkey,
|
||||
authority_index: 1,
|
||||
use_lamports_unit: false,
|
||||
});
|
||||
process_command(&config).unwrap();
|
||||
rpc_client.get_account(&programdata_pubkey).unwrap_err();
|
||||
let recipient_account = rpc_client.get_account(&recipient_pubkey).unwrap();
|
||||
assert_eq!(programdata_lamports, recipient_account.lamports);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cli_program_write_buffer() {
|
||||
solana_logger::setup();
|
||||
@ -557,7 +647,8 @@ fn test_cli_program_write_buffer() {
|
||||
let mint_keypair = Keypair::new();
|
||||
let mint_pubkey = mint_keypair.pubkey();
|
||||
let faucet_addr = run_local_faucet(mint_keypair, None);
|
||||
let test_validator = TestValidator::with_no_fees(mint_pubkey, Some(faucet_addr));
|
||||
let test_validator =
|
||||
TestValidator::with_no_fees(mint_pubkey, Some(faucet_addr), SocketAddrSpace::Unspecified);
|
||||
|
||||
let rpc_client =
|
||||
RpcClient::new_with_commitment(test_validator.rpc_url(), CommitmentConfig::processed());
|
||||
@ -606,7 +697,7 @@ fn test_cli_program_write_buffer() {
|
||||
.unwrap()
|
||||
.as_str()
|
||||
.unwrap();
|
||||
let new_buffer_pubkey = Pubkey::from_str(&buffer_pubkey_str).unwrap();
|
||||
let new_buffer_pubkey = Pubkey::from_str(buffer_pubkey_str).unwrap();
|
||||
let buffer_account = rpc_client.get_account(&new_buffer_pubkey).unwrap();
|
||||
assert_eq!(buffer_account.lamports, minimum_balance_for_buffer_default);
|
||||
assert_eq!(buffer_account.owner, bpf_loader_upgradeable::id());
|
||||
@ -641,7 +732,7 @@ fn test_cli_program_write_buffer() {
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
buffer_keypair.pubkey(),
|
||||
Pubkey::from_str(&buffer_pubkey_str).unwrap()
|
||||
Pubkey::from_str(buffer_pubkey_str).unwrap()
|
||||
);
|
||||
let buffer_account = rpc_client.get_account(&buffer_keypair.pubkey()).unwrap();
|
||||
assert_eq!(buffer_account.lamports, minimum_balance_for_buffer);
|
||||
@ -661,6 +752,8 @@ fn test_cli_program_write_buffer() {
|
||||
config.command = CliCommand::Program(ProgramCliCommand::Show {
|
||||
account_pubkey: Some(buffer_keypair.pubkey()),
|
||||
authority_pubkey: keypair.pubkey(),
|
||||
get_programs: false,
|
||||
get_buffers: false,
|
||||
all: false,
|
||||
use_lamports_unit: false,
|
||||
});
|
||||
@ -675,7 +768,7 @@ fn test_cli_program_write_buffer() {
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
keypair.pubkey(),
|
||||
Pubkey::from_str(&authority_pubkey_str).unwrap()
|
||||
Pubkey::from_str(authority_pubkey_str).unwrap()
|
||||
);
|
||||
|
||||
// Specify buffer authority
|
||||
@ -700,7 +793,7 @@ fn test_cli_program_write_buffer() {
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
buffer_keypair.pubkey(),
|
||||
Pubkey::from_str(&buffer_pubkey_str).unwrap()
|
||||
Pubkey::from_str(buffer_pubkey_str).unwrap()
|
||||
);
|
||||
let buffer_account = rpc_client.get_account(&buffer_keypair.pubkey()).unwrap();
|
||||
assert_eq!(buffer_account.lamports, minimum_balance_for_buffer_default);
|
||||
@ -735,7 +828,7 @@ fn test_cli_program_write_buffer() {
|
||||
.unwrap()
|
||||
.as_str()
|
||||
.unwrap();
|
||||
let buffer_pubkey = Pubkey::from_str(&buffer_pubkey_str).unwrap();
|
||||
let buffer_pubkey = Pubkey::from_str(buffer_pubkey_str).unwrap();
|
||||
let buffer_account = rpc_client.get_account(&buffer_pubkey).unwrap();
|
||||
assert_eq!(buffer_account.lamports, minimum_balance_for_buffer_default);
|
||||
assert_eq!(buffer_account.owner, bpf_loader_upgradeable::id());
|
||||
@ -754,6 +847,8 @@ fn test_cli_program_write_buffer() {
|
||||
config.command = CliCommand::Program(ProgramCliCommand::Show {
|
||||
account_pubkey: Some(buffer_pubkey),
|
||||
authority_pubkey: keypair.pubkey(),
|
||||
get_programs: false,
|
||||
get_buffers: false,
|
||||
all: false,
|
||||
use_lamports_unit: false,
|
||||
});
|
||||
@ -768,7 +863,7 @@ fn test_cli_program_write_buffer() {
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
authority_keypair.pubkey(),
|
||||
Pubkey::from_str(&authority_pubkey_str).unwrap()
|
||||
Pubkey::from_str(authority_pubkey_str).unwrap()
|
||||
);
|
||||
|
||||
// Close buffer
|
||||
@ -806,7 +901,7 @@ fn test_cli_program_write_buffer() {
|
||||
.unwrap()
|
||||
.as_str()
|
||||
.unwrap();
|
||||
let new_buffer_pubkey = Pubkey::from_str(&buffer_pubkey_str).unwrap();
|
||||
let new_buffer_pubkey = Pubkey::from_str(buffer_pubkey_str).unwrap();
|
||||
|
||||
// Close buffers and deposit default keypair
|
||||
let pre_lamports = rpc_client.get_account(&keypair.pubkey()).unwrap().lamports;
|
||||
@ -839,7 +934,8 @@ fn test_cli_program_set_buffer_authority() {
|
||||
let mint_keypair = Keypair::new();
|
||||
let mint_pubkey = mint_keypair.pubkey();
|
||||
let faucet_addr = run_local_faucet(mint_keypair, None);
|
||||
let test_validator = TestValidator::with_no_fees(mint_pubkey, Some(faucet_addr));
|
||||
let test_validator =
|
||||
TestValidator::with_no_fees(mint_pubkey, Some(faucet_addr), SocketAddrSpace::Unspecified);
|
||||
|
||||
let rpc_client =
|
||||
RpcClient::new_with_commitment(test_validator.rpc_url(), CommitmentConfig::processed());
|
||||
@ -901,7 +997,7 @@ fn test_cli_program_set_buffer_authority() {
|
||||
.as_str()
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
Pubkey::from_str(&new_buffer_authority_str).unwrap(),
|
||||
Pubkey::from_str(new_buffer_authority_str).unwrap(),
|
||||
new_buffer_authority.pubkey()
|
||||
);
|
||||
let buffer_account = rpc_client.get_account(&buffer_keypair.pubkey()).unwrap();
|
||||
@ -928,7 +1024,7 @@ fn test_cli_program_set_buffer_authority() {
|
||||
.as_str()
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
Pubkey::from_str(&buffer_authority_str).unwrap(),
|
||||
Pubkey::from_str(buffer_authority_str).unwrap(),
|
||||
buffer_keypair.pubkey()
|
||||
);
|
||||
let buffer_account = rpc_client.get_account(&buffer_keypair.pubkey()).unwrap();
|
||||
@ -952,7 +1048,8 @@ fn test_cli_program_mismatch_buffer_authority() {
|
||||
let mint_keypair = Keypair::new();
|
||||
let mint_pubkey = mint_keypair.pubkey();
|
||||
let faucet_addr = run_local_faucet(mint_keypair, None);
|
||||
let test_validator = TestValidator::with_no_fees(mint_pubkey, Some(faucet_addr));
|
||||
let test_validator =
|
||||
TestValidator::with_no_fees(mint_pubkey, Some(faucet_addr), SocketAddrSpace::Unspecified);
|
||||
|
||||
let rpc_client =
|
||||
RpcClient::new_with_commitment(test_validator.rpc_url(), CommitmentConfig::processed());
|
||||
@ -1041,7 +1138,8 @@ fn test_cli_program_show() {
|
||||
let mint_keypair = Keypair::new();
|
||||
let mint_pubkey = mint_keypair.pubkey();
|
||||
let faucet_addr = run_local_faucet(mint_keypair, None);
|
||||
let test_validator = TestValidator::with_no_fees(mint_pubkey, Some(faucet_addr));
|
||||
let test_validator =
|
||||
TestValidator::with_no_fees(mint_pubkey, Some(faucet_addr), SocketAddrSpace::Unspecified);
|
||||
|
||||
let rpc_client =
|
||||
RpcClient::new_with_commitment(test_validator.rpc_url(), CommitmentConfig::processed());
|
||||
@ -1087,6 +1185,8 @@ fn test_cli_program_show() {
|
||||
config.command = CliCommand::Program(ProgramCliCommand::Show {
|
||||
account_pubkey: Some(buffer_keypair.pubkey()),
|
||||
authority_pubkey: keypair.pubkey(),
|
||||
get_programs: false,
|
||||
get_buffers: false,
|
||||
all: false,
|
||||
use_lamports_unit: false,
|
||||
});
|
||||
@ -1101,7 +1201,7 @@ fn test_cli_program_show() {
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
buffer_keypair.pubkey(),
|
||||
Pubkey::from_str(&address_str).unwrap()
|
||||
Pubkey::from_str(address_str).unwrap()
|
||||
);
|
||||
let authority_str = json
|
||||
.as_object()
|
||||
@ -1112,7 +1212,7 @@ fn test_cli_program_show() {
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
authority_keypair.pubkey(),
|
||||
Pubkey::from_str(&authority_str).unwrap()
|
||||
Pubkey::from_str(authority_str).unwrap()
|
||||
);
|
||||
let data_len = json
|
||||
.as_object()
|
||||
@ -1147,6 +1247,8 @@ fn test_cli_program_show() {
|
||||
config.command = CliCommand::Program(ProgramCliCommand::Show {
|
||||
account_pubkey: Some(program_keypair.pubkey()),
|
||||
authority_pubkey: keypair.pubkey(),
|
||||
get_programs: false,
|
||||
get_buffers: false,
|
||||
all: false,
|
||||
use_lamports_unit: false,
|
||||
});
|
||||
@ -1161,7 +1263,7 @@ fn test_cli_program_show() {
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
program_keypair.pubkey(),
|
||||
Pubkey::from_str(&address_str).unwrap()
|
||||
Pubkey::from_str(address_str).unwrap()
|
||||
);
|
||||
let programdata_address_str = json
|
||||
.as_object()
|
||||
@ -1176,7 +1278,7 @@ fn test_cli_program_show() {
|
||||
);
|
||||
assert_eq!(
|
||||
programdata_pubkey,
|
||||
Pubkey::from_str(&programdata_address_str).unwrap()
|
||||
Pubkey::from_str(programdata_address_str).unwrap()
|
||||
);
|
||||
let authority_str = json
|
||||
.as_object()
|
||||
@ -1187,7 +1289,7 @@ fn test_cli_program_show() {
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
authority_keypair.pubkey(),
|
||||
Pubkey::from_str(&authority_str).unwrap()
|
||||
Pubkey::from_str(authority_str).unwrap()
|
||||
);
|
||||
let deployed_slot = json
|
||||
.as_object()
|
||||
@ -1221,7 +1323,8 @@ fn test_cli_program_dump() {
|
||||
let mint_keypair = Keypair::new();
|
||||
let mint_pubkey = mint_keypair.pubkey();
|
||||
let faucet_addr = run_local_faucet(mint_keypair, None);
|
||||
let test_validator = TestValidator::with_no_fees(mint_pubkey, Some(faucet_addr));
|
||||
let test_validator =
|
||||
TestValidator::with_no_fees(mint_pubkey, Some(faucet_addr), SocketAddrSpace::Unspecified);
|
||||
|
||||
let rpc_client =
|
||||
RpcClient::new_with_commitment(test_validator.rpc_url(), CommitmentConfig::processed());
|
||||
|
@ -6,13 +6,15 @@ use solana_sdk::{
|
||||
commitment_config::CommitmentConfig,
|
||||
signature::{Keypair, Signer},
|
||||
};
|
||||
use solana_streamer::socket::SocketAddrSpace;
|
||||
|
||||
#[test]
|
||||
fn test_cli_request_airdrop() {
|
||||
let mint_keypair = Keypair::new();
|
||||
let mint_pubkey = mint_keypair.pubkey();
|
||||
let faucet_addr = run_local_faucet(mint_keypair, None);
|
||||
let test_validator = TestValidator::with_no_fees(mint_pubkey, Some(faucet_addr));
|
||||
let test_validator =
|
||||
TestValidator::with_no_fees(mint_pubkey, Some(faucet_addr), SocketAddrSpace::Unspecified);
|
||||
|
||||
let mut bob_config = CliConfig::recent_for_tests();
|
||||
bob_config.json_rpc_url = test_validator.rpc_url();
|
||||
|
@ -1,6 +1,7 @@
|
||||
use solana_cli::{
|
||||
cli::{process_command, request_and_confirm_airdrop, CliCommand, CliConfig},
|
||||
spend_utils::SpendAmount,
|
||||
stake::StakeAuthorizationIndexed,
|
||||
test_utils::{check_ready, check_recent_balance},
|
||||
};
|
||||
use solana_cli_output::{parse_sign_only_reply_string, OutputFormat};
|
||||
@ -17,18 +18,22 @@ use solana_sdk::{
|
||||
nonce::State as NonceState,
|
||||
pubkey::Pubkey,
|
||||
signature::{keypair_from_seed, Keypair, Signer},
|
||||
stake::{
|
||||
self,
|
||||
instruction::LockupArgs,
|
||||
state::{Lockup, StakeAuthorize, StakeState},
|
||||
},
|
||||
};
|
||||
use solana_stake_program::{
|
||||
stake_instruction::LockupArgs,
|
||||
stake_state::{Lockup, StakeAuthorize, StakeState},
|
||||
};
|
||||
use solana_streamer::socket::SocketAddrSpace;
|
||||
|
||||
#[test]
|
||||
fn test_stake_delegation_force() {
|
||||
let mint_keypair = Keypair::new();
|
||||
let mint_pubkey = mint_keypair.pubkey();
|
||||
let authorized_withdrawer = Keypair::new().pubkey();
|
||||
let faucet_addr = run_local_faucet(mint_keypair, None);
|
||||
let test_validator = TestValidator::with_no_fees(mint_pubkey, Some(faucet_addr));
|
||||
let test_validator =
|
||||
TestValidator::with_no_fees(mint_pubkey, Some(faucet_addr), SocketAddrSpace::Unspecified);
|
||||
|
||||
let rpc_client =
|
||||
RpcClient::new_with_commitment(test_validator.rpc_url(), CommitmentConfig::processed());
|
||||
@ -49,7 +54,7 @@ fn test_stake_delegation_force() {
|
||||
seed: None,
|
||||
identity_account: 0,
|
||||
authorized_voter: None,
|
||||
authorized_withdrawer: None,
|
||||
authorized_withdrawer,
|
||||
commission: 0,
|
||||
memo: None,
|
||||
};
|
||||
@ -63,6 +68,7 @@ fn test_stake_delegation_force() {
|
||||
seed: None,
|
||||
staker: None,
|
||||
withdrawer: None,
|
||||
withdrawer_signer: None,
|
||||
lockup: Lockup::default(),
|
||||
amount: SpendAmount::Some(50_000),
|
||||
sign_only: false,
|
||||
@ -117,7 +123,8 @@ fn test_seed_stake_delegation_and_deactivation() {
|
||||
let mint_keypair = Keypair::new();
|
||||
let mint_pubkey = mint_keypair.pubkey();
|
||||
let faucet_addr = run_local_faucet(mint_keypair, None);
|
||||
let test_validator = TestValidator::with_no_fees(mint_pubkey, Some(faucet_addr));
|
||||
let test_validator =
|
||||
TestValidator::with_no_fees(mint_pubkey, Some(faucet_addr), SocketAddrSpace::Unspecified);
|
||||
|
||||
let rpc_client =
|
||||
RpcClient::new_with_commitment(test_validator.rpc_url(), CommitmentConfig::processed());
|
||||
@ -139,7 +146,7 @@ fn test_seed_stake_delegation_and_deactivation() {
|
||||
let stake_address = Pubkey::create_with_seed(
|
||||
&config_validator.signers[0].pubkey(),
|
||||
"hi there",
|
||||
&solana_stake_program::id(),
|
||||
&stake::program::id(),
|
||||
)
|
||||
.expect("bad seed");
|
||||
|
||||
@ -150,6 +157,7 @@ fn test_seed_stake_delegation_and_deactivation() {
|
||||
seed: Some("hi there".to_string()),
|
||||
staker: None,
|
||||
withdrawer: None,
|
||||
withdrawer_signer: None,
|
||||
lockup: Lockup::default(),
|
||||
amount: SpendAmount::Some(50_000),
|
||||
sign_only: false,
|
||||
@ -202,7 +210,8 @@ fn test_stake_delegation_and_deactivation() {
|
||||
let mint_keypair = Keypair::new();
|
||||
let mint_pubkey = mint_keypair.pubkey();
|
||||
let faucet_addr = run_local_faucet(mint_keypair, None);
|
||||
let test_validator = TestValidator::with_no_fees(mint_pubkey, Some(faucet_addr));
|
||||
let test_validator =
|
||||
TestValidator::with_no_fees(mint_pubkey, Some(faucet_addr), SocketAddrSpace::Unspecified);
|
||||
|
||||
let rpc_client =
|
||||
RpcClient::new_with_commitment(test_validator.rpc_url(), CommitmentConfig::processed());
|
||||
@ -230,6 +239,7 @@ fn test_stake_delegation_and_deactivation() {
|
||||
seed: None,
|
||||
staker: None,
|
||||
withdrawer: None,
|
||||
withdrawer_signer: None,
|
||||
lockup: Lockup::default(),
|
||||
amount: SpendAmount::Some(50_000),
|
||||
sign_only: false,
|
||||
@ -283,7 +293,8 @@ fn test_offline_stake_delegation_and_deactivation() {
|
||||
let mint_keypair = Keypair::new();
|
||||
let mint_pubkey = mint_keypair.pubkey();
|
||||
let faucet_addr = run_local_faucet(mint_keypair, None);
|
||||
let test_validator = TestValidator::with_no_fees(mint_pubkey, Some(faucet_addr));
|
||||
let test_validator =
|
||||
TestValidator::with_no_fees(mint_pubkey, Some(faucet_addr), SocketAddrSpace::Unspecified);
|
||||
|
||||
let rpc_client =
|
||||
RpcClient::new_with_commitment(test_validator.rpc_url(), CommitmentConfig::processed());
|
||||
@ -331,6 +342,7 @@ fn test_offline_stake_delegation_and_deactivation() {
|
||||
seed: None,
|
||||
staker: Some(config_offline.signers[0].pubkey()),
|
||||
withdrawer: None,
|
||||
withdrawer_signer: None,
|
||||
lockup: Lockup::default(),
|
||||
amount: SpendAmount::Some(50_000),
|
||||
sign_only: false,
|
||||
@ -425,7 +437,8 @@ fn test_nonced_stake_delegation_and_deactivation() {
|
||||
let mint_keypair = Keypair::new();
|
||||
let mint_pubkey = mint_keypair.pubkey();
|
||||
let faucet_addr = run_local_faucet(mint_keypair, None);
|
||||
let test_validator = TestValidator::with_no_fees(mint_pubkey, Some(faucet_addr));
|
||||
let test_validator =
|
||||
TestValidator::with_no_fees(mint_pubkey, Some(faucet_addr), SocketAddrSpace::Unspecified);
|
||||
|
||||
let rpc_client =
|
||||
RpcClient::new_with_commitment(test_validator.rpc_url(), CommitmentConfig::processed());
|
||||
@ -450,6 +463,7 @@ fn test_nonced_stake_delegation_and_deactivation() {
|
||||
seed: None,
|
||||
staker: None,
|
||||
withdrawer: None,
|
||||
withdrawer_signer: None,
|
||||
lockup: Lockup::default(),
|
||||
amount: SpendAmount::Some(50_000),
|
||||
sign_only: false,
|
||||
@ -541,7 +555,8 @@ fn test_stake_authorize() {
|
||||
let mint_keypair = Keypair::new();
|
||||
let mint_pubkey = mint_keypair.pubkey();
|
||||
let faucet_addr = run_local_faucet(mint_keypair, None);
|
||||
let test_validator = TestValidator::with_no_fees(mint_pubkey, Some(faucet_addr));
|
||||
let test_validator =
|
||||
TestValidator::with_no_fees(mint_pubkey, Some(faucet_addr), SocketAddrSpace::Unspecified);
|
||||
|
||||
let rpc_client =
|
||||
RpcClient::new_with_commitment(test_validator.rpc_url(), CommitmentConfig::processed());
|
||||
@ -580,6 +595,7 @@ fn test_stake_authorize() {
|
||||
seed: None,
|
||||
staker: None,
|
||||
withdrawer: None,
|
||||
withdrawer_signer: None,
|
||||
lockup: Lockup::default(),
|
||||
amount: SpendAmount::Some(50_000),
|
||||
sign_only: false,
|
||||
@ -599,7 +615,12 @@ fn test_stake_authorize() {
|
||||
config.signers.pop();
|
||||
config.command = CliCommand::StakeAuthorize {
|
||||
stake_account_pubkey,
|
||||
new_authorizations: vec![(StakeAuthorize::Staker, online_authority_pubkey, 0)],
|
||||
new_authorizations: vec![StakeAuthorizationIndexed {
|
||||
authorization_type: StakeAuthorize::Staker,
|
||||
new_authority_pubkey: online_authority_pubkey,
|
||||
authority: 0,
|
||||
new_authority_signer: None,
|
||||
}],
|
||||
sign_only: false,
|
||||
dump_transaction_message: false,
|
||||
blockhash_query: BlockhashQuery::default(),
|
||||
@ -628,8 +649,18 @@ fn test_stake_authorize() {
|
||||
config.command = CliCommand::StakeAuthorize {
|
||||
stake_account_pubkey,
|
||||
new_authorizations: vec![
|
||||
(StakeAuthorize::Staker, online_authority2_pubkey, 1),
|
||||
(StakeAuthorize::Withdrawer, withdraw_authority_pubkey, 0),
|
||||
StakeAuthorizationIndexed {
|
||||
authorization_type: StakeAuthorize::Staker,
|
||||
new_authority_pubkey: online_authority2_pubkey,
|
||||
authority: 1,
|
||||
new_authority_signer: None,
|
||||
},
|
||||
StakeAuthorizationIndexed {
|
||||
authorization_type: StakeAuthorize::Withdrawer,
|
||||
new_authority_pubkey: withdraw_authority_pubkey,
|
||||
authority: 0,
|
||||
new_authority_signer: None,
|
||||
},
|
||||
],
|
||||
sign_only: false,
|
||||
dump_transaction_message: false,
|
||||
@ -656,7 +687,12 @@ fn test_stake_authorize() {
|
||||
config.signers.push(&online_authority2);
|
||||
config.command = CliCommand::StakeAuthorize {
|
||||
stake_account_pubkey,
|
||||
new_authorizations: vec![(StakeAuthorize::Staker, offline_authority_pubkey, 1)],
|
||||
new_authorizations: vec![StakeAuthorizationIndexed {
|
||||
authorization_type: StakeAuthorize::Staker,
|
||||
new_authority_pubkey: offline_authority_pubkey,
|
||||
authority: 1,
|
||||
new_authority_signer: None,
|
||||
}],
|
||||
sign_only: false,
|
||||
dump_transaction_message: false,
|
||||
blockhash_query: BlockhashQuery::default(),
|
||||
@ -682,7 +718,12 @@ fn test_stake_authorize() {
|
||||
let (blockhash, _) = rpc_client.get_recent_blockhash().unwrap();
|
||||
config_offline.command = CliCommand::StakeAuthorize {
|
||||
stake_account_pubkey,
|
||||
new_authorizations: vec![(StakeAuthorize::Staker, nonced_authority_pubkey, 0)],
|
||||
new_authorizations: vec![StakeAuthorizationIndexed {
|
||||
authorization_type: StakeAuthorize::Staker,
|
||||
new_authority_pubkey: nonced_authority_pubkey,
|
||||
authority: 0,
|
||||
new_authority_signer: None,
|
||||
}],
|
||||
sign_only: true,
|
||||
dump_transaction_message: false,
|
||||
blockhash_query: BlockhashQuery::None(blockhash),
|
||||
@ -701,7 +742,12 @@ fn test_stake_authorize() {
|
||||
config.signers = vec![&offline_presigner];
|
||||
config.command = CliCommand::StakeAuthorize {
|
||||
stake_account_pubkey,
|
||||
new_authorizations: vec![(StakeAuthorize::Staker, nonced_authority_pubkey, 0)],
|
||||
new_authorizations: vec![StakeAuthorizationIndexed {
|
||||
authorization_type: StakeAuthorize::Staker,
|
||||
new_authority_pubkey: nonced_authority_pubkey,
|
||||
authority: 0,
|
||||
new_authority_signer: None,
|
||||
}],
|
||||
sign_only: false,
|
||||
dump_transaction_message: false,
|
||||
blockhash_query: BlockhashQuery::FeeCalculator(blockhash_query::Source::Cluster, blockhash),
|
||||
@ -752,7 +798,12 @@ fn test_stake_authorize() {
|
||||
config_offline.signers.push(&nonced_authority);
|
||||
config_offline.command = CliCommand::StakeAuthorize {
|
||||
stake_account_pubkey,
|
||||
new_authorizations: vec![(StakeAuthorize::Staker, online_authority_pubkey, 1)],
|
||||
new_authorizations: vec![StakeAuthorizationIndexed {
|
||||
authorization_type: StakeAuthorize::Staker,
|
||||
new_authority_pubkey: online_authority_pubkey,
|
||||
authority: 1,
|
||||
new_authority_signer: None,
|
||||
}],
|
||||
sign_only: true,
|
||||
dump_transaction_message: false,
|
||||
blockhash_query: BlockhashQuery::None(nonce_hash),
|
||||
@ -772,7 +823,12 @@ fn test_stake_authorize() {
|
||||
config.signers = vec![&offline_presigner, &nonced_authority_presigner];
|
||||
config.command = CliCommand::StakeAuthorize {
|
||||
stake_account_pubkey,
|
||||
new_authorizations: vec![(StakeAuthorize::Staker, online_authority_pubkey, 1)],
|
||||
new_authorizations: vec![StakeAuthorizationIndexed {
|
||||
authorization_type: StakeAuthorize::Staker,
|
||||
new_authority_pubkey: online_authority_pubkey,
|
||||
authority: 1,
|
||||
new_authority_signer: None,
|
||||
}],
|
||||
sign_only: false,
|
||||
dump_transaction_message: false,
|
||||
blockhash_query: BlockhashQuery::FeeCalculator(
|
||||
@ -814,7 +870,12 @@ fn test_stake_authorize_with_fee_payer() {
|
||||
let mint_keypair = Keypair::new();
|
||||
let mint_pubkey = mint_keypair.pubkey();
|
||||
let faucet_addr = run_local_faucet(mint_keypair, None);
|
||||
let test_validator = TestValidator::with_custom_fees(mint_pubkey, SIG_FEE, Some(faucet_addr));
|
||||
let test_validator = TestValidator::with_custom_fees(
|
||||
mint_pubkey,
|
||||
SIG_FEE,
|
||||
Some(faucet_addr),
|
||||
SocketAddrSpace::Unspecified,
|
||||
);
|
||||
|
||||
let rpc_client =
|
||||
RpcClient::new_with_commitment(test_validator.rpc_url(), CommitmentConfig::processed());
|
||||
@ -860,6 +921,7 @@ fn test_stake_authorize_with_fee_payer() {
|
||||
seed: None,
|
||||
staker: None,
|
||||
withdrawer: None,
|
||||
withdrawer_signer: None,
|
||||
lockup: Lockup::default(),
|
||||
amount: SpendAmount::Some(50_000),
|
||||
sign_only: false,
|
||||
@ -879,7 +941,12 @@ fn test_stake_authorize_with_fee_payer() {
|
||||
config.signers = vec![&default_signer, &payer_keypair];
|
||||
config.command = CliCommand::StakeAuthorize {
|
||||
stake_account_pubkey,
|
||||
new_authorizations: vec![(StakeAuthorize::Staker, offline_pubkey, 0)],
|
||||
new_authorizations: vec![StakeAuthorizationIndexed {
|
||||
authorization_type: StakeAuthorize::Staker,
|
||||
new_authority_pubkey: offline_pubkey,
|
||||
authority: 0,
|
||||
new_authority_signer: None,
|
||||
}],
|
||||
sign_only: false,
|
||||
dump_transaction_message: false,
|
||||
blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
|
||||
@ -901,7 +968,12 @@ fn test_stake_authorize_with_fee_payer() {
|
||||
let (blockhash, _) = rpc_client.get_recent_blockhash().unwrap();
|
||||
config_offline.command = CliCommand::StakeAuthorize {
|
||||
stake_account_pubkey,
|
||||
new_authorizations: vec![(StakeAuthorize::Staker, payer_pubkey, 0)],
|
||||
new_authorizations: vec![StakeAuthorizationIndexed {
|
||||
authorization_type: StakeAuthorize::Staker,
|
||||
new_authority_pubkey: payer_pubkey,
|
||||
authority: 0,
|
||||
new_authority_signer: None,
|
||||
}],
|
||||
sign_only: true,
|
||||
dump_transaction_message: false,
|
||||
blockhash_query: BlockhashQuery::None(blockhash),
|
||||
@ -920,7 +992,12 @@ fn test_stake_authorize_with_fee_payer() {
|
||||
config.signers = vec![&offline_presigner];
|
||||
config.command = CliCommand::StakeAuthorize {
|
||||
stake_account_pubkey,
|
||||
new_authorizations: vec![(StakeAuthorize::Staker, payer_pubkey, 0)],
|
||||
new_authorizations: vec![StakeAuthorizationIndexed {
|
||||
authorization_type: StakeAuthorize::Staker,
|
||||
new_authority_pubkey: payer_pubkey,
|
||||
authority: 0,
|
||||
new_authority_signer: None,
|
||||
}],
|
||||
sign_only: false,
|
||||
dump_transaction_message: false,
|
||||
blockhash_query: BlockhashQuery::FeeCalculator(blockhash_query::Source::Cluster, blockhash),
|
||||
@ -946,7 +1023,12 @@ fn test_stake_split() {
|
||||
let mint_keypair = Keypair::new();
|
||||
let mint_pubkey = mint_keypair.pubkey();
|
||||
let faucet_addr = run_local_faucet(mint_keypair, None);
|
||||
let test_validator = TestValidator::with_custom_fees(mint_pubkey, 1, Some(faucet_addr));
|
||||
let test_validator = TestValidator::with_custom_fees(
|
||||
mint_pubkey,
|
||||
1,
|
||||
Some(faucet_addr),
|
||||
SocketAddrSpace::Unspecified,
|
||||
);
|
||||
|
||||
let rpc_client =
|
||||
RpcClient::new_with_commitment(test_validator.rpc_url(), CommitmentConfig::processed());
|
||||
@ -984,6 +1066,7 @@ fn test_stake_split() {
|
||||
seed: None,
|
||||
staker: Some(offline_pubkey),
|
||||
withdrawer: Some(offline_pubkey),
|
||||
withdrawer_signer: None,
|
||||
lockup: Lockup::default(),
|
||||
amount: SpendAmount::Some(10 * minimum_stake_balance),
|
||||
sign_only: false,
|
||||
@ -1089,7 +1172,12 @@ fn test_stake_set_lockup() {
|
||||
let mint_keypair = Keypair::new();
|
||||
let mint_pubkey = mint_keypair.pubkey();
|
||||
let faucet_addr = run_local_faucet(mint_keypair, None);
|
||||
let test_validator = TestValidator::with_custom_fees(mint_pubkey, 1, Some(faucet_addr));
|
||||
let test_validator = TestValidator::with_custom_fees(
|
||||
mint_pubkey,
|
||||
1,
|
||||
Some(faucet_addr),
|
||||
SocketAddrSpace::Unspecified,
|
||||
);
|
||||
|
||||
let rpc_client =
|
||||
RpcClient::new_with_commitment(test_validator.rpc_url(), CommitmentConfig::processed());
|
||||
@ -1134,6 +1222,7 @@ fn test_stake_set_lockup() {
|
||||
seed: None,
|
||||
staker: Some(offline_pubkey),
|
||||
withdrawer: Some(config.signers[0].pubkey()),
|
||||
withdrawer_signer: None,
|
||||
lockup,
|
||||
amount: SpendAmount::Some(10 * minimum_stake_balance),
|
||||
sign_only: false,
|
||||
@ -1162,6 +1251,7 @@ fn test_stake_set_lockup() {
|
||||
config.command = CliCommand::StakeSetLockup {
|
||||
stake_account_pubkey,
|
||||
lockup,
|
||||
new_custodian_signer: None,
|
||||
custodian: 0,
|
||||
sign_only: false,
|
||||
dump_transaction_message: false,
|
||||
@ -1197,6 +1287,7 @@ fn test_stake_set_lockup() {
|
||||
config.command = CliCommand::StakeSetLockup {
|
||||
stake_account_pubkey,
|
||||
lockup,
|
||||
new_custodian_signer: None,
|
||||
custodian: 0,
|
||||
sign_only: false,
|
||||
dump_transaction_message: false,
|
||||
@ -1217,6 +1308,7 @@ fn test_stake_set_lockup() {
|
||||
config.command = CliCommand::StakeSetLockup {
|
||||
stake_account_pubkey,
|
||||
lockup,
|
||||
new_custodian_signer: None,
|
||||
custodian: 1,
|
||||
sign_only: false,
|
||||
dump_transaction_message: false,
|
||||
@ -1249,6 +1341,7 @@ fn test_stake_set_lockup() {
|
||||
config.command = CliCommand::StakeSetLockup {
|
||||
stake_account_pubkey,
|
||||
lockup,
|
||||
new_custodian_signer: None,
|
||||
custodian: 1,
|
||||
sign_only: false,
|
||||
dump_transaction_message: false,
|
||||
@ -1296,6 +1389,7 @@ fn test_stake_set_lockup() {
|
||||
config_offline.command = CliCommand::StakeSetLockup {
|
||||
stake_account_pubkey,
|
||||
lockup,
|
||||
new_custodian_signer: None,
|
||||
custodian: 0,
|
||||
sign_only: true,
|
||||
dump_transaction_message: false,
|
||||
@ -1314,6 +1408,7 @@ fn test_stake_set_lockup() {
|
||||
config.command = CliCommand::StakeSetLockup {
|
||||
stake_account_pubkey,
|
||||
lockup,
|
||||
new_custodian_signer: None,
|
||||
custodian: 0,
|
||||
sign_only: false,
|
||||
dump_transaction_message: false,
|
||||
@ -1348,7 +1443,8 @@ fn test_offline_nonced_create_stake_account_and_withdraw() {
|
||||
let mint_keypair = Keypair::new();
|
||||
let mint_pubkey = mint_keypair.pubkey();
|
||||
let faucet_addr = run_local_faucet(mint_keypair, None);
|
||||
let test_validator = TestValidator::with_no_fees(mint_pubkey, Some(faucet_addr));
|
||||
let test_validator =
|
||||
TestValidator::with_no_fees(mint_pubkey, Some(faucet_addr), SocketAddrSpace::Unspecified);
|
||||
|
||||
let rpc_client =
|
||||
RpcClient::new_with_commitment(test_validator.rpc_url(), CommitmentConfig::processed());
|
||||
@ -1408,6 +1504,7 @@ fn test_offline_nonced_create_stake_account_and_withdraw() {
|
||||
seed: None,
|
||||
staker: None,
|
||||
withdrawer: None,
|
||||
withdrawer_signer: None,
|
||||
lockup: Lockup::default(),
|
||||
amount: SpendAmount::Some(50_000),
|
||||
sign_only: true,
|
||||
@ -1431,6 +1528,7 @@ fn test_offline_nonced_create_stake_account_and_withdraw() {
|
||||
seed: None,
|
||||
staker: Some(offline_pubkey),
|
||||
withdrawer: None,
|
||||
withdrawer_signer: None,
|
||||
lockup: Lockup::default(),
|
||||
amount: SpendAmount::Some(50_000),
|
||||
sign_only: false,
|
||||
@ -1520,6 +1618,7 @@ fn test_offline_nonced_create_stake_account_and_withdraw() {
|
||||
seed: Some(seed.to_string()),
|
||||
staker: None,
|
||||
withdrawer: None,
|
||||
withdrawer_signer: None,
|
||||
lockup: Lockup::default(),
|
||||
amount: SpendAmount::Some(50_000),
|
||||
sign_only: true,
|
||||
@ -1541,6 +1640,7 @@ fn test_offline_nonced_create_stake_account_and_withdraw() {
|
||||
seed: Some(seed.to_string()),
|
||||
staker: Some(offline_pubkey),
|
||||
withdrawer: Some(offline_pubkey),
|
||||
withdrawer_signer: None,
|
||||
lockup: Lockup::default(),
|
||||
amount: SpendAmount::Some(50_000),
|
||||
sign_only: false,
|
||||
@ -1557,6 +1657,232 @@ fn test_offline_nonced_create_stake_account_and_withdraw() {
|
||||
};
|
||||
process_command(&config).unwrap();
|
||||
let seed_address =
|
||||
Pubkey::create_with_seed(&stake_pubkey, seed, &solana_stake_program::id()).unwrap();
|
||||
Pubkey::create_with_seed(&stake_pubkey, seed, &stake::program::id()).unwrap();
|
||||
check_recent_balance(50_000, &rpc_client, &seed_address);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_stake_checked_instructions() {
|
||||
solana_logger::setup();
|
||||
|
||||
let mint_keypair = Keypair::new();
|
||||
let mint_pubkey = mint_keypair.pubkey();
|
||||
let faucet_addr = run_local_faucet(mint_keypair, None);
|
||||
let test_validator =
|
||||
TestValidator::with_no_fees(mint_pubkey, Some(faucet_addr), SocketAddrSpace::Unspecified);
|
||||
|
||||
let rpc_client =
|
||||
RpcClient::new_with_commitment(test_validator.rpc_url(), CommitmentConfig::processed());
|
||||
let default_signer = Keypair::new();
|
||||
|
||||
let mut config = CliConfig::recent_for_tests();
|
||||
config.json_rpc_url = test_validator.rpc_url();
|
||||
config.signers = vec![&default_signer];
|
||||
|
||||
request_and_confirm_airdrop(&rpc_client, &config, &config.signers[0].pubkey(), 100_000)
|
||||
.unwrap();
|
||||
|
||||
// Create stake account with withdrawer
|
||||
let stake_keypair = Keypair::new();
|
||||
let stake_account_pubkey = stake_keypair.pubkey();
|
||||
let withdrawer_keypair = Keypair::new();
|
||||
let withdrawer_pubkey = withdrawer_keypair.pubkey();
|
||||
config.signers.push(&stake_keypair);
|
||||
config.command = CliCommand::CreateStakeAccount {
|
||||
stake_account: 1,
|
||||
seed: None,
|
||||
staker: None,
|
||||
withdrawer: Some(withdrawer_pubkey),
|
||||
withdrawer_signer: Some(1),
|
||||
lockup: Lockup::default(),
|
||||
amount: SpendAmount::Some(50_000),
|
||||
sign_only: false,
|
||||
dump_transaction_message: false,
|
||||
blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
|
||||
nonce_account: None,
|
||||
nonce_authority: 0,
|
||||
memo: None,
|
||||
fee_payer: 0,
|
||||
from: 0,
|
||||
};
|
||||
process_command(&config).unwrap_err(); // unsigned authority should fail
|
||||
|
||||
config.signers = vec![&default_signer, &stake_keypair, &withdrawer_keypair];
|
||||
config.command = CliCommand::CreateStakeAccount {
|
||||
stake_account: 1,
|
||||
seed: None,
|
||||
staker: None,
|
||||
withdrawer: Some(withdrawer_pubkey),
|
||||
withdrawer_signer: Some(1),
|
||||
lockup: Lockup::default(),
|
||||
amount: SpendAmount::Some(50_000),
|
||||
sign_only: false,
|
||||
dump_transaction_message: false,
|
||||
blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
|
||||
nonce_account: None,
|
||||
nonce_authority: 0,
|
||||
memo: None,
|
||||
fee_payer: 0,
|
||||
from: 0,
|
||||
};
|
||||
process_command(&config).unwrap();
|
||||
|
||||
// Re-authorize account, checking new authority
|
||||
let staker_keypair = Keypair::new();
|
||||
let staker_pubkey = staker_keypair.pubkey();
|
||||
config.signers = vec![&default_signer];
|
||||
config.command = CliCommand::StakeAuthorize {
|
||||
stake_account_pubkey,
|
||||
new_authorizations: vec![StakeAuthorizationIndexed {
|
||||
authorization_type: StakeAuthorize::Staker,
|
||||
new_authority_pubkey: staker_pubkey,
|
||||
authority: 0,
|
||||
new_authority_signer: Some(0),
|
||||
}],
|
||||
sign_only: false,
|
||||
dump_transaction_message: false,
|
||||
blockhash_query: BlockhashQuery::default(),
|
||||
nonce_account: None,
|
||||
nonce_authority: 0,
|
||||
memo: None,
|
||||
fee_payer: 0,
|
||||
custodian: None,
|
||||
no_wait: false,
|
||||
};
|
||||
process_command(&config).unwrap_err(); // unsigned authority should fail
|
||||
|
||||
config.signers = vec![&default_signer, &staker_keypair];
|
||||
config.command = CliCommand::StakeAuthorize {
|
||||
stake_account_pubkey,
|
||||
new_authorizations: vec![StakeAuthorizationIndexed {
|
||||
authorization_type: StakeAuthorize::Staker,
|
||||
new_authority_pubkey: staker_pubkey,
|
||||
authority: 0,
|
||||
new_authority_signer: Some(1),
|
||||
}],
|
||||
sign_only: false,
|
||||
dump_transaction_message: false,
|
||||
blockhash_query: BlockhashQuery::default(),
|
||||
nonce_account: None,
|
||||
nonce_authority: 0,
|
||||
memo: None,
|
||||
fee_payer: 0,
|
||||
custodian: None,
|
||||
no_wait: false,
|
||||
};
|
||||
process_command(&config).unwrap();
|
||||
let stake_account = rpc_client.get_account(&stake_account_pubkey).unwrap();
|
||||
let stake_state: StakeState = stake_account.state().unwrap();
|
||||
let current_authority = match stake_state {
|
||||
StakeState::Initialized(meta) => meta.authorized.staker,
|
||||
_ => panic!("Unexpected stake state!"),
|
||||
};
|
||||
assert_eq!(current_authority, staker_pubkey);
|
||||
|
||||
let new_withdrawer_keypair = Keypair::new();
|
||||
let new_withdrawer_pubkey = new_withdrawer_keypair.pubkey();
|
||||
config.signers = vec![&default_signer, &withdrawer_keypair];
|
||||
config.command = CliCommand::StakeAuthorize {
|
||||
stake_account_pubkey,
|
||||
new_authorizations: vec![StakeAuthorizationIndexed {
|
||||
authorization_type: StakeAuthorize::Withdrawer,
|
||||
new_authority_pubkey: new_withdrawer_pubkey,
|
||||
authority: 1,
|
||||
new_authority_signer: Some(1),
|
||||
}],
|
||||
sign_only: false,
|
||||
dump_transaction_message: false,
|
||||
blockhash_query: BlockhashQuery::default(),
|
||||
nonce_account: None,
|
||||
nonce_authority: 0,
|
||||
memo: None,
|
||||
fee_payer: 0,
|
||||
custodian: None,
|
||||
no_wait: false,
|
||||
};
|
||||
process_command(&config).unwrap_err(); // unsigned authority should fail
|
||||
|
||||
config.signers = vec![
|
||||
&default_signer,
|
||||
&withdrawer_keypair,
|
||||
&new_withdrawer_keypair,
|
||||
];
|
||||
config.command = CliCommand::StakeAuthorize {
|
||||
stake_account_pubkey,
|
||||
new_authorizations: vec![StakeAuthorizationIndexed {
|
||||
authorization_type: StakeAuthorize::Withdrawer,
|
||||
new_authority_pubkey: new_withdrawer_pubkey,
|
||||
authority: 1,
|
||||
new_authority_signer: Some(2),
|
||||
}],
|
||||
sign_only: false,
|
||||
dump_transaction_message: false,
|
||||
blockhash_query: BlockhashQuery::default(),
|
||||
nonce_account: None,
|
||||
nonce_authority: 0,
|
||||
memo: None,
|
||||
fee_payer: 0,
|
||||
custodian: None,
|
||||
no_wait: false,
|
||||
};
|
||||
process_command(&config).unwrap();
|
||||
let stake_account = rpc_client.get_account(&stake_account_pubkey).unwrap();
|
||||
let stake_state: StakeState = stake_account.state().unwrap();
|
||||
let current_authority = match stake_state {
|
||||
StakeState::Initialized(meta) => meta.authorized.withdrawer,
|
||||
_ => panic!("Unexpected stake state!"),
|
||||
};
|
||||
assert_eq!(current_authority, new_withdrawer_pubkey);
|
||||
|
||||
// Set lockup, checking new custodian
|
||||
let custodian = Keypair::new();
|
||||
let custodian_pubkey = custodian.pubkey();
|
||||
let lockup = LockupArgs {
|
||||
unix_timestamp: Some(1_581_534_570),
|
||||
epoch: Some(200),
|
||||
custodian: Some(custodian_pubkey),
|
||||
};
|
||||
config.signers = vec![&default_signer, &new_withdrawer_keypair];
|
||||
config.command = CliCommand::StakeSetLockup {
|
||||
stake_account_pubkey,
|
||||
lockup,
|
||||
new_custodian_signer: Some(1),
|
||||
custodian: 1,
|
||||
sign_only: false,
|
||||
dump_transaction_message: false,
|
||||
blockhash_query: BlockhashQuery::default(),
|
||||
nonce_account: None,
|
||||
nonce_authority: 0,
|
||||
memo: None,
|
||||
fee_payer: 0,
|
||||
};
|
||||
process_command(&config).unwrap_err(); // unsigned new custodian should fail
|
||||
|
||||
config.signers = vec![&default_signer, &new_withdrawer_keypair, &custodian];
|
||||
config.command = CliCommand::StakeSetLockup {
|
||||
stake_account_pubkey,
|
||||
lockup,
|
||||
new_custodian_signer: Some(2),
|
||||
custodian: 1,
|
||||
sign_only: false,
|
||||
dump_transaction_message: false,
|
||||
blockhash_query: BlockhashQuery::default(),
|
||||
nonce_account: None,
|
||||
nonce_authority: 0,
|
||||
memo: None,
|
||||
fee_payer: 0,
|
||||
};
|
||||
process_command(&config).unwrap();
|
||||
let stake_account = rpc_client.get_account(&stake_account_pubkey).unwrap();
|
||||
let stake_state: StakeState = stake_account.state().unwrap();
|
||||
let current_lockup = match stake_state {
|
||||
StakeState::Initialized(meta) => meta.lockup,
|
||||
_ => panic!("Unexpected stake state!"),
|
||||
};
|
||||
assert_eq!(
|
||||
current_lockup.unix_timestamp,
|
||||
lockup.unix_timestamp.unwrap()
|
||||
);
|
||||
assert_eq!(current_lockup.epoch, lockup.epoch.unwrap());
|
||||
assert_eq!(current_lockup.custodian, custodian_pubkey);
|
||||
}
|
||||
|
@ -16,7 +16,9 @@ use solana_sdk::{
|
||||
nonce::State as NonceState,
|
||||
pubkey::Pubkey,
|
||||
signature::{keypair_from_seed, Keypair, NullSigner, Signer},
|
||||
stake,
|
||||
};
|
||||
use solana_streamer::socket::SocketAddrSpace;
|
||||
|
||||
#[test]
|
||||
fn test_transfer() {
|
||||
@ -24,7 +26,12 @@ fn test_transfer() {
|
||||
let mint_keypair = Keypair::new();
|
||||
let mint_pubkey = mint_keypair.pubkey();
|
||||
let faucet_addr = run_local_faucet(mint_keypair, None);
|
||||
let test_validator = TestValidator::with_custom_fees(mint_pubkey, 1, Some(faucet_addr));
|
||||
let test_validator = TestValidator::with_custom_fees(
|
||||
mint_pubkey,
|
||||
1,
|
||||
Some(faucet_addr),
|
||||
SocketAddrSpace::Unspecified,
|
||||
);
|
||||
|
||||
let rpc_client =
|
||||
RpcClient::new_with_commitment(test_validator.rpc_url(), CommitmentConfig::processed());
|
||||
@ -276,7 +283,12 @@ fn test_transfer_multisession_signing() {
|
||||
let mint_keypair = Keypair::new();
|
||||
let mint_pubkey = mint_keypair.pubkey();
|
||||
let faucet_addr = run_local_faucet(mint_keypair, None);
|
||||
let test_validator = TestValidator::with_custom_fees(mint_pubkey, 1, Some(faucet_addr));
|
||||
let test_validator = TestValidator::with_custom_fees(
|
||||
mint_pubkey,
|
||||
1,
|
||||
Some(faucet_addr),
|
||||
SocketAddrSpace::Unspecified,
|
||||
);
|
||||
|
||||
let to_pubkey = Pubkey::new(&[1u8; 32]);
|
||||
let offline_from_signer = keypair_from_seed(&[2u8; 32]).unwrap();
|
||||
@ -403,7 +415,12 @@ fn test_transfer_all() {
|
||||
let mint_keypair = Keypair::new();
|
||||
let mint_pubkey = mint_keypair.pubkey();
|
||||
let faucet_addr = run_local_faucet(mint_keypair, None);
|
||||
let test_validator = TestValidator::with_custom_fees(mint_pubkey, 1, Some(faucet_addr));
|
||||
let test_validator = TestValidator::with_custom_fees(
|
||||
mint_pubkey,
|
||||
1,
|
||||
Some(faucet_addr),
|
||||
SocketAddrSpace::Unspecified,
|
||||
);
|
||||
|
||||
let rpc_client =
|
||||
RpcClient::new_with_commitment(test_validator.rpc_url(), CommitmentConfig::processed());
|
||||
@ -451,7 +468,12 @@ fn test_transfer_unfunded_recipient() {
|
||||
let mint_keypair = Keypair::new();
|
||||
let mint_pubkey = mint_keypair.pubkey();
|
||||
let faucet_addr = run_local_faucet(mint_keypair, None);
|
||||
let test_validator = TestValidator::with_custom_fees(mint_pubkey, 1, Some(faucet_addr));
|
||||
let test_validator = TestValidator::with_custom_fees(
|
||||
mint_pubkey,
|
||||
1,
|
||||
Some(faucet_addr),
|
||||
SocketAddrSpace::Unspecified,
|
||||
);
|
||||
|
||||
let rpc_client =
|
||||
RpcClient::new_with_commitment(test_validator.rpc_url(), CommitmentConfig::processed());
|
||||
@ -499,7 +521,12 @@ fn test_transfer_with_seed() {
|
||||
let mint_keypair = Keypair::new();
|
||||
let mint_pubkey = mint_keypair.pubkey();
|
||||
let faucet_addr = run_local_faucet(mint_keypair, None);
|
||||
let test_validator = TestValidator::with_custom_fees(mint_pubkey, 1, Some(faucet_addr));
|
||||
let test_validator = TestValidator::with_custom_fees(
|
||||
mint_pubkey,
|
||||
1,
|
||||
Some(faucet_addr),
|
||||
SocketAddrSpace::Unspecified,
|
||||
);
|
||||
|
||||
let rpc_client =
|
||||
RpcClient::new_with_commitment(test_validator.rpc_url(), CommitmentConfig::processed());
|
||||
@ -513,7 +540,7 @@ fn test_transfer_with_seed() {
|
||||
let sender_pubkey = config.signers[0].pubkey();
|
||||
let recipient_pubkey = Pubkey::new(&[1u8; 32]);
|
||||
let derived_address_seed = "seed".to_string();
|
||||
let derived_address_program_id = solana_stake_program::id();
|
||||
let derived_address_program_id = stake::program::id();
|
||||
let derived_address = Pubkey::create_with_seed(
|
||||
&sender_pubkey,
|
||||
&derived_address_seed,
|
||||
|
@ -14,6 +14,7 @@ use solana_sdk::{
|
||||
commitment_config::CommitmentConfig,
|
||||
signature::{Keypair, Signer},
|
||||
};
|
||||
use solana_streamer::socket::SocketAddrSpace;
|
||||
use solana_vote_program::vote_state::{VoteAuthorize, VoteState, VoteStateVersions};
|
||||
|
||||
#[test]
|
||||
@ -21,7 +22,8 @@ fn test_vote_authorize_and_withdraw() {
|
||||
let mint_keypair = Keypair::new();
|
||||
let mint_pubkey = mint_keypair.pubkey();
|
||||
let faucet_addr = run_local_faucet(mint_keypair, None);
|
||||
let test_validator = TestValidator::with_no_fees(mint_pubkey, Some(faucet_addr));
|
||||
let test_validator =
|
||||
TestValidator::with_no_fees(mint_pubkey, Some(faucet_addr), SocketAddrSpace::Unspecified);
|
||||
|
||||
let rpc_client =
|
||||
RpcClient::new_with_commitment(test_validator.rpc_url(), CommitmentConfig::processed());
|
||||
@ -43,7 +45,7 @@ fn test_vote_authorize_and_withdraw() {
|
||||
seed: None,
|
||||
identity_account: 0,
|
||||
authorized_voter: None,
|
||||
authorized_withdrawer: Some(config.signers[0].pubkey()),
|
||||
authorized_withdrawer: config.signers[0].pubkey(),
|
||||
commission: 0,
|
||||
memo: None,
|
||||
};
|
||||
@ -83,13 +85,48 @@ fn test_vote_authorize_and_withdraw() {
|
||||
check_recent_balance(expected_balance, &rpc_client, &vote_account_pubkey);
|
||||
|
||||
// Authorize vote account withdrawal to another signer
|
||||
let withdraw_authority = Keypair::new();
|
||||
let first_withdraw_authority = Keypair::new();
|
||||
config.signers = vec![&default_signer];
|
||||
config.command = CliCommand::VoteAuthorize {
|
||||
vote_account_pubkey,
|
||||
new_authorized_pubkey: first_withdraw_authority.pubkey(),
|
||||
vote_authorize: VoteAuthorize::Withdrawer,
|
||||
memo: None,
|
||||
authorized: 0,
|
||||
new_authorized: None,
|
||||
};
|
||||
process_command(&config).unwrap();
|
||||
let vote_account = rpc_client
|
||||
.get_account(&vote_account_keypair.pubkey())
|
||||
.unwrap();
|
||||
let vote_state: VoteStateVersions = vote_account.state().unwrap();
|
||||
let authorized_withdrawer = vote_state.convert_to_current().authorized_withdrawer;
|
||||
assert_eq!(authorized_withdrawer, first_withdraw_authority.pubkey());
|
||||
|
||||
// Authorize vote account withdrawal to another signer with checked instruction
|
||||
let withdraw_authority = Keypair::new();
|
||||
config.signers = vec![&default_signer, &first_withdraw_authority];
|
||||
config.command = CliCommand::VoteAuthorize {
|
||||
vote_account_pubkey,
|
||||
new_authorized_pubkey: withdraw_authority.pubkey(),
|
||||
vote_authorize: VoteAuthorize::Withdrawer,
|
||||
memo: None,
|
||||
authorized: 1,
|
||||
new_authorized: Some(1),
|
||||
};
|
||||
process_command(&config).unwrap_err(); // unsigned by new authority should fail
|
||||
config.signers = vec![
|
||||
&default_signer,
|
||||
&first_withdraw_authority,
|
||||
&withdraw_authority,
|
||||
];
|
||||
config.command = CliCommand::VoteAuthorize {
|
||||
vote_account_pubkey,
|
||||
new_authorized_pubkey: withdraw_authority.pubkey(),
|
||||
vote_authorize: VoteAuthorize::Withdrawer,
|
||||
memo: None,
|
||||
authorized: 1,
|
||||
new_authorized: Some(2),
|
||||
};
|
||||
process_command(&config).unwrap();
|
||||
let vote_account = rpc_client
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "solana-client"
|
||||
version = "1.7.0"
|
||||
version = "1.7.14"
|
||||
description = "Solana Client"
|
||||
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
@ -15,7 +15,7 @@ bincode = "1.3.1"
|
||||
bs58 = "0.3.1"
|
||||
clap = "2.33.0"
|
||||
indicatif = "0.15.0"
|
||||
jsonrpc-core = "17.0.0"
|
||||
jsonrpc-core = "18.0.0"
|
||||
log = "0.4.11"
|
||||
net2 = "0.2.37"
|
||||
rayon = "1.5.0"
|
||||
@ -24,14 +24,14 @@ semver = "0.11.0"
|
||||
serde = "1.0.122"
|
||||
serde_derive = "1.0.103"
|
||||
serde_json = "1.0.56"
|
||||
solana-account-decoder = { path = "../account-decoder", version = "=1.7.0" }
|
||||
solana-clap-utils = { path = "../clap-utils", version = "=1.7.0" }
|
||||
solana-faucet = { path = "../faucet", version = "=1.7.0" }
|
||||
solana-net-utils = { path = "../net-utils", version = "=1.7.0" }
|
||||
solana-sdk = { path = "../sdk", version = "=1.7.0" }
|
||||
solana-transaction-status = { path = "../transaction-status", version = "=1.7.0" }
|
||||
solana-version = { path = "../version", version = "=1.7.0" }
|
||||
solana-vote-program = { path = "../programs/vote", version = "=1.7.0" }
|
||||
solana-account-decoder = { path = "../account-decoder", version = "=1.7.14" }
|
||||
solana-clap-utils = { path = "../clap-utils", version = "=1.7.14" }
|
||||
solana-faucet = { path = "../faucet", version = "=1.7.14" }
|
||||
solana-net-utils = { path = "../net-utils", version = "=1.7.14" }
|
||||
solana-sdk = { path = "../sdk", version = "=1.7.14" }
|
||||
solana-transaction-status = { path = "../transaction-status", version = "=1.7.14" }
|
||||
solana-version = { path = "../version", version = "=1.7.14" }
|
||||
solana-vote-program = { path = "../programs/vote", version = "=1.7.14" }
|
||||
thiserror = "1.0"
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
tungstenite = "0.10.1"
|
||||
@ -39,8 +39,8 @@ url = "2.1.1"
|
||||
|
||||
[dev-dependencies]
|
||||
assert_matches = "1.3.0"
|
||||
jsonrpc-http-server = "17.0.0"
|
||||
solana-logger = { path = "../logger", version = "=1.7.0" }
|
||||
jsonrpc-http-server = "18.0.0"
|
||||
solana-logger = { path = "../logger", version = "=1.7.14" }
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
@ -122,10 +122,10 @@ mod tests {
|
||||
use crate::{
|
||||
blockhash_query,
|
||||
rpc_request::RpcRequest,
|
||||
rpc_response::{Response, RpcFeeCalculator, RpcResponseContext},
|
||||
rpc_response::{Response, RpcFeeCalculator, RpcFees, RpcResponseContext},
|
||||
};
|
||||
use clap::App;
|
||||
use serde_json::{self, json, Value};
|
||||
use serde_json::{self, json};
|
||||
use solana_account_decoder::{UiAccount, UiAccountEncoding};
|
||||
use solana_sdk::{account::Account, hash::hash, nonce, system_program};
|
||||
use std::collections::HashMap;
|
||||
@ -288,10 +288,12 @@ mod tests {
|
||||
let rpc_fee_calc = FeeCalculator::new(42);
|
||||
let get_recent_blockhash_response = json!(Response {
|
||||
context: RpcResponseContext { slot: 1 },
|
||||
value: json!((
|
||||
Value::String(rpc_blockhash.to_string()),
|
||||
serde_json::to_value(rpc_fee_calc.clone()).unwrap()
|
||||
)),
|
||||
value: json!(RpcFees {
|
||||
blockhash: rpc_blockhash.to_string(),
|
||||
fee_calculator: rpc_fee_calc.clone(),
|
||||
last_valid_slot: 42,
|
||||
last_valid_block_height: 42,
|
||||
}),
|
||||
});
|
||||
let get_fee_calculator_for_blockhash_response = json!(Response {
|
||||
context: RpcResponseContext { slot: 1 },
|
||||
@ -300,10 +302,7 @@ mod tests {
|
||||
}),
|
||||
});
|
||||
let mut mocks = HashMap::new();
|
||||
mocks.insert(
|
||||
RpcRequest::GetRecentBlockhash,
|
||||
get_recent_blockhash_response.clone(),
|
||||
);
|
||||
mocks.insert(RpcRequest::GetFees, get_recent_blockhash_response.clone());
|
||||
let rpc_client = RpcClient::new_mock_with_mocks("".to_string(), mocks);
|
||||
assert_eq!(
|
||||
BlockhashQuery::default()
|
||||
@ -312,10 +311,7 @@ mod tests {
|
||||
(rpc_blockhash, rpc_fee_calc.clone()),
|
||||
);
|
||||
let mut mocks = HashMap::new();
|
||||
mocks.insert(
|
||||
RpcRequest::GetRecentBlockhash,
|
||||
get_recent_blockhash_response.clone(),
|
||||
);
|
||||
mocks.insert(RpcRequest::GetFees, get_recent_blockhash_response.clone());
|
||||
mocks.insert(
|
||||
RpcRequest::GetFeeCalculatorForBlockhash,
|
||||
get_fee_calculator_for_blockhash_response,
|
||||
@ -328,10 +324,7 @@ mod tests {
|
||||
(test_blockhash, rpc_fee_calc),
|
||||
);
|
||||
let mut mocks = HashMap::new();
|
||||
mocks.insert(
|
||||
RpcRequest::GetRecentBlockhash,
|
||||
get_recent_blockhash_response,
|
||||
);
|
||||
mocks.insert(RpcRequest::GetFees, get_recent_blockhash_response);
|
||||
let rpc_client = RpcClient::new_mock_with_mocks("".to_string(), mocks);
|
||||
assert_eq!(
|
||||
BlockhashQuery::None(test_blockhash)
|
||||
|
@ -1,5 +1,5 @@
|
||||
use {
|
||||
crate::rpc_request,
|
||||
crate::{rpc_request, rpc_response},
|
||||
solana_faucet::faucet::FaucetError,
|
||||
solana_sdk::{
|
||||
signature::SignerError, transaction::TransactionError, transport::TransportError,
|
||||
@ -30,6 +30,24 @@ pub enum ClientErrorKind {
|
||||
Custom(String),
|
||||
}
|
||||
|
||||
impl ClientErrorKind {
|
||||
pub fn get_transaction_error(&self) -> Option<TransactionError> {
|
||||
match self {
|
||||
Self::RpcError(rpc_request::RpcError::RpcResponseError {
|
||||
data:
|
||||
rpc_request::RpcResponseErrorData::SendTransactionPreflightFailure(
|
||||
rpc_response::RpcSimulateTransactionResult {
|
||||
err: Some(tx_err), ..
|
||||
},
|
||||
),
|
||||
..
|
||||
}) => Some(tx_err.clone()),
|
||||
Self::TransactionError(tx_err) => Some(tx_err.clone()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<TransportError> for ClientErrorKind {
|
||||
fn from(err: TransportError) -> Self {
|
||||
match err {
|
||||
@ -86,6 +104,10 @@ impl ClientError {
|
||||
pub fn kind(&self) -> &ClientErrorKind {
|
||||
&self.kind
|
||||
}
|
||||
|
||||
pub fn get_transaction_error(&self) -> Option<TransactionError> {
|
||||
self.kind.get_transaction_error()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ClientErrorKind> for ClientError {
|
||||
|
@ -1,10 +1,12 @@
|
||||
//! The standard [`RpcSender`] over HTTP.
|
||||
|
||||
use {
|
||||
crate::{
|
||||
client_error::Result,
|
||||
rpc_custom_error,
|
||||
rpc_request::{RpcError, RpcRequest, RpcResponseErrorData},
|
||||
rpc_response::RpcSimulateTransactionResult,
|
||||
rpc_sender::RpcSender,
|
||||
rpc_sender::*,
|
||||
},
|
||||
log::*,
|
||||
reqwest::{
|
||||
@ -15,10 +17,10 @@ use {
|
||||
std::{
|
||||
sync::{
|
||||
atomic::{AtomicU64, Ordering},
|
||||
Arc,
|
||||
Arc, RwLock,
|
||||
},
|
||||
thread::sleep,
|
||||
time::Duration,
|
||||
time::{Duration, Instant},
|
||||
},
|
||||
};
|
||||
|
||||
@ -26,13 +28,22 @@ pub struct HttpSender {
|
||||
client: Arc<reqwest::blocking::Client>,
|
||||
url: String,
|
||||
request_id: AtomicU64,
|
||||
stats: RwLock<RpcTransportStats>,
|
||||
}
|
||||
|
||||
/// The standard [`RpcSender`] over HTTP.
|
||||
impl HttpSender {
|
||||
/// Create an HTTP RPC sender.
|
||||
///
|
||||
/// The URL is an HTTP URL, usually for port 8899, as in
|
||||
/// "http://localhost:8899". The sender has a default timeout of 30 seconds.
|
||||
pub fn new(url: String) -> Self {
|
||||
Self::new_with_timeout(url, Duration::from_secs(30))
|
||||
}
|
||||
|
||||
/// Create an HTTP RPC sender.
|
||||
///
|
||||
/// The URL is an HTTP URL, usually for port 8899.
|
||||
pub fn new_with_timeout(url: String, timeout: Duration) -> Self {
|
||||
// `reqwest::blocking::Client` panics if run in a tokio async context. Shuttle the
|
||||
// request to a different tokio thread to avoid this
|
||||
@ -49,6 +60,7 @@ impl HttpSender {
|
||||
client,
|
||||
url,
|
||||
request_id: AtomicU64::new(0),
|
||||
stats: RwLock::new(RpcTransportStats::default()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -57,11 +69,45 @@ impl HttpSender {
|
||||
struct RpcErrorObject {
|
||||
code: i64,
|
||||
message: String,
|
||||
data: serde_json::Value,
|
||||
}
|
||||
|
||||
struct StatsUpdater<'a> {
|
||||
stats: &'a RwLock<RpcTransportStats>,
|
||||
request_start_time: Instant,
|
||||
rate_limited_time: Duration,
|
||||
}
|
||||
|
||||
impl<'a> StatsUpdater<'a> {
|
||||
fn new(stats: &'a RwLock<RpcTransportStats>) -> Self {
|
||||
Self {
|
||||
stats,
|
||||
request_start_time: Instant::now(),
|
||||
rate_limited_time: Duration::default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn add_rate_limited_time(&mut self, duration: Duration) {
|
||||
self.rate_limited_time += duration;
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Drop for StatsUpdater<'a> {
|
||||
fn drop(&mut self) {
|
||||
let mut stats = self.stats.write().unwrap();
|
||||
stats.request_count += 1;
|
||||
stats.elapsed_time += Instant::now().duration_since(self.request_start_time);
|
||||
stats.rate_limited_time += self.rate_limited_time;
|
||||
}
|
||||
}
|
||||
|
||||
impl RpcSender for HttpSender {
|
||||
fn get_transport_stats(&self) -> RpcTransportStats {
|
||||
self.stats.read().unwrap().clone()
|
||||
}
|
||||
|
||||
fn send(&self, request: RpcRequest, params: serde_json::Value) -> Result<serde_json::Value> {
|
||||
let mut stats_updater = StatsUpdater::new(&self.stats);
|
||||
|
||||
let request_id = self.request_id.fetch_add(1, Ordering::Relaxed);
|
||||
let request_json = request.build_request_json(request_id, params).to_string();
|
||||
|
||||
@ -79,45 +125,42 @@ impl RpcSender for HttpSender {
|
||||
.body(request_json)
|
||||
.send()
|
||||
})
|
||||
};
|
||||
}?;
|
||||
|
||||
match response {
|
||||
Ok(response) => {
|
||||
if !response.status().is_success() {
|
||||
if response.status() == StatusCode::TOO_MANY_REQUESTS
|
||||
&& too_many_requests_retries > 0
|
||||
{
|
||||
let mut duration = Duration::from_millis(500);
|
||||
if let Some(retry_after) = response.headers().get(RETRY_AFTER) {
|
||||
if let Ok(retry_after) = retry_after.to_str() {
|
||||
if let Ok(retry_after) = retry_after.parse::<u64>() {
|
||||
if retry_after < 120 {
|
||||
duration = Duration::from_secs(retry_after);
|
||||
}
|
||||
}
|
||||
if !response.status().is_success() {
|
||||
if response.status() == StatusCode::TOO_MANY_REQUESTS
|
||||
&& too_many_requests_retries > 0
|
||||
{
|
||||
let mut duration = Duration::from_millis(500);
|
||||
if let Some(retry_after) = response.headers().get(RETRY_AFTER) {
|
||||
if let Ok(retry_after) = retry_after.to_str() {
|
||||
if let Ok(retry_after) = retry_after.parse::<u64>() {
|
||||
if retry_after < 120 {
|
||||
duration = Duration::from_secs(retry_after);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
too_many_requests_retries -= 1;
|
||||
debug!(
|
||||
too_many_requests_retries -= 1;
|
||||
debug!(
|
||||
"Too many requests: server responded with {:?}, {} retries left, pausing for {:?}",
|
||||
response, too_many_requests_retries, duration
|
||||
);
|
||||
|
||||
sleep(duration);
|
||||
continue;
|
||||
}
|
||||
return Err(response.error_for_status().unwrap_err().into());
|
||||
}
|
||||
sleep(duration);
|
||||
stats_updater.add_rate_limited_time(duration);
|
||||
continue;
|
||||
}
|
||||
return Err(response.error_for_status().unwrap_err().into());
|
||||
}
|
||||
|
||||
let response_text = tokio::task::block_in_place(move || response.text())?;
|
||||
|
||||
let json: serde_json::Value = serde_json::from_str(&response_text)?;
|
||||
if json["error"].is_object() {
|
||||
return match serde_json::from_value::<RpcErrorObject>(json["error"].clone())
|
||||
{
|
||||
Ok(rpc_error_object) => {
|
||||
let data = match rpc_error_object.code {
|
||||
let mut json =
|
||||
tokio::task::block_in_place(move || response.json::<serde_json::Value>())?;
|
||||
if json["error"].is_object() {
|
||||
return match serde_json::from_value::<RpcErrorObject>(json["error"].clone()) {
|
||||
Ok(rpc_error_object) => {
|
||||
let data = match rpc_error_object.code {
|
||||
rpc_custom_error::JSON_RPC_SERVER_ERROR_SEND_TRANSACTION_PREFLIGHT_FAILURE => {
|
||||
match serde_json::from_value::<RpcSimulateTransactionResult>(json["error"]["data"].clone()) {
|
||||
Ok(data) => RpcResponseErrorData::SendTransactionPreflightFailure(data),
|
||||
@ -138,27 +181,22 @@ impl RpcSender for HttpSender {
|
||||
_ => RpcResponseErrorData::Empty
|
||||
};
|
||||
|
||||
Err(RpcError::RpcResponseError {
|
||||
code: rpc_error_object.code,
|
||||
message: rpc_error_object.message,
|
||||
data,
|
||||
}
|
||||
.into())
|
||||
}
|
||||
Err(err) => Err(RpcError::RpcRequestError(format!(
|
||||
"Failed to deserialize RPC error response: {} [{}]",
|
||||
serde_json::to_string(&json["error"]).unwrap(),
|
||||
err
|
||||
))
|
||||
.into()),
|
||||
};
|
||||
Err(RpcError::RpcResponseError {
|
||||
code: rpc_error_object.code,
|
||||
message: rpc_error_object.message,
|
||||
data,
|
||||
}
|
||||
.into())
|
||||
}
|
||||
return Ok(json["result"].clone());
|
||||
}
|
||||
Err(err) => {
|
||||
return Err(err.into());
|
||||
}
|
||||
Err(err) => Err(RpcError::RpcRequestError(format!(
|
||||
"Failed to deserialize RPC error response: {} [{}]",
|
||||
serde_json::to_string(&json["error"]).unwrap(),
|
||||
err
|
||||
))
|
||||
.into()),
|
||||
};
|
||||
}
|
||||
return Ok(json["result"].take());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,21 +1,42 @@
|
||||
//! An [`RpcSender`] used for unit testing [`RpcClient`](crate::rpc_client::RpcClient).
|
||||
|
||||
use {
|
||||
crate::{
|
||||
client_error::Result,
|
||||
rpc_config::RpcBlockProductionConfig,
|
||||
rpc_request::RpcRequest,
|
||||
rpc_response::{Response, RpcResponseContext, RpcVersionInfo},
|
||||
rpc_sender::RpcSender,
|
||||
rpc_response::{
|
||||
Response, RpcAccountBalance, RpcBlockProduction, RpcBlockProductionRange,
|
||||
RpcConfirmedTransactionStatusWithSignature, RpcContactInfo, RpcFees, RpcIdentity,
|
||||
RpcInflationGovernor, RpcInflationRate, RpcInflationReward, RpcKeyedAccount,
|
||||
RpcPerfSample, RpcResponseContext, RpcSimulateTransactionResult, RpcStakeActivation,
|
||||
RpcSupply, RpcVersionInfo, RpcVoteAccountInfo, RpcVoteAccountStatus,
|
||||
StakeActivationState,
|
||||
},
|
||||
rpc_sender::*,
|
||||
},
|
||||
serde_json::{json, Number, Value},
|
||||
solana_account_decoder::{UiAccount, UiAccountEncoding},
|
||||
solana_sdk::{
|
||||
account::Account,
|
||||
clock::{Slot, UnixTimestamp},
|
||||
epoch_info::EpochInfo,
|
||||
fee_calculator::{FeeCalculator, FeeRateGovernor},
|
||||
instruction::InstructionError,
|
||||
message::MessageHeader,
|
||||
pubkey::Pubkey,
|
||||
signature::Signature,
|
||||
sysvar::epoch_schedule::EpochSchedule,
|
||||
transaction::{self, Transaction, TransactionError},
|
||||
},
|
||||
solana_transaction_status::{TransactionConfirmationStatus, TransactionStatus},
|
||||
solana_transaction_status::{
|
||||
EncodedConfirmedBlock, EncodedConfirmedTransaction, EncodedTransaction,
|
||||
EncodedTransactionWithStatusMeta, Rewards, TransactionConfirmationStatus,
|
||||
TransactionStatus, UiCompiledInstruction, UiMessage, UiRawMessage, UiTransaction,
|
||||
UiTransactionEncoding, UiTransactionStatusMeta,
|
||||
},
|
||||
solana_version::Version,
|
||||
std::{collections::HashMap, sync::RwLock},
|
||||
std::{collections::HashMap, net::SocketAddr, str::FromStr, sync::RwLock},
|
||||
};
|
||||
|
||||
pub const PUBKEY: &str = "7RoSF9fUmdphVCpabEoefH81WwrW7orsWonXWqTXkKV8";
|
||||
@ -28,6 +49,31 @@ pub struct MockSender {
|
||||
url: String,
|
||||
}
|
||||
|
||||
/// An [`RpcSender`] used for unit testing [`RpcClient`](crate::rpc_client::RpcClient).
|
||||
///
|
||||
/// This is primarily for internal use.
|
||||
///
|
||||
/// Unless directed otherwise, it will generally return a reasonable default
|
||||
/// response, at least for [`RpcRequest`] values for which responses have been
|
||||
/// implemented.
|
||||
///
|
||||
/// The behavior can be customized in two ways:
|
||||
///
|
||||
/// 1) The `url` constructor argument is not actually a URL, but a simple string
|
||||
/// directive that changes `MockSender`s behavior in specific scenarios.
|
||||
///
|
||||
/// If `url` is "fails" then any call to `send` will return `Ok(Value::Null)`.
|
||||
///
|
||||
/// It is customary to set the `url` to "succeeds" for mocks that should
|
||||
/// return sucessfully, though this value is not actually interpreted.
|
||||
///
|
||||
/// Other possible values of `url` are specific to different `RpcRequest`
|
||||
/// values. Read the implementation for specifics.
|
||||
///
|
||||
/// 2) Custom responses can be configured by providing [`Mocks`] to the
|
||||
/// [`MockSender::new_with_mocks`] constructor. This type is a [`HashMap`]
|
||||
/// from [`RpcRequest`] to a JSON [`Value`] response, Any entries in this map
|
||||
/// override the default behavior for the given request.
|
||||
impl MockSender {
|
||||
pub fn new(url: String) -> Self {
|
||||
Self::new_with_mocks(url, Mocks::default())
|
||||
@ -42,6 +88,10 @@ impl MockSender {
|
||||
}
|
||||
|
||||
impl RpcSender for MockSender {
|
||||
fn get_transport_stats(&self) -> RpcTransportStats {
|
||||
RpcTransportStats::default()
|
||||
}
|
||||
|
||||
fn send(&self, request: RpcRequest, params: serde_json::Value) -> Result<serde_json::Value> {
|
||||
if let Some(value) = self.mocks.write().unwrap().remove(&request) {
|
||||
return Ok(value);
|
||||
@ -49,23 +99,26 @@ impl RpcSender for MockSender {
|
||||
if self.url == "fails" {
|
||||
return Ok(Value::Null);
|
||||
}
|
||||
let val = match request {
|
||||
RpcRequest::GetAccountInfo => serde_json::to_value(Response {
|
||||
|
||||
let method = &request.build_request_json(42, params.clone())["method"];
|
||||
|
||||
let val = match method.as_str().unwrap() {
|
||||
"getAccountInfo" => serde_json::to_value(Response {
|
||||
context: RpcResponseContext { slot: 1 },
|
||||
value: Value::Null,
|
||||
})?,
|
||||
RpcRequest::GetBalance => serde_json::to_value(Response {
|
||||
"getBalance" => serde_json::to_value(Response {
|
||||
context: RpcResponseContext { slot: 1 },
|
||||
value: Value::Number(Number::from(50)),
|
||||
})?,
|
||||
RpcRequest::GetRecentBlockhash => serde_json::to_value(Response {
|
||||
"getRecentBlockhash" => serde_json::to_value(Response {
|
||||
context: RpcResponseContext { slot: 1 },
|
||||
value: (
|
||||
Value::String(PUBKEY.to_string()),
|
||||
serde_json::to_value(FeeCalculator::default()).unwrap(),
|
||||
),
|
||||
})?,
|
||||
RpcRequest::GetEpochInfo => serde_json::to_value(EpochInfo {
|
||||
"getEpochInfo" => serde_json::to_value(EpochInfo {
|
||||
epoch: 1,
|
||||
slot_index: 2,
|
||||
slots_in_epoch: 32,
|
||||
@ -73,7 +126,7 @@ impl RpcSender for MockSender {
|
||||
block_height: 34,
|
||||
transaction_count: Some(123),
|
||||
})?,
|
||||
RpcRequest::GetFeeCalculatorForBlockhash => {
|
||||
"getFeeCalculatorForBlockhash" => {
|
||||
let value = if self.url == "blockhash_expired" {
|
||||
Value::Null
|
||||
} else {
|
||||
@ -84,11 +137,21 @@ impl RpcSender for MockSender {
|
||||
value,
|
||||
})?
|
||||
}
|
||||
RpcRequest::GetFeeRateGovernor => serde_json::to_value(Response {
|
||||
"getFeeRateGovernor" => serde_json::to_value(Response {
|
||||
context: RpcResponseContext { slot: 1 },
|
||||
value: serde_json::to_value(FeeRateGovernor::default()).unwrap(),
|
||||
})?,
|
||||
RpcRequest::GetSignatureStatuses => {
|
||||
"getFees" => serde_json::to_value(Response {
|
||||
context: RpcResponseContext { slot: 1 },
|
||||
value: serde_json::to_value(RpcFees {
|
||||
blockhash: PUBKEY.to_string(),
|
||||
fee_calculator: FeeCalculator::default(),
|
||||
last_valid_slot: 42,
|
||||
last_valid_block_height: 42,
|
||||
})
|
||||
.unwrap(),
|
||||
})?,
|
||||
"getSignatureStatuses" => {
|
||||
let status: transaction::Result<()> = if self.url == "account_in_use" {
|
||||
Err(TransactionError::AccountInUse)
|
||||
} else if self.url == "instruction_error" {
|
||||
@ -122,11 +185,133 @@ impl RpcSender for MockSender {
|
||||
value: statuses,
|
||||
})?
|
||||
}
|
||||
RpcRequest::GetTransactionCount => Value::Number(Number::from(1234)),
|
||||
RpcRequest::GetSlot => Value::Number(Number::from(0)),
|
||||
RpcRequest::GetMaxShredInsertSlot => Value::Number(Number::from(0)),
|
||||
RpcRequest::RequestAirdrop => Value::String(Signature::new(&[8; 64]).to_string()),
|
||||
RpcRequest::SendTransaction => {
|
||||
"getTransaction" => serde_json::to_value(EncodedConfirmedTransaction {
|
||||
slot: 2,
|
||||
transaction: EncodedTransactionWithStatusMeta {
|
||||
transaction: EncodedTransaction::Json(
|
||||
UiTransaction {
|
||||
signatures: vec!["3AsdoALgZFuq2oUVWrDYhg2pNeaLJKPLf8hU2mQ6U8qJxeJ6hsrPVpMn9ma39DtfYCrDQSvngWRP8NnTpEhezJpE".to_string()],
|
||||
message: UiMessage::Raw(
|
||||
UiRawMessage {
|
||||
header: MessageHeader {
|
||||
num_required_signatures: 1,
|
||||
num_readonly_signed_accounts: 0,
|
||||
num_readonly_unsigned_accounts: 1,
|
||||
},
|
||||
account_keys: vec![
|
||||
"C6eBmAXKg6JhJWkajGa5YRGUfG4YKXwbxF5Ufv7PtExZ".to_string(),
|
||||
"2Gd5eoR5J4BV89uXbtunpbNhjmw3wa1NbRHxTHzDzZLX".to_string(),
|
||||
"11111111111111111111111111111111".to_string(),
|
||||
],
|
||||
recent_blockhash: "D37n3BSG71oUWcWjbZ37jZP7UfsxG2QMKeuALJ1PYvM6".to_string(),
|
||||
instructions: vec![UiCompiledInstruction {
|
||||
program_id_index: 2,
|
||||
accounts: vec![0, 1],
|
||||
data: "3Bxs49DitAvXtoDR".to_string(),
|
||||
}],
|
||||
})
|
||||
}),
|
||||
meta: Some(UiTransactionStatusMeta {
|
||||
err: None,
|
||||
status: Ok(()),
|
||||
fee: 0,
|
||||
pre_balances: vec![499999999999999950, 50, 1],
|
||||
post_balances: vec![499999999999999950, 50, 1],
|
||||
inner_instructions: None,
|
||||
log_messages: None,
|
||||
pre_token_balances: None,
|
||||
post_token_balances: None,
|
||||
rewards: None,
|
||||
}),
|
||||
},
|
||||
block_time: Some(1628633791),
|
||||
})?,
|
||||
"getTransactionCount" => json![1234],
|
||||
"getSlot" => json![0],
|
||||
"getMaxShredInsertSlot" => json![0],
|
||||
"requestAirdrop" => Value::String(Signature::new(&[8; 64]).to_string()),
|
||||
"getSnapshotSlot" => Value::Number(Number::from(0)),
|
||||
"getBlockHeight" => Value::Number(Number::from(1234)),
|
||||
"getSlotLeaders" => json!([PUBKEY]),
|
||||
"getBlockProduction" => {
|
||||
if params.is_null() {
|
||||
json!(Response {
|
||||
context: RpcResponseContext { slot: 1 },
|
||||
value: RpcBlockProduction {
|
||||
by_identity: HashMap::new(),
|
||||
range: RpcBlockProductionRange {
|
||||
first_slot: 1,
|
||||
last_slot: 2,
|
||||
},
|
||||
},
|
||||
})
|
||||
} else {
|
||||
let config: Vec<RpcBlockProductionConfig> =
|
||||
serde_json::from_value(params).unwrap();
|
||||
let config = config[0].clone();
|
||||
let mut by_identity = HashMap::new();
|
||||
by_identity.insert(config.identity.unwrap(), (1, 123));
|
||||
let config_range = config.range.unwrap_or_default();
|
||||
|
||||
json!(Response {
|
||||
context: RpcResponseContext { slot: 1 },
|
||||
value: RpcBlockProduction {
|
||||
by_identity,
|
||||
range: RpcBlockProductionRange {
|
||||
first_slot: config_range.first_slot,
|
||||
last_slot: {
|
||||
if let Some(last_slot) = config_range.last_slot {
|
||||
last_slot
|
||||
} else {
|
||||
2
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
"getStakeActivation" => json!(RpcStakeActivation {
|
||||
state: StakeActivationState::Activating,
|
||||
active: 123,
|
||||
inactive: 12,
|
||||
}),
|
||||
"getSupply" => json!(Response {
|
||||
context: RpcResponseContext { slot: 1 },
|
||||
value: RpcSupply {
|
||||
total: 100000000,
|
||||
circulating: 50000,
|
||||
non_circulating: 20000,
|
||||
non_circulating_accounts: vec![PUBKEY.to_string()],
|
||||
},
|
||||
}),
|
||||
"getLargestAccounts" => {
|
||||
let rpc_account_balance = RpcAccountBalance {
|
||||
address: PUBKEY.to_string(),
|
||||
lamports: 10000,
|
||||
};
|
||||
|
||||
json!(Response {
|
||||
context: RpcResponseContext { slot: 1 },
|
||||
value: vec![rpc_account_balance],
|
||||
})
|
||||
}
|
||||
"getVoteAccounts" => {
|
||||
json!(RpcVoteAccountStatus {
|
||||
current: vec![],
|
||||
delinquent: vec![RpcVoteAccountInfo {
|
||||
vote_pubkey: PUBKEY.to_string(),
|
||||
node_pubkey: PUBKEY.to_string(),
|
||||
activated_stake: 0,
|
||||
commission: 0,
|
||||
epoch_vote_account: false,
|
||||
epoch_credits: vec![],
|
||||
last_vote: 0,
|
||||
root_slot: Slot::default(),
|
||||
}],
|
||||
})
|
||||
}
|
||||
"sendTransaction" => {
|
||||
let signature = if self.url == "malicious" {
|
||||
Signature::new(&[8; 64]).to_string()
|
||||
} else {
|
||||
@ -137,14 +322,124 @@ impl RpcSender for MockSender {
|
||||
};
|
||||
Value::String(signature)
|
||||
}
|
||||
RpcRequest::GetMinimumBalanceForRentExemption => Value::Number(Number::from(20)),
|
||||
RpcRequest::GetVersion => {
|
||||
"simulateTransaction" => serde_json::to_value(Response {
|
||||
context: RpcResponseContext { slot: 1 },
|
||||
value: RpcSimulateTransactionResult {
|
||||
err: None,
|
||||
logs: None,
|
||||
accounts: None,
|
||||
},
|
||||
})?,
|
||||
"getMinimumBalanceForRentExemption" => json![20],
|
||||
"getVersion" => {
|
||||
let version = Version::default();
|
||||
json!(RpcVersionInfo {
|
||||
solana_core: version.to_string(),
|
||||
feature_set: Some(version.feature_set),
|
||||
})
|
||||
}
|
||||
"getClusterNodes" => serde_json::to_value(vec![RpcContactInfo {
|
||||
pubkey: PUBKEY.to_string(),
|
||||
gossip: Some(SocketAddr::from(([10, 239, 6, 48], 8899))),
|
||||
tpu: Some(SocketAddr::from(([10, 239, 6, 48], 8856))),
|
||||
rpc: Some(SocketAddr::from(([10, 239, 6, 48], 8899))),
|
||||
version: Some("1.0.0 c375ce1f".to_string()),
|
||||
feature_set: None,
|
||||
shred_version: None,
|
||||
}])?,
|
||||
"getBlock" => serde_json::to_value(EncodedConfirmedBlock {
|
||||
previous_blockhash: "mfcyqEXB3DnHXki6KjjmZck6YjmZLvpAByy2fj4nh6B".to_string(),
|
||||
blockhash: "3Eq21vXNB5s86c62bVuUfTeaMif1N2kUqRPBmGRJhyTA".to_string(),
|
||||
parent_slot: 429,
|
||||
transactions: vec![EncodedTransactionWithStatusMeta {
|
||||
transaction: EncodedTransaction::Binary(
|
||||
"ju9xZWuDBX4pRxX2oZkTjxU5jB4SSTgEGhX8bQ8PURNzyzqKMPPpNvWihx8zUe\
|
||||
FfrbVNoAaEsNKZvGzAnTDy5bhNT9kt6KFCTBixpvrLCzg4M5UdFUQYrn1gdgjX\
|
||||
pLHxcaShD81xBNaFDgnA2nkkdHnKtZt4hVSfKAmw3VRZbjrZ7L2fKZBx21CwsG\
|
||||
hD6onjM2M3qZW5C8J6d1pj41MxKmZgPBSha3MyKkNLkAGFASK"
|
||||
.to_string(),
|
||||
UiTransactionEncoding::Base58,
|
||||
),
|
||||
meta: None,
|
||||
}],
|
||||
rewards: Rewards::new(),
|
||||
block_time: None,
|
||||
block_height: Some(428),
|
||||
})?,
|
||||
"getBlocks" => serde_json::to_value(vec![1, 2, 3])?,
|
||||
"getBlocksWithLimit" => serde_json::to_value(vec![1, 2, 3])?,
|
||||
"getSignaturesForAddress" => {
|
||||
serde_json::to_value(vec![RpcConfirmedTransactionStatusWithSignature {
|
||||
signature: SIGNATURE.to_string(),
|
||||
slot: 123,
|
||||
err: None,
|
||||
memo: None,
|
||||
block_time: None,
|
||||
confirmation_status: Some(TransactionConfirmationStatus::Finalized),
|
||||
}])?
|
||||
}
|
||||
"getBlockTime" => serde_json::to_value(UnixTimestamp::default())?,
|
||||
"getEpochSchedule" => serde_json::to_value(EpochSchedule::default())?,
|
||||
"getRecentPerformanceSamples" => serde_json::to_value(vec![RpcPerfSample {
|
||||
slot: 347873,
|
||||
num_transactions: 125,
|
||||
num_slots: 123,
|
||||
sample_period_secs: 60,
|
||||
}])?,
|
||||
"getIdentity" => serde_json::to_value(RpcIdentity {
|
||||
identity: PUBKEY.to_string(),
|
||||
})?,
|
||||
"getInflationGovernor" => serde_json::to_value(
|
||||
RpcInflationGovernor {
|
||||
initial: 0.08,
|
||||
terminal: 0.015,
|
||||
taper: 0.15,
|
||||
foundation: 0.05,
|
||||
foundation_term: 7.0,
|
||||
})?,
|
||||
"getInflationRate" => serde_json::to_value(
|
||||
RpcInflationRate {
|
||||
total: 0.08,
|
||||
validator: 0.076,
|
||||
foundation: 0.004,
|
||||
epoch: 0,
|
||||
})?,
|
||||
"getInflationReward" => serde_json::to_value(vec![
|
||||
Some(RpcInflationReward {
|
||||
epoch: 2,
|
||||
effective_slot: 224,
|
||||
amount: 2500,
|
||||
post_balance: 499999442500,
|
||||
commission: None,
|
||||
})])?,
|
||||
"minimumLedgerSlot" => json![123],
|
||||
"getMaxRetransmitSlot" => json![123],
|
||||
"getMultipleAccounts" => serde_json::to_value(Response {
|
||||
context: RpcResponseContext { slot: 1 },
|
||||
value: vec![Value::Null, Value::Null]
|
||||
})?,
|
||||
"getProgramAccounts" => {
|
||||
let pubkey = Pubkey::from_str(&PUBKEY.to_string()).unwrap();
|
||||
let account = Account {
|
||||
lamports: 1_000_000,
|
||||
data: vec![],
|
||||
owner: pubkey,
|
||||
executable: false,
|
||||
rent_epoch: 0,
|
||||
};
|
||||
serde_json::to_value(vec![
|
||||
RpcKeyedAccount {
|
||||
pubkey: PUBKEY.to_string(),
|
||||
account: UiAccount::encode(
|
||||
&pubkey,
|
||||
&account,
|
||||
UiAccountEncoding::Base64,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
}
|
||||
])?
|
||||
},
|
||||
_ => Value::Null,
|
||||
};
|
||||
Ok(val)
|
||||
|
@ -22,7 +22,8 @@ use {
|
||||
mpsc::{channel, Receiver},
|
||||
Arc, RwLock,
|
||||
},
|
||||
thread::JoinHandle,
|
||||
thread::{sleep, JoinHandle},
|
||||
time::Duration,
|
||||
},
|
||||
thiserror::Error,
|
||||
tungstenite::{client::AutoStream, connect, Message, WebSocket},
|
||||
@ -164,6 +165,28 @@ pub type SignatureSubscription = (
|
||||
|
||||
pub struct PubsubClient {}
|
||||
|
||||
fn connect_with_retry(url: Url) -> Result<WebSocket<AutoStream>, tungstenite::Error> {
|
||||
let mut connection_retries = 5;
|
||||
loop {
|
||||
let result = connect(url.clone()).map(|(socket, _)| socket);
|
||||
if let Err(tungstenite::Error::Http(status_code)) = &result {
|
||||
if *status_code == reqwest::StatusCode::TOO_MANY_REQUESTS && connection_retries > 0 {
|
||||
let duration = Duration::from_millis(500) * 2u32.pow(5 - connection_retries);
|
||||
|
||||
connection_retries -= 1;
|
||||
debug!(
|
||||
"Too many requests: server responded with {:?}, {} retries left, pausing for {:?}",
|
||||
status_code, connection_retries, duration
|
||||
);
|
||||
|
||||
sleep(duration);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
impl PubsubClient {
|
||||
pub fn logs_subscribe(
|
||||
url: &str,
|
||||
@ -171,7 +194,7 @@ impl PubsubClient {
|
||||
config: RpcTransactionLogsConfig,
|
||||
) -> Result<LogsSubscription, PubsubClientError> {
|
||||
let url = Url::parse(url)?;
|
||||
let (socket, _response) = connect(url)?;
|
||||
let socket = connect_with_retry(url)?;
|
||||
let (sender, receiver) = channel();
|
||||
|
||||
let socket = Arc::new(RwLock::new(socket));
|
||||
@ -226,7 +249,7 @@ impl PubsubClient {
|
||||
|
||||
pub fn slot_subscribe(url: &str) -> Result<SlotsSubscription, PubsubClientError> {
|
||||
let url = Url::parse(url)?;
|
||||
let (socket, _response) = connect(url)?;
|
||||
let socket = connect_with_retry(url)?;
|
||||
let (sender, receiver) = channel::<SlotInfo>();
|
||||
|
||||
let socket = Arc::new(RwLock::new(socket));
|
||||
@ -282,7 +305,7 @@ impl PubsubClient {
|
||||
config: Option<RpcSignatureSubscribeConfig>,
|
||||
) -> Result<SignatureSubscription, PubsubClientError> {
|
||||
let url = Url::parse(url)?;
|
||||
let (socket, _response) = connect(url)?;
|
||||
let socket = connect_with_retry(url)?;
|
||||
let (sender, receiver) = channel();
|
||||
|
||||
let socket = Arc::new(RwLock::new(socket));
|
||||
@ -348,7 +371,7 @@ impl PubsubClient {
|
||||
handler: impl Fn(SlotUpdate) + Send + 'static,
|
||||
) -> Result<PubsubClientSubscription<SlotUpdate>, PubsubClientError> {
|
||||
let url = Url::parse(url)?;
|
||||
let (socket, _response) = connect(url)?;
|
||||
let socket = connect_with_retry(url)?;
|
||||
|
||||
let socket = Arc::new(RwLock::new(socket));
|
||||
let exit = Arc::new(AtomicBool::new(false));
|
||||
|
@ -31,7 +31,7 @@ impl LargestAccountsCache {
|
||||
&self,
|
||||
filter: &Option<RpcLargestAccountsFilter>,
|
||||
) -> Option<(u64, Vec<RpcAccountBalance>)> {
|
||||
self.cache.get(&filter).and_then(|value| {
|
||||
self.cache.get(filter).and_then(|value| {
|
||||
if let Ok(elapsed) = value.cached_time.elapsed() {
|
||||
if elapsed < Duration::from_secs(self.duration) {
|
||||
return Some((value.slot, value.accounts.clone()));
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -21,6 +21,7 @@ pub struct RpcSendTransactionConfig {
|
||||
pub skip_preflight: bool,
|
||||
pub preflight_commitment: Option<CommitmentLevel>,
|
||||
pub encoding: Option<UiTransactionEncoding>,
|
||||
pub max_retries: Option<usize>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
|
||||
@ -81,6 +82,8 @@ pub struct RpcGetVoteAccountsConfig {
|
||||
pub vote_pubkey: Option<String>, // validator vote address, as a base-58 encoded string
|
||||
#[serde(flatten)]
|
||||
pub commitment: Option<CommitmentConfig>,
|
||||
pub keep_unstaked_delinquents: Option<bool>,
|
||||
pub delinquent_slot_distance: Option<u64>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
@ -114,6 +117,15 @@ pub struct RpcLargestAccountsConfig {
|
||||
pub filter: Option<RpcLargestAccountsFilter>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RpcSupplyConfig {
|
||||
#[serde(flatten)]
|
||||
pub commitment: Option<CommitmentConfig>,
|
||||
#[serde(default)]
|
||||
pub exclude_non_circulating_accounts_list: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RpcEpochConfig {
|
||||
|
@ -1,5 +1,5 @@
|
||||
//! Implementation defined RPC server errors
|
||||
|
||||
use thiserror::Error;
|
||||
use {
|
||||
crate::rpc_response::RpcSimulateTransactionResult,
|
||||
jsonrpc_core::{Error, ErrorCode},
|
||||
@ -17,35 +17,43 @@ pub const JSON_RPC_SERVER_ERROR_NO_SNAPSHOT: i64 = -32008;
|
||||
pub const JSON_RPC_SERVER_ERROR_LONG_TERM_STORAGE_SLOT_SKIPPED: i64 = -32009;
|
||||
pub const JSON_RPC_SERVER_ERROR_KEY_EXCLUDED_FROM_SECONDARY_INDEX: i64 = -32010;
|
||||
pub const JSON_RPC_SERVER_ERROR_TRANSACTION_HISTORY_NOT_AVAILABLE: i64 = -32011;
|
||||
pub const JSON_RPC_SCAN_ERROR: i64 = -32012;
|
||||
pub const JSON_RPC_SERVER_ERROR_TRANSACTION_SIGNATURE_LEN_MISMATCH: i64 = -32013;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum RpcCustomError {
|
||||
#[error("BlockCleanedUp")]
|
||||
BlockCleanedUp {
|
||||
slot: Slot,
|
||||
first_available_block: Slot,
|
||||
},
|
||||
#[error("SendTransactionPreflightFailure")]
|
||||
SendTransactionPreflightFailure {
|
||||
message: String,
|
||||
result: RpcSimulateTransactionResult,
|
||||
},
|
||||
#[error("TransactionSignatureVerificationFailure")]
|
||||
TransactionSignatureVerificationFailure,
|
||||
BlockNotAvailable {
|
||||
slot: Slot,
|
||||
},
|
||||
NodeUnhealthy {
|
||||
num_slots_behind: Option<Slot>,
|
||||
},
|
||||
#[error("BlockNotAvailable")]
|
||||
BlockNotAvailable { slot: Slot },
|
||||
#[error("NodeUnhealthy")]
|
||||
NodeUnhealthy { num_slots_behind: Option<Slot> },
|
||||
#[error("TransactionPrecompileVerificationFailure")]
|
||||
TransactionPrecompileVerificationFailure(solana_sdk::transaction::TransactionError),
|
||||
SlotSkipped {
|
||||
slot: Slot,
|
||||
},
|
||||
#[error("SlotSkipped")]
|
||||
SlotSkipped { slot: Slot },
|
||||
#[error("NoSnapshot")]
|
||||
NoSnapshot,
|
||||
LongTermStorageSlotSkipped {
|
||||
slot: Slot,
|
||||
},
|
||||
KeyExcludedFromSecondaryIndex {
|
||||
index_key: String,
|
||||
},
|
||||
#[error("LongTermStorageSlotSkipped")]
|
||||
LongTermStorageSlotSkipped { slot: Slot },
|
||||
#[error("KeyExcludedFromSecondaryIndex")]
|
||||
KeyExcludedFromSecondaryIndex { index_key: String },
|
||||
#[error("TransactionHistoryNotAvailable")]
|
||||
TransactionHistoryNotAvailable,
|
||||
#[error("ScanError")]
|
||||
ScanError { message: String },
|
||||
#[error("TransactionSignatureLenMismatch")]
|
||||
TransactionSignatureLenMismatch,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
@ -141,6 +149,18 @@ impl From<RpcCustomError> for Error {
|
||||
message: "Transaction history is not available from this node".to_string(),
|
||||
data: None,
|
||||
},
|
||||
RpcCustomError::ScanError { message } => Self {
|
||||
code: ErrorCode::ServerError(JSON_RPC_SCAN_ERROR),
|
||||
message,
|
||||
data: None,
|
||||
},
|
||||
RpcCustomError::TransactionSignatureLenMismatch => Self {
|
||||
code: ErrorCode::ServerError(
|
||||
JSON_RPC_SERVER_ERROR_TRANSACTION_SIGNATURE_LEN_MISMATCH,
|
||||
),
|
||||
message: "Transaction signature length mismatch".to_string(),
|
||||
data: None,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum RpcFilterType {
|
||||
DataSize(u64),
|
||||
@ -40,19 +40,19 @@ pub enum RpcFilterError {
|
||||
Base58DataTooLarge,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum MemcmpEncoding {
|
||||
Binary,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase", untagged)]
|
||||
pub enum MemcmpEncodedBytes {
|
||||
Binary(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
pub struct Memcmp {
|
||||
/// Data offset to begin match
|
||||
pub offset: usize,
|
||||
|
@ -86,6 +86,9 @@ pub enum RpcRequest {
|
||||
SendTransaction,
|
||||
SimulateTransaction,
|
||||
SignVote,
|
||||
Custom {
|
||||
method: &'static str,
|
||||
},
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
@ -154,6 +157,7 @@ impl fmt::Display for RpcRequest {
|
||||
RpcRequest::SendTransaction => "sendTransaction",
|
||||
RpcRequest::SimulateTransaction => "simulateTransaction",
|
||||
RpcRequest::SignVote => "signVote",
|
||||
RpcRequest::Custom { method } => method,
|
||||
};
|
||||
|
||||
write!(f, "{}", method)
|
||||
|
@ -4,6 +4,7 @@ use {
|
||||
solana_sdk::{
|
||||
clock::{Epoch, Slot, UnixTimestamp},
|
||||
fee_calculator::{FeeCalculator, FeeRateGovernor},
|
||||
hash::Hash,
|
||||
inflation::Inflation,
|
||||
transaction::{Result, TransactionError},
|
||||
},
|
||||
@ -57,6 +58,14 @@ pub struct DeprecatedRpcFees {
|
||||
pub last_valid_slot: Slot,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Fees {
|
||||
pub blockhash: Hash,
|
||||
pub fee_calculator: FeeCalculator,
|
||||
pub last_valid_block_height: u64,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RpcFeeCalculator {
|
||||
@ -394,8 +403,9 @@ pub struct RpcPerfSample {
|
||||
pub struct RpcInflationReward {
|
||||
pub epoch: Epoch,
|
||||
pub effective_slot: Slot,
|
||||
pub amount: u64, // lamports
|
||||
pub post_balance: u64, // lamports
|
||||
pub amount: u64, // lamports
|
||||
pub post_balance: u64, // lamports
|
||||
pub commission: Option<u8>, // Vote account commission when the reward was credited
|
||||
}
|
||||
|
||||
impl From<ConfirmedTransactionStatusWithSignature> for RpcConfirmedTransactionStatusWithSignature {
|
||||
|
@ -1,5 +1,35 @@
|
||||
use crate::{client_error::Result, rpc_request::RpcRequest};
|
||||
//! A transport for RPC calls.
|
||||
|
||||
use {
|
||||
crate::{client_error::Result, rpc_request::RpcRequest},
|
||||
std::time::Duration,
|
||||
};
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
pub struct RpcTransportStats {
|
||||
/// Number of RPC requests issued
|
||||
pub request_count: usize,
|
||||
|
||||
/// Total amount of time spent transacting with the RPC server
|
||||
pub elapsed_time: Duration,
|
||||
|
||||
/// Total amount of waiting time due to RPC server rate limiting
|
||||
/// (a subset of `elapsed_time`)
|
||||
pub rate_limited_time: Duration,
|
||||
}
|
||||
|
||||
/// A transport for RPC calls.
|
||||
///
|
||||
/// `RpcSender` implements the underlying transport of requests to, and
|
||||
/// responses from, a Solana node, and is used primarily by [`RpcClient`].
|
||||
///
|
||||
/// It is typically implemented by [`HttpSender`] in production, and
|
||||
/// [`MockSender`] in unit tests.
|
||||
///
|
||||
/// [`RpcClient`]: crate::rpc_client::RpcClient
|
||||
/// [`HttpSender`]: crate::http_sender::HttpSender
|
||||
/// [`MockSender`]: crate::mock_sender::MockSender
|
||||
pub trait RpcSender {
|
||||
fn send(&self, request: RpcRequest, params: serde_json::Value) -> Result<serde_json::Value>;
|
||||
fn get_transport_stats(&self) -> RpcTransportStats;
|
||||
}
|
||||
|
@ -451,7 +451,7 @@ impl SyncClient for ThinClient {
|
||||
) -> TransportResult<Option<transaction::Result<()>>> {
|
||||
let status = self
|
||||
.rpc_client()
|
||||
.get_signature_status(&signature)
|
||||
.get_signature_status(signature)
|
||||
.map_err(|err| {
|
||||
io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
@ -468,7 +468,7 @@ impl SyncClient for ThinClient {
|
||||
) -> TransportResult<Option<transaction::Result<()>>> {
|
||||
let status = self
|
||||
.rpc_client()
|
||||
.get_signature_status_with_commitment(&signature, commitment_config)
|
||||
.get_signature_status_with_commitment(signature, commitment_config)
|
||||
.map_err(|err| {
|
||||
io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
|
@ -5,7 +5,9 @@ use crate::{
|
||||
};
|
||||
use bincode::serialize;
|
||||
use log::*;
|
||||
use solana_sdk::{clock::Slot, pubkey::Pubkey, transaction::Transaction};
|
||||
use solana_sdk::{
|
||||
clock::Slot, commitment_config::CommitmentConfig, pubkey::Pubkey, transaction::Transaction,
|
||||
};
|
||||
use std::{
|
||||
collections::{HashMap, HashSet, VecDeque},
|
||||
net::{SocketAddr, UdpSocket},
|
||||
@ -121,7 +123,7 @@ struct LeaderTpuCache {
|
||||
impl LeaderTpuCache {
|
||||
fn new(rpc_client: &RpcClient, first_slot: Slot) -> Self {
|
||||
let leaders = Self::fetch_slot_leaders(rpc_client, first_slot).unwrap_or_default();
|
||||
let leader_tpu_map = Self::fetch_cluster_tpu_sockets(&rpc_client).unwrap_or_default();
|
||||
let leader_tpu_map = Self::fetch_cluster_tpu_sockets(rpc_client).unwrap_or_default();
|
||||
Self {
|
||||
first_slot,
|
||||
leaders,
|
||||
@ -242,7 +244,7 @@ struct LeaderTpuService {
|
||||
|
||||
impl LeaderTpuService {
|
||||
fn new(rpc_client: Arc<RpcClient>, websocket_url: &str, exit: Arc<AtomicBool>) -> Result<Self> {
|
||||
let start_slot = rpc_client.get_max_shred_insert_slot()?;
|
||||
let start_slot = rpc_client.get_slot_with_commitment(CommitmentConfig::processed())?;
|
||||
|
||||
let recent_slots = RecentLeaderSlots::new(start_slot);
|
||||
let leader_tpu_cache = Arc::new(RwLock::new(LeaderTpuCache::new(&rpc_client, start_slot)));
|
||||
|
@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "solana-core"
|
||||
description = "Blockchain, Rebuilt for Scale"
|
||||
version = "1.7.0"
|
||||
version = "1.7.14"
|
||||
homepage = "https://solana.com/"
|
||||
documentation = "https://docs.rs/solana-core"
|
||||
readme = "../README.md"
|
||||
@ -22,17 +22,12 @@ bv = { version = "0.11.1", features = ["serde"] }
|
||||
bs58 = "0.3.1"
|
||||
byteorder = "1.3.4"
|
||||
chrono = { version = "0.4.11", features = ["serde"] }
|
||||
core_affinity = "0.5.10"
|
||||
crossbeam-channel = "0.4"
|
||||
ed25519-dalek = "=1.0.1"
|
||||
fs_extra = "1.2.0"
|
||||
flate2 = "1.0"
|
||||
indexmap = { version = "1.5", features = ["rayon"] }
|
||||
itertools = "0.9.0"
|
||||
jsonrpc-core = "17.0.0"
|
||||
jsonrpc-core-client = { version = "17.0.0", features = ["ipc", "ws"] }
|
||||
jsonrpc-derive = "17.0.0"
|
||||
jsonrpc-http-server = "17.0.0"
|
||||
libc = "0.2.81"
|
||||
log = "0.4.11"
|
||||
lru = "0.6.1"
|
||||
@ -44,54 +39,54 @@ rand_chacha = "0.2.2"
|
||||
rand_core = "0.6.2"
|
||||
raptorq = "1.4.2"
|
||||
rayon = "1.5.0"
|
||||
regex = "1.3.9"
|
||||
retain_mut = "0.1.2"
|
||||
serde = "1.0.122"
|
||||
serde_bytes = "0.11"
|
||||
serde_derive = "1.0.103"
|
||||
serde_json = "1.0.56"
|
||||
solana-account-decoder = { path = "../account-decoder", version = "=1.7.0" }
|
||||
solana-banks-server = { path = "../banks-server", version = "=1.7.0" }
|
||||
solana-clap-utils = { path = "../clap-utils", version = "=1.7.0" }
|
||||
solana-client = { path = "../client", version = "=1.7.0" }
|
||||
solana-faucet = { path = "../faucet", version = "=1.7.0" }
|
||||
solana-gossip = { path = "../gossip", version = "=1.7.0" }
|
||||
solana-ledger = { path = "../ledger", version = "=1.7.0" }
|
||||
solana-logger = { path = "../logger", version = "=1.7.0" }
|
||||
solana-merkle-tree = { path = "../merkle-tree", version = "=1.7.0" }
|
||||
solana-metrics = { path = "../metrics", version = "=1.7.0" }
|
||||
solana-measure = { path = "../measure", version = "=1.7.0" }
|
||||
solana-net-utils = { path = "../net-utils", version = "=1.7.0" }
|
||||
solana-perf = { path = "../perf", version = "=1.7.0" }
|
||||
solana-program-test = { path = "../program-test", version = "=1.7.0" }
|
||||
solana-rpc = { path = "../rpc", version = "=1.7.0" }
|
||||
solana-runtime = { path = "../runtime", version = "=1.7.0" }
|
||||
solana-sdk = { path = "../sdk", version = "=1.7.0" }
|
||||
solana-frozen-abi = { path = "../frozen-abi", version = "=1.7.0" }
|
||||
solana-frozen-abi-macro = { path = "../frozen-abi/macro", version = "=1.7.0" }
|
||||
solana-stake-program = { path = "../programs/stake", version = "=1.7.0" }
|
||||
solana-storage-bigtable = { path = "../storage-bigtable", version = "=1.7.0" }
|
||||
solana-streamer = { path = "../streamer", version = "=1.7.0" }
|
||||
solana-sys-tuner = { path = "../sys-tuner", version = "=1.7.0" }
|
||||
solana-transaction-status = { path = "../transaction-status", version = "=1.7.0" }
|
||||
solana-version = { path = "../version", version = "=1.7.0" }
|
||||
solana-vote-program = { path = "../programs/vote", version = "=1.7.0" }
|
||||
spl-token-v2-0 = { package = "spl-token", version = "=3.1.0", features = ["no-entrypoint"] }
|
||||
solana-account-decoder = { path = "../account-decoder", version = "=1.7.14" }
|
||||
solana-banks-server = { path = "../banks-server", version = "=1.7.14" }
|
||||
solana-clap-utils = { path = "../clap-utils", version = "=1.7.14" }
|
||||
solana-client = { path = "../client", version = "=1.7.14" }
|
||||
solana-gossip = { path = "../gossip", version = "=1.7.14" }
|
||||
solana-ledger = { path = "../ledger", version = "=1.7.14" }
|
||||
solana-logger = { path = "../logger", version = "=1.7.14" }
|
||||
solana-merkle-tree = { path = "../merkle-tree", version = "=1.7.14" }
|
||||
solana-metrics = { path = "../metrics", version = "=1.7.14" }
|
||||
solana-measure = { path = "../measure", version = "=1.7.14" }
|
||||
solana-net-utils = { path = "../net-utils", version = "=1.7.14" }
|
||||
solana-perf = { path = "../perf", version = "=1.7.14" }
|
||||
solana-poh = { path = "../poh", version = "=1.7.14" }
|
||||
solana-program-test = { path = "../program-test", version = "=1.7.14" }
|
||||
solana-rpc = { path = "../rpc", version = "=1.7.14" }
|
||||
solana-runtime = { path = "../runtime", version = "=1.7.14" }
|
||||
solana-sdk = { path = "../sdk", version = "=1.7.14" }
|
||||
solana-frozen-abi = { path = "../frozen-abi", version = "=1.7.14" }
|
||||
solana-frozen-abi-macro = { path = "../frozen-abi/macro", version = "=1.7.14" }
|
||||
solana-streamer = { path = "../streamer", version = "=1.7.14" }
|
||||
solana-transaction-status = { path = "../transaction-status", version = "=1.7.14" }
|
||||
solana-version = { path = "../version", version = "=1.7.14" }
|
||||
solana-vote-program = { path = "../programs/vote", version = "=1.7.14" }
|
||||
spl-token-v2-0 = { package = "spl-token", version = "=3.2.0", features = ["no-entrypoint"] }
|
||||
tempfile = "3.1.0"
|
||||
thiserror = "1.0"
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
tokio_02 = { version = "0.2", package = "tokio", features = ["full"] }
|
||||
tokio-util = { version = "0.3", features = ["codec"] } # This crate needs to stay in sync with tokio_02, until that dependency can be removed
|
||||
solana-rayon-threadlimit = { path = "../rayon-threadlimit", version = "=1.7.0" }
|
||||
solana-rayon-threadlimit = { path = "../rayon-threadlimit", version = "=1.7.14" }
|
||||
trees = "0.2.1"
|
||||
|
||||
[dev-dependencies]
|
||||
jsonrpc-core = "18.0.0"
|
||||
jsonrpc-core-client = { version = "18.0.0", features = ["ipc", "ws"] }
|
||||
jsonrpc-derive = "18.0.0"
|
||||
jsonrpc-pubsub = "18.0.0"
|
||||
matches = "0.1.6"
|
||||
num_cpus = "1.13.0"
|
||||
reqwest = { version = "0.11.2", default-features = false, features = ["blocking", "rustls-tls", "json"] }
|
||||
serde_json = "1.0.56"
|
||||
serial_test = "0.4.0"
|
||||
solana-stake-program = { path = "../programs/stake", version = "=1.7.14" }
|
||||
solana-version = { path = "../version", version = "=1.7.14" }
|
||||
symlink = "0.1.0"
|
||||
systemstat = "0.1.5"
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
|
||||
[build-dependencies]
|
||||
rustc_version = "0.2"
|
||||
@ -111,9 +106,6 @@ name = "gen_keys"
|
||||
[[bench]]
|
||||
name = "sigverify_stage"
|
||||
|
||||
[[bench]]
|
||||
name = "poh"
|
||||
|
||||
[[bench]]
|
||||
name = "retransmit_stage"
|
||||
|
||||
|
@ -7,8 +7,7 @@ use crossbeam_channel::unbounded;
|
||||
use log::*;
|
||||
use rand::{thread_rng, Rng};
|
||||
use rayon::prelude::*;
|
||||
use solana_core::banking_stage::{create_test_recorder, BankingStage, BankingStageStats};
|
||||
use solana_core::poh_recorder::WorkingBankEntry;
|
||||
use solana_core::banking_stage::{BankingStage, BankingStageStats};
|
||||
use solana_gossip::cluster_info::ClusterInfo;
|
||||
use solana_gossip::cluster_info::Node;
|
||||
use solana_ledger::blockstore_processor::process_entries;
|
||||
@ -17,6 +16,7 @@ use solana_ledger::genesis_utils::{create_genesis_config, GenesisConfigInfo};
|
||||
use solana_ledger::{blockstore::Blockstore, get_tmp_ledger_path};
|
||||
use solana_perf::packet::to_packets_chunked;
|
||||
use solana_perf::test_tx::test_tx;
|
||||
use solana_poh::poh_recorder::{create_test_recorder, WorkingBankEntry};
|
||||
use solana_runtime::bank::Bank;
|
||||
use solana_sdk::genesis_config::GenesisConfig;
|
||||
use solana_sdk::hash::Hash;
|
||||
@ -29,6 +29,7 @@ use solana_sdk::system_instruction;
|
||||
use solana_sdk::system_transaction;
|
||||
use solana_sdk::timing::{duration_as_us, timestamp};
|
||||
use solana_sdk::transaction::Transaction;
|
||||
use solana_streamer::socket::SocketAddrSpace;
|
||||
use std::collections::VecDeque;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::sync::mpsc::Receiver;
|
||||
@ -107,7 +108,7 @@ fn make_accounts_txs(txes: usize, mint_keypair: &Keypair, hash: Hash) -> Vec<Tra
|
||||
.into_par_iter()
|
||||
.map(|_| {
|
||||
let mut new = dummy.clone();
|
||||
let sig: Vec<u8> = (0..64).map(|_| thread_rng().gen()).collect();
|
||||
let sig: Vec<_> = (0..64).map(|_| thread_rng().gen::<u8>()).collect();
|
||||
new.message.account_keys[0] = pubkey::new_rand();
|
||||
new.message.account_keys[1] = pubkey::new_rand();
|
||||
new.signatures = vec![Signature::new(&sig[0..64])];
|
||||
@ -157,6 +158,7 @@ fn bench_banking(bencher: &mut Bencher, tx_type: TransactionType) {
|
||||
genesis_config.ticks_per_slot = 10_000;
|
||||
|
||||
let (verified_sender, verified_receiver) = unbounded();
|
||||
let (tpu_vote_sender, tpu_vote_receiver) = unbounded();
|
||||
let (vote_sender, vote_receiver) = unbounded();
|
||||
let mut bank = Bank::new(&genesis_config);
|
||||
// Allow arbitrary transaction processing time for the purposes of this bench
|
||||
@ -183,7 +185,7 @@ fn bench_banking(bencher: &mut Bencher, tx_type: TransactionType) {
|
||||
});
|
||||
//sanity check, make sure all the transactions can execute sequentially
|
||||
transactions.iter().for_each(|tx| {
|
||||
let res = bank.process_transaction(&tx);
|
||||
let res = bank.process_transaction(tx);
|
||||
assert!(res.is_ok(), "sanity test transactions");
|
||||
});
|
||||
bank.clear_signatures();
|
||||
@ -201,13 +203,18 @@ fn bench_banking(bencher: &mut Bencher, tx_type: TransactionType) {
|
||||
);
|
||||
let (exit, poh_recorder, poh_service, signal_receiver) =
|
||||
create_test_recorder(&bank, &blockstore, None);
|
||||
let cluster_info = ClusterInfo::new_with_invalid_keypair(Node::new_localhost().info);
|
||||
let cluster_info = ClusterInfo::new(
|
||||
Node::new_localhost().info,
|
||||
Arc::new(Keypair::new()),
|
||||
SocketAddrSpace::Unspecified,
|
||||
);
|
||||
let cluster_info = Arc::new(cluster_info);
|
||||
let (s, _r) = unbounded();
|
||||
let _banking_stage = BankingStage::new(
|
||||
&cluster_info,
|
||||
&poh_recorder,
|
||||
verified_receiver,
|
||||
tpu_vote_receiver,
|
||||
vote_receiver,
|
||||
None,
|
||||
s,
|
||||
@ -254,6 +261,7 @@ fn bench_banking(bencher: &mut Bencher, tx_type: TransactionType) {
|
||||
start += chunk_len;
|
||||
start %= verified.len();
|
||||
});
|
||||
drop(tpu_vote_sender);
|
||||
drop(vote_sender);
|
||||
exit.store(true, Ordering::Relaxed);
|
||||
poh_service.join().unwrap();
|
||||
|
@ -2,29 +2,54 @@
|
||||
|
||||
extern crate test;
|
||||
|
||||
use rand::{thread_rng, Rng};
|
||||
use solana_core::broadcast_stage::broadcast_metrics::TransmitShredsStats;
|
||||
use solana_core::broadcast_stage::{broadcast_shreds, get_broadcast_peers};
|
||||
use solana_gossip::cluster_info::{ClusterInfo, Node};
|
||||
use solana_gossip::contact_info::ContactInfo;
|
||||
use solana_ledger::shred::Shred;
|
||||
use solana_sdk::pubkey;
|
||||
use solana_sdk::timing::timestamp;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
net::UdpSocket,
|
||||
sync::{atomic::AtomicU64, Arc},
|
||||
use {
|
||||
rand::{thread_rng, Rng},
|
||||
solana_core::{
|
||||
broadcast_stage::{
|
||||
broadcast_metrics::TransmitShredsStats, broadcast_shreds, BroadcastStage,
|
||||
},
|
||||
cluster_nodes::ClusterNodesCache,
|
||||
},
|
||||
solana_gossip::{
|
||||
cluster_info::{ClusterInfo, Node},
|
||||
contact_info::ContactInfo,
|
||||
},
|
||||
solana_ledger::{
|
||||
genesis_utils::{create_genesis_config, GenesisConfigInfo},
|
||||
shred::Shred,
|
||||
},
|
||||
solana_runtime::{bank::Bank, bank_forks::BankForks},
|
||||
solana_sdk::{
|
||||
pubkey,
|
||||
signature::Keypair,
|
||||
timing::{timestamp, AtomicInterval},
|
||||
},
|
||||
solana_streamer::socket::SocketAddrSpace,
|
||||
std::{
|
||||
collections::HashMap,
|
||||
net::UdpSocket,
|
||||
sync::{Arc, RwLock},
|
||||
time::Duration,
|
||||
},
|
||||
test::Bencher,
|
||||
};
|
||||
use test::Bencher;
|
||||
|
||||
#[bench]
|
||||
fn broadcast_shreds_bench(bencher: &mut Bencher) {
|
||||
solana_logger::setup();
|
||||
let leader_pubkey = pubkey::new_rand();
|
||||
let leader_info = Node::new_localhost_with_pubkey(&leader_pubkey);
|
||||
let cluster_info = ClusterInfo::new_with_invalid_keypair(leader_info.info);
|
||||
let cluster_info = ClusterInfo::new(
|
||||
leader_info.info,
|
||||
Arc::new(Keypair::new()),
|
||||
SocketAddrSpace::Unspecified,
|
||||
);
|
||||
let socket = UdpSocket::bind("0.0.0.0:0").unwrap();
|
||||
|
||||
let GenesisConfigInfo { genesis_config, .. } = create_genesis_config(10_000);
|
||||
let bank = Bank::new(&genesis_config);
|
||||
let bank_forks = Arc::new(RwLock::new(BankForks::new(bank)));
|
||||
|
||||
const NUM_SHREDS: usize = 32;
|
||||
let shreds = vec![Shred::new_empty_data_shred(); NUM_SHREDS];
|
||||
let mut stakes = HashMap::new();
|
||||
@ -36,18 +61,23 @@ fn broadcast_shreds_bench(bencher: &mut Bencher) {
|
||||
stakes.insert(id, thread_rng().gen_range(1, NUM_PEERS) as u64);
|
||||
}
|
||||
let cluster_info = Arc::new(cluster_info);
|
||||
let (peers, peers_and_stakes) = get_broadcast_peers(&cluster_info, Some(&stakes));
|
||||
let cluster_nodes_cache = ClusterNodesCache::<BroadcastStage>::new(
|
||||
8, // cap
|
||||
Duration::from_secs(5), // ttl
|
||||
);
|
||||
let shreds = Arc::new(shreds);
|
||||
let last_datapoint = Arc::new(AtomicU64::new(0));
|
||||
let last_datapoint = Arc::new(AtomicInterval::default());
|
||||
bencher.iter(move || {
|
||||
let shreds = shreds.clone();
|
||||
broadcast_shreds(
|
||||
&socket,
|
||||
&shreds,
|
||||
&peers_and_stakes,
|
||||
&peers,
|
||||
&cluster_nodes_cache,
|
||||
&last_datapoint,
|
||||
&mut TransmitShredsStats::default(),
|
||||
&SocketAddrSpace::Unspecified,
|
||||
&cluster_info,
|
||||
&bank_forks,
|
||||
)
|
||||
.unwrap();
|
||||
});
|
||||
|
@ -24,10 +24,10 @@ fn bench_save_tower(bench: &mut Bencher) {
|
||||
let heaviest_bank = BankForks::new(Bank::default()).working_bank();
|
||||
let tower = Tower::new(
|
||||
&node_keypair.pubkey(),
|
||||
&vote_account_pubkey,
|
||||
vote_account_pubkey,
|
||||
0,
|
||||
&heaviest_bank,
|
||||
&path,
|
||||
path,
|
||||
);
|
||||
|
||||
bench.iter(move || {
|
||||
|
@ -3,43 +3,62 @@
|
||||
extern crate solana_core;
|
||||
extern crate test;
|
||||
|
||||
use log::*;
|
||||
use solana_core::retransmit_stage::retransmitter;
|
||||
use solana_gossip::cluster_info::{ClusterInfo, Node};
|
||||
use solana_gossip::contact_info::ContactInfo;
|
||||
use solana_ledger::entry::Entry;
|
||||
use solana_ledger::genesis_utils::{create_genesis_config, GenesisConfigInfo};
|
||||
use solana_ledger::leader_schedule_cache::LeaderScheduleCache;
|
||||
use solana_ledger::shred::Shredder;
|
||||
use solana_measure::measure::Measure;
|
||||
use solana_perf::packet::{Packet, Packets};
|
||||
use solana_rpc::max_slots::MaxSlots;
|
||||
use solana_runtime::bank::Bank;
|
||||
use solana_runtime::bank_forks::BankForks;
|
||||
use solana_sdk::hash::Hash;
|
||||
use solana_sdk::pubkey;
|
||||
use solana_sdk::signature::{Keypair, Signer};
|
||||
use solana_sdk::system_transaction;
|
||||
use solana_sdk::timing::timestamp;
|
||||
use std::net::UdpSocket;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::sync::mpsc::channel;
|
||||
use std::sync::Mutex;
|
||||
use std::sync::{Arc, RwLock};
|
||||
use std::thread::sleep;
|
||||
use std::thread::Builder;
|
||||
use std::time::Duration;
|
||||
use test::Bencher;
|
||||
use {
|
||||
log::*,
|
||||
solana_core::retransmit_stage::retransmitter,
|
||||
solana_gossip::{
|
||||
cluster_info::{ClusterInfo, Node},
|
||||
contact_info::ContactInfo,
|
||||
},
|
||||
solana_ledger::{
|
||||
entry::Entry,
|
||||
genesis_utils::{create_genesis_config, GenesisConfigInfo},
|
||||
leader_schedule_cache::LeaderScheduleCache,
|
||||
shred::Shredder,
|
||||
},
|
||||
solana_measure::measure::Measure,
|
||||
solana_runtime::{bank::Bank, bank_forks::BankForks},
|
||||
solana_sdk::{
|
||||
hash::Hash,
|
||||
pubkey::Pubkey,
|
||||
signature::{Keypair, Signer},
|
||||
system_transaction,
|
||||
timing::timestamp,
|
||||
},
|
||||
solana_streamer::socket::SocketAddrSpace,
|
||||
std::{
|
||||
net::UdpSocket,
|
||||
sync::{
|
||||
atomic::{AtomicUsize, Ordering},
|
||||
mpsc::channel,
|
||||
Arc, RwLock,
|
||||
},
|
||||
thread::{sleep, Builder},
|
||||
time::Duration,
|
||||
},
|
||||
test::Bencher,
|
||||
};
|
||||
|
||||
// TODO: The benchmark is ignored as it currently may indefinitely block.
|
||||
// The code incorrectly expects that the node receiving the shred on tvu socket
|
||||
// retransmits that to other nodes in its neighborhood. But that is no longer
|
||||
// the case since https://github.com/solana-labs/solana/pull/17716.
|
||||
// So depending on shred seed, peers may not receive packets and the receive
|
||||
// threads loop indefinitely.
|
||||
#[ignore]
|
||||
#[bench]
|
||||
#[allow(clippy::same_item_push)]
|
||||
fn bench_retransmitter(bencher: &mut Bencher) {
|
||||
solana_logger::setup();
|
||||
let cluster_info = ClusterInfo::new_with_invalid_keypair(Node::new_localhost().info);
|
||||
let cluster_info = ClusterInfo::new(
|
||||
Node::new_localhost().info,
|
||||
Arc::new(Keypair::new()),
|
||||
SocketAddrSpace::Unspecified,
|
||||
);
|
||||
const NUM_PEERS: usize = 4;
|
||||
let mut peer_sockets = Vec::new();
|
||||
for _ in 0..NUM_PEERS {
|
||||
let id = pubkey::new_rand();
|
||||
let id = Pubkey::new_unique();
|
||||
let socket = UdpSocket::bind("0.0.0.0:0").unwrap();
|
||||
let mut contact_info = ContactInfo::new_localhost(&id, timestamp());
|
||||
contact_info.tvu = socket.local_addr().unwrap();
|
||||
@ -58,8 +77,7 @@ fn bench_retransmitter(bencher: &mut Bencher) {
|
||||
let bank_forks = BankForks::new(bank0);
|
||||
let bank = bank_forks.working_bank();
|
||||
let bank_forks = Arc::new(RwLock::new(bank_forks));
|
||||
let (packet_sender, packet_receiver) = channel();
|
||||
let packet_receiver = Arc::new(Mutex::new(packet_receiver));
|
||||
let (shreds_sender, shreds_receiver) = channel();
|
||||
const NUM_THREADS: usize = 2;
|
||||
let sockets = (0..NUM_THREADS)
|
||||
.map(|_| UdpSocket::bind("0.0.0.0:0").unwrap())
|
||||
@ -89,10 +107,10 @@ fn bench_retransmitter(bencher: &mut Bencher) {
|
||||
let retransmitter_handles = retransmitter(
|
||||
Arc::new(sockets),
|
||||
bank_forks,
|
||||
&leader_schedule_cache,
|
||||
leader_schedule_cache,
|
||||
cluster_info,
|
||||
packet_receiver,
|
||||
&Arc::new(MaxSlots::default()),
|
||||
shreds_receiver,
|
||||
Arc::default(), // solana_rpc::max_slots::MaxSlots
|
||||
None,
|
||||
);
|
||||
|
||||
@ -130,9 +148,7 @@ fn bench_retransmitter(bencher: &mut Bencher) {
|
||||
shred.set_index(index);
|
||||
index += 1;
|
||||
index %= 200;
|
||||
let mut p = Packet::default();
|
||||
shred.copy_to_packet(&mut p);
|
||||
let _ = packet_sender.send(Packets::new(vec![p]));
|
||||
let _ = shreds_sender.send(vec![shred.clone()]);
|
||||
}
|
||||
slot += 1;
|
||||
|
||||
@ -148,7 +164,5 @@ fn bench_retransmitter(bencher: &mut Bencher) {
|
||||
total.store(0, Ordering::Relaxed);
|
||||
});
|
||||
|
||||
for t in retransmitter_handles {
|
||||
t.join().unwrap();
|
||||
}
|
||||
retransmitter_handles.join().unwrap();
|
||||
}
|
||||
|
@ -18,6 +18,44 @@ use std::sync::mpsc::channel;
|
||||
use std::time::{Duration, Instant};
|
||||
use test::Bencher;
|
||||
|
||||
#[bench]
|
||||
fn bench_packet_discard(bencher: &mut Bencher) {
|
||||
solana_logger::setup();
|
||||
let len = 30 * 1000;
|
||||
let chunk_size = 1024;
|
||||
let tx = test_tx();
|
||||
let mut batches = to_packets_chunked(&vec![tx; len], chunk_size);
|
||||
|
||||
let mut total = 0;
|
||||
|
||||
let ips: Vec<_> = (0..10_000)
|
||||
.into_iter()
|
||||
.map(|_| {
|
||||
let mut addr = [0u16; 8];
|
||||
thread_rng().fill(&mut addr);
|
||||
addr
|
||||
})
|
||||
.collect();
|
||||
|
||||
for batch in batches.iter_mut() {
|
||||
total += batch.packets.len();
|
||||
for p in batch.packets.iter_mut() {
|
||||
let ip_index = thread_rng().gen_range(0, ips.len());
|
||||
p.meta.addr = ips[ip_index];
|
||||
}
|
||||
}
|
||||
info!("total packets: {}", total);
|
||||
|
||||
bencher.iter(move || {
|
||||
SigVerifyStage::discard_excess_packets(&mut batches, 10_000);
|
||||
for batch in batches.iter_mut() {
|
||||
for p in batch.packets.iter_mut() {
|
||||
p.meta.discard = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn bench_sigverify_stage(bencher: &mut Bencher) {
|
||||
solana_logger::setup();
|
||||
|
@ -1,7 +1,7 @@
|
||||
// Service to verify accounts hashes with other trusted validator nodes.
|
||||
//
|
||||
// Each interval, publish the snapshat hash which is the full accounts state
|
||||
// hash on gossip. Monitor gossip for messages from validators in the --trusted-validators
|
||||
// hash on gossip. Monitor gossip for messages from validators in the `--known-validator`s
|
||||
// set and halt the node if a mismatch is detected.
|
||||
|
||||
use crate::snapshot_packager_service::PendingSnapshotPackage;
|
||||
@ -148,7 +148,7 @@ impl AccountsHashVerifier {
|
||||
for (slot, hash) in hashes.iter() {
|
||||
slot_to_hash.insert(*slot, *hash);
|
||||
}
|
||||
if Self::should_halt(&cluster_info, trusted_validators, &mut slot_to_hash) {
|
||||
if Self::should_halt(cluster_info, trusted_validators, &mut slot_to_hash) {
|
||||
exit.store(true, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
@ -223,13 +223,22 @@ mod tests {
|
||||
hash::hash,
|
||||
signature::{Keypair, Signer},
|
||||
};
|
||||
use solana_streamer::socket::SocketAddrSpace;
|
||||
|
||||
fn new_test_cluster_info(contact_info: ContactInfo) -> ClusterInfo {
|
||||
ClusterInfo::new(
|
||||
contact_info,
|
||||
Arc::new(Keypair::new()),
|
||||
SocketAddrSpace::Unspecified,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_should_halt() {
|
||||
let keypair = Keypair::new();
|
||||
|
||||
let contact_info = ContactInfo::new_localhost(&keypair.pubkey(), 0);
|
||||
let cluster_info = ClusterInfo::new_with_invalid_keypair(contact_info);
|
||||
let cluster_info = new_test_cluster_info(contact_info);
|
||||
let cluster_info = Arc::new(cluster_info);
|
||||
|
||||
let mut trusted_validators = HashSet::new();
|
||||
@ -265,7 +274,7 @@ mod tests {
|
||||
let keypair = Keypair::new();
|
||||
|
||||
let contact_info = ContactInfo::new_localhost(&keypair.pubkey(), 0);
|
||||
let cluster_info = ClusterInfo::new_with_invalid_keypair(contact_info);
|
||||
let cluster_info = new_test_cluster_info(contact_info);
|
||||
let cluster_info = Arc::new(cluster_info);
|
||||
|
||||
let trusted_validators = HashSet::new();
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,67 +1,83 @@
|
||||
//! A stage to broadcast data from a leader node to validators
|
||||
#![allow(clippy::rc_buffer)]
|
||||
use self::{
|
||||
broadcast_fake_shreds_run::BroadcastFakeShredsRun, broadcast_metrics::*,
|
||||
fail_entry_verification_broadcast_run::FailEntryVerificationBroadcastRun,
|
||||
standard_broadcast_run::StandardBroadcastRun,
|
||||
};
|
||||
use crate::{
|
||||
poh_recorder::WorkingBankEntry,
|
||||
result::{Error, Result},
|
||||
};
|
||||
use crossbeam_channel::{
|
||||
Receiver as CrossbeamReceiver, RecvTimeoutError as CrossbeamRecvTimeoutError,
|
||||
Sender as CrossbeamSender,
|
||||
};
|
||||
use solana_gossip::{
|
||||
cluster_info::{self, ClusterInfo, ClusterInfoError},
|
||||
contact_info::ContactInfo,
|
||||
crds_gossip_pull::CRDS_GOSSIP_PULL_CRDS_TIMEOUT_MS,
|
||||
weighted_shuffle::weighted_best,
|
||||
};
|
||||
use solana_ledger::{blockstore::Blockstore, shred::Shred};
|
||||
use solana_measure::measure::Measure;
|
||||
use solana_metrics::{inc_new_counter_error, inc_new_counter_info};
|
||||
use solana_runtime::bank::Bank;
|
||||
use solana_sdk::timing::timestamp;
|
||||
use solana_sdk::{clock::Slot, pubkey::Pubkey};
|
||||
use solana_streamer::sendmmsg::send_mmsg;
|
||||
use std::sync::atomic::AtomicU64;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
net::UdpSocket,
|
||||
sync::atomic::{AtomicBool, Ordering},
|
||||
sync::mpsc::{channel, Receiver, RecvError, RecvTimeoutError, Sender},
|
||||
sync::{Arc, Mutex},
|
||||
thread::{self, Builder, JoinHandle},
|
||||
time::{Duration, Instant},
|
||||
use {
|
||||
self::{
|
||||
broadcast_duplicates_run::BroadcastDuplicatesRun,
|
||||
broadcast_fake_shreds_run::BroadcastFakeShredsRun, broadcast_metrics::*,
|
||||
fail_entry_verification_broadcast_run::FailEntryVerificationBroadcastRun,
|
||||
standard_broadcast_run::StandardBroadcastRun,
|
||||
},
|
||||
crate::{
|
||||
cluster_nodes::{ClusterNodes, ClusterNodesCache},
|
||||
result::{Error, Result},
|
||||
},
|
||||
crossbeam_channel::{
|
||||
Receiver as CrossbeamReceiver, RecvTimeoutError as CrossbeamRecvTimeoutError,
|
||||
Sender as CrossbeamSender,
|
||||
},
|
||||
itertools::Itertools,
|
||||
solana_gossip::cluster_info::{ClusterInfo, ClusterInfoError},
|
||||
solana_ledger::{blockstore::Blockstore, shred::Shred},
|
||||
solana_measure::measure::Measure,
|
||||
solana_metrics::{inc_new_counter_error, inc_new_counter_info},
|
||||
solana_poh::poh_recorder::WorkingBankEntry,
|
||||
solana_runtime::{bank::Bank, bank_forks::BankForks},
|
||||
solana_sdk::timing::{timestamp, AtomicInterval},
|
||||
solana_sdk::{clock::Slot, pubkey::Pubkey},
|
||||
solana_streamer::{
|
||||
sendmmsg::{batch_send, SendPktsError},
|
||||
socket::SocketAddrSpace,
|
||||
},
|
||||
std::{
|
||||
collections::HashMap,
|
||||
net::UdpSocket,
|
||||
sync::atomic::{AtomicBool, Ordering},
|
||||
sync::mpsc::{channel, Receiver, RecvError, RecvTimeoutError, Sender},
|
||||
sync::{Arc, Mutex, RwLock},
|
||||
thread::{self, Builder, JoinHandle},
|
||||
time::{Duration, Instant},
|
||||
},
|
||||
};
|
||||
|
||||
mod broadcast_duplicates_run;
|
||||
mod broadcast_fake_shreds_run;
|
||||
pub mod broadcast_metrics;
|
||||
pub(crate) mod broadcast_utils;
|
||||
mod fail_entry_verification_broadcast_run;
|
||||
mod standard_broadcast_run;
|
||||
|
||||
const CLUSTER_NODES_CACHE_NUM_EPOCH_CAP: usize = 8;
|
||||
const CLUSTER_NODES_CACHE_TTL: Duration = Duration::from_secs(5);
|
||||
|
||||
pub(crate) const NUM_INSERT_THREADS: usize = 2;
|
||||
pub(crate) type RetransmitSlotsSender = CrossbeamSender<HashMap<Slot, Arc<Bank>>>;
|
||||
pub(crate) type RetransmitSlotsReceiver = CrossbeamReceiver<HashMap<Slot, Arc<Bank>>>;
|
||||
pub(crate) type RecordReceiver = Receiver<(Arc<Vec<Shred>>, Option<BroadcastShredBatchInfo>)>;
|
||||
pub(crate) type TransmitReceiver = Receiver<(TransmitShreds, Option<BroadcastShredBatchInfo>)>;
|
||||
pub(crate) type TransmitReceiver = Receiver<(Arc<Vec<Shred>>, Option<BroadcastShredBatchInfo>)>;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
pub enum BroadcastStageReturnType {
|
||||
ChannelDisconnected,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Debug)]
|
||||
pub struct BroadcastDuplicatesConfig {
|
||||
/// Percentage of stake to send different version of slots to
|
||||
pub stake_partition: u8,
|
||||
/// Number of slots to wait before sending duplicate shreds
|
||||
pub duplicate_send_delay: usize,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Debug)]
|
||||
pub enum BroadcastStageType {
|
||||
Standard,
|
||||
FailEntryVerification,
|
||||
BroadcastFakeShreds,
|
||||
BroadcastDuplicates(BroadcastDuplicatesConfig),
|
||||
}
|
||||
|
||||
impl BroadcastStageType {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new_broadcast_stage(
|
||||
&self,
|
||||
sock: Vec<UdpSocket>,
|
||||
@ -70,6 +86,7 @@ impl BroadcastStageType {
|
||||
retransmit_slots_receiver: RetransmitSlotsReceiver,
|
||||
exit_sender: &Arc<AtomicBool>,
|
||||
blockstore: &Arc<Blockstore>,
|
||||
bank_forks: &Arc<RwLock<BankForks>>,
|
||||
shred_version: u16,
|
||||
) -> BroadcastStage {
|
||||
let keypair = cluster_info.keypair.clone();
|
||||
@ -81,6 +98,7 @@ impl BroadcastStageType {
|
||||
retransmit_slots_receiver,
|
||||
exit_sender,
|
||||
blockstore,
|
||||
bank_forks,
|
||||
StandardBroadcastRun::new(keypair, shred_version),
|
||||
),
|
||||
|
||||
@ -91,6 +109,7 @@ impl BroadcastStageType {
|
||||
retransmit_slots_receiver,
|
||||
exit_sender,
|
||||
blockstore,
|
||||
bank_forks,
|
||||
FailEntryVerificationBroadcastRun::new(keypair, shred_version),
|
||||
),
|
||||
|
||||
@ -101,19 +120,30 @@ impl BroadcastStageType {
|
||||
retransmit_slots_receiver,
|
||||
exit_sender,
|
||||
blockstore,
|
||||
bank_forks,
|
||||
BroadcastFakeShredsRun::new(keypair, 0, shred_version),
|
||||
),
|
||||
|
||||
BroadcastStageType::BroadcastDuplicates(config) => BroadcastStage::new(
|
||||
sock,
|
||||
cluster_info,
|
||||
receiver,
|
||||
retransmit_slots_receiver,
|
||||
exit_sender,
|
||||
blockstore,
|
||||
bank_forks,
|
||||
BroadcastDuplicatesRun::new(keypair, shred_version, config.clone()),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub type TransmitShreds = (Option<Arc<HashMap<Pubkey, u64>>>, Arc<Vec<Shred>>);
|
||||
trait BroadcastRun {
|
||||
fn run(
|
||||
&mut self,
|
||||
blockstore: &Arc<Blockstore>,
|
||||
receiver: &Receiver<WorkingBankEntry>,
|
||||
socket_sender: &Sender<(TransmitShreds, Option<BroadcastShredBatchInfo>)>,
|
||||
socket_sender: &Sender<(Arc<Vec<Shred>>, Option<BroadcastShredBatchInfo>)>,
|
||||
blockstore_sender: &Sender<(Arc<Vec<Shred>>, Option<BroadcastShredBatchInfo>)>,
|
||||
) -> Result<()>;
|
||||
fn transmit(
|
||||
@ -121,6 +151,7 @@ trait BroadcastRun {
|
||||
receiver: &Arc<Mutex<TransmitReceiver>>,
|
||||
cluster_info: &ClusterInfo,
|
||||
sock: &UdpSocket,
|
||||
bank_forks: &Arc<RwLock<BankForks>>,
|
||||
) -> Result<()>;
|
||||
fn record(
|
||||
&mut self,
|
||||
@ -156,7 +187,7 @@ impl BroadcastStage {
|
||||
fn run(
|
||||
blockstore: &Arc<Blockstore>,
|
||||
receiver: &Receiver<WorkingBankEntry>,
|
||||
socket_sender: &Sender<(TransmitShreds, Option<BroadcastShredBatchInfo>)>,
|
||||
socket_sender: &Sender<(Arc<Vec<Shred>>, Option<BroadcastShredBatchInfo>)>,
|
||||
blockstore_sender: &Sender<(Arc<Vec<Shred>>, Option<BroadcastShredBatchInfo>)>,
|
||||
mut broadcast_stage_run: impl BroadcastRun,
|
||||
) -> BroadcastStageReturnType {
|
||||
@ -172,15 +203,15 @@ impl BroadcastStage {
|
||||
fn handle_error(r: Result<()>, name: &str) -> Option<BroadcastStageReturnType> {
|
||||
if let Err(e) = r {
|
||||
match e {
|
||||
Error::RecvTimeoutError(RecvTimeoutError::Disconnected)
|
||||
| Error::SendError
|
||||
| Error::RecvError(RecvError)
|
||||
| Error::CrossbeamRecvTimeoutError(CrossbeamRecvTimeoutError::Disconnected) => {
|
||||
Error::RecvTimeout(RecvTimeoutError::Disconnected)
|
||||
| Error::Send
|
||||
| Error::Recv(RecvError)
|
||||
| Error::CrossbeamRecvTimeout(CrossbeamRecvTimeoutError::Disconnected) => {
|
||||
return Some(BroadcastStageReturnType::ChannelDisconnected);
|
||||
}
|
||||
Error::RecvTimeoutError(RecvTimeoutError::Timeout)
|
||||
| Error::CrossbeamRecvTimeoutError(CrossbeamRecvTimeoutError::Timeout) => (),
|
||||
Error::ClusterInfoError(ClusterInfoError::NoPeers) => (), // TODO: Why are the unit-tests throwing hundreds of these?
|
||||
Error::RecvTimeout(RecvTimeoutError::Timeout)
|
||||
| Error::CrossbeamRecvTimeout(CrossbeamRecvTimeoutError::Timeout) => (),
|
||||
Error::ClusterInfo(ClusterInfoError::NoPeers) => (), // TODO: Why are the unit-tests throwing hundreds of these?
|
||||
_ => {
|
||||
inc_new_counter_error!("streamer-broadcaster-error", 1, 1);
|
||||
error!("{} broadcaster error: {:?}", name, e);
|
||||
@ -214,6 +245,7 @@ impl BroadcastStage {
|
||||
retransmit_slots_receiver: RetransmitSlotsReceiver,
|
||||
exit_sender: &Arc<AtomicBool>,
|
||||
blockstore: &Arc<Blockstore>,
|
||||
bank_forks: &Arc<RwLock<BankForks>>,
|
||||
broadcast_stage_run: impl BroadcastRun + Send + 'static + Clone,
|
||||
) -> Self {
|
||||
let btree = blockstore.clone();
|
||||
@ -242,10 +274,12 @@ impl BroadcastStage {
|
||||
let socket_receiver = socket_receiver.clone();
|
||||
let mut bs_transmit = broadcast_stage_run.clone();
|
||||
let cluster_info = cluster_info.clone();
|
||||
let bank_forks = bank_forks.clone();
|
||||
let t = Builder::new()
|
||||
.name("solana-broadcaster-transmit".to_string())
|
||||
.spawn(move || loop {
|
||||
let res = bs_transmit.transmit(&socket_receiver, &cluster_info, &sock);
|
||||
let res =
|
||||
bs_transmit.transmit(&socket_receiver, &cluster_info, &sock, &bank_forks);
|
||||
let res = Self::handle_error(res, "solana-broadcaster-transmit");
|
||||
if let Some(res) = res {
|
||||
return res;
|
||||
@ -296,7 +330,7 @@ impl BroadcastStage {
|
||||
fn check_retransmit_signals(
|
||||
blockstore: &Blockstore,
|
||||
retransmit_slots_receiver: &RetransmitSlotsReceiver,
|
||||
socket_sender: &Sender<(TransmitShreds, Option<BroadcastShredBatchInfo>)>,
|
||||
socket_sender: &Sender<(Arc<Vec<Shred>>, Option<BroadcastShredBatchInfo>)>,
|
||||
) -> Result<()> {
|
||||
let timer = Duration::from_millis(100);
|
||||
|
||||
@ -307,27 +341,26 @@ impl BroadcastStage {
|
||||
}
|
||||
|
||||
for (_, bank) in retransmit_slots.iter() {
|
||||
let bank_epoch = bank.get_leader_schedule_epoch(bank.slot());
|
||||
let stakes = bank.epoch_staked_nodes(bank_epoch);
|
||||
let stakes = stakes.map(Arc::new);
|
||||
let slot = bank.slot();
|
||||
let data_shreds = Arc::new(
|
||||
blockstore
|
||||
.get_data_shreds_for_slot(bank.slot(), 0)
|
||||
.get_data_shreds_for_slot(slot, 0)
|
||||
.expect("My own shreds must be reconstructable"),
|
||||
);
|
||||
|
||||
debug_assert!(data_shreds.iter().all(|shred| shred.slot() == slot));
|
||||
if !data_shreds.is_empty() {
|
||||
socket_sender.send(((stakes.clone(), data_shreds), None))?;
|
||||
socket_sender.send((data_shreds, None))?;
|
||||
}
|
||||
|
||||
let coding_shreds = Arc::new(
|
||||
blockstore
|
||||
.get_coding_shreds_for_slot(bank.slot(), 0)
|
||||
.get_coding_shreds_for_slot(slot, 0)
|
||||
.expect("My own shreds must be reconstructable"),
|
||||
);
|
||||
|
||||
debug_assert!(coding_shreds.iter().all(|shred| shred.slot() == slot));
|
||||
if !coding_shreds.is_empty() {
|
||||
socket_sender.send(((stakes.clone(), coding_shreds), None))?;
|
||||
socket_sender.send((coding_shreds, None))?;
|
||||
}
|
||||
}
|
||||
|
||||
@ -343,16 +376,13 @@ impl BroadcastStage {
|
||||
}
|
||||
|
||||
fn update_peer_stats(
|
||||
num_live_peers: i64,
|
||||
broadcast_len: i64,
|
||||
last_datapoint_submit: &Arc<AtomicU64>,
|
||||
cluster_nodes: &ClusterNodes<BroadcastStage>,
|
||||
last_datapoint_submit: &Arc<AtomicInterval>,
|
||||
) {
|
||||
let now = timestamp();
|
||||
let last = last_datapoint_submit.load(Ordering::Relaxed);
|
||||
#[allow(deprecated)]
|
||||
if now.saturating_sub(last) > 1000
|
||||
&& last_datapoint_submit.compare_and_swap(last, now, Ordering::Relaxed) == last
|
||||
{
|
||||
if last_datapoint_submit.should_update(1000) {
|
||||
let now = timestamp();
|
||||
let num_live_peers = cluster_nodes.num_peers_live(now);
|
||||
let broadcast_len = cluster_nodes.num_peers() + 1;
|
||||
datapoint_info!(
|
||||
"cluster_info-num_nodes",
|
||||
("live_count", num_live_peers, i64),
|
||||
@ -361,81 +391,56 @@ fn update_peer_stats(
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_broadcast_peers(
|
||||
cluster_info: &ClusterInfo,
|
||||
stakes: Option<&HashMap<Pubkey, u64>>,
|
||||
) -> (Vec<ContactInfo>, Vec<(u64, usize)>) {
|
||||
let mut peers = cluster_info.tvu_peers();
|
||||
let peers_and_stakes = cluster_info::stake_weight_peers(&mut peers, stakes);
|
||||
(peers, peers_and_stakes)
|
||||
}
|
||||
|
||||
/// broadcast messages from the leader to layer 1 nodes
|
||||
/// # Remarks
|
||||
pub fn broadcast_shreds(
|
||||
s: &UdpSocket,
|
||||
shreds: &[Shred],
|
||||
peers_and_stakes: &[(u64, usize)],
|
||||
peers: &[ContactInfo],
|
||||
last_datapoint_submit: &Arc<AtomicU64>,
|
||||
cluster_nodes_cache: &ClusterNodesCache<BroadcastStage>,
|
||||
last_datapoint_submit: &Arc<AtomicInterval>,
|
||||
transmit_stats: &mut TransmitShredsStats,
|
||||
socket_addr_space: &SocketAddrSpace,
|
||||
cluster_info: &ClusterInfo,
|
||||
bank_forks: &Arc<RwLock<BankForks>>,
|
||||
) -> Result<()> {
|
||||
let broadcast_len = peers_and_stakes.len();
|
||||
if broadcast_len == 0 {
|
||||
update_peer_stats(1, 1, last_datapoint_submit);
|
||||
return Ok(());
|
||||
}
|
||||
let mut result = Ok(());
|
||||
let mut shred_select = Measure::start("shred_select");
|
||||
// Only the leader broadcasts shreds.
|
||||
let leader = cluster_info.id();
|
||||
let (root_bank, working_bank) = {
|
||||
let bank_forks = bank_forks.read().unwrap();
|
||||
(bank_forks.root_bank(), bank_forks.working_bank())
|
||||
};
|
||||
let packets: Vec<_> = shreds
|
||||
.iter()
|
||||
.map(|shred| {
|
||||
let broadcast_index = weighted_best(&peers_and_stakes, shred.seed());
|
||||
|
||||
(&shred.payload, &peers[broadcast_index].tvu)
|
||||
.group_by(|shred| shred.slot())
|
||||
.into_iter()
|
||||
.flat_map(|(slot, shreds)| {
|
||||
let cluster_nodes =
|
||||
cluster_nodes_cache.get(slot, &root_bank, &working_bank, cluster_info);
|
||||
update_peer_stats(&cluster_nodes, last_datapoint_submit);
|
||||
let root_bank = root_bank.clone();
|
||||
shreds.filter_map(move |shred| {
|
||||
let seed = shred.seed(leader, &root_bank);
|
||||
let node = cluster_nodes.get_broadcast_peer(seed)?;
|
||||
socket_addr_space
|
||||
.check(&node.tvu)
|
||||
.then(|| (&shred.payload, node.tvu))
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
shred_select.stop();
|
||||
transmit_stats.shred_select += shred_select.as_us();
|
||||
|
||||
let mut sent = 0;
|
||||
let mut send_mmsg_time = Measure::start("send_mmsg");
|
||||
while sent < packets.len() {
|
||||
match send_mmsg(s, &packets[sent..]) {
|
||||
Ok(n) => sent += n,
|
||||
Err(e) => {
|
||||
return Err(Error::Io(e));
|
||||
}
|
||||
}
|
||||
if let Err(SendPktsError::IoError(ioerr, num_failed)) = batch_send(s, &packets[..]) {
|
||||
transmit_stats.dropped_packets += num_failed;
|
||||
result = Err(Error::Io(ioerr));
|
||||
}
|
||||
send_mmsg_time.stop();
|
||||
transmit_stats.send_mmsg_elapsed += send_mmsg_time.as_us();
|
||||
|
||||
let num_live_peers = num_live_peers(&peers);
|
||||
update_peer_stats(
|
||||
num_live_peers,
|
||||
broadcast_len as i64 + 1,
|
||||
last_datapoint_submit,
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn distance(a: u64, b: u64) -> u64 {
|
||||
if a > b {
|
||||
a - b
|
||||
} else {
|
||||
b - a
|
||||
}
|
||||
}
|
||||
|
||||
fn num_live_peers(peers: &[ContactInfo]) -> i64 {
|
||||
let mut num_live_peers = 1i64;
|
||||
peers.iter().for_each(|p| {
|
||||
// A peer is considered live if they generated their contact info recently
|
||||
if distance(timestamp(), p.wallclock) <= CRDS_GOSSIP_PULL_CRDS_TIMEOUT_MS {
|
||||
num_live_peers += 1;
|
||||
}
|
||||
});
|
||||
num_live_peers
|
||||
transmit_stats.total_packets += packets.len();
|
||||
result
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@ -461,15 +466,15 @@ pub mod test {
|
||||
};
|
||||
|
||||
#[allow(clippy::implicit_hasher)]
|
||||
pub fn make_transmit_shreds(
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn make_transmit_shreds(
|
||||
slot: Slot,
|
||||
num: u64,
|
||||
stakes: Option<Arc<HashMap<Pubkey, u64>>>,
|
||||
) -> (
|
||||
Vec<Shred>,
|
||||
Vec<Shred>,
|
||||
Vec<TransmitShreds>,
|
||||
Vec<TransmitShreds>,
|
||||
Vec<Arc<Vec<Shred>>>,
|
||||
Vec<Arc<Vec<Shred>>>,
|
||||
) {
|
||||
let num_entries = max_ticks_per_n_shreds(num, None);
|
||||
let (data_shreds, _) = make_slot_entries(slot, 0, num_entries);
|
||||
@ -486,11 +491,11 @@ pub mod test {
|
||||
coding_shreds.clone(),
|
||||
data_shreds
|
||||
.into_iter()
|
||||
.map(|s| (stakes.clone(), Arc::new(vec![s])))
|
||||
.map(|shred| Arc::new(vec![shred]))
|
||||
.collect(),
|
||||
coding_shreds
|
||||
.into_iter()
|
||||
.map(|s| (stakes.clone(), Arc::new(vec![s])))
|
||||
.map(|shred| Arc::new(vec![shred]))
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
@ -502,15 +507,15 @@ pub mod test {
|
||||
num_expected_data_shreds: u64,
|
||||
num_expected_coding_shreds: u64,
|
||||
) {
|
||||
while let Ok((new_retransmit_slots, _)) = transmit_receiver.try_recv() {
|
||||
if new_retransmit_slots.1[0].is_data() {
|
||||
for data_shred in new_retransmit_slots.1.iter() {
|
||||
while let Ok((shreds, _)) = transmit_receiver.try_recv() {
|
||||
if shreds[0].is_data() {
|
||||
for data_shred in shreds.iter() {
|
||||
assert_eq!(data_shred.index() as u64, data_index);
|
||||
data_index += 1;
|
||||
}
|
||||
} else {
|
||||
assert_eq!(new_retransmit_slots.1[0].index() as u64, coding_index);
|
||||
for coding_shred in new_retransmit_slots.1.iter() {
|
||||
assert_eq!(shreds[0].index() as u64, coding_index);
|
||||
for coding_shred in shreds.iter() {
|
||||
assert_eq!(coding_shred.index() as u64, coding_index);
|
||||
coding_index += 1;
|
||||
}
|
||||
@ -521,19 +526,6 @@ pub mod test {
|
||||
assert_eq!(num_expected_coding_shreds, coding_index);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_num_live_peers() {
|
||||
let mut ci = ContactInfo {
|
||||
wallclock: std::u64::MAX,
|
||||
..ContactInfo::default()
|
||||
};
|
||||
assert_eq!(num_live_peers(&[ci.clone()]), 1);
|
||||
ci.wallclock = timestamp() - 1;
|
||||
assert_eq!(num_live_peers(&[ci.clone()]), 2);
|
||||
ci.wallclock = timestamp() - CRDS_GOSSIP_PULL_CRDS_TIMEOUT_MS - 1;
|
||||
assert_eq!(num_live_peers(&[ci]), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_duplicate_retransmit_signal() {
|
||||
// Setup
|
||||
@ -547,7 +539,7 @@ pub mod test {
|
||||
// Make some shreds
|
||||
let updated_slot = 0;
|
||||
let (all_data_shreds, all_coding_shreds, _, _all_coding_transmit_shreds) =
|
||||
make_transmit_shreds(updated_slot, 10, None);
|
||||
make_transmit_shreds(updated_slot, 10);
|
||||
let num_data_shreds = all_data_shreds.len();
|
||||
let num_coding_shreds = all_coding_shreds.len();
|
||||
assert!(num_data_shreds >= 10);
|
||||
@ -607,14 +599,20 @@ pub mod test {
|
||||
let broadcast_buddy = Node::new_localhost_with_pubkey(&buddy_keypair.pubkey());
|
||||
|
||||
// Fill the cluster_info with the buddy's info
|
||||
let cluster_info = ClusterInfo::new_with_invalid_keypair(leader_info.info.clone());
|
||||
let cluster_info = ClusterInfo::new(
|
||||
leader_info.info.clone(),
|
||||
Arc::new(Keypair::new()),
|
||||
SocketAddrSpace::Unspecified,
|
||||
);
|
||||
cluster_info.insert_info(broadcast_buddy.info);
|
||||
let cluster_info = Arc::new(cluster_info);
|
||||
|
||||
let exit_sender = Arc::new(AtomicBool::new(false));
|
||||
|
||||
let GenesisConfigInfo { genesis_config, .. } = create_genesis_config(10_000);
|
||||
let bank = Arc::new(Bank::new(&genesis_config));
|
||||
let bank = Bank::new(&genesis_config);
|
||||
let bank_forks = Arc::new(RwLock::new(BankForks::new(bank)));
|
||||
let bank = bank_forks.read().unwrap().root_bank();
|
||||
|
||||
let leader_keypair = cluster_info.keypair.clone();
|
||||
// Start up the broadcast stage
|
||||
@ -625,6 +623,7 @@ pub mod test {
|
||||
retransmit_slots_receiver,
|
||||
&exit_sender,
|
||||
&blockstore,
|
||||
&bank_forks,
|
||||
StandardBroadcastRun::new(leader_keypair, 0),
|
||||
);
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user