Compare commits
442 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
6242809a07 | ||
|
1958bb1169 | ||
|
5e2de5648d | ||
|
16ded2115c | ||
|
b775e8748c | ||
|
9d00220d88 | ||
|
a00cbb55b9 | ||
|
7ae3b55dde | ||
|
280437bad2 | ||
|
722879b96c | ||
|
c22b83aa6c | ||
|
9387ee6f3b | ||
|
211a42fb98 | ||
|
247fa529b0 | ||
|
b3bfe7b6ad | ||
|
109c15c97c | ||
|
32b05e7ba0 | ||
|
1da88658c3 | ||
|
77d8468df2 | ||
|
163efc3bdf | ||
|
e7aa6cd5ea | ||
|
eb12d29683 | ||
|
979e07501a | ||
|
297c08310f | ||
|
f0afbf4948 | ||
|
c82c750091 | ||
|
a7d1223583 | ||
|
b550f5bc19 | ||
|
5d341d9bf4 | ||
|
82ba19488e | ||
|
fa7067950a | ||
|
d69f09f152 | ||
|
0b510ac9b4 | ||
|
96e587c83e | ||
|
fc6e7a5e2a | ||
|
087c43ad33 | ||
|
7d67f5b18c | ||
|
80f5127dfa | ||
|
bb5a69aa4d | ||
|
23e6ff3e94 | ||
|
5e0ca65100 | ||
|
6500fed8b7 | ||
|
a80ac11b68 | ||
|
5f03a17a6e | ||
|
a52a22f558 | ||
|
dfd09a5c13 | ||
|
37eb205b54 | ||
|
e4a68f7d99 | ||
|
f0be7032cb | ||
|
241fb938c1 | ||
|
11190259d5 | ||
|
7f9d5ac383 | ||
|
abfae5d46a | ||
|
081f1cd118 | ||
|
6f31373b21 | ||
|
b231fb2c18 | ||
|
e255c85bef | ||
|
e5bb1597a4 | ||
|
a97d89fb5a | ||
|
1f1dd58c78 | ||
|
b21ce376fb | ||
|
451d974f26 | ||
|
f254bf85eb | ||
|
8b80628b38 | ||
|
2ac95a3ebc | ||
|
ddef2fb7fa | ||
|
de4d2e640e | ||
|
c857467262 | ||
|
671fb3519d | ||
|
9aed0b0952 | ||
|
767c89526a | ||
|
2bfe545438 | ||
|
804a284a52 | ||
|
51e5189ffb | ||
|
5216f51ff2 | ||
|
0c55add96b | ||
|
291f81d5b0 | ||
|
5c8a878f1b | ||
|
7148aaa30c | ||
|
7a3c4c184f | ||
|
bc5f434e48 | ||
|
569edbb241 | ||
|
abf2d71f4c | ||
|
1a8b57fcd0 | ||
|
723c03dfbd | ||
|
0a1fcfa08b | ||
|
8820933287 | ||
|
460c643f8e | ||
|
65600f9a1f | ||
|
ef61dc9780 | ||
|
477e5d4bff | ||
|
d54632da00 | ||
|
26b420bd39 | ||
|
c3dda3ce0c | ||
|
c527e1f2e5 | ||
|
135f47b6be | ||
|
6656b3965f | ||
|
3068572bb9 | ||
|
f48236837c | ||
|
59beb8e548 | ||
|
543f7e7ec1 | ||
|
efe563201f | ||
|
603cae4a5c | ||
|
73fb9695bc | ||
|
1aec2102d4 | ||
|
99012f022e | ||
|
20afb912cd | ||
|
563231132f | ||
|
32ec9147bb | ||
|
4be8842925 | ||
|
b1ef9045ec | ||
|
dbe4d87e60 | ||
|
d2efa3aa15 | ||
|
ccd2c6cc13 | ||
|
e976b1547a | ||
|
03ac807756 | ||
|
067871cc39 | ||
|
e9ceb99460 | ||
|
cd994f0162 | ||
|
01e4d0a1e9 | ||
|
7f0f43cb28 | ||
|
d0bf97d25d | ||
|
c3056734e3 | ||
|
5e2b9e595d | ||
|
9be3e00546 | ||
|
0c2dcd759c | ||
|
b711476811 | ||
|
e4fe7dfbbd | ||
|
40e62c60d3 | ||
|
a93d37b804 | ||
|
65e6df2b0d | ||
|
f02bd10d4a | ||
|
d7d8a751d9 | ||
|
f6f4193d4d | ||
|
fe0077d88a | ||
|
8016f61ce8 | ||
|
0b6366da9c | ||
|
d567a62cc7 | ||
|
eccea2b1ea | ||
|
bfd93cc13f | ||
|
b21c89e494 | ||
|
253b68abf1 | ||
|
49034b8016 | ||
|
3838fb62d4 | ||
|
9f267fc5e7 | ||
|
5a82265650 | ||
|
77d2ed95ff | ||
|
858ca752e2 | ||
|
fea0bd234c | ||
|
7af7d5f22c | ||
|
de4cccd977 | ||
|
9f74136632 | ||
|
21484acc20 | ||
|
36ad03af1f | ||
|
e255ee52b1 | ||
|
972540907b | ||
|
cfeed09f1f | ||
|
dadebb2bba | ||
|
169403a15e | ||
|
e2a874370b | ||
|
baf7713744 | ||
|
573304cf73 | ||
|
ec6d5933de | ||
|
30b815e7bc | ||
|
f463ebfde2 | ||
|
ba0aa706e4 | ||
|
eacf9209f7 | ||
|
ba12a14494 | ||
|
4e60f95854 | ||
|
a7193ce834 | ||
|
f12a467177 | ||
|
45a92337f4 | ||
|
bfa6e9bdf6 | ||
|
a7d9a52690 | ||
|
4b3391f1d8 | ||
|
19b203f344 | ||
|
6150faafb6 | ||
|
49e608295e | ||
|
636be95e2a | ||
|
0db5a74bb9 | ||
|
08fd4905db | ||
|
3610b6f31c | ||
|
b35f35a7e8 | ||
|
790a6b7550 | ||
|
30d2e15945 | ||
|
7b3c7a075a | ||
|
f534698618 | ||
|
fe1347b441 | ||
|
8f6c01f0f8 | ||
|
066ff36175 | ||
|
5da9e7cb8a | ||
|
09b68f2fbb | ||
|
d9fcd84bc2 | ||
|
86703384dc | ||
|
580b0859e8 | ||
|
5d8c5254b0 | ||
|
ea83292daa | ||
|
f1c3e6dc36 | ||
|
bdd19c09d1 | ||
|
95cbfce900 | ||
|
a404a9d802 | ||
|
15cd1283e8 | ||
|
512a193674 | ||
|
de37795ca1 | ||
|
cb7871347d | ||
|
b4b9ea7771 | ||
|
62b7bf5365 | ||
|
91c57cd70d | ||
|
cb35fc185b | ||
|
cca9009f23 | ||
|
d8d73ff56c | ||
|
34504797b4 | ||
|
1767e4fbde | ||
|
39515cae5e | ||
|
116d67e1e3 | ||
|
08bda35fd6 | ||
|
ba1d0927e6 | ||
|
555df9f96c | ||
|
99166a4a59 | ||
|
92534849db | ||
|
5ba8b4884b | ||
|
cb878f2ea8 | ||
|
b1d5bf30d2 | ||
|
481c60e287 | ||
|
86242dc3ba | ||
|
71899deb53 | ||
|
7e2e0d4a86 | ||
|
d4cc7c6b66 | ||
|
b62349f081 | ||
|
d16638dc90 | ||
|
b97fc31fcd | ||
|
4378634970 | ||
|
10e12d14e1 | ||
|
c5cfbee853 | ||
|
c276670a94 | ||
|
676da0a836 | ||
|
0021cf924f | ||
|
d593ee187c | ||
|
a07bfc2d76 | ||
|
f0e9843dd4 | ||
|
a2f643e7c7 | ||
|
7ebaf1c192 | ||
|
6a61e7a01e | ||
|
1c23f135bf | ||
|
3c67f71695 | ||
|
d380b9cef7 | ||
|
7415821156 | ||
|
4a6c3a9331 | ||
|
0cd1cce588 | ||
|
f762b4a730 | ||
|
08f7f2546e | ||
|
cb701fbbd9 | ||
|
231ff7d70d | ||
|
a154414e65 | ||
|
673c679523 | ||
|
65a7621b17 | ||
|
c9da25836a | ||
|
b48dd58fda | ||
|
40f32fd37d | ||
|
fef4100f5f | ||
|
68bf58aac0 | ||
|
5677662d61 | ||
|
6755fd0c96 | ||
|
dfbe38b859 | ||
|
480a35d678 | ||
|
e6b53c262b | ||
|
e127631f8d | ||
|
2d246581ed | ||
|
952a5b11c1 | ||
|
c49a8cb67c | ||
|
733a1c85cf | ||
|
c8f7719c9e | ||
|
c89f5e28b7 | ||
|
38e4c34d18 | ||
|
afa7343bc2 | ||
|
8ea584e01f | ||
|
239dc9b0b7 | ||
|
6e6a55b7d6 | ||
|
815bad8a6c | ||
|
74f813574f | ||
|
07648f43db | ||
|
99f0d29e65 | ||
|
87825f3beb | ||
|
8e38f90e54 | ||
|
d72c90e475 | ||
|
459ae81655 | ||
|
a2ce22f11b | ||
|
44ad4a1ecd | ||
|
fcd8dd75c5 | ||
|
ca262fdeb9 | ||
|
3d6bb95932 | ||
|
e5d36fcfb3 | ||
|
061965e291 | ||
|
e56681a2f6 | ||
|
8cf23903ce | ||
|
7ac2aae730 | ||
|
6f5b9331bd | ||
|
a04375e204 | ||
|
7b19d26a6e | ||
|
3c3a3f0b50 | ||
|
0367b32533 | ||
|
09392ee562 | ||
|
1a848e22ea | ||
|
3d8cadebc0 | ||
|
47b8d518c5 | ||
|
011d2dd41d | ||
|
bdfffd0151 | ||
|
722cebc4c3 | ||
|
771b98a168 | ||
|
1b02ec4f6e | ||
|
aae51925c1 | ||
|
00626fbf4c | ||
|
14ffc05fd4 | ||
|
6e5c9a1b1c | ||
|
1276b92462 | ||
|
89241cedba | ||
|
0e3e3a03cc | ||
|
25fe93e9fb | ||
|
4440a8d9fa | ||
|
b737e562ab | ||
|
9cb32b2105 | ||
|
f46ad1b7b7 | ||
|
b15603b4eb | ||
|
c7267799ce | ||
|
ed0a083cd9 | ||
|
484bd48b35 | ||
|
ae73cc8d05 | ||
|
fc59a08f0e | ||
|
15da7968c5 | ||
|
b58a6e2b6e | ||
|
ec15ea079f | ||
|
4b5a05bf38 | ||
|
7dd7141307 | ||
|
e5175c843d | ||
|
d5ff64b0d7 | ||
|
0fbdc7e152 | ||
|
49aca9ecd8 | ||
|
fcc147b4f2 | ||
|
c455d1b1c5 | ||
|
e9b29fc697 | ||
|
fdea6fad26 | ||
|
a4bc31341a | ||
|
4af797c0a2 | ||
|
a1e06df4a8 | ||
|
1f2480fd9f | ||
|
8587bd0d69 | ||
|
0063a58e95 | ||
|
9aeb3bc5d6 | ||
|
97665b977e | ||
|
c45ed29cf4 | ||
|
635afbabff | ||
|
98afdad1dd | ||
|
c085b94b43 | ||
|
a53946c485 | ||
|
f6de92c346 | ||
|
46f9822d62 | ||
|
6dad84d228 | ||
|
3582607aa0 | ||
|
f051077350 | ||
|
ffd6f3e6bf | ||
|
c6b2eb07ee | ||
|
7a3e1f9826 | ||
|
8a690b6cf7 | ||
|
8688efa89b | ||
|
3b047e5b99 | ||
|
3cddc731b2 | ||
|
1d29a583c6 | ||
|
b5335edb35 | ||
|
abee1e83eb | ||
|
6c5be574c8 | ||
|
c1f993d2fc | ||
|
e2ddb2f0ea | ||
|
f3faba5ca9 | ||
|
3a6fd91739 | ||
|
2d2b3d8287 | ||
|
6e47b88399 | ||
|
941e56c6c7 | ||
|
d1adc2a446 | ||
|
02da7dfedf | ||
|
eb0fd3625a | ||
|
b87e606626 | ||
|
1c91376f78 | ||
|
10067ad07b | ||
|
eb76289107 | ||
|
8926736e1c | ||
|
bf4c169703 | ||
|
0020e43476 | ||
|
a9a2c76221 | ||
|
4754b4e871 | ||
|
52ffb9a64a | ||
|
bd0b1503c6 | ||
|
10e7fa40ac | ||
|
198ed407b7 | ||
|
d96af2dd23 | ||
|
192cca8f98 | ||
|
ee716e1c55 | ||
|
6dd3c7c2dd | ||
|
582b4c9edf | ||
|
f15add2a74 | ||
|
74d48910e2 | ||
|
c53e8ee3ad | ||
|
c5e5fedc47 | ||
|
b9929dcd67 | ||
|
554a158443 | ||
|
b7fa4b7ee1 | ||
|
fd44cee8cc | ||
|
c6a362cce2 | ||
|
252180c244 | ||
|
e02b4e698e | ||
|
4811afe8eb | ||
|
bc4568b10f | ||
|
d59c131e90 | ||
|
825027f9f7 | ||
|
9b8f0bee99 | ||
|
fc13c1d654 | ||
|
a57758e9c9 | ||
|
564590462a | ||
|
269f6af97e | ||
|
57b8a59632 | ||
|
4289f52d2b | ||
|
573f68620b | ||
|
4bfe64688b | ||
|
50034848a5 | ||
|
981294cbc6 | ||
|
ff728e5e56 | ||
|
9aaf41bef2 | ||
|
271eec656c | ||
|
13d071607f | ||
|
ffe35d9a10 | ||
|
bb2fb07b39 | ||
|
85fc51dc61 | ||
|
0276b6c4c2 | ||
|
c481e4fe7f | ||
|
76a3b3ad11 | ||
|
356c663e88 | ||
|
015bbc1e12 | ||
|
454a9f3175 | ||
|
485b3d64a1 | ||
|
5d170d83c0 | ||
|
f54d8ea3ab | ||
|
ef9f54b3d4 | ||
|
8d0b102b44 |
4
.gitignore
vendored
4
.gitignore
vendored
@@ -1,7 +1,3 @@
|
||||
/docs/html/
|
||||
/docs/src/tests.ok
|
||||
/docs/src/cli/usage.md
|
||||
/docs/src/.gitbook/assets/*.svg
|
||||
/farf/
|
||||
/solana-release/
|
||||
/solana-release.tar.bz2
|
||||
|
805
Cargo.lock
generated
805
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -28,6 +28,7 @@ members = [
|
||||
"local-cluster",
|
||||
"logger",
|
||||
"log-analyzer",
|
||||
"merkle-root-bench",
|
||||
"merkle-tree",
|
||||
"stake-o-matic",
|
||||
"storage-bigtable",
|
||||
|
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "solana-account-decoder"
|
||||
version = "1.5.0"
|
||||
version = "1.5.13"
|
||||
description = "Solana account decoder"
|
||||
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
@@ -15,14 +15,14 @@ bs58 = "0.3.1"
|
||||
bv = "0.11.1"
|
||||
Inflector = "0.11.4"
|
||||
lazy_static = "1.4.0"
|
||||
serde = "1.0.112"
|
||||
serde = "1.0.118"
|
||||
serde_derive = "1.0.103"
|
||||
serde_json = "1.0.56"
|
||||
solana-config-program = { path = "../programs/config", version = "1.5.0" }
|
||||
solana-sdk = { path = "../sdk", version = "1.5.0" }
|
||||
solana-stake-program = { path = "../programs/stake", version = "1.5.0" }
|
||||
solana-vote-program = { path = "../programs/vote", version = "1.5.0" }
|
||||
spl-token-v2-0 = { package = "spl-token", version = "=3.0.0", features = ["no-entrypoint"] }
|
||||
solana-config-program = { path = "../programs/config", version = "1.5.13" }
|
||||
solana-sdk = { path = "../sdk", version = "1.5.13" }
|
||||
solana-stake-program = { path = "../programs/stake", version = "1.5.13" }
|
||||
solana-vote-program = { path = "../programs/vote", version = "1.5.13" }
|
||||
spl-token-v2-0 = { package = "spl-token", version = "=3.1.0", features = ["no-entrypoint"] }
|
||||
thiserror = "1.0"
|
||||
zstd = "0.5.1"
|
||||
|
||||
|
@@ -1,9 +1,11 @@
|
||||
#![allow(clippy::integer_arithmetic)]
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
|
||||
pub mod parse_account_data;
|
||||
pub mod parse_bpf_loader;
|
||||
pub mod parse_config;
|
||||
pub mod parse_nonce;
|
||||
pub mod parse_stake;
|
||||
@@ -22,6 +24,7 @@ use {
|
||||
};
|
||||
|
||||
pub type StringAmount = String;
|
||||
pub type StringDecimals = String;
|
||||
|
||||
/// A duplicate representation of an Account for pretty JSON serialization
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
|
@@ -1,4 +1,5 @@
|
||||
use crate::{
|
||||
parse_bpf_loader::parse_bpf_upgradeable_loader,
|
||||
parse_config::parse_config,
|
||||
parse_nonce::parse_nonce,
|
||||
parse_stake::parse_stake,
|
||||
@@ -13,6 +14,7 @@ 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 SYSTEM_PROGRAM_ID: Pubkey = system_program::id();
|
||||
@@ -21,6 +23,10 @@ lazy_static! {
|
||||
static ref VOTE_PROGRAM_ID: Pubkey = solana_vote_program::id();
|
||||
pub static ref PARSABLE_PROGRAM_IDS: HashMap<Pubkey, ParsableAccount> = {
|
||||
let mut m = HashMap::new();
|
||||
m.insert(
|
||||
*BPF_UPGRADEABLE_LOADER_PROGRAM_ID,
|
||||
ParsableAccount::BpfUpgradeableLoader,
|
||||
);
|
||||
m.insert(*CONFIG_PROGRAM_ID, ParsableAccount::Config);
|
||||
m.insert(*SYSTEM_PROGRAM_ID, ParsableAccount::Nonce);
|
||||
m.insert(*TOKEN_PROGRAM_ID, ParsableAccount::SplToken);
|
||||
@@ -60,6 +66,7 @@ pub struct ParsedAccount {
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum ParsableAccount {
|
||||
BpfUpgradeableLoader,
|
||||
Config,
|
||||
Nonce,
|
||||
SplToken,
|
||||
@@ -84,6 +91,9 @@ pub fn parse_account_data(
|
||||
.ok_or(ParseAccountError::ProgramNotParsable)?;
|
||||
let additional_data = additional_data.unwrap_or_default();
|
||||
let parsed_json = match program_name {
|
||||
ParsableAccount::BpfUpgradeableLoader => {
|
||||
serde_json::to_value(parse_bpf_upgradeable_loader(data)?)?
|
||||
}
|
||||
ParsableAccount::Config => serde_json::to_value(parse_config(data, pubkey)?)?,
|
||||
ParsableAccount::Nonce => serde_json::to_value(parse_nonce(data)?)?,
|
||||
ParsableAccount::SplToken => {
|
||||
@@ -118,7 +128,7 @@ mod test {
|
||||
|
||||
let vote_state = VoteState::default();
|
||||
let mut vote_account_data: Vec<u8> = vec![0; VoteState::size_of()];
|
||||
let versioned = VoteStateVersions::Current(Box::new(vote_state));
|
||||
let versioned = VoteStateVersions::new_current(vote_state);
|
||||
VoteState::serialize(&versioned, &mut vote_account_data).unwrap();
|
||||
let parsed = parse_account_data(
|
||||
&account_pubkey,
|
||||
|
181
account-decoder/src/parse_bpf_loader.rs
Normal file
181
account-decoder/src/parse_bpf_loader.rs
Normal file
@@ -0,0 +1,181 @@
|
||||
use crate::{
|
||||
parse_account_data::{ParsableAccount, ParseAccountError},
|
||||
UiAccountData, UiAccountEncoding,
|
||||
};
|
||||
use bincode::{deserialize, serialized_size};
|
||||
use solana_sdk::{bpf_loader_upgradeable::UpgradeableLoaderState, pubkey::Pubkey};
|
||||
|
||||
pub fn parse_bpf_upgradeable_loader(
|
||||
data: &[u8],
|
||||
) -> Result<BpfUpgradeableLoaderAccountType, ParseAccountError> {
|
||||
let account_state: UpgradeableLoaderState = deserialize(data).map_err(|_| {
|
||||
ParseAccountError::AccountNotParsable(ParsableAccount::BpfUpgradeableLoader)
|
||||
})?;
|
||||
let parsed_account = match account_state {
|
||||
UpgradeableLoaderState::Uninitialized => BpfUpgradeableLoaderAccountType::Uninitialized,
|
||||
UpgradeableLoaderState::Buffer { authority_address } => {
|
||||
let offset = if authority_address.is_some() {
|
||||
UpgradeableLoaderState::buffer_data_offset().unwrap()
|
||||
} else {
|
||||
// This case included for code completeness; in practice, a Buffer account will
|
||||
// always have authority_address.is_some()
|
||||
UpgradeableLoaderState::buffer_data_offset().unwrap()
|
||||
- serialized_size(&Pubkey::default()).unwrap() as usize
|
||||
};
|
||||
BpfUpgradeableLoaderAccountType::Buffer(UiBuffer {
|
||||
authority: authority_address.map(|pubkey| pubkey.to_string()),
|
||||
data: UiAccountData::Binary(
|
||||
base64::encode(&data[offset as usize..]),
|
||||
UiAccountEncoding::Base64,
|
||||
),
|
||||
})
|
||||
}
|
||||
UpgradeableLoaderState::Program {
|
||||
programdata_address,
|
||||
} => BpfUpgradeableLoaderAccountType::Program(UiProgram {
|
||||
program_data: programdata_address.to_string(),
|
||||
}),
|
||||
UpgradeableLoaderState::ProgramData {
|
||||
slot,
|
||||
upgrade_authority_address,
|
||||
} => {
|
||||
let offset = if upgrade_authority_address.is_some() {
|
||||
UpgradeableLoaderState::programdata_data_offset().unwrap()
|
||||
} else {
|
||||
UpgradeableLoaderState::programdata_data_offset().unwrap()
|
||||
- serialized_size(&Pubkey::default()).unwrap() as usize
|
||||
};
|
||||
BpfUpgradeableLoaderAccountType::ProgramData(UiProgramData {
|
||||
slot,
|
||||
authority: upgrade_authority_address.map(|pubkey| pubkey.to_string()),
|
||||
data: UiAccountData::Binary(
|
||||
base64::encode(&data[offset as usize..]),
|
||||
UiAccountEncoding::Base64,
|
||||
),
|
||||
})
|
||||
}
|
||||
};
|
||||
Ok(parsed_account)
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase", tag = "type", content = "info")]
|
||||
pub enum BpfUpgradeableLoaderAccountType {
|
||||
Uninitialized,
|
||||
Buffer(UiBuffer),
|
||||
Program(UiProgram),
|
||||
ProgramData(UiProgramData),
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct UiBuffer {
|
||||
pub authority: Option<String>,
|
||||
pub data: UiAccountData,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct UiProgram {
|
||||
pub program_data: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct UiProgramData {
|
||||
pub slot: u64,
|
||||
pub authority: Option<String>,
|
||||
pub data: UiAccountData,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use bincode::serialize;
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
|
||||
#[test]
|
||||
fn test_parse_bpf_upgradeable_loader_accounts() {
|
||||
let bpf_loader_state = UpgradeableLoaderState::Uninitialized;
|
||||
let account_data = serialize(&bpf_loader_state).unwrap();
|
||||
assert_eq!(
|
||||
parse_bpf_upgradeable_loader(&account_data).unwrap(),
|
||||
BpfUpgradeableLoaderAccountType::Uninitialized
|
||||
);
|
||||
|
||||
let program = vec![7u8; 64]; // Arbitrary program data
|
||||
|
||||
let authority = Pubkey::new_unique();
|
||||
let bpf_loader_state = UpgradeableLoaderState::Buffer {
|
||||
authority_address: Some(authority),
|
||||
};
|
||||
let mut account_data = serialize(&bpf_loader_state).unwrap();
|
||||
account_data.extend_from_slice(&program);
|
||||
assert_eq!(
|
||||
parse_bpf_upgradeable_loader(&account_data).unwrap(),
|
||||
BpfUpgradeableLoaderAccountType::Buffer(UiBuffer {
|
||||
authority: Some(authority.to_string()),
|
||||
data: UiAccountData::Binary(base64::encode(&program), UiAccountEncoding::Base64),
|
||||
})
|
||||
);
|
||||
|
||||
// This case included for code completeness; in practice, a Buffer account will always have
|
||||
// authority_address.is_some()
|
||||
let bpf_loader_state = UpgradeableLoaderState::Buffer {
|
||||
authority_address: None,
|
||||
};
|
||||
let mut account_data = serialize(&bpf_loader_state).unwrap();
|
||||
account_data.extend_from_slice(&program);
|
||||
assert_eq!(
|
||||
parse_bpf_upgradeable_loader(&account_data).unwrap(),
|
||||
BpfUpgradeableLoaderAccountType::Buffer(UiBuffer {
|
||||
authority: None,
|
||||
data: UiAccountData::Binary(base64::encode(&program), UiAccountEncoding::Base64),
|
||||
})
|
||||
);
|
||||
|
||||
let programdata_address = Pubkey::new_unique();
|
||||
let bpf_loader_state = UpgradeableLoaderState::Program {
|
||||
programdata_address,
|
||||
};
|
||||
let account_data = serialize(&bpf_loader_state).unwrap();
|
||||
assert_eq!(
|
||||
parse_bpf_upgradeable_loader(&account_data).unwrap(),
|
||||
BpfUpgradeableLoaderAccountType::Program(UiProgram {
|
||||
program_data: programdata_address.to_string(),
|
||||
})
|
||||
);
|
||||
|
||||
let authority = Pubkey::new_unique();
|
||||
let slot = 42;
|
||||
let bpf_loader_state = UpgradeableLoaderState::ProgramData {
|
||||
slot,
|
||||
upgrade_authority_address: Some(authority),
|
||||
};
|
||||
let mut account_data = serialize(&bpf_loader_state).unwrap();
|
||||
account_data.extend_from_slice(&program);
|
||||
assert_eq!(
|
||||
parse_bpf_upgradeable_loader(&account_data).unwrap(),
|
||||
BpfUpgradeableLoaderAccountType::ProgramData(UiProgramData {
|
||||
slot,
|
||||
authority: Some(authority.to_string()),
|
||||
data: UiAccountData::Binary(base64::encode(&program), UiAccountEncoding::Base64),
|
||||
})
|
||||
);
|
||||
|
||||
let bpf_loader_state = UpgradeableLoaderState::ProgramData {
|
||||
slot,
|
||||
upgrade_authority_address: None,
|
||||
};
|
||||
let mut account_data = serialize(&bpf_loader_state).unwrap();
|
||||
account_data.extend_from_slice(&program);
|
||||
assert_eq!(
|
||||
parse_bpf_upgradeable_loader(&account_data).unwrap(),
|
||||
BpfUpgradeableLoaderAccountType::ProgramData(UiProgramData {
|
||||
slot,
|
||||
authority: None,
|
||||
data: UiAccountData::Binary(base64::encode(&program), UiAccountEncoding::Base64),
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
@@ -1,6 +1,6 @@
|
||||
use crate::{
|
||||
parse_account_data::{ParsableAccount, ParseAccountError},
|
||||
StringAmount,
|
||||
StringAmount, StringDecimals,
|
||||
};
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
use spl_token_v2_0::{
|
||||
@@ -158,46 +158,64 @@ impl From<AccountState> for UiAccountState {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn real_number_string(amount: u64, decimals: u8) -> StringDecimals {
|
||||
let decimals = decimals as usize;
|
||||
if decimals > 0 {
|
||||
// Left-pad zeros to decimals + 1, so we at least have an integer zero
|
||||
let mut s = format!("{:01$}", amount, decimals + 1);
|
||||
// Add the decimal point (Sorry, "," locales!)
|
||||
s.insert(s.len() - decimals, '.');
|
||||
s
|
||||
} else {
|
||||
amount.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn real_number_string_trimmed(amount: u64, decimals: u8) -> StringDecimals {
|
||||
let s = real_number_string(amount, decimals);
|
||||
let zeros_trimmed = s.trim_end_matches('0');
|
||||
let decimal_trimmed = zeros_trimmed.trim_end_matches('.');
|
||||
decimal_trimmed.to_string()
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct UiTokenAmount {
|
||||
pub ui_amount: f64,
|
||||
pub ui_amount: Option<f64>,
|
||||
pub decimals: u8,
|
||||
pub amount: StringAmount,
|
||||
pub ui_amount_string: StringDecimals,
|
||||
}
|
||||
|
||||
impl UiTokenAmount {
|
||||
pub fn real_number_string(&self) -> String {
|
||||
let decimals = self.decimals as usize;
|
||||
if decimals > 0 {
|
||||
let amount = u64::from_str(&self.amount).unwrap_or(0);
|
||||
|
||||
// Left-pad zeros to decimals + 1, so we at least have an integer zero
|
||||
let mut s = format!("{:01$}", amount, decimals + 1);
|
||||
|
||||
// Add the decimal point (Sorry, "," locales!)
|
||||
s.insert(s.len() - decimals, '.');
|
||||
s
|
||||
} else {
|
||||
self.amount.clone()
|
||||
}
|
||||
real_number_string(
|
||||
u64::from_str(&self.amount).unwrap_or_default(),
|
||||
self.decimals as u8,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn real_number_string_trimmed(&self) -> String {
|
||||
let s = self.real_number_string();
|
||||
let zeros_trimmed = s.trim_end_matches('0');
|
||||
let decimal_trimmed = zeros_trimmed.trim_end_matches('.');
|
||||
decimal_trimmed.to_string()
|
||||
if !self.ui_amount_string.is_empty() {
|
||||
self.ui_amount_string.clone()
|
||||
} else {
|
||||
real_number_string_trimmed(
|
||||
u64::from_str(&self.amount).unwrap_or_default(),
|
||||
self.decimals as u8,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn token_amount_to_ui_amount(amount: u64, decimals: u8) -> UiTokenAmount {
|
||||
// Use `amount_to_ui_amount()` once spl_token is bumped to a version that supports it: https://github.com/solana-labs/solana-program-library/pull/211
|
||||
let amount_decimals = amount as f64 / 10_usize.pow(decimals as u32) as f64;
|
||||
let amount_decimals = 10_usize
|
||||
.checked_pow(decimals as u32)
|
||||
.map(|dividend| amount as f64 / dividend as f64);
|
||||
UiTokenAmount {
|
||||
ui_amount: amount_decimals,
|
||||
decimals,
|
||||
amount: amount.to_string(),
|
||||
ui_amount_string: real_number_string_trimmed(amount, decimals),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -253,9 +271,10 @@ mod test {
|
||||
mint: mint_pubkey.to_string(),
|
||||
owner: owner_pubkey.to_string(),
|
||||
token_amount: UiTokenAmount {
|
||||
ui_amount: 0.42,
|
||||
ui_amount: Some(0.42),
|
||||
decimals: 2,
|
||||
amount: "42".to_string()
|
||||
amount: "42".to_string(),
|
||||
ui_amount_string: "0.42".to_string()
|
||||
},
|
||||
delegate: None,
|
||||
state: UiAccountState::Initialized,
|
||||
@@ -336,17 +355,51 @@ mod test {
|
||||
|
||||
#[test]
|
||||
fn test_ui_token_amount_real_string() {
|
||||
assert_eq!(&real_number_string(1, 0), "1");
|
||||
assert_eq!(&real_number_string_trimmed(1, 0), "1");
|
||||
let token_amount = token_amount_to_ui_amount(1, 0);
|
||||
assert_eq!(&token_amount.real_number_string(), "1");
|
||||
assert_eq!(&token_amount.real_number_string_trimmed(), "1");
|
||||
assert_eq!(
|
||||
token_amount.ui_amount_string,
|
||||
real_number_string_trimmed(1, 0)
|
||||
);
|
||||
assert_eq!(token_amount.ui_amount, Some(1.0));
|
||||
assert_eq!(&real_number_string(1, 9), "0.000000001");
|
||||
assert_eq!(&real_number_string_trimmed(1, 9), "0.000000001");
|
||||
let token_amount = token_amount_to_ui_amount(1, 9);
|
||||
assert_eq!(&token_amount.real_number_string(), "0.000000001");
|
||||
assert_eq!(&token_amount.real_number_string_trimmed(), "0.000000001");
|
||||
assert_eq!(
|
||||
token_amount.ui_amount_string,
|
||||
real_number_string_trimmed(1, 9)
|
||||
);
|
||||
assert_eq!(token_amount.ui_amount, Some(0.000000001));
|
||||
assert_eq!(&real_number_string(1_000_000_000, 9), "1.000000000");
|
||||
assert_eq!(&real_number_string_trimmed(1_000_000_000, 9), "1");
|
||||
let token_amount = token_amount_to_ui_amount(1_000_000_000, 9);
|
||||
assert_eq!(&token_amount.real_number_string(), "1.000000000");
|
||||
assert_eq!(&token_amount.real_number_string_trimmed(), "1");
|
||||
assert_eq!(
|
||||
token_amount.ui_amount_string,
|
||||
real_number_string_trimmed(1_000_000_000, 9)
|
||||
);
|
||||
assert_eq!(token_amount.ui_amount, Some(1.0));
|
||||
assert_eq!(&real_number_string(1_234_567_890, 3), "1234567.890");
|
||||
assert_eq!(&real_number_string_trimmed(1_234_567_890, 3), "1234567.89");
|
||||
let token_amount = token_amount_to_ui_amount(1_234_567_890, 3);
|
||||
assert_eq!(&token_amount.real_number_string(), "1234567.890");
|
||||
assert_eq!(&token_amount.real_number_string_trimmed(), "1234567.89");
|
||||
assert_eq!(
|
||||
token_amount.ui_amount_string,
|
||||
real_number_string_trimmed(1_234_567_890, 3)
|
||||
);
|
||||
assert_eq!(token_amount.ui_amount, Some(1234567.89));
|
||||
assert_eq!(
|
||||
&real_number_string(1_234_567_890, 25),
|
||||
"0.0000000000000001234567890"
|
||||
);
|
||||
assert_eq!(
|
||||
&real_number_string_trimmed(1_234_567_890, 25),
|
||||
"0.000000000000000123456789"
|
||||
);
|
||||
let token_amount = token_amount_to_ui_amount(1_234_567_890, 20);
|
||||
assert_eq!(
|
||||
token_amount.ui_amount_string,
|
||||
real_number_string_trimmed(1_234_567_890, 20)
|
||||
);
|
||||
assert_eq!(token_amount.ui_amount, None);
|
||||
}
|
||||
}
|
||||
|
@@ -128,7 +128,7 @@ mod test {
|
||||
fn test_parse_vote() {
|
||||
let vote_state = VoteState::default();
|
||||
let mut vote_account_data: Vec<u8> = vec![0; VoteState::size_of()];
|
||||
let versioned = VoteStateVersions::Current(Box::new(vote_state));
|
||||
let versioned = VoteStateVersions::new_current(vote_state);
|
||||
VoteState::serialize(&versioned, &mut vote_account_data).unwrap();
|
||||
let expected_vote_state = UiVoteState {
|
||||
node_pubkey: Pubkey::default().to_string(),
|
||||
|
@@ -2,7 +2,7 @@
|
||||
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
|
||||
edition = "2018"
|
||||
name = "solana-accounts-bench"
|
||||
version = "1.5.0"
|
||||
version = "1.5.13"
|
||||
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.4.0"
|
||||
solana-logger = { path = "../logger", version = "1.5.0" }
|
||||
solana-runtime = { path = "../runtime", version = "1.5.0" }
|
||||
solana-measure = { path = "../measure", version = "1.5.0" }
|
||||
solana-sdk = { path = "../sdk", version = "1.5.0" }
|
||||
solana-version = { path = "../version", version = "1.5.0" }
|
||||
solana-logger = { path = "../logger", version = "1.5.13" }
|
||||
solana-runtime = { path = "../runtime", version = "1.5.13" }
|
||||
solana-measure = { path = "../measure", version = "1.5.13" }
|
||||
solana-sdk = { path = "../sdk", version = "1.5.13" }
|
||||
solana-version = { path = "../version", version = "1.5.13" }
|
||||
rand = "0.7.0"
|
||||
clap = "2.33.1"
|
||||
crossbeam-channel = "0.4"
|
||||
|
@@ -1,14 +1,15 @@
|
||||
#![allow(clippy::integer_arithmetic)]
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
use clap::{crate_description, crate_name, value_t, App, Arg};
|
||||
use rayon::prelude::*;
|
||||
use solana_measure::measure::Measure;
|
||||
use solana_runtime::{
|
||||
accounts::{create_test_accounts, update_accounts, Accounts},
|
||||
accounts::{create_test_accounts, update_accounts_bench, Accounts},
|
||||
accounts_index::Ancestors,
|
||||
};
|
||||
use solana_sdk::{genesis_config::ClusterType, pubkey::Pubkey};
|
||||
use std::env;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
use std::{collections::HashSet, env, fs, path::PathBuf};
|
||||
|
||||
fn main() {
|
||||
solana_logger::setup();
|
||||
@@ -53,10 +54,12 @@ fn main() {
|
||||
|
||||
let path = PathBuf::from(env::var("FARF_DIR").unwrap_or_else(|_| "farf".to_owned()))
|
||||
.join("accounts-bench");
|
||||
println!("cleaning file system: {:?}", path);
|
||||
if fs::remove_dir_all(path.clone()).is_err() {
|
||||
println!("Warning: Couldn't remove {:?}", path);
|
||||
}
|
||||
let accounts = Accounts::new(vec![path], &ClusterType::Testnet);
|
||||
let accounts =
|
||||
Accounts::new_with_config(vec![path], &ClusterType::Testnet, HashSet::new(), false);
|
||||
println!("Creating {} accounts", num_accounts);
|
||||
let mut create_time = Measure::start("create accounts");
|
||||
let pubkeys: Vec<_> = (0..num_slots)
|
||||
@@ -85,6 +88,8 @@ fn main() {
|
||||
ancestors.insert(i as u64, i - 1);
|
||||
accounts.add_root(i as u64);
|
||||
}
|
||||
let mut elapsed = vec![0; iterations];
|
||||
let mut elapsed_store = vec![0; iterations];
|
||||
for x in 0..iterations {
|
||||
if clean {
|
||||
let mut time = Measure::start("clean");
|
||||
@@ -92,19 +97,46 @@ fn main() {
|
||||
time.stop();
|
||||
println!("{}", time);
|
||||
for slot in 0..num_slots {
|
||||
update_accounts(&accounts, &pubkeys, ((x + 1) * num_slots + slot) as u64);
|
||||
update_accounts_bench(&accounts, &pubkeys, ((x + 1) * num_slots + slot) as u64);
|
||||
accounts.add_root((x * num_slots + slot) as u64);
|
||||
}
|
||||
} else {
|
||||
let mut pubkeys: Vec<Pubkey> = vec![];
|
||||
let mut time = Measure::start("hash");
|
||||
let hash = accounts
|
||||
let results = accounts
|
||||
.accounts_db
|
||||
.update_accounts_hash(0, &ancestors, true)
|
||||
.0;
|
||||
.update_accounts_hash(0, &ancestors, true);
|
||||
time.stop();
|
||||
println!("hash: {} {}", hash, time);
|
||||
let mut time_store = Measure::start("hash using store");
|
||||
let results_store = accounts.accounts_db.update_accounts_hash_with_index_option(
|
||||
false,
|
||||
false,
|
||||
solana_sdk::clock::Slot::default(),
|
||||
&ancestors,
|
||||
true,
|
||||
None,
|
||||
);
|
||||
time_store.stop();
|
||||
if results != results_store {
|
||||
error!("results different: \n{:?}\n{:?}", results, results_store);
|
||||
}
|
||||
println!(
|
||||
"hash,{},{},{},{}%",
|
||||
results.0,
|
||||
time,
|
||||
time_store,
|
||||
(time_store.as_us() as f64 / time.as_us() as f64 * 100.0f64) as u32
|
||||
);
|
||||
create_test_accounts(&accounts, &mut pubkeys, 1, 0);
|
||||
elapsed[x] = time.as_us();
|
||||
elapsed_store[x] = time_store.as_us();
|
||||
}
|
||||
}
|
||||
|
||||
for x in elapsed {
|
||||
info!("update_accounts_hash(us),{}", x);
|
||||
}
|
||||
for x in elapsed_store {
|
||||
info!("calculate_accounts_hash_without_index(us),{}", x);
|
||||
}
|
||||
}
|
||||
|
@@ -2,7 +2,7 @@
|
||||
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
|
||||
edition = "2018"
|
||||
name = "solana-banking-bench"
|
||||
version = "1.5.0"
|
||||
version = "1.5.13"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
@@ -14,16 +14,16 @@ crossbeam-channel = "0.4"
|
||||
log = "0.4.11"
|
||||
rand = "0.7.0"
|
||||
rayon = "1.4.0"
|
||||
solana-core = { path = "../core", version = "1.5.0" }
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.5.0" }
|
||||
solana-streamer = { path = "../streamer", version = "1.5.0" }
|
||||
solana-perf = { path = "../perf", version = "1.5.0" }
|
||||
solana-ledger = { path = "../ledger", version = "1.5.0" }
|
||||
solana-logger = { path = "../logger", version = "1.5.0" }
|
||||
solana-runtime = { path = "../runtime", version = "1.5.0" }
|
||||
solana-measure = { path = "../measure", version = "1.5.0" }
|
||||
solana-sdk = { path = "../sdk", version = "1.5.0" }
|
||||
solana-version = { path = "../version", version = "1.5.0" }
|
||||
solana-core = { path = "../core", version = "1.5.13" }
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.5.13" }
|
||||
solana-streamer = { path = "../streamer", version = "1.5.13" }
|
||||
solana-perf = { path = "../perf", version = "1.5.13" }
|
||||
solana-ledger = { path = "../ledger", version = "1.5.13" }
|
||||
solana-logger = { path = "../logger", version = "1.5.13" }
|
||||
solana-runtime = { path = "../runtime", version = "1.5.13" }
|
||||
solana-measure = { path = "../measure", version = "1.5.13" }
|
||||
solana-sdk = { path = "../sdk", version = "1.5.13" }
|
||||
solana-version = { path = "../version", version = "1.5.13" }
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
@@ -1,3 +1,4 @@
|
||||
#![allow(clippy::integer_arithmetic)]
|
||||
use clap::{crate_description, crate_name, value_t, App, Arg};
|
||||
use crossbeam_channel::unbounded;
|
||||
use log::*;
|
||||
@@ -18,7 +19,7 @@ use solana_ledger::{
|
||||
use solana_measure::measure::Measure;
|
||||
use solana_perf::packet::to_packets_chunked;
|
||||
use solana_runtime::{
|
||||
accounts_background_service::ABSRequestSender, bank::Bank, bank_forks::BankForks,
|
||||
accounts_background_service::AbsRequestSender, bank::Bank, bank_forks::BankForks,
|
||||
};
|
||||
use solana_sdk::{
|
||||
hash::Hash,
|
||||
@@ -325,7 +326,7 @@ fn main() {
|
||||
poh_recorder.lock().unwrap().set_bank(&bank);
|
||||
assert!(poh_recorder.lock().unwrap().bank().is_some());
|
||||
if bank.slot() > 32 {
|
||||
bank_forks.set_root(root, &ABSRequestSender::default(), None);
|
||||
bank_forks.set_root(root, &AbsRequestSender::default(), None);
|
||||
root += 1;
|
||||
}
|
||||
debug!(
|
||||
|
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "solana-banks-client"
|
||||
version = "1.5.0"
|
||||
version = "1.5.13"
|
||||
description = "Solana banks client"
|
||||
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
@@ -10,17 +10,20 @@ edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
bincode = "1.3.1"
|
||||
borsh = "0.8.1"
|
||||
borsh-derive = "0.8.1"
|
||||
futures = "0.3"
|
||||
mio = "0.7.6"
|
||||
solana-banks-interface = { path = "../banks-interface", version = "1.5.0" }
|
||||
solana-sdk = { path = "../sdk", version = "1.5.0" }
|
||||
solana-banks-interface = { path = "../banks-interface", version = "1.5.13" }
|
||||
solana-program = { path = "../sdk/program", version = "1.5.13" }
|
||||
solana-sdk = { path = "../sdk", version = "1.5.13" }
|
||||
tarpc = { version = "0.23.0", features = ["full"] }
|
||||
tokio = { version = "0.3", features = ["full"] }
|
||||
tokio = { version = "0.3.5", features = ["full"] }
|
||||
tokio-serde = { version = "0.6", features = ["bincode"] }
|
||||
|
||||
[dev-dependencies]
|
||||
solana-runtime = { path = "../runtime", version = "1.5.0" }
|
||||
solana-banks-server = { path = "../banks-server", version = "1.5.0" }
|
||||
solana-runtime = { path = "../runtime", version = "1.5.13" }
|
||||
solana-banks-server = { path = "../banks-server", version = "1.5.13" }
|
||||
|
||||
[lib]
|
||||
crate-type = ["lib"]
|
||||
|
@@ -5,19 +5,18 @@
|
||||
//! but they are undocumented, may change over time, and are generally more
|
||||
//! cumbersome to use.
|
||||
|
||||
use borsh::BorshDeserialize;
|
||||
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,
|
||||
};
|
||||
use solana_sdk::{
|
||||
account::{from_account, Account},
|
||||
clock::Slot,
|
||||
commitment_config::CommitmentLevel,
|
||||
fee_calculator::FeeCalculator,
|
||||
hash::Hash,
|
||||
pubkey::Pubkey,
|
||||
rent::Rent,
|
||||
signature::Signature,
|
||||
sysvar,
|
||||
transaction::{self, Transaction},
|
||||
transport,
|
||||
};
|
||||
@@ -122,7 +121,7 @@ impl BanksClient {
|
||||
pub fn get_fees(
|
||||
&mut self,
|
||||
) -> impl Future<Output = io::Result<(FeeCalculator, Hash, Slot)>> + '_ {
|
||||
self.get_fees_with_commitment_and_context(context::current(), CommitmentLevel::Root)
|
||||
self.get_fees_with_commitment_and_context(context::current(), CommitmentLevel::default())
|
||||
}
|
||||
|
||||
/// Return the cluster rent
|
||||
@@ -196,7 +195,7 @@ impl BanksClient {
|
||||
/// 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.
|
||||
pub fn get_root_slot(&mut self) -> impl Future<Output = io::Result<Slot>> + '_ {
|
||||
self.get_slot_with_context(context::current(), CommitmentLevel::Root)
|
||||
self.get_slot_with_context(context::current(), CommitmentLevel::default())
|
||||
}
|
||||
|
||||
/// Return the account at the given address at the slot corresponding to the given
|
||||
@@ -218,6 +217,33 @@ impl BanksClient {
|
||||
self.get_account_with_commitment(address, CommitmentLevel::default())
|
||||
}
|
||||
|
||||
/// Return the unpacked account data at the given address
|
||||
/// If the account is not found, an error is returned
|
||||
pub fn get_packed_account_data<T: Pack>(
|
||||
&mut self,
|
||||
address: Pubkey,
|
||||
) -> impl Future<Output = io::Result<T>> + '_ {
|
||||
self.get_account(address).map(|result| {
|
||||
let account =
|
||||
result?.ok_or_else(|| io::Error::new(io::ErrorKind::Other, "Account not found"))?;
|
||||
T::unpack_from_slice(&account.data)
|
||||
.map_err(|_| io::Error::new(io::ErrorKind::Other, "Failed to deserialize account"))
|
||||
})
|
||||
}
|
||||
|
||||
/// Return the unpacked account data at the given address
|
||||
/// If the account is not found, an error is returned
|
||||
pub fn get_account_data_with_borsh<T: BorshDeserialize>(
|
||||
&mut self,
|
||||
address: Pubkey,
|
||||
) -> impl Future<Output = io::Result<T>> + '_ {
|
||||
self.get_account(address).map(|result| {
|
||||
let account =
|
||||
result?.ok_or_else(|| io::Error::new(io::ErrorKind::Other, "account not found"))?;
|
||||
T::try_from_slice(&account.data)
|
||||
})
|
||||
}
|
||||
|
||||
/// Return the balance in lamports of an account at the given address at the slot
|
||||
/// corresponding to the given commitment level.
|
||||
pub fn get_balance_with_commitment(
|
||||
@@ -289,7 +315,10 @@ pub async fn start_tcp_client<T: ToSocketAddrs>(addr: T) -> io::Result<BanksClie
|
||||
mod tests {
|
||||
use super::*;
|
||||
use solana_banks_server::banks_server::start_local_server;
|
||||
use solana_runtime::{bank::Bank, bank_forks::BankForks, genesis_utils::create_genesis_config};
|
||||
use solana_runtime::{
|
||||
bank::Bank, bank_forks::BankForks, commitment::BlockCommitmentCache,
|
||||
genesis_utils::create_genesis_config,
|
||||
};
|
||||
use solana_sdk::{message::Message, signature::Signer, system_instruction};
|
||||
use std::sync::{Arc, RwLock};
|
||||
use tarpc::transport;
|
||||
@@ -308,9 +337,12 @@ mod tests {
|
||||
// `runtime.block_on()` just once, to run all the async code.
|
||||
|
||||
let genesis = create_genesis_config(10);
|
||||
let bank_forks = Arc::new(RwLock::new(BankForks::new(Bank::new(
|
||||
&genesis.genesis_config,
|
||||
))));
|
||||
let bank = Bank::new(&genesis.genesis_config);
|
||||
let slot = bank.slot();
|
||||
let block_commitment_cache = Arc::new(RwLock::new(
|
||||
BlockCommitmentCache::new_for_tests_with_slots(slot, slot),
|
||||
));
|
||||
let bank_forks = Arc::new(RwLock::new(BankForks::new(bank)));
|
||||
|
||||
let bob_pubkey = solana_sdk::pubkey::new_rand();
|
||||
let mint_pubkey = genesis.mint_keypair.pubkey();
|
||||
@@ -318,7 +350,7 @@ mod tests {
|
||||
let message = Message::new(&[instruction], Some(&mint_pubkey));
|
||||
|
||||
Runtime::new()?.block_on(async {
|
||||
let client_transport = start_local_server(&bank_forks).await;
|
||||
let client_transport = start_local_server(bank_forks, block_commitment_cache).await;
|
||||
let mut banks_client = start_client(client_transport).await?;
|
||||
|
||||
let recent_blockhash = banks_client.get_recent_blockhash().await?;
|
||||
@@ -336,9 +368,12 @@ mod tests {
|
||||
// server-side functionality is available to the client.
|
||||
|
||||
let genesis = create_genesis_config(10);
|
||||
let bank_forks = Arc::new(RwLock::new(BankForks::new(Bank::new(
|
||||
&genesis.genesis_config,
|
||||
))));
|
||||
let bank = Bank::new(&genesis.genesis_config);
|
||||
let slot = bank.slot();
|
||||
let block_commitment_cache = Arc::new(RwLock::new(
|
||||
BlockCommitmentCache::new_for_tests_with_slots(slot, slot),
|
||||
));
|
||||
let bank_forks = Arc::new(RwLock::new(BankForks::new(bank)));
|
||||
|
||||
let mint_pubkey = &genesis.mint_keypair.pubkey();
|
||||
let bob_pubkey = solana_sdk::pubkey::new_rand();
|
||||
@@ -346,7 +381,7 @@ mod tests {
|
||||
let message = Message::new(&[instruction], Some(&mint_pubkey));
|
||||
|
||||
Runtime::new()?.block_on(async {
|
||||
let client_transport = start_local_server(&bank_forks).await;
|
||||
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 transaction = Transaction::new(&[&genesis.mint_keypair], message, recent_blockhash);
|
||||
|
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "solana-banks-interface"
|
||||
version = "1.5.0"
|
||||
version = "1.5.13"
|
||||
description = "Solana banks RPC interface"
|
||||
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
@@ -10,12 +10,12 @@ edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
mio = "0.7.6"
|
||||
serde = { version = "1.0.112", features = ["derive"] }
|
||||
solana-sdk = { path = "../sdk", version = "1.5.0" }
|
||||
serde = { version = "1.0.118", features = ["derive"] }
|
||||
solana-sdk = { path = "../sdk", version = "1.5.13" }
|
||||
tarpc = { version = "0.23.0", features = ["full"] }
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { version = "0.3", features = ["full"] }
|
||||
tokio = { version = "0.3.5", features = ["full"] }
|
||||
|
||||
[lib]
|
||||
crate-type = ["lib"]
|
||||
|
@@ -10,11 +10,19 @@ use solana_sdk::{
|
||||
transaction::{self, Transaction, TransactionError},
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub enum TransactionConfirmationStatus {
|
||||
Processed,
|
||||
Confirmed,
|
||||
Finalized,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct TransactionStatus {
|
||||
pub slot: Slot,
|
||||
pub confirmations: Option<usize>, // None = rooted
|
||||
pub err: Option<TransactionError>,
|
||||
pub confirmation_status: Option<TransactionConfirmationStatus>,
|
||||
}
|
||||
|
||||
#[tarpc::service]
|
||||
|
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "solana-banks-server"
|
||||
version = "1.5.0"
|
||||
version = "1.5.13"
|
||||
description = "Solana banks server"
|
||||
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
@@ -13,10 +13,10 @@ bincode = "1.3.1"
|
||||
futures = "0.3"
|
||||
log = "0.4.11"
|
||||
mio = "0.7.6"
|
||||
solana-banks-interface = { path = "../banks-interface", version = "1.5.0" }
|
||||
solana-runtime = { path = "../runtime", version = "1.5.0" }
|
||||
solana-sdk = { path = "../sdk", version = "1.5.0" }
|
||||
solana-metrics = { path = "../metrics", version = "1.5.0" }
|
||||
solana-banks-interface = { path = "../banks-interface", version = "1.5.13" }
|
||||
solana-runtime = { path = "../runtime", version = "1.5.13" }
|
||||
solana-sdk = { path = "../sdk", version = "1.5.13" }
|
||||
solana-metrics = { path = "../metrics", version = "1.5.13" }
|
||||
tarpc = { version = "0.23.0", features = ["full"] }
|
||||
tokio = { version = "0.3", features = ["full"] }
|
||||
tokio-serde = { version = "0.6", features = ["bincode"] }
|
||||
|
@@ -4,7 +4,9 @@ use futures::{
|
||||
future,
|
||||
prelude::stream::{self, StreamExt},
|
||||
};
|
||||
use solana_banks_interface::{Banks, BanksRequest, BanksResponse, TransactionStatus};
|
||||
use solana_banks_interface::{
|
||||
Banks, BanksRequest, BanksResponse, TransactionConfirmationStatus, TransactionStatus,
|
||||
};
|
||||
use solana_runtime::{bank::Bank, bank_forks::BankForks, commitment::BlockCommitmentCache};
|
||||
use solana_sdk::{
|
||||
account::Account,
|
||||
@@ -60,7 +62,7 @@ impl BanksServer {
|
||||
}
|
||||
}
|
||||
|
||||
fn run(bank: &Bank, transaction_receiver: Receiver<TransactionInfo>) {
|
||||
fn run(bank_forks: Arc<RwLock<BankForks>>, transaction_receiver: Receiver<TransactionInfo>) {
|
||||
while let Ok(info) = transaction_receiver.recv() {
|
||||
let mut transaction_infos = vec![info];
|
||||
while let Ok(info) = transaction_receiver.try_recv() {
|
||||
@@ -70,21 +72,28 @@ impl BanksServer {
|
||||
.into_iter()
|
||||
.map(|info| deserialize(&info.wire_transaction).unwrap())
|
||||
.collect();
|
||||
let bank = bank_forks.read().unwrap().working_bank();
|
||||
let _ = bank.process_transactions(&transactions);
|
||||
}
|
||||
}
|
||||
|
||||
/// Useful for unit-testing
|
||||
fn new_loopback(bank_forks: Arc<RwLock<BankForks>>) -> Self {
|
||||
fn new_loopback(
|
||||
bank_forks: Arc<RwLock<BankForks>>,
|
||||
block_commitment_cache: Arc<RwLock<BlockCommitmentCache>>,
|
||||
) -> Self {
|
||||
let (transaction_sender, transaction_receiver) = channel();
|
||||
let bank = bank_forks.read().unwrap().working_bank();
|
||||
let slot = bank.slot();
|
||||
let block_commitment_cache = Arc::new(RwLock::new(
|
||||
BlockCommitmentCache::new_for_tests_with_slots(slot, slot),
|
||||
));
|
||||
{
|
||||
// ensure that the commitment cache and bank are synced
|
||||
let mut w_block_commitment_cache = block_commitment_cache.write().unwrap();
|
||||
w_block_commitment_cache.set_all_slots(slot, slot);
|
||||
}
|
||||
let server_bank_forks = bank_forks.clone();
|
||||
Builder::new()
|
||||
.name("solana-bank-forks-client".to_string())
|
||||
.spawn(move || Self::run(&bank, transaction_receiver))
|
||||
.spawn(move || Self::run(server_bank_forks, transaction_receiver))
|
||||
.unwrap();
|
||||
Self::new(bank_forks, block_commitment_cache, transaction_sender)
|
||||
}
|
||||
@@ -165,11 +174,17 @@ impl Banks for BanksServer {
|
||||
_: Context,
|
||||
signature: Signature,
|
||||
) -> Option<TransactionStatus> {
|
||||
let bank = self.bank(CommitmentLevel::Recent);
|
||||
let bank = self.bank(CommitmentLevel::Processed);
|
||||
let (slot, status) = bank.get_signature_status_slot(&signature)?;
|
||||
let r_block_commitment_cache = self.block_commitment_cache.read().unwrap();
|
||||
|
||||
let confirmations = if r_block_commitment_cache.root() >= slot {
|
||||
let optimistically_confirmed_bank = self.bank(CommitmentLevel::Confirmed);
|
||||
let optimistically_confirmed =
|
||||
optimistically_confirmed_bank.get_signature_status_slot(&signature);
|
||||
|
||||
let confirmations = if r_block_commitment_cache.root() >= slot
|
||||
&& r_block_commitment_cache.highest_confirmed_root() >= slot
|
||||
{
|
||||
None
|
||||
} else {
|
||||
r_block_commitment_cache
|
||||
@@ -180,6 +195,13 @@ impl Banks for BanksServer {
|
||||
slot,
|
||||
confirmations,
|
||||
err: status.err(),
|
||||
confirmation_status: if confirmations.is_none() {
|
||||
Some(TransactionConfirmationStatus::Finalized)
|
||||
} else if optimistically_confirmed.is_some() {
|
||||
Some(TransactionConfirmationStatus::Confirmed)
|
||||
} else {
|
||||
Some(TransactionConfirmationStatus::Processed)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -225,9 +247,10 @@ impl Banks for BanksServer {
|
||||
}
|
||||
|
||||
pub async fn start_local_server(
|
||||
bank_forks: &Arc<RwLock<BankForks>>,
|
||||
bank_forks: Arc<RwLock<BankForks>>,
|
||||
block_commitment_cache: Arc<RwLock<BlockCommitmentCache>>,
|
||||
) -> UnboundedChannel<Response<BanksResponse>, ClientMessage<BanksRequest>> {
|
||||
let banks_server = BanksServer::new_loopback(bank_forks.clone());
|
||||
let banks_server = BanksServer::new_loopback(bank_forks, block_commitment_cache);
|
||||
let (client_transport, server_transport) = transport::channel::unbounded();
|
||||
let server = server::new(server::Config::default())
|
||||
.incoming(stream::once(future::ready(server_transport)))
|
||||
|
@@ -1,3 +1,4 @@
|
||||
#![allow(clippy::integer_arithmetic)]
|
||||
pub mod banks_server;
|
||||
pub mod rpc_banks_service;
|
||||
pub mod send_transaction_service;
|
||||
|
@@ -2,7 +2,7 @@
|
||||
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
|
||||
edition = "2018"
|
||||
name = "solana-bench-exchange"
|
||||
version = "1.5.0"
|
||||
version = "1.5.13"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
@@ -18,21 +18,21 @@ rand = "0.7.0"
|
||||
rayon = "1.4.0"
|
||||
serde_json = "1.0.56"
|
||||
serde_yaml = "0.8.13"
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.5.0" }
|
||||
solana-core = { path = "../core", version = "1.5.0" }
|
||||
solana-genesis = { path = "../genesis", version = "1.5.0" }
|
||||
solana-client = { path = "../client", version = "1.5.0" }
|
||||
solana-faucet = { path = "../faucet", version = "1.5.0" }
|
||||
solana-exchange-program = { path = "../programs/exchange", version = "1.5.0" }
|
||||
solana-logger = { path = "../logger", version = "1.5.0" }
|
||||
solana-metrics = { path = "../metrics", version = "1.5.0" }
|
||||
solana-net-utils = { path = "../net-utils", version = "1.5.0" }
|
||||
solana-runtime = { path = "../runtime", version = "1.5.0" }
|
||||
solana-sdk = { path = "../sdk", version = "1.5.0" }
|
||||
solana-version = { path = "../version", version = "1.5.0" }
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.5.13" }
|
||||
solana-core = { path = "../core", version = "1.5.13" }
|
||||
solana-genesis = { path = "../genesis", version = "1.5.13" }
|
||||
solana-client = { path = "../client", version = "1.5.13" }
|
||||
solana-faucet = { path = "../faucet", version = "1.5.13" }
|
||||
solana-exchange-program = { path = "../programs/exchange", version = "1.5.13" }
|
||||
solana-logger = { path = "../logger", version = "1.5.13" }
|
||||
solana-metrics = { path = "../metrics", version = "1.5.13" }
|
||||
solana-net-utils = { path = "../net-utils", version = "1.5.13" }
|
||||
solana-runtime = { path = "../runtime", version = "1.5.13" }
|
||||
solana-sdk = { path = "../sdk", version = "1.5.13" }
|
||||
solana-version = { path = "../version", version = "1.5.13" }
|
||||
|
||||
[dev-dependencies]
|
||||
solana-local-cluster = { path = "../local-cluster", version = "1.5.0" }
|
||||
solana-local-cluster = { path = "../local-cluster", version = "1.5.13" }
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
@@ -1,4 +1,5 @@
|
||||
#![allow(clippy::useless_attribute)]
|
||||
#![allow(clippy::integer_arithmetic)]
|
||||
|
||||
use crate::order_book::*;
|
||||
use itertools::izip;
|
||||
@@ -390,7 +391,7 @@ fn swapper<T>(
|
||||
while client
|
||||
.get_balance_with_commitment(
|
||||
&trade_infos[trade_index].trade_account,
|
||||
CommitmentConfig::recent(),
|
||||
CommitmentConfig::processed(),
|
||||
)
|
||||
.unwrap_or(0)
|
||||
== 0
|
||||
@@ -445,7 +446,7 @@ fn swapper<T>(
|
||||
account_group = (account_group + 1) % account_groups as usize;
|
||||
|
||||
let (blockhash, _fee_calculator, _last_valid_slot) = client
|
||||
.get_recent_blockhash_with_commitment(CommitmentConfig::recent())
|
||||
.get_recent_blockhash_with_commitment(CommitmentConfig::processed())
|
||||
.expect("Failed to get blockhash");
|
||||
let to_swap_txs: Vec<_> = to_swap
|
||||
.par_iter()
|
||||
@@ -571,7 +572,7 @@ fn trader<T>(
|
||||
account_group = (account_group + 1) % account_groups as usize;
|
||||
|
||||
let (blockhash, _fee_calculator, _last_valid_slot) = client
|
||||
.get_recent_blockhash_with_commitment(CommitmentConfig::recent())
|
||||
.get_recent_blockhash_with_commitment(CommitmentConfig::processed())
|
||||
.expect("Failed to get blockhash");
|
||||
|
||||
trades.chunks(chunk_size).for_each(|chunk| {
|
||||
@@ -658,7 +659,7 @@ where
|
||||
{
|
||||
for s in &tx.signatures {
|
||||
if let Ok(Some(r)) =
|
||||
sync_client.get_signature_status_with_commitment(s, CommitmentConfig::recent())
|
||||
sync_client.get_signature_status_with_commitment(s, CommitmentConfig::processed())
|
||||
{
|
||||
match r {
|
||||
Ok(_) => {
|
||||
@@ -681,7 +682,7 @@ fn verify_funding_transfer<T: SyncClient + ?Sized>(
|
||||
if verify_transaction(client, tx) {
|
||||
for a in &tx.message().account_keys[1..] {
|
||||
if client
|
||||
.get_balance_with_commitment(a, CommitmentConfig::recent())
|
||||
.get_balance_with_commitment(a, CommitmentConfig::processed())
|
||||
.unwrap_or(0)
|
||||
>= amount
|
||||
{
|
||||
@@ -764,7 +765,7 @@ pub fn fund_keys<T: Client>(client: &T, source: &Keypair, dests: &[Arc<Keypair>]
|
||||
);
|
||||
|
||||
let (blockhash, _fee_calculator, _last_valid_slot) = client
|
||||
.get_recent_blockhash_with_commitment(CommitmentConfig::recent())
|
||||
.get_recent_blockhash_with_commitment(CommitmentConfig::processed())
|
||||
.expect("blockhash");
|
||||
to_fund_txs.par_iter_mut().for_each(|(k, tx)| {
|
||||
tx.sign(&[*k], blockhash);
|
||||
@@ -803,7 +804,7 @@ pub fn fund_keys<T: Client>(client: &T, source: &Keypair, dests: &[Arc<Keypair>]
|
||||
funded.append(&mut new_funded);
|
||||
funded.retain(|(k, b)| {
|
||||
client
|
||||
.get_balance_with_commitment(&k.pubkey(), CommitmentConfig::recent())
|
||||
.get_balance_with_commitment(&k.pubkey(), CommitmentConfig::processed())
|
||||
.unwrap_or(0)
|
||||
> lamports
|
||||
&& *b > lamports
|
||||
@@ -857,7 +858,7 @@ pub fn create_token_accounts<T: Client>(
|
||||
let mut retries = 0;
|
||||
while !to_create_txs.is_empty() {
|
||||
let (blockhash, _fee_calculator, _last_valid_slot) = client
|
||||
.get_recent_blockhash_with_commitment(CommitmentConfig::recent())
|
||||
.get_recent_blockhash_with_commitment(CommitmentConfig::processed())
|
||||
.expect("Failed to get blockhash");
|
||||
to_create_txs
|
||||
.par_iter_mut()
|
||||
@@ -903,7 +904,7 @@ pub fn create_token_accounts<T: Client>(
|
||||
let mut new_notfunded: Vec<(&Arc<Keypair>, &Keypair)> = vec![];
|
||||
for f in ¬funded {
|
||||
if client
|
||||
.get_balance_with_commitment(&f.1.pubkey(), CommitmentConfig::recent())
|
||||
.get_balance_with_commitment(&f.1.pubkey(), CommitmentConfig::processed())
|
||||
.unwrap_or(0)
|
||||
== 0
|
||||
{
|
||||
@@ -968,7 +969,7 @@ pub fn airdrop_lamports<T: Client>(
|
||||
id: &Keypair,
|
||||
amount: u64,
|
||||
) {
|
||||
let balance = client.get_balance_with_commitment(&id.pubkey(), CommitmentConfig::recent());
|
||||
let balance = client.get_balance_with_commitment(&id.pubkey(), CommitmentConfig::processed());
|
||||
let balance = balance.unwrap_or(0);
|
||||
if balance >= amount {
|
||||
return;
|
||||
@@ -986,7 +987,7 @@ pub fn airdrop_lamports<T: Client>(
|
||||
let mut tries = 0;
|
||||
loop {
|
||||
let (blockhash, _fee_calculator, _last_valid_slot) = client
|
||||
.get_recent_blockhash_with_commitment(CommitmentConfig::recent())
|
||||
.get_recent_blockhash_with_commitment(CommitmentConfig::processed())
|
||||
.expect("Failed to get blockhash");
|
||||
match request_airdrop_transaction(&faucet_addr, &id.pubkey(), amount_to_drop, blockhash) {
|
||||
Ok(transaction) => {
|
||||
@@ -995,14 +996,14 @@ pub fn airdrop_lamports<T: Client>(
|
||||
for _ in 0..30 {
|
||||
if let Ok(Some(_)) = client.get_signature_status_with_commitment(
|
||||
&signature,
|
||||
CommitmentConfig::recent(),
|
||||
CommitmentConfig::processed(),
|
||||
) {
|
||||
break;
|
||||
}
|
||||
sleep(Duration::from_millis(100));
|
||||
}
|
||||
if client
|
||||
.get_balance_with_commitment(&id.pubkey(), CommitmentConfig::recent())
|
||||
.get_balance_with_commitment(&id.pubkey(), CommitmentConfig::processed())
|
||||
.unwrap_or(0)
|
||||
>= amount
|
||||
{
|
||||
|
@@ -1,3 +1,4 @@
|
||||
#![allow(clippy::integer_arithmetic)]
|
||||
pub mod bench;
|
||||
mod cli;
|
||||
pub mod order_book;
|
||||
|
@@ -5,7 +5,7 @@ use solana_core::validator::ValidatorConfig;
|
||||
use solana_exchange_program::exchange_processor::process_instruction;
|
||||
use solana_exchange_program::id;
|
||||
use solana_exchange_program::solana_exchange_program;
|
||||
use solana_faucet::faucet::run_local_faucet;
|
||||
use solana_faucet::faucet::run_local_faucet_with_port;
|
||||
use solana_local_cluster::local_cluster::{ClusterConfig, LocalCluster};
|
||||
use solana_runtime::bank::Bank;
|
||||
use solana_runtime::bank_client::BankClient;
|
||||
@@ -57,8 +57,11 @@ fn test_exchange_local_cluster() {
|
||||
);
|
||||
|
||||
let (addr_sender, addr_receiver) = channel();
|
||||
run_local_faucet(faucet_keypair, addr_sender, Some(1_000_000_000_000));
|
||||
let faucet_addr = addr_receiver.recv_timeout(Duration::from_secs(2)).unwrap();
|
||||
run_local_faucet_with_port(faucet_keypair, addr_sender, Some(1_000_000_000_000), 0);
|
||||
let faucet_addr = addr_receiver
|
||||
.recv_timeout(Duration::from_secs(2))
|
||||
.expect("run_local_faucet")
|
||||
.expect("faucet_addr");
|
||||
|
||||
info!("Connecting to the cluster");
|
||||
let nodes =
|
||||
|
@@ -2,7 +2,7 @@
|
||||
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
|
||||
edition = "2018"
|
||||
name = "solana-bench-streamer"
|
||||
version = "1.5.0"
|
||||
version = "1.5.13"
|
||||
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.5.0" }
|
||||
solana-streamer = { path = "../streamer", version = "1.5.0" }
|
||||
solana-logger = { path = "../logger", version = "1.5.0" }
|
||||
solana-net-utils = { path = "../net-utils", version = "1.5.0" }
|
||||
solana-version = { path = "../version", version = "1.5.0" }
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.5.13" }
|
||||
solana-streamer = { path = "../streamer", version = "1.5.13" }
|
||||
solana-logger = { path = "../logger", version = "1.5.13" }
|
||||
solana-net-utils = { path = "../net-utils", version = "1.5.13" }
|
||||
solana-version = { path = "../version", version = "1.5.13" }
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
@@ -1,3 +1,4 @@
|
||||
#![allow(clippy::integer_arithmetic)]
|
||||
use clap::{crate_description, crate_name, App, Arg};
|
||||
use solana_streamer::packet::{Packet, Packets, PacketsRecycler, PACKET_DATA_SIZE};
|
||||
use solana_streamer::streamer::{receiver, PacketReceiver};
|
||||
@@ -74,7 +75,7 @@ fn main() -> Result<()> {
|
||||
|
||||
let mut read_channels = Vec::new();
|
||||
let mut read_threads = Vec::new();
|
||||
let recycler = PacketsRecycler::default();
|
||||
let recycler = PacketsRecycler::new_without_limit("bench-streamer-recycler-shrink-stats");
|
||||
for _ in 0..num_sockets {
|
||||
let read = solana_net_utils::bind_to(ip_addr, port, false).unwrap();
|
||||
read.set_read_timeout(Some(Duration::new(1, 0))).unwrap();
|
||||
@@ -90,6 +91,7 @@ fn main() -> Result<()> {
|
||||
s_reader,
|
||||
recycler.clone(),
|
||||
"bench-streamer-test",
|
||||
1,
|
||||
));
|
||||
}
|
||||
|
||||
|
@@ -2,7 +2,7 @@
|
||||
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
|
||||
edition = "2018"
|
||||
name = "solana-bench-tps"
|
||||
version = "1.5.0"
|
||||
version = "1.5.13"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
@@ -15,23 +15,23 @@ log = "0.4.11"
|
||||
rayon = "1.4.0"
|
||||
serde_json = "1.0.56"
|
||||
serde_yaml = "0.8.13"
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.5.0" }
|
||||
solana-core = { path = "../core", version = "1.5.0" }
|
||||
solana-genesis = { path = "../genesis", version = "1.5.0" }
|
||||
solana-client = { path = "../client", version = "1.5.0" }
|
||||
solana-faucet = { path = "../faucet", version = "1.5.0" }
|
||||
solana-logger = { path = "../logger", version = "1.5.0" }
|
||||
solana-metrics = { path = "../metrics", version = "1.5.0" }
|
||||
solana-measure = { path = "../measure", version = "1.5.0" }
|
||||
solana-net-utils = { path = "../net-utils", version = "1.5.0" }
|
||||
solana-runtime = { path = "../runtime", version = "1.5.0" }
|
||||
solana-sdk = { path = "../sdk", version = "1.5.0" }
|
||||
solana-version = { path = "../version", version = "1.5.0" }
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.5.13" }
|
||||
solana-core = { path = "../core", version = "1.5.13" }
|
||||
solana-genesis = { path = "../genesis", version = "1.5.13" }
|
||||
solana-client = { path = "../client", version = "1.5.13" }
|
||||
solana-faucet = { path = "../faucet", version = "1.5.13" }
|
||||
solana-logger = { path = "../logger", version = "1.5.13" }
|
||||
solana-metrics = { path = "../metrics", version = "1.5.13" }
|
||||
solana-measure = { path = "../measure", version = "1.5.13" }
|
||||
solana-net-utils = { path = "../net-utils", version = "1.5.13" }
|
||||
solana-runtime = { path = "../runtime", version = "1.5.13" }
|
||||
solana-sdk = { path = "../sdk", version = "1.5.13" }
|
||||
solana-version = { path = "../version", version = "1.5.13" }
|
||||
|
||||
[dev-dependencies]
|
||||
serial_test = "0.4.0"
|
||||
serial_test_derive = "0.4.0"
|
||||
solana-local-cluster = { path = "../local-cluster", version = "1.5.0" }
|
||||
solana-local-cluster = { path = "../local-cluster", version = "1.5.13" }
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
@@ -48,7 +48,7 @@ pub type SharedTransactions = Arc<RwLock<VecDeque<Vec<(Transaction, u64)>>>>;
|
||||
|
||||
fn get_recent_blockhash<T: Client>(client: &T) -> (Hash, FeeCalculator) {
|
||||
loop {
|
||||
match client.get_recent_blockhash_with_commitment(CommitmentConfig::recent()) {
|
||||
match client.get_recent_blockhash_with_commitment(CommitmentConfig::processed()) {
|
||||
Ok((blockhash, fee_calculator, _last_valid_slot)) => {
|
||||
return (blockhash, fee_calculator)
|
||||
}
|
||||
@@ -497,7 +497,7 @@ fn do_tx_transfers<T: Client>(
|
||||
|
||||
fn verify_funding_transfer<T: Client>(client: &Arc<T>, tx: &Transaction, amount: u64) -> bool {
|
||||
for a in &tx.message().account_keys[1..] {
|
||||
match client.get_balance_with_commitment(a, CommitmentConfig::recent()) {
|
||||
match client.get_balance_with_commitment(a, CommitmentConfig::processed()) {
|
||||
Ok(balance) => return balance >= amount,
|
||||
Err(err) => error!("failed to get balance {:?}", err),
|
||||
}
|
||||
@@ -762,7 +762,7 @@ pub fn airdrop_lamports<T: Client>(
|
||||
};
|
||||
|
||||
let current_balance = client
|
||||
.get_balance_with_commitment(&id.pubkey(), CommitmentConfig::recent())
|
||||
.get_balance_with_commitment(&id.pubkey(), CommitmentConfig::processed())
|
||||
.unwrap_or_else(|e| {
|
||||
info!("airdrop error {}", e);
|
||||
starting_balance
|
||||
@@ -967,7 +967,7 @@ mod tests {
|
||||
for kp in &keypairs {
|
||||
assert_eq!(
|
||||
client
|
||||
.get_balance_with_commitment(&kp.pubkey(), CommitmentConfig::recent())
|
||||
.get_balance_with_commitment(&kp.pubkey(), CommitmentConfig::processed())
|
||||
.unwrap(),
|
||||
lamports
|
||||
);
|
||||
|
@@ -1,2 +1,3 @@
|
||||
#![allow(clippy::integer_arithmetic)]
|
||||
pub mod bench;
|
||||
pub mod cli;
|
||||
|
@@ -1,3 +1,4 @@
|
||||
#![allow(clippy::integer_arithmetic)]
|
||||
use log::*;
|
||||
use solana_bench_tps::bench::{do_bench_tps, generate_and_fund_keypairs, generate_keypairs};
|
||||
use solana_bench_tps::cli;
|
||||
|
@@ -1,10 +1,11 @@
|
||||
#![allow(clippy::integer_arithmetic)]
|
||||
use serial_test_derive::serial;
|
||||
use solana_bench_tps::bench::{do_bench_tps, generate_and_fund_keypairs};
|
||||
use solana_bench_tps::cli::Config;
|
||||
use solana_client::thin_client::create_client;
|
||||
use solana_core::cluster_info::VALIDATOR_PORT_RANGE;
|
||||
use solana_core::validator::ValidatorConfig;
|
||||
use solana_faucet::faucet::run_local_faucet;
|
||||
use solana_faucet::faucet::run_local_faucet_with_port;
|
||||
use solana_local_cluster::local_cluster::{ClusterConfig, LocalCluster};
|
||||
use solana_sdk::signature::{Keypair, Signer};
|
||||
use std::sync::{mpsc::channel, Arc};
|
||||
@@ -36,8 +37,11 @@ fn test_bench_tps_local_cluster(config: Config) {
|
||||
));
|
||||
|
||||
let (addr_sender, addr_receiver) = channel();
|
||||
run_local_faucet(faucet_keypair, addr_sender, None);
|
||||
let faucet_addr = addr_receiver.recv_timeout(Duration::from_secs(2)).unwrap();
|
||||
run_local_faucet_with_port(faucet_keypair, addr_sender, None, 0);
|
||||
let faucet_addr = addr_receiver
|
||||
.recv_timeout(Duration::from_secs(2))
|
||||
.expect("run_local_faucet")
|
||||
.expect("faucet_addr");
|
||||
|
||||
let lamports_per_account = 100;
|
||||
|
||||
|
47
ci/do-audit.sh
Executable file
47
ci/do-audit.sh
Executable file
@@ -0,0 +1,47 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
|
||||
here="$(dirname "$0")"
|
||||
src_root="$(readlink -f "${here}/..")"
|
||||
|
||||
cd "${src_root}"
|
||||
|
||||
source ci/rust-version.sh stable
|
||||
|
||||
cargo_audit_ignores=(
|
||||
# failure is officially deprecated/unmaintained
|
||||
#
|
||||
# Blocked on multiple upstream crates removing their `failure` dependency.
|
||||
--ignore RUSTSEC-2020-0036
|
||||
|
||||
# `net2` crate has been deprecated; use `socket2` instead
|
||||
#
|
||||
# Blocked on https://github.com/paritytech/jsonrpc/issues/575
|
||||
--ignore RUSTSEC-2020-0016
|
||||
|
||||
# stdweb is unmaintained
|
||||
#
|
||||
# Blocked on multiple upstream crates removing their `stdweb` dependency.
|
||||
--ignore RUSTSEC-2020-0056
|
||||
|
||||
# Potential segfault in the time crate
|
||||
#
|
||||
# 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
|
||||
|
||||
# hyper is upgraded on master/v1.6 but not for v1.5
|
||||
--ignore RUSTSEC-2021-0020
|
||||
|
||||
# generic-array: arr! macro erases lifetimes
|
||||
#
|
||||
# ed25519-dalek and libsecp256k1 not upgraded for v1.5
|
||||
--ignore RUSTSEC-2020-0146
|
||||
|
||||
)
|
||||
scripts/cargo-for-all-lock-files.sh +"$rust_stable" audit "${cargo_audit_ignores[@]}"
|
@@ -1,4 +1,4 @@
|
||||
FROM solanalabs/rust:1.48.0
|
||||
FROM solanalabs/rust:1.49.0
|
||||
ARG date
|
||||
|
||||
RUN set -x \
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# Note: when the rust version is changed also modify
|
||||
# ci/rust-version.sh to pick up the new image tag
|
||||
FROM rust:1.48.0
|
||||
FROM rust:1.49.0
|
||||
|
||||
# Add Google Protocol Buffers for Libra's metrics library.
|
||||
ENV PROTOC_VERSION 3.8.0
|
||||
|
@@ -80,7 +80,7 @@ nodes=(
|
||||
"multinode-demo/validator.sh \
|
||||
--enable-rpc-exit \
|
||||
--no-restart \
|
||||
--dynamic-port-range 8050-8100
|
||||
--dynamic-port-range 8050-8100 \
|
||||
--init-complete-file init-complete-node1.log \
|
||||
--rpc-port 18899"
|
||||
)
|
||||
|
@@ -19,6 +19,7 @@ declare prints=(
|
||||
# Parts of the tree that are expected to be print free
|
||||
declare print_free_tree=(
|
||||
':core/src/**.rs'
|
||||
':^core/src/validator.rs'
|
||||
':faucet/src/**.rs'
|
||||
':ledger/src/**.rs'
|
||||
':metrics/src/**.rs'
|
||||
|
@@ -18,13 +18,13 @@
|
||||
if [[ -n $RUST_STABLE_VERSION ]]; then
|
||||
stable_version="$RUST_STABLE_VERSION"
|
||||
else
|
||||
stable_version=1.48.0
|
||||
stable_version=1.49.0
|
||||
fi
|
||||
|
||||
if [[ -n $RUST_NIGHTLY_VERSION ]]; then
|
||||
nightly_version="$RUST_NIGHTLY_VERSION"
|
||||
else
|
||||
nightly_version=2020-12-13
|
||||
nightly_version=2021-01-23
|
||||
fi
|
||||
|
||||
|
||||
|
@@ -76,7 +76,7 @@ RestartForceExitStatus=SIGPIPE
|
||||
TimeoutStartSec=10
|
||||
TimeoutStopSec=0
|
||||
KillMode=process
|
||||
LimitNOFILE=500000
|
||||
LimitNOFILE=700000
|
||||
|
||||
[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 500000" > /etc/security/limits.d/90-solana-nofiles.conf
|
||||
echo "* - nofile 700000" > /etc/security/limits.d/90-solana-nofiles.conf
|
||||
|
||||
|
@@ -23,6 +23,10 @@ fi
|
||||
BENCH_FILE=bench_output.log
|
||||
BENCH_ARTIFACT=current_bench_results.log
|
||||
|
||||
# solana-keygen required when building C programs
|
||||
_ "$cargo" build --manifest-path=keygen/Cargo.toml
|
||||
export PATH="$PWD/target/debug":$PATH
|
||||
|
||||
# Clear the C dependency files, if dependeny 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
|
||||
|
@@ -12,6 +12,16 @@ cargo="$(readlink -f "./cargo")"
|
||||
|
||||
scripts/increment-cargo-version.sh check
|
||||
|
||||
# Disallow uncommitted Cargo.lock changes
|
||||
(
|
||||
_ scripts/cargo-for-all-lock-files.sh tree
|
||||
set +e
|
||||
if ! _ git diff --exit-code; then
|
||||
echo -e "\nError: Uncommitted Cargo.lock changes" 1>&2
|
||||
exit 1
|
||||
fi
|
||||
)
|
||||
|
||||
echo --- build environment
|
||||
(
|
||||
set -x
|
||||
@@ -52,50 +62,24 @@ else
|
||||
fi
|
||||
|
||||
_ ci/order-crates-for-publishing.py
|
||||
_ "$cargo" stable fmt --all -- --check
|
||||
|
||||
# -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
|
||||
_ "$cargo" nightly clippy -Zunstable-options --workspace --all-targets -- --deny=warnings --deny=clippy::integer_arithmetic
|
||||
|
||||
cargo_audit_ignores=(
|
||||
# failure is officially deprecated/unmaintained
|
||||
#
|
||||
# Blocked on multiple upstream crates removing their `failure` dependency.
|
||||
--ignore RUSTSEC-2020-0036
|
||||
_ "$cargo" stable fmt --all -- --check
|
||||
|
||||
# `net2` crate has been deprecated; use `socket2` instead
|
||||
#
|
||||
# Blocked on https://github.com/paritytech/jsonrpc/issues/575
|
||||
--ignore RUSTSEC-2020-0016
|
||||
|
||||
# stdweb is unmaintained
|
||||
#
|
||||
# Blocked on multiple upstream crates removing their `stdweb` dependency.
|
||||
--ignore RUSTSEC-2020-0056
|
||||
|
||||
# Potential segfault in the time crate
|
||||
#
|
||||
# Blocked on multiple crates updating `time` to >= 0.2.23
|
||||
--ignore RUSTSEC-2020-0071
|
||||
|
||||
# memmap crate is unmaintained
|
||||
#
|
||||
# Blocked on us releasing new solana crates
|
||||
--ignore RUSTSEC-2020-0077
|
||||
)
|
||||
_ scripts/cargo-for-all-lock-files.sh +"$rust_stable" audit "${cargo_audit_ignores[@]}"
|
||||
_ ci/do-audit.sh
|
||||
|
||||
{
|
||||
cd programs/bpf
|
||||
_ "$cargo" stable audit
|
||||
for project in rust/*/ ; do
|
||||
echo "+++ do_bpf_checks $project"
|
||||
(
|
||||
cd "$project"
|
||||
_ "$cargo" nightly clippy -- --deny=warnings --allow=clippy::missing_safety_doc
|
||||
_ "$cargo" stable fmt -- --check
|
||||
_ "$cargo" nightly test
|
||||
_ "$cargo" nightly clippy -- --deny=warnings --allow=clippy::missing_safety_doc
|
||||
)
|
||||
done
|
||||
}
|
||||
|
@@ -39,6 +39,10 @@ test-stable)
|
||||
_ "$cargo" stable test --jobs "$NPROC" --all --exclude solana-local-cluster ${V:+--verbose} -- --nocapture
|
||||
;;
|
||||
test-stable-perf)
|
||||
# solana-keygen required when building C programs
|
||||
_ "$cargo" build --manifest-path=keygen/Cargo.toml
|
||||
export PATH="$PWD/target/debug":$PATH
|
||||
|
||||
# BPF solana-sdk legacy compile test
|
||||
./cargo-build-bpf --manifest-path sdk/Cargo.toml
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "solana-clap-utils"
|
||||
version = "1.5.0"
|
||||
version = "1.5.13"
|
||||
description = "Solana utilities for the clap"
|
||||
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
@@ -11,8 +11,8 @@ edition = "2018"
|
||||
[dependencies]
|
||||
clap = "2.33.0"
|
||||
rpassword = "4.0"
|
||||
solana-remote-wallet = { path = "../remote-wallet", version = "1.5.0" }
|
||||
solana-sdk = { path = "../sdk", version = "1.5.0" }
|
||||
solana-remote-wallet = { path = "../remote-wallet", version = "1.5.13" }
|
||||
solana-sdk = { path = "../sdk", version = "1.5.13" }
|
||||
thiserror = "1.0.21"
|
||||
tiny-bip39 = "0.7.0"
|
||||
url = "2.1.0"
|
||||
|
@@ -1,22 +0,0 @@
|
||||
use crate::ArgConstant;
|
||||
use clap::Arg;
|
||||
|
||||
pub const COMMITMENT_ARG: ArgConstant<'static> = ArgConstant {
|
||||
name: "commitment",
|
||||
long: "commitment",
|
||||
help: "Return information at the selected commitment level",
|
||||
};
|
||||
|
||||
pub fn commitment_arg<'a, 'b>() -> Arg<'a, 'b> {
|
||||
commitment_arg_with_default("recent")
|
||||
}
|
||||
|
||||
pub fn commitment_arg_with_default<'a, 'b>(default_value: &'static str) -> Arg<'a, 'b> {
|
||||
Arg::with_name(COMMITMENT_ARG.name)
|
||||
.long(COMMITMENT_ARG.long)
|
||||
.takes_value(true)
|
||||
.possible_values(&["recent", "single", "singleGossip", "root", "max"])
|
||||
.default_value(default_value)
|
||||
.value_name("COMMITMENT_LEVEL")
|
||||
.help(COMMITMENT_ARG.help)
|
||||
}
|
@@ -167,12 +167,12 @@ pub fn resolve_signer(
|
||||
name: &str,
|
||||
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
|
||||
) -> Result<Option<String>, Box<dyn std::error::Error>> {
|
||||
Ok(resolve_signer_from_path(
|
||||
resolve_signer_from_path(
|
||||
matches,
|
||||
matches.value_of(name).unwrap(),
|
||||
name,
|
||||
wallet_manager,
|
||||
)?)
|
||||
)
|
||||
}
|
||||
|
||||
pub fn lamports_of_sol(matches: &ArgMatches<'_>, name: &str) -> Option<u64> {
|
||||
@@ -184,14 +184,9 @@ pub fn cluster_type_of(matches: &ArgMatches<'_>, name: &str) -> Option<ClusterTy
|
||||
}
|
||||
|
||||
pub fn commitment_of(matches: &ArgMatches<'_>, name: &str) -> Option<CommitmentConfig> {
|
||||
matches.value_of(name).map(|value| match value {
|
||||
"max" => CommitmentConfig::max(),
|
||||
"recent" => CommitmentConfig::recent(),
|
||||
"root" => CommitmentConfig::root(),
|
||||
"single" => CommitmentConfig::single(),
|
||||
"singleGossip" => CommitmentConfig::single_gossip(),
|
||||
_ => CommitmentConfig::default(),
|
||||
})
|
||||
matches
|
||||
.value_of(name)
|
||||
.map(|value| CommitmentConfig::from_str(value).unwrap_or_default())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@@ -1,9 +1,9 @@
|
||||
use crate::keypair::{parse_keypair_path, KeypairUrl, ASK_KEYWORD};
|
||||
use chrono::DateTime;
|
||||
use solana_sdk::{
|
||||
clock::Slot,
|
||||
clock::{Epoch, Slot},
|
||||
hash::Hash,
|
||||
pubkey::Pubkey,
|
||||
pubkey::{Pubkey, MAX_SEED_LEN},
|
||||
signature::{read_keypair_file, Signature},
|
||||
};
|
||||
use std::fmt::Display;
|
||||
@@ -148,6 +148,40 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_url_or_moniker<T>(string: T) -> Result<(), String>
|
||||
where
|
||||
T: AsRef<str> + Display,
|
||||
{
|
||||
match url::Url::parse(&normalize_to_url_if_moniker(string.as_ref())) {
|
||||
Ok(url) => {
|
||||
if url.has_host() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err("no host provided".to_string())
|
||||
}
|
||||
}
|
||||
Err(err) => Err(format!("{}", err)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn normalize_to_url_if_moniker<T: AsRef<str>>(url_or_moniker: T) -> String {
|
||||
match url_or_moniker.as_ref() {
|
||||
"m" | "mainnet-beta" => "https://api.mainnet-beta.solana.com",
|
||||
"t" | "testnet" => "https://testnet.solana.com",
|
||||
"d" | "devnet" => "https://devnet.solana.com",
|
||||
"l" | "localhost" => "http://localhost:8899",
|
||||
url => url,
|
||||
}
|
||||
.to_string()
|
||||
}
|
||||
|
||||
pub fn is_epoch<T>(epoch: T) -> Result<(), String>
|
||||
where
|
||||
T: AsRef<str> + Display,
|
||||
{
|
||||
is_parsable_generic::<Epoch, _>(epoch)
|
||||
}
|
||||
|
||||
pub fn is_slot<T>(slot: T) -> Result<(), String>
|
||||
where
|
||||
T: AsRef<str> + Display,
|
||||
@@ -257,6 +291,21 @@ where
|
||||
.map(|_| ())
|
||||
}
|
||||
|
||||
pub fn is_derived_address_seed<T>(value: T) -> Result<(), String>
|
||||
where
|
||||
T: AsRef<str> + Display,
|
||||
{
|
||||
let value = value.as_ref();
|
||||
if value.len() > MAX_SEED_LEN {
|
||||
Err(format!(
|
||||
"Address seed must not be longer than {} bytes",
|
||||
MAX_SEED_LEN
|
||||
))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
@@ -58,6 +58,15 @@ impl CliSignerInfo {
|
||||
Some(0)
|
||||
}
|
||||
}
|
||||
pub fn index_of_or_none(&self, pubkey: Option<Pubkey>) -> Option<usize> {
|
||||
if let Some(pubkey) = pubkey {
|
||||
self.signers
|
||||
.iter()
|
||||
.position(|signer| signer.pubkey() == pubkey)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DefaultSigner {
|
||||
|
@@ -23,7 +23,6 @@ impl std::fmt::Debug for DisplayError {
|
||||
}
|
||||
}
|
||||
|
||||
pub mod commitment;
|
||||
pub mod fee_payer;
|
||||
pub mod input_parsers;
|
||||
pub mod input_validators;
|
||||
|
@@ -3,7 +3,7 @@ authors = ["Solana Maintainers <maintainers@solana.foundation>"]
|
||||
edition = "2018"
|
||||
name = "solana-cli-config"
|
||||
description = "Blockchain, Rebuilt for Scale"
|
||||
version = "1.5.0"
|
||||
version = "1.5.13"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
@@ -11,7 +11,7 @@ homepage = "https://solana.com/"
|
||||
[dependencies]
|
||||
dirs-next = "2.0.0"
|
||||
lazy_static = "1.4.0"
|
||||
serde = "1.0.112"
|
||||
serde = "1.0.118"
|
||||
serde_derive = "1.0.103"
|
||||
serde_yaml = "0.8.13"
|
||||
url = "2.1.1"
|
||||
|
@@ -17,9 +17,10 @@ pub struct Config {
|
||||
pub json_rpc_url: String,
|
||||
pub websocket_url: String,
|
||||
pub keypair_path: String,
|
||||
|
||||
#[serde(default)]
|
||||
pub address_labels: HashMap<String, String>,
|
||||
#[serde(default)]
|
||||
pub commitment: String,
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
@@ -41,11 +42,14 @@ impl Default for Config {
|
||||
"System Program".to_string(),
|
||||
);
|
||||
|
||||
let commitment = "confirmed".to_string();
|
||||
|
||||
Self {
|
||||
json_rpc_url,
|
||||
websocket_url,
|
||||
keypair_path,
|
||||
address_labels,
|
||||
commitment,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,3 +1,4 @@
|
||||
#![allow(clippy::integer_arithmetic)]
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
|
||||
|
@@ -3,7 +3,7 @@ authors = ["Solana Maintainers <maintainers@solana.foundation>"]
|
||||
edition = "2018"
|
||||
name = "solana-cli-output"
|
||||
description = "Blockchain, Rebuilt for Scale"
|
||||
version = "1.5.0"
|
||||
version = "1.5.13"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
@@ -14,16 +14,16 @@ console = "0.11.3"
|
||||
humantime = "2.0.1"
|
||||
Inflector = "0.11.4"
|
||||
indicatif = "0.15.0"
|
||||
serde = "1.0.112"
|
||||
serde = "1.0.118"
|
||||
serde_derive = "1.0.103"
|
||||
serde_json = "1.0.56"
|
||||
solana-account-decoder = { path = "../account-decoder", version = "1.5.0" }
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.5.0" }
|
||||
solana-client = { path = "../client", version = "1.5.0" }
|
||||
solana-sdk = { path = "../sdk", version = "1.5.0" }
|
||||
solana-stake-program = { path = "../programs/stake", version = "1.5.0" }
|
||||
solana-transaction-status = { path = "../transaction-status", version = "1.5.0" }
|
||||
solana-vote-program = { path = "../programs/vote", version = "1.5.0" }
|
||||
solana-account-decoder = { path = "../account-decoder", version = "1.5.13" }
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.5.13" }
|
||||
solana-client = { path = "../client", version = "1.5.13" }
|
||||
solana-sdk = { path = "../sdk", version = "1.5.13" }
|
||||
solana-stake-program = { path = "../programs/stake", version = "1.5.13" }
|
||||
solana-transaction-status = { path = "../transaction-status", version = "1.5.13" }
|
||||
solana-vote-program = { path = "../programs/vote", version = "1.5.13" }
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
@@ -1,37 +1,48 @@
|
||||
use crate::{
|
||||
display::{build_balance_message, format_labeled_address, writeln_name_value},
|
||||
QuietDisplay, VerboseDisplay,
|
||||
};
|
||||
use chrono::{DateTime, NaiveDateTime, SecondsFormat, Utc};
|
||||
use console::{style, Emoji};
|
||||
use inflector::cases::titlecase::to_title_case;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{Map, Value};
|
||||
use solana_account_decoder::parse_token::UiTokenAccount;
|
||||
use solana_clap_utils::keypair::SignOnly;
|
||||
use solana_client::rpc_response::{
|
||||
RpcAccountBalance, RpcKeyedAccount, RpcSupply, RpcVoteAccountInfo,
|
||||
};
|
||||
use solana_sdk::{
|
||||
clock::{self, Epoch, Slot, UnixTimestamp},
|
||||
epoch_info::EpochInfo,
|
||||
hash::Hash,
|
||||
native_token::lamports_to_sol,
|
||||
pubkey::Pubkey,
|
||||
signature::Signature,
|
||||
stake_history::StakeHistoryEntry,
|
||||
transaction::Transaction,
|
||||
};
|
||||
use solana_stake_program::stake_state::{Authorized, Lockup};
|
||||
use solana_vote_program::{
|
||||
authorized_voters::AuthorizedVoters,
|
||||
vote_state::{BlockTimestamp, Lockout},
|
||||
};
|
||||
use std::{
|
||||
collections::{BTreeMap, HashMap},
|
||||
fmt,
|
||||
str::FromStr,
|
||||
time::Duration,
|
||||
use {
|
||||
crate::{
|
||||
display::{
|
||||
build_balance_message, build_balance_message_with_config, format_labeled_address,
|
||||
unix_timestamp_to_string, writeln_name_value, writeln_transaction,
|
||||
BuildBalanceMessageConfig,
|
||||
},
|
||||
QuietDisplay, VerboseDisplay,
|
||||
},
|
||||
chrono::{Local, TimeZone},
|
||||
console::{style, Emoji},
|
||||
inflector::cases::titlecase::to_title_case,
|
||||
serde::{Deserialize, Serialize},
|
||||
serde_json::{Map, Value},
|
||||
solana_account_decoder::parse_token::UiTokenAccount,
|
||||
solana_clap_utils::keypair::SignOnly,
|
||||
solana_client::rpc_response::{
|
||||
RpcAccountBalance, RpcInflationGovernor, RpcInflationRate, RpcKeyedAccount, RpcSupply,
|
||||
RpcVoteAccountInfo,
|
||||
},
|
||||
solana_sdk::{
|
||||
clock::{self, Epoch, Slot, UnixTimestamp},
|
||||
epoch_info::EpochInfo,
|
||||
hash::Hash,
|
||||
native_token::lamports_to_sol,
|
||||
pubkey::Pubkey,
|
||||
signature::Signature,
|
||||
stake_history::StakeHistoryEntry,
|
||||
transaction::{Transaction, TransactionError},
|
||||
},
|
||||
solana_stake_program::stake_state::{Authorized, Lockup},
|
||||
solana_transaction_status::{
|
||||
EncodedConfirmedBlock, EncodedTransaction, TransactionConfirmationStatus,
|
||||
UiTransactionStatusMeta,
|
||||
},
|
||||
solana_vote_program::{
|
||||
authorized_voters::AuthorizedVoters,
|
||||
vote_state::{BlockTimestamp, Lockout},
|
||||
},
|
||||
std::{
|
||||
collections::{BTreeMap, HashMap},
|
||||
fmt,
|
||||
str::FromStr,
|
||||
time::Duration,
|
||||
},
|
||||
};
|
||||
|
||||
static WARNING: Emoji = Emoji("⚠️", "!");
|
||||
@@ -241,6 +252,9 @@ impl fmt::Display for CliEpochInfo {
|
||||
)?;
|
||||
writeln_name_value(f, "Slot:", &self.epoch_info.absolute_slot.to_string())?;
|
||||
writeln_name_value(f, "Epoch:", &self.epoch_info.epoch.to_string())?;
|
||||
if let Some(transaction_count) = &self.epoch_info.transaction_count {
|
||||
writeln_name_value(f, "Transaction Count:", &transaction_count.to_string())?;
|
||||
}
|
||||
let start_slot = self.epoch_info.absolute_slot - self.epoch_info.slot_index;
|
||||
let end_slot = start_slot + self.epoch_info.slots_in_epoch;
|
||||
writeln_name_value(
|
||||
@@ -355,6 +369,42 @@ impl fmt::Display for CliValidators {
|
||||
},
|
||||
)
|
||||
}
|
||||
writeln!(
|
||||
f,
|
||||
"{}",
|
||||
style(format!(
|
||||
" {:<44} {:<38} {} {} {} {:>10} {:^8} {}",
|
||||
"Identity",
|
||||
"Vote Account",
|
||||
"Commission",
|
||||
"Last Vote",
|
||||
"Root Block",
|
||||
"Credits",
|
||||
"Version",
|
||||
"Active Stake",
|
||||
))
|
||||
.bold()
|
||||
)?;
|
||||
for validator in &self.current_validators {
|
||||
write_vote_account(
|
||||
f,
|
||||
validator,
|
||||
self.total_active_stake,
|
||||
self.use_lamports_unit,
|
||||
false,
|
||||
)?;
|
||||
}
|
||||
for validator in &self.delinquent_validators {
|
||||
write_vote_account(
|
||||
f,
|
||||
validator,
|
||||
self.total_active_stake,
|
||||
self.use_lamports_unit,
|
||||
true,
|
||||
)?;
|
||||
}
|
||||
|
||||
writeln!(f)?;
|
||||
writeln_name_value(
|
||||
f,
|
||||
"Active Stake:",
|
||||
@@ -406,41 +456,6 @@ impl fmt::Display for CliValidators {
|
||||
)?;
|
||||
}
|
||||
|
||||
writeln!(f)?;
|
||||
writeln!(
|
||||
f,
|
||||
"{}",
|
||||
style(format!(
|
||||
" {:<44} {:<38} {} {} {} {:>10} {:^8} {}",
|
||||
"Identity",
|
||||
"Vote Account",
|
||||
"Commission",
|
||||
"Last Vote",
|
||||
"Root Block",
|
||||
"Credits",
|
||||
"Version",
|
||||
"Active Stake",
|
||||
))
|
||||
.bold()
|
||||
)?;
|
||||
for validator in &self.current_validators {
|
||||
write_vote_account(
|
||||
f,
|
||||
validator,
|
||||
self.total_active_stake,
|
||||
self.use_lamports_unit,
|
||||
false,
|
||||
)?;
|
||||
}
|
||||
for validator in &self.delinquent_validators {
|
||||
write_vote_account(
|
||||
f,
|
||||
validator,
|
||||
self.total_active_stake,
|
||||
self.use_lamports_unit,
|
||||
true,
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -646,13 +661,13 @@ fn show_epoch_rewards(
|
||||
writeln!(f, "Epoch Rewards:")?;
|
||||
writeln!(
|
||||
f,
|
||||
" {:<8} {:<11} {:<15} {:<15} {:>14} {:>14}",
|
||||
" {:<6} {:<11} {:<16} {:<16} {:>14} {:>14}",
|
||||
"Epoch", "Reward Slot", "Amount", "New Balance", "Percent Change", "APR"
|
||||
)?;
|
||||
for reward in epoch_rewards {
|
||||
writeln!(
|
||||
f,
|
||||
" {:<8} {:<11} ◎{:<14.9} ◎{:<14.9} {:>13.9}% {}",
|
||||
" {:<6} {:<11} ◎{:<16.9} ◎{:<14.9} {:>13.2}% {}",
|
||||
reward.epoch,
|
||||
reward.effective_slot,
|
||||
lamports_to_sol(reward.amount),
|
||||
@@ -660,7 +675,7 @@ fn show_epoch_rewards(
|
||||
reward.percent_change,
|
||||
reward
|
||||
.apr
|
||||
.map(|apr| format!("{:>13.9}%", apr))
|
||||
.map(|apr| format!("{:>13.2}%", apr))
|
||||
.unwrap_or_default(),
|
||||
)?;
|
||||
}
|
||||
@@ -901,14 +916,19 @@ impl fmt::Display for CliStakeHistory {
|
||||
))
|
||||
.bold()
|
||||
)?;
|
||||
let config = BuildBalanceMessageConfig {
|
||||
use_lamports_unit: self.use_lamports_unit,
|
||||
show_unit: false,
|
||||
trim_trailing_zeros: false,
|
||||
};
|
||||
for entry in &self.entries {
|
||||
writeln!(
|
||||
f,
|
||||
" {:>5} {:>20} {:>20} {:>20} {}",
|
||||
entry.epoch,
|
||||
build_balance_message(entry.effective_stake, self.use_lamports_unit, false),
|
||||
build_balance_message(entry.activating_stake, self.use_lamports_unit, false),
|
||||
build_balance_message(entry.deactivating_stake, self.use_lamports_unit, false),
|
||||
build_balance_message_with_config(entry.effective_stake, &config),
|
||||
build_balance_message_with_config(entry.activating_stake, &config),
|
||||
build_balance_message_with_config(entry.deactivating_stake, &config),
|
||||
if self.use_lamports_unit {
|
||||
"lamports"
|
||||
} else {
|
||||
@@ -1143,18 +1163,6 @@ pub struct CliBlockTime {
|
||||
impl QuietDisplay for CliBlockTime {}
|
||||
impl VerboseDisplay for CliBlockTime {}
|
||||
|
||||
fn unix_timestamp_to_string(unix_timestamp: UnixTimestamp) -> String {
|
||||
format!(
|
||||
"{} (UnixTimestamp: {})",
|
||||
match NaiveDateTime::from_timestamp_opt(unix_timestamp, 0) {
|
||||
Some(ndt) =>
|
||||
DateTime::<Utc>::from_utc(ndt, Utc).to_rfc3339_opts(SecondsFormat::Secs, true),
|
||||
None => "unknown".to_string(),
|
||||
},
|
||||
unix_timestamp,
|
||||
)
|
||||
}
|
||||
|
||||
impl fmt::Display for CliBlockTime {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
writeln_name_value(f, "Block:", &self.slot.to_string())?;
|
||||
@@ -1162,6 +1170,104 @@ impl fmt::Display for CliBlockTime {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CliLeaderSchedule {
|
||||
pub epoch: Epoch,
|
||||
pub leader_schedule_entries: Vec<CliLeaderScheduleEntry>,
|
||||
}
|
||||
|
||||
impl QuietDisplay for CliLeaderSchedule {}
|
||||
impl VerboseDisplay for CliLeaderSchedule {}
|
||||
|
||||
impl fmt::Display for CliLeaderSchedule {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
for entry in &self.leader_schedule_entries {
|
||||
writeln!(f, " {:<15} {:<44}", entry.slot, entry.leader)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CliLeaderScheduleEntry {
|
||||
pub slot: Slot,
|
||||
pub leader: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CliInflation {
|
||||
pub governor: RpcInflationGovernor,
|
||||
pub current_rate: RpcInflationRate,
|
||||
}
|
||||
|
||||
impl QuietDisplay for CliInflation {}
|
||||
impl VerboseDisplay for CliInflation {}
|
||||
|
||||
impl fmt::Display for CliInflation {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
writeln!(f, "{}", style("Inflation Governor:").bold())?;
|
||||
if (self.governor.initial - self.governor.terminal).abs() < f64::EPSILON {
|
||||
writeln!(
|
||||
f,
|
||||
"Fixed APR: {:>5.2}%",
|
||||
self.governor.terminal * 100.
|
||||
)?;
|
||||
} else {
|
||||
writeln!(
|
||||
f,
|
||||
"Initial APR: {:>5.2}%",
|
||||
self.governor.initial * 100.
|
||||
)?;
|
||||
writeln!(
|
||||
f,
|
||||
"Terminal APR: {:>5.2}%",
|
||||
self.governor.terminal * 100.
|
||||
)?;
|
||||
writeln!(
|
||||
f,
|
||||
"Rate reduction per year: {:>5.2}%",
|
||||
self.governor.taper * 100.
|
||||
)?;
|
||||
}
|
||||
if self.governor.foundation_term > 0. {
|
||||
writeln!(
|
||||
f,
|
||||
"Foundation percentage: {:>5.2}%",
|
||||
self.governor.foundation
|
||||
)?;
|
||||
writeln!(
|
||||
f,
|
||||
"Foundation term: {:.1} years",
|
||||
self.governor.foundation_term
|
||||
)?;
|
||||
}
|
||||
|
||||
writeln!(
|
||||
f,
|
||||
"\n{}",
|
||||
style(format!("Inflation for Epoch {}:", self.current_rate.epoch)).bold()
|
||||
)?;
|
||||
writeln!(
|
||||
f,
|
||||
"Total APR: {:>5.2}%",
|
||||
self.current_rate.total * 100.
|
||||
)?;
|
||||
writeln!(
|
||||
f,
|
||||
"Staking APR: {:>5.2}%",
|
||||
self.current_rate.validator * 100.
|
||||
)?;
|
||||
writeln!(
|
||||
f,
|
||||
"Foundation APR: {:>5.2}%",
|
||||
self.current_rate.foundation * 100.
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Default)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CliSignOnlyData {
|
||||
@@ -1300,17 +1406,17 @@ impl fmt::Display for CliSupply {
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CliFees {
|
||||
pub struct CliFeesInner {
|
||||
pub slot: Slot,
|
||||
pub blockhash: String,
|
||||
pub lamports_per_signature: u64,
|
||||
pub last_valid_slot: Slot,
|
||||
pub last_valid_slot: Option<Slot>,
|
||||
}
|
||||
|
||||
impl QuietDisplay for CliFees {}
|
||||
impl VerboseDisplay for CliFees {}
|
||||
impl QuietDisplay for CliFeesInner {}
|
||||
impl VerboseDisplay for CliFeesInner {}
|
||||
|
||||
impl fmt::Display for CliFees {
|
||||
impl fmt::Display for CliFeesInner {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
writeln_name_value(f, "Blockhash:", &self.blockhash)?;
|
||||
writeln_name_value(
|
||||
@@ -1318,8 +1424,51 @@ impl fmt::Display for CliFees {
|
||||
"Lamports per signature:",
|
||||
&self.lamports_per_signature.to_string(),
|
||||
)?;
|
||||
writeln_name_value(f, "Last valid slot:", &self.last_valid_slot.to_string())?;
|
||||
Ok(())
|
||||
let last_valid_slot = self
|
||||
.last_valid_slot
|
||||
.map(|s| s.to_string())
|
||||
.unwrap_or_default();
|
||||
writeln_name_value(f, "Last valid slot:", &last_valid_slot)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CliFees {
|
||||
#[serde(flatten, skip_serializing_if = "Option::is_none")]
|
||||
pub inner: Option<CliFeesInner>,
|
||||
}
|
||||
|
||||
impl QuietDisplay for CliFees {}
|
||||
impl VerboseDisplay for CliFees {}
|
||||
|
||||
impl fmt::Display for CliFees {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self.inner.as_ref() {
|
||||
Some(inner) => write!(f, "{}", inner),
|
||||
None => write!(f, "Fees unavailable"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CliFees {
|
||||
pub fn some(
|
||||
slot: Slot,
|
||||
blockhash: Hash,
|
||||
lamports_per_signature: u64,
|
||||
last_valid_slot: Option<Slot>,
|
||||
) -> Self {
|
||||
Self {
|
||||
inner: Some(CliFeesInner {
|
||||
slot,
|
||||
blockhash: blockhash.to_string(),
|
||||
lamports_per_signature,
|
||||
last_valid_slot,
|
||||
}),
|
||||
}
|
||||
}
|
||||
pub fn none() -> Self {
|
||||
Self { inner: None }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1367,6 +1516,114 @@ impl fmt::Display for CliTokenAccount {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CliProgramId {
|
||||
pub program_id: String,
|
||||
}
|
||||
|
||||
impl QuietDisplay for CliProgramId {}
|
||||
impl VerboseDisplay for CliProgramId {}
|
||||
|
||||
impl fmt::Display for CliProgramId {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
writeln_name_value(f, "Program Id:", &self.program_id)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CliProgramBuffer {
|
||||
pub buffer: String,
|
||||
}
|
||||
|
||||
impl QuietDisplay for CliProgramBuffer {}
|
||||
impl VerboseDisplay for CliProgramBuffer {}
|
||||
|
||||
impl fmt::Display for CliProgramBuffer {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
writeln_name_value(f, "Buffer:", &self.buffer)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum CliProgramAccountType {
|
||||
Buffer,
|
||||
Program,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CliProgramAuthority {
|
||||
pub authority: String,
|
||||
pub account_type: CliProgramAccountType,
|
||||
}
|
||||
|
||||
impl QuietDisplay for CliProgramAuthority {}
|
||||
impl VerboseDisplay for CliProgramAuthority {}
|
||||
|
||||
impl fmt::Display for CliProgramAuthority {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
writeln_name_value(f, "Account Type:", &format!("{:?}", self.account_type))?;
|
||||
writeln_name_value(f, "Authority:", &self.authority)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CliUpgradeableProgram {
|
||||
pub program_id: String,
|
||||
pub programdata_address: String,
|
||||
pub authority: String,
|
||||
pub last_deploy_slot: u64,
|
||||
pub data_len: usize,
|
||||
}
|
||||
impl QuietDisplay for CliUpgradeableProgram {}
|
||||
impl VerboseDisplay for CliUpgradeableProgram {}
|
||||
impl fmt::Display for CliUpgradeableProgram {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
writeln!(f)?;
|
||||
writeln_name_value(f, "Program Id:", &self.program_id)?;
|
||||
writeln_name_value(f, "ProgramData Address:", &self.programdata_address)?;
|
||||
writeln_name_value(f, "Authority:", &self.authority)?;
|
||||
writeln_name_value(
|
||||
f,
|
||||
"Last Deployed In Slot:",
|
||||
&self.last_deploy_slot.to_string(),
|
||||
)?;
|
||||
writeln_name_value(
|
||||
f,
|
||||
"Data Length:",
|
||||
&format!("{:?} ({:#x?}) bytes", self.data_len, self.data_len),
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CliUpgradeableBuffer {
|
||||
pub address: String,
|
||||
pub authority: String,
|
||||
pub data_len: usize,
|
||||
}
|
||||
impl QuietDisplay for CliUpgradeableBuffer {}
|
||||
impl VerboseDisplay for CliUpgradeableBuffer {}
|
||||
impl fmt::Display for CliUpgradeableBuffer {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
writeln!(f)?;
|
||||
writeln_name_value(f, "Buffer Address:", &self.address)?;
|
||||
writeln_name_value(f, "Authority:", &self.authority)?;
|
||||
writeln_name_value(
|
||||
f,
|
||||
"Data Length:",
|
||||
&format!("{:?} ({:#x?}) bytes", self.data_len, self.data_len),
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn return_signers(
|
||||
tx: &Transaction,
|
||||
output_format: &OutputFormat,
|
||||
@@ -1453,6 +1710,224 @@ pub fn parse_sign_only_reply_string(reply: &str) -> SignOnly {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum CliSignatureVerificationStatus {
|
||||
None,
|
||||
Pass,
|
||||
Fail,
|
||||
}
|
||||
|
||||
impl CliSignatureVerificationStatus {
|
||||
pub fn verify_transaction(tx: &Transaction) -> Vec<Self> {
|
||||
tx.verify_with_results()
|
||||
.iter()
|
||||
.zip(&tx.signatures)
|
||||
.map(|(stat, sig)| match stat {
|
||||
true => CliSignatureVerificationStatus::Pass,
|
||||
false if sig == &Signature::default() => CliSignatureVerificationStatus::None,
|
||||
false => CliSignatureVerificationStatus::Fail,
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for CliSignatureVerificationStatus {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Self::None => write!(f, "none"),
|
||||
Self::Pass => write!(f, "pass"),
|
||||
Self::Fail => write!(f, "fail"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CliBlock {
|
||||
#[serde(flatten)]
|
||||
pub encoded_confirmed_block: EncodedConfirmedBlock,
|
||||
#[serde(skip_serializing)]
|
||||
pub slot: Slot,
|
||||
}
|
||||
|
||||
impl QuietDisplay for CliBlock {}
|
||||
impl VerboseDisplay for CliBlock {}
|
||||
|
||||
impl fmt::Display for CliBlock {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
writeln!(f, "Slot: {}", self.slot)?;
|
||||
writeln!(
|
||||
f,
|
||||
"Parent Slot: {}",
|
||||
self.encoded_confirmed_block.parent_slot
|
||||
)?;
|
||||
writeln!(f, "Blockhash: {}", self.encoded_confirmed_block.blockhash)?;
|
||||
writeln!(
|
||||
f,
|
||||
"Previous Blockhash: {}",
|
||||
self.encoded_confirmed_block.previous_blockhash
|
||||
)?;
|
||||
if let Some(block_time) = self.encoded_confirmed_block.block_time {
|
||||
writeln!(f, "Block Time: {:?}", Local.timestamp(block_time, 0))?;
|
||||
}
|
||||
if !self.encoded_confirmed_block.rewards.is_empty() {
|
||||
let mut rewards = self.encoded_confirmed_block.rewards.clone();
|
||||
rewards.sort_by(|a, b| a.pubkey.cmp(&b.pubkey));
|
||||
let mut total_rewards = 0;
|
||||
writeln!(f, "Rewards:")?;
|
||||
writeln!(
|
||||
f,
|
||||
" {:<44} {:^15} {:<15} {:<20} {:>14}",
|
||||
"Address", "Type", "Amount", "New Balance", "Percent Change"
|
||||
)?;
|
||||
for reward in rewards {
|
||||
let sign = if reward.lamports < 0 { "-" } else { "" };
|
||||
|
||||
total_rewards += reward.lamports;
|
||||
writeln!(
|
||||
f,
|
||||
" {:<44} {:^15} {:>15} {}",
|
||||
reward.pubkey,
|
||||
if let Some(reward_type) = reward.reward_type {
|
||||
format!("{}", reward_type)
|
||||
} else {
|
||||
"-".to_string()
|
||||
},
|
||||
format!(
|
||||
"{}◎{:<14.9}",
|
||||
sign,
|
||||
lamports_to_sol(reward.lamports.abs() as u64)
|
||||
),
|
||||
if reward.post_balance == 0 {
|
||||
" - -".to_string()
|
||||
} else {
|
||||
format!(
|
||||
"◎{:<19.9} {:>13.9}%",
|
||||
lamports_to_sol(reward.post_balance),
|
||||
(reward.lamports.abs() as f64
|
||||
/ (reward.post_balance as f64 - reward.lamports as f64))
|
||||
* 100.0
|
||||
)
|
||||
}
|
||||
)?;
|
||||
}
|
||||
|
||||
let sign = if total_rewards < 0 { "-" } else { "" };
|
||||
writeln!(
|
||||
f,
|
||||
"Total Rewards: {}◎{:<12.9}",
|
||||
sign,
|
||||
lamports_to_sol(total_rewards.abs() as u64)
|
||||
)?;
|
||||
}
|
||||
for (index, transaction_with_meta) in
|
||||
self.encoded_confirmed_block.transactions.iter().enumerate()
|
||||
{
|
||||
writeln!(f, "Transaction {}:", index)?;
|
||||
writeln_transaction(
|
||||
f,
|
||||
&transaction_with_meta.transaction.decode().unwrap(),
|
||||
&transaction_with_meta.meta,
|
||||
" ",
|
||||
None,
|
||||
None,
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CliTransaction {
|
||||
pub transaction: EncodedTransaction,
|
||||
pub meta: Option<UiTransactionStatusMeta>,
|
||||
pub block_time: Option<UnixTimestamp>,
|
||||
#[serde(skip_serializing)]
|
||||
pub slot: Option<Slot>,
|
||||
#[serde(skip_serializing)]
|
||||
pub decoded_transaction: Transaction,
|
||||
#[serde(skip_serializing)]
|
||||
pub prefix: String,
|
||||
#[serde(skip_serializing_if = "Vec::is_empty")]
|
||||
pub sigverify_status: Vec<CliSignatureVerificationStatus>,
|
||||
}
|
||||
|
||||
impl QuietDisplay for CliTransaction {}
|
||||
impl VerboseDisplay for CliTransaction {}
|
||||
|
||||
impl fmt::Display for CliTransaction {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
writeln_transaction(
|
||||
f,
|
||||
&self.decoded_transaction,
|
||||
&self.meta,
|
||||
&self.prefix,
|
||||
if !self.sigverify_status.is_empty() {
|
||||
Some(&self.sigverify_status)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
self.block_time,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CliTransactionConfirmation {
|
||||
pub confirmation_status: Option<TransactionConfirmationStatus>,
|
||||
#[serde(flatten, skip_serializing_if = "Option::is_none")]
|
||||
pub transaction: Option<CliTransaction>,
|
||||
#[serde(skip_serializing)]
|
||||
pub get_transaction_error: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub err: Option<TransactionError>,
|
||||
}
|
||||
|
||||
impl QuietDisplay for CliTransactionConfirmation {}
|
||||
impl VerboseDisplay for CliTransactionConfirmation {
|
||||
fn write_str(&self, w: &mut dyn std::fmt::Write) -> std::fmt::Result {
|
||||
if let Some(transaction) = &self.transaction {
|
||||
writeln!(
|
||||
w,
|
||||
"\nTransaction executed in slot {}:",
|
||||
transaction.slot.expect("slot should exist")
|
||||
)?;
|
||||
write!(w, "{}", transaction)?;
|
||||
} else if let Some(confirmation_status) = &self.confirmation_status {
|
||||
if confirmation_status != &TransactionConfirmationStatus::Finalized {
|
||||
writeln!(w)?;
|
||||
writeln!(
|
||||
w,
|
||||
"Unable to get finalized transaction details: not yet finalized"
|
||||
)?;
|
||||
} else if let Some(err) = &self.get_transaction_error {
|
||||
writeln!(w)?;
|
||||
writeln!(w, "Unable to get finalized transaction details: {}", err)?;
|
||||
}
|
||||
}
|
||||
writeln!(w)?;
|
||||
write!(w, "{}", self)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for CliTransactionConfirmation {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match &self.confirmation_status {
|
||||
None => write!(f, "Not found"),
|
||||
Some(confirmation_status) => {
|
||||
if let Some(err) = &self.err {
|
||||
write!(f, "Transaction failed: {}", err)
|
||||
} else {
|
||||
write!(f, "{:?}", confirmation_status)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
@@ -1,28 +1,73 @@
|
||||
use console::style;
|
||||
use indicatif::{ProgressBar, ProgressStyle};
|
||||
use solana_sdk::{
|
||||
hash::Hash, native_token::lamports_to_sol, program_utils::limited_deserialize,
|
||||
transaction::Transaction,
|
||||
use {
|
||||
crate::cli_output::CliSignatureVerificationStatus,
|
||||
chrono::{DateTime, Local, NaiveDateTime, SecondsFormat, TimeZone, Utc},
|
||||
console::style,
|
||||
indicatif::{ProgressBar, ProgressStyle},
|
||||
solana_sdk::{
|
||||
clock::UnixTimestamp, hash::Hash, native_token::lamports_to_sol,
|
||||
program_utils::limited_deserialize, transaction::Transaction,
|
||||
},
|
||||
solana_transaction_status::UiTransactionStatusMeta,
|
||||
std::{collections::HashMap, fmt, io},
|
||||
};
|
||||
use solana_transaction_status::UiTransactionStatusMeta;
|
||||
use std::{collections::HashMap, fmt, io};
|
||||
|
||||
pub fn build_balance_message(lamports: u64, use_lamports_unit: bool, show_unit: bool) -> String {
|
||||
if use_lamports_unit {
|
||||
let ess = if lamports == 1 { "" } else { "s" };
|
||||
let unit = if show_unit {
|
||||
format!(" lamport{}", ess)
|
||||
} else {
|
||||
"".to_string()
|
||||
};
|
||||
format!("{:?}{}", lamports, unit)
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct BuildBalanceMessageConfig {
|
||||
pub use_lamports_unit: bool,
|
||||
pub show_unit: bool,
|
||||
pub trim_trailing_zeros: bool,
|
||||
}
|
||||
|
||||
impl Default for BuildBalanceMessageConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
use_lamports_unit: false,
|
||||
show_unit: true,
|
||||
trim_trailing_zeros: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build_balance_message_with_config(
|
||||
lamports: u64,
|
||||
config: &BuildBalanceMessageConfig,
|
||||
) -> String {
|
||||
let value = if config.use_lamports_unit {
|
||||
lamports.to_string()
|
||||
} else {
|
||||
let sol = lamports_to_sol(lamports);
|
||||
let sol_str = format!("{:.9}", sol);
|
||||
let pretty_sol = sol_str.trim_end_matches('0').trim_end_matches('.');
|
||||
let unit = if show_unit { " SOL" } else { "" };
|
||||
format!("{}{}", pretty_sol, unit)
|
||||
}
|
||||
if config.trim_trailing_zeros {
|
||||
sol_str
|
||||
.trim_end_matches('0')
|
||||
.trim_end_matches('.')
|
||||
.to_string()
|
||||
} else {
|
||||
sol_str
|
||||
}
|
||||
};
|
||||
let unit = if config.show_unit {
|
||||
if config.use_lamports_unit {
|
||||
let ess = if lamports == 1 { "" } else { "s" };
|
||||
format!(" lamport{}", ess)
|
||||
} else {
|
||||
" SOL".to_string()
|
||||
}
|
||||
} else {
|
||||
"".to_string()
|
||||
};
|
||||
format!("{}{}", value, unit)
|
||||
}
|
||||
|
||||
pub fn build_balance_message(lamports: u64, use_lamports_unit: bool, show_unit: bool) -> String {
|
||||
build_balance_message_with_config(
|
||||
lamports,
|
||||
&BuildBalanceMessageConfig {
|
||||
use_lamports_unit,
|
||||
show_unit,
|
||||
..BuildBalanceMessageConfig::default()
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// Pretty print a "name value"
|
||||
@@ -35,7 +80,7 @@ pub fn println_name_value(name: &str, value: &str) {
|
||||
println!("{} {}", style(name).bold(), styled_value);
|
||||
}
|
||||
|
||||
pub fn writeln_name_value(f: &mut fmt::Formatter, name: &str, value: &str) -> fmt::Result {
|
||||
pub fn writeln_name_value(f: &mut dyn fmt::Write, name: &str, value: &str) -> fmt::Result {
|
||||
let styled_value = if value.is_empty() {
|
||||
style("(not set)").italic()
|
||||
} else {
|
||||
@@ -85,18 +130,41 @@ pub fn write_transaction<W: io::Write>(
|
||||
transaction: &Transaction,
|
||||
transaction_status: &Option<UiTransactionStatusMeta>,
|
||||
prefix: &str,
|
||||
sigverify_status: Option<&[CliSignatureVerificationStatus]>,
|
||||
block_time: Option<UnixTimestamp>,
|
||||
) -> io::Result<()> {
|
||||
let message = &transaction.message;
|
||||
if let Some(block_time) = block_time {
|
||||
writeln!(
|
||||
w,
|
||||
"{}Block Time: {:?}",
|
||||
prefix,
|
||||
Local.timestamp(block_time, 0)
|
||||
)?;
|
||||
}
|
||||
writeln!(
|
||||
w,
|
||||
"{}Recent Blockhash: {:?}",
|
||||
prefix, message.recent_blockhash
|
||||
)?;
|
||||
for (signature_index, signature) in transaction.signatures.iter().enumerate() {
|
||||
let sigverify_statuses = if let Some(sigverify_status) = sigverify_status {
|
||||
sigverify_status
|
||||
.iter()
|
||||
.map(|s| format!(" ({})", s))
|
||||
.collect()
|
||||
} else {
|
||||
vec!["".to_string(); transaction.signatures.len()]
|
||||
};
|
||||
for (signature_index, (signature, sigverify_status)) in transaction
|
||||
.signatures
|
||||
.iter()
|
||||
.zip(&sigverify_statuses)
|
||||
.enumerate()
|
||||
{
|
||||
writeln!(
|
||||
w,
|
||||
"{}Signature {}: {:?}",
|
||||
prefix, signature_index, signature
|
||||
"{}Signature {}: {:?}{}",
|
||||
prefix, signature_index, signature, sigverify_status,
|
||||
)?;
|
||||
}
|
||||
writeln!(w, "{}{:?}", prefix, message.header)?;
|
||||
@@ -217,15 +285,52 @@ pub fn println_transaction(
|
||||
transaction: &Transaction,
|
||||
transaction_status: &Option<UiTransactionStatusMeta>,
|
||||
prefix: &str,
|
||||
sigverify_status: Option<&[CliSignatureVerificationStatus]>,
|
||||
block_time: Option<UnixTimestamp>,
|
||||
) {
|
||||
let mut w = Vec::new();
|
||||
if write_transaction(&mut w, transaction, transaction_status, prefix).is_ok() {
|
||||
if write_transaction(
|
||||
&mut w,
|
||||
transaction,
|
||||
transaction_status,
|
||||
prefix,
|
||||
sigverify_status,
|
||||
block_time,
|
||||
)
|
||||
.is_ok()
|
||||
{
|
||||
if let Ok(s) = String::from_utf8(w) {
|
||||
print!("{}", s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn writeln_transaction(
|
||||
f: &mut dyn fmt::Write,
|
||||
transaction: &Transaction,
|
||||
transaction_status: &Option<UiTransactionStatusMeta>,
|
||||
prefix: &str,
|
||||
sigverify_status: Option<&[CliSignatureVerificationStatus]>,
|
||||
block_time: Option<UnixTimestamp>,
|
||||
) -> fmt::Result {
|
||||
let mut w = Vec::new();
|
||||
if write_transaction(
|
||||
&mut w,
|
||||
transaction,
|
||||
transaction_status,
|
||||
prefix,
|
||||
sigverify_status,
|
||||
block_time,
|
||||
)
|
||||
.is_ok()
|
||||
{
|
||||
if let Ok(s) = String::from_utf8(w) {
|
||||
write!(f, "{}", s)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Creates a new process bar for processing that will take an unknown amount of time
|
||||
pub fn new_spinner_progress_bar() -> ProgressBar {
|
||||
let progress_bar = ProgressBar::new(42);
|
||||
@@ -235,6 +340,13 @@ pub fn new_spinner_progress_bar() -> ProgressBar {
|
||||
progress_bar
|
||||
}
|
||||
|
||||
pub fn unix_timestamp_to_string(unix_timestamp: UnixTimestamp) -> String {
|
||||
match NaiveDateTime::from_timestamp_opt(unix_timestamp, 0) {
|
||||
Some(ndt) => DateTime::<Utc>::from_utc(ndt, Utc).to_rfc3339_opts(SecondsFormat::Secs, true),
|
||||
None => format!("UnixTimestamp {}", unix_timestamp),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
@@ -1,3 +1,4 @@
|
||||
#![allow(clippy::integer_arithmetic)]
|
||||
mod cli_output;
|
||||
pub mod display;
|
||||
pub use cli_output::*;
|
||||
|
@@ -3,7 +3,7 @@ authors = ["Solana Maintainers <maintainers@solana.foundation>"]
|
||||
edition = "2018"
|
||||
name = "solana-cli"
|
||||
description = "Blockchain, Rebuilt for Scale"
|
||||
version = "1.5.0"
|
||||
version = "1.5.13"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
@@ -24,32 +24,32 @@ humantime = "2.0.1"
|
||||
num-traits = "0.2"
|
||||
pretty-hex = "0.2.1"
|
||||
reqwest = { version = "0.10.8", default-features = false, features = ["blocking", "rustls-tls", "json"] }
|
||||
serde = "1.0.112"
|
||||
serde = "1.0.118"
|
||||
serde_derive = "1.0.103"
|
||||
serde_json = "1.0.56"
|
||||
solana-account-decoder = { path = "../account-decoder", version = "1.5.0" }
|
||||
solana-bpf-loader-program = { path = "../programs/bpf_loader", version = "1.5.0" }
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.5.0" }
|
||||
solana-cli-config = { path = "../cli-config", version = "1.5.0" }
|
||||
solana-cli-output = { path = "../cli-output", version = "1.5.0" }
|
||||
solana-client = { path = "../client", version = "1.5.0" }
|
||||
solana-config-program = { path = "../programs/config", version = "1.5.0" }
|
||||
solana-faucet = { path = "../faucet", version = "1.5.0" }
|
||||
solana-logger = { path = "../logger", version = "1.5.0" }
|
||||
solana-net-utils = { path = "../net-utils", version = "1.5.0" }
|
||||
solana_rbpf = "=0.2.2"
|
||||
solana-remote-wallet = { path = "../remote-wallet", version = "1.5.0" }
|
||||
solana-sdk = { path = "../sdk", version = "1.5.0" }
|
||||
solana-stake-program = { path = "../programs/stake", version = "1.5.0" }
|
||||
solana-transaction-status = { path = "../transaction-status", version = "1.5.0" }
|
||||
solana-version = { path = "../version", version = "1.5.0" }
|
||||
solana-vote-program = { path = "../programs/vote", version = "1.5.0" }
|
||||
solana-account-decoder = { path = "../account-decoder", version = "1.5.13" }
|
||||
solana-bpf-loader-program = { path = "../programs/bpf_loader", version = "1.5.13" }
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.5.13" }
|
||||
solana-cli-config = { path = "../cli-config", version = "1.5.13" }
|
||||
solana-cli-output = { path = "../cli-output", version = "1.5.13" }
|
||||
solana-client = { path = "../client", version = "1.5.13" }
|
||||
solana-config-program = { path = "../programs/config", version = "1.5.13" }
|
||||
solana-faucet = { path = "../faucet", version = "1.5.13" }
|
||||
solana-logger = { path = "../logger", version = "1.5.13" }
|
||||
solana-net-utils = { path = "../net-utils", version = "1.5.13" }
|
||||
solana_rbpf = "=0.2.5"
|
||||
solana-remote-wallet = { path = "../remote-wallet", version = "1.5.13" }
|
||||
solana-sdk = { path = "../sdk", version = "1.5.13" }
|
||||
solana-stake-program = { path = "../programs/stake", version = "1.5.13" }
|
||||
solana-transaction-status = { path = "../transaction-status", version = "1.5.13" }
|
||||
solana-version = { path = "../version", version = "1.5.13" }
|
||||
solana-vote-program = { path = "../programs/vote", version = "1.5.13" }
|
||||
thiserror = "1.0.21"
|
||||
tiny-bip39 = "0.7.0"
|
||||
url = "2.1.1"
|
||||
|
||||
[dev-dependencies]
|
||||
solana-core = { path = "../core", version = "1.5.0" }
|
||||
solana-core = { path = "../core", version = "1.5.13" }
|
||||
tempfile = "3.1.0"
|
||||
|
||||
[[bin]]
|
||||
|
@@ -86,9 +86,13 @@ pub fn check_account_for_spend_multiple_fees_with_commitment(
|
||||
return Err(CliError::InsufficientFundsForSpendAndFee(
|
||||
lamports_to_sol(balance),
|
||||
lamports_to_sol(fee),
|
||||
*account_pubkey,
|
||||
));
|
||||
} else {
|
||||
return Err(CliError::InsufficientFundsForFee(lamports_to_sol(fee)));
|
||||
return Err(CliError::InsufficientFundsForFee(
|
||||
lamports_to_sol(fee),
|
||||
*account_pubkey,
|
||||
));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
|
1742
cli/src/cli.rs
1742
cli/src/cli.rs
File diff suppressed because it is too large
Load Diff
@@ -3,11 +3,10 @@ use crate::{
|
||||
spend_utils::{resolve_spend_tx_and_check_account_balance, SpendAmount},
|
||||
stake::is_stake_program_v2_enabled,
|
||||
};
|
||||
use chrono::{Local, TimeZone};
|
||||
use clap::{value_t, value_t_or_exit, App, AppSettings, Arg, ArgMatches, SubCommand};
|
||||
use console::{style, Emoji};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use solana_clap_utils::{
|
||||
commitment::{commitment_arg, commitment_arg_with_default},
|
||||
input_parsers::*,
|
||||
input_validators::*,
|
||||
keypair::DefaultSigner,
|
||||
@@ -15,7 +14,8 @@ use solana_clap_utils::{
|
||||
};
|
||||
use solana_cli_output::{
|
||||
display::{
|
||||
format_labeled_address, new_spinner_progress_bar, println_name_value, println_transaction,
|
||||
build_balance_message, format_labeled_address, new_spinner_progress_bar,
|
||||
println_name_value, println_transaction, unix_timestamp_to_string, writeln_name_value,
|
||||
},
|
||||
*,
|
||||
};
|
||||
@@ -41,17 +41,21 @@ use solana_sdk::{
|
||||
message::Message,
|
||||
native_token::lamports_to_sol,
|
||||
pubkey::{self, Pubkey},
|
||||
rent::Rent,
|
||||
rpc_port::DEFAULT_RPC_PORT_STR,
|
||||
signature::Signature,
|
||||
system_instruction, system_program,
|
||||
sysvar::{
|
||||
self,
|
||||
stake_history::{self},
|
||||
},
|
||||
timing,
|
||||
transaction::Transaction,
|
||||
};
|
||||
use solana_transaction_status::UiTransactionEncoding;
|
||||
use std::{
|
||||
collections::{BTreeMap, HashMap, VecDeque},
|
||||
fmt,
|
||||
net::SocketAddr,
|
||||
sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
@@ -88,14 +92,14 @@ impl ClusterQuerySubCommands for App<'_, '_> {
|
||||
.arg(
|
||||
pubkey!(Arg::with_name("node_pubkey")
|
||||
.index(1)
|
||||
.value_name("VALIDATOR_PUBKEY")
|
||||
.required(true),
|
||||
.value_name("OUR_VALIDATOR_PUBKEY")
|
||||
.required(false),
|
||||
"Identity pubkey of the validator"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("node_json_rpc_url")
|
||||
.index(2)
|
||||
.value_name("URL")
|
||||
.value_name("OUR_URL")
|
||||
.takes_value(true)
|
||||
.validator(is_url)
|
||||
.help("JSON RPC URL for validator, which is useful for validators with a private RPC service")
|
||||
@@ -106,17 +110,42 @@ impl ClusterQuerySubCommands for App<'_, '_> {
|
||||
.takes_value(false)
|
||||
.help("Continue reporting progress even after the validator has caught up"),
|
||||
)
|
||||
.arg(commitment_arg()),
|
||||
.arg(
|
||||
Arg::with_name("our_localhost")
|
||||
.long("our-localhost")
|
||||
.takes_value(false)
|
||||
.value_name("PORT")
|
||||
.default_value(&DEFAULT_RPC_PORT_STR)
|
||||
.validator(is_port)
|
||||
.help("Guess Identity pubkey and validator rpc node assuming local (possibly private) validator"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("log")
|
||||
.long("log")
|
||||
.takes_value(false)
|
||||
.help("Don't update the progress inplace; instead show updates with its own new lines"),
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("cluster-date")
|
||||
.about("Get current cluster date, computed from genesis creation time and network time")
|
||||
.about("Get current cluster date, computed from genesis creation time and network time"),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("cluster-version")
|
||||
.about("Get the version of the cluster entrypoint"),
|
||||
)
|
||||
.subcommand(SubCommand::with_name("fees").about("Display current cluster fees"))
|
||||
.subcommand(
|
||||
SubCommand::with_name("fees")
|
||||
.about("Display current cluster fees")
|
||||
.arg(
|
||||
Arg::with_name("blockhash")
|
||||
.long("blockhash")
|
||||
.takes_value(true)
|
||||
.value_name("BLOCKHASH")
|
||||
.validator(is_hash)
|
||||
.help("Query fees for BLOCKHASH instead of the the most recent blockhash")
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("first-available-block")
|
||||
.about("Get the first available block in the storage"),
|
||||
@@ -132,12 +161,21 @@ impl ClusterQuerySubCommands for App<'_, '_> {
|
||||
.help("Slot number of the block to query")
|
||||
)
|
||||
)
|
||||
.subcommand(SubCommand::with_name("leader-schedule").about("Display leader schedule"))
|
||||
.subcommand(SubCommand::with_name("leader-schedule")
|
||||
.about("Display leader schedule")
|
||||
.arg(
|
||||
Arg::with_name("epoch")
|
||||
.long("epoch")
|
||||
.takes_value(true)
|
||||
.value_name("EPOCH")
|
||||
.validator(is_epoch)
|
||||
.help("Epoch to show leader schedule for. (default: current)")
|
||||
)
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("epoch-info")
|
||||
.about("Get information about the current epoch")
|
||||
.alias("get-epoch-info")
|
||||
.arg(commitment_arg()),
|
||||
.alias("get-epoch-info"),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("genesis-hash")
|
||||
@@ -146,16 +184,13 @@ impl ClusterQuerySubCommands for App<'_, '_> {
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("slot").about("Get current slot")
|
||||
.alias("get-slot")
|
||||
.arg(commitment_arg()),
|
||||
.alias("get-slot"),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("block-height").about("Get current block height")
|
||||
.arg(commitment_arg()),
|
||||
SubCommand::with_name("block-height").about("Get current block height"),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("epoch").about("Get current epoch")
|
||||
.arg(commitment_arg()),
|
||||
SubCommand::with_name("epoch").about("Get current epoch"),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("largest-accounts").about("Get addresses of largest cluster accounts")
|
||||
@@ -171,8 +206,7 @@ impl ClusterQuerySubCommands for App<'_, '_> {
|
||||
.takes_value(false)
|
||||
.conflicts_with("circulating")
|
||||
.help("Filter address list to only non-circulating accounts")
|
||||
)
|
||||
.arg(commitment_arg()),
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("supply").about("Get information about the cluster supply of SOL")
|
||||
@@ -181,18 +215,15 @@ impl ClusterQuerySubCommands for App<'_, '_> {
|
||||
.long("print-accounts")
|
||||
.takes_value(false)
|
||||
.help("Print list of non-circualting account addresses")
|
||||
)
|
||||
.arg(commitment_arg()),
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("total-supply").about("Get total number of SOL")
|
||||
.setting(AppSettings::Hidden)
|
||||
.arg(commitment_arg()),
|
||||
.setting(AppSettings::Hidden),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("transaction-count").about("Get current transaction count")
|
||||
.alias("get-transaction-count")
|
||||
.arg(commitment_arg()),
|
||||
.alias("get-transaction-count"),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("ping")
|
||||
@@ -239,8 +270,7 @@ impl ClusterQuerySubCommands for App<'_, '_> {
|
||||
.default_value("15")
|
||||
.help("Wait up to timeout seconds for transaction confirmation"),
|
||||
)
|
||||
.arg(blockhash_arg())
|
||||
.arg(commitment_arg()),
|
||||
.arg(blockhash_arg()),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("live-slots")
|
||||
@@ -263,8 +293,7 @@ impl ClusterQuerySubCommands for App<'_, '_> {
|
||||
.takes_value(false)
|
||||
.conflicts_with("address")
|
||||
.help("Include vote transactions when monitoring all transactions")
|
||||
)
|
||||
.arg(commitment_arg_with_default("singleGossip")),
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("block-production")
|
||||
@@ -314,8 +343,7 @@ impl ClusterQuerySubCommands for App<'_, '_> {
|
||||
.long("lamports")
|
||||
.takes_value(false)
|
||||
.help("Display balance in lamports instead of SOL"),
|
||||
)
|
||||
.arg(commitment_arg()),
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("transaction-history")
|
||||
@@ -362,6 +390,23 @@ impl ClusterQuerySubCommands for App<'_, '_> {
|
||||
.index(1),
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("rent")
|
||||
.about("Calculate per-epoch and rent-exempt-minimum values for a given account data length.")
|
||||
.arg(
|
||||
Arg::with_name("data_length")
|
||||
.index(1)
|
||||
.value_name("DATA_LENGTH")
|
||||
.required(true)
|
||||
.help("Length of data in the account to calculate rent for"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("lamports")
|
||||
.long("lamports")
|
||||
.takes_value(false)
|
||||
.help("Display rent in lamports instead of SOL"),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -369,14 +414,31 @@ pub fn parse_catchup(
|
||||
matches: &ArgMatches<'_>,
|
||||
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
|
||||
) -> Result<CliCommandInfo, CliError> {
|
||||
let node_pubkey = pubkey_of_signer(matches, "node_pubkey", wallet_manager)?.unwrap();
|
||||
let node_pubkey = pubkey_of_signer(matches, "node_pubkey", wallet_manager)?;
|
||||
let mut our_localhost_port = value_t!(matches, "our_localhost", u16).ok();
|
||||
// if there is no explicitly specified --our-localhost,
|
||||
// disable the guess mode (= our_localhost_port)
|
||||
if matches.occurrences_of("our_localhost") == 0 {
|
||||
our_localhost_port = None
|
||||
}
|
||||
let node_json_rpc_url = value_t!(matches, "node_json_rpc_url", String).ok();
|
||||
// requirement of node_pubkey is relaxed only if our_localhost_port
|
||||
if our_localhost_port.is_none() && node_pubkey.is_none() {
|
||||
return Err(CliError::BadParameter(
|
||||
"OUR_VALIDATOR_PUBKEY (and possibly OUR_URL) must be specified \
|
||||
unless --our-localhost is given"
|
||||
.into(),
|
||||
));
|
||||
}
|
||||
let follow = matches.is_present("follow");
|
||||
let log = matches.is_present("log");
|
||||
Ok(CliCommandInfo {
|
||||
command: CliCommand::Catchup {
|
||||
node_pubkey,
|
||||
node_json_rpc_url,
|
||||
follow,
|
||||
our_localhost_port,
|
||||
log,
|
||||
},
|
||||
signers: vec![],
|
||||
})
|
||||
@@ -556,38 +618,76 @@ pub fn parse_transaction_history(
|
||||
pub fn process_catchup(
|
||||
rpc_client: &RpcClient,
|
||||
config: &CliConfig,
|
||||
node_pubkey: &Pubkey,
|
||||
node_json_rpc_url: &Option<String>,
|
||||
node_pubkey: Option<Pubkey>,
|
||||
mut node_json_rpc_url: Option<String>,
|
||||
follow: bool,
|
||||
our_localhost_port: Option<u16>,
|
||||
log: bool,
|
||||
) -> ProcessResult {
|
||||
let sleep_interval = 5;
|
||||
|
||||
let progress_bar = new_spinner_progress_bar();
|
||||
progress_bar.set_message("Connecting...");
|
||||
|
||||
let node_client = if let Some(node_json_rpc_url) = node_json_rpc_url {
|
||||
RpcClient::new(node_json_rpc_url.to_string())
|
||||
} else {
|
||||
let rpc_addr = loop {
|
||||
let cluster_nodes = rpc_client.get_cluster_nodes()?;
|
||||
if let Some(contact_info) = cluster_nodes
|
||||
.iter()
|
||||
.find(|contact_info| contact_info.pubkey == node_pubkey.to_string())
|
||||
{
|
||||
if let Some(rpc_addr) = contact_info.rpc {
|
||||
break rpc_addr;
|
||||
}
|
||||
progress_bar.set_message(&format!("RPC service not found for {}", node_pubkey));
|
||||
} else {
|
||||
progress_bar.set_message(&format!(
|
||||
"Contact information not found for {}",
|
||||
node_pubkey
|
||||
));
|
||||
}
|
||||
sleep(Duration::from_secs(sleep_interval as u64));
|
||||
};
|
||||
if let Some(our_localhost_port) = our_localhost_port {
|
||||
let gussed_default = Some(format!("http://localhost:{}", our_localhost_port));
|
||||
if node_json_rpc_url.is_some() && node_json_rpc_url != gussed_default {
|
||||
// go to new line to leave this message on console
|
||||
println!(
|
||||
"Prefering explicitly given rpc ({}) as us, \
|
||||
although --our-localhost is given\n",
|
||||
node_json_rpc_url.as_ref().unwrap()
|
||||
);
|
||||
} else {
|
||||
node_json_rpc_url = gussed_default;
|
||||
}
|
||||
}
|
||||
|
||||
RpcClient::new_socket(rpc_addr)
|
||||
let (node_client, node_pubkey) = if our_localhost_port.is_some() {
|
||||
let client = RpcClient::new(node_json_rpc_url.unwrap());
|
||||
let guessed_default = Some(client.get_identity()?);
|
||||
(
|
||||
client,
|
||||
(if node_pubkey.is_some() && node_pubkey != guessed_default {
|
||||
// go to new line to leave this message on console
|
||||
println!(
|
||||
"Prefering explicitly given node pubkey ({}) as us, \
|
||||
although --our-localhost is given\n",
|
||||
node_pubkey.unwrap()
|
||||
);
|
||||
node_pubkey
|
||||
} else {
|
||||
guessed_default
|
||||
})
|
||||
.unwrap(),
|
||||
)
|
||||
} else if let Some(node_pubkey) = node_pubkey {
|
||||
if let Some(node_json_rpc_url) = node_json_rpc_url {
|
||||
(RpcClient::new(node_json_rpc_url), node_pubkey)
|
||||
} else {
|
||||
let rpc_addr = loop {
|
||||
let cluster_nodes = rpc_client.get_cluster_nodes()?;
|
||||
if let Some(contact_info) = cluster_nodes
|
||||
.iter()
|
||||
.find(|contact_info| contact_info.pubkey == node_pubkey.to_string())
|
||||
{
|
||||
if let Some(rpc_addr) = contact_info.rpc {
|
||||
break rpc_addr;
|
||||
}
|
||||
progress_bar.set_message(&format!("RPC service not found for {}", node_pubkey));
|
||||
} else {
|
||||
progress_bar.set_message(&format!(
|
||||
"Contact information not found for {}",
|
||||
node_pubkey
|
||||
));
|
||||
}
|
||||
sleep(Duration::from_secs(sleep_interval as u64));
|
||||
};
|
||||
|
||||
(RpcClient::new_socket(rpc_addr), node_pubkey)
|
||||
}
|
||||
} else {
|
||||
unreachable!()
|
||||
};
|
||||
|
||||
let reported_node_pubkey = loop {
|
||||
@@ -604,7 +704,7 @@ pub fn process_catchup(
|
||||
}
|
||||
};
|
||||
|
||||
if reported_node_pubkey != *node_pubkey {
|
||||
if reported_node_pubkey != node_pubkey {
|
||||
return Err(format!(
|
||||
"The identity reported by node RPC URL does not match. Expected: {:?}. Reported: {:?}",
|
||||
node_pubkey, reported_node_pubkey
|
||||
@@ -612,15 +712,41 @@ pub fn process_catchup(
|
||||
.into());
|
||||
}
|
||||
|
||||
if rpc_client.get_identity()? == *node_pubkey {
|
||||
if rpc_client.get_identity()? == node_pubkey {
|
||||
return Err("Both RPC URLs reference the same node, unable to monitor for catchup. Try a different --url".into());
|
||||
}
|
||||
|
||||
let mut previous_rpc_slot = std::u64::MAX;
|
||||
let mut previous_slot_distance = 0;
|
||||
let mut retry_count = 0;
|
||||
let max_retry_count = 5;
|
||||
let mut get_slot_while_retrying = |client: &RpcClient| {
|
||||
loop {
|
||||
match client.get_slot_with_commitment(config.commitment) {
|
||||
Ok(r) => {
|
||||
retry_count = 0;
|
||||
return Ok(r);
|
||||
}
|
||||
Err(e) => {
|
||||
if retry_count >= max_retry_count {
|
||||
return Err(e);
|
||||
}
|
||||
retry_count += 1;
|
||||
if log {
|
||||
// go to new line to leave this message on console
|
||||
println!("Retrying({}/{}): {}\n", retry_count, max_retry_count, e);
|
||||
}
|
||||
sleep(Duration::from_secs(1));
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
loop {
|
||||
let rpc_slot = rpc_client.get_slot_with_commitment(config.commitment)?;
|
||||
let node_slot = node_client.get_slot_with_commitment(config.commitment)?;
|
||||
// humbly retry; the reference node (rpc_client) could be spotty,
|
||||
// especially if pointing to api.meinnet-beta.solana.com at times
|
||||
let rpc_slot = get_slot_while_retrying(rpc_client)?;
|
||||
let node_slot = get_slot_while_retrying(&node_client)?;
|
||||
if !follow && node_slot > std::cmp::min(previous_rpc_slot, rpc_slot) {
|
||||
progress_bar.finish_and_clear();
|
||||
return Ok(format!(
|
||||
@@ -643,15 +769,21 @@ pub fn process_catchup(
|
||||
};
|
||||
|
||||
progress_bar.set_message(&format!(
|
||||
"{} slots behind (us:{} them:{}){}",
|
||||
slot_distance,
|
||||
"{} slot(s) {} (us:{} them:{}){}",
|
||||
slot_distance.abs(),
|
||||
if slot_distance >= 0 {
|
||||
"behind"
|
||||
} else {
|
||||
"ahead"
|
||||
},
|
||||
node_slot,
|
||||
rpc_slot,
|
||||
if slot_distance == 0 || previous_rpc_slot == std::u64::MAX {
|
||||
"".to_string()
|
||||
} else {
|
||||
format!(
|
||||
", {} at {:.1} slots/second{}",
|
||||
", {} node is {} at {:.1} slots/second{}",
|
||||
if slot_distance >= 0 { "our" } else { "their" },
|
||||
if slots_per_second < 0.0 {
|
||||
"falling behind"
|
||||
} else {
|
||||
@@ -660,8 +792,11 @@ pub fn process_catchup(
|
||||
slots_per_second,
|
||||
time_remaining
|
||||
)
|
||||
}
|
||||
},
|
||||
));
|
||||
if log {
|
||||
println!();
|
||||
}
|
||||
|
||||
sleep(Duration::from_secs(sleep_interval as u64));
|
||||
previous_rpc_slot = rpc_slot;
|
||||
@@ -670,8 +805,7 @@ pub fn process_catchup(
|
||||
}
|
||||
|
||||
pub fn process_cluster_date(rpc_client: &RpcClient, config: &CliConfig) -> ProcessResult {
|
||||
let result = rpc_client
|
||||
.get_account_with_commitment(&sysvar::clock::id(), CommitmentConfig::default())?;
|
||||
let result = rpc_client.get_account_with_commitment(&sysvar::clock::id(), config.commitment)?;
|
||||
if let Some(clock_account) = result.value {
|
||||
let clock: Clock = from_account(&clock_account).ok_or_else(|| {
|
||||
CliError::RpcRequestError("Failed to deserialize clock sysvar".to_string())
|
||||
@@ -696,14 +830,35 @@ pub fn process_cluster_version(rpc_client: &RpcClient, config: &CliConfig) -> Pr
|
||||
}
|
||||
}
|
||||
|
||||
pub fn process_fees(rpc_client: &RpcClient, config: &CliConfig) -> ProcessResult {
|
||||
let result = rpc_client.get_recent_blockhash_with_commitment(CommitmentConfig::default())?;
|
||||
let (recent_blockhash, fee_calculator, last_valid_slot) = result.value;
|
||||
let fees = CliFees {
|
||||
slot: result.context.slot,
|
||||
blockhash: recent_blockhash.to_string(),
|
||||
lamports_per_signature: fee_calculator.lamports_per_signature,
|
||||
last_valid_slot,
|
||||
pub fn process_fees(
|
||||
rpc_client: &RpcClient,
|
||||
config: &CliConfig,
|
||||
blockhash: Option<&Hash>,
|
||||
) -> ProcessResult {
|
||||
let fees = if let Some(recent_blockhash) = blockhash {
|
||||
let result = rpc_client.get_fee_calculator_for_blockhash_with_commitment(
|
||||
recent_blockhash,
|
||||
config.commitment,
|
||||
)?;
|
||||
if let Some(fee_calculator) = result.value {
|
||||
CliFees::some(
|
||||
result.context.slot,
|
||||
*recent_blockhash,
|
||||
fee_calculator.lamports_per_signature,
|
||||
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;
|
||||
CliFees::some(
|
||||
result.context.slot,
|
||||
recent_blockhash,
|
||||
fee_calculator.lamports_per_signature,
|
||||
Some(last_valid_slot),
|
||||
)
|
||||
};
|
||||
Ok(config.output_format.formatted_string(&fees))
|
||||
}
|
||||
@@ -713,9 +868,27 @@ pub fn process_first_available_block(rpc_client: &RpcClient) -> ProcessResult {
|
||||
Ok(format!("{}", first_available_block))
|
||||
}
|
||||
|
||||
pub fn process_leader_schedule(rpc_client: &RpcClient) -> ProcessResult {
|
||||
pub fn parse_leader_schedule(matches: &ArgMatches<'_>) -> Result<CliCommandInfo, CliError> {
|
||||
let epoch = value_of(matches, "epoch");
|
||||
Ok(CliCommandInfo {
|
||||
command: CliCommand::LeaderSchedule { epoch },
|
||||
signers: vec![],
|
||||
})
|
||||
}
|
||||
|
||||
pub fn process_leader_schedule(
|
||||
rpc_client: &RpcClient,
|
||||
config: &CliConfig,
|
||||
epoch: Option<Epoch>,
|
||||
) -> ProcessResult {
|
||||
let epoch_info = rpc_client.get_epoch_info()?;
|
||||
let first_slot_in_epoch = epoch_info.absolute_slot - epoch_info.slot_index;
|
||||
let epoch = epoch.unwrap_or(epoch_info.epoch);
|
||||
if epoch > epoch_info.epoch {
|
||||
return Err(format!("Epoch {} is in the future", epoch).into());
|
||||
}
|
||||
|
||||
let epoch_schedule = rpc_client.get_epoch_schedule()?;
|
||||
let first_slot_in_epoch = epoch_schedule.get_first_slot_in_epoch(epoch);
|
||||
|
||||
let leader_schedule = rpc_client.get_leader_schedule(Some(first_slot_in_epoch))?;
|
||||
if leader_schedule.is_none() {
|
||||
@@ -737,92 +910,38 @@ pub fn process_leader_schedule(rpc_client: &RpcClient) -> ProcessResult {
|
||||
}
|
||||
}
|
||||
|
||||
let mut leader_schedule_entries = vec![];
|
||||
for (slot_index, leader) in leader_per_slot_index.iter().enumerate() {
|
||||
println!(
|
||||
" {:<15} {:<44}",
|
||||
first_slot_in_epoch + slot_index as u64,
|
||||
leader
|
||||
);
|
||||
leader_schedule_entries.push(CliLeaderScheduleEntry {
|
||||
slot: first_slot_in_epoch + slot_index as u64,
|
||||
leader: leader.to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
Ok("".to_string())
|
||||
Ok(config.output_format.formatted_string(&CliLeaderSchedule {
|
||||
epoch,
|
||||
leader_schedule_entries,
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn process_get_block(
|
||||
rpc_client: &RpcClient,
|
||||
_config: &CliConfig,
|
||||
config: &CliConfig,
|
||||
slot: Option<Slot>,
|
||||
) -> ProcessResult {
|
||||
let slot = if let Some(slot) = slot {
|
||||
slot
|
||||
} else {
|
||||
rpc_client.get_slot()?
|
||||
rpc_client.get_slot_with_commitment(CommitmentConfig::finalized())?
|
||||
};
|
||||
|
||||
let mut block =
|
||||
let encoded_confirmed_block =
|
||||
rpc_client.get_confirmed_block_with_encoding(slot, UiTransactionEncoding::Base64)?;
|
||||
|
||||
println!("Slot: {}", slot);
|
||||
println!("Parent Slot: {}", block.parent_slot);
|
||||
println!("Blockhash: {}", block.blockhash);
|
||||
println!("Previous Blockhash: {}", block.previous_blockhash);
|
||||
if let Some(block_time) = block.block_time {
|
||||
println!("Block Time: {:?}", Local.timestamp(block_time, 0));
|
||||
}
|
||||
if !block.rewards.is_empty() {
|
||||
block.rewards.sort_by(|a, b| a.pubkey.cmp(&b.pubkey));
|
||||
let mut total_rewards = 0;
|
||||
println!("Rewards:",);
|
||||
println!(
|
||||
" {:<44} {:^15} {:<15} {:<20} {:>14}",
|
||||
"Address", "Type", "Amount", "New Balance", "Percent Change"
|
||||
);
|
||||
for reward in block.rewards {
|
||||
let sign = if reward.lamports < 0 { "-" } else { "" };
|
||||
|
||||
total_rewards += reward.lamports;
|
||||
println!(
|
||||
" {:<44} {:^15} {:>15} {}",
|
||||
reward.pubkey,
|
||||
if let Some(reward_type) = reward.reward_type {
|
||||
format!("{}", reward_type)
|
||||
} else {
|
||||
"-".to_string()
|
||||
},
|
||||
format!(
|
||||
"{}◎{:<14.9}",
|
||||
sign,
|
||||
lamports_to_sol(reward.lamports.abs() as u64)
|
||||
),
|
||||
if reward.post_balance == 0 {
|
||||
" - -".to_string()
|
||||
} else {
|
||||
format!(
|
||||
"◎{:<19.9} {:>13.9}%",
|
||||
lamports_to_sol(reward.post_balance),
|
||||
reward.lamports.abs() as f64
|
||||
/ (reward.post_balance as f64 - reward.lamports as f64)
|
||||
)
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
let sign = if total_rewards < 0 { "-" } else { "" };
|
||||
println!(
|
||||
"Total Rewards: {}◎{:<12.9}",
|
||||
sign,
|
||||
lamports_to_sol(total_rewards.abs() as u64)
|
||||
);
|
||||
}
|
||||
for (index, transaction_with_meta) in block.transactions.iter().enumerate() {
|
||||
println!("Transaction {}:", index);
|
||||
println_transaction(
|
||||
&transaction_with_meta.transaction.decode().unwrap(),
|
||||
&transaction_with_meta.meta,
|
||||
" ",
|
||||
);
|
||||
}
|
||||
Ok("".to_string())
|
||||
let cli_block = CliBlock {
|
||||
encoded_confirmed_block,
|
||||
slot,
|
||||
};
|
||||
Ok(config.output_format.formatted_string(&cli_block))
|
||||
}
|
||||
|
||||
pub fn process_get_block_time(
|
||||
@@ -833,22 +952,20 @@ pub fn process_get_block_time(
|
||||
let slot = if let Some(slot) = slot {
|
||||
slot
|
||||
} else {
|
||||
rpc_client.get_slot()?
|
||||
rpc_client.get_slot_with_commitment(CommitmentConfig::finalized())?
|
||||
};
|
||||
let timestamp = rpc_client.get_block_time(slot)?;
|
||||
let block_time = CliBlockTime { slot, timestamp };
|
||||
Ok(config.output_format.formatted_string(&block_time))
|
||||
}
|
||||
|
||||
pub fn process_get_epoch(rpc_client: &RpcClient, config: &CliConfig) -> ProcessResult {
|
||||
let epoch_info = rpc_client.get_epoch_info_with_commitment(config.commitment)?;
|
||||
pub fn process_get_epoch(rpc_client: &RpcClient, _config: &CliConfig) -> ProcessResult {
|
||||
let epoch_info = rpc_client.get_epoch_info()?;
|
||||
Ok(epoch_info.epoch.to_string())
|
||||
}
|
||||
|
||||
pub fn process_get_epoch_info(rpc_client: &RpcClient, config: &CliConfig) -> ProcessResult {
|
||||
let epoch_info: CliEpochInfo = rpc_client
|
||||
.get_epoch_info_with_commitment(config.commitment)?
|
||||
.into();
|
||||
let epoch_info: CliEpochInfo = rpc_client.get_epoch_info()?.into();
|
||||
Ok(config.output_format.formatted_string(&epoch_info))
|
||||
}
|
||||
|
||||
@@ -857,15 +974,13 @@ pub fn process_get_genesis_hash(rpc_client: &RpcClient) -> ProcessResult {
|
||||
Ok(genesis_hash.to_string())
|
||||
}
|
||||
|
||||
pub fn process_get_slot(rpc_client: &RpcClient, config: &CliConfig) -> ProcessResult {
|
||||
let slot = rpc_client.get_slot_with_commitment(config.commitment)?;
|
||||
pub fn process_get_slot(rpc_client: &RpcClient, _config: &CliConfig) -> ProcessResult {
|
||||
let slot = rpc_client.get_slot()?;
|
||||
Ok(slot.to_string())
|
||||
}
|
||||
|
||||
pub fn process_get_block_height(rpc_client: &RpcClient, config: &CliConfig) -> ProcessResult {
|
||||
let epoch_info: CliEpochInfo = rpc_client
|
||||
.get_epoch_info_with_commitment(config.commitment)?
|
||||
.into();
|
||||
pub fn process_get_block_height(rpc_client: &RpcClient, _config: &CliConfig) -> ProcessResult {
|
||||
let epoch_info: CliEpochInfo = rpc_client.get_epoch_info()?.into();
|
||||
Ok(epoch_info.epoch_info.block_height.to_string())
|
||||
}
|
||||
|
||||
@@ -886,7 +1001,7 @@ pub fn process_show_block_production(
|
||||
slot_limit: Option<u64>,
|
||||
) -> ProcessResult {
|
||||
let epoch_schedule = rpc_client.get_epoch_schedule()?;
|
||||
let epoch_info = rpc_client.get_epoch_info_with_commitment(CommitmentConfig::root())?;
|
||||
let epoch_info = rpc_client.get_epoch_info_with_commitment(CommitmentConfig::finalized())?;
|
||||
|
||||
let epoch = epoch.unwrap_or(epoch_info.epoch);
|
||||
if epoch > epoch_info.epoch {
|
||||
@@ -945,7 +1060,7 @@ pub fn process_show_block_production(
|
||||
|
||||
progress_bar.set_message(&format!("Fetching leader schedule for epoch {}...", epoch));
|
||||
let leader_schedule = rpc_client
|
||||
.get_leader_schedule_with_commitment(Some(start_slot), CommitmentConfig::root())?;
|
||||
.get_leader_schedule_with_commitment(Some(start_slot), CommitmentConfig::finalized())?;
|
||||
if leader_schedule.is_none() {
|
||||
return Err(format!("Unable to fetch leader schedule for slot {}", start_slot).into());
|
||||
}
|
||||
@@ -1051,19 +1166,19 @@ pub fn process_supply(
|
||||
config: &CliConfig,
|
||||
print_accounts: bool,
|
||||
) -> ProcessResult {
|
||||
let supply_response = rpc_client.supply_with_commitment(config.commitment)?;
|
||||
let supply_response = rpc_client.supply()?;
|
||||
let mut supply: CliSupply = supply_response.value.into();
|
||||
supply.print_accounts = print_accounts;
|
||||
Ok(config.output_format.formatted_string(&supply))
|
||||
}
|
||||
|
||||
pub fn process_total_supply(rpc_client: &RpcClient, config: &CliConfig) -> ProcessResult {
|
||||
let total_supply = rpc_client.total_supply_with_commitment(config.commitment)?;
|
||||
pub fn process_total_supply(rpc_client: &RpcClient, _config: &CliConfig) -> ProcessResult {
|
||||
let total_supply = rpc_client.total_supply()?;
|
||||
Ok(format!("{} SOL", lamports_to_sol(total_supply)))
|
||||
}
|
||||
|
||||
pub fn process_get_transaction_count(rpc_client: &RpcClient, config: &CliConfig) -> ProcessResult {
|
||||
let transaction_count = rpc_client.get_transaction_count_with_commitment(config.commitment)?;
|
||||
pub fn process_get_transaction_count(rpc_client: &RpcClient, _config: &CliConfig) -> ProcessResult {
|
||||
let transaction_count = rpc_client.get_transaction_count()?;
|
||||
Ok(transaction_count.to_string())
|
||||
}
|
||||
|
||||
@@ -1154,8 +1269,7 @@ pub fn process_ping(
|
||||
Ok(signature) => {
|
||||
let transaction_sent = Instant::now();
|
||||
loop {
|
||||
let signature_status = rpc_client
|
||||
.get_signature_status_with_commitment(&signature, config.commitment)?;
|
||||
let signature_status = rpc_client.get_signature_status(&signature)?;
|
||||
let elapsed_time = Instant::now().duration_since(transaction_sent);
|
||||
if let Some(transaction_status) = signature_status {
|
||||
match transaction_status {
|
||||
@@ -1495,7 +1609,7 @@ pub fn process_show_stakes(
|
||||
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 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 {
|
||||
@@ -1559,8 +1673,8 @@ pub fn process_show_validators(
|
||||
config: &CliConfig,
|
||||
use_lamports_unit: bool,
|
||||
) -> ProcessResult {
|
||||
let epoch_info = rpc_client.get_epoch_info_with_commitment(config.commitment)?;
|
||||
let vote_accounts = rpc_client.get_vote_accounts_with_commitment(config.commitment)?;
|
||||
let epoch_info = rpc_client.get_epoch_info()?;
|
||||
let vote_accounts = rpc_client.get_vote_accounts()?;
|
||||
|
||||
let mut node_version = HashMap::new();
|
||||
let unknown_version = "unknown".to_string();
|
||||
@@ -1671,9 +1785,14 @@ pub fn process_transaction_history(
|
||||
for result in results {
|
||||
if config.verbose {
|
||||
println!(
|
||||
"{} [slot={} status={}] {}",
|
||||
"{} [slot={} {}status={}] {}",
|
||||
result.signature,
|
||||
result.slot,
|
||||
match result.block_time {
|
||||
None => "".to_string(),
|
||||
Some(block_time) =>
|
||||
format!("timestamp={} ", unix_timestamp_to_string(block_time)),
|
||||
},
|
||||
match result.err {
|
||||
None => "Confirmed".to_string(),
|
||||
Some(err) => format!("Failed: {:?}", err),
|
||||
@@ -1698,6 +1817,8 @@ pub fn process_transaction_history(
|
||||
.expect("Successful decode"),
|
||||
&confirmed_transaction.transaction.meta,
|
||||
" ",
|
||||
None,
|
||||
None,
|
||||
);
|
||||
}
|
||||
Err(err) => println!(" Unable to get confirmed transaction details: {}", err),
|
||||
@@ -1709,6 +1830,62 @@ pub fn process_transaction_history(
|
||||
Ok(transactions_found)
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct CliRentCalculation {
|
||||
pub lamports_per_byte_year: u64,
|
||||
pub lamports_per_epoch: u64,
|
||||
pub rent_exempt_minimum_lamports: u64,
|
||||
#[serde(skip)]
|
||||
pub use_lamports_unit: bool,
|
||||
}
|
||||
|
||||
impl CliRentCalculation {
|
||||
fn build_balance_message(&self, lamports: u64) -> String {
|
||||
build_balance_message(lamports, self.use_lamports_unit, true)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for CliRentCalculation {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let per_byte_year = self.build_balance_message(self.lamports_per_byte_year);
|
||||
let per_epoch = self.build_balance_message(self.lamports_per_epoch);
|
||||
let exempt_minimum = self.build_balance_message(self.rent_exempt_minimum_lamports);
|
||||
writeln_name_value(f, "Rent per byte-year:", &per_byte_year)?;
|
||||
writeln_name_value(f, "Rent per epoch:", &per_epoch)?;
|
||||
writeln_name_value(f, "Rent-exempt minimum:", &exempt_minimum)
|
||||
}
|
||||
}
|
||||
|
||||
impl QuietDisplay for CliRentCalculation {}
|
||||
impl VerboseDisplay for CliRentCalculation {}
|
||||
|
||||
pub fn process_calculate_rent(
|
||||
rpc_client: &RpcClient,
|
||||
config: &CliConfig,
|
||||
data_length: usize,
|
||||
use_lamports_unit: bool,
|
||||
) -> ProcessResult {
|
||||
let epoch_schedule = rpc_client.get_epoch_schedule()?;
|
||||
let rent_account = rpc_client.get_account(&sysvar::rent::id())?;
|
||||
let rent: Rent = rent_account.deserialize_data()?;
|
||||
let rent_exempt_minimum_lamports = rent.minimum_balance(data_length);
|
||||
let seconds_per_tick = Duration::from_secs_f64(1.0 / clock::DEFAULT_TICKS_PER_SECOND as f64);
|
||||
let slots_per_year =
|
||||
timing::years_as_slots(1.0, &seconds_per_tick, clock::DEFAULT_TICKS_PER_SLOT);
|
||||
let slots_per_epoch = epoch_schedule.slots_per_epoch as f64;
|
||||
let years_per_epoch = slots_per_epoch / slots_per_year;
|
||||
let (lamports_per_epoch, _) = rent.due(0, data_length, years_per_epoch);
|
||||
let cli_rent_calculation = CliRentCalculation {
|
||||
lamports_per_byte_year: rent.lamports_per_byte_year,
|
||||
lamports_per_epoch,
|
||||
rent_exempt_minimum_lamports,
|
||||
use_lamports_unit,
|
||||
};
|
||||
|
||||
Ok(config.output_format.formatted_string(&cli_rent_calculation))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -1759,7 +1936,24 @@ mod tests {
|
||||
assert_eq!(
|
||||
parse_command(&test_fees, &default_signer, &mut None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::Fees,
|
||||
command: CliCommand::Fees { blockhash: None },
|
||||
signers: vec![],
|
||||
}
|
||||
);
|
||||
|
||||
let blockhash = Hash::new_unique();
|
||||
let test_fees = test_commands.clone().get_matches_from(vec![
|
||||
"test",
|
||||
"fees",
|
||||
"--blockhash",
|
||||
&blockhash.to_string(),
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&test_fees, &default_signer, &mut None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::Fees {
|
||||
blockhash: Some(blockhash)
|
||||
},
|
||||
signers: vec![],
|
||||
}
|
||||
);
|
||||
@@ -1851,8 +2045,6 @@ mod tests {
|
||||
"-t",
|
||||
"3",
|
||||
"-D",
|
||||
"--commitment",
|
||||
"max",
|
||||
"--blockhash",
|
||||
"4CCNp28j6AhGq7PkjPDP4wbQWBS8LLbQin2xV5n8frKX",
|
||||
]);
|
||||
|
@@ -19,10 +19,22 @@ use solana_sdk::{
|
||||
};
|
||||
use std::{collections::HashMap, fmt, sync::Arc};
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
pub enum ForceActivation {
|
||||
No,
|
||||
Almost,
|
||||
Yes,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum FeatureCliCommand {
|
||||
Status { features: Vec<Pubkey> },
|
||||
Activate { feature: Pubkey },
|
||||
Status {
|
||||
features: Vec<Pubkey>,
|
||||
},
|
||||
Activate {
|
||||
feature: Pubkey,
|
||||
force: ForceActivation,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
@@ -58,8 +70,8 @@ impl fmt::Display for CliFeatures {
|
||||
f,
|
||||
"{}",
|
||||
style(format!(
|
||||
"{:<44} {:<40} {}",
|
||||
"Feature", "Description", "Status"
|
||||
"{:<44} | {:<27} | {}",
|
||||
"Feature", "Status", "Description"
|
||||
))
|
||||
.bold()
|
||||
)?;
|
||||
@@ -67,15 +79,15 @@ impl fmt::Display for CliFeatures {
|
||||
for feature in &self.features {
|
||||
writeln!(
|
||||
f,
|
||||
"{:<44} {:<40} {}",
|
||||
"{:<44} | {:<27} | {}",
|
||||
feature.id,
|
||||
feature.description,
|
||||
match feature.status {
|
||||
CliFeatureStatus::Inactive => style("inactive".to_string()).red(),
|
||||
CliFeatureStatus::Pending => style("activation pending".to_string()).yellow(),
|
||||
CliFeatureStatus::Active(activation_slot) =>
|
||||
style(format!("active since slot {}", activation_slot)).green(),
|
||||
}
|
||||
},
|
||||
feature.description,
|
||||
)?;
|
||||
}
|
||||
if self.inactive && !self.feature_activation_allowed {
|
||||
@@ -126,6 +138,13 @@ impl FeatureSubCommands for App<'_, '_> {
|
||||
.index(1)
|
||||
.required(true)
|
||||
.help("The signer for the feature to activate"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("force")
|
||||
.long("yolo")
|
||||
.hidden(true)
|
||||
.multiple(true)
|
||||
.help("Override activation sanity checks. Don't use this flag"),
|
||||
),
|
||||
),
|
||||
)
|
||||
@@ -152,13 +171,20 @@ pub fn parse_feature_subcommand(
|
||||
("activate", Some(matches)) => {
|
||||
let (feature_signer, feature) = signer_of(matches, "feature", wallet_manager)?;
|
||||
let mut signers = vec![default_signer.signer_from_path(matches, wallet_manager)?];
|
||||
|
||||
let force = match matches.occurrences_of("force") {
|
||||
2 => ForceActivation::Yes,
|
||||
1 => ForceActivation::Almost,
|
||||
_ => ForceActivation::No,
|
||||
};
|
||||
|
||||
signers.push(feature_signer.unwrap());
|
||||
let feature = feature.unwrap();
|
||||
|
||||
known_feature(&feature)?;
|
||||
|
||||
CliCommandInfo {
|
||||
command: CliCommand::Feature(FeatureCliCommand::Activate { feature }),
|
||||
command: CliCommand::Feature(FeatureCliCommand::Activate { feature, force }),
|
||||
signers,
|
||||
}
|
||||
}
|
||||
@@ -189,11 +215,13 @@ pub fn process_feature_subcommand(
|
||||
) -> ProcessResult {
|
||||
match feature_subcommand {
|
||||
FeatureCliCommand::Status { features } => process_status(rpc_client, config, features),
|
||||
FeatureCliCommand::Activate { feature } => process_activate(rpc_client, config, *feature),
|
||||
FeatureCliCommand::Activate { feature, force } => {
|
||||
process_activate(rpc_client, config, *feature, *force)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn active_stake_by_feature_set(rpc_client: &RpcClient) -> Result<HashMap<u32, u64>, ClientError> {
|
||||
fn active_stake_by_feature_set(rpc_client: &RpcClient) -> Result<HashMap<u32, f64>, ClientError> {
|
||||
// Validator identity -> feature set
|
||||
let feature_set_map = rpc_client
|
||||
.get_cluster_nodes()?
|
||||
@@ -211,7 +239,7 @@ fn active_stake_by_feature_set(rpc_client: &RpcClient) -> Result<HashMap<u32, u6
|
||||
.sum();
|
||||
|
||||
// Sum all active stake by feature set
|
||||
let mut active_stake_by_feature_set = HashMap::new();
|
||||
let mut active_stake_by_feature_set: HashMap<u32, u64> = HashMap::new();
|
||||
for vote_account in vote_accounts.current {
|
||||
if let Some(Some(feature_set)) = feature_set_map.get(&vote_account.node_pubkey) {
|
||||
*active_stake_by_feature_set.entry(*feature_set).or_default() +=
|
||||
@@ -223,11 +251,15 @@ fn active_stake_by_feature_set(rpc_client: &RpcClient) -> Result<HashMap<u32, u6
|
||||
}
|
||||
}
|
||||
|
||||
// Convert active stake to a percentage so the caller doesn't need `total_active_stake`
|
||||
for (_, val) in active_stake_by_feature_set.iter_mut() {
|
||||
*val = *val * 100 / total_active_stake;
|
||||
}
|
||||
Ok(active_stake_by_feature_set)
|
||||
Ok(active_stake_by_feature_set
|
||||
.into_iter()
|
||||
.map(|(feature_set, active_stake)| {
|
||||
(
|
||||
feature_set,
|
||||
active_stake as f64 * 100. / total_active_stake as f64,
|
||||
)
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
|
||||
// Feature activation is only allowed when 95% of the active stake is on the current feature set
|
||||
@@ -238,7 +270,7 @@ fn feature_activation_allowed(rpc_client: &RpcClient, quiet: bool) -> Result<boo
|
||||
|
||||
let feature_activation_allowed = active_stake_by_feature_set
|
||||
.get(&my_feature_set)
|
||||
.map(|percentage| *percentage >= 95)
|
||||
.map(|percentage| *percentage >= 95.)
|
||||
.unwrap_or(false);
|
||||
|
||||
if !feature_activation_allowed && !quiet {
|
||||
@@ -255,15 +287,15 @@ fn feature_activation_allowed(rpc_client: &RpcClient, quiet: bool) -> Result<boo
|
||||
}
|
||||
println!(
|
||||
"{}",
|
||||
style(format!("Tool Feture Set: {}", my_feature_set)).bold()
|
||||
style(format!("Tool Feature Set: {}", my_feature_set)).bold()
|
||||
);
|
||||
println!("{}", style("Cluster Feature Sets and Stakes:").bold());
|
||||
for (feature_set, percentage) in active_stake_by_feature_set.iter() {
|
||||
if *feature_set == 0 {
|
||||
println!("unknown - {}%", percentage);
|
||||
println!(" unknown - {:.2}%", percentage);
|
||||
} else {
|
||||
println!(
|
||||
"{} - {}% {}",
|
||||
" {:<10} - {:.2}% {}",
|
||||
feature_set,
|
||||
percentage,
|
||||
if *feature_set == my_feature_set {
|
||||
@@ -329,12 +361,14 @@ fn process_activate(
|
||||
rpc_client: &RpcClient,
|
||||
config: &CliConfig,
|
||||
feature_id: Pubkey,
|
||||
force: ForceActivation,
|
||||
) -> ProcessResult {
|
||||
let account = rpc_client
|
||||
.get_multiple_accounts(&[feature_id])?
|
||||
.into_iter()
|
||||
.next()
|
||||
.unwrap();
|
||||
|
||||
if let Some(account) = account {
|
||||
if feature::from_account(&account).is_some() {
|
||||
return Err(format!("{} has already been activated", feature_id).into());
|
||||
@@ -342,7 +376,13 @@ fn process_activate(
|
||||
}
|
||||
|
||||
if !feature_activation_allowed(rpc_client, false)? {
|
||||
return Err("Feature activation is not allowed at this time".into());
|
||||
match force {
|
||||
ForceActivation::Almost =>
|
||||
return Err("Add force argument once more to override the sanity check to force feature activation ".into()),
|
||||
ForceActivation::Yes => println!("FEATURE ACTIVATION FORCED"),
|
||||
ForceActivation::No =>
|
||||
return Err("Feature activation is not allowed at this time".into()),
|
||||
}
|
||||
}
|
||||
|
||||
let rent = rpc_client.get_minimum_balance_for_rent_exemption(Feature::size_of())?;
|
||||
|
@@ -1,7 +1,7 @@
|
||||
use crate::cli::{CliCommand, CliCommandInfo, CliConfig, CliError, ProcessResult};
|
||||
use clap::{App, ArgMatches, SubCommand};
|
||||
use console::style;
|
||||
use solana_clap_utils::keypair::*;
|
||||
use solana_cli_output::CliInflation;
|
||||
use solana_client::rpc_client::RpcClient;
|
||||
use solana_remote_wallet::remote_wallet::RemoteWalletManager;
|
||||
use std::sync::Arc;
|
||||
@@ -34,56 +34,18 @@ pub fn parse_inflation_subcommand(
|
||||
|
||||
pub fn process_inflation_subcommand(
|
||||
rpc_client: &RpcClient,
|
||||
_config: &CliConfig,
|
||||
config: &CliConfig,
|
||||
inflation_subcommand: &InflationCliCommand,
|
||||
) -> ProcessResult {
|
||||
assert_eq!(*inflation_subcommand, InflationCliCommand::Show);
|
||||
|
||||
let governor = rpc_client.get_inflation_governor()?;
|
||||
let current_inflation_rate = rpc_client.get_inflation_rate()?;
|
||||
let current_rate = rpc_client.get_inflation_rate()?;
|
||||
|
||||
println!("{}", style("Inflation Governor:").bold());
|
||||
if (governor.initial - governor.terminal).abs() < f64::EPSILON {
|
||||
println!(
|
||||
"Fixed APR: {:>5.2}%",
|
||||
governor.terminal * 100.
|
||||
);
|
||||
} else {
|
||||
println!("Initial APR: {:>5.2}%", governor.initial * 100.);
|
||||
println!(
|
||||
"Terminal APR: {:>5.2}%",
|
||||
governor.terminal * 100.
|
||||
);
|
||||
println!("Rate reduction per year: {:>5.2}%", governor.taper * 100.);
|
||||
}
|
||||
if governor.foundation_term > 0. {
|
||||
println!("Foundation percentage: {:>5.2}%", governor.foundation);
|
||||
println!(
|
||||
"Foundation term: {:.1} years",
|
||||
governor.foundation_term
|
||||
);
|
||||
}
|
||||
let inflation = CliInflation {
|
||||
governor,
|
||||
current_rate,
|
||||
};
|
||||
|
||||
println!(
|
||||
"\n{}",
|
||||
style(format!(
|
||||
"Inflation for Epoch {}:",
|
||||
current_inflation_rate.epoch
|
||||
))
|
||||
.bold()
|
||||
);
|
||||
println!(
|
||||
"Total APR: {:>5.2}%",
|
||||
current_inflation_rate.total * 100.
|
||||
);
|
||||
println!(
|
||||
"Staking APR: {:>5.2}%",
|
||||
current_inflation_rate.validator * 100.
|
||||
);
|
||||
println!(
|
||||
"Foundation APR: {:>5.2}%",
|
||||
current_inflation_rate.foundation * 100.
|
||||
);
|
||||
|
||||
Ok("".to_string())
|
||||
Ok(config.output_format.formatted_string(&inflation))
|
||||
}
|
||||
|
@@ -1,3 +1,4 @@
|
||||
#![allow(clippy::integer_arithmetic)]
|
||||
macro_rules! ACCOUNT_STRING {
|
||||
() => {
|
||||
r#", one of:
|
||||
@@ -26,6 +27,7 @@ pub mod cluster_query;
|
||||
pub mod feature;
|
||||
pub mod inflation;
|
||||
pub mod nonce;
|
||||
pub mod program;
|
||||
pub mod send_tpu;
|
||||
pub mod spend_utils;
|
||||
pub mod stake;
|
||||
|
@@ -3,11 +3,8 @@ use clap::{
|
||||
SubCommand,
|
||||
};
|
||||
use console::style;
|
||||
|
||||
use solana_clap_utils::{
|
||||
commitment::COMMITMENT_ARG,
|
||||
input_parsers::commitment_of,
|
||||
input_validators::is_url,
|
||||
input_validators::{is_url, is_url_or_moniker, normalize_to_url_if_moniker},
|
||||
keypair::{CliSigners, DefaultSigner, SKIP_SEED_PHRASE_VALIDATION_ARG},
|
||||
DisplayError,
|
||||
};
|
||||
@@ -63,12 +60,19 @@ fn parse_settings(matches: &ArgMatches<'_>) -> Result<bool, Box<dyn error::Error
|
||||
);
|
||||
let (keypair_setting_type, keypair_path) =
|
||||
CliConfig::compute_keypair_path_setting("", &config.keypair_path);
|
||||
let (commitment_setting_type, commitment) =
|
||||
CliConfig::compute_commitment_config("", &config.commitment);
|
||||
|
||||
if let Some(field) = subcommand_matches.value_of("specific_setting") {
|
||||
let (field_name, value, setting_type) = match field {
|
||||
"json_rpc_url" => ("RPC URL", json_rpc_url, url_setting_type),
|
||||
"websocket_url" => ("WebSocket URL", websocket_url, ws_setting_type),
|
||||
"keypair" => ("Key Path", keypair_path, keypair_setting_type),
|
||||
"commitment" => (
|
||||
"Commitment",
|
||||
commitment.commitment.to_string(),
|
||||
commitment_setting_type,
|
||||
),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
println_name_value_or(&format!("{}:", field_name), &value, setting_type);
|
||||
@@ -77,11 +81,16 @@ fn parse_settings(matches: &ArgMatches<'_>) -> Result<bool, Box<dyn error::Error
|
||||
println_name_value_or("RPC URL:", &json_rpc_url, url_setting_type);
|
||||
println_name_value_or("WebSocket URL:", &websocket_url, ws_setting_type);
|
||||
println_name_value_or("Keypair Path:", &keypair_path, keypair_setting_type);
|
||||
println_name_value_or(
|
||||
"Commitment:",
|
||||
&commitment.commitment.to_string(),
|
||||
commitment_setting_type,
|
||||
);
|
||||
}
|
||||
}
|
||||
("set", Some(subcommand_matches)) => {
|
||||
if let Some(url) = subcommand_matches.value_of("json_rpc_url") {
|
||||
config.json_rpc_url = url.to_string();
|
||||
config.json_rpc_url = normalize_to_url_if_moniker(url);
|
||||
// Revert to a computed `websocket_url` value when `json_rpc_url` is
|
||||
// changed
|
||||
config.websocket_url = "".to_string();
|
||||
@@ -92,6 +101,9 @@ fn parse_settings(matches: &ArgMatches<'_>) -> Result<bool, Box<dyn error::Error
|
||||
if let Some(keypair) = subcommand_matches.value_of("keypair") {
|
||||
config.keypair_path = keypair.to_string();
|
||||
}
|
||||
if let Some(commitment) = subcommand_matches.value_of("commitment") {
|
||||
config.commitment = commitment.to_string();
|
||||
}
|
||||
|
||||
config.save(config_file)?;
|
||||
|
||||
@@ -105,11 +117,18 @@ fn parse_settings(matches: &ArgMatches<'_>) -> Result<bool, Box<dyn error::Error
|
||||
);
|
||||
let (keypair_setting_type, keypair_path) =
|
||||
CliConfig::compute_keypair_path_setting("", &config.keypair_path);
|
||||
let (commitment_setting_type, commitment) =
|
||||
CliConfig::compute_commitment_config("", &config.commitment);
|
||||
|
||||
println_name_value("Config File:", config_file);
|
||||
println_name_value_or("RPC URL:", &json_rpc_url, url_setting_type);
|
||||
println_name_value_or("WebSocket URL:", &websocket_url, ws_setting_type);
|
||||
println_name_value_or("Keypair Path:", &keypair_path, keypair_setting_type);
|
||||
println_name_value_or(
|
||||
"Commitment:",
|
||||
&commitment.commitment.to_string(),
|
||||
commitment_setting_type,
|
||||
);
|
||||
}
|
||||
("import-address-labels", Some(subcommand_matches)) => {
|
||||
let filename = value_t_or_exit!(subcommand_matches, "filename", PathBuf);
|
||||
@@ -165,8 +184,18 @@ pub fn parse_args<'a>(
|
||||
path: default_signer_path.clone(),
|
||||
};
|
||||
|
||||
let CliCommandInfo { command, signers } =
|
||||
parse_command(&matches, &default_signer, &mut wallet_manager)?;
|
||||
let CliCommandInfo {
|
||||
command,
|
||||
mut signers,
|
||||
} = parse_command(&matches, &default_signer, &mut wallet_manager)?;
|
||||
|
||||
if signers.is_empty() {
|
||||
if let Ok(signer_info) =
|
||||
default_signer.generate_unique_signers(vec![None], matches, &mut wallet_manager)
|
||||
{
|
||||
signers.extend(signer_info.signers);
|
||||
}
|
||||
}
|
||||
|
||||
let verbose = matches.is_present("verbose");
|
||||
let output_format = matches
|
||||
@@ -182,11 +211,10 @@ pub fn parse_args<'a>(
|
||||
OutputFormat::Display
|
||||
});
|
||||
|
||||
let commitment = matches
|
||||
.subcommand_name()
|
||||
.and_then(|name| matches.subcommand_matches(name))
|
||||
.and_then(|sub_matches| commitment_of(sub_matches, COMMITMENT_ARG.long))
|
||||
.unwrap_or_default();
|
||||
let (_, commitment) = CliConfig::compute_commitment_config(
|
||||
matches.value_of("commitment").unwrap_or(""),
|
||||
&config.commitment,
|
||||
);
|
||||
|
||||
let address_labels = if matches.is_present("no_address_labels") {
|
||||
HashMap::new()
|
||||
@@ -206,7 +234,10 @@ pub fn parse_args<'a>(
|
||||
verbose,
|
||||
output_format,
|
||||
commitment,
|
||||
send_transaction_config: RpcSendTransactionConfig::default(),
|
||||
send_transaction_config: RpcSendTransactionConfig {
|
||||
preflight_commitment: Some(commitment.commitment),
|
||||
..RpcSendTransactionConfig::default()
|
||||
},
|
||||
address_labels,
|
||||
},
|
||||
signers,
|
||||
@@ -214,7 +245,7 @@ pub fn parse_args<'a>(
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Box<dyn error::Error>> {
|
||||
solana_logger::setup();
|
||||
solana_logger::setup_with_default("off");
|
||||
let matches = app(
|
||||
crate_name!(),
|
||||
crate_description!(),
|
||||
@@ -238,11 +269,14 @@ fn main() -> Result<(), Box<dyn error::Error>> {
|
||||
Arg::with_name("json_rpc_url")
|
||||
.short("u")
|
||||
.long("url")
|
||||
.value_name("URL")
|
||||
.value_name("URL_OR_MONIKER")
|
||||
.takes_value(true)
|
||||
.global(true)
|
||||
.validator(is_url)
|
||||
.help("JSON RPC URL for the solana cluster"),
|
||||
.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")
|
||||
@@ -262,6 +296,25 @@ fn main() -> Result<(), Box<dyn error::Error>> {
|
||||
.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")
|
||||
@@ -313,7 +366,12 @@ fn main() -> Result<(), Box<dyn error::Error>> {
|
||||
.index(1)
|
||||
.value_name("CONFIG_FIELD")
|
||||
.takes_value(true)
|
||||
.possible_values(&["json_rpc_url", "websocket_url", "keypair"])
|
||||
.possible_values(&[
|
||||
"json_rpc_url",
|
||||
"websocket_url",
|
||||
"keypair",
|
||||
"commitment",
|
||||
])
|
||||
.help("Return a specific config setting"),
|
||||
),
|
||||
)
|
||||
@@ -322,7 +380,7 @@ fn main() -> Result<(), Box<dyn error::Error>> {
|
||||
.about("Set a config setting")
|
||||
.group(
|
||||
ArgGroup::with_name("config_settings")
|
||||
.args(&["json_rpc_url", "websocket_url", "keypair"])
|
||||
.args(&["json_rpc_url", "websocket_url", "keypair", "commitment"])
|
||||
.multiple(true)
|
||||
.required(true),
|
||||
),
|
||||
|
@@ -332,9 +332,7 @@ pub fn process_authorize_nonce_account(
|
||||
nonce_authority: SignerIndex,
|
||||
new_authority: &Pubkey,
|
||||
) -> ProcessResult {
|
||||
let (recent_blockhash, fee_calculator, _) = rpc_client
|
||||
.get_recent_blockhash_with_commitment(config.commitment)?
|
||||
.value;
|
||||
let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?;
|
||||
|
||||
let nonce_authority = config.signers[nonce_authority];
|
||||
let ix = authorize_nonce_account(nonce_account, &nonce_authority.pubkey(), new_authority);
|
||||
@@ -349,11 +347,7 @@ pub fn process_authorize_nonce_account(
|
||||
&tx.message,
|
||||
config.commitment,
|
||||
)?;
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner_and_config(
|
||||
&tx,
|
||||
config.commitment,
|
||||
config.send_transaction_config,
|
||||
);
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner(&tx);
|
||||
log_instruction_custom_error::<NonceError>(result, &config)
|
||||
}
|
||||
|
||||
@@ -400,9 +394,7 @@ pub fn process_create_nonce_account(
|
||||
Message::new(&ixs, Some(&config.signers[0].pubkey()))
|
||||
};
|
||||
|
||||
let (recent_blockhash, fee_calculator, _) = rpc_client
|
||||
.get_recent_blockhash_with_commitment(config.commitment)?
|
||||
.value;
|
||||
let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?;
|
||||
|
||||
let (message, lamports) = resolve_spend_tx_and_check_account_balance(
|
||||
rpc_client,
|
||||
@@ -414,9 +406,7 @@ pub fn process_create_nonce_account(
|
||||
config.commitment,
|
||||
)?;
|
||||
|
||||
if let Ok(nonce_account) =
|
||||
get_account_with_commitment(rpc_client, &nonce_account_address, config.commitment)
|
||||
{
|
||||
if let Ok(nonce_account) = get_account(rpc_client, &nonce_account_address) {
|
||||
let err_msg = if state_from_account(&nonce_account).is_ok() {
|
||||
format!("Nonce account {} already exists", nonce_account_address)
|
||||
} else {
|
||||
@@ -439,11 +429,7 @@ pub fn process_create_nonce_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_and_config(
|
||||
&tx,
|
||||
config.commitment,
|
||||
config.send_transaction_config,
|
||||
);
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner(&tx);
|
||||
log_instruction_custom_error::<SystemError>(result, &config)
|
||||
}
|
||||
|
||||
@@ -471,20 +457,17 @@ pub fn process_new_nonce(
|
||||
(&nonce_account, "nonce_account_pubkey".to_string()),
|
||||
)?;
|
||||
|
||||
let nonce_account_check =
|
||||
rpc_client.get_account_with_commitment(&nonce_account, config.commitment);
|
||||
if nonce_account_check.is_err() || nonce_account_check.unwrap().value.is_none() {
|
||||
return Err(CliError::BadParameter(
|
||||
"Unable to create new nonce, no nonce account found".to_string(),
|
||||
)
|
||||
if let Err(err) = rpc_client.get_account(&nonce_account) {
|
||||
return Err(CliError::BadParameter(format!(
|
||||
"Unable to advance nonce account {}. error: {}",
|
||||
nonce_account, err
|
||||
))
|
||||
.into());
|
||||
}
|
||||
|
||||
let nonce_authority = config.signers[nonce_authority];
|
||||
let ix = advance_nonce_account(&nonce_account, &nonce_authority.pubkey());
|
||||
let (recent_blockhash, fee_calculator, _) = rpc_client
|
||||
.get_recent_blockhash_with_commitment(config.commitment)?
|
||||
.value;
|
||||
let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?;
|
||||
let message = Message::new(&[ix], Some(&config.signers[0].pubkey()));
|
||||
let mut tx = Transaction::new_unsigned(message);
|
||||
tx.try_sign(&config.signers, recent_blockhash)?;
|
||||
@@ -495,11 +478,7 @@ pub fn process_new_nonce(
|
||||
&tx.message,
|
||||
config.commitment,
|
||||
)?;
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner_and_config(
|
||||
&tx,
|
||||
config.commitment,
|
||||
config.send_transaction_config,
|
||||
);
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner(&tx);
|
||||
log_instruction_custom_error::<SystemError>(result, &config)
|
||||
}
|
||||
|
||||
@@ -541,9 +520,7 @@ pub fn process_withdraw_from_nonce_account(
|
||||
destination_account_pubkey: &Pubkey,
|
||||
lamports: u64,
|
||||
) -> ProcessResult {
|
||||
let (recent_blockhash, fee_calculator, _) = rpc_client
|
||||
.get_recent_blockhash_with_commitment(config.commitment)?
|
||||
.value;
|
||||
let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?;
|
||||
|
||||
let nonce_authority = config.signers[nonce_authority];
|
||||
let ix = withdraw_nonce_account(
|
||||
@@ -562,11 +539,7 @@ pub fn process_withdraw_from_nonce_account(
|
||||
&tx.message,
|
||||
config.commitment,
|
||||
)?;
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner_and_config(
|
||||
&tx,
|
||||
config.commitment,
|
||||
config.send_transaction_config,
|
||||
);
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner(&tx);
|
||||
log_instruction_custom_error::<NonceError>(result, &config)
|
||||
}
|
||||
|
||||
|
2362
cli/src/program.rs
Normal file
2362
cli/src/program.rs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,21 +1,38 @@
|
||||
use log::*;
|
||||
use solana_client::rpc_response::{RpcContactInfo, RpcLeaderSchedule};
|
||||
use solana_sdk::clock::NUM_CONSECUTIVE_LEADER_SLOTS;
|
||||
use std::net::{SocketAddr, UdpSocket};
|
||||
|
||||
pub fn get_leader_tpu(
|
||||
pub fn get_leader_tpus(
|
||||
slot_index: u64,
|
||||
num_leaders: u64,
|
||||
leader_schedule: Option<&RpcLeaderSchedule>,
|
||||
cluster_nodes: Option<&Vec<RpcContactInfo>>,
|
||||
) -> Option<SocketAddr> {
|
||||
leader_schedule?
|
||||
.iter()
|
||||
.find(|(_pubkey, slots)| slots.iter().any(|slot| *slot as u64 == slot_index))
|
||||
.and_then(|(pubkey, _)| {
|
||||
cluster_nodes?
|
||||
) -> Vec<SocketAddr> {
|
||||
let leaders: Vec<_> = (0..num_leaders)
|
||||
.filter_map(|i| {
|
||||
leader_schedule?
|
||||
.iter()
|
||||
.find(|contact_info| contact_info.pubkey == *pubkey)
|
||||
.and_then(|contact_info| contact_info.tpu)
|
||||
.find(|(_pubkey, slots)| {
|
||||
slots.iter().any(|slot| {
|
||||
*slot as u64 == (slot_index + (i * NUM_CONSECUTIVE_LEADER_SLOTS))
|
||||
})
|
||||
})
|
||||
.and_then(|(pubkey, _)| {
|
||||
cluster_nodes?
|
||||
.iter()
|
||||
.find(|contact_info| contact_info.pubkey == *pubkey)
|
||||
.and_then(|contact_info| contact_info.tpu)
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
let mut unique_leaders = vec![];
|
||||
for leader in leaders.into_iter() {
|
||||
if !unique_leaders.contains(&leader) {
|
||||
unique_leaders.push(leader);
|
||||
}
|
||||
}
|
||||
unique_leaders
|
||||
}
|
||||
|
||||
pub fn send_transaction_tpu(
|
||||
|
@@ -107,15 +107,22 @@ where
|
||||
return Err(CliError::InsufficientFundsForSpendAndFee(
|
||||
lamports_to_sol(spend),
|
||||
lamports_to_sol(fee),
|
||||
*from_pubkey,
|
||||
));
|
||||
}
|
||||
} else {
|
||||
if from_balance < spend {
|
||||
return Err(CliError::InsufficientFundsForSpend(lamports_to_sol(spend)));
|
||||
return Err(CliError::InsufficientFundsForSpend(
|
||||
lamports_to_sol(spend),
|
||||
*from_pubkey,
|
||||
));
|
||||
}
|
||||
if !check_account_for_balance_with_commitment(rpc_client, fee_pubkey, fee, commitment)?
|
||||
{
|
||||
return Err(CliError::InsufficientFundsForFee(lamports_to_sol(fee)));
|
||||
return Err(CliError::InsufficientFundsForFee(
|
||||
lamports_to_sol(fee),
|
||||
*fee_pubkey,
|
||||
));
|
||||
}
|
||||
}
|
||||
Ok((message, spend))
|
||||
|
140
cli/src/stake.rs
140
cli/src/stake.rs
@@ -64,6 +64,12 @@ pub const WITHDRAW_AUTHORITY_ARG: ArgConstant<'static> = ArgConstant {
|
||||
help: "Authorized withdrawer [default: cli config keypair]",
|
||||
};
|
||||
|
||||
pub const CUSTODIAN_ARG: ArgConstant<'static> = ArgConstant {
|
||||
name: "custodian",
|
||||
long: "custodian",
|
||||
help: "Authority to override account lockup",
|
||||
};
|
||||
|
||||
fn stake_authority_arg<'a, 'b>() -> Arg<'a, 'b> {
|
||||
Arg::with_name(STAKE_AUTHORITY_ARG.name)
|
||||
.long(STAKE_AUTHORITY_ARG.long)
|
||||
@@ -82,6 +88,15 @@ fn withdraw_authority_arg<'a, 'b>() -> Arg<'a, 'b> {
|
||||
.help(WITHDRAW_AUTHORITY_ARG.help)
|
||||
}
|
||||
|
||||
fn custodian_arg<'a, 'b>() -> Arg<'a, 'b> {
|
||||
Arg::with_name(CUSTODIAN_ARG.name)
|
||||
.long(CUSTODIAN_ARG.long)
|
||||
.takes_value(true)
|
||||
.value_name("KEYPAIR")
|
||||
.validator(is_valid_signer)
|
||||
.help(CUSTODIAN_ARG.help)
|
||||
}
|
||||
|
||||
pub trait StakeSubCommands {
|
||||
fn stake_subcommands(self) -> Self;
|
||||
}
|
||||
@@ -223,6 +238,7 @@ impl StakeSubCommands for App<'_, '_> {
|
||||
.offline_args()
|
||||
.nonce_args(false)
|
||||
.arg(fee_payer_arg())
|
||||
.arg(custodian_arg())
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("deactivate-stake")
|
||||
@@ -252,7 +268,7 @@ impl StakeSubCommands for App<'_, '_> {
|
||||
.arg(
|
||||
Arg::with_name("split_stake_account")
|
||||
.index(2)
|
||||
.value_name("ACCOUNT_KEYPAIR")
|
||||
.value_name("SPLIT_STAKE_ACCOUNT")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.validator(is_valid_signer)
|
||||
@@ -272,7 +288,7 @@ impl StakeSubCommands for App<'_, '_> {
|
||||
.long("seed")
|
||||
.value_name("STRING")
|
||||
.takes_value(true)
|
||||
.help("Seed for address generation; if specified, the resulting account will be at a derived address of the SPLIT STAKE ACCOUNT pubkey")
|
||||
.help("Seed for address generation; if specified, the resulting account will be at a derived address of the SPLIT_STAKE_ACCOUNT pubkey")
|
||||
)
|
||||
.arg(stake_authority_arg())
|
||||
.offline_args()
|
||||
@@ -331,14 +347,7 @@ impl StakeSubCommands for App<'_, '_> {
|
||||
.offline_args()
|
||||
.nonce_args(false)
|
||||
.arg(fee_payer_arg())
|
||||
.arg(
|
||||
Arg::with_name("custodian")
|
||||
.long("custodian")
|
||||
.takes_value(true)
|
||||
.value_name("KEYPAIR")
|
||||
.validator(is_valid_signer)
|
||||
.help("Authority to override account lockup")
|
||||
)
|
||||
.arg(custodian_arg())
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("stake-set-lockup")
|
||||
@@ -403,7 +412,7 @@ impl StakeSubCommands for App<'_, '_> {
|
||||
.long("lamports")
|
||||
.takes_value(false)
|
||||
.help("Display balance in lamports instead of SOL")
|
||||
)
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("stake-history")
|
||||
@@ -561,11 +570,15 @@ pub fn parse_stake_authorize(
|
||||
let (nonce_authority, nonce_authority_pubkey) =
|
||||
signer_of(matches, NONCE_AUTHORITY_ARG.name, wallet_manager)?;
|
||||
let (fee_payer, fee_payer_pubkey) = signer_of(matches, FEE_PAYER_ARG.name, wallet_manager)?;
|
||||
let (custodian, custodian_pubkey) = signer_of(matches, "custodian", wallet_manager)?;
|
||||
|
||||
bulk_signers.push(fee_payer);
|
||||
if nonce_account.is_some() {
|
||||
bulk_signers.push(nonce_authority);
|
||||
}
|
||||
if custodian.is_some() {
|
||||
bulk_signers.push(custodian);
|
||||
}
|
||||
let signer_info =
|
||||
default_signer.generate_unique_signers(bulk_signers, matches, wallet_manager)?;
|
||||
|
||||
@@ -591,6 +604,7 @@ pub fn parse_stake_authorize(
|
||||
nonce_account,
|
||||
nonce_authority: signer_info.index_of(nonce_authority_pubkey).unwrap(),
|
||||
fee_payer: signer_info.index_of(fee_payer_pubkey).unwrap(),
|
||||
custodian: custodian_pubkey.and_then(|_| signer_info.index_of(custodian_pubkey)),
|
||||
},
|
||||
signers: signer_info.signers,
|
||||
})
|
||||
@@ -959,11 +973,7 @@ pub fn process_create_stake_account(
|
||||
return_signers(&tx, &config.output_format)
|
||||
} else {
|
||||
tx.try_sign(&config.signers, recent_blockhash)?;
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner_and_config(
|
||||
&tx,
|
||||
config.commitment,
|
||||
config.send_transaction_config,
|
||||
);
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner(&tx);
|
||||
log_instruction_custom_error::<SystemError>(result, &config)
|
||||
}
|
||||
}
|
||||
@@ -974,6 +984,7 @@ pub fn process_stake_authorize(
|
||||
config: &CliConfig,
|
||||
stake_account_pubkey: &Pubkey,
|
||||
new_authorizations: &[(StakeAuthorize, Pubkey, SignerIndex)],
|
||||
custodian: Option<SignerIndex>,
|
||||
sign_only: bool,
|
||||
blockhash_query: &BlockhashQuery,
|
||||
nonce_account: Option<Pubkey>,
|
||||
@@ -981,6 +992,7 @@ pub fn process_stake_authorize(
|
||||
fee_payer: SignerIndex,
|
||||
) -> ProcessResult {
|
||||
let mut ixs = Vec::new();
|
||||
let custodian = custodian.map(|index| config.signers[index]);
|
||||
for (stake_authorize, authorized_pubkey, authority) in new_authorizations.iter() {
|
||||
check_unique_pubkeys(
|
||||
(stake_account_pubkey, "stake_account_pubkey".to_string()),
|
||||
@@ -992,6 +1004,7 @@ pub fn process_stake_authorize(
|
||||
&authority.pubkey(), // currently authorized
|
||||
authorized_pubkey, // new stake signer
|
||||
*stake_authorize, // stake or withdraw
|
||||
custodian.map(|signer| signer.pubkey()).as_ref(),
|
||||
));
|
||||
}
|
||||
|
||||
@@ -1033,11 +1046,7 @@ pub fn process_stake_authorize(
|
||||
&tx.message,
|
||||
config.commitment,
|
||||
)?;
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner_and_config(
|
||||
&tx,
|
||||
config.commitment,
|
||||
config.send_transaction_config,
|
||||
);
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner(&tx);
|
||||
log_instruction_custom_error::<StakeError>(result, &config)
|
||||
}
|
||||
}
|
||||
@@ -1096,11 +1105,7 @@ pub fn process_deactivate_stake_account(
|
||||
&tx.message,
|
||||
config.commitment,
|
||||
)?;
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner_and_config(
|
||||
&tx,
|
||||
config.commitment,
|
||||
config.send_transaction_config,
|
||||
);
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner(&tx);
|
||||
log_instruction_custom_error::<StakeError>(result, &config)
|
||||
}
|
||||
}
|
||||
@@ -1168,11 +1173,7 @@ pub fn process_withdraw_stake(
|
||||
&tx.message,
|
||||
config.commitment,
|
||||
)?;
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner_and_config(
|
||||
&tx,
|
||||
config.commitment,
|
||||
config.send_transaction_config,
|
||||
);
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner(&tx);
|
||||
log_instruction_custom_error::<SystemError>(result, &config)
|
||||
}
|
||||
}
|
||||
@@ -1311,11 +1312,7 @@ pub fn process_split_stake(
|
||||
&tx.message,
|
||||
config.commitment,
|
||||
)?;
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner_and_config(
|
||||
&tx,
|
||||
config.commitment,
|
||||
config.send_transaction_config,
|
||||
);
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner(&tx);
|
||||
log_instruction_custom_error::<StakeError>(result, &config)
|
||||
}
|
||||
}
|
||||
@@ -1479,11 +1476,7 @@ pub fn process_stake_set_lockup(
|
||||
&tx.message,
|
||||
config.commitment,
|
||||
)?;
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner_and_config(
|
||||
&tx,
|
||||
config.commitment,
|
||||
config.send_transaction_config,
|
||||
);
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner(&tx);
|
||||
log_instruction_custom_error::<StakeError>(result, &config)
|
||||
}
|
||||
}
|
||||
@@ -1662,13 +1655,13 @@ pub(crate) fn fetch_epoch_rewards(
|
||||
.find(|reward| reward.pubkey == address.to_string())
|
||||
{
|
||||
if reward.post_balance > reward.lamports.try_into().unwrap_or(0) {
|
||||
let percent_change = reward.lamports.abs() as f64
|
||||
let rate_change = reward.lamports.abs() as f64
|
||||
/ (reward.post_balance as f64 - reward.lamports as f64);
|
||||
|
||||
let apr = wallclock_epoch_duration.map(|wallclock_epoch_duration| {
|
||||
let wallclock_epochs_per_year =
|
||||
(SECONDS_PER_DAY * 356) as f64 / wallclock_epoch_duration;
|
||||
percent_change * wallclock_epochs_per_year
|
||||
rate_change * wallclock_epochs_per_year
|
||||
});
|
||||
|
||||
all_epoch_rewards.push(CliEpochReward {
|
||||
@@ -1676,8 +1669,8 @@ pub(crate) fn fetch_epoch_rewards(
|
||||
effective_slot,
|
||||
amount: reward.lamports.abs() as u64,
|
||||
post_balance: reward.post_balance,
|
||||
percent_change,
|
||||
apr,
|
||||
percent_change: rate_change * 100.0,
|
||||
apr: apr.map(|r| r * 100.0),
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1725,7 +1718,7 @@ pub fn process_show_stake_account(
|
||||
use_lamports_unit,
|
||||
&stake_history,
|
||||
&clock,
|
||||
is_stake_program_v2_enabled(rpc_client), // At v1.6, this check can be removed and simply passed as `true`
|
||||
is_stake_program_v2_enabled(rpc_client)?, // At v1.6, this check can be removed and simply passed as `true`
|
||||
);
|
||||
|
||||
if state.stake_type == CliStakeType::Stake {
|
||||
@@ -1792,23 +1785,15 @@ pub fn process_delegate_stake(
|
||||
if !sign_only {
|
||||
// Sanity check the vote account to ensure it is attached to a validator that has recently
|
||||
// voted at the tip of the ledger
|
||||
let vote_account = rpc_client
|
||||
.get_account_with_commitment(vote_account_pubkey, config.commitment)
|
||||
.map_err(|_| {
|
||||
let vote_account_data = rpc_client
|
||||
.get_account(vote_account_pubkey)
|
||||
.map_err(|err| {
|
||||
CliError::RpcRequestError(format!(
|
||||
"Vote account not found: {}",
|
||||
vote_account_pubkey
|
||||
"Vote account not found: {}. error: {}",
|
||||
vote_account_pubkey, err,
|
||||
))
|
||||
})?;
|
||||
let vote_account_data = if let Some(account) = vote_account.value {
|
||||
account.data
|
||||
} else {
|
||||
return Err(CliError::RpcRequestError(format!(
|
||||
"Vote account not found: {}",
|
||||
vote_account_pubkey
|
||||
))
|
||||
.into());
|
||||
};
|
||||
})?
|
||||
.data;
|
||||
|
||||
let vote_state = VoteState::deserialize(&vote_account_data).map_err(|_| {
|
||||
CliError::RpcRequestError(
|
||||
@@ -1888,22 +1873,18 @@ pub fn process_delegate_stake(
|
||||
&tx.message,
|
||||
config.commitment,
|
||||
)?;
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner_and_config(
|
||||
&tx,
|
||||
config.commitment,
|
||||
config.send_transaction_config,
|
||||
);
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner(&tx);
|
||||
log_instruction_custom_error::<StakeError>(result, &config)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_stake_program_v2_enabled(rpc_client: &RpcClient) -> bool {
|
||||
rpc_client
|
||||
.get_account(&feature_set::stake_program_v2::id())
|
||||
.ok()
|
||||
.and_then(|account| feature::from_account(&account))
|
||||
pub fn is_stake_program_v2_enabled(
|
||||
rpc_client: &RpcClient,
|
||||
) -> Result<bool, Box<dyn std::error::Error>> {
|
||||
let feature_account = rpc_client.get_account(&feature_set::stake_program_v2::id())?;
|
||||
Ok(feature::from_account(&feature_account)
|
||||
.and_then(|feature| feature.activated_at)
|
||||
.is_some()
|
||||
.is_some())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -1975,6 +1956,7 @@ mod tests {
|
||||
nonce_account: None,
|
||||
nonce_authority: 0,
|
||||
fee_payer: 0,
|
||||
custodian: None,
|
||||
},
|
||||
signers: vec![read_keypair_file(&default_keypair_file).unwrap().into(),],
|
||||
},
|
||||
@@ -2009,6 +1991,7 @@ mod tests {
|
||||
nonce_account: None,
|
||||
nonce_authority: 0,
|
||||
fee_payer: 0,
|
||||
custodian: None,
|
||||
},
|
||||
signers: vec![
|
||||
read_keypair_file(&default_keypair_file).unwrap().into(),
|
||||
@@ -2047,6 +2030,7 @@ mod tests {
|
||||
nonce_account: None,
|
||||
nonce_authority: 0,
|
||||
fee_payer: 0,
|
||||
custodian: None,
|
||||
},
|
||||
signers: vec![
|
||||
read_keypair_file(&default_keypair_file).unwrap().into(),
|
||||
@@ -2074,6 +2058,7 @@ mod tests {
|
||||
nonce_account: None,
|
||||
nonce_authority: 0,
|
||||
fee_payer: 0,
|
||||
custodian: None,
|
||||
},
|
||||
signers: vec![read_keypair_file(&default_keypair_file).unwrap().into(),],
|
||||
},
|
||||
@@ -2098,6 +2083,7 @@ mod tests {
|
||||
nonce_account: None,
|
||||
nonce_authority: 0,
|
||||
fee_payer: 0,
|
||||
custodian: None,
|
||||
},
|
||||
signers: vec![
|
||||
read_keypair_file(&default_keypair_file).unwrap().into(),
|
||||
@@ -2128,6 +2114,7 @@ mod tests {
|
||||
nonce_account: None,
|
||||
nonce_authority: 0,
|
||||
fee_payer: 0,
|
||||
custodian: None,
|
||||
},
|
||||
signers: vec![
|
||||
read_keypair_file(&default_keypair_file).unwrap().into(),
|
||||
@@ -2159,6 +2146,7 @@ mod tests {
|
||||
nonce_account: None,
|
||||
nonce_authority: 0,
|
||||
fee_payer: 0,
|
||||
custodian: None,
|
||||
},
|
||||
signers: vec![read_keypair_file(&default_keypair_file).unwrap().into(),],
|
||||
},
|
||||
@@ -2187,6 +2175,7 @@ mod tests {
|
||||
nonce_account: None,
|
||||
nonce_authority: 0,
|
||||
fee_payer: 0,
|
||||
custodian: None,
|
||||
},
|
||||
signers: vec![
|
||||
read_keypair_file(&default_keypair_file).unwrap().into(),
|
||||
@@ -2221,6 +2210,7 @@ mod tests {
|
||||
nonce_account: None,
|
||||
nonce_authority: 0,
|
||||
fee_payer: 0,
|
||||
custodian: None,
|
||||
},
|
||||
signers: vec![read_keypair_file(&default_keypair_file).unwrap().into()],
|
||||
}
|
||||
@@ -2257,6 +2247,7 @@ mod tests {
|
||||
nonce_account: None,
|
||||
nonce_authority: 0,
|
||||
fee_payer: 1,
|
||||
custodian: None,
|
||||
},
|
||||
signers: vec![
|
||||
read_keypair_file(&default_keypair_file).unwrap().into(),
|
||||
@@ -2303,6 +2294,7 @@ mod tests {
|
||||
nonce_account: Some(nonce_account),
|
||||
nonce_authority: 2,
|
||||
fee_payer: 1,
|
||||
custodian: None,
|
||||
},
|
||||
signers: vec![
|
||||
read_keypair_file(&default_keypair_file).unwrap().into(),
|
||||
@@ -2335,6 +2327,7 @@ mod tests {
|
||||
nonce_account: None,
|
||||
nonce_authority: 0,
|
||||
fee_payer: 0,
|
||||
custodian: None,
|
||||
},
|
||||
signers: vec![read_keypair_file(&default_keypair_file).unwrap().into()],
|
||||
}
|
||||
@@ -2372,6 +2365,7 @@ mod tests {
|
||||
nonce_account: Some(nonce_account_pubkey),
|
||||
nonce_authority: 1,
|
||||
fee_payer: 0,
|
||||
custodian: None,
|
||||
},
|
||||
signers: vec![
|
||||
read_keypair_file(&default_keypair_file).unwrap().into(),
|
||||
@@ -2405,6 +2399,7 @@ mod tests {
|
||||
nonce_account: None,
|
||||
nonce_authority: 0,
|
||||
fee_payer: 1,
|
||||
custodian: None,
|
||||
},
|
||||
signers: vec![
|
||||
read_keypair_file(&default_keypair_file).unwrap().into(),
|
||||
@@ -2442,6 +2437,7 @@ mod tests {
|
||||
nonce_account: None,
|
||||
nonce_authority: 0,
|
||||
fee_payer: 1,
|
||||
custodian: None,
|
||||
},
|
||||
signers: vec![
|
||||
read_keypair_file(&default_keypair_file).unwrap().into(),
|
||||
|
@@ -5,7 +5,7 @@ use std::{thread::sleep, time::Duration};
|
||||
pub fn check_recent_balance(expected_balance: u64, client: &RpcClient, pubkey: &Pubkey) {
|
||||
(0..5).for_each(|tries| {
|
||||
let balance = client
|
||||
.get_balance_with_commitment(pubkey, CommitmentConfig::recent())
|
||||
.get_balance_with_commitment(pubkey, CommitmentConfig::processed())
|
||||
.unwrap()
|
||||
.value;
|
||||
if balance == expected_balance {
|
||||
@@ -20,7 +20,7 @@ pub fn check_recent_balance(expected_balance: u64, client: &RpcClient, pubkey: &
|
||||
|
||||
pub fn check_ready(rpc_client: &RpcClient) {
|
||||
while rpc_client
|
||||
.get_slot_with_commitment(CommitmentConfig::recent())
|
||||
.get_slot_with_commitment(CommitmentConfig::processed())
|
||||
.unwrap()
|
||||
< 5
|
||||
{
|
||||
|
@@ -20,7 +20,6 @@ use solana_config_program::{config_instruction, get_config_data, ConfigKeys, Con
|
||||
use solana_remote_wallet::remote_wallet::RemoteWalletManager;
|
||||
use solana_sdk::{
|
||||
account::Account,
|
||||
commitment_config::CommitmentConfig,
|
||||
message::Message,
|
||||
pubkey::Pubkey,
|
||||
signature::{Keypair, Signer},
|
||||
@@ -288,9 +287,7 @@ pub fn process_set_validator_info(
|
||||
};
|
||||
|
||||
// Check existence of validator-info account
|
||||
let balance = rpc_client
|
||||
.poll_get_balance_with_commitment(&info_pubkey, CommitmentConfig::default())
|
||||
.unwrap_or(0);
|
||||
let balance = rpc_client.get_balance(&info_pubkey).unwrap_or(0);
|
||||
|
||||
let lamports =
|
||||
rpc_client.get_minimum_balance_for_rent_exemption(ValidatorInfo::max_space() as usize)?;
|
||||
|
@@ -8,7 +8,6 @@ use crate::{
|
||||
};
|
||||
use clap::{value_t_or_exit, App, Arg, ArgMatches, SubCommand};
|
||||
use solana_clap_utils::{
|
||||
commitment::commitment_arg,
|
||||
input_parsers::*,
|
||||
input_validators::*,
|
||||
keypair::{DefaultSigner, SignerIndex},
|
||||
@@ -208,8 +207,7 @@ impl VoteSubCommands for App<'_, '_> {
|
||||
.long("lamports")
|
||||
.takes_value(false)
|
||||
.help("Display balance in lamports instead of SOL"),
|
||||
)
|
||||
.arg(commitment_arg()),
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("withdraw-from-vote-account")
|
||||
@@ -494,9 +492,7 @@ pub fn process_create_vote_account(
|
||||
}
|
||||
}
|
||||
|
||||
let (recent_blockhash, fee_calculator, _) = rpc_client
|
||||
.get_recent_blockhash_with_commitment(config.commitment)?
|
||||
.value;
|
||||
let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?;
|
||||
|
||||
let (message, _) = resolve_spend_tx_and_check_account_balance(
|
||||
rpc_client,
|
||||
@@ -509,11 +505,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_and_config(
|
||||
&tx,
|
||||
config.commitment,
|
||||
config.send_transaction_config,
|
||||
);
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner(&tx);
|
||||
log_instruction_custom_error::<SystemError>(result, &config)
|
||||
}
|
||||
|
||||
@@ -536,9 +528,7 @@ pub fn process_vote_authorize(
|
||||
(&authorized.pubkey(), "authorized_account".to_string()),
|
||||
(new_authorized_pubkey, "new_authorized_pubkey".to_string()),
|
||||
)?;
|
||||
let (recent_blockhash, fee_calculator, _) = rpc_client
|
||||
.get_recent_blockhash_with_commitment(config.commitment)?
|
||||
.value;
|
||||
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
|
||||
@@ -556,11 +546,7 @@ pub fn process_vote_authorize(
|
||||
&tx.message,
|
||||
config.commitment,
|
||||
)?;
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner_and_config(
|
||||
&tx,
|
||||
config.commitment,
|
||||
config.send_transaction_config,
|
||||
);
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner(&tx);
|
||||
log_instruction_custom_error::<VoteError>(result, &config)
|
||||
}
|
||||
|
||||
@@ -578,9 +564,7 @@ pub fn process_vote_update_validator(
|
||||
(vote_account_pubkey, "vote_account_pubkey".to_string()),
|
||||
(&new_identity_pubkey, "new_identity_account".to_string()),
|
||||
)?;
|
||||
let (recent_blockhash, fee_calculator, _) = rpc_client
|
||||
.get_recent_blockhash_with_commitment(config.commitment)?
|
||||
.value;
|
||||
let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?;
|
||||
let ixs = vec![vote_instruction::update_validator_identity(
|
||||
vote_account_pubkey,
|
||||
&authorized_withdrawer.pubkey(),
|
||||
@@ -597,11 +581,7 @@ pub fn process_vote_update_validator(
|
||||
&tx.message,
|
||||
config.commitment,
|
||||
)?;
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner_and_config(
|
||||
&tx,
|
||||
config.commitment,
|
||||
config.send_transaction_config,
|
||||
);
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner(&tx);
|
||||
log_instruction_custom_error::<VoteError>(result, &config)
|
||||
}
|
||||
|
||||
@@ -613,9 +593,7 @@ pub fn process_vote_update_commission(
|
||||
withdraw_authority: SignerIndex,
|
||||
) -> ProcessResult {
|
||||
let authorized_withdrawer = config.signers[withdraw_authority];
|
||||
let (recent_blockhash, fee_calculator, _) = rpc_client
|
||||
.get_recent_blockhash_with_commitment(config.commitment)?
|
||||
.value;
|
||||
let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?;
|
||||
let ixs = vec![vote_instruction::update_commission(
|
||||
vote_account_pubkey,
|
||||
&authorized_withdrawer.pubkey(),
|
||||
@@ -632,11 +610,7 @@ pub fn process_vote_update_commission(
|
||||
&tx.message,
|
||||
config.commitment,
|
||||
)?;
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner_and_config(
|
||||
&tx,
|
||||
config.commitment,
|
||||
config.send_transaction_config,
|
||||
);
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner(&tx);
|
||||
log_instruction_custom_error::<VoteError>(result, &config)
|
||||
}
|
||||
|
||||
@@ -733,14 +707,10 @@ pub fn process_withdraw_from_vote_account(
|
||||
withdraw_amount: SpendAmount,
|
||||
destination_account_pubkey: &Pubkey,
|
||||
) -> ProcessResult {
|
||||
let (recent_blockhash, fee_calculator, _) = rpc_client
|
||||
.get_recent_blockhash_with_commitment(config.commitment)?
|
||||
.value;
|
||||
let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?;
|
||||
let withdraw_authority = config.signers[withdraw_authority];
|
||||
|
||||
let current_balance = rpc_client
|
||||
.get_balance_with_commitment(&vote_account_pubkey, config.commitment)?
|
||||
.value;
|
||||
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 {
|
||||
@@ -773,11 +743,7 @@ pub fn process_withdraw_from_vote_account(
|
||||
&transaction.message,
|
||||
config.commitment,
|
||||
)?;
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner_and_config(
|
||||
&transaction,
|
||||
config.commitment,
|
||||
config.send_transaction_config,
|
||||
);
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner(&transaction);
|
||||
log_instruction_custom_error::<VoteError>(result, &config)
|
||||
}
|
||||
|
||||
|
@@ -1,422 +0,0 @@
|
||||
use serde_json::Value;
|
||||
use solana_cli::cli::{process_command, CliCommand, CliConfig};
|
||||
use solana_client::rpc_client::RpcClient;
|
||||
use solana_core::test_validator::TestValidator;
|
||||
use solana_faucet::faucet::run_local_faucet;
|
||||
use solana_sdk::{
|
||||
bpf_loader,
|
||||
bpf_loader_upgradeable::{self, UpgradeableLoaderState},
|
||||
commitment_config::CommitmentConfig,
|
||||
pubkey::Pubkey,
|
||||
signature::{Keypair, Signer},
|
||||
};
|
||||
use std::{fs::File, io::Read, path::PathBuf, str::FromStr, sync::mpsc::channel};
|
||||
|
||||
#[test]
|
||||
fn test_cli_deploy_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 test_validator = TestValidator::with_no_fees(mint_keypair.pubkey());
|
||||
|
||||
let (sender, receiver) = channel();
|
||||
run_local_faucet(mint_keypair, sender, None);
|
||||
let faucet_addr = receiver.recv().unwrap();
|
||||
|
||||
let rpc_client = RpcClient::new(test_validator.rpc_url());
|
||||
|
||||
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 minimum_balance_for_rent_exemption = rpc_client
|
||||
.get_minimum_balance_for_rent_exemption(program_data.len())
|
||||
.unwrap();
|
||||
|
||||
let mut config = CliConfig::recent_for_tests();
|
||||
let keypair = Keypair::new();
|
||||
config.json_rpc_url = test_validator.rpc_url();
|
||||
config.command = CliCommand::Airdrop {
|
||||
faucet_host: None,
|
||||
faucet_port: faucet_addr.port(),
|
||||
pubkey: None,
|
||||
lamports: 4 * minimum_balance_for_rent_exemption, // min balance for rent exemption for three programs + leftover for tx processing
|
||||
};
|
||||
config.signers = vec![&keypair];
|
||||
process_command(&config).unwrap();
|
||||
|
||||
config.command = CliCommand::ProgramDeploy {
|
||||
program_location: pathbuf.to_str().unwrap().to_string(),
|
||||
buffer: None,
|
||||
use_deprecated_loader: false,
|
||||
use_upgradeable_loader: false,
|
||||
allow_excessive_balance: false,
|
||||
upgrade_authority: None,
|
||||
max_len: None,
|
||||
};
|
||||
|
||||
let response = process_command(&config);
|
||||
let json: Value = serde_json::from_str(&response.unwrap()).unwrap();
|
||||
let program_id_str = json
|
||||
.as_object()
|
||||
.unwrap()
|
||||
.get("programId")
|
||||
.unwrap()
|
||||
.as_str()
|
||||
.unwrap();
|
||||
let program_id = Pubkey::from_str(&program_id_str).unwrap();
|
||||
let account0 = rpc_client
|
||||
.get_account_with_commitment(&program_id, CommitmentConfig::recent())
|
||||
.unwrap()
|
||||
.value
|
||||
.unwrap();
|
||||
assert_eq!(account0.lamports, minimum_balance_for_rent_exemption);
|
||||
assert_eq!(account0.owner, bpf_loader::id());
|
||||
assert_eq!(account0.executable, true);
|
||||
|
||||
let mut file = File::open(pathbuf.to_str().unwrap().to_string()).unwrap();
|
||||
let mut elf = Vec::new();
|
||||
file.read_to_end(&mut elf).unwrap();
|
||||
|
||||
assert_eq!(account0.data, elf);
|
||||
|
||||
// Test custom address
|
||||
let custom_address_keypair = Keypair::new();
|
||||
config.signers = vec![&keypair, &custom_address_keypair];
|
||||
config.command = CliCommand::ProgramDeploy {
|
||||
program_location: pathbuf.to_str().unwrap().to_string(),
|
||||
buffer: Some(1),
|
||||
use_deprecated_loader: false,
|
||||
use_upgradeable_loader: false,
|
||||
allow_excessive_balance: false,
|
||||
upgrade_authority: None,
|
||||
max_len: None,
|
||||
};
|
||||
process_command(&config).unwrap();
|
||||
let account1 = rpc_client
|
||||
.get_account_with_commitment(&custom_address_keypair.pubkey(), CommitmentConfig::recent())
|
||||
.unwrap()
|
||||
.value
|
||||
.unwrap();
|
||||
assert_eq!(account1.lamports, minimum_balance_for_rent_exemption);
|
||||
assert_eq!(account1.owner, bpf_loader::id());
|
||||
assert_eq!(account1.executable, true);
|
||||
assert_eq!(account0.data, account1.data);
|
||||
|
||||
// Attempt to redeploy to the same address
|
||||
process_command(&config).unwrap_err();
|
||||
|
||||
// Attempt to deploy to account with excess balance
|
||||
let custom_address_keypair = Keypair::new();
|
||||
config.command = CliCommand::Airdrop {
|
||||
faucet_host: None,
|
||||
faucet_port: faucet_addr.port(),
|
||||
pubkey: None,
|
||||
lamports: 2 * minimum_balance_for_rent_exemption, // Anything over minimum_balance_for_rent_exemption should trigger err
|
||||
};
|
||||
config.signers = vec![&custom_address_keypair];
|
||||
process_command(&config).unwrap();
|
||||
|
||||
config.signers = vec![&keypair, &custom_address_keypair];
|
||||
config.command = CliCommand::ProgramDeploy {
|
||||
program_location: pathbuf.to_str().unwrap().to_string(),
|
||||
buffer: Some(1),
|
||||
use_deprecated_loader: false,
|
||||
use_upgradeable_loader: false,
|
||||
allow_excessive_balance: false,
|
||||
upgrade_authority: None,
|
||||
max_len: None,
|
||||
};
|
||||
process_command(&config).unwrap_err();
|
||||
|
||||
// Use forcing parameter to deploy to account with excess balance
|
||||
config.command = CliCommand::ProgramDeploy {
|
||||
program_location: pathbuf.to_str().unwrap().to_string(),
|
||||
buffer: Some(1),
|
||||
use_deprecated_loader: false,
|
||||
use_upgradeable_loader: false,
|
||||
allow_excessive_balance: true,
|
||||
upgrade_authority: None,
|
||||
max_len: None,
|
||||
};
|
||||
process_command(&config).unwrap();
|
||||
let account2 = rpc_client
|
||||
.get_account_with_commitment(&custom_address_keypair.pubkey(), CommitmentConfig::recent())
|
||||
.unwrap()
|
||||
.value
|
||||
.unwrap();
|
||||
assert_eq!(account2.lamports, 2 * minimum_balance_for_rent_exemption);
|
||||
assert_eq!(account2.owner, bpf_loader::id());
|
||||
assert_eq!(account2.executable, true);
|
||||
assert_eq!(account0.data, account2.data);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cli_deploy_upgradeable_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 test_validator = TestValidator::with_no_fees(mint_keypair.pubkey());
|
||||
|
||||
let (sender, receiver) = channel();
|
||||
run_local_faucet(mint_keypair, sender, None);
|
||||
let faucet_addr = receiver.recv().unwrap();
|
||||
|
||||
let rpc_client = RpcClient::new(test_validator.rpc_url());
|
||||
|
||||
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();
|
||||
println!(
|
||||
"max_len {:?} {:?}",
|
||||
max_len,
|
||||
UpgradeableLoaderState::programdata_len(max_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.command = CliCommand::Airdrop {
|
||||
faucet_host: None,
|
||||
faucet_port: faucet_addr.port(),
|
||||
pubkey: None,
|
||||
lamports: 100 * minimum_balance_for_programdata + minimum_balance_for_program,
|
||||
};
|
||||
config.signers = vec![&keypair];
|
||||
process_command(&config).unwrap();
|
||||
|
||||
// Deploy and attempt to upgrade a non-upgradeable program
|
||||
config.command = CliCommand::ProgramDeploy {
|
||||
program_location: pathbuf.to_str().unwrap().to_string(),
|
||||
buffer: None,
|
||||
use_deprecated_loader: false,
|
||||
use_upgradeable_loader: true,
|
||||
allow_excessive_balance: false,
|
||||
upgrade_authority: None,
|
||||
max_len: Some(max_len),
|
||||
};
|
||||
let response = process_command(&config);
|
||||
let json: Value = serde_json::from_str(&response.unwrap()).unwrap();
|
||||
let program_id_str = json
|
||||
.as_object()
|
||||
.unwrap()
|
||||
.get("programId")
|
||||
.unwrap()
|
||||
.as_str()
|
||||
.unwrap();
|
||||
let program_id = Pubkey::from_str(&program_id_str).unwrap();
|
||||
|
||||
config.signers = vec![&keypair, &upgrade_authority];
|
||||
config.command = CliCommand::ProgramUpgrade {
|
||||
program_location: pathbuf.to_str().unwrap().to_string(),
|
||||
program: program_id,
|
||||
buffer: None,
|
||||
upgrade_authority: 1,
|
||||
};
|
||||
process_command(&config).unwrap_err();
|
||||
|
||||
// Deploy the upgradeable program
|
||||
config.command = CliCommand::ProgramDeploy {
|
||||
program_location: pathbuf.to_str().unwrap().to_string(),
|
||||
buffer: None,
|
||||
use_deprecated_loader: false,
|
||||
use_upgradeable_loader: true,
|
||||
allow_excessive_balance: false,
|
||||
upgrade_authority: Some(upgrade_authority.pubkey()),
|
||||
max_len: Some(max_len),
|
||||
};
|
||||
let response = process_command(&config);
|
||||
let json: Value = serde_json::from_str(&response.unwrap()).unwrap();
|
||||
let program_id_str = json
|
||||
.as_object()
|
||||
.unwrap()
|
||||
.get("programId")
|
||||
.unwrap()
|
||||
.as_str()
|
||||
.unwrap();
|
||||
let program_id = Pubkey::from_str(&program_id_str).unwrap();
|
||||
let program_account = rpc_client
|
||||
.get_account_with_commitment(&program_id, CommitmentConfig::recent())
|
||||
.unwrap()
|
||||
.value
|
||||
.unwrap();
|
||||
assert_eq!(program_account.lamports, minimum_balance_for_program);
|
||||
assert_eq!(program_account.owner, bpf_loader_upgradeable::id());
|
||||
assert_eq!(program_account.executable, true);
|
||||
let (programdata_pubkey, _) =
|
||||
Pubkey::find_program_address(&[program_id.as_ref()], &bpf_loader_upgradeable::id());
|
||||
let programdata_account = rpc_client
|
||||
.get_account_with_commitment(&programdata_pubkey, CommitmentConfig::recent())
|
||||
.unwrap()
|
||||
.value
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
programdata_account.lamports,
|
||||
minimum_balance_for_programdata
|
||||
);
|
||||
assert_eq!(programdata_account.owner, bpf_loader_upgradeable::id());
|
||||
assert_eq!(programdata_account.executable, false);
|
||||
assert_eq!(
|
||||
programdata_account.data[UpgradeableLoaderState::programdata_data_offset().unwrap()..],
|
||||
program_data[..]
|
||||
);
|
||||
|
||||
// Upgrade the program
|
||||
config.signers = vec![&keypair, &upgrade_authority];
|
||||
config.command = CliCommand::ProgramUpgrade {
|
||||
program_location: pathbuf.to_str().unwrap().to_string(),
|
||||
program: program_id,
|
||||
buffer: None,
|
||||
upgrade_authority: 1,
|
||||
};
|
||||
let response = process_command(&config);
|
||||
let json: Value = serde_json::from_str(&response.unwrap()).unwrap();
|
||||
let program_id_str = json
|
||||
.as_object()
|
||||
.unwrap()
|
||||
.get("programId")
|
||||
.unwrap()
|
||||
.as_str()
|
||||
.unwrap();
|
||||
let program_id = Pubkey::from_str(&program_id_str).unwrap();
|
||||
let program_account = rpc_client
|
||||
.get_account_with_commitment(&program_id, CommitmentConfig::recent())
|
||||
.unwrap()
|
||||
.value
|
||||
.unwrap();
|
||||
assert_eq!(program_account.lamports, minimum_balance_for_program);
|
||||
assert_eq!(program_account.owner, bpf_loader_upgradeable::id());
|
||||
assert_eq!(program_account.executable, true);
|
||||
let (programdata_pubkey, _) =
|
||||
Pubkey::find_program_address(&[program_id.as_ref()], &bpf_loader_upgradeable::id());
|
||||
let programdata_account = rpc_client
|
||||
.get_account_with_commitment(&programdata_pubkey, CommitmentConfig::recent())
|
||||
.unwrap()
|
||||
.value
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
programdata_account.lamports,
|
||||
minimum_balance_for_programdata
|
||||
);
|
||||
assert_eq!(programdata_account.owner, bpf_loader_upgradeable::id());
|
||||
assert_eq!(programdata_account.executable, false);
|
||||
assert_eq!(
|
||||
programdata_account.data[UpgradeableLoaderState::programdata_data_offset().unwrap()..],
|
||||
program_data[..]
|
||||
);
|
||||
|
||||
// Set a new authority
|
||||
let new_upgrade_authority = Keypair::new();
|
||||
config.signers = vec![&keypair, &upgrade_authority];
|
||||
config.command = CliCommand::SetProgramUpgradeAuthority {
|
||||
program: program_id,
|
||||
upgrade_authority: 1,
|
||||
new_upgrade_authority: Some(new_upgrade_authority.pubkey()),
|
||||
};
|
||||
let response = process_command(&config);
|
||||
let json: Value = serde_json::from_str(&response.unwrap()).unwrap();
|
||||
let new_upgrade_authority_str = json
|
||||
.as_object()
|
||||
.unwrap()
|
||||
.get("UpgradeAuthority")
|
||||
.unwrap()
|
||||
.as_str()
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
Pubkey::from_str(&new_upgrade_authority_str).unwrap(),
|
||||
new_upgrade_authority.pubkey()
|
||||
);
|
||||
|
||||
// Upgrade with new authority
|
||||
config.signers = vec![&keypair, &new_upgrade_authority];
|
||||
config.command = CliCommand::ProgramUpgrade {
|
||||
program_location: pathbuf.to_str().unwrap().to_string(),
|
||||
program: program_id,
|
||||
buffer: None,
|
||||
upgrade_authority: 1,
|
||||
};
|
||||
let response = process_command(&config);
|
||||
let json: Value = serde_json::from_str(&response.unwrap()).unwrap();
|
||||
let program_id_str = json
|
||||
.as_object()
|
||||
.unwrap()
|
||||
.get("programId")
|
||||
.unwrap()
|
||||
.as_str()
|
||||
.unwrap();
|
||||
let program_id = Pubkey::from_str(&program_id_str).unwrap();
|
||||
let program_account = rpc_client
|
||||
.get_account_with_commitment(&program_id, CommitmentConfig::recent())
|
||||
.unwrap()
|
||||
.value
|
||||
.unwrap();
|
||||
assert_eq!(program_account.lamports, minimum_balance_for_program);
|
||||
assert_eq!(program_account.owner, bpf_loader_upgradeable::id());
|
||||
assert_eq!(program_account.executable, true);
|
||||
let (programdata_pubkey, _) =
|
||||
Pubkey::find_program_address(&[program_id.as_ref()], &bpf_loader_upgradeable::id());
|
||||
let programdata_account = rpc_client
|
||||
.get_account_with_commitment(&programdata_pubkey, CommitmentConfig::recent())
|
||||
.unwrap()
|
||||
.value
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
programdata_account.lamports,
|
||||
minimum_balance_for_programdata
|
||||
);
|
||||
assert_eq!(programdata_account.owner, bpf_loader_upgradeable::id());
|
||||
assert_eq!(programdata_account.executable, false);
|
||||
assert_eq!(
|
||||
programdata_account.data[UpgradeableLoaderState::programdata_data_offset().unwrap()..],
|
||||
program_data[..]
|
||||
);
|
||||
|
||||
// Set a no authority
|
||||
config.signers = vec![&keypair, &new_upgrade_authority];
|
||||
config.command = CliCommand::SetProgramUpgradeAuthority {
|
||||
program: program_id,
|
||||
upgrade_authority: 1,
|
||||
new_upgrade_authority: None,
|
||||
};
|
||||
let response = process_command(&config);
|
||||
let json: Value = serde_json::from_str(&response.unwrap()).unwrap();
|
||||
let new_upgrade_authority_str = json
|
||||
.as_object()
|
||||
.unwrap()
|
||||
.get("UpgradeAuthority")
|
||||
.unwrap()
|
||||
.as_str()
|
||||
.unwrap();
|
||||
assert_eq!(new_upgrade_authority_str, "None");
|
||||
|
||||
// Upgrade with no authority
|
||||
config.signers = vec![&keypair, &new_upgrade_authority];
|
||||
config.command = CliCommand::ProgramUpgrade {
|
||||
program_location: pathbuf.to_str().unwrap().to_string(),
|
||||
program: program_id,
|
||||
buffer: None,
|
||||
upgrade_authority: 1,
|
||||
};
|
||||
process_command(&config).unwrap_err();
|
||||
}
|
@@ -18,7 +18,6 @@ use solana_sdk::{
|
||||
signature::{keypair_from_seed, Keypair, Signer},
|
||||
system_program,
|
||||
};
|
||||
use std::sync::mpsc::channel;
|
||||
|
||||
#[test]
|
||||
fn test_nonce() {
|
||||
@@ -59,11 +58,10 @@ fn full_battery_tests(
|
||||
seed: Option<String>,
|
||||
use_nonce_authority: bool,
|
||||
) {
|
||||
let (sender, receiver) = channel();
|
||||
run_local_faucet(mint_keypair, sender, None);
|
||||
let faucet_addr = receiver.recv().unwrap();
|
||||
let faucet_addr = run_local_faucet(mint_keypair, None);
|
||||
|
||||
let rpc_client = RpcClient::new(test_validator.rpc_url());
|
||||
let rpc_client =
|
||||
RpcClient::new_with_commitment(test_validator.rpc_url(), CommitmentConfig::processed());
|
||||
let json_rpc_url = test_validator.rpc_url();
|
||||
|
||||
let mut config_payer = CliConfig::recent_for_tests();
|
||||
@@ -218,9 +216,7 @@ fn test_create_account_with_seed() {
|
||||
let mint_keypair = Keypair::new();
|
||||
let test_validator = TestValidator::with_custom_fees(mint_keypair.pubkey(), 1);
|
||||
|
||||
let (sender, receiver) = channel();
|
||||
run_local_faucet(mint_keypair, sender, None);
|
||||
let faucet_addr = receiver.recv().unwrap();
|
||||
let faucet_addr = run_local_faucet(mint_keypair, None);
|
||||
|
||||
let offline_nonce_authority_signer = keypair_from_seed(&[1u8; 32]).unwrap();
|
||||
let online_nonce_creator_signer = keypair_from_seed(&[2u8; 32]).unwrap();
|
||||
@@ -228,7 +224,8 @@ fn test_create_account_with_seed() {
|
||||
let config = CliConfig::recent_for_tests();
|
||||
|
||||
// Setup accounts
|
||||
let rpc_client = RpcClient::new(test_validator.rpc_url());
|
||||
let rpc_client =
|
||||
RpcClient::new_with_commitment(test_validator.rpc_url(), CommitmentConfig::processed());
|
||||
request_and_confirm_airdrop(
|
||||
&rpc_client,
|
||||
&faucet_addr,
|
||||
@@ -278,7 +275,7 @@ fn test_create_account_with_seed() {
|
||||
let nonce_hash = nonce_utils::get_account_with_commitment(
|
||||
&rpc_client,
|
||||
&nonce_address,
|
||||
CommitmentConfig::recent(),
|
||||
CommitmentConfig::processed(),
|
||||
)
|
||||
.and_then(|ref a| nonce_utils::data_from_account(a))
|
||||
.unwrap()
|
||||
@@ -301,6 +298,8 @@ fn test_create_account_with_seed() {
|
||||
nonce_account: Some(nonce_address),
|
||||
nonce_authority: 0,
|
||||
fee_payer: 0,
|
||||
derived_address_seed: None,
|
||||
derived_address_program_id: None,
|
||||
};
|
||||
authority_config.output_format = OutputFormat::JsonCompact;
|
||||
let sign_only_reply = process_command(&authority_config).unwrap();
|
||||
@@ -325,6 +324,8 @@ fn test_create_account_with_seed() {
|
||||
nonce_account: Some(nonce_address),
|
||||
nonce_authority: 0,
|
||||
fee_payer: 0,
|
||||
derived_address_seed: None,
|
||||
derived_address_program_id: None,
|
||||
};
|
||||
process_command(&submit_config).unwrap();
|
||||
check_recent_balance(241, &rpc_client, &nonce_address);
|
||||
|
1220
cli/tests/program.rs
Normal file
1220
cli/tests/program.rs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -6,16 +6,13 @@ use solana_sdk::{
|
||||
commitment_config::CommitmentConfig,
|
||||
signature::{Keypair, Signer},
|
||||
};
|
||||
use std::sync::mpsc::channel;
|
||||
|
||||
#[test]
|
||||
fn test_cli_request_airdrop() {
|
||||
let mint_keypair = Keypair::new();
|
||||
let test_validator = TestValidator::with_no_fees(mint_keypair.pubkey());
|
||||
|
||||
let (sender, receiver) = channel();
|
||||
run_local_faucet(mint_keypair, sender, None);
|
||||
let faucet_addr = receiver.recv().unwrap();
|
||||
let faucet_addr = run_local_faucet(mint_keypair, None);
|
||||
|
||||
let mut bob_config = CliConfig::recent_for_tests();
|
||||
bob_config.json_rpc_url = test_validator.rpc_url();
|
||||
@@ -31,11 +28,11 @@ fn test_cli_request_airdrop() {
|
||||
let sig_response = process_command(&bob_config);
|
||||
sig_response.unwrap();
|
||||
|
||||
let rpc_client = RpcClient::new(test_validator.rpc_url());
|
||||
let rpc_client =
|
||||
RpcClient::new_with_commitment(test_validator.rpc_url(), CommitmentConfig::processed());
|
||||
|
||||
let balance = rpc_client
|
||||
.get_balance_with_commitment(&bob_config.signers[0].pubkey(), CommitmentConfig::recent())
|
||||
.unwrap()
|
||||
.value;
|
||||
.get_balance(&bob_config.signers[0].pubkey())
|
||||
.unwrap();
|
||||
assert_eq!(balance, 50);
|
||||
}
|
||||
|
@@ -22,17 +22,15 @@ use solana_stake_program::{
|
||||
stake_instruction::LockupArgs,
|
||||
stake_state::{Lockup, StakeAuthorize, StakeState},
|
||||
};
|
||||
use std::sync::mpsc::channel;
|
||||
|
||||
#[test]
|
||||
fn test_stake_delegation_force() {
|
||||
let mint_keypair = Keypair::new();
|
||||
let test_validator = TestValidator::with_no_fees(mint_keypair.pubkey());
|
||||
let (sender, receiver) = channel();
|
||||
run_local_faucet(mint_keypair, sender, None);
|
||||
let faucet_addr = receiver.recv().unwrap();
|
||||
let faucet_addr = run_local_faucet(mint_keypair, None);
|
||||
|
||||
let rpc_client = RpcClient::new(test_validator.rpc_url());
|
||||
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();
|
||||
@@ -116,11 +114,10 @@ fn test_seed_stake_delegation_and_deactivation() {
|
||||
|
||||
let mint_keypair = Keypair::new();
|
||||
let test_validator = TestValidator::with_no_fees(mint_keypair.pubkey());
|
||||
let (sender, receiver) = channel();
|
||||
run_local_faucet(mint_keypair, sender, None);
|
||||
let faucet_addr = receiver.recv().unwrap();
|
||||
let faucet_addr = run_local_faucet(mint_keypair, None);
|
||||
|
||||
let rpc_client = RpcClient::new(test_validator.rpc_url());
|
||||
let rpc_client =
|
||||
RpcClient::new_with_commitment(test_validator.rpc_url(), CommitmentConfig::processed());
|
||||
|
||||
let validator_keypair = keypair_from_seed(&[0u8; 32]).unwrap();
|
||||
let mut config_validator = CliConfig::recent_for_tests();
|
||||
@@ -195,11 +192,10 @@ fn test_stake_delegation_and_deactivation() {
|
||||
|
||||
let mint_keypair = Keypair::new();
|
||||
let test_validator = TestValidator::with_no_fees(mint_keypair.pubkey());
|
||||
let (sender, receiver) = channel();
|
||||
run_local_faucet(mint_keypair, sender, None);
|
||||
let faucet_addr = receiver.recv().unwrap();
|
||||
let faucet_addr = run_local_faucet(mint_keypair, None);
|
||||
|
||||
let rpc_client = RpcClient::new(test_validator.rpc_url());
|
||||
let rpc_client =
|
||||
RpcClient::new_with_commitment(test_validator.rpc_url(), CommitmentConfig::processed());
|
||||
let validator_keypair = Keypair::new();
|
||||
|
||||
let mut config_validator = CliConfig::recent_for_tests();
|
||||
@@ -270,11 +266,10 @@ fn test_offline_stake_delegation_and_deactivation() {
|
||||
|
||||
let mint_keypair = Keypair::new();
|
||||
let test_validator = TestValidator::with_no_fees(mint_keypair.pubkey());
|
||||
let (sender, receiver) = channel();
|
||||
run_local_faucet(mint_keypair, sender, None);
|
||||
let faucet_addr = receiver.recv().unwrap();
|
||||
let faucet_addr = run_local_faucet(mint_keypair, None);
|
||||
|
||||
let rpc_client = RpcClient::new(test_validator.rpc_url());
|
||||
let rpc_client =
|
||||
RpcClient::new_with_commitment(test_validator.rpc_url(), CommitmentConfig::processed());
|
||||
|
||||
let mut config_validator = CliConfig::recent_for_tests();
|
||||
config_validator.json_rpc_url = test_validator.rpc_url();
|
||||
@@ -291,7 +286,7 @@ fn test_offline_stake_delegation_and_deactivation() {
|
||||
config_offline.command = CliCommand::ClusterVersion;
|
||||
let offline_keypair = Keypair::new();
|
||||
config_offline.signers = vec![&offline_keypair];
|
||||
// Verfiy that we cannot reach the cluster
|
||||
// Verify that we cannot reach the cluster
|
||||
process_command(&config_offline).unwrap_err();
|
||||
|
||||
request_and_confirm_airdrop(
|
||||
@@ -402,11 +397,10 @@ fn test_nonced_stake_delegation_and_deactivation() {
|
||||
|
||||
let mint_keypair = Keypair::new();
|
||||
let test_validator = TestValidator::with_no_fees(mint_keypair.pubkey());
|
||||
let (sender, receiver) = channel();
|
||||
run_local_faucet(mint_keypair, sender, None);
|
||||
let faucet_addr = receiver.recv().unwrap();
|
||||
let faucet_addr = run_local_faucet(mint_keypair, None);
|
||||
|
||||
let rpc_client = RpcClient::new(test_validator.rpc_url());
|
||||
let rpc_client =
|
||||
RpcClient::new_with_commitment(test_validator.rpc_url(), CommitmentConfig::processed());
|
||||
|
||||
let config_keypair = keypair_from_seed(&[0u8; 32]).unwrap();
|
||||
let mut config = CliConfig::recent_for_tests();
|
||||
@@ -460,7 +454,7 @@ fn test_nonced_stake_delegation_and_deactivation() {
|
||||
let nonce_hash = nonce_utils::get_account_with_commitment(
|
||||
&rpc_client,
|
||||
&nonce_account.pubkey(),
|
||||
CommitmentConfig::recent(),
|
||||
CommitmentConfig::processed(),
|
||||
)
|
||||
.and_then(|ref a| nonce_utils::data_from_account(a))
|
||||
.unwrap()
|
||||
@@ -488,7 +482,7 @@ fn test_nonced_stake_delegation_and_deactivation() {
|
||||
let nonce_hash = nonce_utils::get_account_with_commitment(
|
||||
&rpc_client,
|
||||
&nonce_account.pubkey(),
|
||||
CommitmentConfig::recent(),
|
||||
CommitmentConfig::processed(),
|
||||
)
|
||||
.and_then(|ref a| nonce_utils::data_from_account(a))
|
||||
.unwrap()
|
||||
@@ -516,11 +510,10 @@ fn test_stake_authorize() {
|
||||
|
||||
let mint_keypair = Keypair::new();
|
||||
let test_validator = TestValidator::with_no_fees(mint_keypair.pubkey());
|
||||
let (sender, receiver) = channel();
|
||||
run_local_faucet(mint_keypair, sender, None);
|
||||
let faucet_addr = receiver.recv().unwrap();
|
||||
let faucet_addr = run_local_faucet(mint_keypair, None);
|
||||
|
||||
let rpc_client = RpcClient::new(test_validator.rpc_url());
|
||||
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();
|
||||
@@ -542,7 +535,7 @@ fn test_stake_authorize() {
|
||||
config_offline.json_rpc_url = String::default();
|
||||
let offline_authority_pubkey = config_offline.signers[0].pubkey();
|
||||
config_offline.command = CliCommand::ClusterVersion;
|
||||
// Verfiy that we cannot reach the cluster
|
||||
// Verify that we cannot reach the cluster
|
||||
process_command(&config_offline).unwrap_err();
|
||||
|
||||
request_and_confirm_airdrop(
|
||||
@@ -586,13 +579,10 @@ fn test_stake_authorize() {
|
||||
nonce_account: None,
|
||||
nonce_authority: 0,
|
||||
fee_payer: 0,
|
||||
custodian: None,
|
||||
};
|
||||
process_command(&config).unwrap();
|
||||
let stake_account = rpc_client
|
||||
.get_account_with_commitment(&stake_account_pubkey, CommitmentConfig::recent())
|
||||
.unwrap()
|
||||
.value
|
||||
.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,
|
||||
@@ -617,13 +607,10 @@ fn test_stake_authorize() {
|
||||
nonce_account: None,
|
||||
nonce_authority: 0,
|
||||
fee_payer: 0,
|
||||
custodian: None,
|
||||
};
|
||||
process_command(&config).unwrap();
|
||||
let stake_account = rpc_client
|
||||
.get_account_with_commitment(&stake_account_pubkey, CommitmentConfig::recent())
|
||||
.unwrap()
|
||||
.value
|
||||
.unwrap();
|
||||
let stake_account = rpc_client.get_account(&stake_account_pubkey).unwrap();
|
||||
let stake_state: StakeState = stake_account.state().unwrap();
|
||||
let (current_staker, current_withdrawer) = match stake_state {
|
||||
StakeState::Initialized(meta) => (meta.authorized.staker, meta.authorized.withdrawer),
|
||||
@@ -643,13 +630,10 @@ fn test_stake_authorize() {
|
||||
nonce_account: None,
|
||||
nonce_authority: 0,
|
||||
fee_payer: 0,
|
||||
custodian: None,
|
||||
};
|
||||
process_command(&config).unwrap();
|
||||
let stake_account = rpc_client
|
||||
.get_account_with_commitment(&stake_account_pubkey, CommitmentConfig::recent())
|
||||
.unwrap()
|
||||
.value
|
||||
.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,
|
||||
@@ -660,10 +644,7 @@ fn test_stake_authorize() {
|
||||
// Offline assignment of new nonced stake authority
|
||||
let nonced_authority = Keypair::new();
|
||||
let nonced_authority_pubkey = nonced_authority.pubkey();
|
||||
let (blockhash, _, _) = rpc_client
|
||||
.get_recent_blockhash_with_commitment(CommitmentConfig::recent())
|
||||
.unwrap()
|
||||
.value;
|
||||
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)],
|
||||
@@ -672,6 +653,7 @@ fn test_stake_authorize() {
|
||||
nonce_account: None,
|
||||
nonce_authority: 0,
|
||||
fee_payer: 0,
|
||||
custodian: None,
|
||||
};
|
||||
config_offline.output_format = OutputFormat::JsonCompact;
|
||||
let sign_reply = process_command(&config_offline).unwrap();
|
||||
@@ -687,13 +669,10 @@ fn test_stake_authorize() {
|
||||
nonce_account: None,
|
||||
nonce_authority: 0,
|
||||
fee_payer: 0,
|
||||
custodian: None,
|
||||
};
|
||||
process_command(&config).unwrap();
|
||||
let stake_account = rpc_client
|
||||
.get_account_with_commitment(&stake_account_pubkey, CommitmentConfig::recent())
|
||||
.unwrap()
|
||||
.value
|
||||
.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,
|
||||
@@ -719,7 +698,7 @@ fn test_stake_authorize() {
|
||||
let nonce_hash = nonce_utils::get_account_with_commitment(
|
||||
&rpc_client,
|
||||
&nonce_account.pubkey(),
|
||||
CommitmentConfig::recent(),
|
||||
CommitmentConfig::processed(),
|
||||
)
|
||||
.and_then(|ref a| nonce_utils::data_from_account(a))
|
||||
.unwrap()
|
||||
@@ -737,6 +716,7 @@ fn test_stake_authorize() {
|
||||
nonce_account: Some(nonce_account.pubkey()),
|
||||
nonce_authority: 0,
|
||||
fee_payer: 0,
|
||||
custodian: None,
|
||||
};
|
||||
let sign_reply = process_command(&config_offline).unwrap();
|
||||
let sign_only = parse_sign_only_reply_string(&sign_reply);
|
||||
@@ -756,13 +736,10 @@ fn test_stake_authorize() {
|
||||
nonce_account: Some(nonce_account.pubkey()),
|
||||
nonce_authority: 0,
|
||||
fee_payer: 0,
|
||||
custodian: None,
|
||||
};
|
||||
process_command(&config).unwrap();
|
||||
let stake_account = rpc_client
|
||||
.get_account_with_commitment(&stake_account_pubkey, CommitmentConfig::recent())
|
||||
.unwrap()
|
||||
.value
|
||||
.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,
|
||||
@@ -773,7 +750,7 @@ fn test_stake_authorize() {
|
||||
let new_nonce_hash = nonce_utils::get_account_with_commitment(
|
||||
&rpc_client,
|
||||
&nonce_account.pubkey(),
|
||||
CommitmentConfig::recent(),
|
||||
CommitmentConfig::processed(),
|
||||
)
|
||||
.and_then(|ref a| nonce_utils::data_from_account(a))
|
||||
.unwrap()
|
||||
@@ -788,11 +765,10 @@ fn test_stake_authorize_with_fee_payer() {
|
||||
|
||||
let mint_keypair = Keypair::new();
|
||||
let test_validator = TestValidator::with_custom_fees(mint_keypair.pubkey(), SIG_FEE);
|
||||
let (sender, receiver) = channel();
|
||||
run_local_faucet(mint_keypair, sender, None);
|
||||
let faucet_addr = receiver.recv().unwrap();
|
||||
let faucet_addr = run_local_faucet(mint_keypair, None);
|
||||
|
||||
let rpc_client = RpcClient::new(test_validator.rpc_url());
|
||||
let rpc_client =
|
||||
RpcClient::new_with_commitment(test_validator.rpc_url(), CommitmentConfig::processed());
|
||||
let default_signer = Keypair::new();
|
||||
let default_pubkey = default_signer.pubkey();
|
||||
|
||||
@@ -861,6 +837,7 @@ fn test_stake_authorize_with_fee_payer() {
|
||||
nonce_account: None,
|
||||
nonce_authority: 0,
|
||||
fee_payer: 1,
|
||||
custodian: None,
|
||||
};
|
||||
process_command(&config).unwrap();
|
||||
// `config` balance has not changed, despite submitting the TX
|
||||
@@ -870,10 +847,7 @@ fn test_stake_authorize_with_fee_payer() {
|
||||
check_recent_balance(100_000 - SIG_FEE - SIG_FEE, &rpc_client, &payer_pubkey);
|
||||
|
||||
// Assign authority with offline fee payer
|
||||
let (blockhash, _, _) = rpc_client
|
||||
.get_recent_blockhash_with_commitment(CommitmentConfig::recent())
|
||||
.unwrap()
|
||||
.value;
|
||||
let (blockhash, _) = rpc_client.get_recent_blockhash().unwrap();
|
||||
config_offline.command = CliCommand::StakeAuthorize {
|
||||
stake_account_pubkey,
|
||||
new_authorizations: vec![(StakeAuthorize::Staker, payer_pubkey, 0)],
|
||||
@@ -882,6 +856,7 @@ fn test_stake_authorize_with_fee_payer() {
|
||||
nonce_account: None,
|
||||
nonce_authority: 0,
|
||||
fee_payer: 0,
|
||||
custodian: None,
|
||||
};
|
||||
config_offline.output_format = OutputFormat::JsonCompact;
|
||||
let sign_reply = process_command(&config_offline).unwrap();
|
||||
@@ -897,6 +872,7 @@ fn test_stake_authorize_with_fee_payer() {
|
||||
nonce_account: None,
|
||||
nonce_authority: 0,
|
||||
fee_payer: 0,
|
||||
custodian: None,
|
||||
};
|
||||
process_command(&config).unwrap();
|
||||
// `config`'s balance again has not changed
|
||||
@@ -912,11 +888,10 @@ fn test_stake_split() {
|
||||
|
||||
let mint_keypair = Keypair::new();
|
||||
let test_validator = TestValidator::with_custom_fees(mint_keypair.pubkey(), 1);
|
||||
let (sender, receiver) = channel();
|
||||
run_local_faucet(mint_keypair, sender, None);
|
||||
let faucet_addr = receiver.recv().unwrap();
|
||||
let faucet_addr = run_local_faucet(mint_keypair, None);
|
||||
|
||||
let rpc_client = RpcClient::new(test_validator.rpc_url());
|
||||
let rpc_client =
|
||||
RpcClient::new_with_commitment(test_validator.rpc_url(), CommitmentConfig::processed());
|
||||
let default_signer = Keypair::new();
|
||||
let offline_signer = Keypair::new();
|
||||
|
||||
@@ -993,7 +968,7 @@ fn test_stake_split() {
|
||||
let nonce_hash = nonce_utils::get_account_with_commitment(
|
||||
&rpc_client,
|
||||
&nonce_account.pubkey(),
|
||||
CommitmentConfig::recent(),
|
||||
CommitmentConfig::processed(),
|
||||
)
|
||||
.and_then(|ref a| nonce_utils::data_from_account(a))
|
||||
.unwrap()
|
||||
@@ -1055,11 +1030,10 @@ fn test_stake_set_lockup() {
|
||||
|
||||
let mint_keypair = Keypair::new();
|
||||
let test_validator = TestValidator::with_custom_fees(mint_keypair.pubkey(), 1);
|
||||
let (sender, receiver) = channel();
|
||||
run_local_faucet(mint_keypair, sender, None);
|
||||
let faucet_addr = receiver.recv().unwrap();
|
||||
let faucet_addr = run_local_faucet(mint_keypair, None);
|
||||
|
||||
let rpc_client = RpcClient::new(test_validator.rpc_url());
|
||||
let rpc_client =
|
||||
RpcClient::new_with_commitment(test_validator.rpc_url(), CommitmentConfig::processed());
|
||||
let default_signer = Keypair::new();
|
||||
let offline_signer = Keypair::new();
|
||||
|
||||
@@ -1142,11 +1116,7 @@ fn test_stake_set_lockup() {
|
||||
fee_payer: 0,
|
||||
};
|
||||
process_command(&config).unwrap();
|
||||
let stake_account = rpc_client
|
||||
.get_account_with_commitment(&stake_account_pubkey, CommitmentConfig::recent())
|
||||
.unwrap()
|
||||
.value
|
||||
.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,
|
||||
@@ -1197,11 +1167,7 @@ fn test_stake_set_lockup() {
|
||||
fee_payer: 0,
|
||||
};
|
||||
process_command(&config).unwrap();
|
||||
let stake_account = rpc_client
|
||||
.get_account_with_commitment(&stake_account_pubkey, CommitmentConfig::recent())
|
||||
.unwrap()
|
||||
.value
|
||||
.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,
|
||||
@@ -1252,7 +1218,7 @@ fn test_stake_set_lockup() {
|
||||
let nonce_hash = nonce_utils::get_account_with_commitment(
|
||||
&rpc_client,
|
||||
&nonce_account.pubkey(),
|
||||
CommitmentConfig::recent(),
|
||||
CommitmentConfig::processed(),
|
||||
)
|
||||
.and_then(|ref a| nonce_utils::data_from_account(a))
|
||||
.unwrap()
|
||||
@@ -1294,11 +1260,7 @@ fn test_stake_set_lockup() {
|
||||
fee_payer: 0,
|
||||
};
|
||||
process_command(&config).unwrap();
|
||||
let stake_account = rpc_client
|
||||
.get_account_with_commitment(&stake_account_pubkey, CommitmentConfig::recent())
|
||||
.unwrap()
|
||||
.value
|
||||
.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,
|
||||
@@ -1318,11 +1280,10 @@ fn test_offline_nonced_create_stake_account_and_withdraw() {
|
||||
|
||||
let mint_keypair = Keypair::new();
|
||||
let test_validator = TestValidator::with_no_fees(mint_keypair.pubkey());
|
||||
let (sender, receiver) = channel();
|
||||
run_local_faucet(mint_keypair, sender, None);
|
||||
let faucet_addr = receiver.recv().unwrap();
|
||||
let faucet_addr = run_local_faucet(mint_keypair, None);
|
||||
|
||||
let rpc_client = RpcClient::new(test_validator.rpc_url());
|
||||
let rpc_client =
|
||||
RpcClient::new_with_commitment(test_validator.rpc_url(), CommitmentConfig::processed());
|
||||
let mut config = CliConfig::recent_for_tests();
|
||||
let default_signer = keypair_from_seed(&[1u8; 32]).unwrap();
|
||||
config.signers = vec![&default_signer];
|
||||
@@ -1334,7 +1295,7 @@ fn test_offline_nonced_create_stake_account_and_withdraw() {
|
||||
let offline_pubkey = config_offline.signers[0].pubkey();
|
||||
config_offline.json_rpc_url = String::default();
|
||||
config_offline.command = CliCommand::ClusterVersion;
|
||||
// Verfiy that we cannot reach the cluster
|
||||
// Verify that we cannot reach the cluster
|
||||
process_command(&config_offline).unwrap_err();
|
||||
|
||||
request_and_confirm_airdrop(
|
||||
@@ -1370,7 +1331,7 @@ fn test_offline_nonced_create_stake_account_and_withdraw() {
|
||||
let nonce_hash = nonce_utils::get_account_with_commitment(
|
||||
&rpc_client,
|
||||
&nonce_account.pubkey(),
|
||||
CommitmentConfig::recent(),
|
||||
CommitmentConfig::processed(),
|
||||
)
|
||||
.and_then(|ref a| nonce_utils::data_from_account(a))
|
||||
.unwrap()
|
||||
@@ -1425,7 +1386,7 @@ fn test_offline_nonced_create_stake_account_and_withdraw() {
|
||||
let nonce_hash = nonce_utils::get_account_with_commitment(
|
||||
&rpc_client,
|
||||
&nonce_account.pubkey(),
|
||||
CommitmentConfig::recent(),
|
||||
CommitmentConfig::processed(),
|
||||
)
|
||||
.and_then(|ref a| nonce_utils::data_from_account(a))
|
||||
.unwrap()
|
||||
@@ -1473,7 +1434,7 @@ fn test_offline_nonced_create_stake_account_and_withdraw() {
|
||||
let nonce_hash = nonce_utils::get_account_with_commitment(
|
||||
&rpc_client,
|
||||
&nonce_account.pubkey(),
|
||||
CommitmentConfig::recent(),
|
||||
CommitmentConfig::processed(),
|
||||
)
|
||||
.and_then(|ref a| nonce_utils::data_from_account(a))
|
||||
.unwrap()
|
||||
|
@@ -17,19 +17,16 @@ use solana_sdk::{
|
||||
pubkey::Pubkey,
|
||||
signature::{keypair_from_seed, Keypair, NullSigner, Signer},
|
||||
};
|
||||
use std::sync::mpsc::channel;
|
||||
|
||||
#[test]
|
||||
fn test_transfer() {
|
||||
solana_logger::setup();
|
||||
let mint_keypair = Keypair::new();
|
||||
let test_validator = TestValidator::with_custom_fees(mint_keypair.pubkey(), 1);
|
||||
let faucet_addr = run_local_faucet(mint_keypair, None);
|
||||
|
||||
let (sender, receiver) = channel();
|
||||
run_local_faucet(mint_keypair, sender, None);
|
||||
let faucet_addr = receiver.recv().unwrap();
|
||||
|
||||
let rpc_client = RpcClient::new(test_validator.rpc_url());
|
||||
let rpc_client =
|
||||
RpcClient::new_with_commitment(test_validator.rpc_url(), CommitmentConfig::processed());
|
||||
|
||||
let default_signer = Keypair::new();
|
||||
let default_offline_signer = Keypair::new();
|
||||
@@ -59,6 +56,8 @@ fn test_transfer() {
|
||||
nonce_account: None,
|
||||
nonce_authority: 0,
|
||||
fee_payer: 0,
|
||||
derived_address_seed: None,
|
||||
derived_address_program_id: None,
|
||||
};
|
||||
process_command(&config).unwrap();
|
||||
check_recent_balance(49_989, &rpc_client, &sender_pubkey);
|
||||
@@ -75,6 +74,8 @@ fn test_transfer() {
|
||||
nonce_account: None,
|
||||
nonce_authority: 0,
|
||||
fee_payer: 0,
|
||||
derived_address_seed: None,
|
||||
derived_address_program_id: None,
|
||||
};
|
||||
assert!(process_command(&config).is_err());
|
||||
check_recent_balance(49_989, &rpc_client, &sender_pubkey);
|
||||
@@ -92,10 +93,7 @@ fn test_transfer() {
|
||||
check_recent_balance(50, &rpc_client, &offline_pubkey);
|
||||
|
||||
// Offline transfer
|
||||
let (blockhash, _, _) = rpc_client
|
||||
.get_recent_blockhash_with_commitment(CommitmentConfig::recent())
|
||||
.unwrap()
|
||||
.value;
|
||||
let (blockhash, _) = rpc_client.get_recent_blockhash().unwrap();
|
||||
offline.command = CliCommand::Transfer {
|
||||
amount: SpendAmount::Some(10),
|
||||
to: recipient_pubkey,
|
||||
@@ -106,6 +104,8 @@ fn test_transfer() {
|
||||
nonce_account: None,
|
||||
nonce_authority: 0,
|
||||
fee_payer: 0,
|
||||
derived_address_seed: None,
|
||||
derived_address_program_id: None,
|
||||
};
|
||||
offline.output_format = OutputFormat::JsonCompact;
|
||||
let sign_only_reply = process_command(&offline).unwrap();
|
||||
@@ -123,6 +123,8 @@ fn test_transfer() {
|
||||
nonce_account: None,
|
||||
nonce_authority: 0,
|
||||
fee_payer: 0,
|
||||
derived_address_seed: None,
|
||||
derived_address_program_id: None,
|
||||
};
|
||||
process_command(&config).unwrap();
|
||||
check_recent_balance(39, &rpc_client, &offline_pubkey);
|
||||
@@ -147,7 +149,7 @@ fn test_transfer() {
|
||||
let nonce_hash = nonce_utils::get_account_with_commitment(
|
||||
&rpc_client,
|
||||
&nonce_account.pubkey(),
|
||||
CommitmentConfig::recent(),
|
||||
CommitmentConfig::processed(),
|
||||
)
|
||||
.and_then(|ref a| nonce_utils::data_from_account(a))
|
||||
.unwrap()
|
||||
@@ -168,6 +170,8 @@ fn test_transfer() {
|
||||
nonce_account: Some(nonce_account.pubkey()),
|
||||
nonce_authority: 0,
|
||||
fee_payer: 0,
|
||||
derived_address_seed: None,
|
||||
derived_address_program_id: None,
|
||||
};
|
||||
process_command(&config).unwrap();
|
||||
check_recent_balance(49_976 - minimum_nonce_balance, &rpc_client, &sender_pubkey);
|
||||
@@ -175,7 +179,7 @@ fn test_transfer() {
|
||||
let new_nonce_hash = nonce_utils::get_account_with_commitment(
|
||||
&rpc_client,
|
||||
&nonce_account.pubkey(),
|
||||
CommitmentConfig::recent(),
|
||||
CommitmentConfig::processed(),
|
||||
)
|
||||
.and_then(|ref a| nonce_utils::data_from_account(a))
|
||||
.unwrap()
|
||||
@@ -196,7 +200,7 @@ fn test_transfer() {
|
||||
let nonce_hash = nonce_utils::get_account_with_commitment(
|
||||
&rpc_client,
|
||||
&nonce_account.pubkey(),
|
||||
CommitmentConfig::recent(),
|
||||
CommitmentConfig::processed(),
|
||||
)
|
||||
.and_then(|ref a| nonce_utils::data_from_account(a))
|
||||
.unwrap()
|
||||
@@ -214,6 +218,8 @@ fn test_transfer() {
|
||||
nonce_account: Some(nonce_account.pubkey()),
|
||||
nonce_authority: 0,
|
||||
fee_payer: 0,
|
||||
derived_address_seed: None,
|
||||
derived_address_program_id: None,
|
||||
};
|
||||
let sign_only_reply = process_command(&offline).unwrap();
|
||||
let sign_only = parse_sign_only_reply_string(&sign_only_reply);
|
||||
@@ -233,6 +239,8 @@ fn test_transfer() {
|
||||
nonce_account: Some(nonce_account.pubkey()),
|
||||
nonce_authority: 0,
|
||||
fee_payer: 0,
|
||||
derived_address_seed: None,
|
||||
derived_address_program_id: None,
|
||||
};
|
||||
process_command(&config).unwrap();
|
||||
check_recent_balance(28, &rpc_client, &offline_pubkey);
|
||||
@@ -244,10 +252,7 @@ fn test_transfer_multisession_signing() {
|
||||
solana_logger::setup();
|
||||
let mint_keypair = Keypair::new();
|
||||
let test_validator = TestValidator::with_custom_fees(mint_keypair.pubkey(), 1);
|
||||
|
||||
let (sender, receiver) = channel();
|
||||
run_local_faucet(mint_keypair, sender, None);
|
||||
let faucet_addr = receiver.recv().unwrap();
|
||||
let faucet_addr = run_local_faucet(mint_keypair, None);
|
||||
|
||||
let to_pubkey = Pubkey::new(&[1u8; 32]);
|
||||
let offline_from_signer = keypair_from_seed(&[2u8; 32]).unwrap();
|
||||
@@ -256,7 +261,8 @@ fn test_transfer_multisession_signing() {
|
||||
let config = CliConfig::recent_for_tests();
|
||||
|
||||
// Setup accounts
|
||||
let rpc_client = RpcClient::new(test_validator.rpc_url());
|
||||
let rpc_client =
|
||||
RpcClient::new_with_commitment(test_validator.rpc_url(), CommitmentConfig::processed());
|
||||
request_and_confirm_airdrop(
|
||||
&rpc_client,
|
||||
&faucet_addr,
|
||||
@@ -279,10 +285,7 @@ fn test_transfer_multisession_signing() {
|
||||
|
||||
check_ready(&rpc_client);
|
||||
|
||||
let (blockhash, _, _) = rpc_client
|
||||
.get_recent_blockhash_with_commitment(CommitmentConfig::recent())
|
||||
.unwrap()
|
||||
.value;
|
||||
let (blockhash, _) = rpc_client.get_recent_blockhash().unwrap();
|
||||
|
||||
// Offline fee-payer signs first
|
||||
let mut fee_payer_config = CliConfig::recent_for_tests();
|
||||
@@ -301,6 +304,8 @@ fn test_transfer_multisession_signing() {
|
||||
nonce_account: None,
|
||||
nonce_authority: 0,
|
||||
fee_payer: 0,
|
||||
derived_address_seed: None,
|
||||
derived_address_program_id: None,
|
||||
};
|
||||
fee_payer_config.output_format = OutputFormat::JsonCompact;
|
||||
let sign_only_reply = process_command(&fee_payer_config).unwrap();
|
||||
@@ -327,6 +332,8 @@ fn test_transfer_multisession_signing() {
|
||||
nonce_account: None,
|
||||
nonce_authority: 0,
|
||||
fee_payer: 0,
|
||||
derived_address_seed: None,
|
||||
derived_address_program_id: None,
|
||||
};
|
||||
from_config.output_format = OutputFormat::JsonCompact;
|
||||
let sign_only_reply = process_command(&from_config).unwrap();
|
||||
@@ -350,6 +357,8 @@ fn test_transfer_multisession_signing() {
|
||||
nonce_account: None,
|
||||
nonce_authority: 0,
|
||||
fee_payer: 0,
|
||||
derived_address_seed: None,
|
||||
derived_address_program_id: None,
|
||||
};
|
||||
process_command(&config).unwrap();
|
||||
|
||||
@@ -363,12 +372,10 @@ fn test_transfer_all() {
|
||||
solana_logger::setup();
|
||||
let mint_keypair = Keypair::new();
|
||||
let test_validator = TestValidator::with_custom_fees(mint_keypair.pubkey(), 1);
|
||||
let faucet_addr = run_local_faucet(mint_keypair, None);
|
||||
|
||||
let (sender, receiver) = channel();
|
||||
run_local_faucet(mint_keypair, sender, None);
|
||||
let faucet_addr = receiver.recv().unwrap();
|
||||
|
||||
let rpc_client = RpcClient::new(test_validator.rpc_url());
|
||||
let rpc_client =
|
||||
RpcClient::new_with_commitment(test_validator.rpc_url(), CommitmentConfig::processed());
|
||||
|
||||
let default_signer = Keypair::new();
|
||||
|
||||
@@ -397,8 +404,66 @@ fn test_transfer_all() {
|
||||
nonce_account: None,
|
||||
nonce_authority: 0,
|
||||
fee_payer: 0,
|
||||
derived_address_seed: None,
|
||||
derived_address_program_id: None,
|
||||
};
|
||||
process_command(&config).unwrap();
|
||||
check_recent_balance(0, &rpc_client, &sender_pubkey);
|
||||
check_recent_balance(49_999, &rpc_client, &recipient_pubkey);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_transfer_with_seed() {
|
||||
solana_logger::setup();
|
||||
let mint_keypair = Keypair::new();
|
||||
let test_validator = TestValidator::with_custom_fees(mint_keypair.pubkey(), 1);
|
||||
let faucet_addr = run_local_faucet(mint_keypair, None);
|
||||
|
||||
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];
|
||||
|
||||
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 = Pubkey::create_with_seed(
|
||||
&sender_pubkey,
|
||||
&derived_address_seed,
|
||||
&derived_address_program_id,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
request_and_confirm_airdrop(&rpc_client, &faucet_addr, &sender_pubkey, 1, &config).unwrap();
|
||||
request_and_confirm_airdrop(&rpc_client, &faucet_addr, &derived_address, 50_000, &config)
|
||||
.unwrap();
|
||||
check_recent_balance(1, &rpc_client, &sender_pubkey);
|
||||
check_recent_balance(50_000, &rpc_client, &derived_address);
|
||||
check_recent_balance(0, &rpc_client, &recipient_pubkey);
|
||||
|
||||
check_ready(&rpc_client);
|
||||
|
||||
// Transfer with seed
|
||||
config.command = CliCommand::Transfer {
|
||||
amount: SpendAmount::Some(50_000),
|
||||
to: recipient_pubkey,
|
||||
from: 0,
|
||||
sign_only: false,
|
||||
no_wait: false,
|
||||
blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
|
||||
nonce_account: None,
|
||||
nonce_authority: 0,
|
||||
fee_payer: 0,
|
||||
derived_address_seed: Some(derived_address_seed),
|
||||
derived_address_program_id: Some(derived_address_program_id),
|
||||
};
|
||||
process_command(&config).unwrap();
|
||||
check_recent_balance(0, &rpc_client, &sender_pubkey);
|
||||
check_recent_balance(50_000, &rpc_client, &recipient_pubkey);
|
||||
check_recent_balance(0, &rpc_client, &derived_address);
|
||||
}
|
||||
|
@@ -15,17 +15,15 @@ use solana_sdk::{
|
||||
signature::{Keypair, Signer},
|
||||
};
|
||||
use solana_vote_program::vote_state::{VoteAuthorize, VoteState, VoteStateVersions};
|
||||
use std::sync::mpsc::channel;
|
||||
|
||||
#[test]
|
||||
fn test_vote_authorize_and_withdraw() {
|
||||
let mint_keypair = Keypair::new();
|
||||
let test_validator = TestValidator::with_no_fees(mint_keypair.pubkey());
|
||||
let (sender, receiver) = channel();
|
||||
run_local_faucet(mint_keypair, sender, None);
|
||||
let faucet_addr = receiver.recv().unwrap();
|
||||
let faucet_addr = run_local_faucet(mint_keypair, None);
|
||||
|
||||
let rpc_client = RpcClient::new(test_validator.rpc_url());
|
||||
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();
|
||||
@@ -55,9 +53,7 @@ fn test_vote_authorize_and_withdraw() {
|
||||
};
|
||||
process_command(&config).unwrap();
|
||||
let vote_account = rpc_client
|
||||
.get_account_with_commitment(&vote_account_keypair.pubkey(), CommitmentConfig::recent())
|
||||
.unwrap()
|
||||
.value
|
||||
.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;
|
||||
@@ -80,6 +76,8 @@ fn test_vote_authorize_and_withdraw() {
|
||||
nonce_account: None,
|
||||
nonce_authority: 0,
|
||||
fee_payer: 0,
|
||||
derived_address_seed: None,
|
||||
derived_address_program_id: None,
|
||||
};
|
||||
process_command(&config).unwrap();
|
||||
let expected_balance = expected_balance + 1_000;
|
||||
@@ -95,9 +93,7 @@ fn test_vote_authorize_and_withdraw() {
|
||||
};
|
||||
process_command(&config).unwrap();
|
||||
let vote_account = rpc_client
|
||||
.get_account_with_commitment(&vote_account_keypair.pubkey(), CommitmentConfig::recent())
|
||||
.unwrap()
|
||||
.value
|
||||
.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;
|
||||
|
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "solana-client"
|
||||
version = "1.5.0"
|
||||
version = "1.5.13"
|
||||
description = "Solana Client"
|
||||
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
@@ -20,16 +20,16 @@ net2 = "0.2.37"
|
||||
rayon = "1.4.0"
|
||||
reqwest = { version = "0.10.8", default-features = false, features = ["blocking", "rustls-tls", "json"] }
|
||||
semver = "0.11.0"
|
||||
serde = "1.0.112"
|
||||
serde = "1.0.118"
|
||||
serde_derive = "1.0.103"
|
||||
serde_json = "1.0.56"
|
||||
solana-account-decoder = { path = "../account-decoder", version = "1.5.0" }
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.5.0" }
|
||||
solana-net-utils = { path = "../net-utils", version = "1.5.0" }
|
||||
solana-sdk = { path = "../sdk", version = "1.5.0" }
|
||||
solana-transaction-status = { path = "../transaction-status", version = "1.5.0" }
|
||||
solana-version = { path = "../version", version = "1.5.0" }
|
||||
solana-vote-program = { path = "../programs/vote", version = "1.5.0" }
|
||||
solana-account-decoder = { path = "../account-decoder", version = "1.5.13" }
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.5.13" }
|
||||
solana-net-utils = { path = "../net-utils", version = "1.5.13" }
|
||||
solana-sdk = { path = "../sdk", version = "1.5.13" }
|
||||
solana-transaction-status = { path = "../transaction-status", version = "1.5.13" }
|
||||
solana-version = { path = "../version", version = "1.5.13" }
|
||||
solana-vote-program = { path = "../programs/vote", version = "1.5.13" }
|
||||
thiserror = "1.0"
|
||||
tungstenite = "0.10.1"
|
||||
url = "2.1.1"
|
||||
@@ -38,7 +38,7 @@ url = "2.1.1"
|
||||
assert_matches = "1.3.0"
|
||||
jsonrpc-core = "15.0.0"
|
||||
jsonrpc-http-server = "15.0.0"
|
||||
solana-logger = { path = "../logger", version = "1.5.0" }
|
||||
solana-logger = { path = "../logger", version = "1.5.13" }
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
@@ -5,6 +5,8 @@ use solana_sdk::{
|
||||
use std::io;
|
||||
use thiserror::Error;
|
||||
|
||||
pub use reqwest; // export `reqwest` for clients
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum ClientErrorKind {
|
||||
#[error(transparent)]
|
||||
@@ -33,16 +35,16 @@ impl From<TransportError> for ClientErrorKind {
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<TransportError> for ClientErrorKind {
|
||||
fn into(self) -> TransportError {
|
||||
match self {
|
||||
Self::Io(err) => TransportError::IoError(err),
|
||||
Self::TransactionError(err) => TransportError::TransactionError(err),
|
||||
Self::Reqwest(err) => TransportError::Custom(format!("{:?}", err)),
|
||||
Self::RpcError(err) => TransportError::Custom(format!("{:?}", err)),
|
||||
Self::SerdeJson(err) => TransportError::Custom(format!("{:?}", err)),
|
||||
Self::SigningError(err) => TransportError::Custom(format!("{:?}", err)),
|
||||
Self::Custom(err) => TransportError::Custom(format!("{:?}", err)),
|
||||
impl From<ClientErrorKind> for TransportError {
|
||||
fn from(client_error_kind: ClientErrorKind) -> Self {
|
||||
match client_error_kind {
|
||||
ClientErrorKind::Io(err) => Self::IoError(err),
|
||||
ClientErrorKind::TransactionError(err) => Self::TransactionError(err),
|
||||
ClientErrorKind::Reqwest(err) => Self::Custom(format!("{:?}", err)),
|
||||
ClientErrorKind::RpcError(err) => Self::Custom(format!("{:?}", err)),
|
||||
ClientErrorKind::SerdeJson(err) => Self::Custom(format!("{:?}", err)),
|
||||
ClientErrorKind::SigningError(err) => Self::Custom(format!("{:?}", err)),
|
||||
ClientErrorKind::Custom(err) => Self::Custom(format!("{:?}", err)),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -98,9 +100,9 @@ impl From<TransportError> for ClientError {
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<TransportError> for ClientError {
|
||||
fn into(self) -> TransportError {
|
||||
self.kind.into()
|
||||
impl From<ClientError> for TransportError {
|
||||
fn from(client_error: ClientError) -> Self {
|
||||
client_error.kind.into()
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -85,6 +85,14 @@ impl RpcSender for HttpSender {
|
||||
}
|
||||
}
|
||||
},
|
||||
rpc_custom_error::JSON_RPC_SERVER_ERROR_NODE_UNHEALTHY => {
|
||||
match serde_json::from_value::<rpc_custom_error::NodeUnhealthyErrorData>(json["error"]["data"].clone()) {
|
||||
Ok(rpc_custom_error::NodeUnhealthyErrorData {num_slots_behind}) => RpcResponseErrorData::NodeUnhealthy {num_slots_behind},
|
||||
Err(_err) => {
|
||||
RpcResponseErrorData::Empty
|
||||
}
|
||||
}
|
||||
},
|
||||
_ => RpcResponseErrorData::Empty
|
||||
};
|
||||
|
||||
|
@@ -1,3 +1,4 @@
|
||||
#![allow(clippy::integer_arithmetic)]
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
|
||||
@@ -8,6 +9,7 @@ pub mod mock_sender;
|
||||
pub mod nonce_utils;
|
||||
pub mod perf_utils;
|
||||
pub mod pubsub_client;
|
||||
pub mod rpc_cache;
|
||||
pub mod rpc_client;
|
||||
pub mod rpc_config;
|
||||
pub mod rpc_custom_error;
|
||||
|
@@ -12,7 +12,7 @@ use solana_sdk::{
|
||||
signature::Signature,
|
||||
transaction::{self, Transaction, TransactionError},
|
||||
};
|
||||
use solana_transaction_status::TransactionStatus;
|
||||
use solana_transaction_status::{TransactionConfirmationStatus, TransactionStatus};
|
||||
use solana_version::Version;
|
||||
use std::{collections::HashMap, sync::RwLock};
|
||||
|
||||
@@ -48,6 +48,10 @@ impl RpcSender for MockSender {
|
||||
return Ok(Value::Null);
|
||||
}
|
||||
let val = match request {
|
||||
RpcRequest::GetAccountInfo => serde_json::to_value(Response {
|
||||
context: RpcResponseContext { slot: 1 },
|
||||
value: Value::Null,
|
||||
})?,
|
||||
RpcRequest::GetBalance => serde_json::to_value(Response {
|
||||
context: RpcResponseContext { slot: 1 },
|
||||
value: Value::Number(Number::from(50)),
|
||||
@@ -65,6 +69,7 @@ impl RpcSender for MockSender {
|
||||
slots_in_epoch: 32,
|
||||
absolute_slot: 34,
|
||||
block_height: 34,
|
||||
transaction_count: Some(123),
|
||||
})?,
|
||||
RpcRequest::GetFeeCalculatorForBlockhash => {
|
||||
let value = if self.url == "blockhash_expired" {
|
||||
@@ -101,6 +106,7 @@ impl RpcSender for MockSender {
|
||||
slot: 1,
|
||||
confirmations: None,
|
||||
err,
|
||||
confirmation_status: Some(TransactionConfirmationStatus::Finalized),
|
||||
})
|
||||
};
|
||||
let statuses: Vec<Option<TransactionStatus>> = params.as_array().unwrap()[0]
|
||||
|
@@ -33,7 +33,7 @@ pub fn sample_txs<T>(
|
||||
let mut now = Instant::now();
|
||||
let start_time = now;
|
||||
let initial_txs = client
|
||||
.get_transaction_count_with_commitment(CommitmentConfig::recent())
|
||||
.get_transaction_count_with_commitment(CommitmentConfig::processed())
|
||||
.expect("transaction count");
|
||||
let mut last_txs = initial_txs;
|
||||
|
||||
@@ -42,7 +42,7 @@ pub fn sample_txs<T>(
|
||||
let elapsed = now.elapsed();
|
||||
now = Instant::now();
|
||||
let mut txs;
|
||||
match client.get_transaction_count_with_commitment(CommitmentConfig::recent()) {
|
||||
match client.get_transaction_count_with_commitment(CommitmentConfig::processed()) {
|
||||
Err(e) => {
|
||||
// ThinClient with multiple options should pick a better one now.
|
||||
info!("Couldn't get transaction count {:?}", e);
|
||||
|
@@ -98,7 +98,7 @@ where
|
||||
}
|
||||
|
||||
pub fn send_unsubscribe(&self) -> Result<(), PubsubClientError> {
|
||||
let method = format!("{}Unubscribe", self.operation);
|
||||
let method = format!("{}Unsubscribe", self.operation);
|
||||
self.socket
|
||||
.write()
|
||||
.unwrap()
|
||||
|
75
client/src/rpc_cache.rs
Normal file
75
client/src/rpc_cache.rs
Normal file
@@ -0,0 +1,75 @@
|
||||
use crate::{rpc_config::RpcLargestAccountsFilter, rpc_response::RpcAccountBalance};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
time::{Duration, SystemTime},
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct LargestAccountsCache {
|
||||
duration: u64,
|
||||
cache: HashMap<Option<RpcLargestAccountsFilter>, LargestAccountsCacheValue>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct LargestAccountsCacheValue {
|
||||
accounts: Vec<RpcAccountBalance>,
|
||||
slot: u64,
|
||||
cached_time: SystemTime,
|
||||
}
|
||||
|
||||
impl LargestAccountsCache {
|
||||
pub fn new(duration: u64) -> Self {
|
||||
Self {
|
||||
duration,
|
||||
cache: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_largest_accounts(
|
||||
&self,
|
||||
filter: &Option<RpcLargestAccountsFilter>,
|
||||
) -> Option<(u64, Vec<RpcAccountBalance>)> {
|
||||
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()));
|
||||
}
|
||||
}
|
||||
None
|
||||
})
|
||||
}
|
||||
|
||||
pub fn set_largest_accounts(
|
||||
&mut self,
|
||||
filter: &Option<RpcLargestAccountsFilter>,
|
||||
slot: u64,
|
||||
accounts: &[RpcAccountBalance],
|
||||
) {
|
||||
self.cache.insert(
|
||||
filter.clone(),
|
||||
LargestAccountsCacheValue {
|
||||
accounts: accounts.to_owned(),
|
||||
slot,
|
||||
cached_time: SystemTime::now(),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_old_entries_expire() {
|
||||
let mut cache = LargestAccountsCache::new(1);
|
||||
|
||||
let filter = Some(RpcLargestAccountsFilter::Circulating);
|
||||
|
||||
let accounts: Vec<RpcAccountBalance> = Vec::new();
|
||||
|
||||
cache.set_largest_accounts(&filter, 1000, &accounts);
|
||||
std::thread::sleep(Duration::from_secs(1));
|
||||
assert_eq!(cache.get_largest_accounts(&filter), None);
|
||||
}
|
||||
}
|
@@ -40,6 +40,7 @@ use solana_transaction_status::{
|
||||
};
|
||||
use solana_vote_program::vote_state::MAX_LOCKOUT_HISTORY;
|
||||
use std::{
|
||||
cmp::min,
|
||||
net::SocketAddr,
|
||||
sync::RwLock,
|
||||
thread::sleep,
|
||||
@@ -49,7 +50,7 @@ use std::{
|
||||
pub struct RpcClient {
|
||||
sender: Box<dyn RpcSender + Send + Sync + 'static>,
|
||||
commitment_config: CommitmentConfig,
|
||||
default_cluster_transaction_encoding: RwLock<Option<UiTransactionEncoding>>,
|
||||
node_version: RwLock<Option<semver::Version>>,
|
||||
}
|
||||
|
||||
fn serialize_encode_transaction(
|
||||
@@ -79,7 +80,7 @@ impl RpcClient {
|
||||
) -> Self {
|
||||
Self {
|
||||
sender: Box::new(sender),
|
||||
default_cluster_transaction_encoding: RwLock::new(None),
|
||||
node_version: RwLock::new(None),
|
||||
commitment_config,
|
||||
}
|
||||
}
|
||||
@@ -99,6 +100,17 @@ impl RpcClient {
|
||||
)
|
||||
}
|
||||
|
||||
pub fn new_with_timeout_and_commitment(
|
||||
url: String,
|
||||
timeout: Duration,
|
||||
commitment_config: CommitmentConfig,
|
||||
) -> Self {
|
||||
Self::new_sender(
|
||||
HttpSender::new_with_timeout(url, timeout),
|
||||
commitment_config,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn new_mock(url: String) -> Self {
|
||||
Self::new_sender(MockSender::new(url), CommitmentConfig::default())
|
||||
}
|
||||
@@ -119,16 +131,54 @@ impl RpcClient {
|
||||
Self::new_with_timeout(url, timeout)
|
||||
}
|
||||
|
||||
pub fn confirm_transaction(&self, signature: &Signature) -> ClientResult<bool> {
|
||||
Ok(self
|
||||
.confirm_transaction_with_commitment(signature, self.commitment_config)?
|
||||
.value)
|
||||
fn get_node_version(&self) -> Result<semver::Version, RpcError> {
|
||||
let r_node_version = self.node_version.read().unwrap();
|
||||
if let Some(version) = &*r_node_version {
|
||||
Ok(version.clone())
|
||||
} else {
|
||||
drop(r_node_version);
|
||||
let mut w_node_version = self.node_version.write().unwrap();
|
||||
let node_version = self.get_version().map_err(|e| {
|
||||
RpcError::RpcRequestError(format!("cluster version query failed: {}", e))
|
||||
})?;
|
||||
let node_version = semver::Version::parse(&node_version.solana_core).map_err(|e| {
|
||||
RpcError::RpcRequestError(format!("failed to parse cluster version: {}", e))
|
||||
})?;
|
||||
*w_node_version = Some(node_version.clone());
|
||||
Ok(node_version)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn commitment(&self) -> CommitmentConfig {
|
||||
self.commitment_config
|
||||
}
|
||||
|
||||
fn use_deprecated_commitment(&self) -> Result<bool, RpcError> {
|
||||
Ok(self.get_node_version()? < semver::Version::new(1, 5, 5))
|
||||
}
|
||||
|
||||
fn maybe_map_commitment(
|
||||
&self,
|
||||
requested_commitment: CommitmentConfig,
|
||||
) -> Result<CommitmentConfig, RpcError> {
|
||||
if matches!(
|
||||
requested_commitment.commitment,
|
||||
CommitmentLevel::Finalized | CommitmentLevel::Confirmed | CommitmentLevel::Processed
|
||||
) && self.use_deprecated_commitment()?
|
||||
{
|
||||
return Ok(CommitmentConfig::use_deprecated_commitment(
|
||||
requested_commitment,
|
||||
));
|
||||
}
|
||||
Ok(requested_commitment)
|
||||
}
|
||||
|
||||
pub fn confirm_transaction(&self, signature: &Signature) -> ClientResult<bool> {
|
||||
Ok(self
|
||||
.confirm_transaction_with_commitment(signature, self.commitment_config)?
|
||||
.value)
|
||||
}
|
||||
|
||||
pub fn confirm_transaction_with_commitment(
|
||||
&self,
|
||||
signature: &Signature,
|
||||
@@ -150,34 +200,20 @@ impl RpcClient {
|
||||
self.send_transaction_with_config(
|
||||
transaction,
|
||||
RpcSendTransactionConfig {
|
||||
preflight_commitment: Some(self.commitment_config.commitment),
|
||||
preflight_commitment: Some(
|
||||
self.maybe_map_commitment(self.commitment_config)?
|
||||
.commitment,
|
||||
),
|
||||
..RpcSendTransactionConfig::default()
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn default_cluster_transaction_encoding(&self) -> Result<UiTransactionEncoding, RpcError> {
|
||||
let default_cluster_transaction_encoding =
|
||||
self.default_cluster_transaction_encoding.read().unwrap();
|
||||
if let Some(encoding) = *default_cluster_transaction_encoding {
|
||||
Ok(encoding)
|
||||
if self.get_node_version()? < semver::Version::new(1, 3, 16) {
|
||||
Ok(UiTransactionEncoding::Base58)
|
||||
} else {
|
||||
drop(default_cluster_transaction_encoding);
|
||||
let cluster_version = self.get_version().map_err(|e| {
|
||||
RpcError::RpcRequestError(format!("cluster version query failed: {}", e))
|
||||
})?;
|
||||
let cluster_version =
|
||||
semver::Version::parse(&cluster_version.solana_core).map_err(|e| {
|
||||
RpcError::RpcRequestError(format!("failed to parse cluster version: {}", e))
|
||||
})?;
|
||||
// Prefer base64 since 1.3.16
|
||||
let encoding = if cluster_version < semver::Version::new(1, 3, 16) {
|
||||
UiTransactionEncoding::Base58
|
||||
} else {
|
||||
UiTransactionEncoding::Base64
|
||||
};
|
||||
*self.default_cluster_transaction_encoding.write().unwrap() = Some(encoding);
|
||||
Ok(encoding)
|
||||
Ok(UiTransactionEncoding::Base64)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -191,8 +227,13 @@ impl RpcClient {
|
||||
} else {
|
||||
self.default_cluster_transaction_encoding()?
|
||||
};
|
||||
let preflight_commitment = CommitmentConfig {
|
||||
commitment: config.preflight_commitment.unwrap_or_default(),
|
||||
};
|
||||
let preflight_commitment = self.maybe_map_commitment(preflight_commitment)?;
|
||||
let config = RpcSendTransactionConfig {
|
||||
encoding: Some(encoding),
|
||||
preflight_commitment: Some(preflight_commitment.commitment),
|
||||
..config
|
||||
};
|
||||
let serialized_encoded = serialize_encode_transaction(transaction, encoding)?;
|
||||
@@ -218,6 +259,7 @@ impl RpcClient {
|
||||
for (i, log) in logs.iter().enumerate() {
|
||||
debug!("{:>3}: {}", i + 1, log);
|
||||
}
|
||||
debug!("");
|
||||
}
|
||||
}
|
||||
return Err(err);
|
||||
@@ -265,8 +307,11 @@ impl RpcClient {
|
||||
} else {
|
||||
self.default_cluster_transaction_encoding()?
|
||||
};
|
||||
let commitment = config.commitment.unwrap_or_default();
|
||||
let commitment = self.maybe_map_commitment(commitment)?;
|
||||
let config = RpcSimulateTransactionConfig {
|
||||
encoding: Some(encoding),
|
||||
commitment: Some(commitment),
|
||||
..config
|
||||
};
|
||||
let serialized_encoded = serialize_encode_transaction(transaction, encoding)?;
|
||||
@@ -276,6 +321,10 @@ impl RpcClient {
|
||||
)
|
||||
}
|
||||
|
||||
pub fn get_snapshot_slot(&self) -> ClientResult<Slot> {
|
||||
self.send(RpcRequest::GetSnapshotSlot, Value::Null)
|
||||
}
|
||||
|
||||
pub fn get_signature_status(
|
||||
&self,
|
||||
signature: &Signature,
|
||||
@@ -345,14 +394,24 @@ impl RpcClient {
|
||||
&self,
|
||||
commitment_config: CommitmentConfig,
|
||||
) -> ClientResult<Slot> {
|
||||
self.send(RpcRequest::GetSlot, json!([commitment_config]))
|
||||
self.send(
|
||||
RpcRequest::GetSlot,
|
||||
json!([self.maybe_map_commitment(commitment_config)?]),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn supply(&self) -> RpcResult<RpcSupply> {
|
||||
self.supply_with_commitment(self.commitment_config)
|
||||
}
|
||||
|
||||
pub fn supply_with_commitment(
|
||||
&self,
|
||||
commitment_config: CommitmentConfig,
|
||||
) -> RpcResult<RpcSupply> {
|
||||
self.send(RpcRequest::GetSupply, json!([commitment_config]))
|
||||
self.send(
|
||||
RpcRequest::GetSupply,
|
||||
json!([self.maybe_map_commitment(commitment_config)?]),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn total_supply(&self) -> ClientResult<u64> {
|
||||
@@ -363,13 +422,22 @@ impl RpcClient {
|
||||
&self,
|
||||
commitment_config: CommitmentConfig,
|
||||
) -> ClientResult<u64> {
|
||||
self.send(RpcRequest::GetTotalSupply, json!([commitment_config]))
|
||||
self.send(
|
||||
RpcRequest::GetTotalSupply,
|
||||
json!([self.maybe_map_commitment(commitment_config)?]),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn get_largest_accounts_with_config(
|
||||
&self,
|
||||
config: RpcLargestAccountsConfig,
|
||||
) -> RpcResult<Vec<RpcAccountBalance>> {
|
||||
let commitment = config.commitment.unwrap_or_default();
|
||||
let commitment = self.maybe_map_commitment(commitment)?;
|
||||
let config = RpcLargestAccountsConfig {
|
||||
commitment: Some(commitment),
|
||||
..config
|
||||
};
|
||||
self.send(RpcRequest::GetLargestAccounts, json!([config]))
|
||||
}
|
||||
|
||||
@@ -381,7 +449,10 @@ impl RpcClient {
|
||||
&self,
|
||||
commitment_config: CommitmentConfig,
|
||||
) -> ClientResult<RpcVoteAccountStatus> {
|
||||
self.send(RpcRequest::GetVoteAccounts, json!([commitment_config]))
|
||||
self.send(
|
||||
RpcRequest::GetVoteAccounts,
|
||||
json!([self.maybe_map_commitment(commitment_config)?]),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn wait_for_max_stake(
|
||||
@@ -541,7 +612,10 @@ impl RpcClient {
|
||||
&self,
|
||||
commitment_config: CommitmentConfig,
|
||||
) -> ClientResult<EpochInfo> {
|
||||
self.send(RpcRequest::GetEpochInfo, json!([commitment_config]))
|
||||
self.send(
|
||||
RpcRequest::GetEpochInfo,
|
||||
json!([self.maybe_map_commitment(commitment_config)?]),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn get_leader_schedule(
|
||||
@@ -558,7 +632,7 @@ impl RpcClient {
|
||||
) -> ClientResult<Option<RpcLeaderSchedule>> {
|
||||
self.send(
|
||||
RpcRequest::GetLeaderSchedule,
|
||||
json!([slot, commitment_config]),
|
||||
json!([slot, self.maybe_map_commitment(commitment_config)?]),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -599,7 +673,7 @@ impl RpcClient {
|
||||
) -> ClientResult<Signature> {
|
||||
let signature = self.send_transaction(transaction)?;
|
||||
let recent_blockhash = if uses_durable_nonce(transaction).is_some() {
|
||||
self.get_recent_blockhash_with_commitment(CommitmentConfig::recent())?
|
||||
self.get_recent_blockhash_with_commitment(CommitmentConfig::processed())?
|
||||
.value
|
||||
.0
|
||||
} else {
|
||||
@@ -611,7 +685,7 @@ impl RpcClient {
|
||||
if self
|
||||
.get_fee_calculator_for_blockhash_with_commitment(
|
||||
&recent_blockhash,
|
||||
CommitmentConfig::recent(),
|
||||
CommitmentConfig::processed(),
|
||||
)?
|
||||
.value
|
||||
.is_none()
|
||||
@@ -657,7 +731,7 @@ impl RpcClient {
|
||||
) -> RpcResult<Option<Account>> {
|
||||
let config = RpcAccountInfoConfig {
|
||||
encoding: Some(UiAccountEncoding::Base64),
|
||||
commitment: Some(commitment_config),
|
||||
commitment: Some(self.maybe_map_commitment(commitment_config)?),
|
||||
data_slice: None,
|
||||
};
|
||||
let response = self.sender.send(
|
||||
@@ -704,7 +778,7 @@ impl RpcClient {
|
||||
) -> RpcResult<Vec<Option<Account>>> {
|
||||
let config = RpcAccountInfoConfig {
|
||||
encoding: Some(UiAccountEncoding::Base64),
|
||||
commitment: Some(commitment_config),
|
||||
commitment: Some(self.maybe_map_commitment(commitment_config)?),
|
||||
data_slice: None,
|
||||
};
|
||||
let pubkeys: Vec<_> = pubkeys.iter().map(|pubkey| pubkey.to_string()).collect();
|
||||
@@ -758,7 +832,10 @@ impl RpcClient {
|
||||
) -> RpcResult<u64> {
|
||||
self.send(
|
||||
RpcRequest::GetBalance,
|
||||
json!([pubkey.to_string(), commitment_config]),
|
||||
json!([
|
||||
pubkey.to_string(),
|
||||
self.maybe_map_commitment(commitment_config)?
|
||||
]),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -769,6 +846,7 @@ impl RpcClient {
|
||||
filters: None,
|
||||
account_config: RpcAccountInfoConfig {
|
||||
encoding: Some(UiAccountEncoding::Base64),
|
||||
commitment: Some(self.commitment_config),
|
||||
..RpcAccountInfoConfig::default()
|
||||
},
|
||||
},
|
||||
@@ -780,6 +858,16 @@ impl RpcClient {
|
||||
pubkey: &Pubkey,
|
||||
config: RpcProgramAccountsConfig,
|
||||
) -> ClientResult<Vec<(Pubkey, Account)>> {
|
||||
let commitment = config.account_config.commitment.unwrap_or_default();
|
||||
let commitment = self.maybe_map_commitment(commitment)?;
|
||||
let account_config = RpcAccountInfoConfig {
|
||||
commitment: Some(commitment),
|
||||
..config.account_config
|
||||
};
|
||||
let config = RpcProgramAccountsConfig {
|
||||
account_config,
|
||||
..config
|
||||
};
|
||||
let accounts: Vec<RpcKeyedAccount> = self.send(
|
||||
RpcRequest::GetProgramAccounts,
|
||||
json!([pubkey.to_string(), config]),
|
||||
@@ -796,7 +884,10 @@ impl RpcClient {
|
||||
&self,
|
||||
commitment_config: CommitmentConfig,
|
||||
) -> ClientResult<u64> {
|
||||
self.send(RpcRequest::GetTransactionCount, json!([commitment_config]))
|
||||
self.send(
|
||||
RpcRequest::GetTransactionCount,
|
||||
json!([self.maybe_map_commitment(commitment_config)?]),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn get_recent_blockhash(&self) -> ClientResult<(Hash, FeeCalculator)> {
|
||||
@@ -818,9 +909,11 @@ impl RpcClient {
|
||||
fee_calculator,
|
||||
last_valid_slot,
|
||||
},
|
||||
}) =
|
||||
self.send::<Response<RpcFees>>(RpcRequest::GetFees, json!([commitment_config]))
|
||||
{
|
||||
}) = self
|
||||
.send::<Response<RpcFees>>(
|
||||
RpcRequest::GetFees,
|
||||
json!([self.maybe_map_commitment(commitment_config)?]),
|
||||
) {
|
||||
(context, blockhash, fee_calculator, last_valid_slot)
|
||||
} else if let Ok(Response {
|
||||
context,
|
||||
@@ -831,7 +924,7 @@ impl RpcClient {
|
||||
},
|
||||
}) = self.send::<Response<RpcBlockhashFeeCalculator>>(
|
||||
RpcRequest::GetRecentBlockhash,
|
||||
json!([commitment_config]),
|
||||
json!([self.maybe_map_commitment(commitment_config)?]),
|
||||
) {
|
||||
(context, blockhash, fee_calculator, 0)
|
||||
} else {
|
||||
@@ -869,7 +962,10 @@ impl RpcClient {
|
||||
) -> RpcResult<Option<FeeCalculator>> {
|
||||
let Response { context, value } = self.send::<Response<Option<RpcFeeCalculator>>>(
|
||||
RpcRequest::GetFeeCalculatorForBlockhash,
|
||||
json!([blockhash.to_string(), commitment_config]),
|
||||
json!([
|
||||
blockhash.to_string(),
|
||||
self.maybe_map_commitment(commitment_config)?
|
||||
]),
|
||||
)?;
|
||||
|
||||
Ok(Response {
|
||||
@@ -932,6 +1028,11 @@ impl RpcClient {
|
||||
Ok(hash)
|
||||
}
|
||||
|
||||
pub fn get_health(&self) -> ClientResult<()> {
|
||||
self.send::<String>(RpcRequest::GetHealth, Value::Null)
|
||||
.map(|_| ())
|
||||
}
|
||||
|
||||
pub fn get_token_account(&self, pubkey: &Pubkey) -> ClientResult<Option<UiTokenAccount>> {
|
||||
Ok(self
|
||||
.get_token_account_with_commitment(pubkey, self.commitment_config)?
|
||||
@@ -945,7 +1046,7 @@ impl RpcClient {
|
||||
) -> RpcResult<Option<UiTokenAccount>> {
|
||||
let config = RpcAccountInfoConfig {
|
||||
encoding: Some(UiAccountEncoding::JsonParsed),
|
||||
commitment: Some(commitment_config),
|
||||
commitment: Some(self.maybe_map_commitment(commitment_config)?),
|
||||
data_slice: None,
|
||||
};
|
||||
let response = self.sender.send(
|
||||
@@ -1006,7 +1107,10 @@ impl RpcClient {
|
||||
) -> RpcResult<UiTokenAmount> {
|
||||
self.send(
|
||||
RpcRequest::GetTokenAccountBalance,
|
||||
json!([pubkey.to_string(), commitment_config]),
|
||||
json!([
|
||||
pubkey.to_string(),
|
||||
self.maybe_map_commitment(commitment_config)?
|
||||
]),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1039,7 +1143,7 @@ impl RpcClient {
|
||||
|
||||
let config = RpcAccountInfoConfig {
|
||||
encoding: Some(UiAccountEncoding::JsonParsed),
|
||||
commitment: Some(commitment_config),
|
||||
commitment: Some(self.maybe_map_commitment(commitment_config)?),
|
||||
data_slice: None,
|
||||
};
|
||||
|
||||
@@ -1078,7 +1182,7 @@ impl RpcClient {
|
||||
|
||||
let config = RpcAccountInfoConfig {
|
||||
encoding: Some(UiAccountEncoding::JsonParsed),
|
||||
commitment: Some(commitment_config),
|
||||
commitment: Some(self.maybe_map_commitment(commitment_config)?),
|
||||
data_slice: None,
|
||||
};
|
||||
|
||||
@@ -1101,7 +1205,10 @@ impl RpcClient {
|
||||
) -> RpcResult<UiTokenAmount> {
|
||||
self.send(
|
||||
RpcRequest::GetTokenSupply,
|
||||
json!([mint.to_string(), commitment_config]),
|
||||
json!([
|
||||
mint.to_string(),
|
||||
self.maybe_map_commitment(commitment_config)?
|
||||
]),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1306,9 +1413,10 @@ impl RpcClient {
|
||||
commitment: CommitmentConfig,
|
||||
config: RpcSendTransactionConfig,
|
||||
) -> ClientResult<Signature> {
|
||||
let desired_confirmations = match commitment.commitment {
|
||||
CommitmentLevel::Max | CommitmentLevel::Root => MAX_LOCKOUT_HISTORY + 1,
|
||||
_ => 1,
|
||||
let desired_confirmations = if commitment.is_finalized() {
|
||||
MAX_LOCKOUT_HISTORY + 1
|
||||
} else {
|
||||
1
|
||||
};
|
||||
let mut confirmations = 0;
|
||||
|
||||
@@ -1319,7 +1427,7 @@ impl RpcClient {
|
||||
confirmations, desired_confirmations, transaction.signatures[0],
|
||||
));
|
||||
let recent_blockhash = if uses_durable_nonce(transaction).is_some() {
|
||||
self.get_recent_blockhash_with_commitment(CommitmentConfig::recent())?
|
||||
self.get_recent_blockhash_with_commitment(CommitmentConfig::processed())?
|
||||
.value
|
||||
.0
|
||||
} else {
|
||||
@@ -1328,13 +1436,13 @@ impl RpcClient {
|
||||
let signature = self.send_transaction_with_config(transaction, config)?;
|
||||
let (signature, status) = loop {
|
||||
// Get recent commitment in order to count confirmations for successful transactions
|
||||
let status =
|
||||
self.get_signature_status_with_commitment(&signature, CommitmentConfig::recent())?;
|
||||
let status = self
|
||||
.get_signature_status_with_commitment(&signature, CommitmentConfig::processed())?;
|
||||
if status.is_none() {
|
||||
if self
|
||||
.get_fee_calculator_for_blockhash_with_commitment(
|
||||
&recent_blockhash,
|
||||
CommitmentConfig::recent(),
|
||||
CommitmentConfig::processed(),
|
||||
)?
|
||||
.value
|
||||
.is_none()
|
||||
@@ -1364,29 +1472,20 @@ impl RpcClient {
|
||||
}
|
||||
let now = Instant::now();
|
||||
loop {
|
||||
match commitment.commitment {
|
||||
CommitmentLevel::Max | CommitmentLevel::Root =>
|
||||
// Return when default (max) commitment is reached
|
||||
// Failed transactions have already been eliminated, `is_some` check is sufficient
|
||||
{
|
||||
if self.get_signature_status(&signature)?.is_some() {
|
||||
progress_bar.set_message("Transaction confirmed");
|
||||
progress_bar.finish_and_clear();
|
||||
return Ok(signature);
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
// Return when one confirmation has been reached
|
||||
if confirmations >= desired_confirmations {
|
||||
progress_bar.set_message("Transaction reached commitment");
|
||||
progress_bar.finish_and_clear();
|
||||
return Ok(signature);
|
||||
}
|
||||
}
|
||||
// Return when specified commitment is reached
|
||||
// Failed transactions have already been eliminated, `is_some` check is sufficient
|
||||
if self
|
||||
.get_signature_status_with_commitment(&signature, commitment)?
|
||||
.is_some()
|
||||
{
|
||||
progress_bar.set_message("Transaction confirmed");
|
||||
progress_bar.finish_and_clear();
|
||||
return Ok(signature);
|
||||
}
|
||||
|
||||
progress_bar.set_message(&format!(
|
||||
"[{}/{}] Finalizing transaction {}",
|
||||
confirmations + 1,
|
||||
min(confirmations + 1, desired_confirmations),
|
||||
desired_confirmations,
|
||||
signature,
|
||||
));
|
||||
|
@@ -31,7 +31,7 @@ pub struct RpcSimulateTransactionConfig {
|
||||
pub encoding: Option<UiTransactionEncoding>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum RpcLargestAccountsFilter {
|
||||
Circulating,
|
||||
|
@@ -8,8 +8,11 @@ pub const JSON_RPC_SERVER_ERROR_BLOCK_CLEANED_UP: i64 = -32001;
|
||||
pub const JSON_RPC_SERVER_ERROR_SEND_TRANSACTION_PREFLIGHT_FAILURE: i64 = -32002;
|
||||
pub const JSON_RPC_SERVER_ERROR_TRANSACTION_SIGNATURE_VERIFICATION_FAILURE: i64 = -32003;
|
||||
pub const JSON_RPC_SERVER_ERROR_BLOCK_NOT_AVAILABLE: i64 = -32004;
|
||||
pub const JSON_RPC_SERVER_ERROR_NODE_UNHEALTHLY: i64 = -32005;
|
||||
pub const JSON_RPC_SERVER_ERROR_NODE_UNHEALTHY: i64 = -32005;
|
||||
pub const JSON_RPC_SERVER_ERROR_TRANSACTION_PRECOMPILE_VERIFICATION_FAILURE: i64 = -32006;
|
||||
pub const JSON_RPC_SERVER_ERROR_SLOT_SKIPPED: i64 = -32007;
|
||||
pub const JSON_RPC_SERVER_ERROR_NO_SNAPSHOT: i64 = -32008;
|
||||
pub const JSON_RPC_SERVER_ERROR_LONG_TERM_STORAGE_SLOT_SKIPPED: i64 = -32009;
|
||||
|
||||
pub enum RpcCustomError {
|
||||
BlockCleanedUp {
|
||||
@@ -24,8 +27,23 @@ pub enum RpcCustomError {
|
||||
BlockNotAvailable {
|
||||
slot: Slot,
|
||||
},
|
||||
RpcNodeUnhealthy,
|
||||
NodeUnhealthy {
|
||||
num_slots_behind: Option<Slot>,
|
||||
},
|
||||
TransactionPrecompileVerificationFailure(solana_sdk::transaction::TransactionError),
|
||||
SlotSkipped {
|
||||
slot: Slot,
|
||||
},
|
||||
NoSnapshot,
|
||||
LongTermStorageSlotSkipped {
|
||||
slot: Slot,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct NodeUnhealthyErrorData {
|
||||
pub num_slots_behind: Option<Slot>,
|
||||
}
|
||||
|
||||
impl From<RpcCustomError> for Error {
|
||||
@@ -61,10 +79,16 @@ impl From<RpcCustomError> for Error {
|
||||
message: format!("Block not available for slot {}", slot),
|
||||
data: None,
|
||||
},
|
||||
RpcCustomError::RpcNodeUnhealthy => Self {
|
||||
code: ErrorCode::ServerError(JSON_RPC_SERVER_ERROR_NODE_UNHEALTHLY),
|
||||
message: "RPC node is unhealthy".to_string(),
|
||||
data: None,
|
||||
RpcCustomError::NodeUnhealthy { num_slots_behind } => Self {
|
||||
code: ErrorCode::ServerError(JSON_RPC_SERVER_ERROR_NODE_UNHEALTHY),
|
||||
message: if let Some(num_slots_behind) = num_slots_behind {
|
||||
format!("Node is behind by {} slots", num_slots_behind)
|
||||
} else {
|
||||
"Node is unhealthy".to_string()
|
||||
},
|
||||
data: Some(serde_json::json!(NodeUnhealthyErrorData {
|
||||
num_slots_behind
|
||||
})),
|
||||
},
|
||||
RpcCustomError::TransactionPrecompileVerificationFailure(e) => Self {
|
||||
code: ErrorCode::ServerError(
|
||||
@@ -73,6 +97,24 @@ impl From<RpcCustomError> for Error {
|
||||
message: format!("Transaction precompile verification failure {:?}", e),
|
||||
data: None,
|
||||
},
|
||||
RpcCustomError::SlotSkipped { slot } => Self {
|
||||
code: ErrorCode::ServerError(JSON_RPC_SERVER_ERROR_SLOT_SKIPPED),
|
||||
message: format!(
|
||||
"Slot {} was skipped, or missing due to ledger jump to recent snapshot",
|
||||
slot
|
||||
),
|
||||
data: None,
|
||||
},
|
||||
RpcCustomError::NoSnapshot => Self {
|
||||
code: ErrorCode::ServerError(JSON_RPC_SERVER_ERROR_NO_SNAPSHOT),
|
||||
message: "No snapshot".to_string(),
|
||||
data: None,
|
||||
},
|
||||
RpcCustomError::LongTermStorageSlotSkipped { slot } => Self {
|
||||
code: ErrorCode::ServerError(JSON_RPC_SERVER_ERROR_LONG_TERM_STORAGE_SLOT_SKIPPED),
|
||||
message: format!("Slot {} was skipped, or missing in long-term storage", slot),
|
||||
data: None,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -16,10 +16,15 @@ impl RpcFilterType {
|
||||
match encoding {
|
||||
MemcmpEncoding::Binary => {
|
||||
let MemcmpEncodedBytes::Binary(bytes) = &compare.bytes;
|
||||
bs58::decode(&bytes)
|
||||
.into_vec()
|
||||
.map(|_| ())
|
||||
.map_err(|e| e.into())
|
||||
|
||||
if bytes.len() > 128 {
|
||||
Err(RpcFilterError::Base58DataTooLarge)
|
||||
} else {
|
||||
bs58::decode(&bytes)
|
||||
.into_vec()
|
||||
.map(|_| ())
|
||||
.map_err(|e| e.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -27,10 +32,12 @@ impl RpcFilterType {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
#[derive(Error, PartialEq, Debug)]
|
||||
pub enum RpcFilterError {
|
||||
#[error("bs58 decode error")]
|
||||
DecodeError(#[from] bs58::decode::Error),
|
||||
#[error("encoded binary (base 58) data should be less than 129 bytes")]
|
||||
Base58DataTooLarge,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
@@ -140,4 +147,36 @@ mod tests {
|
||||
}
|
||||
.bytes_match(&data));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_verify_memcmp() {
|
||||
let base58_bytes = "\
|
||||
1111111111111111111111111111111111111111111111111111111111111111\
|
||||
1111111111111111111111111111111111111111111111111111111111111111";
|
||||
assert_eq!(base58_bytes.len(), 128);
|
||||
assert_eq!(
|
||||
RpcFilterType::Memcmp(Memcmp {
|
||||
offset: 0,
|
||||
bytes: MemcmpEncodedBytes::Binary(base58_bytes.to_string()),
|
||||
encoding: None,
|
||||
})
|
||||
.verify(),
|
||||
Ok(())
|
||||
);
|
||||
|
||||
let base58_bytes = "\
|
||||
1111111111111111111111111111111111111111111111111111111111111111\
|
||||
1111111111111111111111111111111111111111111111111111111111111111\
|
||||
1";
|
||||
assert_eq!(base58_bytes.len(), 129);
|
||||
assert_eq!(
|
||||
RpcFilterType::Memcmp(Memcmp {
|
||||
offset: 0,
|
||||
bytes: MemcmpEncodedBytes::Binary(base58_bytes.to_string()),
|
||||
encoding: None,
|
||||
})
|
||||
.verify(),
|
||||
Err(RpcFilterError::Base58DataTooLarge)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
use crate::rpc_response::RpcSimulateTransactionResult;
|
||||
use serde_json::{json, Value};
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
use solana_sdk::{clock::Slot, pubkey::Pubkey};
|
||||
use std::fmt;
|
||||
use thiserror::Error;
|
||||
|
||||
@@ -25,6 +25,7 @@ pub enum RpcRequest {
|
||||
GetFees,
|
||||
GetFirstAvailableBlock,
|
||||
GetGenesisHash,
|
||||
GetHealth,
|
||||
GetIdentity,
|
||||
GetInflationGovernor,
|
||||
GetInflationRate,
|
||||
@@ -34,6 +35,7 @@ pub enum RpcRequest {
|
||||
GetMultipleAccounts,
|
||||
GetProgramAccounts,
|
||||
GetRecentBlockhash,
|
||||
GetSnapshotSlot,
|
||||
GetSignatureStatuses,
|
||||
GetSlot,
|
||||
GetSlotLeader,
|
||||
@@ -80,6 +82,7 @@ impl fmt::Display for RpcRequest {
|
||||
RpcRequest::GetFees => "getFees",
|
||||
RpcRequest::GetFirstAvailableBlock => "getFirstAvailableBlock",
|
||||
RpcRequest::GetGenesisHash => "getGenesisHash",
|
||||
RpcRequest::GetHealth => "getHealth",
|
||||
RpcRequest::GetIdentity => "getIdentity",
|
||||
RpcRequest::GetInflationGovernor => "getInflationGovernor",
|
||||
RpcRequest::GetInflationRate => "getInflationRate",
|
||||
@@ -89,6 +92,7 @@ impl fmt::Display for RpcRequest {
|
||||
RpcRequest::GetMultipleAccounts => "getMultipleAccounts",
|
||||
RpcRequest::GetProgramAccounts => "getProgramAccounts",
|
||||
RpcRequest::GetRecentBlockhash => "getRecentBlockhash",
|
||||
RpcRequest::GetSnapshotSlot => "getSnapshotSlot",
|
||||
RpcRequest::GetSignatureStatuses => "getSignatureStatuses",
|
||||
RpcRequest::GetSlot => "getSlot",
|
||||
RpcRequest::GetSlotLeader => "getSlotLeader",
|
||||
@@ -123,6 +127,7 @@ pub const MAX_GET_CONFIRMED_BLOCKS_RANGE: u64 = 500_000;
|
||||
pub const MAX_GET_CONFIRMED_SIGNATURES_FOR_ADDRESS2_LIMIT: usize = 1_000;
|
||||
pub const MAX_MULTIPLE_ACCOUNTS: usize = 100;
|
||||
pub const NUM_LARGEST_ACCOUNTS: usize = 20;
|
||||
pub const MAX_GET_PROGRAM_ACCOUNT_FILTERS: usize = 4;
|
||||
|
||||
// Validators that are this number of slots behind are considered delinquent
|
||||
pub const DELINQUENT_VALIDATOR_SLOT_DISTANCE: u64 = 128;
|
||||
@@ -143,6 +148,7 @@ impl RpcRequest {
|
||||
pub enum RpcResponseErrorData {
|
||||
Empty,
|
||||
SendTransactionPreflightFailure(RpcSimulateTransactionResult),
|
||||
NodeUnhealthy { num_slots_behind: Option<Slot> },
|
||||
}
|
||||
|
||||
impl fmt::Display for RpcResponseErrorData {
|
||||
@@ -243,7 +249,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_build_request_json_config_options() {
|
||||
let commitment_config = CommitmentConfig {
|
||||
commitment: CommitmentLevel::Max,
|
||||
commitment: CommitmentLevel::Finalized,
|
||||
};
|
||||
let addr = json!("deadbeefXjn8o3yroDHxUtKsZZgoy4GPkPPXfouKNHhx");
|
||||
|
||||
|
@@ -1,7 +1,7 @@
|
||||
use crate::client_error;
|
||||
use solana_account_decoder::{parse_token::UiTokenAmount, UiAccount};
|
||||
use solana_sdk::{
|
||||
clock::{Epoch, Slot},
|
||||
clock::{Epoch, Slot, UnixTimestamp},
|
||||
fee_calculator::{FeeCalculator, FeeRateGovernor},
|
||||
inflation::Inflation,
|
||||
transaction::{Result, TransactionError},
|
||||
@@ -101,6 +101,15 @@ pub struct SlotInfo {
|
||||
pub root: Slot,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(rename_all = "camelCase", tag = "type")]
|
||||
pub enum SlotUpdate {
|
||||
OptimisticConfirmation { slot: Slot, timestamp: u64 },
|
||||
FirstShredReceived { slot: Slot, timestamp: u64 },
|
||||
Frozen { slot: Slot, timestamp: u64 },
|
||||
Root { slot: Slot, timestamp: u64 },
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
#[serde(rename_all = "camelCase", untagged)]
|
||||
pub enum RpcSignatureResult {
|
||||
@@ -286,6 +295,7 @@ pub struct RpcConfirmedTransactionStatusWithSignature {
|
||||
pub slot: Slot,
|
||||
pub err: Option<TransactionError>,
|
||||
pub memo: Option<String>,
|
||||
pub block_time: Option<UnixTimestamp>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
@@ -304,12 +314,14 @@ impl From<ConfirmedTransactionStatusWithSignature> for RpcConfirmedTransactionSt
|
||||
slot,
|
||||
err,
|
||||
memo,
|
||||
block_time,
|
||||
} = value;
|
||||
Self {
|
||||
signature: signature.to_string(),
|
||||
slot,
|
||||
err,
|
||||
memo,
|
||||
block_time,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "solana-core"
|
||||
description = "Blockchain, Rebuilt for Scale"
|
||||
version = "1.5.0"
|
||||
version = "1.5.13"
|
||||
documentation = "https://docs.rs/solana"
|
||||
homepage = "https://solana.com/"
|
||||
readme = "../README.md"
|
||||
@@ -24,7 +24,7 @@ chrono = { version = "0.4.11", features = ["serde"] }
|
||||
core_affinity = "0.5.10"
|
||||
crossbeam-channel = "0.4"
|
||||
ed25519-dalek = "=1.0.0-pre.4"
|
||||
fs_extra = "1.1.0"
|
||||
fs_extra = "1.2.0"
|
||||
flate2 = "1.0"
|
||||
indexmap = { version = "1.5", features = ["rayon"] }
|
||||
itertools = "0.9.0"
|
||||
@@ -38,52 +38,57 @@ log = "0.4.11"
|
||||
lru = "0.6.1"
|
||||
miow = "0.2.2"
|
||||
net2 = "0.2.37"
|
||||
num_cpus = "1.13.0"
|
||||
num-traits = "0.2"
|
||||
rand = "0.7.0"
|
||||
rand_chacha = "0.2.2"
|
||||
raptorq = "1.4.2"
|
||||
rayon = "1.4.1"
|
||||
regex = "1.3.9"
|
||||
serde = "1.0.112"
|
||||
retain_mut = "0.1.2"
|
||||
rustversion = "1.0.4"
|
||||
serde = "1.0.118"
|
||||
serde_bytes = "0.11"
|
||||
serde_derive = "1.0.103"
|
||||
serde_json = "1.0.56"
|
||||
solana-account-decoder = { path = "../account-decoder", version = "1.5.0" }
|
||||
solana-banks-server = { path = "../banks-server", version = "1.5.0" }
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.5.0" }
|
||||
solana-client = { path = "../client", version = "1.5.0" }
|
||||
solana-faucet = { path = "../faucet", version = "1.5.0" }
|
||||
solana-ledger = { path = "../ledger", version = "1.5.0" }
|
||||
solana-logger = { path = "../logger", version = "1.5.0" }
|
||||
solana-merkle-tree = { path = "../merkle-tree", version = "1.5.0" }
|
||||
solana-metrics = { path = "../metrics", version = "1.5.0" }
|
||||
solana-measure = { path = "../measure", version = "1.5.0" }
|
||||
solana-net-utils = { path = "../net-utils", version = "1.5.0" }
|
||||
solana-perf = { path = "../perf", version = "1.5.0" }
|
||||
solana-program-test = { path = "../program-test", version = "1.5.0" }
|
||||
solana-runtime = { path = "../runtime", version = "1.5.0" }
|
||||
solana-sdk = { path = "../sdk", version = "1.5.0" }
|
||||
solana-frozen-abi = { path = "../frozen-abi", version = "1.5.0" }
|
||||
solana-frozen-abi-macro = { path = "../frozen-abi/macro", version = "1.5.0" }
|
||||
solana-stake-program = { path = "../programs/stake", version = "1.5.0" }
|
||||
solana-storage-bigtable = { path = "../storage-bigtable", version = "1.5.0" }
|
||||
solana-streamer = { path = "../streamer", version = "1.5.0" }
|
||||
solana-sys-tuner = { path = "../sys-tuner", version = "1.5.0" }
|
||||
solana-transaction-status = { path = "../transaction-status", version = "1.5.0" }
|
||||
solana-version = { path = "../version", version = "1.5.0" }
|
||||
solana-vote-program = { path = "../programs/vote", version = "1.5.0" }
|
||||
spl-token-v2-0 = { package = "spl-token", version = "=3.0.0", features = ["no-entrypoint"] }
|
||||
solana-account-decoder = { path = "../account-decoder", version = "1.5.13" }
|
||||
solana-banks-server = { path = "../banks-server", version = "1.5.13" }
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.5.13" }
|
||||
solana-client = { path = "../client", version = "1.5.13" }
|
||||
solana-faucet = { path = "../faucet", version = "1.5.13" }
|
||||
solana-ledger = { path = "../ledger", version = "1.5.13" }
|
||||
solana-logger = { path = "../logger", version = "1.5.13" }
|
||||
solana-merkle-tree = { path = "../merkle-tree", version = "1.5.13" }
|
||||
solana-metrics = { path = "../metrics", version = "1.5.13" }
|
||||
solana-measure = { path = "../measure", version = "1.5.13" }
|
||||
solana-net-utils = { path = "../net-utils", version = "1.5.13" }
|
||||
solana-perf = { path = "../perf", version = "1.5.13" }
|
||||
solana-program-test = { path = "../program-test", version = "1.5.13" }
|
||||
solana-runtime = { path = "../runtime", version = "1.5.13" }
|
||||
solana-sdk = { path = "../sdk", version = "1.5.13" }
|
||||
solana-frozen-abi = { path = "../frozen-abi", version = "1.5.13" }
|
||||
solana-frozen-abi-macro = { path = "../frozen-abi/macro", version = "1.5.13" }
|
||||
solana-stake-program = { path = "../programs/stake", version = "1.5.13" }
|
||||
solana-storage-bigtable = { path = "../storage-bigtable", version = "1.5.13" }
|
||||
solana-streamer = { path = "../streamer", version = "1.5.13" }
|
||||
solana-sys-tuner = { path = "../sys-tuner", version = "1.5.13" }
|
||||
solana-transaction-status = { path = "../transaction-status", version = "1.5.13" }
|
||||
solana-version = { path = "../version", version = "1.5.13" }
|
||||
solana-vote-program = { path = "../programs/vote", version = "1.5.13" }
|
||||
spl-token-v2-0 = { package = "spl-token", version = "=3.1.0", features = ["no-entrypoint"] }
|
||||
tempfile = "3.1.0"
|
||||
thiserror = "1.0"
|
||||
tokio = { version = "0.2", features = ["full"] }
|
||||
tokio_01 = { version = "0.1", package = "tokio" }
|
||||
tokio_01_bytes = { version = "0.4.7", package = "bytes" }
|
||||
tokio_fs_01 = { version = "0.1", package = "tokio-fs" }
|
||||
tokio_io_01 = { version = "0.1", package = "tokio-io" }
|
||||
solana-rayon-threadlimit = { path = "../rayon-threadlimit", version = "1.5.0" }
|
||||
tokio_codec_01 = { version = "0.1", package = "tokio-codec" }
|
||||
solana-rayon-threadlimit = { path = "../rayon-threadlimit", version = "1.5.13" }
|
||||
trees = "0.2.1"
|
||||
|
||||
[dev-dependencies]
|
||||
matches = "0.1.6"
|
||||
num_cpus = "1.13.0"
|
||||
reqwest = { version = "0.10.8", default-features = false, features = ["blocking", "rustls-tls", "json"] }
|
||||
serial_test = "0.4.0"
|
||||
serial_test_derive = "0.4.0"
|
||||
|
@@ -1,3 +1,4 @@
|
||||
#![allow(clippy::integer_arithmetic)]
|
||||
#![feature(test)]
|
||||
|
||||
extern crate test;
|
||||
@@ -28,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 std::collections::VecDeque;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::sync::mpsc::Receiver;
|
||||
use std::sync::Arc;
|
||||
@@ -68,10 +70,10 @@ fn bench_consume_buffered(bencher: &mut Bencher) {
|
||||
let len = 4096;
|
||||
let chunk_size = 1024;
|
||||
let batches = to_packets_chunked(&vec![tx; len], chunk_size);
|
||||
let mut packets = vec![];
|
||||
let mut packets = VecDeque::new();
|
||||
for batch in batches {
|
||||
let batch_len = batch.packets.len();
|
||||
packets.push((batch, vec![0usize; batch_len]));
|
||||
packets.push_back((batch, vec![0usize; batch_len], false));
|
||||
}
|
||||
let (s, _r) = unbounded();
|
||||
// This tests the performance of buffering packets.
|
||||
@@ -81,9 +83,10 @@ fn bench_consume_buffered(bencher: &mut Bencher) {
|
||||
&my_pubkey,
|
||||
&poh_recorder,
|
||||
&mut packets,
|
||||
10_000,
|
||||
None,
|
||||
&s,
|
||||
None::<Box<dyn Fn()>>,
|
||||
None,
|
||||
);
|
||||
});
|
||||
|
||||
|
@@ -1,3 +1,4 @@
|
||||
#![allow(clippy::integer_arithmetic)]
|
||||
#![feature(test)]
|
||||
extern crate solana_ledger;
|
||||
extern crate test;
|
||||
|
@@ -35,9 +35,8 @@ fn broadcast_shreds_bench(bencher: &mut Bencher) {
|
||||
cluster_info.insert_info(contact_info);
|
||||
stakes.insert(id, thread_rng().gen_range(1, NUM_PEERS) as u64);
|
||||
}
|
||||
let stakes = Arc::new(stakes);
|
||||
let cluster_info = Arc::new(cluster_info);
|
||||
let (peers, peers_and_stakes) = get_broadcast_peers(&cluster_info, Some(stakes));
|
||||
let (peers, peers_and_stakes) = get_broadcast_peers(&cluster_info, Some(&stakes));
|
||||
let shreds = Arc::new(shreds);
|
||||
let last_datapoint = Arc::new(AtomicU64::new(0));
|
||||
bencher.iter(move || {
|
||||
|
@@ -6,6 +6,7 @@ extern crate test;
|
||||
use log::*;
|
||||
use solana_core::cluster_info::{ClusterInfo, Node};
|
||||
use solana_core::contact_info::ContactInfo;
|
||||
use solana_core::max_slots::MaxSlots;
|
||||
use solana_core::retransmit_stage::retransmitter;
|
||||
use solana_ledger::entry::Entry;
|
||||
use solana_ledger::genesis_utils::{create_genesis_config, GenesisConfigInfo};
|
||||
@@ -92,6 +93,8 @@ fn bench_retransmitter(bencher: &mut Bencher) {
|
||||
&leader_schedule_cache,
|
||||
cluster_info,
|
||||
packet_receiver,
|
||||
&Arc::new(MaxSlots::default()),
|
||||
None,
|
||||
);
|
||||
|
||||
let mut index = 0;
|
||||
|
@@ -1,3 +1,4 @@
|
||||
#![allow(clippy::integer_arithmetic)]
|
||||
#![feature(test)]
|
||||
|
||||
extern crate test;
|
||||
|
@@ -4,9 +4,14 @@
|
||||
// hash on gossip. Monitor gossip for messages from validators in the --trusted-validators
|
||||
// set and halt the node if a mismatch is detected.
|
||||
|
||||
use crate::cluster_info::{ClusterInfo, MAX_SNAPSHOT_HASHES};
|
||||
use solana_runtime::snapshot_package::{
|
||||
AccountsPackage, AccountsPackageReceiver, AccountsPackageSender,
|
||||
use crate::{
|
||||
cluster_info::{ClusterInfo, MAX_SNAPSHOT_HASHES},
|
||||
snapshot_packager_service::PendingSnapshotPackage,
|
||||
};
|
||||
use rayon::ThreadPool;
|
||||
use solana_runtime::{
|
||||
accounts_db,
|
||||
snapshot_package::{AccountsPackage, AccountsPackagePre, AccountsPackageReceiver},
|
||||
};
|
||||
use solana_sdk::{clock::Slot, hash::Hash, pubkey::Pubkey};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
@@ -27,7 +32,7 @@ pub struct AccountsHashVerifier {
|
||||
impl AccountsHashVerifier {
|
||||
pub fn new(
|
||||
accounts_package_receiver: AccountsPackageReceiver,
|
||||
accounts_package_sender: Option<AccountsPackageSender>,
|
||||
pending_snapshot_package: Option<PendingSnapshotPackage>,
|
||||
exit: &Arc<AtomicBool>,
|
||||
cluster_info: &Arc<ClusterInfo>,
|
||||
trusted_validators: Option<HashSet<Pubkey>>,
|
||||
@@ -41,6 +46,7 @@ impl AccountsHashVerifier {
|
||||
.name("solana-accounts-hash".to_string())
|
||||
.spawn(move || {
|
||||
let mut hashes = vec![];
|
||||
let mut thread_pool_storage = None;
|
||||
loop {
|
||||
if exit.load(Ordering::Relaxed) {
|
||||
break;
|
||||
@@ -48,16 +54,24 @@ impl AccountsHashVerifier {
|
||||
|
||||
match accounts_package_receiver.recv_timeout(Duration::from_secs(1)) {
|
||||
Ok(accounts_package) => {
|
||||
Self::process_accounts_package(
|
||||
if accounts_package.hash_for_testing.is_some()
|
||||
&& thread_pool_storage.is_none()
|
||||
{
|
||||
thread_pool_storage =
|
||||
Some(accounts_db::make_min_priority_thread_pool());
|
||||
}
|
||||
|
||||
Self::process_accounts_package_pre(
|
||||
accounts_package,
|
||||
&cluster_info,
|
||||
&trusted_validators,
|
||||
halt_on_trusted_validators_accounts_hash_mismatch,
|
||||
&accounts_package_sender,
|
||||
&pending_snapshot_package,
|
||||
&mut hashes,
|
||||
&exit,
|
||||
fault_injection_rate_slots,
|
||||
snapshot_interval_slots,
|
||||
thread_pool_storage.as_ref(),
|
||||
);
|
||||
}
|
||||
Err(RecvTimeoutError::Disconnected) => break,
|
||||
@@ -71,29 +85,60 @@ impl AccountsHashVerifier {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn process_accounts_package_pre(
|
||||
accounts_package: AccountsPackagePre,
|
||||
cluster_info: &ClusterInfo,
|
||||
trusted_validators: &Option<HashSet<Pubkey>>,
|
||||
halt_on_trusted_validator_accounts_hash_mismatch: bool,
|
||||
pending_snapshot_package: &Option<PendingSnapshotPackage>,
|
||||
hashes: &mut Vec<(Slot, Hash)>,
|
||||
exit: &Arc<AtomicBool>,
|
||||
fault_injection_rate_slots: u64,
|
||||
snapshot_interval_slots: u64,
|
||||
thread_pool: Option<&ThreadPool>,
|
||||
) {
|
||||
let accounts_package = solana_runtime::snapshot_utils::process_accounts_package_pre(
|
||||
accounts_package,
|
||||
thread_pool,
|
||||
);
|
||||
Self::process_accounts_package(
|
||||
accounts_package,
|
||||
cluster_info,
|
||||
trusted_validators,
|
||||
halt_on_trusted_validator_accounts_hash_mismatch,
|
||||
pending_snapshot_package,
|
||||
hashes,
|
||||
exit,
|
||||
fault_injection_rate_slots,
|
||||
snapshot_interval_slots,
|
||||
);
|
||||
}
|
||||
|
||||
fn process_accounts_package(
|
||||
accounts_package: AccountsPackage,
|
||||
cluster_info: &ClusterInfo,
|
||||
trusted_validators: &Option<HashSet<Pubkey>>,
|
||||
halt_on_trusted_validator_accounts_hash_mismatch: bool,
|
||||
accounts_package_sender: &Option<AccountsPackageSender>,
|
||||
pending_snapshot_package: &Option<PendingSnapshotPackage>,
|
||||
hashes: &mut Vec<(Slot, Hash)>,
|
||||
exit: &Arc<AtomicBool>,
|
||||
fault_injection_rate_slots: u64,
|
||||
snapshot_interval_slots: u64,
|
||||
) {
|
||||
let hash = accounts_package.hash;
|
||||
if fault_injection_rate_slots != 0
|
||||
&& accounts_package.root % fault_injection_rate_slots == 0
|
||||
&& accounts_package.slot % fault_injection_rate_slots == 0
|
||||
{
|
||||
// For testing, publish an invalid hash to gossip.
|
||||
use rand::{thread_rng, Rng};
|
||||
use solana_sdk::hash::extend_and_hash;
|
||||
warn!("inserting fault at slot: {}", accounts_package.root);
|
||||
warn!("inserting fault at slot: {}", accounts_package.slot);
|
||||
let rand = thread_rng().gen_range(0, 10);
|
||||
let hash = extend_and_hash(&accounts_package.hash, &[rand]);
|
||||
hashes.push((accounts_package.root, hash));
|
||||
let hash = extend_and_hash(&hash, &[rand]);
|
||||
hashes.push((accounts_package.slot, hash));
|
||||
} else {
|
||||
hashes.push((accounts_package.root, accounts_package.hash));
|
||||
hashes.push((accounts_package.slot, hash));
|
||||
}
|
||||
|
||||
while hashes.len() > MAX_SNAPSHOT_HASHES {
|
||||
@@ -111,8 +156,8 @@ impl AccountsHashVerifier {
|
||||
}
|
||||
|
||||
if accounts_package.block_height % snapshot_interval_slots == 0 {
|
||||
if let Some(sender) = accounts_package_sender.as_ref() {
|
||||
if sender.send(accounts_package).is_err() {}
|
||||
if let Some(pending_snapshot_package) = pending_snapshot_package.as_ref() {
|
||||
*pending_snapshot_package.lock().unwrap() = Some(accounts_package);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -175,7 +220,7 @@ mod tests {
|
||||
use super::*;
|
||||
use crate::cluster_info::make_accounts_hashes_message;
|
||||
use crate::contact_info::ContactInfo;
|
||||
use solana_runtime::bank_forks::CompressionType;
|
||||
use solana_runtime::bank_forks::ArchiveFormat;
|
||||
use solana_runtime::snapshot_utils::SnapshotVersion;
|
||||
use solana_sdk::{
|
||||
hash::hash,
|
||||
@@ -234,12 +279,12 @@ mod tests {
|
||||
let accounts_package = AccountsPackage {
|
||||
hash: hash(&[i as u8]),
|
||||
block_height: 100 + i as u64,
|
||||
root: 100 + i as u64,
|
||||
slot: 100 + i as u64,
|
||||
slot_deltas: vec![],
|
||||
snapshot_links,
|
||||
tar_output_file: PathBuf::from("."),
|
||||
storages: vec![],
|
||||
compression: CompressionType::Bzip2,
|
||||
archive_format: ArchiveFormat::TarBzip2,
|
||||
snapshot_version: SnapshotVersion::default(),
|
||||
};
|
||||
|
||||
@@ -254,6 +299,9 @@ mod tests {
|
||||
0,
|
||||
100,
|
||||
);
|
||||
// sleep for 1ms to create a newer timestmap for gossip entry
|
||||
// otherwise the timestamp won't be newer.
|
||||
std::thread::sleep(Duration::from_millis(1));
|
||||
}
|
||||
cluster_info.flush_push_queue();
|
||||
let cluster_hashes = cluster_info
|
||||
|
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user