Compare commits
59 Commits
Author | SHA1 | Date | |
---|---|---|---|
38b2957a8e | |||
9eb39df93f | |||
f34ce94347 | |||
5c6411fc06 | |||
3ab428693a | |||
7ffaf2ad29 | |||
a5e4a1f2d8 | |||
6ae99848f4 | |||
5342b3a53f | |||
50a3c98d21 | |||
ca2b7a2c5b | |||
e900623817 | |||
a46e953ebb | |||
3bbc51a6ae | |||
7ae2464cf3 | |||
58a36ce484 | |||
61bd9e6a28 | |||
495ab631d6 | |||
93906847f9 | |||
70f4c4a974 | |||
ad6078da2d | |||
2a617f2d07 | |||
6af3c6ecbc | |||
a5938e5a21 | |||
8eaa8d8788 | |||
51940eec9b | |||
d1e16b2bc4 | |||
97051c87b4 | |||
2521913654 | |||
803c87b4bd | |||
8ff9ee3a06 | |||
25bf2c6062 | |||
fc80b77fc4 | |||
5a7707362c | |||
0102ee3fa9 | |||
d29cb19a73 | |||
1cc66f0cd7 | |||
e2cfc513eb | |||
8115a962e9 | |||
0c804e2ef2 | |||
cbe36a7a63 | |||
174825fecf | |||
c7e80bebdc | |||
57abc370fa | |||
42ab421a87 | |||
9ab27291f5 | |||
27e4e9cb8d | |||
b0cf65dfc8 | |||
bfc97c682c | |||
231c9b25d1 | |||
f536d805ed | |||
cb6e7426a4 | |||
71f391ae04 | |||
12bf34f059 | |||
a3a14d6b5b | |||
5e22db017a | |||
2cd09957b4 | |||
d1ec6c0b8b | |||
7722723400 |
3
.buildkite/env/secrets.ejson
vendored
3
.buildkite/env/secrets.ejson
vendored
@ -8,8 +8,5 @@
|
||||
"INFLUX_DATABASE": "EJ[1:yGpTmjdbyjW2kjgIHkFoJv7Ue7EbUvUbqHyw6anGgWg=:5KI9WBkXx3R/W4m256mU5MJOE7N8aAT9:Cb8QFELZ9I60t5zhJ9h55Kcs]",
|
||||
"INFLUX_PASSWORD": "EJ[1:yGpTmjdbyjW2kjgIHkFoJv7Ue7EbUvUbqHyw6anGgWg=:hQRMpLCrav+OYkNphkeM4hagdVoZv5Iw:AUO76rr6+gF1OLJA8ZLSG8wHKXgYCPNk6gRCV8rBhZBJ4KwDaxpvOhMl7bxxXG6jol7v4aRa/Lk=]",
|
||||
"INFLUX_USERNAME": "EJ[1:yGpTmjdbyjW2kjgIHkFoJv7Ue7EbUvUbqHyw6anGgWg=:R7BNmQjfeqoGDAFTJu9bYTGHol2NgnYN:Q2tOT/EBcFvhFk+DKLKmVU7tLCpVC3Ui]",
|
||||
"SOLANA_INSTALL_UPDATE_MANIFEST_KEYPAIR_x86_64_unknown_linux_gnu": "EJ[1:yGpTmjdbyjW2kjgIHkFoJv7Ue7EbUvUbqHyw6anGgWg=:Egc2dMrHDU0NcZ71LwGv/V66shUhwYUE:04VoIb8CKy7KYhQ5W4cEW9SDKZltxWBL5Hob106lMBbUOD/yUvKYcG3Ep8JfTMwO3K8zowW5HpU/IdGoilX0XWLiJJ6t+p05WWK0TA16nOEtwrEG+UK8wm3sN+xCO20i4jDhpNpgg3FYFHT5rKTHW8+zaBTNUX/SFxkN67Lm+92IM28CXYE43SU1WV6H99hGFFVpTK5JVM3JuYU1ex/dHRE+xCzTr4MYUB/F+nGoNFW8HUDV/y0e1jxT9to3x0SmnytEEuk+5RUzFuEt9cKNFeNml3fOCi4qL+sfj/Y5pjH9xDiUxsvH/8NL35jbLP244aFHgWcp]",
|
||||
"SOLANA_INSTALL_UPDATE_MANIFEST_KEYPAIR_x86_64_apple_darwin": "EJ[1:yGpTmjdbyjW2kjgIHkFoJv7Ue7EbUvUbqHyw6anGgWg=:NeOxSoWCvXB9AL4H6OK26l/7bmsKd/oz:Ijfoxtvk2CHlN1ZXHup3Gg/914kbbAkEGWJfvozA8UIe+aUzUObMyTrKkVOeNAH8Q8YH9tNzk7RRnrTcpnzeCCBLlWcVEeruMxHox3mPRzmSeDLxtbzCl9VePlRO3T7jg90K5hW+ZAkd5J/WJNzpAcmr93ts/of3MbvGHSujId/efCTzJEcP6JInnBb8Vrj7TlgKbzUlnqpq1+NjYPSXN3maKa9pKeo2JWxZlGBMoy6QWUUY5GbYEylw9smwh1LJcHZjlaZNMuOl4gNKtaSr38IXQkAXaRUJDPAmPras00YObKzXU8RkTrP4EoP/jx5LPR7f]",
|
||||
"SOLANA_INSTALL_UPDATE_MANIFEST_KEYPAIR_x86_64_pc_windows_msvc": "EJ[1:yGpTmjdbyjW2kjgIHkFoJv7Ue7EbUvUbqHyw6anGgWg=:7t+56twjW+jR7fpFNNeRFLPd7E4lbmyN:JuviDpkQrfVcNUGRGsa2e/UhvH6tTYyk1s4cHHE5xZH1NByL7Kpqx36VG/+o1AUGEeSQdsBnKgzYdMoFYbO8o50DoRPc86QIEVXCupD6J9avxLFtQgOWgJp+/mCdUVXlqXiFs/vQgS/L4psrcKdF6WHd77BeUr6ll8DjH+9m5FC9Rcai2pXno6VbPpunHQ0oUdYzhFR64+LiRacBaefQ9igZ+nSEWDLqbaZSyfm9viWkijoVFTq8gAgdXXEh7g0QdxVE5T6bPristJhT6jWBhWunPUCDNFFErWIsbRGctepl4pbCWqh2hNTw9btSgVfeY6uGCOsdy9E=]"
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
os:
|
||||
- osx
|
||||
- windows
|
||||
|
||||
language: rust
|
||||
rust:
|
||||
|
711
Cargo.lock
generated
711
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "solana-archiver-lib"
|
||||
version = "1.0.6"
|
||||
version = "1.0.9"
|
||||
description = "Solana Archiver Library"
|
||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
@ -15,22 +15,22 @@ ed25519-dalek = "=1.0.0-pre.1"
|
||||
log = "0.4.8"
|
||||
rand = "0.6.5"
|
||||
rand_chacha = "0.1.1"
|
||||
solana-client = { path = "../client", version = "1.0.6" }
|
||||
solana-storage-program = { path = "../programs/storage", version = "1.0.6" }
|
||||
solana-client = { path = "../client", version = "1.0.9" }
|
||||
solana-storage-program = { path = "../programs/storage", version = "1.0.9" }
|
||||
thiserror = "1.0"
|
||||
serde = "1.0.104"
|
||||
serde_json = "1.0.46"
|
||||
serde_derive = "1.0.103"
|
||||
solana-net-utils = { path = "../net-utils", version = "1.0.6" }
|
||||
solana-chacha = { path = "../chacha", version = "1.0.6" }
|
||||
solana-chacha-sys = { path = "../chacha-sys", version = "1.0.6" }
|
||||
solana-ledger = { path = "../ledger", version = "1.0.6" }
|
||||
solana-logger = { path = "../logger", version = "1.0.6" }
|
||||
solana-perf = { path = "../perf", version = "1.0.6" }
|
||||
solana-sdk = { path = "../sdk", version = "1.0.6" }
|
||||
solana-core = { path = "../core", version = "1.0.6" }
|
||||
solana-archiver-utils = { path = "../archiver-utils", version = "1.0.6" }
|
||||
solana-metrics = { path = "../metrics", version = "1.0.6" }
|
||||
solana-net-utils = { path = "../net-utils", version = "1.0.9" }
|
||||
solana-chacha = { path = "../chacha", version = "1.0.9" }
|
||||
solana-chacha-sys = { path = "../chacha-sys", version = "1.0.9" }
|
||||
solana-ledger = { path = "../ledger", version = "1.0.9" }
|
||||
solana-logger = { path = "../logger", version = "1.0.9" }
|
||||
solana-perf = { path = "../perf", version = "1.0.9" }
|
||||
solana-sdk = { path = "../sdk", version = "1.0.9" }
|
||||
solana-core = { path = "../core", version = "1.0.9" }
|
||||
solana-archiver-utils = { path = "../archiver-utils", version = "1.0.9" }
|
||||
solana-metrics = { path = "../metrics", version = "1.0.9" }
|
||||
|
||||
[dev-dependencies]
|
||||
hex = "0.4.0"
|
||||
|
@ -235,6 +235,7 @@ impl Archiver {
|
||||
shred_forward_sockets,
|
||||
repair_socket.clone(),
|
||||
&shred_fetch_sender,
|
||||
None,
|
||||
&exit,
|
||||
);
|
||||
let (slot_sender, slot_receiver) = channel();
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "solana-archiver-utils"
|
||||
version = "1.0.6"
|
||||
version = "1.0.9"
|
||||
description = "Solana Archiver Utils"
|
||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
@ -11,12 +11,12 @@ edition = "2018"
|
||||
[dependencies]
|
||||
log = "0.4.8"
|
||||
rand = "0.6.5"
|
||||
solana-chacha = { path = "../chacha", version = "1.0.6" }
|
||||
solana-chacha-sys = { path = "../chacha-sys", version = "1.0.6" }
|
||||
solana-ledger = { path = "../ledger", version = "1.0.6" }
|
||||
solana-logger = { path = "../logger", version = "1.0.6" }
|
||||
solana-perf = { path = "../perf", version = "1.0.6" }
|
||||
solana-sdk = { path = "../sdk", version = "1.0.6" }
|
||||
solana-chacha = { path = "../chacha", version = "1.0.9" }
|
||||
solana-chacha-sys = { path = "../chacha-sys", version = "1.0.9" }
|
||||
solana-ledger = { path = "../ledger", version = "1.0.9" }
|
||||
solana-logger = { path = "../logger", version = "1.0.9" }
|
||||
solana-perf = { path = "../perf", version = "1.0.9" }
|
||||
solana-sdk = { path = "../sdk", version = "1.0.9" }
|
||||
|
||||
[dev-dependencies]
|
||||
hex = "0.4.0"
|
||||
|
@ -2,7 +2,7 @@
|
||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
edition = "2018"
|
||||
name = "solana-archiver"
|
||||
version = "1.0.6"
|
||||
version = "1.0.9"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
@ -10,11 +10,11 @@ homepage = "https://solana.com/"
|
||||
[dependencies]
|
||||
clap = "2.33.0"
|
||||
console = "0.9.2"
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.0.6" }
|
||||
solana-core = { path = "../core", version = "1.0.6" }
|
||||
solana-logger = { path = "../logger", version = "1.0.6" }
|
||||
solana-metrics = { path = "../metrics", version = "1.0.6" }
|
||||
solana-archiver-lib = { path = "../archiver-lib", version = "1.0.6" }
|
||||
solana-net-utils = { path = "../net-utils", version = "1.0.6" }
|
||||
solana-sdk = { path = "../sdk", version = "1.0.6" }
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.0.9" }
|
||||
solana-core = { path = "../core", version = "1.0.9" }
|
||||
solana-logger = { path = "../logger", version = "1.0.9" }
|
||||
solana-metrics = { path = "../metrics", version = "1.0.9" }
|
||||
solana-archiver-lib = { path = "../archiver-lib", version = "1.0.9" }
|
||||
solana-net-utils = { path = "../net-utils", version = "1.0.9" }
|
||||
solana-sdk = { path = "../sdk", version = "1.0.9" }
|
||||
|
||||
|
@ -28,7 +28,7 @@ fn main() {
|
||||
.arg(
|
||||
Arg::with_name("identity_keypair")
|
||||
.short("i")
|
||||
.long("identity-keypair")
|
||||
.long("identity")
|
||||
.value_name("PATH")
|
||||
.takes_value(true)
|
||||
.validator(is_keypair_or_ask_keyword)
|
||||
|
@ -2,7 +2,7 @@
|
||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
edition = "2018"
|
||||
name = "solana-banking-bench"
|
||||
version = "1.0.6"
|
||||
version = "1.0.9"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
@ -10,11 +10,11 @@ homepage = "https://solana.com/"
|
||||
[dependencies]
|
||||
log = "0.4.6"
|
||||
rayon = "1.2.0"
|
||||
solana-core = { path = "../core", version = "1.0.6" }
|
||||
solana-ledger = { path = "../ledger", version = "1.0.6" }
|
||||
solana-logger = { path = "../logger", version = "1.0.6" }
|
||||
solana-runtime = { path = "../runtime", version = "1.0.6" }
|
||||
solana-measure = { path = "../measure", version = "1.0.6" }
|
||||
solana-sdk = { path = "../sdk", version = "1.0.6" }
|
||||
solana-core = { path = "../core", version = "1.0.9" }
|
||||
solana-ledger = { path = "../ledger", version = "1.0.9" }
|
||||
solana-logger = { path = "../logger", version = "1.0.9" }
|
||||
solana-runtime = { path = "../runtime", version = "1.0.9" }
|
||||
solana-measure = { path = "../measure", version = "1.0.9" }
|
||||
solana-sdk = { path = "../sdk", version = "1.0.9" }
|
||||
rand = "0.6.5"
|
||||
crossbeam-channel = "0.3"
|
||||
|
@ -2,7 +2,7 @@
|
||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
edition = "2018"
|
||||
name = "solana-bench-exchange"
|
||||
version = "1.0.6"
|
||||
version = "1.0.9"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
@ -18,17 +18,17 @@ rand = "0.6.5"
|
||||
rayon = "1.2.0"
|
||||
serde_json = "1.0.46"
|
||||
serde_yaml = "0.8.11"
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.0.6" }
|
||||
solana-core = { path = "../core", version = "1.0.6" }
|
||||
solana-genesis = { path = "../genesis", version = "1.0.6" }
|
||||
solana-client = { path = "../client", version = "1.0.6" }
|
||||
solana-faucet = { path = "../faucet", version = "1.0.6" }
|
||||
solana-exchange-program = { path = "../programs/exchange", version = "1.0.6" }
|
||||
solana-logger = { path = "../logger", version = "1.0.6" }
|
||||
solana-metrics = { path = "../metrics", version = "1.0.6" }
|
||||
solana-net-utils = { path = "../net-utils", version = "1.0.6" }
|
||||
solana-runtime = { path = "../runtime", version = "1.0.6" }
|
||||
solana-sdk = { path = "../sdk", version = "1.0.6" }
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.0.9" }
|
||||
solana-core = { path = "../core", version = "1.0.9" }
|
||||
solana-genesis = { path = "../genesis", version = "1.0.9" }
|
||||
solana-client = { path = "../client", version = "1.0.9" }
|
||||
solana-faucet = { path = "../faucet", version = "1.0.9" }
|
||||
solana-exchange-program = { path = "../programs/exchange", version = "1.0.9" }
|
||||
solana-logger = { path = "../logger", version = "1.0.9" }
|
||||
solana-metrics = { path = "../metrics", version = "1.0.9" }
|
||||
solana-net-utils = { path = "../net-utils", version = "1.0.9" }
|
||||
solana-runtime = { path = "../runtime", version = "1.0.9" }
|
||||
solana-sdk = { path = "../sdk", version = "1.0.9" }
|
||||
|
||||
[dev-dependencies]
|
||||
solana-local-cluster = { path = "../local-cluster", version = "1.0.6" }
|
||||
solana-local-cluster = { path = "../local-cluster", version = "1.0.9" }
|
||||
|
@ -2,14 +2,14 @@
|
||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
edition = "2018"
|
||||
name = "solana-bench-streamer"
|
||||
version = "1.0.6"
|
||||
version = "1.0.9"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
|
||||
[dependencies]
|
||||
clap = "2.33.0"
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.0.6" }
|
||||
solana-core = { path = "../core", version = "1.0.6" }
|
||||
solana-logger = { path = "../logger", version = "1.0.6" }
|
||||
solana-net-utils = { path = "../net-utils", version = "1.0.6" }
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.0.9" }
|
||||
solana-core = { path = "../core", version = "1.0.9" }
|
||||
solana-logger = { path = "../logger", version = "1.0.9" }
|
||||
solana-net-utils = { path = "../net-utils", version = "1.0.9" }
|
||||
|
@ -2,7 +2,7 @@
|
||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
edition = "2018"
|
||||
name = "solana-bench-tps"
|
||||
version = "1.0.6"
|
||||
version = "1.0.9"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
@ -14,24 +14,24 @@ log = "0.4.8"
|
||||
rayon = "1.2.0"
|
||||
serde_json = "1.0.46"
|
||||
serde_yaml = "0.8.11"
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.0.6" }
|
||||
solana-core = { path = "../core", version = "1.0.6" }
|
||||
solana-genesis = { path = "../genesis", version = "1.0.6" }
|
||||
solana-client = { path = "../client", version = "1.0.6" }
|
||||
solana-faucet = { path = "../faucet", version = "1.0.6" }
|
||||
solana-librapay = { path = "../programs/librapay", version = "1.0.6", optional = true }
|
||||
solana-logger = { path = "../logger", version = "1.0.6" }
|
||||
solana-metrics = { path = "../metrics", version = "1.0.6" }
|
||||
solana-measure = { path = "../measure", version = "1.0.6" }
|
||||
solana-net-utils = { path = "../net-utils", version = "1.0.6" }
|
||||
solana-runtime = { path = "../runtime", version = "1.0.6" }
|
||||
solana-sdk = { path = "../sdk", version = "1.0.6" }
|
||||
solana-move-loader-program = { path = "../programs/move_loader", version = "1.0.6", optional = true }
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.0.9" }
|
||||
solana-core = { path = "../core", version = "1.0.9" }
|
||||
solana-genesis = { path = "../genesis", version = "1.0.9" }
|
||||
solana-client = { path = "../client", version = "1.0.9" }
|
||||
solana-faucet = { path = "../faucet", version = "1.0.9" }
|
||||
solana-librapay = { path = "../programs/librapay", version = "1.0.9", optional = true }
|
||||
solana-logger = { path = "../logger", version = "1.0.9" }
|
||||
solana-metrics = { path = "../metrics", version = "1.0.9" }
|
||||
solana-measure = { path = "../measure", version = "1.0.9" }
|
||||
solana-net-utils = { path = "../net-utils", version = "1.0.9" }
|
||||
solana-runtime = { path = "../runtime", version = "1.0.9" }
|
||||
solana-sdk = { path = "../sdk", version = "1.0.9" }
|
||||
solana-move-loader-program = { path = "../programs/move_loader", version = "1.0.9", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
serial_test = "0.3.2"
|
||||
serial_test_derive = "0.4.0"
|
||||
solana-local-cluster = { path = "../local-cluster", version = "1.0.6" }
|
||||
solana-local-cluster = { path = "../local-cluster", version = "1.0.9" }
|
||||
|
||||
[features]
|
||||
move = ["solana-librapay", "solana-move-loader-program"]
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "solana-chacha-cuda"
|
||||
version = "1.0.6"
|
||||
version = "1.0.9"
|
||||
description = "Solana Chacha Cuda APIs"
|
||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
@ -10,12 +10,12 @@ edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
log = "0.4.8"
|
||||
solana-archiver-utils = { path = "../archiver-utils", version = "1.0.6" }
|
||||
solana-chacha = { path = "../chacha", version = "1.0.6" }
|
||||
solana-ledger = { path = "../ledger", version = "1.0.6" }
|
||||
solana-logger = { path = "../logger", version = "1.0.6" }
|
||||
solana-perf = { path = "../perf", version = "1.0.6" }
|
||||
solana-sdk = { path = "../sdk", version = "1.0.6" }
|
||||
solana-archiver-utils = { path = "../archiver-utils", version = "1.0.9" }
|
||||
solana-chacha = { path = "../chacha", version = "1.0.9" }
|
||||
solana-ledger = { path = "../ledger", version = "1.0.9" }
|
||||
solana-logger = { path = "../logger", version = "1.0.9" }
|
||||
solana-perf = { path = "../perf", version = "1.0.9" }
|
||||
solana-sdk = { path = "../sdk", version = "1.0.9" }
|
||||
|
||||
[dev-dependencies]
|
||||
hex-literal = "0.2.1"
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "solana-chacha-sys"
|
||||
version = "1.0.6"
|
||||
version = "1.0.9"
|
||||
description = "Solana chacha-sys"
|
||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "solana-chacha"
|
||||
version = "1.0.6"
|
||||
version = "1.0.9"
|
||||
description = "Solana Chacha APIs"
|
||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
@ -12,11 +12,11 @@ edition = "2018"
|
||||
log = "0.4.8"
|
||||
rand = "0.6.5"
|
||||
rand_chacha = "0.1.1"
|
||||
solana-chacha-sys = { path = "../chacha-sys", version = "1.0.6" }
|
||||
solana-ledger = { path = "../ledger", version = "1.0.6" }
|
||||
solana-logger = { path = "../logger", version = "1.0.6" }
|
||||
solana-perf = { path = "../perf", version = "1.0.6" }
|
||||
solana-sdk = { path = "../sdk", version = "1.0.6" }
|
||||
solana-chacha-sys = { path = "../chacha-sys", version = "1.0.9" }
|
||||
solana-ledger = { path = "../ledger", version = "1.0.9" }
|
||||
solana-logger = { path = "../logger", version = "1.0.9" }
|
||||
solana-perf = { path = "../perf", version = "1.0.9" }
|
||||
solana-sdk = { path = "../sdk", version = "1.0.9" }
|
||||
|
||||
[dev-dependencies]
|
||||
hex-literal = "0.2.1"
|
||||
|
@ -29,7 +29,7 @@ steps:
|
||||
timeout_in_minutes: 20
|
||||
- command: ". ci/rust-version.sh; ci/docker-run.sh $$rust_stable_docker_image ci/test-local-cluster.sh"
|
||||
name: "local-cluster"
|
||||
timeout_in_minutes: 30
|
||||
timeout_in_minutes: 45
|
||||
artifact_paths: "log-*.txt"
|
||||
- command: ". ci/rust-version.sh; ci/docker-run.sh $$rust_nightly_docker_image ci/test-coverage.sh"
|
||||
name: "coverage"
|
||||
|
@ -1,4 +1,4 @@
|
||||
FROM solanalabs/rust:1.41.1
|
||||
FROM solanalabs/rust:1.42.0
|
||||
ARG date
|
||||
|
||||
RUN set -x \
|
||||
|
@ -15,6 +15,8 @@ To update the pinned version:
|
||||
1. Run `ci/docker-rust-nightly/build.sh` to rebuild the nightly image locally,
|
||||
or potentially `ci/docker-rust-nightly/build.sh YYYY-MM-DD` if there's a
|
||||
specific YYYY-MM-DD that is desired (default is today's build).
|
||||
Check https://rust-lang.github.io/rustup-components-history/ for build
|
||||
status
|
||||
1. Update `ci/rust-version.sh` to reflect the new nightly `YYY-MM-DD`
|
||||
1. Run `SOLANA_DOCKER_RUN_NOSETUID=1 ci/docker-run.sh --nopull solanalabs/rust-nightly:YYYY-MM-DD ci/test-coverage.sh`
|
||||
to confirm the new nightly image builds. Fix any issues as needed
|
||||
|
@ -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.41.1
|
||||
FROM rust:1.42.0
|
||||
|
||||
# Add Google Protocol Buffers for Libra's metrics library.
|
||||
ENV PROTOC_VERSION 3.8.0
|
||||
|
@ -178,7 +178,7 @@ startNodes() {
|
||||
|
||||
(
|
||||
set -x
|
||||
$solana_cli --keypair config/bootstrap-validator/identity-keypair.json \
|
||||
$solana_cli --keypair config/bootstrap-validator/identity.json \
|
||||
--url http://127.0.0.1:8899 genesis-hash
|
||||
) | tee genesis-hash.log
|
||||
maybeExpectedGenesisHash="--expected-genesis-hash $(tail -n1 genesis-hash.log)"
|
||||
|
@ -45,7 +45,7 @@ linux)
|
||||
TARGET=x86_64-unknown-linux-gnu
|
||||
;;
|
||||
windows)
|
||||
TARGET=x86_64-pc-windows-msvc
|
||||
TARGET=x86_64-pc-windows-gnu
|
||||
;;
|
||||
*)
|
||||
echo CI_OS_NAME unset
|
||||
@ -73,15 +73,6 @@ echo --- Creating release tarball
|
||||
source ci/rust-version.sh stable
|
||||
scripts/cargo-install-all.sh +"$rust_stable" --use-move solana-release
|
||||
|
||||
# Reduce the Windows archive size until
|
||||
# https://github.com/appveyor/ci/issues/2997 is fixed
|
||||
if [[ -n $APPVEYOR ]]; then
|
||||
rm -f \
|
||||
solana-release/bin/solana-validator.exe \
|
||||
solana-release/bin/solana-bench-exchange.exe \
|
||||
|
||||
fi
|
||||
|
||||
tar cvf solana-release-$TARGET.tar solana-release
|
||||
bzip2 solana-release-$TARGET.tar
|
||||
cp solana-release/bin/solana-install-init solana-install-init-$TARGET
|
||||
|
@ -16,13 +16,13 @@
|
||||
if [[ -n $RUST_STABLE_VERSION ]]; then
|
||||
stable_version="$RUST_STABLE_VERSION"
|
||||
else
|
||||
stable_version=1.41.1
|
||||
stable_version=1.42.0
|
||||
fi
|
||||
|
||||
if [[ -n $RUST_NIGHTLY_VERSION ]]; then
|
||||
nightly_version="$RUST_NIGHTLY_VERSION"
|
||||
else
|
||||
nightly_version=2020-02-27
|
||||
nightly_version=2020-03-12
|
||||
fi
|
||||
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "solana-clap-utils"
|
||||
version = "1.0.6"
|
||||
version = "1.0.9"
|
||||
description = "Solana utilities for the clap"
|
||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
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.0.6" }
|
||||
solana-sdk = { path = "../sdk", version = "1.0.6" }
|
||||
solana-remote-wallet = { path = "../remote-wallet", version = "1.0.9" }
|
||||
solana-sdk = { path = "../sdk", version = "1.0.9" }
|
||||
thiserror = "1.0.11"
|
||||
tiny-bip39 = "0.7.0"
|
||||
url = "2.1.0"
|
||||
|
@ -1,6 +1,6 @@
|
||||
use crate::keypair::{
|
||||
keypair_from_seed_phrase, pubkey_from_path, signer_from_path, ASK_KEYWORD,
|
||||
SKIP_SEED_PHRASE_VALIDATION_ARG,
|
||||
keypair_from_seed_phrase, pubkey_from_path, resolve_signer_from_path, signer_from_path,
|
||||
ASK_KEYWORD, SKIP_SEED_PHRASE_VALIDATION_ARG,
|
||||
};
|
||||
use chrono::DateTime;
|
||||
use clap::ArgMatches;
|
||||
@ -129,6 +129,35 @@ pub fn pubkey_of_signer(
|
||||
}
|
||||
}
|
||||
|
||||
pub fn pubkeys_of_multiple_signers(
|
||||
matches: &ArgMatches<'_>,
|
||||
name: &str,
|
||||
wallet_manager: Option<&Arc<RemoteWalletManager>>,
|
||||
) -> Result<Option<Vec<Pubkey>>, Box<dyn std::error::Error>> {
|
||||
if let Some(pubkey_matches) = matches.values_of(name) {
|
||||
let mut pubkeys: Vec<Pubkey> = vec![];
|
||||
for signer in pubkey_matches {
|
||||
pubkeys.push(pubkey_from_path(matches, signer, name, wallet_manager)?);
|
||||
}
|
||||
Ok(Some(pubkeys))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn resolve_signer(
|
||||
matches: &ArgMatches<'_>,
|
||||
name: &str,
|
||||
wallet_manager: Option<&Arc<RemoteWalletManager>>,
|
||||
) -> Result<Option<String>, Box<dyn std::error::Error>> {
|
||||
Ok(resolve_signer_from_path(
|
||||
matches,
|
||||
matches.value_of(name).unwrap(),
|
||||
name,
|
||||
wallet_manager,
|
||||
)?)
|
||||
}
|
||||
|
||||
pub fn lamports_of_sol(matches: &ArgMatches<'_>, name: &str) -> Option<u64> {
|
||||
value_of(matches, name).map(sol_to_lamports)
|
||||
}
|
||||
|
@ -46,18 +46,27 @@ pub fn is_pubkey_or_keypair(string: String) -> Result<(), String> {
|
||||
is_pubkey(string.clone()).or_else(|_| is_keypair(string))
|
||||
}
|
||||
|
||||
// Return an error if string cannot be parsed as pubkey or keypair file or keypair ask keyword
|
||||
pub fn is_pubkey_or_keypair_or_ask_keyword(string: String) -> Result<(), String> {
|
||||
is_pubkey(string.clone()).or_else(|_| is_keypair_or_ask_keyword(string))
|
||||
}
|
||||
|
||||
pub fn is_valid_signer(string: String) -> Result<(), String> {
|
||||
// Return an error if string cannot be parsed as a pubkey string, or a valid Signer that can
|
||||
// produce a pubkey()
|
||||
pub fn is_valid_pubkey(string: String) -> Result<(), String> {
|
||||
match parse_keypair_path(&string) {
|
||||
KeypairUrl::Filepath(path) => is_keypair(path),
|
||||
_ => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
// Return an error if string cannot be parsed as a valid Signer. This is an alias of
|
||||
// `is_valid_pubkey`, and does accept pubkey strings, even though a Pubkey is not by itself
|
||||
// sufficient to sign a transaction.
|
||||
//
|
||||
// In the current offline-signing implementation, a pubkey is the valid input for a signer field
|
||||
// when paired with an offline `--signer` argument to provide a Presigner (pubkey + signature).
|
||||
// Clap validators can't check multiple fields at once, so the verification that a `--signer` is
|
||||
// also provided and correct happens in parsing, not in validation.
|
||||
pub fn is_valid_signer(string: String) -> Result<(), String> {
|
||||
is_valid_pubkey(string)
|
||||
}
|
||||
|
||||
// Return an error if string cannot be parsed as pubkey=signature string
|
||||
pub fn is_pubkey_sig(string: String) -> Result<(), String> {
|
||||
let mut signer = string.split('=');
|
||||
|
@ -1,4 +1,8 @@
|
||||
use crate::{input_parsers::pubkeys_sigs_of, offline::SIGNER_ARG, ArgConstant};
|
||||
use crate::{
|
||||
input_parsers::pubkeys_sigs_of,
|
||||
offline::{SIGNER_ARG, SIGN_ONLY_ARG},
|
||||
ArgConstant,
|
||||
};
|
||||
use bip39::{Language, Mnemonic, Seed};
|
||||
use clap::ArgMatches;
|
||||
use rpassword::prompt_password_stderr;
|
||||
@ -10,7 +14,7 @@ use solana_sdk::{
|
||||
pubkey::Pubkey,
|
||||
signature::{
|
||||
keypair_from_seed, keypair_from_seed_phrase_and_passphrase, read_keypair,
|
||||
read_keypair_file, Keypair, Presigner, Signature, Signer,
|
||||
read_keypair_file, Keypair, NullSigner, Presigner, Signature, Signer,
|
||||
},
|
||||
};
|
||||
use std::{
|
||||
@ -101,10 +105,12 @@ pub fn signer_from_path(
|
||||
.and_then(|presigners| presigner_from_pubkey_sigs(&pubkey, presigners));
|
||||
if let Some(presigner) = presigner {
|
||||
Ok(Box::new(presigner))
|
||||
} else if matches.is_present(SIGN_ONLY_ARG.name) {
|
||||
Ok(Box::new(NullSigner::new(&pubkey)))
|
||||
} else {
|
||||
Err(std::io::Error::new(
|
||||
std::io::ErrorKind::Other,
|
||||
"missing signature for supplied pubkey".to_string(),
|
||||
format!("missing signature for supplied pubkey: {}", pubkey),
|
||||
)
|
||||
.into())
|
||||
}
|
||||
@ -124,6 +130,51 @@ pub fn pubkey_from_path(
|
||||
}
|
||||
}
|
||||
|
||||
pub fn resolve_signer_from_path(
|
||||
matches: &ArgMatches,
|
||||
path: &str,
|
||||
keypair_name: &str,
|
||||
wallet_manager: Option<&Arc<RemoteWalletManager>>,
|
||||
) -> Result<Option<String>, Box<dyn error::Error>> {
|
||||
match parse_keypair_path(path) {
|
||||
KeypairUrl::Ask => {
|
||||
let skip_validation = matches.is_present(SKIP_SEED_PHRASE_VALIDATION_ARG.name);
|
||||
// This method validates the seed phrase, but returns `None` because there is no path
|
||||
// on disk or to a device
|
||||
keypair_from_seed_phrase(keypair_name, skip_validation, false).map(|_| None)
|
||||
}
|
||||
KeypairUrl::Filepath(path) => match read_keypair_file(&path) {
|
||||
Err(e) => Err(std::io::Error::new(
|
||||
std::io::ErrorKind::Other,
|
||||
format!("could not find keypair file: {} error: {}", path, e),
|
||||
)
|
||||
.into()),
|
||||
Ok(_) => Ok(Some(path.to_string())),
|
||||
},
|
||||
KeypairUrl::Stdin => {
|
||||
let mut stdin = std::io::stdin();
|
||||
// This method validates the keypair from stdin, but returns `None` because there is no
|
||||
// path on disk or to a device
|
||||
read_keypair(&mut stdin).map(|_| None)
|
||||
}
|
||||
KeypairUrl::Usb(path) => {
|
||||
if let Some(wallet_manager) = wallet_manager {
|
||||
let path = generate_remote_keypair(
|
||||
path,
|
||||
wallet_manager,
|
||||
matches.is_present("confirm_key"),
|
||||
keypair_name,
|
||||
)
|
||||
.map(|keypair| keypair.path)?;
|
||||
Ok(Some(path))
|
||||
} else {
|
||||
Err(RemoteWalletError::NoDeviceFound.into())
|
||||
}
|
||||
}
|
||||
_ => Ok(Some(path.to_string())),
|
||||
}
|
||||
}
|
||||
|
||||
// Keyword used to indicate that the user should be asked for a keypair seed phrase
|
||||
pub const ASK_KEYWORD: &str = "ASK";
|
||||
|
||||
|
@ -3,7 +3,7 @@ authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
edition = "2018"
|
||||
name = "solana-cli-config"
|
||||
description = "Blockchain, Rebuilt for Scale"
|
||||
version = "1.0.6"
|
||||
version = "1.0.9"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
|
@ -3,7 +3,7 @@ authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
edition = "2018"
|
||||
name = "solana-cli"
|
||||
description = "Blockchain, Rebuilt for Scale"
|
||||
version = "1.0.6"
|
||||
version = "1.0.9"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
@ -26,28 +26,28 @@ reqwest = { version = "0.10.1", default-features = false, features = ["blocking"
|
||||
serde = "1.0.104"
|
||||
serde_derive = "1.0.103"
|
||||
serde_json = "1.0.46"
|
||||
solana-budget-program = { path = "../programs/budget", version = "1.0.6" }
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.0.6" }
|
||||
solana-cli-config = { path = "../cli-config", version = "1.0.6" }
|
||||
solana-client = { path = "../client", version = "1.0.6" }
|
||||
solana-config-program = { path = "../programs/config", version = "1.0.6" }
|
||||
solana-faucet = { path = "../faucet", version = "1.0.6" }
|
||||
solana-logger = { path = "../logger", version = "1.0.6" }
|
||||
solana-net-utils = { path = "../net-utils", version = "1.0.6" }
|
||||
solana-remote-wallet = { path = "../remote-wallet", version = "1.0.6" }
|
||||
solana-runtime = { path = "../runtime", version = "1.0.6" }
|
||||
solana-sdk = { path = "../sdk", version = "1.0.6" }
|
||||
solana-stake-program = { path = "../programs/stake", version = "1.0.6" }
|
||||
solana-storage-program = { path = "../programs/storage", version = "1.0.6" }
|
||||
solana-vote-program = { path = "../programs/vote", version = "1.0.6" }
|
||||
solana-vote-signer = { path = "../vote-signer", version = "1.0.6" }
|
||||
solana-budget-program = { path = "../programs/budget", version = "1.0.9" }
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.0.9" }
|
||||
solana-cli-config = { path = "../cli-config", version = "1.0.9" }
|
||||
solana-client = { path = "../client", version = "1.0.9" }
|
||||
solana-config-program = { path = "../programs/config", version = "1.0.9" }
|
||||
solana-faucet = { path = "../faucet", version = "1.0.9" }
|
||||
solana-logger = { path = "../logger", version = "1.0.9" }
|
||||
solana-net-utils = { path = "../net-utils", version = "1.0.9" }
|
||||
solana-remote-wallet = { path = "../remote-wallet", version = "1.0.9" }
|
||||
solana-runtime = { path = "../runtime", version = "1.0.9" }
|
||||
solana-sdk = { path = "../sdk", version = "1.0.9" }
|
||||
solana-stake-program = { path = "../programs/stake", version = "1.0.9" }
|
||||
solana-storage-program = { path = "../programs/storage", version = "1.0.9" }
|
||||
solana-vote-program = { path = "../programs/vote", version = "1.0.9" }
|
||||
solana-vote-signer = { path = "../vote-signer", version = "1.0.9" }
|
||||
titlecase = "1.1.0"
|
||||
thiserror = "1.0.11"
|
||||
url = "2.1.1"
|
||||
|
||||
[dev-dependencies]
|
||||
solana-core = { path = "../core", version = "1.0.6" }
|
||||
solana-budget-program = { path = "../programs/budget", version = "1.0.6" }
|
||||
solana-core = { path = "../core", version = "1.0.9" }
|
||||
solana-budget-program = { path = "../programs/budget", version = "1.0.9" }
|
||||
tempfile = "3.1.0"
|
||||
|
||||
[[bin]]
|
||||
|
372
cli/src/cli.rs
372
cli/src/cli.rs
@ -41,6 +41,7 @@ use solana_sdk::{
|
||||
pubkey::Pubkey,
|
||||
signature::{Keypair, Signature, Signer, SignerError},
|
||||
system_instruction::{self, create_address_with_seed, SystemError, MAX_ADDRESS_SEED_LEN},
|
||||
system_program,
|
||||
transaction::{Transaction, TransactionError},
|
||||
};
|
||||
use solana_stake_program::{
|
||||
@ -120,7 +121,7 @@ pub fn fee_payer_arg<'a, 'b>() -> Arg<'a, 'b> {
|
||||
Arg::with_name(FEE_PAYER_ARG.name)
|
||||
.long(FEE_PAYER_ARG.long)
|
||||
.takes_value(true)
|
||||
.value_name("KEYPAIR or PUBKEY or REMOTE WALLET PATH")
|
||||
.value_name("KEYPAIR")
|
||||
.validator(is_valid_signer)
|
||||
.help(FEE_PAYER_ARG.help)
|
||||
}
|
||||
@ -300,9 +301,7 @@ pub enum CliCommand {
|
||||
},
|
||||
StakeAuthorize {
|
||||
stake_account_pubkey: Pubkey,
|
||||
new_authorized_pubkey: Pubkey,
|
||||
stake_authorize: StakeAuthorize,
|
||||
authority: SignerIndex,
|
||||
new_authorizations: Vec<(StakeAuthorize, Pubkey, SignerIndex)>,
|
||||
sign_only: bool,
|
||||
blockhash_query: BlockhashQuery,
|
||||
nonce_account: Option<Pubkey>,
|
||||
@ -351,7 +350,7 @@ pub enum CliCommand {
|
||||
// Vote Commands
|
||||
CreateVoteAccount {
|
||||
seed: Option<String>,
|
||||
node_pubkey: Pubkey,
|
||||
identity_account: SignerIndex,
|
||||
authorized_voter: Option<Pubkey>,
|
||||
authorized_withdrawer: Option<Pubkey>,
|
||||
commission: u8,
|
||||
@ -361,6 +360,12 @@ pub enum CliCommand {
|
||||
use_lamports_unit: bool,
|
||||
commitment_config: CommitmentConfig,
|
||||
},
|
||||
WithdrawFromVoteAccount {
|
||||
vote_account_pubkey: Pubkey,
|
||||
destination_account_pubkey: Pubkey,
|
||||
withdraw_authority: SignerIndex,
|
||||
lamports: u64,
|
||||
},
|
||||
VoteAuthorize {
|
||||
vote_account_pubkey: Pubkey,
|
||||
new_authorized_pubkey: Pubkey,
|
||||
@ -368,7 +373,7 @@ pub enum CliCommand {
|
||||
},
|
||||
VoteUpdateValidator {
|
||||
vote_account_pubkey: Pubkey,
|
||||
new_identity_pubkey: Pubkey,
|
||||
new_identity_account: SignerIndex,
|
||||
},
|
||||
// Wallet Commands
|
||||
Address,
|
||||
@ -385,6 +390,7 @@ pub enum CliCommand {
|
||||
Cancel(Pubkey),
|
||||
Confirm(Signature),
|
||||
Pay(PayCommand),
|
||||
ResolveSigner(Option<String>),
|
||||
ShowAccount {
|
||||
pubkey: Pubkey,
|
||||
output_file: Option<String>,
|
||||
@ -559,7 +565,7 @@ pub fn parse_command(
|
||||
) -> Result<CliCommandInfo, Box<dyn error::Error>> {
|
||||
let response = match matches.subcommand() {
|
||||
// Cluster Query Commands
|
||||
("catchup", Some(matches)) => parse_catchup(matches),
|
||||
("catchup", Some(matches)) => parse_catchup(matches, wallet_manager),
|
||||
("cluster-version", Some(_matches)) => Ok(CliCommandInfo {
|
||||
command: CliCommand::ClusterVersion,
|
||||
signers: vec![],
|
||||
@ -593,7 +599,7 @@ pub fn parse_command(
|
||||
command: CliCommand::ShowGossip,
|
||||
signers: vec![],
|
||||
}),
|
||||
("stakes", Some(matches)) => parse_show_stakes(matches),
|
||||
("stakes", Some(matches)) => parse_show_stakes(matches, wallet_manager),
|
||||
("validators", Some(matches)) => parse_show_validators(matches),
|
||||
// Nonce Commands
|
||||
("authorize-nonce-account", Some(matches)) => {
|
||||
@ -602,11 +608,11 @@ pub fn parse_command(
|
||||
("create-nonce-account", Some(matches)) => {
|
||||
parse_nonce_create_account(matches, default_signer_path, wallet_manager)
|
||||
}
|
||||
("nonce", Some(matches)) => parse_get_nonce(matches),
|
||||
("nonce", Some(matches)) => parse_get_nonce(matches, wallet_manager),
|
||||
("new-nonce", Some(matches)) => {
|
||||
parse_new_nonce(matches, default_signer_path, wallet_manager)
|
||||
}
|
||||
("nonce-account", Some(matches)) => parse_show_nonce_account(matches),
|
||||
("nonce-account", Some(matches)) => parse_show_nonce_account(matches, wallet_manager),
|
||||
("withdraw-from-nonce-account", Some(matches)) => {
|
||||
parse_withdraw_from_nonce_account(matches, default_signer_path, wallet_manager)
|
||||
}
|
||||
@ -636,22 +642,13 @@ pub fn parse_command(
|
||||
("split-stake", Some(matches)) => {
|
||||
parse_split_stake(matches, default_signer_path, wallet_manager)
|
||||
}
|
||||
("stake-authorize-staker", Some(matches)) => parse_stake_authorize(
|
||||
matches,
|
||||
default_signer_path,
|
||||
wallet_manager,
|
||||
StakeAuthorize::Staker,
|
||||
),
|
||||
("stake-authorize-withdrawer", Some(matches)) => parse_stake_authorize(
|
||||
matches,
|
||||
default_signer_path,
|
||||
wallet_manager,
|
||||
StakeAuthorize::Withdrawer,
|
||||
),
|
||||
("stake-authorize", Some(matches)) => {
|
||||
parse_stake_authorize(matches, default_signer_path, wallet_manager)
|
||||
}
|
||||
("stake-set-lockup", Some(matches)) => {
|
||||
parse_stake_set_lockup(matches, default_signer_path, wallet_manager)
|
||||
}
|
||||
("stake-account", Some(matches)) => parse_show_stake_account(matches),
|
||||
("stake-account", Some(matches)) => parse_show_stake_account(matches, wallet_manager),
|
||||
("stake-history", Some(matches)) => parse_show_stake_history(matches),
|
||||
// Storage Commands
|
||||
("create-archiver-storage-account", Some(matches)) => {
|
||||
@ -663,7 +660,9 @@ pub fn parse_command(
|
||||
("claim-storage-reward", Some(matches)) => {
|
||||
parse_storage_claim_reward(matches, default_signer_path, wallet_manager)
|
||||
}
|
||||
("storage-account", Some(matches)) => parse_storage_get_account_command(matches),
|
||||
("storage-account", Some(matches)) => {
|
||||
parse_storage_get_account_command(matches, wallet_manager)
|
||||
}
|
||||
// Validator Info Commands
|
||||
("validator-info", Some(matches)) => match matches.subcommand() {
|
||||
("publish", Some(matches)) => {
|
||||
@ -674,7 +673,7 @@ pub fn parse_command(
|
||||
},
|
||||
// Vote Commands
|
||||
("create-vote-account", Some(matches)) => {
|
||||
parse_vote_create_account(matches, default_signer_path, wallet_manager)
|
||||
parse_create_vote_account(matches, default_signer_path, wallet_manager)
|
||||
}
|
||||
("vote-update-validator", Some(matches)) => {
|
||||
parse_vote_update_validator(matches, default_signer_path, wallet_manager)
|
||||
@ -691,7 +690,10 @@ pub fn parse_command(
|
||||
wallet_manager,
|
||||
VoteAuthorize::Withdrawer,
|
||||
),
|
||||
("vote-account", Some(matches)) => parse_vote_get_account_command(matches),
|
||||
("vote-account", Some(matches)) => parse_vote_get_account_command(matches, wallet_manager),
|
||||
("withdraw-from-vote-account", Some(matches)) => {
|
||||
parse_withdraw_from_vote_account(matches, default_signer_path, wallet_manager)
|
||||
}
|
||||
// Wallet Commands
|
||||
("address", Some(matches)) => Ok(CliCommandInfo {
|
||||
command: CliCommand::Address,
|
||||
@ -724,7 +726,7 @@ pub fn parse_command(
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let pubkey = pubkey_of(matches, "to");
|
||||
let pubkey = pubkey_of_signer(matches, "to", wallet_manager)?;
|
||||
let signers = if pubkey.is_some() {
|
||||
vec![]
|
||||
} else {
|
||||
@ -747,7 +749,7 @@ pub fn parse_command(
|
||||
})
|
||||
}
|
||||
("balance", Some(matches)) => {
|
||||
let pubkey = pubkey_of(matches, "pubkey");
|
||||
let pubkey = pubkey_of_signer(matches, "pubkey", wallet_manager)?;
|
||||
let signers = if pubkey.is_some() {
|
||||
vec![]
|
||||
} else {
|
||||
@ -788,7 +790,7 @@ pub fn parse_command(
|
||||
},
|
||||
("pay", Some(matches)) => {
|
||||
let lamports = lamports_of_sol(matches, "amount").unwrap();
|
||||
let to = pubkey_of(matches, "to").unwrap();
|
||||
let to = pubkey_of_signer(matches, "to", wallet_manager)?.unwrap();
|
||||
let timestamp = if matches.is_present("timestamp") {
|
||||
// Parse input for serde_json
|
||||
let date_string = if !matches.value_of("timestamp").unwrap().contains('Z') {
|
||||
@ -805,7 +807,7 @@ pub fn parse_command(
|
||||
let cancelable = matches.is_present("cancelable");
|
||||
let sign_only = matches.is_present(SIGN_ONLY_ARG.name);
|
||||
let blockhash_query = BlockhashQuery::new_from_matches(matches);
|
||||
let nonce_account = pubkey_of(matches, NONCE_ARG.name);
|
||||
let nonce_account = pubkey_of_signer(matches, NONCE_ARG.name, wallet_manager)?;
|
||||
let (nonce_authority, nonce_authority_pubkey) =
|
||||
signer_of(matches, NONCE_AUTHORITY_ARG.name, wallet_manager)?;
|
||||
|
||||
@ -838,7 +840,8 @@ pub fn parse_command(
|
||||
})
|
||||
}
|
||||
("account", Some(matches)) => {
|
||||
let account_pubkey = pubkey_of(matches, "account_pubkey").unwrap();
|
||||
let account_pubkey =
|
||||
pubkey_of_signer(matches, "account_pubkey", wallet_manager)?.unwrap();
|
||||
let output_file = matches.value_of("output_file");
|
||||
let use_lamports_unit = matches.is_present("lamports");
|
||||
Ok(CliCommandInfo {
|
||||
@ -850,6 +853,13 @@ pub fn parse_command(
|
||||
signers: vec![],
|
||||
})
|
||||
}
|
||||
("resolve-signer", Some(matches)) => {
|
||||
let signer_path = resolve_signer(matches, "signer", wallet_manager)?;
|
||||
Ok(CliCommandInfo {
|
||||
command: CliCommand::ResolveSigner(signer_path),
|
||||
signers: vec![],
|
||||
})
|
||||
}
|
||||
("send-signature", Some(matches)) => {
|
||||
let to = value_of(matches, "to").unwrap();
|
||||
let process_id = value_of(matches, "process_id").unwrap();
|
||||
@ -886,10 +896,10 @@ pub fn parse_command(
|
||||
}
|
||||
("transfer", Some(matches)) => {
|
||||
let lamports = lamports_of_sol(matches, "amount").unwrap();
|
||||
let to = pubkey_of(matches, "to").unwrap();
|
||||
let to = pubkey_of_signer(matches, "to", wallet_manager)?.unwrap();
|
||||
let sign_only = matches.is_present(SIGN_ONLY_ARG.name);
|
||||
let blockhash_query = BlockhashQuery::new_from_matches(matches);
|
||||
let nonce_account = pubkey_of(matches, NONCE_ARG.name);
|
||||
let nonce_account = pubkey_of_signer(matches, NONCE_ARG.name, wallet_manager)?;
|
||||
let (nonce_authority, nonce_authority_pubkey) =
|
||||
signer_of(matches, NONCE_AUTHORITY_ARG.name, wallet_manager)?;
|
||||
let (fee_payer, fee_payer_pubkey) =
|
||||
@ -995,17 +1005,31 @@ pub fn get_blockhash_and_fee_calculator(
|
||||
}
|
||||
|
||||
pub fn return_signers(tx: &Transaction) -> ProcessResult {
|
||||
println_signers(tx);
|
||||
let signers: Vec<_> = tx
|
||||
.signatures
|
||||
let verify_results = tx.verify_with_results();
|
||||
let mut signers = Vec::new();
|
||||
let mut absent = Vec::new();
|
||||
let mut bad_sig = Vec::new();
|
||||
tx.signatures
|
||||
.iter()
|
||||
.zip(tx.message.account_keys.clone())
|
||||
.map(|(signature, pubkey)| format!("{}={}", pubkey, signature))
|
||||
.collect();
|
||||
.zip(tx.message.account_keys.iter())
|
||||
.zip(verify_results.into_iter())
|
||||
.for_each(|((sig, key), res)| {
|
||||
if res {
|
||||
signers.push(format!("{}={}", key, sig))
|
||||
} else if *sig == Signature::default() {
|
||||
absent.push(key.to_string());
|
||||
} else {
|
||||
bad_sig.push(key.to_string());
|
||||
}
|
||||
});
|
||||
|
||||
println_signers(&tx.message.recent_blockhash, &signers, &absent, &bad_sig);
|
||||
|
||||
Ok(json!({
|
||||
"blockhash": tx.message.recent_blockhash.to_string(),
|
||||
"signers": &signers,
|
||||
"absent": &absent,
|
||||
"badSig": &bad_sig,
|
||||
})
|
||||
.to_string())
|
||||
}
|
||||
@ -1028,9 +1052,10 @@ pub fn parse_create_address_with_seed(
|
||||
};
|
||||
|
||||
let program_id = match matches.value_of("program_id").unwrap() {
|
||||
"NONCE" => system_program::id(),
|
||||
"STAKE" => solana_stake_program::id(),
|
||||
"VOTE" => solana_vote_program::id(),
|
||||
"STORAGE" => solana_storage_program::id(),
|
||||
"VOTE" => solana_vote_program::id(),
|
||||
_ => pubkey_of(matches, "program_id").unwrap(),
|
||||
};
|
||||
|
||||
@ -1231,7 +1256,8 @@ fn process_deploy(
|
||||
)?;
|
||||
|
||||
trace!("Creating program account");
|
||||
let result = rpc_client.send_and_confirm_transaction(&mut create_account_tx, &signers);
|
||||
let result =
|
||||
rpc_client.send_and_confirm_transaction_with_spinner(&mut create_account_tx, &signers);
|
||||
log_instruction_custom_error::<SystemError>(result)
|
||||
.map_err(|_| CliError::DynamicProgramError("Program allocate space failed".to_string()))?;
|
||||
|
||||
@ -1240,7 +1266,7 @@ fn process_deploy(
|
||||
|
||||
trace!("Finalizing program account");
|
||||
rpc_client
|
||||
.send_and_confirm_transaction(&mut finalize_tx, &signers)
|
||||
.send_and_confirm_transaction_with_spinner(&mut finalize_tx, &signers)
|
||||
.map_err(|_| {
|
||||
CliError::DynamicProgramError("Program finalize transaction failed".to_string())
|
||||
})?;
|
||||
@ -1289,11 +1315,12 @@ fn process_pay(
|
||||
Message::new(&[ix])
|
||||
};
|
||||
let mut tx = Transaction::new_unsigned(message);
|
||||
tx.try_sign(&config.signers, blockhash)?;
|
||||
|
||||
if sign_only {
|
||||
tx.try_partial_sign(&config.signers, blockhash)?;
|
||||
return_signers(&tx)
|
||||
} else {
|
||||
tx.try_sign(&config.signers, blockhash)?;
|
||||
if let Some(nonce_account) = &nonce_account {
|
||||
let nonce_account = rpc_client.get_account(nonce_account)?;
|
||||
check_nonce_account(&nonce_account, &nonce_authority.pubkey(), &blockhash)?;
|
||||
@ -1304,7 +1331,8 @@ fn process_pay(
|
||||
&fee_calculator,
|
||||
&tx.message,
|
||||
)?;
|
||||
let result = rpc_client.send_and_confirm_transaction(&mut tx, &config.signers);
|
||||
let result =
|
||||
rpc_client.send_and_confirm_transaction_with_spinner(&mut tx, &config.signers);
|
||||
log_instruction_custom_error::<SystemError>(result)
|
||||
}
|
||||
} else if *witnesses == None {
|
||||
@ -1328,18 +1356,21 @@ fn process_pay(
|
||||
);
|
||||
let message = Message::new(&ixs);
|
||||
let mut tx = Transaction::new_unsigned(message);
|
||||
tx.try_sign(&[config.signers[0], &contract_state], blockhash)?;
|
||||
if sign_only {
|
||||
tx.try_partial_sign(&[config.signers[0], &contract_state], blockhash)?;
|
||||
return_signers(&tx)
|
||||
} else {
|
||||
tx.try_sign(&[config.signers[0], &contract_state], blockhash)?;
|
||||
check_account_for_fee(
|
||||
rpc_client,
|
||||
&config.signers[0].pubkey(),
|
||||
&fee_calculator,
|
||||
&tx.message,
|
||||
)?;
|
||||
let result = rpc_client
|
||||
.send_and_confirm_transaction(&mut tx, &[config.signers[0], &contract_state]);
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner(
|
||||
&mut tx,
|
||||
&[config.signers[0], &contract_state],
|
||||
);
|
||||
let signature_str = log_instruction_custom_error::<BudgetError>(result)?;
|
||||
|
||||
Ok(json!({
|
||||
@ -1371,12 +1402,15 @@ fn process_pay(
|
||||
);
|
||||
let message = Message::new(&ixs);
|
||||
let mut tx = Transaction::new_unsigned(message);
|
||||
tx.try_sign(&[config.signers[0], &contract_state], blockhash)?;
|
||||
if sign_only {
|
||||
tx.try_partial_sign(&[config.signers[0], &contract_state], blockhash)?;
|
||||
return_signers(&tx)
|
||||
} else {
|
||||
let result = rpc_client
|
||||
.send_and_confirm_transaction(&mut tx, &[config.signers[0], &contract_state]);
|
||||
tx.try_sign(&[config.signers[0], &contract_state], blockhash)?;
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner(
|
||||
&mut tx,
|
||||
&[config.signers[0], &contract_state],
|
||||
);
|
||||
check_account_for_fee(
|
||||
rpc_client,
|
||||
&config.signers[0].pubkey(),
|
||||
@ -1412,7 +1446,8 @@ fn process_cancel(rpc_client: &RpcClient, config: &CliConfig, pubkey: &Pubkey) -
|
||||
&fee_calculator,
|
||||
&tx.message,
|
||||
)?;
|
||||
let result = rpc_client.send_and_confirm_transaction(&mut tx, &[config.signers[0]]);
|
||||
let result =
|
||||
rpc_client.send_and_confirm_transaction_with_spinner(&mut tx, &[config.signers[0]]);
|
||||
log_instruction_custom_error::<BudgetError>(result)
|
||||
}
|
||||
|
||||
@ -1435,7 +1470,8 @@ fn process_time_elapsed(
|
||||
&fee_calculator,
|
||||
&tx.message,
|
||||
)?;
|
||||
let result = rpc_client.send_and_confirm_transaction(&mut tx, &[config.signers[0]]);
|
||||
let result =
|
||||
rpc_client.send_and_confirm_transaction_with_spinner(&mut tx, &[config.signers[0]]);
|
||||
log_instruction_custom_error::<BudgetError>(result)
|
||||
}
|
||||
|
||||
@ -1477,11 +1513,12 @@ fn process_transfer(
|
||||
Message::new_with_payer(&ixs, Some(&fee_payer.pubkey()))
|
||||
};
|
||||
let mut tx = Transaction::new_unsigned(message);
|
||||
tx.try_sign(&config.signers, recent_blockhash)?;
|
||||
|
||||
if sign_only {
|
||||
tx.try_partial_sign(&config.signers, recent_blockhash)?;
|
||||
return_signers(&tx)
|
||||
} else {
|
||||
tx.try_sign(&config.signers, recent_blockhash)?;
|
||||
if let Some(nonce_account) = &nonce_account {
|
||||
let nonce_account = rpc_client.get_account(nonce_account)?;
|
||||
check_nonce_account(&nonce_account, &nonce_authority.pubkey(), &recent_blockhash)?;
|
||||
@ -1492,7 +1529,7 @@ fn process_transfer(
|
||||
&fee_calculator,
|
||||
&tx.message,
|
||||
)?;
|
||||
let result = rpc_client.send_and_confirm_transaction(&mut tx, &config.signers);
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner(&mut tx, &config.signers);
|
||||
log_instruction_custom_error::<SystemError>(result)
|
||||
}
|
||||
}
|
||||
@ -1515,7 +1552,8 @@ fn process_witness(
|
||||
&fee_calculator,
|
||||
&tx.message,
|
||||
)?;
|
||||
let result = rpc_client.send_and_confirm_transaction(&mut tx, &[config.signers[0]]);
|
||||
let result =
|
||||
rpc_client.send_and_confirm_transaction_with_spinner(&mut tx, &[config.signers[0]]);
|
||||
log_instruction_custom_error::<BudgetError>(result)
|
||||
}
|
||||
|
||||
@ -1776,9 +1814,7 @@ pub fn process_command(config: &CliConfig) -> ProcessResult {
|
||||
}
|
||||
CliCommand::StakeAuthorize {
|
||||
stake_account_pubkey,
|
||||
new_authorized_pubkey,
|
||||
stake_authorize,
|
||||
authority,
|
||||
ref new_authorizations,
|
||||
sign_only,
|
||||
blockhash_query,
|
||||
nonce_account,
|
||||
@ -1788,9 +1824,7 @@ pub fn process_command(config: &CliConfig) -> ProcessResult {
|
||||
&rpc_client,
|
||||
config,
|
||||
&stake_account_pubkey,
|
||||
&new_authorized_pubkey,
|
||||
*stake_authorize,
|
||||
*authority,
|
||||
new_authorizations,
|
||||
*sign_only,
|
||||
blockhash_query,
|
||||
*nonce_account,
|
||||
@ -1893,7 +1927,7 @@ pub fn process_command(config: &CliConfig) -> ProcessResult {
|
||||
// Create vote account
|
||||
CliCommand::CreateVoteAccount {
|
||||
seed,
|
||||
node_pubkey,
|
||||
identity_account,
|
||||
authorized_voter,
|
||||
authorized_withdrawer,
|
||||
commission,
|
||||
@ -1901,7 +1935,7 @@ pub fn process_command(config: &CliConfig) -> ProcessResult {
|
||||
&rpc_client,
|
||||
config,
|
||||
seed,
|
||||
&node_pubkey,
|
||||
*identity_account,
|
||||
authorized_voter,
|
||||
authorized_withdrawer,
|
||||
*commission,
|
||||
@ -1917,6 +1951,19 @@ pub fn process_command(config: &CliConfig) -> ProcessResult {
|
||||
*use_lamports_unit,
|
||||
*commitment_config,
|
||||
),
|
||||
CliCommand::WithdrawFromVoteAccount {
|
||||
vote_account_pubkey,
|
||||
withdraw_authority,
|
||||
lamports,
|
||||
destination_account_pubkey,
|
||||
} => process_withdraw_from_vote_account(
|
||||
&rpc_client,
|
||||
config,
|
||||
vote_account_pubkey,
|
||||
*withdraw_authority,
|
||||
*lamports,
|
||||
destination_account_pubkey,
|
||||
),
|
||||
CliCommand::VoteAuthorize {
|
||||
vote_account_pubkey,
|
||||
new_authorized_pubkey,
|
||||
@ -1930,12 +1977,12 @@ pub fn process_command(config: &CliConfig) -> ProcessResult {
|
||||
),
|
||||
CliCommand::VoteUpdateValidator {
|
||||
vote_account_pubkey,
|
||||
new_identity_pubkey,
|
||||
new_identity_account,
|
||||
} => process_vote_update_validator(
|
||||
&rpc_client,
|
||||
config,
|
||||
&vote_account_pubkey,
|
||||
&new_identity_pubkey,
|
||||
*new_identity_account,
|
||||
),
|
||||
|
||||
// Wallet Commands
|
||||
@ -1998,6 +2045,13 @@ pub fn process_command(config: &CliConfig) -> ProcessResult {
|
||||
*nonce_account,
|
||||
*nonce_authority,
|
||||
),
|
||||
CliCommand::ResolveSigner(path) => {
|
||||
if let Some(path) = path {
|
||||
Ok(path.to_string())
|
||||
} else {
|
||||
Ok("Signer is valid".to_string())
|
||||
}
|
||||
}
|
||||
CliCommand::ShowAccount {
|
||||
pubkey,
|
||||
output_file,
|
||||
@ -2099,7 +2153,7 @@ pub fn request_and_confirm_airdrop(
|
||||
}
|
||||
}?;
|
||||
let mut tx = keypair.airdrop_transaction();
|
||||
let result = rpc_client.send_and_confirm_transaction(&mut tx, &[&keypair]);
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner(&mut tx, &[&keypair]);
|
||||
log_instruction_custom_error::<SystemError>(result)
|
||||
}
|
||||
|
||||
@ -2115,17 +2169,9 @@ where
|
||||
)) = err.kind()
|
||||
{
|
||||
if let Some(specific_error) = E::decode_custom_error_to_enum(*code) {
|
||||
error!("{}::{:?}", E::type_of(), specific_error);
|
||||
eprintln!(
|
||||
"Program Error ({}::{:?}): {}",
|
||||
E::type_of(),
|
||||
specific_error,
|
||||
specific_error
|
||||
);
|
||||
return Err(specific_error.into());
|
||||
}
|
||||
}
|
||||
error!("{:?}", err);
|
||||
Err(err.into())
|
||||
}
|
||||
Ok(sig) => Ok(sig),
|
||||
@ -2179,14 +2225,14 @@ pub fn app<'ab, 'v>(name: &str, about: &'ab str, version: &'v str) -> App<'ab, '
|
||||
.arg(
|
||||
Arg::with_name("faucet_host")
|
||||
.long("faucet-host")
|
||||
.value_name("HOST")
|
||||
.value_name("URL")
|
||||
.takes_value(true)
|
||||
.help("Faucet host to use [default: the --url host]"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("faucet_port")
|
||||
.long("faucet-port")
|
||||
.value_name("PORT")
|
||||
.value_name("PORT_NUMBER")
|
||||
.takes_value(true)
|
||||
.default_value(solana_faucet::faucet::FAUCET_PORT_STR)
|
||||
.help("Faucet port to use"),
|
||||
@ -2203,10 +2249,10 @@ pub fn app<'ab, 'v>(name: &str, about: &'ab str, version: &'v str) -> App<'ab, '
|
||||
.arg(
|
||||
Arg::with_name("to")
|
||||
.index(2)
|
||||
.value_name("PUBKEY")
|
||||
.value_name("RECIPIENT_ADDRESS")
|
||||
.takes_value(true)
|
||||
.validator(is_pubkey_or_keypair)
|
||||
.help("The pubkey of airdrop recipient"),
|
||||
.validator(is_valid_pubkey)
|
||||
.help("The account address of airdrop recipient"),
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
@ -2215,10 +2261,10 @@ pub fn app<'ab, 'v>(name: &str, about: &'ab str, version: &'v str) -> App<'ab, '
|
||||
.arg(
|
||||
Arg::with_name("pubkey")
|
||||
.index(1)
|
||||
.value_name("PUBKEY")
|
||||
.value_name("ACCOUNT_ADDRESS")
|
||||
.takes_value(true)
|
||||
.validator(is_valid_signer)
|
||||
.help("The public key of the balance to check"),
|
||||
.validator(is_valid_pubkey)
|
||||
.help("The account address of the balance to check"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("lamports")
|
||||
@ -2233,11 +2279,11 @@ pub fn app<'ab, 'v>(name: &str, about: &'ab str, version: &'v str) -> App<'ab, '
|
||||
.arg(
|
||||
Arg::with_name("process_id")
|
||||
.index(1)
|
||||
.value_name("PROCESS ID")
|
||||
.value_name("ACCOUNT_ADDRESS")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.validator(is_pubkey)
|
||||
.help("The process id of the transfer to cancel"),
|
||||
.help("The account address of the transfer to cancel"),
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
@ -2246,7 +2292,7 @@ pub fn app<'ab, 'v>(name: &str, about: &'ab str, version: &'v str) -> App<'ab, '
|
||||
.arg(
|
||||
Arg::with_name("signature")
|
||||
.index(1)
|
||||
.value_name("SIGNATURE")
|
||||
.value_name("TRANSACTION_SIGNATURE")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.help("The transaction signature to confirm"),
|
||||
@ -2271,17 +2317,17 @@ pub fn app<'ab, 'v>(name: &str, about: &'ab str, version: &'v str) -> App<'ab, '
|
||||
.required(true)
|
||||
.help(
|
||||
"The program_id that the address will ultimately be used for, \n\
|
||||
or one of STAKE, VOTE, and STORAGE keywords",
|
||||
or one of NONCE, STAKE, STORAGE, and VOTE keywords",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("from")
|
||||
.long("from")
|
||||
.value_name("PUBKEY")
|
||||
.value_name("FROM_PUBKEY")
|
||||
.takes_value(true)
|
||||
.required(false)
|
||||
.validator(is_valid_signer)
|
||||
.help("From (base) key, defaults to client keypair."),
|
||||
.validator(is_valid_pubkey)
|
||||
.help("From (base) key, [default: cli config keypair]"),
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
@ -2290,7 +2336,7 @@ pub fn app<'ab, 'v>(name: &str, about: &'ab str, version: &'v str) -> App<'ab, '
|
||||
.arg(
|
||||
Arg::with_name("program_location")
|
||||
.index(1)
|
||||
.value_name("PATH TO BPF PROGRAM")
|
||||
.value_name("PROGRAM_FILEPATH")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.help("/path/to/program.o"),
|
||||
@ -2302,11 +2348,11 @@ pub fn app<'ab, 'v>(name: &str, about: &'ab str, version: &'v str) -> App<'ab, '
|
||||
.arg(
|
||||
Arg::with_name("to")
|
||||
.index(1)
|
||||
.value_name("TO PUBKEY")
|
||||
.value_name("RECIPIENT_ADDRESS")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.validator(is_pubkey_or_keypair)
|
||||
.help("The pubkey of recipient"),
|
||||
.validator(is_valid_pubkey)
|
||||
.help("The account address of recipient"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("amount")
|
||||
@ -2352,25 +2398,38 @@ pub fn app<'ab, 'v>(name: &str, about: &'ab str, version: &'v str) -> App<'ab, '
|
||||
.arg(nonce_arg())
|
||||
.arg(nonce_authority_arg()),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("resolve-signer")
|
||||
.about("Checks that a signer is valid, and returns its specific path; useful for signers that may be specified generally, eg. usb://ledger")
|
||||
.arg(
|
||||
Arg::with_name("signer")
|
||||
.index(1)
|
||||
.value_name("SIGNER_KEYPAIR")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.validator(is_valid_signer)
|
||||
.help("The signer path to resolve")
|
||||
)
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("send-signature")
|
||||
.about("Send a signature to authorize a transfer")
|
||||
.arg(
|
||||
Arg::with_name("to")
|
||||
.index(1)
|
||||
.value_name("PUBKEY")
|
||||
.value_name("RECIPIENT_ADDRESS")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.validator(is_pubkey)
|
||||
.help("The pubkey of recipient"),
|
||||
.help("The account address of recipient"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("process_id")
|
||||
.index(2)
|
||||
.value_name("PROCESS ID")
|
||||
.value_name("ACCOUNT_ADDRESS")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.help("The process id of the transfer to authorize"),
|
||||
.help("The account address of the transfer to authorize"),
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
@ -2379,19 +2438,19 @@ pub fn app<'ab, 'v>(name: &str, about: &'ab str, version: &'v str) -> App<'ab, '
|
||||
.arg(
|
||||
Arg::with_name("to")
|
||||
.index(1)
|
||||
.value_name("PUBKEY")
|
||||
.value_name("RECIPIENT_ADDRESS")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.validator(is_pubkey)
|
||||
.help("The pubkey of recipient"),
|
||||
.help("The account address of recipient"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("process_id")
|
||||
.index(2)
|
||||
.value_name("PROCESS ID")
|
||||
.value_name("ACCOUNT_ADDRESS")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.help("The process id of the transfer to unlock"),
|
||||
.help("The account address of the transfer to unlock"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("datetime")
|
||||
@ -2407,11 +2466,11 @@ pub fn app<'ab, 'v>(name: &str, about: &'ab str, version: &'v str) -> App<'ab, '
|
||||
.arg(
|
||||
Arg::with_name("to")
|
||||
.index(1)
|
||||
.value_name("TO PUBKEY")
|
||||
.value_name("RECIPIENT_ADDRESS")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.validator(is_pubkey_or_keypair)
|
||||
.help("The pubkey of recipient"),
|
||||
.validator(is_valid_pubkey)
|
||||
.help("The account address of recipient"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("amount")
|
||||
@ -2426,7 +2485,7 @@ pub fn app<'ab, 'v>(name: &str, about: &'ab str, version: &'v str) -> App<'ab, '
|
||||
Arg::with_name("from")
|
||||
.long("from")
|
||||
.takes_value(true)
|
||||
.value_name("KEYPAIR or PUBKEY or REMOTE WALLET PATH")
|
||||
.value_name("KEYPAIR")
|
||||
.validator(is_valid_signer)
|
||||
.help("Source account of funds (if different from client local account)"),
|
||||
)
|
||||
@ -2442,17 +2501,17 @@ pub fn app<'ab, 'v>(name: &str, about: &'ab str, version: &'v str) -> App<'ab, '
|
||||
.arg(
|
||||
Arg::with_name("account_pubkey")
|
||||
.index(1)
|
||||
.value_name("ACCOUNT PUBKEY")
|
||||
.value_name("ACCOUNT_ADDRESS")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.validator(is_pubkey_or_keypair)
|
||||
.validator(is_valid_pubkey)
|
||||
.help("Account pubkey"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("output_file")
|
||||
.long("output")
|
||||
.short("o")
|
||||
.value_name("FILE")
|
||||
.value_name("FILEPATH")
|
||||
.takes_value(true)
|
||||
.help("Write the account data to this file"),
|
||||
)
|
||||
@ -2474,7 +2533,9 @@ mod tests {
|
||||
use solana_client::mock_rpc_client_request::SIGNATURE;
|
||||
use solana_sdk::{
|
||||
pubkey::Pubkey,
|
||||
signature::{keypair_from_seed, read_keypair_file, write_keypair_file, Presigner},
|
||||
signature::{
|
||||
keypair_from_seed, read_keypair_file, write_keypair_file, NullSigner, Presigner,
|
||||
},
|
||||
transaction::TransactionError,
|
||||
};
|
||||
use std::path::PathBuf;
|
||||
@ -2678,6 +2739,7 @@ mod tests {
|
||||
("STAKE", solana_stake_program::id()),
|
||||
("VOTE", solana_vote_program::id()),
|
||||
("STORAGE", solana_storage_program::id()),
|
||||
("NONCE", system_program::id()),
|
||||
] {
|
||||
let test_create_address_with_seed = test_commands.clone().get_matches_from(vec![
|
||||
"test",
|
||||
@ -2730,6 +2792,31 @@ mod tests {
|
||||
}
|
||||
);
|
||||
|
||||
// Test ResolveSigner Subcommand, KeypairUrl::Filepath
|
||||
let test_resolve_signer =
|
||||
test_commands
|
||||
.clone()
|
||||
.get_matches_from(vec!["test", "resolve-signer", &keypair_file]);
|
||||
assert_eq!(
|
||||
parse_command(&test_resolve_signer, "", None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::ResolveSigner(Some(keypair_file.clone())),
|
||||
signers: vec![],
|
||||
}
|
||||
);
|
||||
// Test ResolveSigner Subcommand, KeypairUrl::Pubkey (Presigner)
|
||||
let test_resolve_signer =
|
||||
test_commands
|
||||
.clone()
|
||||
.get_matches_from(vec!["test", "resolve-signer", &pubkey_string]);
|
||||
assert_eq!(
|
||||
parse_command(&test_resolve_signer, "", None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::ResolveSigner(Some(pubkey.to_string())),
|
||||
signers: vec![],
|
||||
}
|
||||
);
|
||||
|
||||
// Test Simple Pay Subcommand
|
||||
let test_pay =
|
||||
test_commands
|
||||
@ -3096,15 +3183,15 @@ mod tests {
|
||||
|
||||
let bob_keypair = Keypair::new();
|
||||
let bob_pubkey = bob_keypair.pubkey();
|
||||
let node_pubkey = Pubkey::new_rand();
|
||||
let identity_keypair = Keypair::new();
|
||||
config.command = CliCommand::CreateVoteAccount {
|
||||
seed: None,
|
||||
node_pubkey,
|
||||
identity_account: 2,
|
||||
authorized_voter: Some(bob_pubkey),
|
||||
authorized_withdrawer: Some(bob_pubkey),
|
||||
commission: 0,
|
||||
};
|
||||
config.signers = vec![&keypair, &bob_keypair];
|
||||
config.signers = vec![&keypair, &bob_keypair, &identity_keypair];
|
||||
let signature = process_command(&config);
|
||||
assert_eq!(signature.unwrap(), SIGNATURE.to_string());
|
||||
|
||||
@ -3118,11 +3205,11 @@ mod tests {
|
||||
let signature = process_command(&config);
|
||||
assert_eq!(signature.unwrap(), SIGNATURE.to_string());
|
||||
|
||||
let new_identity_pubkey = Pubkey::new_rand();
|
||||
config.signers = vec![&keypair, &bob_keypair];
|
||||
let new_identity_keypair = Keypair::new();
|
||||
config.signers = vec![&keypair, &bob_keypair, &new_identity_keypair];
|
||||
config.command = CliCommand::VoteUpdateValidator {
|
||||
vote_account_pubkey: bob_pubkey,
|
||||
new_identity_pubkey,
|
||||
new_identity_account: 2,
|
||||
};
|
||||
let signature = process_command(&config);
|
||||
assert_eq!(signature.unwrap(), SIGNATURE.to_string());
|
||||
@ -3340,14 +3427,15 @@ mod tests {
|
||||
assert!(process_command(&config).is_err());
|
||||
|
||||
let bob_keypair = Keypair::new();
|
||||
let identity_keypair = Keypair::new();
|
||||
config.command = CliCommand::CreateVoteAccount {
|
||||
seed: None,
|
||||
node_pubkey,
|
||||
identity_account: 2,
|
||||
authorized_voter: Some(bob_pubkey),
|
||||
authorized_withdrawer: Some(bob_pubkey),
|
||||
commission: 0,
|
||||
};
|
||||
config.signers = vec![&keypair, &bob_keypair];
|
||||
config.signers = vec![&keypair, &bob_keypair, &identity_keypair];
|
||||
assert!(process_command(&config).is_err());
|
||||
|
||||
config.command = CliCommand::VoteAuthorize {
|
||||
@ -3359,7 +3447,7 @@ mod tests {
|
||||
|
||||
config.command = CliCommand::VoteUpdateValidator {
|
||||
vote_account_pubkey: bob_pubkey,
|
||||
new_identity_pubkey: bob_pubkey,
|
||||
new_identity_account: 1,
|
||||
};
|
||||
assert!(process_command(&config).is_err());
|
||||
|
||||
@ -3577,4 +3665,52 @@ mod tests {
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_return_signers() {
|
||||
struct BadSigner {
|
||||
pubkey: Pubkey,
|
||||
}
|
||||
|
||||
impl BadSigner {
|
||||
pub fn new(pubkey: Pubkey) -> Self {
|
||||
Self { pubkey }
|
||||
}
|
||||
}
|
||||
|
||||
impl Signer for BadSigner {
|
||||
fn try_pubkey(&self) -> Result<Pubkey, SignerError> {
|
||||
Ok(self.pubkey)
|
||||
}
|
||||
|
||||
fn try_sign_message(&self, _message: &[u8]) -> Result<Signature, SignerError> {
|
||||
Ok(Signature::new(&[1u8; 64]))
|
||||
}
|
||||
}
|
||||
|
||||
let present: Box<dyn Signer> = Box::new(keypair_from_seed(&[2u8; 32]).unwrap());
|
||||
let absent: Box<dyn Signer> = Box::new(NullSigner::new(&Pubkey::new(&[3u8; 32])));
|
||||
let bad: Box<dyn Signer> = Box::new(BadSigner::new(Pubkey::new(&[4u8; 32])));
|
||||
let to = Pubkey::new(&[5u8; 32]);
|
||||
let nonce = Pubkey::new(&[6u8; 32]);
|
||||
let from = present.pubkey();
|
||||
let fee_payer = absent.pubkey();
|
||||
let nonce_auth = bad.pubkey();
|
||||
let mut tx = Transaction::new_unsigned(Message::new_with_nonce(
|
||||
vec![system_instruction::transfer(&from, &to, 42)],
|
||||
Some(&fee_payer),
|
||||
&nonce,
|
||||
&nonce_auth,
|
||||
));
|
||||
|
||||
let signers = vec![present.as_ref(), absent.as_ref(), bad.as_ref()];
|
||||
let blockhash = Hash::new(&[7u8; 32]);
|
||||
tx.try_partial_sign(&signers, blockhash).unwrap();
|
||||
let res = return_signers(&tx).unwrap();
|
||||
let sign_only = parse_sign_only_reply_string(&res);
|
||||
assert_eq!(sign_only.blockhash, blockhash);
|
||||
assert_eq!(sign_only.present_signers[0].0, present.pubkey());
|
||||
assert_eq!(sign_only.absent_signers[0], absent.pubkey());
|
||||
assert_eq!(sign_only.bad_signers[0], bad.pubkey());
|
||||
}
|
||||
}
|
||||
|
@ -55,8 +55,8 @@ impl ClusterQuerySubCommands for App<'_, '_> {
|
||||
Arg::with_name("node_pubkey")
|
||||
.index(1)
|
||||
.takes_value(true)
|
||||
.value_name("PUBKEY")
|
||||
.validator(is_pubkey_or_keypair)
|
||||
.value_name("VALIDATOR_PUBKEY")
|
||||
.validator(is_valid_pubkey)
|
||||
.required(true)
|
||||
.help("Identity pubkey of the validator"),
|
||||
)
|
||||
@ -208,10 +208,10 @@ impl ClusterQuerySubCommands for App<'_, '_> {
|
||||
.arg(
|
||||
Arg::with_name("vote_account_pubkeys")
|
||||
.index(1)
|
||||
.value_name("VOTE ACCOUNT PUBKEYS")
|
||||
.value_name("VOTE_ACCOUNT_PUBKEYS")
|
||||
.takes_value(true)
|
||||
.multiple(true)
|
||||
.validator(is_pubkey_or_keypair)
|
||||
.validator(is_valid_pubkey)
|
||||
.help("Only show stake accounts delegated to the provided vote accounts"),
|
||||
)
|
||||
.arg(
|
||||
@ -243,8 +243,11 @@ impl ClusterQuerySubCommands for App<'_, '_> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_catchup(matches: &ArgMatches<'_>) -> Result<CliCommandInfo, CliError> {
|
||||
let node_pubkey = pubkey_of(matches, "node_pubkey").unwrap();
|
||||
pub fn parse_catchup(
|
||||
matches: &ArgMatches<'_>,
|
||||
wallet_manager: Option<&Arc<RemoteWalletManager>>,
|
||||
) -> Result<CliCommandInfo, CliError> {
|
||||
let node_pubkey = pubkey_of_signer(matches, "node_pubkey", wallet_manager)?.unwrap();
|
||||
let node_json_rpc_url = value_t!(matches, "node_json_rpc_url", String).ok();
|
||||
Ok(CliCommandInfo {
|
||||
command: CliCommand::Catchup {
|
||||
@ -334,9 +337,13 @@ pub fn parse_get_transaction_count(matches: &ArgMatches<'_>) -> Result<CliComman
|
||||
})
|
||||
}
|
||||
|
||||
pub fn parse_show_stakes(matches: &ArgMatches<'_>) -> Result<CliCommandInfo, CliError> {
|
||||
pub fn parse_show_stakes(
|
||||
matches: &ArgMatches<'_>,
|
||||
wallet_manager: Option<&Arc<RemoteWalletManager>>,
|
||||
) -> Result<CliCommandInfo, CliError> {
|
||||
let use_lamports_unit = matches.is_present("lamports");
|
||||
let vote_account_pubkeys = pubkeys_of(matches, "vote_account_pubkeys");
|
||||
let vote_account_pubkeys =
|
||||
pubkeys_of_multiple_signers(matches, "vote_account_pubkeys", wallet_manager)?;
|
||||
|
||||
Ok(CliCommandInfo {
|
||||
command: CliCommand::ShowStakes {
|
||||
|
@ -1,6 +1,6 @@
|
||||
use crate::cli::SettingType;
|
||||
use console::style;
|
||||
use solana_sdk::transaction::Transaction;
|
||||
use solana_sdk::hash::Hash;
|
||||
|
||||
// Pretty print a "name value"
|
||||
pub fn println_name_value(name: &str, value: &str) {
|
||||
@ -27,13 +27,25 @@ pub fn println_name_value_or(name: &str, value: &str, setting_type: SettingType)
|
||||
);
|
||||
}
|
||||
|
||||
pub fn println_signers(tx: &Transaction) {
|
||||
pub fn println_signers(
|
||||
blockhash: &Hash,
|
||||
signers: &[String],
|
||||
absent: &[String],
|
||||
bad_sig: &[String],
|
||||
) {
|
||||
println!();
|
||||
println!("Blockhash: {}", tx.message.recent_blockhash);
|
||||
println!("Signers (Pubkey=Signature):");
|
||||
tx.signatures
|
||||
.iter()
|
||||
.zip(tx.message.account_keys.clone())
|
||||
.for_each(|(signature, pubkey)| println!(" {:?}={:?}", pubkey, signature));
|
||||
println!("Blockhash: {}", blockhash);
|
||||
if !signers.is_empty() {
|
||||
println!("Signers (Pubkey=Signature):");
|
||||
signers.iter().for_each(|signer| println!(" {}", signer))
|
||||
}
|
||||
if !absent.is_empty() {
|
||||
println!("Absent Signers (Pubkey):");
|
||||
absent.iter().for_each(|pubkey| println!(" {}", pubkey))
|
||||
}
|
||||
if !bad_sig.is_empty() {
|
||||
println!("Bad Signatures (Pubkey):");
|
||||
bad_sig.iter().for_each(|pubkey| println!(" {}", pubkey))
|
||||
}
|
||||
println!();
|
||||
}
|
||||
|
@ -2,7 +2,8 @@ use clap::{crate_description, crate_name, AppSettings, Arg, ArgGroup, ArgMatches
|
||||
use console::style;
|
||||
|
||||
use solana_clap_utils::{
|
||||
input_validators::is_url, keypair::SKIP_SEED_PHRASE_VALIDATION_ARG, DisplayError,
|
||||
input_validators::is_url, keypair::SKIP_SEED_PHRASE_VALIDATION_ARG, offline::SIGN_ONLY_ARG,
|
||||
DisplayError,
|
||||
};
|
||||
use solana_cli::{
|
||||
cli::{app, parse_command, process_command, CliCommandInfo, CliConfig, CliSigners},
|
||||
@ -33,7 +34,7 @@ fn parse_settings(matches: &ArgMatches<'_>) -> Result<bool, Box<dyn error::Error
|
||||
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" => ("WS URL", websocket_url, ws_setting_type),
|
||||
"websocket_url" => ("WebSocket URL", websocket_url, ws_setting_type),
|
||||
"keypair" => ("Key Path", keypair_path, keypair_setting_type),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
@ -41,7 +42,7 @@ fn parse_settings(matches: &ArgMatches<'_>) -> Result<bool, Box<dyn error::Error
|
||||
} else {
|
||||
println_name_value("Config File:", config_file);
|
||||
println_name_value_or("RPC URL:", &json_rpc_url, url_setting_type);
|
||||
println_name_value_or("WS URL:", &websocket_url, ws_setting_type);
|
||||
println_name_value_or("WebSocket URL:", &websocket_url, ws_setting_type);
|
||||
println_name_value_or("Keypair Path:", &keypair_path, keypair_setting_type);
|
||||
}
|
||||
} else {
|
||||
@ -82,7 +83,7 @@ fn parse_settings(matches: &ArgMatches<'_>) -> Result<bool, Box<dyn error::Error
|
||||
|
||||
println_name_value("Config File:", config_file);
|
||||
println_name_value_or("RPC URL:", &json_rpc_url, url_setting_type);
|
||||
println_name_value_or("WS URL:", &websocket_url, ws_setting_type);
|
||||
println_name_value_or("WebSocket URL:", &websocket_url, ws_setting_type);
|
||||
println_name_value_or("Keypair Path:", &keypair_path, keypair_setting_type);
|
||||
} else {
|
||||
println!(
|
||||
@ -151,7 +152,7 @@ fn main() -> Result<(), Box<dyn error::Error>> {
|
||||
let arg = Arg::with_name("config_file")
|
||||
.short("C")
|
||||
.long("config")
|
||||
.value_name("PATH")
|
||||
.value_name("FILEPATH")
|
||||
.takes_value(true)
|
||||
.global(true)
|
||||
.help("Configuration file to use");
|
||||
@ -184,17 +185,17 @@ fn main() -> Result<(), Box<dyn error::Error>> {
|
||||
Arg::with_name("keypair")
|
||||
.short("k")
|
||||
.long("keypair")
|
||||
.value_name("PATH")
|
||||
.value_name("KEYPAIR")
|
||||
.global(true)
|
||||
.takes_value(true)
|
||||
.help("/path/to/id.json or usb://remote/wallet/path"),
|
||||
.help("Filepath or URL to a keypair"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("verbose")
|
||||
.long("verbose")
|
||||
.short("v")
|
||||
.global(true)
|
||||
.help("Show extra information header"),
|
||||
.help("Show additional information"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(SKIP_SEED_PHRASE_VALIDATION_ARG.name)
|
||||
@ -242,7 +243,13 @@ fn do_main(matches: &ArgMatches<'_>) -> Result<(), Box<dyn error::Error>> {
|
||||
let (mut config, signers) = parse_args(&matches, wallet_manager)?;
|
||||
config.signers = signers.iter().map(|s| s.as_ref()).collect();
|
||||
let result = process_command(&config)?;
|
||||
println!("{}", result);
|
||||
let (_, submatches) = matches.subcommand();
|
||||
let sign_only = submatches
|
||||
.map(|m| m.is_present(SIGN_ONLY_ARG.name))
|
||||
.unwrap_or(false);
|
||||
if !sign_only {
|
||||
println!("{}", result);
|
||||
}
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
130
cli/src/nonce.rs
130
cli/src/nonce.rs
@ -74,7 +74,7 @@ pub fn nonce_arg<'a, 'b>() -> Arg<'a, 'b> {
|
||||
.takes_value(true)
|
||||
.value_name("PUBKEY")
|
||||
.requires(BLOCKHASH_ARG.name)
|
||||
.validator(is_pubkey)
|
||||
.validator(is_valid_pubkey)
|
||||
.help(NONCE_ARG.help)
|
||||
}
|
||||
|
||||
@ -82,7 +82,7 @@ pub fn nonce_authority_arg<'a, 'b>() -> Arg<'a, 'b> {
|
||||
Arg::with_name(NONCE_AUTHORITY_ARG.name)
|
||||
.long(NONCE_AUTHORITY_ARG.long)
|
||||
.takes_value(true)
|
||||
.value_name("KEYPAIR or PUBKEY or REMOTE WALLET PATH")
|
||||
.value_name("KEYPAIR")
|
||||
.validator(is_valid_signer)
|
||||
.help(NONCE_AUTHORITY_ARG.help)
|
||||
}
|
||||
@ -93,30 +93,23 @@ impl NonceSubCommands for App<'_, '_> {
|
||||
SubCommand::with_name("authorize-nonce-account")
|
||||
.about("Assign account authority to a new entity")
|
||||
.arg(
|
||||
Arg::with_name("nonce_account_keypair")
|
||||
Arg::with_name("nonce_account_pubkey")
|
||||
.index(1)
|
||||
.value_name("NONCE_ACCOUNT")
|
||||
.value_name("NONCE_ACCOUNT_ADDRESS")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.validator(is_pubkey_or_keypair)
|
||||
.validator(is_valid_pubkey)
|
||||
.help("Address of the nonce account"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("new_authority")
|
||||
.index(2)
|
||||
.value_name("NEW_AUTHORITY_PUBKEY")
|
||||
.value_name("AUTHORITY_PUBKEY")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.validator(is_pubkey_or_keypair)
|
||||
.validator(is_valid_pubkey)
|
||||
.help("Account to be granted authority of the nonce account"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("seed")
|
||||
.long("seed")
|
||||
.value_name("SEED STRING")
|
||||
.takes_value(true)
|
||||
.help("Seed for address generation; if specified, the resulting account will be at a derived address of the NONCE_ACCOUNT pubkey")
|
||||
)
|
||||
.arg(nonce_authority_arg()),
|
||||
)
|
||||
.subcommand(
|
||||
@ -125,10 +118,10 @@ impl NonceSubCommands for App<'_, '_> {
|
||||
.arg(
|
||||
Arg::with_name("nonce_account_keypair")
|
||||
.index(1)
|
||||
.value_name("NONCE ACCOUNT")
|
||||
.value_name("ACCOUNT_KEYPAIR")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.validator(is_pubkey_or_keypair)
|
||||
.validator(is_valid_signer)
|
||||
.help("Keypair of the nonce account to fund"),
|
||||
)
|
||||
.arg(
|
||||
@ -144,9 +137,16 @@ impl NonceSubCommands for App<'_, '_> {
|
||||
Arg::with_name(NONCE_AUTHORITY_ARG.name)
|
||||
.long(NONCE_AUTHORITY_ARG.long)
|
||||
.takes_value(true)
|
||||
.value_name("BASE58_PUBKEY")
|
||||
.validator(is_pubkey_or_keypair)
|
||||
.value_name("PUBKEY")
|
||||
.validator(is_valid_pubkey)
|
||||
.help("Assign noncing authority to another entity"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("seed")
|
||||
.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 NONCE_ACCOUNT pubkey")
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
@ -156,10 +156,10 @@ impl NonceSubCommands for App<'_, '_> {
|
||||
.arg(
|
||||
Arg::with_name("nonce_account_pubkey")
|
||||
.index(1)
|
||||
.value_name("NONCE ACCOUNT")
|
||||
.value_name("NONCE_ACCOUNT_ADDRESS")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.validator(is_pubkey_or_keypair)
|
||||
.validator(is_valid_pubkey)
|
||||
.help("Address of the nonce account to display"),
|
||||
),
|
||||
)
|
||||
@ -167,12 +167,12 @@ impl NonceSubCommands for App<'_, '_> {
|
||||
SubCommand::with_name("new-nonce")
|
||||
.about("Generate a new nonce, rendering the existing nonce useless")
|
||||
.arg(
|
||||
Arg::with_name("nonce_account_keypair")
|
||||
Arg::with_name("nonce_account_pubkey")
|
||||
.index(1)
|
||||
.value_name("NONCE ACCOUNT")
|
||||
.value_name("NONCE_ACCOUNT_ADDRESS")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.validator(is_pubkey_or_keypair)
|
||||
.validator(is_valid_pubkey)
|
||||
.help("Address of the nonce account"),
|
||||
)
|
||||
.arg(nonce_authority_arg()),
|
||||
@ -184,10 +184,10 @@ impl NonceSubCommands for App<'_, '_> {
|
||||
.arg(
|
||||
Arg::with_name("nonce_account_pubkey")
|
||||
.index(1)
|
||||
.value_name("NONCE ACCOUNT")
|
||||
.value_name("NONCE_ACCOUNT_ADDRESS")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.validator(is_pubkey_or_keypair)
|
||||
.validator(is_valid_pubkey)
|
||||
.help("Address of the nonce account to display"),
|
||||
)
|
||||
.arg(
|
||||
@ -199,24 +199,24 @@ impl NonceSubCommands for App<'_, '_> {
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("withdraw-from-nonce-account")
|
||||
.about("Withdraw lamports from the nonce account")
|
||||
.about("Withdraw SOL from the nonce account")
|
||||
.arg(
|
||||
Arg::with_name("nonce_account_keypair")
|
||||
Arg::with_name("nonce_account_pubkey")
|
||||
.index(1)
|
||||
.value_name("NONCE ACCOUNT")
|
||||
.value_name("NONCE_ACCOUNT_ADDRESS")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.validator(is_keypair_or_ask_keyword)
|
||||
.help("Nonce account from to withdraw from"),
|
||||
.validator(is_valid_pubkey)
|
||||
.help("Nonce account to withdraw from"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("destination_account_pubkey")
|
||||
.index(2)
|
||||
.value_name("DESTINATION ACCOUNT")
|
||||
.value_name("RECIPIENT_ADDRESS")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.validator(is_pubkey_or_keypair)
|
||||
.help("The account to which the lamports should be transferred"),
|
||||
.validator(is_valid_pubkey)
|
||||
.help("The account to which the SOL should be transferred"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("amount")
|
||||
@ -279,8 +279,8 @@ pub fn parse_authorize_nonce_account(
|
||||
default_signer_path: &str,
|
||||
wallet_manager: Option<&Arc<RemoteWalletManager>>,
|
||||
) -> Result<CliCommandInfo, CliError> {
|
||||
let nonce_account = pubkey_of(matches, "nonce_account_keypair").unwrap();
|
||||
let new_authority = pubkey_of(matches, "new_authority").unwrap();
|
||||
let nonce_account = pubkey_of_signer(matches, "nonce_account_pubkey", wallet_manager)?.unwrap();
|
||||
let new_authority = pubkey_of_signer(matches, "new_authority", wallet_manager)?.unwrap();
|
||||
let (nonce_authority, nonce_authority_pubkey) =
|
||||
signer_of(matches, NONCE_AUTHORITY_ARG.name, wallet_manager)?;
|
||||
|
||||
@ -311,7 +311,7 @@ pub fn parse_nonce_create_account(
|
||||
signer_of(matches, "nonce_account_keypair", wallet_manager)?;
|
||||
let seed = matches.value_of("seed").map(|s| s.to_string());
|
||||
let lamports = lamports_of_sol(matches, "amount").unwrap();
|
||||
let nonce_authority = pubkey_of(matches, NONCE_AUTHORITY_ARG.name);
|
||||
let nonce_authority = pubkey_of_signer(matches, NONCE_AUTHORITY_ARG.name, wallet_manager)?;
|
||||
|
||||
let payer_provided = None;
|
||||
let signer_info = generate_unique_signers(
|
||||
@ -332,8 +332,12 @@ pub fn parse_nonce_create_account(
|
||||
})
|
||||
}
|
||||
|
||||
pub fn parse_get_nonce(matches: &ArgMatches<'_>) -> Result<CliCommandInfo, CliError> {
|
||||
let nonce_account_pubkey = pubkey_of(matches, "nonce_account_pubkey").unwrap();
|
||||
pub fn parse_get_nonce(
|
||||
matches: &ArgMatches<'_>,
|
||||
wallet_manager: Option<&Arc<RemoteWalletManager>>,
|
||||
) -> Result<CliCommandInfo, CliError> {
|
||||
let nonce_account_pubkey =
|
||||
pubkey_of_signer(matches, "nonce_account_pubkey", wallet_manager)?.unwrap();
|
||||
|
||||
Ok(CliCommandInfo {
|
||||
command: CliCommand::GetNonce(nonce_account_pubkey),
|
||||
@ -346,7 +350,7 @@ pub fn parse_new_nonce(
|
||||
default_signer_path: &str,
|
||||
wallet_manager: Option<&Arc<RemoteWalletManager>>,
|
||||
) -> Result<CliCommandInfo, CliError> {
|
||||
let nonce_account = pubkey_of(matches, "nonce_account_keypair").unwrap();
|
||||
let nonce_account = pubkey_of_signer(matches, "nonce_account_pubkey", wallet_manager)?.unwrap();
|
||||
let (nonce_authority, nonce_authority_pubkey) =
|
||||
signer_of(matches, NONCE_AUTHORITY_ARG.name, wallet_manager)?;
|
||||
|
||||
@ -367,8 +371,12 @@ pub fn parse_new_nonce(
|
||||
})
|
||||
}
|
||||
|
||||
pub fn parse_show_nonce_account(matches: &ArgMatches<'_>) -> Result<CliCommandInfo, CliError> {
|
||||
let nonce_account_pubkey = pubkey_of(matches, "nonce_account_pubkey").unwrap();
|
||||
pub fn parse_show_nonce_account(
|
||||
matches: &ArgMatches<'_>,
|
||||
wallet_manager: Option<&Arc<RemoteWalletManager>>,
|
||||
) -> Result<CliCommandInfo, CliError> {
|
||||
let nonce_account_pubkey =
|
||||
pubkey_of_signer(matches, "nonce_account_pubkey", wallet_manager)?.unwrap();
|
||||
let use_lamports_unit = matches.is_present("lamports");
|
||||
|
||||
Ok(CliCommandInfo {
|
||||
@ -385,8 +393,9 @@ pub fn parse_withdraw_from_nonce_account(
|
||||
default_signer_path: &str,
|
||||
wallet_manager: Option<&Arc<RemoteWalletManager>>,
|
||||
) -> Result<CliCommandInfo, CliError> {
|
||||
let nonce_account = pubkey_of(matches, "nonce_account_keypair").unwrap();
|
||||
let destination_account_pubkey = pubkey_of(matches, "destination_account_pubkey").unwrap();
|
||||
let nonce_account = pubkey_of_signer(matches, "nonce_account_pubkey", wallet_manager)?.unwrap();
|
||||
let destination_account_pubkey =
|
||||
pubkey_of_signer(matches, "destination_account_pubkey", wallet_manager)?.unwrap();
|
||||
let lamports = lamports_of_sol(matches, "amount").unwrap();
|
||||
let (nonce_authority, nonce_authority_pubkey) =
|
||||
signer_of(matches, NONCE_AUTHORITY_ARG.name, wallet_manager)?;
|
||||
@ -451,7 +460,7 @@ pub fn process_authorize_nonce_account(
|
||||
&fee_calculator,
|
||||
&tx.message,
|
||||
)?;
|
||||
let result = rpc_client.send_and_confirm_transaction(&mut tx, &config.signers);
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner(&mut tx, &config.signers);
|
||||
log_instruction_custom_error::<NonceError>(result)
|
||||
}
|
||||
|
||||
@ -528,7 +537,7 @@ pub fn process_create_nonce_account(
|
||||
&fee_calculator,
|
||||
&tx.message,
|
||||
)?;
|
||||
let result = rpc_client.send_and_confirm_transaction(&mut tx, &config.signers);
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner(&mut tx, &config.signers);
|
||||
log_instruction_custom_error::<SystemError>(result)
|
||||
}
|
||||
|
||||
@ -569,8 +578,8 @@ pub fn process_new_nonce(
|
||||
&fee_calculator,
|
||||
&tx.message,
|
||||
)?;
|
||||
let result =
|
||||
rpc_client.send_and_confirm_transaction(&mut tx, &[config.signers[0], nonce_authority]);
|
||||
let result = rpc_client
|
||||
.send_and_confirm_transaction_with_spinner(&mut tx, &[config.signers[0], nonce_authority]);
|
||||
log_instruction_custom_error::<SystemError>(result)
|
||||
}
|
||||
|
||||
@ -642,7 +651,7 @@ pub fn process_withdraw_from_nonce_account(
|
||||
&fee_calculator,
|
||||
&tx.message,
|
||||
)?;
|
||||
let result = rpc_client.send_and_confirm_transaction(&mut tx, &config.signers);
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner(&mut tx, &config.signers);
|
||||
log_instruction_custom_error::<NonceError>(result)
|
||||
}
|
||||
|
||||
@ -869,31 +878,6 @@ mod tests {
|
||||
}
|
||||
);
|
||||
|
||||
let test_withdraw_from_nonce_account = test_commands.clone().get_matches_from(vec![
|
||||
"test",
|
||||
"withdraw-from-nonce-account",
|
||||
&keypair_file,
|
||||
&nonce_account_string,
|
||||
"42",
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(
|
||||
&test_withdraw_from_nonce_account,
|
||||
&default_keypair_file,
|
||||
None
|
||||
)
|
||||
.unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::WithdrawFromNonceAccount {
|
||||
nonce_account: read_keypair_file(&keypair_file).unwrap().pubkey(),
|
||||
nonce_authority: 0,
|
||||
destination_account_pubkey: nonce_account_pubkey,
|
||||
lamports: 42000000000
|
||||
},
|
||||
signers: vec![read_keypair_file(&default_keypair_file).unwrap().into()],
|
||||
}
|
||||
);
|
||||
|
||||
// Test WithdrawFromNonceAccount Subcommand with authority
|
||||
let test_withdraw_from_nonce_account = test_commands.clone().get_matches_from(vec![
|
||||
"test",
|
||||
|
@ -6,10 +6,16 @@ use serde_json::Value;
|
||||
use solana_clap_utils::{
|
||||
input_parsers::{pubkey_of, value_of},
|
||||
input_validators::{is_hash, is_pubkey_sig},
|
||||
keypair::presigner_from_pubkey_sigs,
|
||||
offline::{BLOCKHASH_ARG, SIGNER_ARG, SIGN_ONLY_ARG},
|
||||
};
|
||||
use solana_client::rpc_client::RpcClient;
|
||||
use solana_sdk::{fee_calculator::FeeCalculator, hash::Hash, pubkey::Pubkey, signature::Signature};
|
||||
use solana_sdk::{
|
||||
fee_calculator::FeeCalculator,
|
||||
hash::Hash,
|
||||
pubkey::Pubkey,
|
||||
signature::{Presigner, Signature},
|
||||
};
|
||||
use std::str::FromStr;
|
||||
|
||||
fn blockhash_arg<'a, 'b>() -> Arg<'a, 'b> {
|
||||
@ -33,7 +39,7 @@ fn signer_arg<'a, 'b>() -> Arg<'a, 'b> {
|
||||
Arg::with_name(SIGNER_ARG.name)
|
||||
.long(SIGNER_ARG.long)
|
||||
.takes_value(true)
|
||||
.value_name("BASE58_PUBKEY=BASE58_SIG")
|
||||
.value_name("PUBKEY=SIGNATURE")
|
||||
.validator(is_pubkey_sig)
|
||||
.requires(BLOCKHASH_ARG.name)
|
||||
.multiple(true)
|
||||
@ -52,12 +58,29 @@ impl OfflineArgs for App<'_, '_> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_sign_only_reply_string(reply: &str) -> (Hash, Vec<(Pubkey, Signature)>) {
|
||||
pub struct SignOnly {
|
||||
pub blockhash: Hash,
|
||||
pub present_signers: Vec<(Pubkey, Signature)>,
|
||||
pub absent_signers: Vec<Pubkey>,
|
||||
pub bad_signers: Vec<Pubkey>,
|
||||
}
|
||||
|
||||
impl SignOnly {
|
||||
pub fn has_all_signers(&self) -> bool {
|
||||
self.absent_signers.is_empty() && self.bad_signers.is_empty()
|
||||
}
|
||||
|
||||
pub fn presigner_of(&self, pubkey: &Pubkey) -> Option<Presigner> {
|
||||
presigner_from_pubkey_sigs(pubkey, &self.present_signers)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_sign_only_reply_string(reply: &str) -> SignOnly {
|
||||
let object: Value = serde_json::from_str(&reply).unwrap();
|
||||
let blockhash_str = object.get("blockhash").unwrap().as_str().unwrap();
|
||||
let blockhash = blockhash_str.parse::<Hash>().unwrap();
|
||||
let signer_strings = object.get("signers").unwrap().as_array().unwrap();
|
||||
let signers = signer_strings
|
||||
let present_signers = signer_strings
|
||||
.iter()
|
||||
.map(|signer_string| {
|
||||
let mut signer = signer_string.as_str().unwrap().split('=');
|
||||
@ -66,5 +89,26 @@ pub fn parse_sign_only_reply_string(reply: &str) -> (Hash, Vec<(Pubkey, Signatur
|
||||
(key, sig)
|
||||
})
|
||||
.collect();
|
||||
(blockhash, signers)
|
||||
let signer_strings = object.get("absent").unwrap().as_array().unwrap();
|
||||
let absent_signers = signer_strings
|
||||
.iter()
|
||||
.map(|val| {
|
||||
let s = val.as_str().unwrap();
|
||||
Pubkey::from_str(s).unwrap()
|
||||
})
|
||||
.collect();
|
||||
let signer_strings = object.get("badSig").unwrap().as_array().unwrap();
|
||||
let bad_signers = signer_strings
|
||||
.iter()
|
||||
.map(|val| {
|
||||
let s = val.as_str().unwrap();
|
||||
Pubkey::from_str(s).unwrap()
|
||||
})
|
||||
.collect();
|
||||
SignOnly {
|
||||
blockhash,
|
||||
present_signers,
|
||||
absent_signers,
|
||||
bad_signers,
|
||||
}
|
||||
}
|
||||
|
672
cli/src/stake.rs
672
cli/src/stake.rs
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,7 @@
|
||||
use crate::cli::{
|
||||
check_account_for_fee, check_unique_pubkeys, log_instruction_custom_error, CliCommand,
|
||||
CliCommandInfo, CliConfig, CliError, ProcessResult, SignerIndex,
|
||||
check_account_for_fee, check_unique_pubkeys, generate_unique_signers,
|
||||
log_instruction_custom_error, CliCommand, CliCommandInfo, CliConfig, CliError, ProcessResult,
|
||||
SignerIndex,
|
||||
};
|
||||
use clap::{App, Arg, ArgMatches, SubCommand};
|
||||
use solana_clap_utils::{input_parsers::*, input_validators::*, keypair::signer_from_path};
|
||||
@ -25,18 +26,18 @@ impl StorageSubCommands for App<'_, '_> {
|
||||
.arg(
|
||||
Arg::with_name("storage_account_owner")
|
||||
.index(1)
|
||||
.value_name("STORAGE ACCOUNT OWNER PUBKEY")
|
||||
.value_name("AUTHORITY_PUBKEY")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.validator(is_pubkey_or_keypair),
|
||||
.validator(is_valid_pubkey),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("storage_account")
|
||||
.index(2)
|
||||
.value_name("STORAGE ACCOUNT")
|
||||
.value_name("ACCOUNT_KEYPAIR")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.validator(is_keypair_or_ask_keyword),
|
||||
.validator(is_valid_signer),
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
@ -45,18 +46,18 @@ impl StorageSubCommands for App<'_, '_> {
|
||||
.arg(
|
||||
Arg::with_name("storage_account_owner")
|
||||
.index(1)
|
||||
.value_name("STORAGE ACCOUNT OWNER PUBKEY")
|
||||
.value_name("AUTHORITY_PUBKEY")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.validator(is_pubkey_or_keypair),
|
||||
.validator(is_valid_pubkey),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("storage_account")
|
||||
.index(2)
|
||||
.value_name("STORAGE ACCOUNT")
|
||||
.value_name("ACCOUNT_KEYPAIR")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.validator(is_keypair_or_ask_keyword),
|
||||
.validator(is_valid_signer),
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
@ -65,19 +66,19 @@ impl StorageSubCommands for App<'_, '_> {
|
||||
.arg(
|
||||
Arg::with_name("node_account_pubkey")
|
||||
.index(1)
|
||||
.value_name("NODE PUBKEY")
|
||||
.value_name("NODE_ACCOUNT_ADDRESS")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.validator(is_pubkey_or_keypair)
|
||||
.validator(is_valid_pubkey)
|
||||
.help("The node account to credit the rewards to"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("storage_account_pubkey")
|
||||
.index(2)
|
||||
.value_name("STORAGE ACCOUNT PUBKEY")
|
||||
.value_name("STORAGE_ACCOUNT_ADDRESS")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.validator(is_pubkey_or_keypair)
|
||||
.validator(is_valid_pubkey)
|
||||
.help("Storage account address to redeem credits for"),
|
||||
),
|
||||
)
|
||||
@ -88,11 +89,11 @@ impl StorageSubCommands for App<'_, '_> {
|
||||
.arg(
|
||||
Arg::with_name("storage_account_pubkey")
|
||||
.index(1)
|
||||
.value_name("STORAGE ACCOUNT PUBKEY")
|
||||
.value_name("STORAGE_ACCOUNT_ADDRESS")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.validator(is_pubkey_or_keypair)
|
||||
.help("Storage account pubkey"),
|
||||
.validator(is_valid_pubkey)
|
||||
.help("Storage account address"),
|
||||
),
|
||||
)
|
||||
}
|
||||
@ -103,18 +104,26 @@ pub fn parse_storage_create_archiver_account(
|
||||
default_signer_path: &str,
|
||||
wallet_manager: Option<&Arc<RemoteWalletManager>>,
|
||||
) -> Result<CliCommandInfo, CliError> {
|
||||
let account_owner = pubkey_of(matches, "storage_account_owner").unwrap();
|
||||
let storage_account = keypair_of(matches, "storage_account").unwrap();
|
||||
let account_owner =
|
||||
pubkey_of_signer(matches, "storage_account_owner", wallet_manager)?.unwrap();
|
||||
let (storage_account, storage_account_pubkey) =
|
||||
signer_of(matches, "storage_account", wallet_manager)?;
|
||||
|
||||
let payer_provided = None;
|
||||
let signer_info = generate_unique_signers(
|
||||
vec![payer_provided, storage_account],
|
||||
matches,
|
||||
default_signer_path,
|
||||
wallet_manager,
|
||||
)?;
|
||||
|
||||
Ok(CliCommandInfo {
|
||||
command: CliCommand::CreateStorageAccount {
|
||||
account_owner,
|
||||
storage_account: 1,
|
||||
storage_account: signer_info.index_of(storage_account_pubkey).unwrap(),
|
||||
account_type: StorageAccountType::Archiver,
|
||||
},
|
||||
signers: vec![
|
||||
signer_from_path(matches, default_signer_path, "keypair", wallet_manager)?,
|
||||
storage_account.into(),
|
||||
],
|
||||
signers: signer_info.signers,
|
||||
})
|
||||
}
|
||||
|
||||
@ -123,18 +132,26 @@ pub fn parse_storage_create_validator_account(
|
||||
default_signer_path: &str,
|
||||
wallet_manager: Option<&Arc<RemoteWalletManager>>,
|
||||
) -> Result<CliCommandInfo, CliError> {
|
||||
let account_owner = pubkey_of(matches, "storage_account_owner").unwrap();
|
||||
let storage_account = keypair_of(matches, "storage_account").unwrap();
|
||||
let account_owner =
|
||||
pubkey_of_signer(matches, "storage_account_owner", wallet_manager)?.unwrap();
|
||||
let (storage_account, storage_account_pubkey) =
|
||||
signer_of(matches, "storage_account", wallet_manager)?;
|
||||
|
||||
let payer_provided = None;
|
||||
let signer_info = generate_unique_signers(
|
||||
vec![payer_provided, storage_account],
|
||||
matches,
|
||||
default_signer_path,
|
||||
wallet_manager,
|
||||
)?;
|
||||
|
||||
Ok(CliCommandInfo {
|
||||
command: CliCommand::CreateStorageAccount {
|
||||
account_owner,
|
||||
storage_account: 1,
|
||||
storage_account: signer_info.index_of(storage_account_pubkey).unwrap(),
|
||||
account_type: StorageAccountType::Validator,
|
||||
},
|
||||
signers: vec![
|
||||
signer_from_path(matches, default_signer_path, "keypair", wallet_manager)?,
|
||||
storage_account.into(),
|
||||
],
|
||||
signers: signer_info.signers,
|
||||
})
|
||||
}
|
||||
|
||||
@ -143,8 +160,10 @@ pub fn parse_storage_claim_reward(
|
||||
default_signer_path: &str,
|
||||
wallet_manager: Option<&Arc<RemoteWalletManager>>,
|
||||
) -> Result<CliCommandInfo, CliError> {
|
||||
let node_account_pubkey = pubkey_of(matches, "node_account_pubkey").unwrap();
|
||||
let storage_account_pubkey = pubkey_of(matches, "storage_account_pubkey").unwrap();
|
||||
let node_account_pubkey =
|
||||
pubkey_of_signer(matches, "node_account_pubkey", wallet_manager)?.unwrap();
|
||||
let storage_account_pubkey =
|
||||
pubkey_of_signer(matches, "storage_account_pubkey", wallet_manager)?.unwrap();
|
||||
Ok(CliCommandInfo {
|
||||
command: CliCommand::ClaimStorageReward {
|
||||
node_account_pubkey,
|
||||
@ -161,8 +180,10 @@ pub fn parse_storage_claim_reward(
|
||||
|
||||
pub fn parse_storage_get_account_command(
|
||||
matches: &ArgMatches<'_>,
|
||||
wallet_manager: Option<&Arc<RemoteWalletManager>>,
|
||||
) -> Result<CliCommandInfo, CliError> {
|
||||
let storage_account_pubkey = pubkey_of(matches, "storage_account_pubkey").unwrap();
|
||||
let storage_account_pubkey =
|
||||
pubkey_of_signer(matches, "storage_account_pubkey", wallet_manager)?.unwrap();
|
||||
Ok(CliCommandInfo {
|
||||
command: CliCommand::ShowStorageAccount(storage_account_pubkey),
|
||||
signers: vec![],
|
||||
@ -221,7 +242,7 @@ pub fn process_create_storage_account(
|
||||
&fee_calculator,
|
||||
&tx.message,
|
||||
)?;
|
||||
let result = rpc_client.send_and_confirm_transaction(&mut tx, &config.signers);
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner(&mut tx, &config.signers);
|
||||
log_instruction_custom_error::<SystemError>(result)
|
||||
}
|
||||
|
||||
@ -245,7 +266,7 @@ pub fn process_claim_storage_reward(
|
||||
&fee_calculator,
|
||||
&tx.message,
|
||||
)?;
|
||||
let signature_str = rpc_client.send_and_confirm_transaction(&mut tx, &signers)?;
|
||||
let signature_str = rpc_client.send_and_confirm_transaction_with_spinner(&mut tx, &signers)?;
|
||||
Ok(signature_str)
|
||||
}
|
||||
|
||||
|
@ -368,7 +368,7 @@ pub fn process_set_validator_info(
|
||||
&fee_calculator,
|
||||
&tx.message,
|
||||
)?;
|
||||
let signature_str = rpc_client.send_and_confirm_transaction(&mut tx, &signers)?;
|
||||
let signature_str = rpc_client.send_and_confirm_transaction_with_spinner(&mut tx, &signers)?;
|
||||
|
||||
println!("Success! Validator info published at: {:?}", info_pubkey);
|
||||
println!("{}", signature_str);
|
||||
|
395
cli/src/vote.rs
395
cli/src/vote.rs
@ -1,7 +1,7 @@
|
||||
use crate::cli::{
|
||||
build_balance_message, check_account_for_fee, check_unique_pubkeys, generate_unique_signers,
|
||||
log_instruction_custom_error, CliCommand, CliCommandInfo, CliConfig, CliError, CliSignerInfo,
|
||||
ProcessResult,
|
||||
ProcessResult, SignerIndex,
|
||||
};
|
||||
use clap::{value_t_or_exit, App, Arg, ArgMatches, SubCommand};
|
||||
use solana_clap_utils::{input_parsers::*, input_validators::*};
|
||||
@ -16,7 +16,7 @@ use solana_sdk::{
|
||||
transaction::Transaction,
|
||||
};
|
||||
use solana_vote_program::{
|
||||
vote_instruction::{self, VoteError},
|
||||
vote_instruction::{self, withdraw, VoteError},
|
||||
vote_state::{VoteAuthorize, VoteInit, VoteState},
|
||||
};
|
||||
use std::sync::Arc;
|
||||
@ -33,25 +33,25 @@ impl VoteSubCommands for App<'_, '_> {
|
||||
.arg(
|
||||
Arg::with_name("vote_account")
|
||||
.index(1)
|
||||
.value_name("VOTE ACCOUNT KEYPAIR")
|
||||
.value_name("ACCOUNT_KEYPAIR")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.validator(is_keypair_or_ask_keyword)
|
||||
.help("Vote account keypair to fund"),
|
||||
.validator(is_valid_signer)
|
||||
.help("Vote account keypair to create"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("identity_pubkey")
|
||||
Arg::with_name("identity_account")
|
||||
.index(2)
|
||||
.value_name("VALIDATOR IDENTITY PUBKEY")
|
||||
.value_name("IDENTITY_KEYPAIR")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.validator(is_pubkey_or_keypair)
|
||||
.help("Validator that will vote with this account"),
|
||||
.validator(is_valid_signer)
|
||||
.help("Keypair of validator that will vote with this account"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("commission")
|
||||
.long("commission")
|
||||
.value_name("NUM")
|
||||
.value_name("PERCENTAGE")
|
||||
.takes_value(true)
|
||||
.default_value("100")
|
||||
.help("The commission taken on reward redemption (0-100)"),
|
||||
@ -59,78 +59,47 @@ impl VoteSubCommands for App<'_, '_> {
|
||||
.arg(
|
||||
Arg::with_name("authorized_voter")
|
||||
.long("authorized-voter")
|
||||
.value_name("PUBKEY")
|
||||
.value_name("VOTER_PUBKEY")
|
||||
.takes_value(true)
|
||||
.validator(is_pubkey_or_keypair)
|
||||
.help("Public key of the authorized voter (defaults to vote account)"),
|
||||
.validator(is_valid_pubkey)
|
||||
.help("Public key of the authorized voter [default: validator identity pubkey]"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("authorized_withdrawer")
|
||||
.long("authorized-withdrawer")
|
||||
.value_name("PUBKEY")
|
||||
.value_name("WITHDRAWER_PUBKEY")
|
||||
.takes_value(true)
|
||||
.validator(is_pubkey_or_keypair)
|
||||
.help("Public key of the authorized withdrawer (defaults to cli config pubkey)"),
|
||||
.validator(is_valid_pubkey)
|
||||
.help("Public key of the authorized withdrawer [default: validator identity pubkey]"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("seed")
|
||||
.long("seed")
|
||||
.value_name("SEED STRING")
|
||||
.value_name("STRING")
|
||||
.takes_value(true)
|
||||
.help("Seed for address generation; if specified, the resulting account will be at a derived address of the VOTE ACCOUNT pubkey")
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("vote-update-validator")
|
||||
.about("Update the vote account's validator identity")
|
||||
.arg(
|
||||
Arg::with_name("vote_account_pubkey")
|
||||
.index(1)
|
||||
.value_name("VOTE ACCOUNT PUBKEY")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.validator(is_pubkey_or_keypair)
|
||||
.help("Vote account to update"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("new_identity_pubkey")
|
||||
.index(2)
|
||||
.value_name("NEW VALIDATOR IDENTITY PUBKEY")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.validator(is_pubkey_or_keypair)
|
||||
.help("New validator that will vote with this account"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("authorized_voter")
|
||||
.index(3)
|
||||
.value_name("AUTHORIZED VOTER KEYPAIR")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.validator(is_keypair)
|
||||
.help("Authorized voter keypair"),
|
||||
)
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("vote-authorize-voter")
|
||||
.about("Authorize a new vote signing keypair for the given vote account")
|
||||
.arg(
|
||||
Arg::with_name("vote_account_pubkey")
|
||||
.index(1)
|
||||
.value_name("VOTE ACCOUNT PUBKEY")
|
||||
.value_name("VOTE_ACCOUNT_ADDRESS")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.validator(is_pubkey_or_keypair)
|
||||
.validator(is_valid_pubkey)
|
||||
.help("Vote account in which to set the authorized voter"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("new_authorized_pubkey")
|
||||
.index(2)
|
||||
.value_name("NEW VOTER PUBKEY")
|
||||
.value_name("AUTHORIZED_PUBKEY")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.validator(is_pubkey_or_keypair)
|
||||
.help("New vote signer to authorize"),
|
||||
.validator(is_valid_pubkey)
|
||||
.help("New authorized vote signer"),
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
@ -139,22 +108,53 @@ impl VoteSubCommands for App<'_, '_> {
|
||||
.arg(
|
||||
Arg::with_name("vote_account_pubkey")
|
||||
.index(1)
|
||||
.value_name("VOTE ACCOUNT PUBKEY")
|
||||
.value_name("VOTE_ACCOUNT_ADDRESS")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.validator(is_pubkey_or_keypair)
|
||||
.validator(is_valid_pubkey)
|
||||
.help("Vote account in which to set the authorized withdrawer"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("new_authorized_pubkey")
|
||||
.index(2)
|
||||
.value_name("NEW WITHDRAWER PUBKEY")
|
||||
.value_name("AUTHORIZED_PUBKEY")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.validator(is_pubkey_or_keypair)
|
||||
.help("New withdrawer to authorize"),
|
||||
.validator(is_valid_pubkey)
|
||||
.help("New authorized withdrawer"),
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("vote-update-validator")
|
||||
.about("Update the vote account's validator identity")
|
||||
.arg(
|
||||
Arg::with_name("vote_account_pubkey")
|
||||
.index(1)
|
||||
.value_name("VOTE_ACCOUNT_ADDRESS")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.validator(is_valid_pubkey)
|
||||
.help("Vote account to update"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("new_identity_account")
|
||||
.index(2)
|
||||
.value_name("IDENTITY_KEYPAIR")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.validator(is_valid_signer)
|
||||
.help("Keypair of new validator that will vote with this account"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("authorized_withdrawer")
|
||||
.index(3)
|
||||
.value_name("AUTHORIZED_KEYPAIR")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.validator(is_valid_signer)
|
||||
.help("Authorized withdrawer keypair"),
|
||||
)
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("vote-account")
|
||||
.about("Show the contents of a vote account")
|
||||
@ -170,10 +170,10 @@ impl VoteSubCommands for App<'_, '_> {
|
||||
.arg(
|
||||
Arg::with_name("vote_account_pubkey")
|
||||
.index(1)
|
||||
.value_name("VOTE ACCOUNT PUBKEY")
|
||||
.value_name("VOTE_ACCOUNT_ADDRESS")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.validator(is_pubkey_or_keypair)
|
||||
.validator(is_valid_pubkey)
|
||||
.help("Vote account pubkey"),
|
||||
)
|
||||
.arg(
|
||||
@ -183,24 +183,64 @@ impl VoteSubCommands for App<'_, '_> {
|
||||
.help("Display balance in lamports instead of SOL"),
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("withdraw-from-vote-account")
|
||||
.about("Withdraw lamports from a vote account into a specified account")
|
||||
.arg(
|
||||
Arg::with_name("vote_account_pubkey")
|
||||
.index(1)
|
||||
.value_name("VOTE_ACCOUNT_ADDRESS")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.validator(is_valid_pubkey)
|
||||
.help("Vote account from which to withdraw"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("destination_account_pubkey")
|
||||
.index(2)
|
||||
.value_name("RECIPIENT_ADDRESS")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.validator(is_valid_pubkey)
|
||||
.help("The recipient of withdrawn SOL"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("amount")
|
||||
.index(3)
|
||||
.value_name("AMOUNT")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.validator(is_amount)
|
||||
.help("The amount to withdraw, in SOL"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("authorized_withdrawer")
|
||||
.long("authorized-withdrawer")
|
||||
.value_name("AUTHORIZED_KEYPAIR")
|
||||
.takes_value(true)
|
||||
.validator(is_valid_signer)
|
||||
.help("Authorized withdrawer [default: cli config keypair]"),
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_vote_create_account(
|
||||
pub fn parse_create_vote_account(
|
||||
matches: &ArgMatches<'_>,
|
||||
default_signer_path: &str,
|
||||
wallet_manager: Option<&Arc<RemoteWalletManager>>,
|
||||
) -> Result<CliCommandInfo, CliError> {
|
||||
let (vote_account, _) = signer_of(matches, "vote_account", wallet_manager)?;
|
||||
let seed = matches.value_of("seed").map(|s| s.to_string());
|
||||
let identity_pubkey = pubkey_of(matches, "identity_pubkey").unwrap();
|
||||
let (identity_account, identity_pubkey) =
|
||||
signer_of(matches, "identity_account", wallet_manager)?;
|
||||
let commission = value_t_or_exit!(matches, "commission", u8);
|
||||
let authorized_voter = pubkey_of(matches, "authorized_voter");
|
||||
let authorized_withdrawer = pubkey_of(matches, "authorized_withdrawer");
|
||||
let authorized_voter = pubkey_of_signer(matches, "authorized_voter", wallet_manager)?;
|
||||
let authorized_withdrawer = pubkey_of_signer(matches, "authorized_withdrawer", wallet_manager)?;
|
||||
|
||||
let payer_provided = None;
|
||||
let CliSignerInfo { signers } = generate_unique_signers(
|
||||
vec![payer_provided, vote_account],
|
||||
let signer_info = generate_unique_signers(
|
||||
vec![payer_provided, vote_account, identity_account],
|
||||
matches,
|
||||
default_signer_path,
|
||||
wallet_manager,
|
||||
@ -209,12 +249,12 @@ pub fn parse_vote_create_account(
|
||||
Ok(CliCommandInfo {
|
||||
command: CliCommand::CreateVoteAccount {
|
||||
seed,
|
||||
node_pubkey: identity_pubkey,
|
||||
identity_account: signer_info.index_of(identity_pubkey).unwrap(),
|
||||
authorized_voter,
|
||||
authorized_withdrawer,
|
||||
commission,
|
||||
},
|
||||
signers,
|
||||
signers: signer_info.signers,
|
||||
})
|
||||
}
|
||||
|
||||
@ -224,8 +264,10 @@ pub fn parse_vote_authorize(
|
||||
wallet_manager: Option<&Arc<RemoteWalletManager>>,
|
||||
vote_authorize: VoteAuthorize,
|
||||
) -> Result<CliCommandInfo, CliError> {
|
||||
let vote_account_pubkey = pubkey_of(matches, "vote_account_pubkey").unwrap();
|
||||
let new_authorized_pubkey = pubkey_of(matches, "new_authorized_pubkey").unwrap();
|
||||
let vote_account_pubkey =
|
||||
pubkey_of_signer(matches, "vote_account_pubkey", wallet_manager)?.unwrap();
|
||||
let new_authorized_pubkey =
|
||||
pubkey_of_signer(matches, "new_authorized_pubkey", wallet_manager)?.unwrap();
|
||||
|
||||
let authorized_voter_provided = None;
|
||||
let CliSignerInfo { signers } = generate_unique_signers(
|
||||
@ -250,13 +292,15 @@ pub fn parse_vote_update_validator(
|
||||
default_signer_path: &str,
|
||||
wallet_manager: Option<&Arc<RemoteWalletManager>>,
|
||||
) -> Result<CliCommandInfo, CliError> {
|
||||
let vote_account_pubkey = pubkey_of(matches, "vote_account_pubkey").unwrap();
|
||||
let new_identity_pubkey = pubkey_of(matches, "new_identity_pubkey").unwrap();
|
||||
let (authorized_voter, _) = signer_of(matches, "authorized_voter", wallet_manager)?;
|
||||
let vote_account_pubkey =
|
||||
pubkey_of_signer(matches, "vote_account_pubkey", wallet_manager)?.unwrap();
|
||||
let (new_identity_account, new_identity_pubkey) =
|
||||
signer_of(matches, "new_identity_account", wallet_manager)?;
|
||||
let (authorized_withdrawer, _) = signer_of(matches, "authorized_withdrawer", wallet_manager)?;
|
||||
|
||||
let payer_provided = None;
|
||||
let CliSignerInfo { signers } = generate_unique_signers(
|
||||
vec![payer_provided, authorized_voter],
|
||||
let signer_info = generate_unique_signers(
|
||||
vec![payer_provided, authorized_withdrawer, new_identity_account],
|
||||
matches,
|
||||
default_signer_path,
|
||||
wallet_manager,
|
||||
@ -265,16 +309,18 @@ pub fn parse_vote_update_validator(
|
||||
Ok(CliCommandInfo {
|
||||
command: CliCommand::VoteUpdateValidator {
|
||||
vote_account_pubkey,
|
||||
new_identity_pubkey,
|
||||
new_identity_account: signer_info.index_of(new_identity_pubkey).unwrap(),
|
||||
},
|
||||
signers,
|
||||
signers: signer_info.signers,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn parse_vote_get_account_command(
|
||||
matches: &ArgMatches<'_>,
|
||||
wallet_manager: Option<&Arc<RemoteWalletManager>>,
|
||||
) -> Result<CliCommandInfo, CliError> {
|
||||
let vote_account_pubkey = pubkey_of(matches, "vote_account_pubkey").unwrap();
|
||||
let vote_account_pubkey =
|
||||
pubkey_of_signer(matches, "vote_account_pubkey", wallet_manager)?.unwrap();
|
||||
let use_lamports_unit = matches.is_present("lamports");
|
||||
let commitment_config = if matches.is_present("confirmed") {
|
||||
CommitmentConfig::default()
|
||||
@ -291,11 +337,43 @@ pub fn parse_vote_get_account_command(
|
||||
})
|
||||
}
|
||||
|
||||
pub fn parse_withdraw_from_vote_account(
|
||||
matches: &ArgMatches<'_>,
|
||||
default_signer_path: &str,
|
||||
wallet_manager: Option<&Arc<RemoteWalletManager>>,
|
||||
) -> Result<CliCommandInfo, CliError> {
|
||||
let vote_account_pubkey =
|
||||
pubkey_of_signer(matches, "vote_account_pubkey", wallet_manager)?.unwrap();
|
||||
let destination_account_pubkey =
|
||||
pubkey_of_signer(matches, "destination_account_pubkey", wallet_manager)?.unwrap();
|
||||
let lamports = lamports_of_sol(matches, "amount").unwrap();
|
||||
let (withdraw_authority, withdraw_authority_pubkey) =
|
||||
signer_of(matches, "authorized_withdrawer", wallet_manager)?;
|
||||
|
||||
let payer_provided = None;
|
||||
let signer_info = generate_unique_signers(
|
||||
vec![payer_provided, withdraw_authority],
|
||||
matches,
|
||||
default_signer_path,
|
||||
wallet_manager,
|
||||
)?;
|
||||
|
||||
Ok(CliCommandInfo {
|
||||
command: CliCommand::WithdrawFromVoteAccount {
|
||||
vote_account_pubkey,
|
||||
destination_account_pubkey,
|
||||
withdraw_authority: signer_info.index_of(withdraw_authority_pubkey).unwrap(),
|
||||
lamports,
|
||||
},
|
||||
signers: signer_info.signers,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn process_create_vote_account(
|
||||
rpc_client: &RpcClient,
|
||||
config: &CliConfig,
|
||||
seed: &Option<String>,
|
||||
identity_pubkey: &Pubkey,
|
||||
identity_account: SignerIndex,
|
||||
authorized_voter: &Option<Pubkey>,
|
||||
authorized_withdrawer: &Option<Pubkey>,
|
||||
commission: u8,
|
||||
@ -312,6 +390,8 @@ pub fn process_create_vote_account(
|
||||
(&vote_account_address, "vote_account".to_string()),
|
||||
)?;
|
||||
|
||||
let identity_account = config.signers[identity_account];
|
||||
let identity_pubkey = identity_account.pubkey();
|
||||
check_unique_pubkeys(
|
||||
(&vote_account_address, "vote_account".to_string()),
|
||||
(&identity_pubkey, "identity_pubkey".to_string()),
|
||||
@ -334,9 +414,9 @@ pub fn process_create_vote_account(
|
||||
.max(1);
|
||||
|
||||
let vote_init = VoteInit {
|
||||
node_pubkey: *identity_pubkey,
|
||||
authorized_voter: authorized_voter.unwrap_or(vote_account_pubkey),
|
||||
authorized_withdrawer: authorized_withdrawer.unwrap_or(vote_account_pubkey),
|
||||
node_pubkey: identity_pubkey,
|
||||
authorized_voter: authorized_voter.unwrap_or(identity_pubkey),
|
||||
authorized_withdrawer: authorized_withdrawer.unwrap_or(identity_pubkey),
|
||||
commission,
|
||||
};
|
||||
|
||||
@ -368,7 +448,7 @@ pub fn process_create_vote_account(
|
||||
&fee_calculator,
|
||||
&tx.message,
|
||||
)?;
|
||||
let result = rpc_client.send_and_confirm_transaction(&mut tx, &config.signers);
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner(&mut tx, &config.signers);
|
||||
log_instruction_custom_error::<SystemError>(result)
|
||||
}
|
||||
|
||||
@ -400,7 +480,8 @@ pub fn process_vote_authorize(
|
||||
&fee_calculator,
|
||||
&tx.message,
|
||||
)?;
|
||||
let result = rpc_client.send_and_confirm_transaction(&mut tx, &[config.signers[0]]);
|
||||
let result =
|
||||
rpc_client.send_and_confirm_transaction_with_spinner(&mut tx, &[config.signers[0]]);
|
||||
log_instruction_custom_error::<VoteError>(result)
|
||||
}
|
||||
|
||||
@ -408,18 +489,20 @@ pub fn process_vote_update_validator(
|
||||
rpc_client: &RpcClient,
|
||||
config: &CliConfig,
|
||||
vote_account_pubkey: &Pubkey,
|
||||
new_identity_pubkey: &Pubkey,
|
||||
new_identity_account: SignerIndex,
|
||||
) -> ProcessResult {
|
||||
let authorized_voter = config.signers[1];
|
||||
let authorized_withdrawer = config.signers[1];
|
||||
let new_identity_account = config.signers[new_identity_account];
|
||||
let new_identity_pubkey = new_identity_account.pubkey();
|
||||
check_unique_pubkeys(
|
||||
(vote_account_pubkey, "vote_account_pubkey".to_string()),
|
||||
(new_identity_pubkey, "new_identity_pubkey".to_string()),
|
||||
(&new_identity_pubkey, "new_identity_account".to_string()),
|
||||
)?;
|
||||
let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?;
|
||||
let ixs = vec![vote_instruction::update_node(
|
||||
vote_account_pubkey,
|
||||
&authorized_voter.pubkey(),
|
||||
new_identity_pubkey,
|
||||
&authorized_withdrawer.pubkey(),
|
||||
&new_identity_pubkey,
|
||||
)];
|
||||
|
||||
let message = Message::new_with_payer(&ixs, Some(&config.signers[0].pubkey()));
|
||||
@ -431,7 +514,7 @@ pub fn process_vote_update_validator(
|
||||
&fee_calculator,
|
||||
&tx.message,
|
||||
)?;
|
||||
let result = rpc_client.send_and_confirm_transaction(&mut tx, &config.signers);
|
||||
let result = rpc_client.send_and_confirm_transaction_with_spinner(&mut tx, &config.signers);
|
||||
log_instruction_custom_error::<VoteError>(result)
|
||||
}
|
||||
|
||||
@ -517,6 +600,38 @@ pub fn process_show_vote_account(
|
||||
Ok("".to_string())
|
||||
}
|
||||
|
||||
pub fn process_withdraw_from_vote_account(
|
||||
rpc_client: &RpcClient,
|
||||
config: &CliConfig,
|
||||
vote_account_pubkey: &Pubkey,
|
||||
withdraw_authority: SignerIndex,
|
||||
lamports: u64,
|
||||
destination_account_pubkey: &Pubkey,
|
||||
) -> ProcessResult {
|
||||
let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?;
|
||||
let withdraw_authority = config.signers[withdraw_authority];
|
||||
|
||||
let ix = withdraw(
|
||||
vote_account_pubkey,
|
||||
&withdraw_authority.pubkey(),
|
||||
lamports,
|
||||
destination_account_pubkey,
|
||||
);
|
||||
|
||||
let message = Message::new_with_payer(&[ix], Some(&config.signers[0].pubkey()));
|
||||
let mut transaction = Transaction::new_unsigned(message);
|
||||
transaction.try_sign(&config.signers, recent_blockhash)?;
|
||||
check_account_for_fee(
|
||||
rpc_client,
|
||||
&config.signers[0].pubkey(),
|
||||
&fee_calculator,
|
||||
&transaction.message,
|
||||
)?;
|
||||
let result =
|
||||
rpc_client.send_and_confirm_transaction_with_spinner(&mut transaction, &config.signers);
|
||||
log_instruction_custom_error::<VoteError>(result)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@ -565,13 +680,14 @@ mod tests {
|
||||
let keypair = Keypair::new();
|
||||
write_keypair(&keypair, tmp_file.as_file_mut()).unwrap();
|
||||
// Test CreateVoteAccount SubCommand
|
||||
let node_pubkey = Pubkey::new_rand();
|
||||
let node_pubkey_string = format!("{}", node_pubkey);
|
||||
let (identity_keypair_file, mut tmp_file) = make_tmp_file();
|
||||
let identity_keypair = Keypair::new();
|
||||
write_keypair(&identity_keypair, tmp_file.as_file_mut()).unwrap();
|
||||
let test_create_vote_account = test_commands.clone().get_matches_from(vec![
|
||||
"test",
|
||||
"create-vote-account",
|
||||
&keypair_file,
|
||||
&node_pubkey_string,
|
||||
&identity_keypair_file,
|
||||
"--commission",
|
||||
"10",
|
||||
]);
|
||||
@ -580,14 +696,15 @@ mod tests {
|
||||
CliCommandInfo {
|
||||
command: CliCommand::CreateVoteAccount {
|
||||
seed: None,
|
||||
node_pubkey,
|
||||
identity_account: 2,
|
||||
authorized_voter: None,
|
||||
authorized_withdrawer: None,
|
||||
commission: 10,
|
||||
},
|
||||
signers: vec![
|
||||
read_keypair_file(&default_keypair_file).unwrap().into(),
|
||||
Box::new(keypair)
|
||||
Box::new(keypair),
|
||||
read_keypair_file(&identity_keypair_file).unwrap().into(),
|
||||
],
|
||||
}
|
||||
);
|
||||
@ -600,21 +717,22 @@ mod tests {
|
||||
"test",
|
||||
"create-vote-account",
|
||||
&keypair_file,
|
||||
&node_pubkey_string,
|
||||
&identity_keypair_file,
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(&test_create_vote_account2, &default_keypair_file, None).unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::CreateVoteAccount {
|
||||
seed: None,
|
||||
node_pubkey,
|
||||
identity_account: 2,
|
||||
authorized_voter: None,
|
||||
authorized_withdrawer: None,
|
||||
commission: 100,
|
||||
},
|
||||
signers: vec![
|
||||
read_keypair_file(&default_keypair_file).unwrap().into(),
|
||||
Box::new(keypair)
|
||||
Box::new(keypair),
|
||||
read_keypair_file(&identity_keypair_file).unwrap().into(),
|
||||
],
|
||||
}
|
||||
);
|
||||
@ -629,7 +747,7 @@ mod tests {
|
||||
"test",
|
||||
"create-vote-account",
|
||||
&keypair_file,
|
||||
&node_pubkey_string,
|
||||
&identity_keypair_file,
|
||||
"--authorized-voter",
|
||||
&authed.to_string(),
|
||||
]);
|
||||
@ -638,14 +756,15 @@ mod tests {
|
||||
CliCommandInfo {
|
||||
command: CliCommand::CreateVoteAccount {
|
||||
seed: None,
|
||||
node_pubkey,
|
||||
identity_account: 2,
|
||||
authorized_voter: Some(authed),
|
||||
authorized_withdrawer: None,
|
||||
commission: 100
|
||||
},
|
||||
signers: vec![
|
||||
read_keypair_file(&default_keypair_file).unwrap().into(),
|
||||
Box::new(keypair)
|
||||
Box::new(keypair),
|
||||
read_keypair_file(&identity_keypair_file).unwrap().into(),
|
||||
],
|
||||
}
|
||||
);
|
||||
@ -658,7 +777,7 @@ mod tests {
|
||||
"test",
|
||||
"create-vote-account",
|
||||
&keypair_file,
|
||||
&node_pubkey_string,
|
||||
&identity_keypair_file,
|
||||
"--authorized-withdrawer",
|
||||
&authed.to_string(),
|
||||
]);
|
||||
@ -667,14 +786,15 @@ mod tests {
|
||||
CliCommandInfo {
|
||||
command: CliCommand::CreateVoteAccount {
|
||||
seed: None,
|
||||
node_pubkey,
|
||||
identity_account: 2,
|
||||
authorized_voter: None,
|
||||
authorized_withdrawer: Some(authed),
|
||||
commission: 100
|
||||
},
|
||||
signers: vec![
|
||||
read_keypair_file(&default_keypair_file).unwrap().into(),
|
||||
Box::new(keypair)
|
||||
Box::new(keypair),
|
||||
read_keypair_file(&identity_keypair_file).unwrap().into(),
|
||||
],
|
||||
}
|
||||
);
|
||||
@ -683,7 +803,7 @@ mod tests {
|
||||
"test",
|
||||
"vote-update-validator",
|
||||
&pubkey_string,
|
||||
&pubkey2_string,
|
||||
&identity_keypair_file,
|
||||
&keypair_file,
|
||||
]);
|
||||
assert_eq!(
|
||||
@ -691,11 +811,72 @@ mod tests {
|
||||
CliCommandInfo {
|
||||
command: CliCommand::VoteUpdateValidator {
|
||||
vote_account_pubkey: pubkey,
|
||||
new_identity_pubkey: pubkey2,
|
||||
new_identity_account: 2,
|
||||
},
|
||||
signers: vec![
|
||||
read_keypair_file(&default_keypair_file).unwrap().into(),
|
||||
Box::new(read_keypair_file(&keypair_file).unwrap())
|
||||
Box::new(read_keypair_file(&keypair_file).unwrap()),
|
||||
read_keypair_file(&identity_keypair_file).unwrap().into(),
|
||||
],
|
||||
}
|
||||
);
|
||||
|
||||
// Test WithdrawFromVoteAccount subcommand
|
||||
let test_withdraw_from_vote_account = test_commands.clone().get_matches_from(vec![
|
||||
"test",
|
||||
"withdraw-from-vote-account",
|
||||
&keypair_file,
|
||||
&pubkey_string,
|
||||
"42",
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(
|
||||
&test_withdraw_from_vote_account,
|
||||
&default_keypair_file,
|
||||
None
|
||||
)
|
||||
.unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::WithdrawFromVoteAccount {
|
||||
vote_account_pubkey: read_keypair_file(&keypair_file).unwrap().pubkey(),
|
||||
destination_account_pubkey: pubkey,
|
||||
withdraw_authority: 0,
|
||||
lamports: 42_000_000_000
|
||||
},
|
||||
signers: vec![read_keypair_file(&default_keypair_file).unwrap().into()],
|
||||
}
|
||||
);
|
||||
|
||||
// Test WithdrawFromVoteAccount subcommand with authority
|
||||
let withdraw_authority = Keypair::new();
|
||||
let (withdraw_authority_file, mut tmp_file) = make_tmp_file();
|
||||
write_keypair(&withdraw_authority, tmp_file.as_file_mut()).unwrap();
|
||||
let test_withdraw_from_vote_account = test_commands.clone().get_matches_from(vec![
|
||||
"test",
|
||||
"withdraw-from-vote-account",
|
||||
&keypair_file,
|
||||
&pubkey_string,
|
||||
"42",
|
||||
"--authorized-withdrawer",
|
||||
&withdraw_authority_file,
|
||||
]);
|
||||
assert_eq!(
|
||||
parse_command(
|
||||
&test_withdraw_from_vote_account,
|
||||
&default_keypair_file,
|
||||
None
|
||||
)
|
||||
.unwrap(),
|
||||
CliCommandInfo {
|
||||
command: CliCommand::WithdrawFromVoteAccount {
|
||||
vote_account_pubkey: read_keypair_file(&keypair_file).unwrap().pubkey(),
|
||||
destination_account_pubkey: pubkey,
|
||||
withdraw_authority: 1,
|
||||
lamports: 42_000_000_000
|
||||
},
|
||||
signers: vec![
|
||||
read_keypair_file(&default_keypair_file).unwrap().into(),
|
||||
read_keypair_file(&withdraw_authority_file).unwrap().into()
|
||||
],
|
||||
}
|
||||
);
|
||||
|
@ -1,6 +1,13 @@
|
||||
use solana_cli::cli::{process_command, request_and_confirm_airdrop, CliCommand, CliConfig};
|
||||
use solana_cli::{
|
||||
cli::{process_command, request_and_confirm_airdrop, CliCommand, CliConfig},
|
||||
nonce,
|
||||
offline::{
|
||||
blockhash_query::{self, BlockhashQuery},
|
||||
parse_sign_only_reply_string,
|
||||
},
|
||||
};
|
||||
use solana_client::rpc_client::RpcClient;
|
||||
use solana_core::validator::TestValidator;
|
||||
use solana_core::validator::{TestValidator, TestValidatorOptions};
|
||||
use solana_faucet::faucet::run_local_faucet;
|
||||
use solana_sdk::{
|
||||
hash::Hash,
|
||||
@ -247,3 +254,124 @@ fn full_battery_tests(
|
||||
check_balance(800, &rpc_client, &nonce_account);
|
||||
check_balance(200, &rpc_client, &payee_pubkey);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_create_account_with_seed() {
|
||||
let TestValidator {
|
||||
server,
|
||||
leader_data,
|
||||
alice: mint_keypair,
|
||||
ledger_path,
|
||||
..
|
||||
} = TestValidator::run_with_options(TestValidatorOptions {
|
||||
fees: 1,
|
||||
bootstrap_validator_lamports: 42_000,
|
||||
});
|
||||
|
||||
let (sender, receiver) = channel();
|
||||
run_local_faucet(mint_keypair, sender, None);
|
||||
let faucet_addr = receiver.recv().unwrap();
|
||||
|
||||
let offline_nonce_authority_signer = keypair_from_seed(&[1u8; 32]).unwrap();
|
||||
let online_nonce_creator_signer = keypair_from_seed(&[2u8; 32]).unwrap();
|
||||
let to_address = Pubkey::new(&[3u8; 32]);
|
||||
|
||||
// Setup accounts
|
||||
let rpc_client = RpcClient::new_socket(leader_data.rpc);
|
||||
request_and_confirm_airdrop(
|
||||
&rpc_client,
|
||||
&faucet_addr,
|
||||
&offline_nonce_authority_signer.pubkey(),
|
||||
42,
|
||||
)
|
||||
.unwrap();
|
||||
request_and_confirm_airdrop(
|
||||
&rpc_client,
|
||||
&faucet_addr,
|
||||
&online_nonce_creator_signer.pubkey(),
|
||||
4242,
|
||||
)
|
||||
.unwrap();
|
||||
check_balance(42, &rpc_client, &offline_nonce_authority_signer.pubkey());
|
||||
check_balance(4242, &rpc_client, &online_nonce_creator_signer.pubkey());
|
||||
check_balance(0, &rpc_client, &to_address);
|
||||
|
||||
// Create nonce account
|
||||
let creator_pubkey = online_nonce_creator_signer.pubkey();
|
||||
let authority_pubkey = offline_nonce_authority_signer.pubkey();
|
||||
let seed = authority_pubkey.to_string()[0..32].to_string();
|
||||
let nonce_address =
|
||||
create_address_with_seed(&creator_pubkey, &seed, &system_program::id()).unwrap();
|
||||
check_balance(0, &rpc_client, &nonce_address);
|
||||
|
||||
let mut creator_config = CliConfig::default();
|
||||
creator_config.json_rpc_url =
|
||||
format!("http://{}:{}", leader_data.rpc.ip(), leader_data.rpc.port());
|
||||
creator_config.signers = vec![&online_nonce_creator_signer];
|
||||
creator_config.command = CliCommand::CreateNonceAccount {
|
||||
nonce_account: 0,
|
||||
seed: Some(seed),
|
||||
nonce_authority: Some(authority_pubkey),
|
||||
lamports: 241,
|
||||
};
|
||||
process_command(&creator_config).unwrap();
|
||||
check_balance(241, &rpc_client, &nonce_address);
|
||||
check_balance(42, &rpc_client, &offline_nonce_authority_signer.pubkey());
|
||||
check_balance(4000, &rpc_client, &online_nonce_creator_signer.pubkey());
|
||||
check_balance(0, &rpc_client, &to_address);
|
||||
|
||||
// Fetch nonce hash
|
||||
let nonce_hash = nonce::get_account(&rpc_client, &nonce_address)
|
||||
.and_then(|ref a| nonce::data_from_account(a))
|
||||
.unwrap()
|
||||
.blockhash;
|
||||
|
||||
// Test by creating transfer TX with nonce, fully offline
|
||||
let mut authority_config = CliConfig::default();
|
||||
authority_config.json_rpc_url = String::default();
|
||||
authority_config.signers = vec![&offline_nonce_authority_signer];
|
||||
// Verify we cannot contact the cluster
|
||||
authority_config.command = CliCommand::ClusterVersion;
|
||||
process_command(&authority_config).unwrap_err();
|
||||
authority_config.command = CliCommand::Transfer {
|
||||
lamports: 10,
|
||||
to: to_address,
|
||||
from: 0,
|
||||
sign_only: true,
|
||||
blockhash_query: BlockhashQuery::None(nonce_hash),
|
||||
nonce_account: Some(nonce_address),
|
||||
nonce_authority: 0,
|
||||
fee_payer: 0,
|
||||
};
|
||||
let sign_only_reply = process_command(&authority_config).unwrap();
|
||||
let sign_only = parse_sign_only_reply_string(&sign_only_reply);
|
||||
let authority_presigner = sign_only.presigner_of(&authority_pubkey).unwrap();
|
||||
assert_eq!(sign_only.blockhash, nonce_hash);
|
||||
|
||||
// And submit it
|
||||
let mut submit_config = CliConfig::default();
|
||||
submit_config.json_rpc_url =
|
||||
format!("http://{}:{}", leader_data.rpc.ip(), leader_data.rpc.port());
|
||||
submit_config.signers = vec![&authority_presigner];
|
||||
submit_config.command = CliCommand::Transfer {
|
||||
lamports: 10,
|
||||
to: to_address,
|
||||
from: 0,
|
||||
sign_only: false,
|
||||
blockhash_query: BlockhashQuery::FeeCalculator(
|
||||
blockhash_query::Source::NonceAccount(nonce_address),
|
||||
sign_only.blockhash,
|
||||
),
|
||||
nonce_account: Some(nonce_address),
|
||||
nonce_authority: 0,
|
||||
fee_payer: 0,
|
||||
};
|
||||
process_command(&submit_config).unwrap();
|
||||
check_balance(241, &rpc_client, &nonce_address);
|
||||
check_balance(31, &rpc_client, &offline_nonce_authority_signer.pubkey());
|
||||
check_balance(4000, &rpc_client, &online_nonce_creator_signer.pubkey());
|
||||
check_balance(10, &rpc_client, &to_address);
|
||||
|
||||
server.close().unwrap();
|
||||
remove_dir_all(ledger_path).unwrap();
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
use chrono::prelude::*;
|
||||
use serde_json::Value;
|
||||
use solana_clap_utils::keypair::presigner_from_pubkey_sigs;
|
||||
use solana_cli::{
|
||||
cli::{process_command, request_and_confirm_airdrop, CliCommand, CliConfig, PayCommand},
|
||||
nonce,
|
||||
@ -335,9 +334,11 @@ fn test_offline_pay_tx() {
|
||||
check_balance(50, &rpc_client, &config_online.signers[0].pubkey());
|
||||
check_balance(0, &rpc_client, &bob_pubkey);
|
||||
|
||||
let (blockhash, signers) = parse_sign_only_reply_string(&sig_response);
|
||||
let offline_presigner =
|
||||
presigner_from_pubkey_sigs(&config_offline.signers[0].pubkey(), &signers).unwrap();
|
||||
let sign_only = parse_sign_only_reply_string(&sig_response);
|
||||
assert!(sign_only.has_all_signers());
|
||||
let offline_presigner = sign_only
|
||||
.presigner_of(&config_offline.signers[0].pubkey())
|
||||
.unwrap();
|
||||
let online_pubkey = config_online.signers[0].pubkey();
|
||||
config_online.signers = vec![&offline_presigner];
|
||||
config_online.command = CliCommand::Pay(PayCommand {
|
||||
|
@ -1,4 +1,3 @@
|
||||
use solana_clap_utils::keypair::presigner_from_pubkey_sigs;
|
||||
use solana_cli::{
|
||||
cli::{process_command, request_and_confirm_airdrop, CliCommand, CliConfig},
|
||||
nonce,
|
||||
@ -69,7 +68,7 @@ fn test_stake_delegation_force() {
|
||||
config.signers = vec![&default_signer, &vote_keypair];
|
||||
config.command = CliCommand::CreateVoteAccount {
|
||||
seed: None,
|
||||
node_pubkey: config.signers[0].pubkey(),
|
||||
identity_account: 0,
|
||||
authorized_voter: None,
|
||||
authorized_withdrawer: None,
|
||||
commission: 0,
|
||||
@ -388,9 +387,11 @@ fn test_offline_stake_delegation_and_deactivation() {
|
||||
fee_payer: 0,
|
||||
};
|
||||
let sig_response = process_command(&config_offline).unwrap();
|
||||
let (blockhash, signers) = parse_sign_only_reply_string(&sig_response);
|
||||
let offline_presigner =
|
||||
presigner_from_pubkey_sigs(&config_offline.signers[0].pubkey(), &signers).unwrap();
|
||||
let sign_only = parse_sign_only_reply_string(&sig_response);
|
||||
assert!(sign_only.has_all_signers());
|
||||
let offline_presigner = sign_only
|
||||
.presigner_of(&config_offline.signers[0].pubkey())
|
||||
.unwrap();
|
||||
config_payer.signers = vec![&offline_presigner];
|
||||
config_payer.command = CliCommand::DelegateStake {
|
||||
stake_account_pubkey: stake_keypair.pubkey(),
|
||||
@ -417,9 +418,11 @@ fn test_offline_stake_delegation_and_deactivation() {
|
||||
fee_payer: 0,
|
||||
};
|
||||
let sig_response = process_command(&config_offline).unwrap();
|
||||
let (blockhash, signers) = parse_sign_only_reply_string(&sig_response);
|
||||
let offline_presigner =
|
||||
presigner_from_pubkey_sigs(&config_offline.signers[0].pubkey(), &signers).unwrap();
|
||||
let sign_only = parse_sign_only_reply_string(&sig_response);
|
||||
assert!(sign_only.has_all_signers());
|
||||
let offline_presigner = sign_only
|
||||
.presigner_of(&config_offline.signers[0].pubkey())
|
||||
.unwrap();
|
||||
config_payer.signers = vec![&offline_presigner];
|
||||
config_payer.command = CliCommand::DeactivateStake {
|
||||
stake_account_pubkey: stake_keypair.pubkey(),
|
||||
@ -623,9 +626,7 @@ fn test_stake_authorize() {
|
||||
config.signers.pop();
|
||||
config.command = CliCommand::StakeAuthorize {
|
||||
stake_account_pubkey,
|
||||
new_authorized_pubkey: online_authority_pubkey,
|
||||
stake_authorize: StakeAuthorize::Staker,
|
||||
authority: 0,
|
||||
new_authorizations: vec![(StakeAuthorize::Staker, online_authority_pubkey, 0)],
|
||||
sign_only: false,
|
||||
blockhash_query: BlockhashQuery::default(),
|
||||
nonce_account: None,
|
||||
@ -641,13 +642,40 @@ fn test_stake_authorize() {
|
||||
};
|
||||
assert_eq!(current_authority, online_authority_pubkey);
|
||||
|
||||
// Assign new offline stake authority
|
||||
// Assign new online stake and withdraw authorities
|
||||
let online_authority2 = Keypair::new();
|
||||
let online_authority2_pubkey = online_authority2.pubkey();
|
||||
let withdraw_authority = Keypair::new();
|
||||
let withdraw_authority_pubkey = withdraw_authority.pubkey();
|
||||
config.signers.push(&online_authority);
|
||||
config.command = CliCommand::StakeAuthorize {
|
||||
stake_account_pubkey,
|
||||
new_authorized_pubkey: offline_authority_pubkey,
|
||||
stake_authorize: StakeAuthorize::Staker,
|
||||
authority: 1,
|
||||
new_authorizations: vec![
|
||||
(StakeAuthorize::Staker, online_authority2_pubkey, 1),
|
||||
(StakeAuthorize::Withdrawer, withdraw_authority_pubkey, 0),
|
||||
],
|
||||
sign_only: false,
|
||||
blockhash_query: BlockhashQuery::default(),
|
||||
nonce_account: None,
|
||||
nonce_authority: 0,
|
||||
fee_payer: 0,
|
||||
};
|
||||
process_command(&config).unwrap();
|
||||
let stake_account = rpc_client.get_account(&stake_account_pubkey).unwrap();
|
||||
let stake_state: StakeState = stake_account.state().unwrap();
|
||||
let (current_staker, current_withdrawer) = match stake_state {
|
||||
StakeState::Initialized(meta) => (meta.authorized.staker, meta.authorized.withdrawer),
|
||||
_ => panic!("Unexpected stake state!"),
|
||||
};
|
||||
assert_eq!(current_staker, online_authority2_pubkey);
|
||||
assert_eq!(current_withdrawer, withdraw_authority_pubkey);
|
||||
|
||||
// Assign new offline stake authority
|
||||
config.signers.pop();
|
||||
config.signers.push(&online_authority2);
|
||||
config.command = CliCommand::StakeAuthorize {
|
||||
stake_account_pubkey,
|
||||
new_authorizations: vec![(StakeAuthorize::Staker, offline_authority_pubkey, 1)],
|
||||
sign_only: false,
|
||||
blockhash_query: BlockhashQuery::default(),
|
||||
nonce_account: None,
|
||||
@ -669,9 +697,7 @@ fn test_stake_authorize() {
|
||||
let (blockhash, _) = rpc_client.get_recent_blockhash().unwrap();
|
||||
config_offline.command = CliCommand::StakeAuthorize {
|
||||
stake_account_pubkey,
|
||||
new_authorized_pubkey: nonced_authority_pubkey,
|
||||
stake_authorize: StakeAuthorize::Staker,
|
||||
authority: 0,
|
||||
new_authorizations: vec![(StakeAuthorize::Staker, nonced_authority_pubkey, 0)],
|
||||
sign_only: true,
|
||||
blockhash_query: BlockhashQuery::None(blockhash),
|
||||
nonce_account: None,
|
||||
@ -679,15 +705,13 @@ fn test_stake_authorize() {
|
||||
fee_payer: 0,
|
||||
};
|
||||
let sign_reply = process_command(&config_offline).unwrap();
|
||||
let (blockhash, signers) = parse_sign_only_reply_string(&sign_reply);
|
||||
let offline_presigner =
|
||||
presigner_from_pubkey_sigs(&offline_authority_pubkey, &signers).unwrap();
|
||||
let sign_only = parse_sign_only_reply_string(&sign_reply);
|
||||
assert!(sign_only.has_all_signers());
|
||||
let offline_presigner = sign_only.presigner_of(&offline_authority_pubkey).unwrap();
|
||||
config.signers = vec![&offline_presigner];
|
||||
config.command = CliCommand::StakeAuthorize {
|
||||
stake_account_pubkey,
|
||||
new_authorized_pubkey: nonced_authority_pubkey,
|
||||
stake_authorize: StakeAuthorize::Staker,
|
||||
authority: 0,
|
||||
new_authorizations: vec![(StakeAuthorize::Staker, nonced_authority_pubkey, 0)],
|
||||
sign_only: false,
|
||||
blockhash_query: BlockhashQuery::FeeCalculator(blockhash_query::Source::Cluster, blockhash),
|
||||
nonce_account: None,
|
||||
@ -729,9 +753,7 @@ fn test_stake_authorize() {
|
||||
config_offline.signers.push(&nonced_authority);
|
||||
config_offline.command = CliCommand::StakeAuthorize {
|
||||
stake_account_pubkey,
|
||||
new_authorized_pubkey: online_authority_pubkey,
|
||||
stake_authorize: StakeAuthorize::Staker,
|
||||
authority: 1,
|
||||
new_authorizations: vec![(StakeAuthorize::Staker, online_authority_pubkey, 1)],
|
||||
sign_only: true,
|
||||
blockhash_query: BlockhashQuery::None(nonce_hash),
|
||||
nonce_account: Some(nonce_account.pubkey()),
|
||||
@ -739,22 +761,19 @@ fn test_stake_authorize() {
|
||||
fee_payer: 0,
|
||||
};
|
||||
let sign_reply = process_command(&config_offline).unwrap();
|
||||
let (blockhash, signers) = parse_sign_only_reply_string(&sign_reply);
|
||||
assert_eq!(blockhash, nonce_hash);
|
||||
let offline_presigner =
|
||||
presigner_from_pubkey_sigs(&offline_authority_pubkey, &signers).unwrap();
|
||||
let nonced_authority_presigner =
|
||||
presigner_from_pubkey_sigs(&nonced_authority_pubkey, &signers).unwrap();
|
||||
let sign_only = parse_sign_only_reply_string(&sign_reply);
|
||||
assert!(sign_only.has_all_signers());
|
||||
assert_eq!(sign_only.blockhash, nonce_hash);
|
||||
let offline_presigner = sign_only.presigner_of(&offline_authority_pubkey).unwrap();
|
||||
let nonced_authority_presigner = sign_only.presigner_of(&nonced_authority_pubkey).unwrap();
|
||||
config.signers = vec![&offline_presigner, &nonced_authority_presigner];
|
||||
config.command = CliCommand::StakeAuthorize {
|
||||
stake_account_pubkey,
|
||||
new_authorized_pubkey: online_authority_pubkey,
|
||||
stake_authorize: StakeAuthorize::Staker,
|
||||
authority: 1,
|
||||
new_authorizations: vec![(StakeAuthorize::Staker, online_authority_pubkey, 1)],
|
||||
sign_only: false,
|
||||
blockhash_query: BlockhashQuery::FeeCalculator(
|
||||
blockhash_query::Source::NonceAccount(nonce_account.pubkey()),
|
||||
blockhash,
|
||||
sign_only.blockhash,
|
||||
),
|
||||
nonce_account: Some(nonce_account.pubkey()),
|
||||
nonce_authority: 0,
|
||||
@ -816,6 +835,7 @@ fn test_stake_authorize_with_fee_payer() {
|
||||
let mut config_offline = CliConfig::default();
|
||||
let offline_signer = Keypair::new();
|
||||
config_offline.signers = vec![&offline_signer];
|
||||
config_offline.json_rpc_url = String::new();
|
||||
let offline_pubkey = config_offline.signers[0].pubkey();
|
||||
// Verify we're offline
|
||||
config_offline.command = CliCommand::ClusterVersion;
|
||||
@ -856,9 +876,7 @@ fn test_stake_authorize_with_fee_payer() {
|
||||
config.signers = vec![&default_signer, &payer_keypair];
|
||||
config.command = CliCommand::StakeAuthorize {
|
||||
stake_account_pubkey,
|
||||
new_authorized_pubkey: offline_pubkey,
|
||||
stake_authorize: StakeAuthorize::Staker,
|
||||
authority: 0,
|
||||
new_authorizations: vec![(StakeAuthorize::Staker, offline_pubkey, 0)],
|
||||
sign_only: false,
|
||||
blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
|
||||
nonce_account: None,
|
||||
@ -876,9 +894,7 @@ fn test_stake_authorize_with_fee_payer() {
|
||||
let (blockhash, _) = rpc_client.get_recent_blockhash().unwrap();
|
||||
config_offline.command = CliCommand::StakeAuthorize {
|
||||
stake_account_pubkey,
|
||||
new_authorized_pubkey: payer_pubkey,
|
||||
stake_authorize: StakeAuthorize::Staker,
|
||||
authority: 0,
|
||||
new_authorizations: vec![(StakeAuthorize::Staker, payer_pubkey, 0)],
|
||||
sign_only: true,
|
||||
blockhash_query: BlockhashQuery::None(blockhash),
|
||||
nonce_account: None,
|
||||
@ -886,14 +902,13 @@ fn test_stake_authorize_with_fee_payer() {
|
||||
fee_payer: 0,
|
||||
};
|
||||
let sign_reply = process_command(&config_offline).unwrap();
|
||||
let (blockhash, signers) = parse_sign_only_reply_string(&sign_reply);
|
||||
let offline_presigner = presigner_from_pubkey_sigs(&offline_pubkey, &signers).unwrap();
|
||||
let sign_only = parse_sign_only_reply_string(&sign_reply);
|
||||
assert!(sign_only.has_all_signers());
|
||||
let offline_presigner = sign_only.presigner_of(&offline_pubkey).unwrap();
|
||||
config.signers = vec![&offline_presigner];
|
||||
config.command = CliCommand::StakeAuthorize {
|
||||
stake_account_pubkey,
|
||||
new_authorized_pubkey: payer_pubkey,
|
||||
stake_authorize: StakeAuthorize::Staker,
|
||||
authority: 0,
|
||||
new_authorizations: vec![(StakeAuthorize::Staker, payer_pubkey, 0)],
|
||||
sign_only: false,
|
||||
blockhash_query: BlockhashQuery::FeeCalculator(blockhash_query::Source::Cluster, blockhash),
|
||||
nonce_account: None,
|
||||
@ -1023,8 +1038,9 @@ fn test_stake_split() {
|
||||
fee_payer: 0,
|
||||
};
|
||||
let sig_response = process_command(&config_offline).unwrap();
|
||||
let (blockhash, signers) = parse_sign_only_reply_string(&sig_response);
|
||||
let offline_presigner = presigner_from_pubkey_sigs(&offline_pubkey, &signers).unwrap();
|
||||
let sign_only = parse_sign_only_reply_string(&sig_response);
|
||||
assert!(sign_only.has_all_signers());
|
||||
let offline_presigner = sign_only.presigner_of(&offline_pubkey).unwrap();
|
||||
config.signers = vec![&offline_presigner, &split_account];
|
||||
config.command = CliCommand::SplitStake {
|
||||
stake_account_pubkey: stake_account_pubkey,
|
||||
@ -1032,7 +1048,7 @@ fn test_stake_split() {
|
||||
sign_only: false,
|
||||
blockhash_query: BlockhashQuery::FeeCalculator(
|
||||
blockhash_query::Source::NonceAccount(nonce_account.pubkey()),
|
||||
blockhash,
|
||||
sign_only.blockhash,
|
||||
),
|
||||
nonce_account: Some(nonce_account.pubkey()),
|
||||
nonce_authority: 0,
|
||||
@ -1275,8 +1291,9 @@ fn test_stake_set_lockup() {
|
||||
fee_payer: 0,
|
||||
};
|
||||
let sig_response = process_command(&config_offline).unwrap();
|
||||
let (blockhash, signers) = parse_sign_only_reply_string(&sig_response);
|
||||
let offline_presigner = presigner_from_pubkey_sigs(&offline_pubkey, &signers).unwrap();
|
||||
let sign_only = parse_sign_only_reply_string(&sig_response);
|
||||
assert!(sign_only.has_all_signers());
|
||||
let offline_presigner = sign_only.presigner_of(&offline_pubkey).unwrap();
|
||||
config.signers = vec![&offline_presigner];
|
||||
config.command = CliCommand::StakeSetLockup {
|
||||
stake_account_pubkey,
|
||||
@ -1285,7 +1302,7 @@ fn test_stake_set_lockup() {
|
||||
sign_only: false,
|
||||
blockhash_query: BlockhashQuery::FeeCalculator(
|
||||
blockhash_query::Source::NonceAccount(nonce_account_pubkey),
|
||||
blockhash,
|
||||
sign_only.blockhash,
|
||||
),
|
||||
nonce_account: Some(nonce_account_pubkey),
|
||||
nonce_authority: 0,
|
||||
@ -1392,9 +1409,10 @@ fn test_offline_nonced_create_stake_account_and_withdraw() {
|
||||
from: 0,
|
||||
};
|
||||
let sig_response = process_command(&config_offline).unwrap();
|
||||
let (blockhash, signers) = parse_sign_only_reply_string(&sig_response);
|
||||
let offline_presigner = presigner_from_pubkey_sigs(&offline_pubkey, &signers).unwrap();
|
||||
let stake_presigner = presigner_from_pubkey_sigs(&stake_pubkey, &signers).unwrap();
|
||||
let sign_only = parse_sign_only_reply_string(&sig_response);
|
||||
assert!(sign_only.has_all_signers());
|
||||
let offline_presigner = sign_only.presigner_of(&offline_pubkey).unwrap();
|
||||
let stake_presigner = sign_only.presigner_of(&stake_pubkey).unwrap();
|
||||
config.signers = vec![&offline_presigner, &stake_presigner];
|
||||
config.command = CliCommand::CreateStakeAccount {
|
||||
stake_account: 1,
|
||||
@ -1406,7 +1424,7 @@ fn test_offline_nonced_create_stake_account_and_withdraw() {
|
||||
sign_only: false,
|
||||
blockhash_query: BlockhashQuery::FeeCalculator(
|
||||
blockhash_query::Source::NonceAccount(nonce_pubkey),
|
||||
blockhash,
|
||||
sign_only.blockhash,
|
||||
),
|
||||
nonce_account: Some(nonce_pubkey),
|
||||
nonce_authority: 0,
|
||||
@ -1438,8 +1456,8 @@ fn test_offline_nonced_create_stake_account_and_withdraw() {
|
||||
fee_payer: 0,
|
||||
};
|
||||
let sig_response = process_command(&config_offline).unwrap();
|
||||
let (blockhash, signers) = parse_sign_only_reply_string(&sig_response);
|
||||
let offline_presigner = presigner_from_pubkey_sigs(&offline_pubkey, &signers).unwrap();
|
||||
let sign_only = parse_sign_only_reply_string(&sig_response);
|
||||
let offline_presigner = sign_only.presigner_of(&offline_pubkey).unwrap();
|
||||
config.signers = vec![&offline_presigner];
|
||||
config.command = CliCommand::WithdrawStake {
|
||||
stake_account_pubkey: stake_pubkey,
|
||||
@ -1449,7 +1467,7 @@ fn test_offline_nonced_create_stake_account_and_withdraw() {
|
||||
sign_only: false,
|
||||
blockhash_query: BlockhashQuery::FeeCalculator(
|
||||
blockhash_query::Source::NonceAccount(nonce_pubkey),
|
||||
blockhash,
|
||||
sign_only.blockhash,
|
||||
),
|
||||
nonce_account: Some(nonce_pubkey),
|
||||
nonce_authority: 0,
|
||||
@ -1482,9 +1500,9 @@ fn test_offline_nonced_create_stake_account_and_withdraw() {
|
||||
from: 0,
|
||||
};
|
||||
let sig_response = process_command(&config_offline).unwrap();
|
||||
let (blockhash, signers) = parse_sign_only_reply_string(&sig_response);
|
||||
let offline_presigner = presigner_from_pubkey_sigs(&offline_pubkey, &signers).unwrap();
|
||||
let stake_presigner = presigner_from_pubkey_sigs(&stake_pubkey, &signers).unwrap();
|
||||
let sign_only = parse_sign_only_reply_string(&sig_response);
|
||||
let offline_presigner = sign_only.presigner_of(&offline_pubkey).unwrap();
|
||||
let stake_presigner = sign_only.presigner_of(&stake_pubkey).unwrap();
|
||||
config.signers = vec![&offline_presigner, &stake_presigner];
|
||||
config.command = CliCommand::CreateStakeAccount {
|
||||
stake_account: 1,
|
||||
@ -1496,7 +1514,7 @@ fn test_offline_nonced_create_stake_account_and_withdraw() {
|
||||
sign_only: false,
|
||||
blockhash_query: BlockhashQuery::FeeCalculator(
|
||||
blockhash_query::Source::NonceAccount(nonce_pubkey),
|
||||
blockhash,
|
||||
sign_only.blockhash,
|
||||
),
|
||||
nonce_account: Some(nonce_pubkey),
|
||||
nonce_authority: 0,
|
||||
|
@ -1,4 +1,3 @@
|
||||
use solana_clap_utils::keypair::presigner_from_pubkey_sigs;
|
||||
use solana_cli::{
|
||||
cli::{process_command, request_and_confirm_airdrop, CliCommand, CliConfig},
|
||||
nonce,
|
||||
@ -13,7 +12,7 @@ use solana_faucet::faucet::run_local_faucet;
|
||||
use solana_sdk::{
|
||||
nonce::State as NonceState,
|
||||
pubkey::Pubkey,
|
||||
signature::{keypair_from_seed, Keypair, Signer},
|
||||
signature::{keypair_from_seed, Keypair, NullSigner, Signer},
|
||||
};
|
||||
use std::{fs::remove_dir_all, sync::mpsc::channel, thread::sleep, time::Duration};
|
||||
|
||||
@ -102,8 +101,9 @@ fn test_transfer() {
|
||||
fee_payer: 0,
|
||||
};
|
||||
let sign_only_reply = process_command(&offline).unwrap();
|
||||
let (blockhash, signers) = parse_sign_only_reply_string(&sign_only_reply);
|
||||
let offline_presigner = presigner_from_pubkey_sigs(&offline_pubkey, &signers).unwrap();
|
||||
let sign_only = parse_sign_only_reply_string(&sign_only_reply);
|
||||
assert!(sign_only.has_all_signers());
|
||||
let offline_presigner = sign_only.presigner_of(&offline_pubkey).unwrap();
|
||||
config.signers = vec![&offline_presigner];
|
||||
config.command = CliCommand::Transfer {
|
||||
lamports: 10,
|
||||
@ -193,8 +193,9 @@ fn test_transfer() {
|
||||
fee_payer: 0,
|
||||
};
|
||||
let sign_only_reply = process_command(&offline).unwrap();
|
||||
let (blockhash, signers) = parse_sign_only_reply_string(&sign_only_reply);
|
||||
let offline_presigner = presigner_from_pubkey_sigs(&offline_pubkey, &signers).unwrap();
|
||||
let sign_only = parse_sign_only_reply_string(&sign_only_reply);
|
||||
assert!(sign_only.has_all_signers());
|
||||
let offline_presigner = sign_only.presigner_of(&offline_pubkey).unwrap();
|
||||
config.signers = vec![&offline_presigner];
|
||||
config.command = CliCommand::Transfer {
|
||||
lamports: 10,
|
||||
@ -203,7 +204,7 @@ fn test_transfer() {
|
||||
sign_only: false,
|
||||
blockhash_query: BlockhashQuery::FeeCalculator(
|
||||
blockhash_query::Source::NonceAccount(nonce_account.pubkey()),
|
||||
blockhash,
|
||||
sign_only.blockhash,
|
||||
),
|
||||
nonce_account: Some(nonce_account.pubkey()),
|
||||
nonce_authority: 0,
|
||||
@ -216,3 +217,114 @@ fn test_transfer() {
|
||||
server.close().unwrap();
|
||||
remove_dir_all(ledger_path).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_transfer_multisession_signing() {
|
||||
let TestValidator {
|
||||
server,
|
||||
leader_data,
|
||||
alice: mint_keypair,
|
||||
ledger_path,
|
||||
..
|
||||
} = TestValidator::run_with_options(TestValidatorOptions {
|
||||
fees: 1,
|
||||
bootstrap_validator_lamports: 42_000,
|
||||
});
|
||||
|
||||
let (sender, receiver) = channel();
|
||||
run_local_faucet(mint_keypair, sender, None);
|
||||
let faucet_addr = receiver.recv().unwrap();
|
||||
|
||||
let to_pubkey = Pubkey::new(&[1u8; 32]);
|
||||
let offline_from_signer = keypair_from_seed(&[2u8; 32]).unwrap();
|
||||
let offline_fee_payer_signer = keypair_from_seed(&[3u8; 32]).unwrap();
|
||||
let from_null_signer = NullSigner::new(&offline_from_signer.pubkey());
|
||||
|
||||
// Setup accounts
|
||||
let rpc_client = RpcClient::new_socket(leader_data.rpc);
|
||||
request_and_confirm_airdrop(&rpc_client, &faucet_addr, &offline_from_signer.pubkey(), 43)
|
||||
.unwrap();
|
||||
request_and_confirm_airdrop(
|
||||
&rpc_client,
|
||||
&faucet_addr,
|
||||
&offline_fee_payer_signer.pubkey(),
|
||||
3,
|
||||
)
|
||||
.unwrap();
|
||||
check_balance(43, &rpc_client, &offline_from_signer.pubkey());
|
||||
check_balance(3, &rpc_client, &offline_fee_payer_signer.pubkey());
|
||||
check_balance(0, &rpc_client, &to_pubkey);
|
||||
|
||||
let (blockhash, _) = rpc_client.get_recent_blockhash().unwrap();
|
||||
|
||||
// Offline fee-payer signs first
|
||||
let mut fee_payer_config = CliConfig::default();
|
||||
fee_payer_config.json_rpc_url = String::default();
|
||||
fee_payer_config.signers = vec![&offline_fee_payer_signer, &from_null_signer];
|
||||
// Verify we cannot contact the cluster
|
||||
fee_payer_config.command = CliCommand::ClusterVersion;
|
||||
process_command(&fee_payer_config).unwrap_err();
|
||||
fee_payer_config.command = CliCommand::Transfer {
|
||||
lamports: 42,
|
||||
to: to_pubkey,
|
||||
from: 1,
|
||||
sign_only: true,
|
||||
blockhash_query: BlockhashQuery::None(blockhash),
|
||||
nonce_account: None,
|
||||
nonce_authority: 0,
|
||||
fee_payer: 0,
|
||||
};
|
||||
let sign_only_reply = process_command(&fee_payer_config).unwrap();
|
||||
let sign_only = parse_sign_only_reply_string(&sign_only_reply);
|
||||
assert!(!sign_only.has_all_signers());
|
||||
let fee_payer_presigner = sign_only
|
||||
.presigner_of(&offline_fee_payer_signer.pubkey())
|
||||
.unwrap();
|
||||
|
||||
// Now the offline fund source
|
||||
let mut from_config = CliConfig::default();
|
||||
from_config.json_rpc_url = String::default();
|
||||
from_config.signers = vec![&fee_payer_presigner, &offline_from_signer];
|
||||
// Verify we cannot contact the cluster
|
||||
from_config.command = CliCommand::ClusterVersion;
|
||||
process_command(&from_config).unwrap_err();
|
||||
from_config.command = CliCommand::Transfer {
|
||||
lamports: 42,
|
||||
to: to_pubkey,
|
||||
from: 1,
|
||||
sign_only: true,
|
||||
blockhash_query: BlockhashQuery::None(blockhash),
|
||||
nonce_account: None,
|
||||
nonce_authority: 0,
|
||||
fee_payer: 0,
|
||||
};
|
||||
let sign_only_reply = process_command(&from_config).unwrap();
|
||||
let sign_only = parse_sign_only_reply_string(&sign_only_reply);
|
||||
assert!(sign_only.has_all_signers());
|
||||
let from_presigner = sign_only
|
||||
.presigner_of(&offline_from_signer.pubkey())
|
||||
.unwrap();
|
||||
|
||||
// Finally submit to the cluster
|
||||
let mut config = CliConfig::default();
|
||||
config.json_rpc_url = format!("http://{}:{}", leader_data.rpc.ip(), leader_data.rpc.port());
|
||||
config.signers = vec![&fee_payer_presigner, &from_presigner];
|
||||
config.command = CliCommand::Transfer {
|
||||
lamports: 42,
|
||||
to: to_pubkey,
|
||||
from: 1,
|
||||
sign_only: false,
|
||||
blockhash_query: BlockhashQuery::FeeCalculator(blockhash_query::Source::Cluster, blockhash),
|
||||
nonce_account: None,
|
||||
nonce_authority: 0,
|
||||
fee_payer: 0,
|
||||
};
|
||||
process_command(&config).unwrap();
|
||||
|
||||
check_balance(1, &rpc_client, &offline_from_signer.pubkey());
|
||||
check_balance(1, &rpc_client, &offline_fee_payer_signer.pubkey());
|
||||
check_balance(42, &rpc_client, &to_pubkey);
|
||||
|
||||
server.close().unwrap();
|
||||
remove_dir_all(ledger_path).unwrap();
|
||||
}
|
||||
|
118
cli/tests/vote.rs
Normal file
118
cli/tests/vote.rs
Normal file
@ -0,0 +1,118 @@
|
||||
use solana_cli::cli::{process_command, request_and_confirm_airdrop, CliCommand, CliConfig};
|
||||
use solana_client::rpc_client::RpcClient;
|
||||
use solana_core::validator::TestValidator;
|
||||
use solana_faucet::faucet::run_local_faucet;
|
||||
use solana_sdk::{
|
||||
account_utils::StateMut,
|
||||
pubkey::Pubkey,
|
||||
signature::{Keypair, Signer},
|
||||
};
|
||||
use solana_vote_program::vote_state::{VoteAuthorize, VoteState, VoteStateVersions};
|
||||
use std::{fs::remove_dir_all, sync::mpsc::channel, thread::sleep, time::Duration};
|
||||
|
||||
fn check_balance(expected_balance: u64, client: &RpcClient, pubkey: &Pubkey) {
|
||||
(0..5).for_each(|tries| {
|
||||
let balance = client.retry_get_balance(pubkey, 1).unwrap().unwrap();
|
||||
if balance == expected_balance {
|
||||
return;
|
||||
}
|
||||
if tries == 4 {
|
||||
assert_eq!(balance, expected_balance);
|
||||
}
|
||||
sleep(Duration::from_millis(500));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_vote_authorize_and_withdraw() {
|
||||
let TestValidator {
|
||||
server,
|
||||
leader_data,
|
||||
alice,
|
||||
ledger_path,
|
||||
..
|
||||
} = TestValidator::run();
|
||||
let (sender, receiver) = channel();
|
||||
run_local_faucet(alice, sender, None);
|
||||
let faucet_addr = receiver.recv().unwrap();
|
||||
|
||||
let rpc_client = RpcClient::new_socket(leader_data.rpc);
|
||||
let default_signer = Keypair::new();
|
||||
|
||||
let mut config = CliConfig::default();
|
||||
config.json_rpc_url = format!("http://{}:{}", leader_data.rpc.ip(), leader_data.rpc.port());
|
||||
config.signers = vec![&default_signer];
|
||||
|
||||
request_and_confirm_airdrop(
|
||||
&rpc_client,
|
||||
&faucet_addr,
|
||||
&config.signers[0].pubkey(),
|
||||
100_000,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// Create vote account
|
||||
let vote_account_keypair = Keypair::new();
|
||||
let vote_account_pubkey = vote_account_keypair.pubkey();
|
||||
config.signers = vec![&default_signer, &vote_account_keypair];
|
||||
config.command = CliCommand::CreateVoteAccount {
|
||||
seed: None,
|
||||
identity_account: 0,
|
||||
authorized_voter: None,
|
||||
authorized_withdrawer: Some(config.signers[0].pubkey()),
|
||||
commission: 0,
|
||||
};
|
||||
process_command(&config).unwrap();
|
||||
let vote_account = rpc_client
|
||||
.get_account(&vote_account_keypair.pubkey())
|
||||
.unwrap();
|
||||
let vote_state: VoteStateVersions = vote_account.state().unwrap();
|
||||
let authorized_withdrawer = vote_state.convert_to_current().authorized_withdrawer;
|
||||
assert_eq!(authorized_withdrawer, config.signers[0].pubkey());
|
||||
let expected_balance = rpc_client
|
||||
.get_minimum_balance_for_rent_exemption(VoteState::size_of())
|
||||
.unwrap()
|
||||
.max(1);
|
||||
check_balance(expected_balance, &rpc_client, &vote_account_pubkey);
|
||||
|
||||
// Authorize vote account withdrawal to another signer
|
||||
let withdraw_authority = Keypair::new();
|
||||
config.signers = vec![&default_signer];
|
||||
config.command = CliCommand::VoteAuthorize {
|
||||
vote_account_pubkey,
|
||||
new_authorized_pubkey: withdraw_authority.pubkey(),
|
||||
vote_authorize: VoteAuthorize::Withdrawer,
|
||||
};
|
||||
process_command(&config).unwrap();
|
||||
let vote_account = rpc_client
|
||||
.get_account(&vote_account_keypair.pubkey())
|
||||
.unwrap();
|
||||
let vote_state: VoteStateVersions = vote_account.state().unwrap();
|
||||
let authorized_withdrawer = vote_state.convert_to_current().authorized_withdrawer;
|
||||
assert_eq!(authorized_withdrawer, withdraw_authority.pubkey());
|
||||
|
||||
// Withdraw from vote account
|
||||
let destination_account = Pubkey::new_rand(); // Send withdrawal to new account to make balance check easy
|
||||
config.signers = vec![&default_signer, &withdraw_authority];
|
||||
config.command = CliCommand::WithdrawFromVoteAccount {
|
||||
vote_account_pubkey,
|
||||
withdraw_authority: 1,
|
||||
lamports: 100,
|
||||
destination_account_pubkey: destination_account,
|
||||
};
|
||||
process_command(&config).unwrap();
|
||||
check_balance(expected_balance - 100, &rpc_client, &vote_account_pubkey);
|
||||
check_balance(100, &rpc_client, &destination_account);
|
||||
|
||||
// Re-assign validator identity
|
||||
let new_identity_keypair = Keypair::new();
|
||||
config.signers.push(&new_identity_keypair);
|
||||
config.command = CliCommand::VoteUpdateValidator {
|
||||
vote_account_pubkey,
|
||||
new_identity_account: 2,
|
||||
};
|
||||
process_command(&config).unwrap();
|
||||
|
||||
server.close().unwrap();
|
||||
remove_dir_all(ledger_path).unwrap();
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "solana-client"
|
||||
version = "1.0.6"
|
||||
version = "1.0.9"
|
||||
description = "Solana Client"
|
||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
@ -11,6 +11,7 @@ edition = "2018"
|
||||
[dependencies]
|
||||
bincode = "1.2.1"
|
||||
bs58 = "0.3.0"
|
||||
indicatif = "0.14.0"
|
||||
jsonrpc-core = "14.0.5"
|
||||
log = "0.4.8"
|
||||
rayon = "1.2.0"
|
||||
@ -18,8 +19,9 @@ reqwest = { version = "0.10.1", default-features = false, features = ["blocking"
|
||||
serde = "1.0.104"
|
||||
serde_derive = "1.0.103"
|
||||
serde_json = "1.0.46"
|
||||
solana-net-utils = { path = "../net-utils", version = "1.0.6" }
|
||||
solana-sdk = { path = "../sdk", version = "1.0.6" }
|
||||
solana-net-utils = { path = "../net-utils", version = "1.0.9" }
|
||||
solana-sdk = { path = "../sdk", version = "1.0.9" }
|
||||
solana-vote-program = { path = "../programs/vote", version = "1.0.9" }
|
||||
thiserror = "1.0"
|
||||
tungstenite = "0.10.1"
|
||||
url = "2.1.1"
|
||||
@ -28,4 +30,4 @@ url = "2.1.1"
|
||||
assert_matches = "1.3.0"
|
||||
jsonrpc-core = "14.0.5"
|
||||
jsonrpc-http-server = "14.0.6"
|
||||
solana-logger = { path = "../logger", version = "1.0.6" }
|
||||
solana-logger = { path = "../logger", version = "1.0.9" }
|
||||
|
@ -2,7 +2,7 @@ use crate::{
|
||||
client_error::Result,
|
||||
generic_rpc_client_request::GenericRpcClientRequest,
|
||||
rpc_request::RpcRequest,
|
||||
rpc_response::{Response, RpcResponseContext},
|
||||
rpc_response::{Response, RpcResponseContext, RpcTransactionStatus},
|
||||
};
|
||||
use serde_json::{Number, Value};
|
||||
use solana_sdk::{
|
||||
@ -87,19 +87,22 @@ impl GenericRpcClientRequest for MockRpcClientRequest {
|
||||
value: serde_json::to_value(FeeRateGovernor::default()).unwrap(),
|
||||
})?,
|
||||
RpcRequest::GetSignatureStatus => {
|
||||
let response: Option<transaction::Result<()>> = if self.url == "account_in_use" {
|
||||
Some(Err(TransactionError::AccountInUse))
|
||||
let status: transaction::Result<()> = if self.url == "account_in_use" {
|
||||
Err(TransactionError::AccountInUse)
|
||||
} else if self.url == "instruction_error" {
|
||||
Some(Err(TransactionError::InstructionError(
|
||||
Err(TransactionError::InstructionError(
|
||||
0,
|
||||
InstructionError::UninitializedAccount,
|
||||
)))
|
||||
} else if self.url == "sig_not_found" {
|
||||
))
|
||||
} else {
|
||||
Ok(())
|
||||
};
|
||||
let status = if self.url == "sig_not_found" {
|
||||
None
|
||||
} else {
|
||||
Some(Ok(()))
|
||||
Some(RpcTransactionStatus { status, slot: 1 })
|
||||
};
|
||||
serde_json::to_value(response).unwrap()
|
||||
serde_json::to_value(vec![status])?
|
||||
}
|
||||
RpcRequest::GetTransactionCount => Value::Number(Number::from(1234)),
|
||||
RpcRequest::GetSlot => Value::Number(Number::from(0)),
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::{
|
||||
client_error::{ClientError, Result as ClientResult},
|
||||
client_error::{ClientError, ClientErrorKind, Result as ClientResult},
|
||||
generic_rpc_client_request::GenericRpcClientRequest,
|
||||
mock_rpc_client_request::{MockRpcClientRequest, Mocks},
|
||||
rpc_client_request::RpcClientRequest,
|
||||
@ -7,10 +7,11 @@ use crate::{
|
||||
rpc_response::{
|
||||
Response, RpcAccount, RpcBlockhashFeeCalculator, RpcConfirmedBlock, RpcContactInfo,
|
||||
RpcEpochInfo, RpcFeeCalculator, RpcFeeRateGovernor, RpcIdentity, RpcKeyedAccount,
|
||||
RpcLeaderSchedule, RpcResult, RpcVersionInfo, RpcVoteAccountStatus,
|
||||
RpcLeaderSchedule, RpcResult, RpcTransactionStatus, RpcVersionInfo, RpcVoteAccountStatus,
|
||||
},
|
||||
};
|
||||
use bincode::serialize;
|
||||
use indicatif::{ProgressBar, ProgressStyle};
|
||||
use log::*;
|
||||
use serde_json::{json, Value};
|
||||
use solana_sdk::{
|
||||
@ -26,9 +27,11 @@ use solana_sdk::{
|
||||
signers::Signers,
|
||||
transaction::{self, Transaction, TransactionError},
|
||||
};
|
||||
use solana_vote_program::vote_state::MAX_LOCKOUT_HISTORY;
|
||||
use std::{
|
||||
error,
|
||||
net::SocketAddr,
|
||||
str::FromStr,
|
||||
thread::sleep,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
@ -117,12 +120,12 @@ impl RpcClient {
|
||||
) -> ClientResult<Option<transaction::Result<()>>> {
|
||||
let signature_status = self.client.send(
|
||||
&RpcRequest::GetSignatureStatus,
|
||||
json!([signature.to_string(), commitment_config]),
|
||||
json!([[signature.to_string()], commitment_config]),
|
||||
5,
|
||||
)?;
|
||||
let result: Option<transaction::Result<()>> =
|
||||
let result: Vec<Option<RpcTransactionStatus>> =
|
||||
serde_json::from_value(signature_status).unwrap();
|
||||
Ok(result)
|
||||
Ok(result[0].clone().map(|status_meta| status_meta.status))
|
||||
}
|
||||
|
||||
pub fn get_slot(&self) -> ClientResult<Slot> {
|
||||
@ -929,6 +932,99 @@ impl RpcClient {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn send_and_confirm_transaction_with_spinner<T: Signers>(
|
||||
&self,
|
||||
transaction: &mut Transaction,
|
||||
signer_keys: &T,
|
||||
) -> ClientResult<String> {
|
||||
let mut confirmations = 0;
|
||||
|
||||
let progress_bar = new_spinner_progress_bar();
|
||||
progress_bar.set_message(&format!(
|
||||
"[{}/{}] Waiting for confirmations",
|
||||
confirmations,
|
||||
MAX_LOCKOUT_HISTORY + 1,
|
||||
));
|
||||
|
||||
let mut send_retries = 20;
|
||||
let signature_str = loop {
|
||||
let mut status_retries = 15;
|
||||
let (signature_str, status) = loop {
|
||||
let signature_str = self.send_transaction(transaction)?;
|
||||
|
||||
// Get recent commitment in order to count confirmations for successful transactions
|
||||
let status = self.get_signature_status_with_commitment(
|
||||
&signature_str,
|
||||
CommitmentConfig::recent(),
|
||||
)?;
|
||||
if status.is_none() {
|
||||
status_retries -= 1;
|
||||
if status_retries == 0 {
|
||||
break (signature_str, status);
|
||||
}
|
||||
} else {
|
||||
break (signature_str, status);
|
||||
}
|
||||
|
||||
if cfg!(not(test)) {
|
||||
sleep(Duration::from_millis(500));
|
||||
}
|
||||
};
|
||||
send_retries = if let Some(result) = status.clone() {
|
||||
match result {
|
||||
Ok(_) => 0,
|
||||
Err(TransactionError::AccountInUse) => {
|
||||
// Fetch a new blockhash and re-sign the transaction before sending it again
|
||||
self.resign_transaction(transaction, signer_keys)?;
|
||||
send_retries - 1
|
||||
}
|
||||
// If transaction errors, return right away; no point in counting confirmations
|
||||
Err(_) => 0,
|
||||
}
|
||||
} else {
|
||||
send_retries - 1
|
||||
};
|
||||
if send_retries == 0 {
|
||||
if let Some(result) = status {
|
||||
match result {
|
||||
Ok(_) => {
|
||||
break signature_str;
|
||||
}
|
||||
Err(err) => {
|
||||
return Err(err.into());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return Err(
|
||||
RpcError::ForUser("unable to confirm transaction. This can happen in situations such as transaction expiration and insufficient fee-payer funds".to_string()).into(),
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
let signature = Signature::from_str(&signature_str).map_err(|_| {
|
||||
ClientError::from(ClientErrorKind::Custom(format!(
|
||||
"Returned string {} cannot be parsed as a signature",
|
||||
signature_str
|
||||
)))
|
||||
})?;
|
||||
loop {
|
||||
// Return when default (max) commitment is reached
|
||||
// Failed transactions have already been eliminated, `is_some` check is sufficient
|
||||
if self.get_signature_status(&signature_str)?.is_some() {
|
||||
progress_bar.set_message("Transaction confirmed");
|
||||
progress_bar.finish_and_clear();
|
||||
return Ok(signature_str);
|
||||
}
|
||||
progress_bar.set_message(&format!(
|
||||
"[{}/{}] Waiting for confirmations",
|
||||
confirmations + 1,
|
||||
MAX_LOCKOUT_HISTORY + 1,
|
||||
));
|
||||
sleep(Duration::from_millis(500));
|
||||
confirmations = self.get_num_blocks_since_signature_confirmation(&signature)?;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn validator_exit(&self) -> ClientResult<bool> {
|
||||
let response = self
|
||||
.client
|
||||
@ -944,6 +1040,14 @@ impl RpcClient {
|
||||
}
|
||||
}
|
||||
|
||||
fn new_spinner_progress_bar() -> ProgressBar {
|
||||
let progress_bar = ProgressBar::new(42);
|
||||
progress_bar
|
||||
.set_style(ProgressStyle::default_spinner().template("{spinner:.green} {wide_msg}"));
|
||||
progress_bar.enable_steady_tick(100);
|
||||
progress_bar
|
||||
}
|
||||
|
||||
pub fn get_rpc_request_str(rpc_addr: SocketAddr, tls: bool) -> String {
|
||||
if tls {
|
||||
format!("https://{}", rpc_addr)
|
||||
|
@ -95,7 +95,7 @@ impl RpcRequest {
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum RpcError {
|
||||
#[error("rpc reques error: {0}")]
|
||||
#[error("rpc request error: {0}")]
|
||||
RpcRequestError(String),
|
||||
#[error("parse error: expected {0}")]
|
||||
ParseError(String), /* "expected" */
|
||||
|
@ -52,7 +52,7 @@ pub struct RpcConfirmedBlock {
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RpcTransactionWithStatusMeta {
|
||||
pub transaction: RpcEncodedTransaction,
|
||||
pub meta: Option<RpcTransactionStatus>,
|
||||
pub meta: Option<RpcTransactionStatusMeta>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
|
||||
@ -136,13 +136,20 @@ pub struct RpcCompiledInstruction {
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RpcTransactionStatus {
|
||||
pub struct RpcTransactionStatusMeta {
|
||||
pub status: Result<()>,
|
||||
pub fee: u64,
|
||||
pub pre_balances: Vec<u64>,
|
||||
pub post_balances: Vec<u64>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RpcTransactionStatus {
|
||||
pub slot: Slot,
|
||||
pub status: Result<()>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RpcBlockhashFeeCalculator {
|
||||
|
@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "solana-core"
|
||||
description = "Blockchain, Rebuilt for Scale"
|
||||
version = "1.0.6"
|
||||
version = "1.0.9"
|
||||
documentation = "https://docs.rs/solana"
|
||||
homepage = "https://solana.com/"
|
||||
readme = "../README.md"
|
||||
@ -15,6 +15,7 @@ codecov = { repository = "solana-labs/solana", branch = "master", service = "git
|
||||
|
||||
[dependencies]
|
||||
bincode = "1.2.1"
|
||||
bv = { version = "0.11.1", features = ["serde"] }
|
||||
bs58 = "0.3.0"
|
||||
byteorder = "1.3.2"
|
||||
chrono = { version = "0.4.10", features = ["serde"] }
|
||||
@ -33,6 +34,7 @@ jsonrpc-ws-server = "14.0.6"
|
||||
libc = "0.2.66"
|
||||
log = "0.4.8"
|
||||
nix = "0.17.0"
|
||||
num_cpus = "1.0.0"
|
||||
num-traits = "0.2"
|
||||
rand = "0.6.5"
|
||||
rand_chacha = "0.1.1"
|
||||
@ -41,34 +43,33 @@ regex = "1.3.4"
|
||||
serde = "1.0.104"
|
||||
serde_derive = "1.0.103"
|
||||
serde_json = "1.0.46"
|
||||
solana-budget-program = { path = "../programs/budget", version = "1.0.6" }
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.0.6" }
|
||||
solana-client = { path = "../client", version = "1.0.6" }
|
||||
solana-faucet = { path = "../faucet", version = "1.0.6" }
|
||||
solana-budget-program = { path = "../programs/budget", version = "1.0.9" }
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.0.9" }
|
||||
solana-client = { path = "../client", version = "1.0.9" }
|
||||
solana-faucet = { path = "../faucet", version = "1.0.9" }
|
||||
ed25519-dalek = "=1.0.0-pre.1"
|
||||
solana-ledger = { path = "../ledger", version = "1.0.6" }
|
||||
solana-logger = { path = "../logger", version = "1.0.6" }
|
||||
solana-merkle-tree = { path = "../merkle-tree", version = "1.0.6" }
|
||||
solana-metrics = { path = "../metrics", version = "1.0.6" }
|
||||
solana-measure = { path = "../measure", version = "1.0.6" }
|
||||
solana-net-utils = { path = "../net-utils", version = "1.0.6" }
|
||||
solana-chacha-cuda = { path = "../chacha-cuda", version = "1.0.6" }
|
||||
solana-perf = { path = "../perf", version = "1.0.6" }
|
||||
solana-runtime = { path = "../runtime", version = "1.0.6" }
|
||||
solana-sdk = { path = "../sdk", version = "1.0.6" }
|
||||
solana-stake-program = { path = "../programs/stake", version = "1.0.6" }
|
||||
solana-storage-program = { path = "../programs/storage", version = "1.0.6" }
|
||||
solana-vote-program = { path = "../programs/vote", version = "1.0.6" }
|
||||
solana-vote-signer = { path = "../vote-signer", version = "1.0.6" }
|
||||
solana-sys-tuner = { path = "../sys-tuner", version = "1.0.6" }
|
||||
sys-info = "0.5.9"
|
||||
solana-ledger = { path = "../ledger", version = "1.0.9" }
|
||||
solana-logger = { path = "../logger", version = "1.0.9" }
|
||||
solana-merkle-tree = { path = "../merkle-tree", version = "1.0.9" }
|
||||
solana-metrics = { path = "../metrics", version = "1.0.9" }
|
||||
solana-measure = { path = "../measure", version = "1.0.9" }
|
||||
solana-net-utils = { path = "../net-utils", version = "1.0.9" }
|
||||
solana-chacha-cuda = { path = "../chacha-cuda", version = "1.0.9" }
|
||||
solana-perf = { path = "../perf", version = "1.0.9" }
|
||||
solana-runtime = { path = "../runtime", version = "1.0.9" }
|
||||
solana-sdk = { path = "../sdk", version = "1.0.9" }
|
||||
solana-stake-program = { path = "../programs/stake", version = "1.0.9" }
|
||||
solana-storage-program = { path = "../programs/storage", version = "1.0.9" }
|
||||
solana-vote-program = { path = "../programs/vote", version = "1.0.9" }
|
||||
solana-vote-signer = { path = "../vote-signer", version = "1.0.9" }
|
||||
solana-sys-tuner = { path = "../sys-tuner", version = "1.0.9" }
|
||||
tempfile = "3.1.0"
|
||||
thiserror = "1.0"
|
||||
tokio = "0.1"
|
||||
tokio-codec = "0.1"
|
||||
tokio-fs = "0.1"
|
||||
tokio-io = "0.1"
|
||||
solana-rayon-threadlimit = { path = "../rayon-threadlimit", version = "1.0.6" }
|
||||
solana-rayon-threadlimit = { path = "../rayon-threadlimit", version = "1.0.9" }
|
||||
trees = "0.2.1"
|
||||
|
||||
[dev-dependencies]
|
||||
|
200
core/src/accounts_hash_verifier.rs
Normal file
200
core/src/accounts_hash_verifier.rs
Normal file
@ -0,0 +1,200 @@
|
||||
// Service to verify accounts hashes with other trusted validator nodes.
|
||||
//
|
||||
// Each interval, publish the snapshat hash which is the full accounts state
|
||||
// hash on gossip. Monitor gossip for messages from validators in the --trusted-validators
|
||||
// set and halt the node if a mismatch is detected.
|
||||
|
||||
use crate::cluster_info::ClusterInfo;
|
||||
use solana_ledger::{
|
||||
snapshot_package::SnapshotPackage, snapshot_package::SnapshotPackageReceiver,
|
||||
snapshot_package::SnapshotPackageSender,
|
||||
};
|
||||
use solana_sdk::{clock::Slot, hash::Hash, pubkey::Pubkey};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::{
|
||||
sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
mpsc::RecvTimeoutError,
|
||||
Arc, RwLock,
|
||||
},
|
||||
thread::{self, Builder, JoinHandle},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
pub struct AccountsHashVerifier {
|
||||
t_accounts_hash_verifier: JoinHandle<()>,
|
||||
}
|
||||
|
||||
impl AccountsHashVerifier {
|
||||
pub fn new(
|
||||
snapshot_package_receiver: SnapshotPackageReceiver,
|
||||
snapshot_package_sender: Option<SnapshotPackageSender>,
|
||||
exit: &Arc<AtomicBool>,
|
||||
cluster_info: &Arc<RwLock<ClusterInfo>>,
|
||||
trusted_validators: Option<HashSet<Pubkey>>,
|
||||
halt_on_trusted_validators_accounts_hash_mismatch: bool,
|
||||
fault_injection_rate_slots: u64,
|
||||
) -> Self {
|
||||
let exit = exit.clone();
|
||||
let cluster_info = cluster_info.clone();
|
||||
let t_accounts_hash_verifier = Builder::new()
|
||||
.name("solana-accounts-hash".to_string())
|
||||
.spawn(move || {
|
||||
let mut hashes = vec![];
|
||||
loop {
|
||||
if exit.load(Ordering::Relaxed) {
|
||||
break;
|
||||
}
|
||||
|
||||
match snapshot_package_receiver.recv_timeout(Duration::from_secs(1)) {
|
||||
Ok(snapshot_package) => {
|
||||
Self::process_snapshot(
|
||||
snapshot_package,
|
||||
&cluster_info,
|
||||
&trusted_validators,
|
||||
halt_on_trusted_validators_accounts_hash_mismatch,
|
||||
&snapshot_package_sender,
|
||||
&mut hashes,
|
||||
&exit,
|
||||
fault_injection_rate_slots,
|
||||
);
|
||||
}
|
||||
Err(RecvTimeoutError::Disconnected) => break,
|
||||
Err(RecvTimeoutError::Timeout) => (),
|
||||
}
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
Self {
|
||||
t_accounts_hash_verifier,
|
||||
}
|
||||
}
|
||||
|
||||
fn process_snapshot(
|
||||
snapshot_package: SnapshotPackage,
|
||||
cluster_info: &Arc<RwLock<ClusterInfo>>,
|
||||
trusted_validators: &Option<HashSet<Pubkey>>,
|
||||
halt_on_trusted_validator_accounts_hash_mismatch: bool,
|
||||
snapshot_package_sender: &Option<SnapshotPackageSender>,
|
||||
hashes: &mut Vec<(Slot, Hash)>,
|
||||
exit: &Arc<AtomicBool>,
|
||||
fault_injection_rate_slots: u64,
|
||||
) {
|
||||
if fault_injection_rate_slots != 0
|
||||
&& snapshot_package.root % 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: {}", snapshot_package.root);
|
||||
let rand = thread_rng().gen_range(0, 10);
|
||||
let hash = extend_and_hash(&snapshot_package.hash, &[rand]);
|
||||
hashes.push((snapshot_package.root, hash));
|
||||
} else {
|
||||
hashes.push((snapshot_package.root, snapshot_package.hash));
|
||||
}
|
||||
|
||||
if halt_on_trusted_validator_accounts_hash_mismatch {
|
||||
let mut slot_to_hash = HashMap::new();
|
||||
for (slot, hash) in hashes.iter() {
|
||||
slot_to_hash.insert(*slot, *hash);
|
||||
}
|
||||
if Self::should_halt(&cluster_info, trusted_validators, &mut slot_to_hash) {
|
||||
exit.store(true, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
if let Some(sender) = snapshot_package_sender.as_ref() {
|
||||
if sender.send(snapshot_package).is_err() {}
|
||||
}
|
||||
|
||||
cluster_info
|
||||
.write()
|
||||
.unwrap()
|
||||
.push_accounts_hashes(hashes.clone());
|
||||
}
|
||||
|
||||
fn should_halt(
|
||||
cluster_info: &Arc<RwLock<ClusterInfo>>,
|
||||
trusted_validators: &Option<HashSet<Pubkey>>,
|
||||
slot_to_hash: &mut HashMap<Slot, Hash>,
|
||||
) -> bool {
|
||||
let mut verified_count = 0;
|
||||
if let Some(trusted_validators) = trusted_validators.as_ref() {
|
||||
for trusted_validator in trusted_validators {
|
||||
let cluster_info_r = cluster_info.read().unwrap();
|
||||
if let Some(accounts_hashes) =
|
||||
cluster_info_r.get_accounts_hash_for_node(trusted_validator)
|
||||
{
|
||||
for (slot, hash) in accounts_hashes {
|
||||
if let Some(reference_hash) = slot_to_hash.get(slot) {
|
||||
if *hash != *reference_hash {
|
||||
error!("Trusted validator {} produced conflicting hashes for slot: {} ({} != {})",
|
||||
trusted_validator,
|
||||
slot,
|
||||
hash,
|
||||
reference_hash,
|
||||
);
|
||||
|
||||
return true;
|
||||
} else {
|
||||
verified_count += 1;
|
||||
}
|
||||
} else {
|
||||
slot_to_hash.insert(*slot, *hash);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
inc_new_counter_info!("accounts_hash_verifier-hashes_verified", verified_count);
|
||||
false
|
||||
}
|
||||
|
||||
pub fn join(self) -> thread::Result<()> {
|
||||
self.t_accounts_hash_verifier.join()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::cluster_info::make_accounts_hashes_message;
|
||||
use crate::contact_info::ContactInfo;
|
||||
use solana_sdk::{
|
||||
hash::hash,
|
||||
signature::{Keypair, Signer},
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn test_should_halt() {
|
||||
let keypair = Keypair::new();
|
||||
|
||||
let contact_info = ContactInfo::new_localhost(&keypair.pubkey(), 0);
|
||||
let cluster_info = ClusterInfo::new_with_invalid_keypair(contact_info);
|
||||
let cluster_info = Arc::new(RwLock::new(cluster_info));
|
||||
|
||||
let mut trusted_validators = HashSet::new();
|
||||
let mut slot_to_hash = HashMap::new();
|
||||
assert!(!AccountsHashVerifier::should_halt(
|
||||
&cluster_info,
|
||||
&Some(trusted_validators.clone()),
|
||||
&mut slot_to_hash,
|
||||
));
|
||||
|
||||
let validator1 = Keypair::new();
|
||||
let hash1 = hash(&[1]);
|
||||
let hash2 = hash(&[2]);
|
||||
{
|
||||
let message = make_accounts_hashes_message(&validator1, vec![(0, hash1)]).unwrap();
|
||||
let mut cluster_info_w = cluster_info.write().unwrap();
|
||||
cluster_info_w.push_message(message);
|
||||
}
|
||||
slot_to_hash.insert(0, hash2);
|
||||
trusted_validators.insert(validator1.pubkey());
|
||||
assert!(AccountsHashVerifier::should_halt(
|
||||
&cluster_info,
|
||||
&Some(trusted_validators.clone()),
|
||||
&mut slot_to_hash,
|
||||
));
|
||||
}
|
||||
}
|
@ -48,7 +48,7 @@ use solana_sdk::timing::duration_as_s;
|
||||
use solana_sdk::{
|
||||
clock::{Slot, DEFAULT_MS_PER_SLOT},
|
||||
pubkey::Pubkey,
|
||||
signature::{Keypair, Signable, Signature},
|
||||
signature::{Keypair, Signable, Signature, Signer},
|
||||
timing::{duration_as_ms, timestamp},
|
||||
transaction::Transaction,
|
||||
};
|
||||
@ -180,6 +180,14 @@ struct PullData {
|
||||
pub filter: CrdsFilter,
|
||||
}
|
||||
|
||||
pub fn make_accounts_hashes_message(
|
||||
keypair: &Keypair,
|
||||
accounts_hashes: Vec<(Slot, Hash)>,
|
||||
) -> Option<CrdsValue> {
|
||||
let message = CrdsData::AccountsHashes(SnapshotHash::new(keypair.pubkey(), accounts_hashes));
|
||||
Some(CrdsValue::new_signed(message, keypair))
|
||||
}
|
||||
|
||||
// TODO These messages should go through the gpu pipeline for spam filtering
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
@ -443,22 +451,36 @@ impl ClusterInfo {
|
||||
.process_push_message(&self.id(), vec![entry], now);
|
||||
}
|
||||
|
||||
pub fn push_snapshot_hashes(&mut self, snapshot_hashes: Vec<(Slot, Hash)>) {
|
||||
if snapshot_hashes.len() > MAX_SNAPSHOT_HASHES {
|
||||
pub fn push_message(&mut self, message: CrdsValue) {
|
||||
let now = message.wallclock();
|
||||
let id = message.pubkey();
|
||||
self.gossip.process_push_message(&id, vec![message], now);
|
||||
}
|
||||
|
||||
pub fn push_accounts_hashes(&mut self, accounts_hashes: Vec<(Slot, Hash)>) {
|
||||
if accounts_hashes.len() > MAX_SNAPSHOT_HASHES {
|
||||
warn!(
|
||||
"snapshot_hashes too large, ignored: {}",
|
||||
snapshot_hashes.len()
|
||||
"accounts hashes too large, ignored: {}",
|
||||
accounts_hashes.len(),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
let now = timestamp();
|
||||
let entry = CrdsValue::new_signed(
|
||||
CrdsData::SnapshotHash(SnapshotHash::new(self.id(), snapshot_hashes, now)),
|
||||
&self.keypair,
|
||||
);
|
||||
self.gossip
|
||||
.process_push_message(&self.id(), vec![entry], now);
|
||||
let message = CrdsData::AccountsHashes(SnapshotHash::new(self.id(), accounts_hashes));
|
||||
self.push_message(CrdsValue::new_signed(message, &self.keypair));
|
||||
}
|
||||
|
||||
pub fn push_snapshot_hashes(&mut self, snapshot_hashes: Vec<(Slot, Hash)>) {
|
||||
if snapshot_hashes.len() > MAX_SNAPSHOT_HASHES {
|
||||
warn!(
|
||||
"snapshot hashes too large, ignored: {}",
|
||||
snapshot_hashes.len(),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
let message = CrdsData::SnapshotHashes(SnapshotHash::new(self.id(), snapshot_hashes));
|
||||
self.push_message(CrdsValue::new_signed(message, &self.keypair));
|
||||
}
|
||||
|
||||
pub fn push_vote(&mut self, tower_index: usize, vote: Transaction) {
|
||||
@ -518,11 +540,19 @@ impl ClusterInfo {
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn get_accounts_hash_for_node(&self, pubkey: &Pubkey) -> Option<&Vec<(Slot, Hash)>> {
|
||||
self.gossip
|
||||
.crds
|
||||
.table
|
||||
.get(&CrdsValueLabel::AccountsHashes(*pubkey))
|
||||
.map(|x| &x.value.accounts_hash().unwrap().hashes)
|
||||
}
|
||||
|
||||
pub fn get_snapshot_hash_for_node(&self, pubkey: &Pubkey) -> Option<&Vec<(Slot, Hash)>> {
|
||||
self.gossip
|
||||
.crds
|
||||
.table
|
||||
.get(&CrdsValueLabel::SnapshotHash(*pubkey))
|
||||
.get(&CrdsValueLabel::SnapshotHashes(*pubkey))
|
||||
.map(|x| &x.value.snapshot_hash().unwrap().hashes)
|
||||
}
|
||||
|
||||
@ -1615,7 +1645,7 @@ impl ClusterInfo {
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn gossip_contact_info(id: &Pubkey, gossip: SocketAddr) -> ContactInfo {
|
||||
pub fn gossip_contact_info(id: &Pubkey, gossip: SocketAddr) -> ContactInfo {
|
||||
ContactInfo {
|
||||
id: *id,
|
||||
gossip,
|
||||
|
@ -51,7 +51,8 @@ impl ClusterInfoVoteListener {
|
||||
if exit.load(Ordering::Relaxed) {
|
||||
return Ok(());
|
||||
}
|
||||
if let Some(bank) = poh_recorder.lock().unwrap().bank() {
|
||||
let poh_bank = poh_recorder.lock().unwrap().bank();
|
||||
if let Some(bank) = poh_bank {
|
||||
let last_ts = bank.last_vote_sync.load(Ordering::Relaxed);
|
||||
let (votes, new_ts) = cluster_info.read().unwrap().get_votes(last_ts);
|
||||
bank.last_vote_sync
|
||||
|
@ -1,5 +1,6 @@
|
||||
use crate::contact_info::ContactInfo;
|
||||
use bincode::{serialize, serialized_size};
|
||||
use solana_sdk::timing::timestamp;
|
||||
use solana_sdk::{
|
||||
clock::Slot,
|
||||
hash::Hash,
|
||||
@ -62,7 +63,8 @@ pub enum CrdsData {
|
||||
ContactInfo(ContactInfo),
|
||||
Vote(VoteIndex, Vote),
|
||||
EpochSlots(EpochSlotIndex, EpochSlots),
|
||||
SnapshotHash(SnapshotHash),
|
||||
SnapshotHashes(SnapshotHash),
|
||||
AccountsHashes(SnapshotHash),
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
|
||||
@ -93,11 +95,11 @@ pub struct SnapshotHash {
|
||||
}
|
||||
|
||||
impl SnapshotHash {
|
||||
pub fn new(from: Pubkey, hashes: Vec<(Slot, Hash)>, wallclock: u64) -> Self {
|
||||
pub fn new(from: Pubkey, hashes: Vec<(Slot, Hash)>) -> Self {
|
||||
Self {
|
||||
from,
|
||||
hashes,
|
||||
wallclock,
|
||||
wallclock: timestamp(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -156,7 +158,8 @@ pub enum CrdsValueLabel {
|
||||
ContactInfo(Pubkey),
|
||||
Vote(VoteIndex, Pubkey),
|
||||
EpochSlots(Pubkey),
|
||||
SnapshotHash(Pubkey),
|
||||
SnapshotHashes(Pubkey),
|
||||
AccountsHashes(Pubkey),
|
||||
}
|
||||
|
||||
impl fmt::Display for CrdsValueLabel {
|
||||
@ -165,7 +168,8 @@ impl fmt::Display for CrdsValueLabel {
|
||||
CrdsValueLabel::ContactInfo(_) => write!(f, "ContactInfo({})", self.pubkey()),
|
||||
CrdsValueLabel::Vote(ix, _) => write!(f, "Vote({}, {})", ix, self.pubkey()),
|
||||
CrdsValueLabel::EpochSlots(_) => write!(f, "EpochSlots({})", self.pubkey()),
|
||||
CrdsValueLabel::SnapshotHash(_) => write!(f, "SnapshotHash({})", self.pubkey()),
|
||||
CrdsValueLabel::SnapshotHashes(_) => write!(f, "SnapshotHashes({})", self.pubkey()),
|
||||
CrdsValueLabel::AccountsHashes(_) => write!(f, "AccountsHashes({})", self.pubkey()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -176,7 +180,8 @@ impl CrdsValueLabel {
|
||||
CrdsValueLabel::ContactInfo(p) => *p,
|
||||
CrdsValueLabel::Vote(_, p) => *p,
|
||||
CrdsValueLabel::EpochSlots(p) => *p,
|
||||
CrdsValueLabel::SnapshotHash(p) => *p,
|
||||
CrdsValueLabel::SnapshotHashes(p) => *p,
|
||||
CrdsValueLabel::AccountsHashes(p) => *p,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -202,7 +207,8 @@ impl CrdsValue {
|
||||
CrdsData::ContactInfo(contact_info) => contact_info.wallclock,
|
||||
CrdsData::Vote(_, vote) => vote.wallclock,
|
||||
CrdsData::EpochSlots(_, vote) => vote.wallclock,
|
||||
CrdsData::SnapshotHash(hash) => hash.wallclock,
|
||||
CrdsData::SnapshotHashes(hash) => hash.wallclock,
|
||||
CrdsData::AccountsHashes(hash) => hash.wallclock,
|
||||
}
|
||||
}
|
||||
pub fn pubkey(&self) -> Pubkey {
|
||||
@ -210,7 +216,8 @@ impl CrdsValue {
|
||||
CrdsData::ContactInfo(contact_info) => contact_info.id,
|
||||
CrdsData::Vote(_, vote) => vote.from,
|
||||
CrdsData::EpochSlots(_, slots) => slots.from,
|
||||
CrdsData::SnapshotHash(hash) => hash.from,
|
||||
CrdsData::SnapshotHashes(hash) => hash.from,
|
||||
CrdsData::AccountsHashes(hash) => hash.from,
|
||||
}
|
||||
}
|
||||
pub fn label(&self) -> CrdsValueLabel {
|
||||
@ -218,7 +225,8 @@ impl CrdsValue {
|
||||
CrdsData::ContactInfo(_) => CrdsValueLabel::ContactInfo(self.pubkey()),
|
||||
CrdsData::Vote(ix, _) => CrdsValueLabel::Vote(*ix, self.pubkey()),
|
||||
CrdsData::EpochSlots(_, _) => CrdsValueLabel::EpochSlots(self.pubkey()),
|
||||
CrdsData::SnapshotHash(_) => CrdsValueLabel::SnapshotHash(self.pubkey()),
|
||||
CrdsData::SnapshotHashes(_) => CrdsValueLabel::SnapshotHashes(self.pubkey()),
|
||||
CrdsData::AccountsHashes(_) => CrdsValueLabel::AccountsHashes(self.pubkey()),
|
||||
}
|
||||
}
|
||||
pub fn contact_info(&self) -> Option<&ContactInfo> {
|
||||
@ -250,7 +258,14 @@ impl CrdsValue {
|
||||
|
||||
pub fn snapshot_hash(&self) -> Option<&SnapshotHash> {
|
||||
match &self.data {
|
||||
CrdsData::SnapshotHash(slots) => Some(slots),
|
||||
CrdsData::SnapshotHashes(slots) => Some(slots),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn accounts_hash(&self) -> Option<&SnapshotHash> {
|
||||
match &self.data {
|
||||
CrdsData::AccountsHashes(slots) => Some(slots),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@ -260,7 +275,8 @@ impl CrdsValue {
|
||||
let mut labels = vec![
|
||||
CrdsValueLabel::ContactInfo(*key),
|
||||
CrdsValueLabel::EpochSlots(*key),
|
||||
CrdsValueLabel::SnapshotHash(*key),
|
||||
CrdsValueLabel::SnapshotHashes(*key),
|
||||
CrdsValueLabel::AccountsHashes(*key),
|
||||
];
|
||||
labels.extend((0..MAX_VOTES).map(|ix| CrdsValueLabel::Vote(ix, *key)));
|
||||
labels
|
||||
@ -310,14 +326,15 @@ mod test {
|
||||
|
||||
#[test]
|
||||
fn test_labels() {
|
||||
let mut hits = [false; 3 + MAX_VOTES as usize];
|
||||
let mut hits = [false; 4 + MAX_VOTES as usize];
|
||||
// this method should cover all the possible labels
|
||||
for v in &CrdsValue::record_labels(&Pubkey::default()) {
|
||||
match v {
|
||||
CrdsValueLabel::ContactInfo(_) => hits[0] = true,
|
||||
CrdsValueLabel::EpochSlots(_) => hits[1] = true,
|
||||
CrdsValueLabel::SnapshotHash(_) => hits[2] = true,
|
||||
CrdsValueLabel::Vote(ix, _) => hits[*ix as usize + 3] = true,
|
||||
CrdsValueLabel::SnapshotHashes(_) => hits[2] = true,
|
||||
CrdsValueLabel::AccountsHashes(_) => hits[3] = true,
|
||||
CrdsValueLabel::Vote(ix, _) => hits[*ix as usize + 4] = true,
|
||||
}
|
||||
}
|
||||
assert!(hits.iter().all(|x| *x));
|
||||
|
@ -5,6 +5,7 @@
|
||||
//! command-line tools to spin up validators and a Rust library
|
||||
//!
|
||||
|
||||
pub mod accounts_hash_verifier;
|
||||
pub mod banking_stage;
|
||||
pub mod broadcast_stage;
|
||||
pub mod cluster_info_vote_listener;
|
||||
|
@ -11,6 +11,7 @@ use crate::{
|
||||
};
|
||||
use solana_ledger::{
|
||||
bank_forks::BankForks,
|
||||
block_error::BlockError,
|
||||
blockstore::Blockstore,
|
||||
blockstore_processor::{
|
||||
self, BlockstoreProcessorError, ConfirmationProgress, ConfirmationTiming,
|
||||
@ -75,7 +76,7 @@ pub struct ReplayStageConfig {
|
||||
pub leader_schedule_cache: Arc<LeaderScheduleCache>,
|
||||
pub slot_full_senders: Vec<Sender<(u64, Pubkey)>>,
|
||||
pub latest_root_senders: Vec<Sender<Slot>>,
|
||||
pub snapshot_package_sender: Option<SnapshotPackageSender>,
|
||||
pub accounts_hash_sender: Option<SnapshotPackageSender>,
|
||||
pub block_commitment_cache: Arc<RwLock<BlockCommitmentCache>>,
|
||||
pub transaction_status_sender: Option<TransactionStatusSender>,
|
||||
pub rewards_recorder_sender: Option<RewardsRecorderSender>,
|
||||
@ -178,7 +179,7 @@ impl ReplayStage {
|
||||
leader_schedule_cache,
|
||||
slot_full_senders,
|
||||
latest_root_senders,
|
||||
snapshot_package_sender,
|
||||
accounts_hash_sender,
|
||||
block_commitment_cache,
|
||||
transaction_status_sender,
|
||||
rewards_recorder_sender,
|
||||
@ -333,7 +334,7 @@ impl ReplayStage {
|
||||
&root_bank_sender,
|
||||
total_staked,
|
||||
&lockouts_sender,
|
||||
&snapshot_package_sender,
|
||||
&accounts_hash_sender,
|
||||
&latest_root_senders,
|
||||
)?;
|
||||
}
|
||||
@ -395,7 +396,8 @@ impl ReplayStage {
|
||||
rewards_recorder_sender.clone(),
|
||||
);
|
||||
|
||||
if let Some(bank) = poh_recorder.lock().unwrap().bank() {
|
||||
let poh_bank = poh_recorder.lock().unwrap().bank();
|
||||
if let Some(bank) = poh_bank {
|
||||
Self::log_leader_change(
|
||||
&my_pubkey,
|
||||
bank.slot(),
|
||||
@ -566,11 +568,19 @@ impl ReplayStage {
|
||||
// errors related to the slot being purged
|
||||
let slot = bank.slot();
|
||||
warn!("Fatal replay error in slot: {}, err: {:?}", slot, err);
|
||||
datapoint_error!(
|
||||
"replay-stage-mark_dead_slot",
|
||||
("error", format!("error: {:?}", err), String),
|
||||
("slot", slot, i64)
|
||||
);
|
||||
if let BlockstoreProcessorError::InvalidBlock(BlockError::InvalidTickCount) = err {
|
||||
datapoint_info!(
|
||||
"replay-stage-mark_dead_slot",
|
||||
("error", format!("error: {:?}", err), String),
|
||||
("slot", slot, i64)
|
||||
);
|
||||
} else {
|
||||
datapoint_error!(
|
||||
"replay-stage-mark_dead_slot",
|
||||
("error", format!("error: {:?}", err), String),
|
||||
("slot", slot, i64)
|
||||
);
|
||||
}
|
||||
bank_progress.is_dead = true;
|
||||
blockstore
|
||||
.set_dead_slot(slot)
|
||||
@ -595,7 +605,7 @@ impl ReplayStage {
|
||||
root_bank_sender: &Sender<Vec<Arc<Bank>>>,
|
||||
total_staked: u64,
|
||||
lockouts_sender: &Sender<CommitmentAggregationData>,
|
||||
snapshot_package_sender: &Option<SnapshotPackageSender>,
|
||||
accounts_hash_sender: &Option<SnapshotPackageSender>,
|
||||
latest_root_senders: &[Sender<Slot>],
|
||||
) -> Result<()> {
|
||||
if bank.is_empty() {
|
||||
@ -622,7 +632,7 @@ impl ReplayStage {
|
||||
blockstore
|
||||
.set_roots(&rooted_slots)
|
||||
.expect("Ledger set roots failed");
|
||||
Self::handle_new_root(new_root, &bank_forks, progress, snapshot_package_sender);
|
||||
Self::handle_new_root(new_root, &bank_forks, progress, accounts_hash_sender);
|
||||
latest_root_senders.iter().for_each(|s| {
|
||||
if let Err(e) = s.send(new_root) {
|
||||
trace!("latest root send failed: {:?}", e);
|
||||
@ -949,12 +959,12 @@ impl ReplayStage {
|
||||
new_root: u64,
|
||||
bank_forks: &RwLock<BankForks>,
|
||||
progress: &mut HashMap<u64, ForkProgress>,
|
||||
snapshot_package_sender: &Option<SnapshotPackageSender>,
|
||||
accounts_hash_sender: &Option<SnapshotPackageSender>,
|
||||
) {
|
||||
bank_forks
|
||||
.write()
|
||||
.unwrap()
|
||||
.set_root(new_root, snapshot_package_sender);
|
||||
.set_root(new_root, accounts_hash_sender);
|
||||
let r_bank_forks = bank_forks.read().unwrap();
|
||||
progress.retain(|k, _| r_bank_forks.get(*k).is_some());
|
||||
}
|
||||
@ -1058,7 +1068,6 @@ pub(crate) mod tests {
|
||||
use crossbeam_channel::unbounded;
|
||||
use solana_client::rpc_response::{RpcEncodedTransaction, RpcTransactionWithStatusMeta};
|
||||
use solana_ledger::{
|
||||
block_error::BlockError,
|
||||
blockstore::make_slot_entries,
|
||||
blockstore::{entries_to_test_shreds, BlockstoreError},
|
||||
create_new_tmp_ledger,
|
||||
|
145
core/src/rpc.rs
145
core/src/rpc.rs
@ -7,18 +7,12 @@ use crate::{
|
||||
use bincode::serialize;
|
||||
use jsonrpc_core::{Error, Metadata, Result};
|
||||
use jsonrpc_derive::rpc;
|
||||
use solana_client::rpc_response::{
|
||||
Response, RpcAccount, RpcBlockCommitment, RpcBlockhashFeeCalculator, RpcConfirmedBlock,
|
||||
RpcContactInfo, RpcEpochInfo, RpcFeeCalculator, RpcFeeRateGovernor, RpcIdentity,
|
||||
RpcKeyedAccount, RpcLeaderSchedule, RpcResponseContext, RpcSignatureConfirmation,
|
||||
RpcStorageTurn, RpcTransactionEncoding, RpcVersionInfo, RpcVoteAccountInfo,
|
||||
RpcVoteAccountStatus,
|
||||
};
|
||||
use solana_client::rpc_response::*;
|
||||
use solana_faucet::faucet::request_airdrop_transaction;
|
||||
use solana_ledger::{
|
||||
bank_forks::BankForks, blockstore::Blockstore, rooted_slot_iterator::RootedSlotIterator,
|
||||
};
|
||||
use solana_runtime::bank::Bank;
|
||||
use solana_runtime::{bank::Bank, status_cache::SignatureConfirmationStatus};
|
||||
use solana_sdk::{
|
||||
clock::{Slot, UnixTimestamp},
|
||||
commitment_config::{CommitmentConfig, CommitmentLevel},
|
||||
@ -28,7 +22,7 @@ use solana_sdk::{
|
||||
pubkey::Pubkey,
|
||||
signature::Signature,
|
||||
timing::slot_duration_from_slots_per_year,
|
||||
transaction::{self, Transaction},
|
||||
transaction::Transaction,
|
||||
};
|
||||
use solana_vote_program::vote_state::{VoteState, MAX_LOCKOUT_HISTORY};
|
||||
use std::{
|
||||
@ -51,7 +45,7 @@ fn new_response<T>(bank: &Bank, value: T) -> RpcResponse<T> {
|
||||
pub struct JsonRpcConfig {
|
||||
pub enable_validator_exit: bool,
|
||||
pub enable_set_log_filter: bool,
|
||||
pub enable_get_confirmed_block: bool,
|
||||
pub enable_rpc_transaction_history: bool,
|
||||
pub identity_pubkey: Pubkey,
|
||||
pub faucet_addr: Option<SocketAddr>,
|
||||
}
|
||||
@ -202,7 +196,9 @@ impl JsonRpcRequestProcessor {
|
||||
Ok(sig) => {
|
||||
let status = bank.get_signature_confirmation_status(&sig);
|
||||
match status {
|
||||
Some((_, result)) => new_response(bank, result.is_ok()),
|
||||
Some(SignatureConfirmationStatus { status, .. }) => {
|
||||
new_response(bank, status.is_ok())
|
||||
}
|
||||
None => new_response(bank, false),
|
||||
}
|
||||
}
|
||||
@ -226,10 +222,16 @@ impl JsonRpcRequestProcessor {
|
||||
) -> Option<RpcSignatureConfirmation> {
|
||||
self.bank(commitment)
|
||||
.get_signature_confirmation_status(&signature)
|
||||
.map(|(confirmations, status)| RpcSignatureConfirmation {
|
||||
confirmations,
|
||||
status,
|
||||
})
|
||||
.map(
|
||||
|SignatureConfirmationStatus {
|
||||
confirmations,
|
||||
status,
|
||||
..
|
||||
}| RpcSignatureConfirmation {
|
||||
confirmations,
|
||||
status,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn get_slot(&self, commitment: Option<CommitmentConfig>) -> Result<u64> {
|
||||
@ -365,7 +367,7 @@ impl JsonRpcRequestProcessor {
|
||||
slot: Slot,
|
||||
encoding: Option<RpcTransactionEncoding>,
|
||||
) -> Result<Option<RpcConfirmedBlock>> {
|
||||
if self.config.enable_get_confirmed_block {
|
||||
if self.config.enable_rpc_transaction_history {
|
||||
Ok(self.blockstore.get_confirmed_block(slot, encoding).ok())
|
||||
} else {
|
||||
Ok(None)
|
||||
@ -413,6 +415,27 @@ impl JsonRpcRequestProcessor {
|
||||
.ok()
|
||||
.unwrap_or(None))
|
||||
}
|
||||
|
||||
pub fn get_signature_status(
|
||||
&self,
|
||||
signatures: Vec<Signature>,
|
||||
commitment: Option<CommitmentConfig>,
|
||||
) -> Result<Vec<Option<RpcTransactionStatus>>> {
|
||||
let mut statuses: Vec<Option<RpcTransactionStatus>> = vec![];
|
||||
|
||||
let bank = self.bank(commitment);
|
||||
|
||||
for signature in signatures {
|
||||
let status = bank.get_signature_confirmation_status(&signature).map(
|
||||
|SignatureConfirmationStatus { slot, status, .. }| RpcTransactionStatus {
|
||||
slot,
|
||||
status,
|
||||
},
|
||||
);
|
||||
statuses.push(status);
|
||||
}
|
||||
Ok(statuses)
|
||||
}
|
||||
}
|
||||
|
||||
fn get_tpu_addr(cluster_info: &Arc<RwLock<ClusterInfo>>) -> Result<SocketAddr> {
|
||||
@ -539,9 +562,9 @@ pub trait RpcSol {
|
||||
fn get_signature_status(
|
||||
&self,
|
||||
meta: Self::Metadata,
|
||||
signature_str: String,
|
||||
signature_strs: Vec<String>,
|
||||
commitment: Option<CommitmentConfig>,
|
||||
) -> Result<Option<transaction::Result<()>>>;
|
||||
) -> Result<Vec<Option<RpcTransactionStatus>>>;
|
||||
|
||||
#[rpc(meta, name = "getSlot")]
|
||||
fn get_slot(&self, meta: Self::Metadata, commitment: Option<CommitmentConfig>) -> Result<u64>;
|
||||
@ -885,11 +908,17 @@ impl RpcSol for RpcSolImpl {
|
||||
fn get_signature_status(
|
||||
&self,
|
||||
meta: Self::Metadata,
|
||||
signature_str: String,
|
||||
signature_strs: Vec<String>,
|
||||
commitment: Option<CommitmentConfig>,
|
||||
) -> Result<Option<transaction::Result<()>>> {
|
||||
self.get_signature_confirmation(meta, signature_str, commitment)
|
||||
.map(|res| res.map(|x| x.status))
|
||||
) -> Result<Vec<Option<RpcTransactionStatus>>> {
|
||||
let mut signatures: Vec<Signature> = vec![];
|
||||
for signature_str in signature_strs {
|
||||
signatures.push(verify_signature(&signature_str)?);
|
||||
}
|
||||
meta.request_processor
|
||||
.read()
|
||||
.unwrap()
|
||||
.get_signature_status(signatures, commitment)
|
||||
}
|
||||
|
||||
fn get_slot(&self, meta: Self::Metadata, commitment: Option<CommitmentConfig>) -> Result<u64> {
|
||||
@ -1200,7 +1229,7 @@ pub mod tests {
|
||||
rpc_port,
|
||||
signature::{Keypair, Signer},
|
||||
system_transaction,
|
||||
transaction::TransactionError,
|
||||
transaction::{self, TransactionError},
|
||||
};
|
||||
use solana_vote_program::{
|
||||
vote_instruction,
|
||||
@ -1329,7 +1358,7 @@ pub mod tests {
|
||||
|
||||
let request_processor = Arc::new(RwLock::new(JsonRpcRequestProcessor::new(
|
||||
JsonRpcConfig {
|
||||
enable_get_confirmed_block: true,
|
||||
enable_rpc_transaction_history: true,
|
||||
identity_pubkey: *pubkey,
|
||||
..JsonRpcConfig::default()
|
||||
},
|
||||
@ -1773,66 +1802,50 @@ pub mod tests {
|
||||
meta,
|
||||
blockhash,
|
||||
alice,
|
||||
confirmed_block_signatures,
|
||||
..
|
||||
} = start_rpc_handler_with_tx(&bob_pubkey);
|
||||
|
||||
let tx = system_transaction::transfer(&alice, &bob_pubkey, 20, blockhash);
|
||||
let req = format!(
|
||||
r#"{{"jsonrpc":"2.0","id":1,"method":"getSignatureStatus","params":["{}"]}}"#,
|
||||
tx.signatures[0]
|
||||
r#"{{"jsonrpc":"2.0","id":1,"method":"getSignatureStatus","params":[["{}"]]}}"#,
|
||||
confirmed_block_signatures[0]
|
||||
);
|
||||
let res = io.handle_request_sync(&req, meta.clone());
|
||||
let expected_res: Option<transaction::Result<()>> = Some(Ok(()));
|
||||
let expected = json!({
|
||||
"jsonrpc": "2.0",
|
||||
"result": expected_res,
|
||||
"id": 1
|
||||
});
|
||||
let expected: Response =
|
||||
serde_json::from_value(expected).expect("expected response deserialization");
|
||||
let result: Response = serde_json::from_str(&res.expect("actual response"))
|
||||
.expect("actual response deserialization");
|
||||
assert_eq!(expected, result);
|
||||
let expected_res: transaction::Result<()> = Ok(());
|
||||
let json: Value = serde_json::from_str(&res.unwrap()).unwrap();
|
||||
let result: Vec<Option<RpcTransactionStatus>> =
|
||||
serde_json::from_value(json["result"].clone())
|
||||
.expect("actual response deserialization");
|
||||
assert_eq!(expected_res, result[0].as_ref().unwrap().status);
|
||||
|
||||
// Test getSignatureStatus request on unprocessed tx
|
||||
let tx = system_transaction::transfer(&alice, &bob_pubkey, 10, blockhash);
|
||||
let req = format!(
|
||||
r#"{{"jsonrpc":"2.0","id":1,"method":"getSignatureStatus","params":["{}"]}}"#,
|
||||
r#"{{"jsonrpc":"2.0","id":1,"method":"getSignatureStatus","params":[["{}"]]}}"#,
|
||||
tx.signatures[0]
|
||||
);
|
||||
let res = io.handle_request_sync(&req, meta.clone());
|
||||
let expected_res: Option<String> = None;
|
||||
let expected = json!({
|
||||
"jsonrpc": "2.0",
|
||||
"result": expected_res,
|
||||
"id": 1
|
||||
});
|
||||
let expected: Response =
|
||||
serde_json::from_value(expected).expect("expected response deserialization");
|
||||
let result: Response = serde_json::from_str(&res.expect("actual response"))
|
||||
.expect("actual response deserialization");
|
||||
assert_eq!(expected, result);
|
||||
let json: Value = serde_json::from_str(&res.unwrap()).unwrap();
|
||||
let result: Vec<Option<RpcTransactionStatus>> =
|
||||
serde_json::from_value(json["result"].clone())
|
||||
.expect("actual response deserialization");
|
||||
assert!(result[0].is_none());
|
||||
|
||||
// Test getSignatureStatus request on a TransactionError
|
||||
let tx = system_transaction::transfer(&alice, &bob_pubkey, std::u64::MAX, blockhash);
|
||||
let req = format!(
|
||||
r#"{{"jsonrpc":"2.0","id":1,"method":"getSignatureStatus","params":["{}"]}}"#,
|
||||
tx.signatures[0]
|
||||
r#"{{"jsonrpc":"2.0","id":1,"method":"getSignatureStatus","params":[["{}"]]}}"#,
|
||||
confirmed_block_signatures[1]
|
||||
);
|
||||
let res = io.handle_request_sync(&req, meta);
|
||||
let expected_res: Option<transaction::Result<()>> = Some(Err(
|
||||
TransactionError::InstructionError(0, InstructionError::CustomError(1)),
|
||||
let res = io.handle_request_sync(&req, meta.clone());
|
||||
let expected_res: transaction::Result<()> = Err(TransactionError::InstructionError(
|
||||
0,
|
||||
InstructionError::CustomError(1),
|
||||
));
|
||||
let expected = json!({
|
||||
"jsonrpc": "2.0",
|
||||
"result": expected_res,
|
||||
"id": 1
|
||||
});
|
||||
let expected: Response =
|
||||
serde_json::from_value(expected).expect("expected response deserialization");
|
||||
let result: Response = serde_json::from_str(&res.expect("actual response"))
|
||||
.expect("actual response deserialization");
|
||||
assert_eq!(expected, result);
|
||||
let json: Value = serde_json::from_str(&res.unwrap()).unwrap();
|
||||
let result: Vec<Option<RpcTransactionStatus>> =
|
||||
serde_json::from_value(json["result"].clone())
|
||||
.expect("actual response deserialization");
|
||||
assert_eq!(expected_res, result[0].as_ref().unwrap().status);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -4,7 +4,7 @@ use crate::rpc_subscriptions::{Confirmations, RpcSubscriptions, SlotInfo};
|
||||
use jsonrpc_core::{Error, ErrorCode, Result};
|
||||
use jsonrpc_derive::rpc;
|
||||
use jsonrpc_pubsub::{typed::Subscriber, Session, SubscriptionId};
|
||||
use solana_client::rpc_response::{RpcAccount, RpcKeyedAccount};
|
||||
use solana_client::rpc_response::{Response as RpcResponse, RpcAccount, RpcKeyedAccount};
|
||||
use solana_sdk::{pubkey::Pubkey, signature::Signature, transaction};
|
||||
use std::sync::{atomic, Arc};
|
||||
|
||||
@ -26,7 +26,7 @@ pub trait RpcSolPubSub {
|
||||
fn account_subscribe(
|
||||
&self,
|
||||
meta: Self::Metadata,
|
||||
subscriber: Subscriber<RpcAccount>,
|
||||
subscriber: Subscriber<RpcResponse<RpcAccount>>,
|
||||
pubkey_str: String,
|
||||
confirmations: Option<Confirmations>,
|
||||
);
|
||||
@ -50,7 +50,7 @@ pub trait RpcSolPubSub {
|
||||
fn program_subscribe(
|
||||
&self,
|
||||
meta: Self::Metadata,
|
||||
subscriber: Subscriber<RpcKeyedAccount>,
|
||||
subscriber: Subscriber<RpcResponse<RpcKeyedAccount>>,
|
||||
pubkey_str: String,
|
||||
confirmations: Option<Confirmations>,
|
||||
);
|
||||
@ -74,7 +74,7 @@ pub trait RpcSolPubSub {
|
||||
fn signature_subscribe(
|
||||
&self,
|
||||
meta: Self::Metadata,
|
||||
subscriber: Subscriber<transaction::Result<()>>,
|
||||
subscriber: Subscriber<RpcResponse<transaction::Result<()>>>,
|
||||
signature_str: String,
|
||||
confirmations: Option<Confirmations>,
|
||||
);
|
||||
@ -133,7 +133,7 @@ impl RpcSolPubSub for RpcSolPubSubImpl {
|
||||
fn account_subscribe(
|
||||
&self,
|
||||
_meta: Self::Metadata,
|
||||
subscriber: Subscriber<RpcAccount>,
|
||||
subscriber: Subscriber<RpcResponse<RpcAccount>>,
|
||||
pubkey_str: String,
|
||||
confirmations: Option<Confirmations>,
|
||||
) {
|
||||
@ -171,7 +171,7 @@ impl RpcSolPubSub for RpcSolPubSubImpl {
|
||||
fn program_subscribe(
|
||||
&self,
|
||||
_meta: Self::Metadata,
|
||||
subscriber: Subscriber<RpcKeyedAccount>,
|
||||
subscriber: Subscriber<RpcResponse<RpcKeyedAccount>>,
|
||||
pubkey_str: String,
|
||||
confirmations: Option<Confirmations>,
|
||||
) {
|
||||
@ -209,7 +209,7 @@ impl RpcSolPubSub for RpcSolPubSubImpl {
|
||||
fn signature_subscribe(
|
||||
&self,
|
||||
_meta: Self::Metadata,
|
||||
subscriber: Subscriber<transaction::Result<()>>,
|
||||
subscriber: Subscriber<RpcResponse<transaction::Result<()>>>,
|
||||
signature_str: String,
|
||||
confirmations: Option<Confirmations>,
|
||||
) {
|
||||
@ -340,13 +340,18 @@ mod tests {
|
||||
// Test signature confirmation notification
|
||||
let response = robust_poll_or_panic(receiver);
|
||||
let expected_res: Option<transaction::Result<()>> = Some(Ok(()));
|
||||
let expected_res_str =
|
||||
serde_json::to_string(&serde_json::to_value(expected_res).unwrap()).unwrap();
|
||||
let expected = format!(
|
||||
r#"{{"jsonrpc":"2.0","method":"signatureNotification","params":{{"result":{},"subscription":0}}}}"#,
|
||||
expected_res_str
|
||||
);
|
||||
assert_eq!(expected, response);
|
||||
let expected = json!({
|
||||
"jsonrpc": "2.0",
|
||||
"method": "signatureNotification",
|
||||
"params": {
|
||||
"result": {
|
||||
"context": { "slot": 0 },
|
||||
"value": expected_res,
|
||||
},
|
||||
"subscription": 0,
|
||||
}
|
||||
});
|
||||
assert_eq!(serde_json::to_string(&expected).unwrap(), response);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -462,11 +467,14 @@ mod tests {
|
||||
"method": "accountNotification",
|
||||
"params": {
|
||||
"result": {
|
||||
"owner": budget_program_id.to_string(),
|
||||
"lamports": 51,
|
||||
"data": bs58::encode(expected_data).into_string(),
|
||||
"executable": false,
|
||||
"rentEpoch": 1,
|
||||
"context": { "slot": 0 },
|
||||
"value": {
|
||||
"owner": budget_program_id.to_string(),
|
||||
"lamports": 51,
|
||||
"data": bs58::encode(expected_data).into_string(),
|
||||
"executable": false,
|
||||
"rentEpoch": 1,
|
||||
},
|
||||
},
|
||||
"subscription": 0,
|
||||
}
|
||||
@ -609,11 +617,14 @@ mod tests {
|
||||
"method": "accountNotification",
|
||||
"params": {
|
||||
"result": {
|
||||
"owner": system_program::id().to_string(),
|
||||
"lamports": 100,
|
||||
"data": "",
|
||||
"executable": false,
|
||||
"rentEpoch": 1,
|
||||
"context": { "slot": 0 },
|
||||
"value": {
|
||||
"owner": system_program::id().to_string(),
|
||||
"lamports": 100,
|
||||
"data": "",
|
||||
"executable": false,
|
||||
"rentEpoch": 1,
|
||||
},
|
||||
},
|
||||
"subscription": 0,
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ use core::hash::Hash;
|
||||
use jsonrpc_core::futures::Future;
|
||||
use jsonrpc_pubsub::{typed::Sink, SubscriptionId};
|
||||
use serde::Serialize;
|
||||
use solana_client::rpc_response::{RpcAccount, RpcKeyedAccount};
|
||||
use solana_client::rpc_response::{Response, RpcAccount, RpcKeyedAccount, RpcResponseContext};
|
||||
use solana_ledger::bank_forks::BankForks;
|
||||
use solana_runtime::bank::Bank;
|
||||
use solana_sdk::{
|
||||
@ -50,11 +50,15 @@ impl std::fmt::Debug for NotificationEntry {
|
||||
}
|
||||
|
||||
type RpcAccountSubscriptions =
|
||||
RwLock<HashMap<Pubkey, HashMap<SubscriptionId, (Sink<RpcAccount>, Confirmations)>>>;
|
||||
type RpcProgramSubscriptions =
|
||||
RwLock<HashMap<Pubkey, HashMap<SubscriptionId, (Sink<RpcKeyedAccount>, Confirmations)>>>;
|
||||
RwLock<HashMap<Pubkey, HashMap<SubscriptionId, (Sink<Response<RpcAccount>>, Confirmations)>>>;
|
||||
type RpcProgramSubscriptions = RwLock<
|
||||
HashMap<Pubkey, HashMap<SubscriptionId, (Sink<Response<RpcKeyedAccount>>, Confirmations)>>,
|
||||
>;
|
||||
type RpcSignatureSubscriptions = RwLock<
|
||||
HashMap<Signature, HashMap<SubscriptionId, (Sink<transaction::Result<()>>, Confirmations)>>,
|
||||
HashMap<
|
||||
Signature,
|
||||
HashMap<SubscriptionId, (Sink<Response<transaction::Result<()>>>, Confirmations)>,
|
||||
>,
|
||||
>;
|
||||
type RpcSlotSubscriptions = RwLock<HashMap<SubscriptionId, Sink<SlotInfo>>>;
|
||||
|
||||
@ -105,8 +109,9 @@ where
|
||||
found
|
||||
}
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn check_confirmations_and_notify<K, S, B, F, X>(
|
||||
subscriptions: &HashMap<K, HashMap<SubscriptionId, (Sink<S>, Confirmations)>>,
|
||||
subscriptions: &HashMap<K, HashMap<SubscriptionId, (Sink<Response<S>>, Confirmations)>>,
|
||||
hashmap_key: &K,
|
||||
current_slot: Slot,
|
||||
bank_forks: &Arc<RwLock<BankForks>>,
|
||||
@ -131,7 +136,7 @@ where
|
||||
|
||||
let mut notified_set: HashSet<SubscriptionId> = HashSet::new();
|
||||
if let Some(hashmap) = subscriptions.get(hashmap_key) {
|
||||
for (bank_sub_id, (sink, confirmations)) in hashmap.iter() {
|
||||
for (sub_id, (sink, confirmations)) in hashmap.iter() {
|
||||
let desired_slot: Vec<u64> = current_ancestors
|
||||
.iter()
|
||||
.filter(|(_, &v)| v == *confirmations)
|
||||
@ -146,16 +151,18 @@ where
|
||||
.collect();
|
||||
let root = if root.len() == 1 { root[0] } else { 0 };
|
||||
if desired_slot.len() == 1 {
|
||||
let desired_bank = bank_forks
|
||||
.read()
|
||||
.unwrap()
|
||||
.get(desired_slot[0])
|
||||
.unwrap()
|
||||
.clone();
|
||||
let slot = desired_slot[0];
|
||||
let desired_bank = bank_forks.read().unwrap().get(slot).unwrap().clone();
|
||||
let results = bank_method(&desired_bank, hashmap_key);
|
||||
for result in filter_results(results, root) {
|
||||
notifier.notify(result, sink);
|
||||
notified_set.insert(bank_sub_id.clone());
|
||||
notifier.notify(
|
||||
Response {
|
||||
context: RpcResponseContext { slot },
|
||||
value: result,
|
||||
},
|
||||
sink,
|
||||
);
|
||||
notified_set.insert(sub_id.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -337,7 +344,7 @@ impl RpcSubscriptions {
|
||||
signature,
|
||||
current_slot,
|
||||
bank_forks,
|
||||
Bank::get_signature_status,
|
||||
Bank::get_signature_status_processed_since_parent,
|
||||
filter_signature_result,
|
||||
notifier,
|
||||
);
|
||||
@ -354,7 +361,7 @@ impl RpcSubscriptions {
|
||||
pubkey: &Pubkey,
|
||||
confirmations: Option<Confirmations>,
|
||||
sub_id: &SubscriptionId,
|
||||
sink: &Sink<RpcAccount>,
|
||||
sink: &Sink<Response<RpcAccount>>,
|
||||
) {
|
||||
let mut subscriptions = self.account_subscriptions.write().unwrap();
|
||||
add_subscription(&mut subscriptions, pubkey, confirmations, sub_id, sink);
|
||||
@ -370,7 +377,7 @@ impl RpcSubscriptions {
|
||||
program_id: &Pubkey,
|
||||
confirmations: Option<Confirmations>,
|
||||
sub_id: &SubscriptionId,
|
||||
sink: &Sink<RpcKeyedAccount>,
|
||||
sink: &Sink<Response<RpcKeyedAccount>>,
|
||||
) {
|
||||
let mut subscriptions = self.program_subscriptions.write().unwrap();
|
||||
add_subscription(&mut subscriptions, program_id, confirmations, sub_id, sink);
|
||||
@ -386,7 +393,7 @@ impl RpcSubscriptions {
|
||||
signature: &Signature,
|
||||
confirmations: Option<Confirmations>,
|
||||
sub_id: &SubscriptionId,
|
||||
sink: &Sink<transaction::Result<()>>,
|
||||
sink: &Sink<Response<transaction::Result<()>>>,
|
||||
) {
|
||||
let mut subscriptions = self.signature_subscriptions.write().unwrap();
|
||||
add_subscription(&mut subscriptions, signature, confirmations, sub_id, sink);
|
||||
@ -606,10 +613,24 @@ pub(crate) mod tests {
|
||||
|
||||
subscriptions.notify_subscribers(0, &bank_forks);
|
||||
let response = robust_poll_or_panic(transport_receiver);
|
||||
let expected = format!(
|
||||
r#"{{"jsonrpc":"2.0","method":"accountNotification","params":{{"result":{{"data":"1111111111111111","executable":false,"lamports":1,"owner":"Budget1111111111111111111111111111111111111","rentEpoch":1}},"subscription":0}}}}"#
|
||||
);
|
||||
assert_eq!(expected, response);
|
||||
let expected = json!({
|
||||
"jsonrpc": "2.0",
|
||||
"method": "accountNotification",
|
||||
"params": {
|
||||
"result": {
|
||||
"context": { "slot": 0 },
|
||||
"value": {
|
||||
"data": "1111111111111111",
|
||||
"executable": false,
|
||||
"lamports": 1,
|
||||
"owner": "Budget1111111111111111111111111111111111111",
|
||||
"rentEpoch": 1,
|
||||
},
|
||||
},
|
||||
"subscription": 0,
|
||||
}
|
||||
});
|
||||
assert_eq!(serde_json::to_string(&expected).unwrap(), response);
|
||||
|
||||
subscriptions.remove_account_subscription(&sub_id);
|
||||
assert!(!subscriptions
|
||||
@ -662,11 +683,27 @@ pub(crate) mod tests {
|
||||
|
||||
subscriptions.notify_subscribers(0, &bank_forks);
|
||||
let response = robust_poll_or_panic(transport_receiver);
|
||||
let expected = format!(
|
||||
r#"{{"jsonrpc":"2.0","method":"programNotification","params":{{"result":{{"account":{{"data":"1111111111111111","executable":false,"lamports":1,"owner":"Budget1111111111111111111111111111111111111","rentEpoch":1}},"pubkey":"{:?}"}},"subscription":0}}}}"#,
|
||||
alice.pubkey()
|
||||
);
|
||||
assert_eq!(expected, response);
|
||||
let expected = json!({
|
||||
"jsonrpc": "2.0",
|
||||
"method": "programNotification",
|
||||
"params": {
|
||||
"result": {
|
||||
"context": { "slot": 0 },
|
||||
"value": {
|
||||
"account": {
|
||||
"data": "1111111111111111",
|
||||
"executable": false,
|
||||
"lamports": 1,
|
||||
"owner": "Budget1111111111111111111111111111111111111",
|
||||
"rentEpoch": 1,
|
||||
},
|
||||
"pubkey": alice.pubkey().to_string(),
|
||||
},
|
||||
},
|
||||
"subscription": 0,
|
||||
}
|
||||
});
|
||||
assert_eq!(serde_json::to_string(&expected).unwrap(), response);
|
||||
|
||||
subscriptions.remove_program_subscription(&sub_id);
|
||||
assert!(!subscriptions
|
||||
@ -685,66 +722,122 @@ pub(crate) mod tests {
|
||||
} = create_genesis_config(100);
|
||||
let bank = Bank::new(&genesis_config);
|
||||
let blockhash = bank.last_blockhash();
|
||||
let bank_forks = Arc::new(RwLock::new(BankForks::new(0, bank)));
|
||||
let mut bank_forks = BankForks::new(0, bank);
|
||||
let alice = Keypair::new();
|
||||
let tx = system_transaction::transfer(&mint_keypair, &alice.pubkey(), 20, blockhash);
|
||||
let signature = tx.signatures[0];
|
||||
|
||||
let past_bank_tx =
|
||||
system_transaction::transfer(&mint_keypair, &alice.pubkey(), 1, blockhash);
|
||||
let unprocessed_tx =
|
||||
system_transaction::transfer(&mint_keypair, &alice.pubkey(), 10, blockhash);
|
||||
let not_ready_signature = unprocessed_tx.signatures[0];
|
||||
system_transaction::transfer(&mint_keypair, &alice.pubkey(), 2, blockhash);
|
||||
let processed_tx =
|
||||
system_transaction::transfer(&mint_keypair, &alice.pubkey(), 3, blockhash);
|
||||
|
||||
bank_forks
|
||||
.write()
|
||||
.unwrap()
|
||||
.get(0)
|
||||
.unwrap()
|
||||
.process_transaction(&tx)
|
||||
.process_transaction(&past_bank_tx)
|
||||
.unwrap();
|
||||
|
||||
let next_bank =
|
||||
Bank::new_from_parent(&bank_forks.banks[&0].clone(), &Pubkey::new_rand(), 1);
|
||||
bank_forks.insert(next_bank);
|
||||
|
||||
bank_forks
|
||||
.get(1)
|
||||
.unwrap()
|
||||
.process_transaction(&processed_tx)
|
||||
.unwrap();
|
||||
|
||||
let bank_forks = Arc::new(RwLock::new(bank_forks));
|
||||
|
||||
let (subscriber, _id_receiver, transport_receiver) =
|
||||
Subscriber::new_test("signatureNotification");
|
||||
let sub_id = SubscriptionId::Number(0 as u64);
|
||||
let remaining_sub_id = SubscriptionId::Number(1 as u64);
|
||||
let sink = subscriber.assign_id(sub_id.clone()).unwrap();
|
||||
let sink = subscriber.assign_id(SubscriptionId::Number(0)).unwrap();
|
||||
let exit = Arc::new(AtomicBool::new(false));
|
||||
let subscriptions = RpcSubscriptions::new(&exit);
|
||||
subscriptions.add_signature_subscription(&signature, None, &sub_id, &sink.clone());
|
||||
subscriptions.add_signature_subscription(
|
||||
¬_ready_signature,
|
||||
None,
|
||||
&remaining_sub_id,
|
||||
&past_bank_tx.signatures[0],
|
||||
Some(0),
|
||||
&SubscriptionId::Number(1 as u64),
|
||||
&sink.clone(),
|
||||
);
|
||||
subscriptions.add_signature_subscription(
|
||||
&processed_tx.signatures[0],
|
||||
Some(0),
|
||||
&SubscriptionId::Number(2 as u64),
|
||||
&sink.clone(),
|
||||
);
|
||||
subscriptions.add_signature_subscription(
|
||||
&unprocessed_tx.signatures[0],
|
||||
Some(0),
|
||||
&SubscriptionId::Number(3 as u64),
|
||||
&sink.clone(),
|
||||
);
|
||||
|
||||
{
|
||||
let sig_subs = subscriptions.signature_subscriptions.read().unwrap();
|
||||
assert!(sig_subs.contains_key(&signature));
|
||||
assert!(sig_subs.contains_key(¬_ready_signature));
|
||||
assert!(sig_subs.contains_key(&past_bank_tx.signatures[0]));
|
||||
assert!(sig_subs.contains_key(&unprocessed_tx.signatures[0]));
|
||||
assert!(sig_subs.contains_key(&processed_tx.signatures[0]));
|
||||
}
|
||||
|
||||
subscriptions.notify_subscribers(0, &bank_forks);
|
||||
subscriptions.notify_subscribers(1, &bank_forks);
|
||||
let response = robust_poll_or_panic(transport_receiver);
|
||||
let expected_res: Option<transaction::Result<()>> = Some(Ok(()));
|
||||
let expected_res_str =
|
||||
serde_json::to_string(&serde_json::to_value(expected_res).unwrap()).unwrap();
|
||||
let expected = format!(
|
||||
r#"{{"jsonrpc":"2.0","method":"signatureNotification","params":{{"result":{},"subscription":0}}}}"#,
|
||||
expected_res_str
|
||||
);
|
||||
assert_eq!(expected, response);
|
||||
let expected = json!({
|
||||
"jsonrpc": "2.0",
|
||||
"method": "signatureNotification",
|
||||
"params": {
|
||||
"result": {
|
||||
"context": { "slot": 1 },
|
||||
"value": expected_res,
|
||||
},
|
||||
"subscription": 0,
|
||||
}
|
||||
});
|
||||
assert_eq!(serde_json::to_string(&expected).unwrap(), response);
|
||||
|
||||
let sig_subs = subscriptions.signature_subscriptions.read().unwrap();
|
||||
|
||||
// Subscription should be automatically removed after notification
|
||||
assert!(!subscriptions
|
||||
.signature_subscriptions
|
||||
.read()
|
||||
.unwrap()
|
||||
.contains_key(&signature));
|
||||
assert!(!sig_subs.contains_key(&processed_tx.signatures[0]));
|
||||
|
||||
// Only one notification is expected for signature processed in previous bank
|
||||
assert_eq!(sig_subs.get(&past_bank_tx.signatures[0]).unwrap().len(), 1);
|
||||
|
||||
// Unprocessed signature subscription should not be removed
|
||||
assert!(subscriptions
|
||||
.signature_subscriptions
|
||||
.read()
|
||||
.unwrap()
|
||||
.contains_key(¬_ready_signature));
|
||||
assert_eq!(
|
||||
sig_subs.get(&unprocessed_tx.signatures[0]).unwrap().len(),
|
||||
1
|
||||
);
|
||||
|
||||
let (subscriber, _id_receiver, transport_receiver) =
|
||||
Subscriber::new_test("signatureNotification");
|
||||
let sink = subscriber.assign_id(SubscriptionId::Number(0)).unwrap();
|
||||
let exit = Arc::new(AtomicBool::new(false));
|
||||
let subscriptions = RpcSubscriptions::new(&exit);
|
||||
|
||||
subscriptions.add_signature_subscription(
|
||||
&past_bank_tx.signatures[0],
|
||||
Some(1),
|
||||
&SubscriptionId::Number(1 as u64),
|
||||
&sink.clone(),
|
||||
);
|
||||
subscriptions.notify_subscribers(1, &bank_forks);
|
||||
let response = robust_poll_or_panic(transport_receiver);
|
||||
let expected_res: Option<transaction::Result<()>> = Some(Ok(()));
|
||||
let expected = json!({
|
||||
"jsonrpc": "2.0",
|
||||
"method": "signatureNotification",
|
||||
"params": {
|
||||
"result": {
|
||||
"context": { "slot": 0 },
|
||||
"value": expected_res,
|
||||
},
|
||||
"subscription": 0,
|
||||
}
|
||||
});
|
||||
assert_eq!(serde_json::to_string(&expected).unwrap(), response);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -2,43 +2,128 @@
|
||||
|
||||
use crate::packet::{Packet, PacketsRecycler};
|
||||
use crate::streamer::{self, PacketReceiver, PacketSender};
|
||||
use bv::BitVec;
|
||||
use solana_ledger::bank_forks::BankForks;
|
||||
use solana_ledger::blockstore::MAX_DATA_SHREDS_PER_SLOT;
|
||||
use solana_ledger::shred::{OFFSET_OF_SHRED_INDEX, SIZE_OF_SHRED_INDEX};
|
||||
use solana_ledger::shred::{
|
||||
OFFSET_OF_SHRED_INDEX, OFFSET_OF_SHRED_SLOT, SIZE_OF_SHRED_INDEX, SIZE_OF_SHRED_SLOT,
|
||||
};
|
||||
use solana_perf::cuda_runtime::PinnedVec;
|
||||
use solana_perf::packet::limited_deserialize;
|
||||
use solana_perf::recycler::Recycler;
|
||||
use solana_sdk::clock::Slot;
|
||||
use std::collections::HashMap;
|
||||
use std::net::UdpSocket;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::sync::mpsc::channel;
|
||||
use std::sync::Arc;
|
||||
use std::sync::RwLock;
|
||||
use std::thread::{self, Builder, JoinHandle};
|
||||
use std::time::Instant;
|
||||
|
||||
pub type ShredsReceived = HashMap<Slot, BitVec<u64>>;
|
||||
|
||||
pub struct ShredFetchStage {
|
||||
thread_hdls: Vec<JoinHandle<()>>,
|
||||
}
|
||||
|
||||
impl ShredFetchStage {
|
||||
// updates packets received on a channel and sends them on another channel
|
||||
fn modify_packets<F>(recvr: PacketReceiver, sendr: PacketSender, modify: F)
|
||||
where
|
||||
F: Fn(&mut Packet),
|
||||
{
|
||||
while let Some(mut p) = recvr.iter().next() {
|
||||
let index_start = OFFSET_OF_SHRED_INDEX;
|
||||
let index_end = index_start + SIZE_OF_SHRED_INDEX;
|
||||
p.packets.iter_mut().for_each(|p| {
|
||||
p.meta.discard = true;
|
||||
if index_end <= p.meta.size {
|
||||
if let Ok(index) = limited_deserialize::<u32>(&p.data[index_start..index_end]) {
|
||||
if index < MAX_DATA_SHREDS_PER_SLOT as u32 {
|
||||
p.meta.discard = false;
|
||||
modify(p);
|
||||
} else {
|
||||
inc_new_counter_warn!("shred_fetch_stage-shred_index_overrun", 1);
|
||||
}
|
||||
fn get_slot_index(p: &Packet, index_overrun: &mut usize) -> Option<(u64, u32)> {
|
||||
let index_start = OFFSET_OF_SHRED_INDEX;
|
||||
let index_end = index_start + SIZE_OF_SHRED_INDEX;
|
||||
let slot_start = OFFSET_OF_SHRED_SLOT;
|
||||
let slot_end = slot_start + SIZE_OF_SHRED_SLOT;
|
||||
|
||||
if index_end <= p.meta.size {
|
||||
if let Ok(index) = limited_deserialize::<u32>(&p.data[index_start..index_end]) {
|
||||
if index < MAX_DATA_SHREDS_PER_SLOT as u32 && slot_end <= p.meta.size {
|
||||
if let Ok(slot) = limited_deserialize::<Slot>(&p.data[slot_start..slot_end]) {
|
||||
return Some((slot, index));
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
*index_overrun += 1;
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn process_packet<F>(
|
||||
p: &mut Packet,
|
||||
shreds_received: &mut ShredsReceived,
|
||||
index_overrun: &mut usize,
|
||||
last_root: Slot,
|
||||
last_slot: Slot,
|
||||
slots_per_epoch: u64,
|
||||
modify: &F,
|
||||
) where
|
||||
F: Fn(&mut Packet),
|
||||
{
|
||||
p.meta.discard = true;
|
||||
if let Some((slot, index)) = Self::get_slot_index(p, index_overrun) {
|
||||
// Seems reasonable to limit shreds to 2 epochs away
|
||||
if slot > last_root && slot < (last_slot + 2 * slots_per_epoch) {
|
||||
// Shred filter
|
||||
let slot_received = shreds_received
|
||||
.entry(slot)
|
||||
.or_insert_with(|| BitVec::new_fill(false, MAX_DATA_SHREDS_PER_SLOT as u64));
|
||||
if !slot_received.get(index.into()) {
|
||||
p.meta.discard = false;
|
||||
modify(p);
|
||||
slot_received.set(index.into(), true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// updates packets received on a channel and sends them on another channel
|
||||
fn modify_packets<F>(
|
||||
recvr: PacketReceiver,
|
||||
sendr: PacketSender,
|
||||
bank_forks: Option<Arc<RwLock<BankForks>>>,
|
||||
modify: F,
|
||||
) where
|
||||
F: Fn(&mut Packet),
|
||||
{
|
||||
let mut shreds_received = ShredsReceived::default();
|
||||
let mut last_cleared = Instant::now();
|
||||
|
||||
// In the case of bank_forks=None, setup to accept any slot range
|
||||
let mut last_root = 0;
|
||||
let mut last_slot = std::u64::MAX;
|
||||
let mut slots_per_epoch = 0;
|
||||
|
||||
while let Some(mut p) = recvr.iter().next() {
|
||||
if last_cleared.elapsed().as_millis() > 200 {
|
||||
shreds_received.clear();
|
||||
last_cleared = Instant::now();
|
||||
if let Some(bank_forks) = bank_forks.as_ref() {
|
||||
let bank_forks_r = bank_forks.read().unwrap();
|
||||
last_root = bank_forks_r.root();
|
||||
let working_bank = bank_forks_r.working_bank();
|
||||
last_slot = working_bank.slot();
|
||||
let root_bank = bank_forks_r
|
||||
.get(bank_forks_r.root())
|
||||
.expect("Root bank should exist");
|
||||
slots_per_epoch = root_bank.get_slots_in_epoch(root_bank.epoch());
|
||||
}
|
||||
}
|
||||
let mut index_overrun = 0;
|
||||
let mut shred_count = 0;
|
||||
p.packets.iter_mut().for_each(|mut packet| {
|
||||
shred_count += 1;
|
||||
Self::process_packet(
|
||||
&mut packet,
|
||||
&mut shreds_received,
|
||||
&mut index_overrun,
|
||||
last_root,
|
||||
last_slot,
|
||||
slots_per_epoch,
|
||||
&modify,
|
||||
);
|
||||
});
|
||||
inc_new_counter_warn!("shred_fetch_stage-shred_index_overrun", index_overrun);
|
||||
inc_new_counter_info!("shred_fetch_stage-shred_count", shred_count);
|
||||
if sendr.send(p).is_err() {
|
||||
break;
|
||||
}
|
||||
@ -50,6 +135,7 @@ impl ShredFetchStage {
|
||||
exit: &Arc<AtomicBool>,
|
||||
sender: PacketSender,
|
||||
recycler: Recycler<PinnedVec<Packet>>,
|
||||
bank_forks: Option<Arc<RwLock<BankForks>>>,
|
||||
modify: F,
|
||||
) -> (Vec<JoinHandle<()>>, JoinHandle<()>)
|
||||
where
|
||||
@ -71,7 +157,7 @@ impl ShredFetchStage {
|
||||
|
||||
let modifier_hdl = Builder::new()
|
||||
.name("solana-tvu-fetch-stage-packet-modifier".to_string())
|
||||
.spawn(|| Self::modify_packets(packet_receiver, sender, modify))
|
||||
.spawn(move || Self::modify_packets(packet_receiver, sender, bank_forks, modify))
|
||||
.unwrap();
|
||||
(streamers, modifier_hdl)
|
||||
}
|
||||
@ -81,6 +167,7 @@ impl ShredFetchStage {
|
||||
forward_sockets: Vec<Arc<UdpSocket>>,
|
||||
repair_socket: Arc<UdpSocket>,
|
||||
sender: &PacketSender,
|
||||
bank_forks: Option<Arc<RwLock<BankForks>>>,
|
||||
exit: &Arc<AtomicBool>,
|
||||
) -> Self {
|
||||
let recycler: PacketsRecycler = Recycler::warmed(100, 1024);
|
||||
@ -100,6 +187,7 @@ impl ShredFetchStage {
|
||||
&exit,
|
||||
sender.clone(),
|
||||
recycler.clone(),
|
||||
bank_forks.clone(),
|
||||
|p| p.meta.forward = true,
|
||||
);
|
||||
|
||||
@ -108,6 +196,7 @@ impl ShredFetchStage {
|
||||
&exit,
|
||||
sender.clone(),
|
||||
recycler.clone(),
|
||||
bank_forks,
|
||||
|p| p.meta.repair = true,
|
||||
);
|
||||
|
||||
@ -128,3 +217,111 @@ impl ShredFetchStage {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use solana_ledger::shred::Shred;
|
||||
|
||||
#[test]
|
||||
fn test_shred_filter() {
|
||||
solana_logger::setup();
|
||||
let mut shreds_received = ShredsReceived::default();
|
||||
let mut packet = Packet::default();
|
||||
let mut index_overrun = 0;
|
||||
let last_root = 0;
|
||||
let last_slot = 100;
|
||||
let slots_per_epoch = 10;
|
||||
// packet size is 0, so cannot get index
|
||||
ShredFetchStage::process_packet(
|
||||
&mut packet,
|
||||
&mut shreds_received,
|
||||
&mut index_overrun,
|
||||
last_root,
|
||||
last_slot,
|
||||
slots_per_epoch,
|
||||
&|_p| {},
|
||||
);
|
||||
assert_eq!(index_overrun, 1);
|
||||
assert!(packet.meta.discard);
|
||||
let shred = Shred::new_from_data(1, 3, 0, None, true, true, 0, 0, 0);
|
||||
shred.copy_to_packet(&mut packet);
|
||||
|
||||
// rejected slot is 1, root is 3
|
||||
ShredFetchStage::process_packet(
|
||||
&mut packet,
|
||||
&mut shreds_received,
|
||||
&mut index_overrun,
|
||||
3,
|
||||
last_slot,
|
||||
slots_per_epoch,
|
||||
&|_p| {},
|
||||
);
|
||||
assert!(packet.meta.discard);
|
||||
|
||||
// Accepted for 1,3
|
||||
ShredFetchStage::process_packet(
|
||||
&mut packet,
|
||||
&mut shreds_received,
|
||||
&mut index_overrun,
|
||||
last_root,
|
||||
last_slot,
|
||||
slots_per_epoch,
|
||||
&|_p| {},
|
||||
);
|
||||
assert!(!packet.meta.discard);
|
||||
|
||||
// shreds_received should filter duplicate
|
||||
ShredFetchStage::process_packet(
|
||||
&mut packet,
|
||||
&mut shreds_received,
|
||||
&mut index_overrun,
|
||||
last_root,
|
||||
last_slot,
|
||||
slots_per_epoch,
|
||||
&|_p| {},
|
||||
);
|
||||
assert!(packet.meta.discard);
|
||||
|
||||
let shred = Shred::new_from_data(1_000_000, 3, 0, None, true, true, 0, 0, 0);
|
||||
shred.copy_to_packet(&mut packet);
|
||||
|
||||
// Slot 1 million is too high
|
||||
ShredFetchStage::process_packet(
|
||||
&mut packet,
|
||||
&mut shreds_received,
|
||||
&mut index_overrun,
|
||||
last_root,
|
||||
last_slot,
|
||||
slots_per_epoch,
|
||||
&|_p| {},
|
||||
);
|
||||
assert!(packet.meta.discard);
|
||||
|
||||
let index = MAX_DATA_SHREDS_PER_SLOT as u32;
|
||||
let shred = Shred::new_from_data(5, index, 0, None, true, true, 0, 0, 0);
|
||||
shred.copy_to_packet(&mut packet);
|
||||
ShredFetchStage::process_packet(
|
||||
&mut packet,
|
||||
&mut shreds_received,
|
||||
&mut index_overrun,
|
||||
last_root,
|
||||
last_slot,
|
||||
slots_per_epoch,
|
||||
&|_p| {},
|
||||
);
|
||||
assert!(packet.meta.discard);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_shred_offsets() {
|
||||
let shred = Shred::new_from_data(1, 3, 0, None, true, true, 0, 0, 0);
|
||||
let mut packet = Packet::default();
|
||||
shred.copy_to_packet(&mut packet);
|
||||
let mut index_overrun = 0;
|
||||
assert_eq!(
|
||||
Some((1, 3)),
|
||||
ShredFetchStage::get_slot_index(&packet, &mut index_overrun)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -11,7 +11,9 @@ use rand::{Rng, SeedableRng};
|
||||
use rand_chacha::ChaChaRng;
|
||||
use solana_chacha_cuda::chacha_cuda::chacha_cbc_encrypt_file_many_keys;
|
||||
use solana_ledger::{bank_forks::BankForks, blockstore::Blockstore};
|
||||
use solana_runtime::{bank::Bank, storage_utils::archiver_accounts};
|
||||
use solana_runtime::{
|
||||
bank::Bank, status_cache::SignatureConfirmationStatus, storage_utils::archiver_accounts,
|
||||
};
|
||||
use solana_sdk::{
|
||||
account::Account,
|
||||
account_utils::StateMut,
|
||||
@ -343,8 +345,13 @@ impl StorageStage {
|
||||
.unwrap()
|
||||
.working_bank()
|
||||
.get_signature_confirmation_status(signature);
|
||||
if let Some((confirmations, res)) = response {
|
||||
if res.is_ok() {
|
||||
if let Some(SignatureConfirmationStatus {
|
||||
confirmations,
|
||||
status,
|
||||
..
|
||||
}) = response
|
||||
{
|
||||
if status.is_ok() {
|
||||
if confirmed_blocks != confirmations {
|
||||
now = Instant::now();
|
||||
confirmed_blocks = confirmations;
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crossbeam_channel::{Receiver, RecvTimeoutError};
|
||||
use solana_client::rpc_response::RpcTransactionStatus;
|
||||
use solana_client::rpc_response::RpcTransactionStatusMeta;
|
||||
use solana_ledger::{blockstore::Blockstore, blockstore_processor::TransactionStatusBatch};
|
||||
use solana_runtime::{
|
||||
bank::{Bank, HashAgeKind},
|
||||
@ -73,7 +73,7 @@ impl TransactionStatusService {
|
||||
blockstore
|
||||
.write_transaction_status(
|
||||
(slot, transaction.signatures[0]),
|
||||
&RpcTransactionStatus {
|
||||
&RpcTransactionStatusMeta {
|
||||
status,
|
||||
fee,
|
||||
pre_balances,
|
||||
|
@ -2,6 +2,7 @@
|
||||
//! validation pipeline in software.
|
||||
|
||||
use crate::{
|
||||
accounts_hash_verifier::AccountsHashVerifier,
|
||||
blockstream_service::BlockstreamService,
|
||||
cluster_info::ClusterInfo,
|
||||
commitment::BlockCommitmentCache,
|
||||
@ -28,6 +29,7 @@ use solana_sdk::{
|
||||
pubkey::Pubkey,
|
||||
signature::{Keypair, Signer},
|
||||
};
|
||||
use std::collections::HashSet;
|
||||
use std::{
|
||||
net::UdpSocket,
|
||||
path::PathBuf,
|
||||
@ -47,6 +49,7 @@ pub struct Tvu {
|
||||
blockstream_service: Option<BlockstreamService>,
|
||||
ledger_cleanup_service: Option<LedgerCleanupService>,
|
||||
storage_stage: StorageStage,
|
||||
accounts_hash_verifier: AccountsHashVerifier,
|
||||
}
|
||||
|
||||
pub struct Sockets {
|
||||
@ -56,6 +59,16 @@ pub struct Sockets {
|
||||
pub forwards: Vec<UdpSocket>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct TvuConfig {
|
||||
pub max_ledger_slots: Option<u64>,
|
||||
pub sigverify_disabled: bool,
|
||||
pub shred_version: u16,
|
||||
pub halt_on_trusted_validators_accounts_hash_mismatch: bool,
|
||||
pub trusted_validators: Option<HashSet<Pubkey>>,
|
||||
pub accounts_hash_fault_injection_slots: u64,
|
||||
}
|
||||
|
||||
impl Tvu {
|
||||
/// This service receives messages from a leader in the network and processes the transactions
|
||||
/// on the bank state.
|
||||
@ -74,7 +87,6 @@ impl Tvu {
|
||||
blockstore: Arc<Blockstore>,
|
||||
storage_state: &StorageState,
|
||||
blockstream_unix_socket: Option<&PathBuf>,
|
||||
max_ledger_slots: Option<u64>,
|
||||
ledger_signal_receiver: Receiver<bool>,
|
||||
subscriptions: &Arc<RpcSubscriptions>,
|
||||
poh_recorder: &Arc<Mutex<PohRecorder>>,
|
||||
@ -82,12 +94,11 @@ impl Tvu {
|
||||
exit: &Arc<AtomicBool>,
|
||||
completed_slots_receiver: CompletedSlotsReceiver,
|
||||
block_commitment_cache: Arc<RwLock<BlockCommitmentCache>>,
|
||||
sigverify_disabled: bool,
|
||||
cfg: Option<Arc<AtomicBool>>,
|
||||
shred_version: u16,
|
||||
transaction_status_sender: Option<TransactionStatusSender>,
|
||||
rewards_recorder_sender: Option<RewardsRecorderSender>,
|
||||
snapshot_package_sender: Option<SnapshotPackageSender>,
|
||||
tvu_config: TvuConfig,
|
||||
) -> Self {
|
||||
let keypair: Arc<Keypair> = cluster_info
|
||||
.read()
|
||||
@ -113,11 +124,12 @@ impl Tvu {
|
||||
forward_sockets,
|
||||
repair_socket.clone(),
|
||||
&fetch_sender,
|
||||
Some(bank_forks.clone()),
|
||||
&exit,
|
||||
);
|
||||
|
||||
let (verified_sender, verified_receiver) = unbounded();
|
||||
let sigverify_stage = if !sigverify_disabled {
|
||||
let sigverify_stage = if !tvu_config.sigverify_disabled {
|
||||
SigVerifyStage::new(
|
||||
fetch_receiver,
|
||||
verified_sender,
|
||||
@ -143,12 +155,23 @@ impl Tvu {
|
||||
completed_slots_receiver,
|
||||
*bank_forks.read().unwrap().working_bank().epoch_schedule(),
|
||||
cfg,
|
||||
shred_version,
|
||||
tvu_config.shred_version,
|
||||
);
|
||||
|
||||
let (blockstream_slot_sender, blockstream_slot_receiver) = channel();
|
||||
let (ledger_cleanup_slot_sender, ledger_cleanup_slot_receiver) = channel();
|
||||
|
||||
let (accounts_hash_sender, accounts_hash_receiver) = channel();
|
||||
let accounts_hash_verifier = AccountsHashVerifier::new(
|
||||
accounts_hash_receiver,
|
||||
snapshot_package_sender,
|
||||
exit,
|
||||
cluster_info,
|
||||
tvu_config.trusted_validators.clone(),
|
||||
tvu_config.halt_on_trusted_validators_accounts_hash_mismatch,
|
||||
tvu_config.accounts_hash_fault_injection_slots,
|
||||
);
|
||||
|
||||
let replay_stage_config = ReplayStageConfig {
|
||||
my_pubkey: keypair.pubkey(),
|
||||
vote_account: *vote_account,
|
||||
@ -158,7 +181,7 @@ impl Tvu {
|
||||
leader_schedule_cache: leader_schedule_cache.clone(),
|
||||
slot_full_senders: vec![blockstream_slot_sender],
|
||||
latest_root_senders: vec![ledger_cleanup_slot_sender],
|
||||
snapshot_package_sender,
|
||||
accounts_hash_sender: Some(accounts_hash_sender),
|
||||
block_commitment_cache,
|
||||
transaction_status_sender,
|
||||
rewards_recorder_sender,
|
||||
@ -185,7 +208,7 @@ impl Tvu {
|
||||
None
|
||||
};
|
||||
|
||||
let ledger_cleanup_service = max_ledger_slots.map(|max_ledger_slots| {
|
||||
let ledger_cleanup_service = tvu_config.max_ledger_slots.map(|max_ledger_slots| {
|
||||
LedgerCleanupService::new(
|
||||
ledger_cleanup_slot_receiver,
|
||||
blockstore.clone(),
|
||||
@ -213,6 +236,7 @@ impl Tvu {
|
||||
blockstream_service,
|
||||
ledger_cleanup_service,
|
||||
storage_stage,
|
||||
accounts_hash_verifier,
|
||||
}
|
||||
}
|
||||
|
||||
@ -228,6 +252,7 @@ impl Tvu {
|
||||
self.ledger_cleanup_service.unwrap().join()?;
|
||||
}
|
||||
self.replay_stage.join()?;
|
||||
self.accounts_hash_verifier.join()?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@ -288,7 +313,6 @@ pub mod tests {
|
||||
blockstore,
|
||||
&StorageState::default(),
|
||||
None,
|
||||
None,
|
||||
l_receiver,
|
||||
&Arc::new(RpcSubscriptions::new(&exit)),
|
||||
&poh_recorder,
|
||||
@ -296,12 +320,11 @@ pub mod tests {
|
||||
&exit,
|
||||
completed_slots_receiver,
|
||||
block_commitment_cache,
|
||||
false,
|
||||
None,
|
||||
0,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
TvuConfig::default(),
|
||||
);
|
||||
exit.store(true, Ordering::Relaxed);
|
||||
tvu.join().unwrap();
|
||||
|
@ -20,7 +20,7 @@ use crate::{
|
||||
storage_stage::StorageState,
|
||||
tpu::Tpu,
|
||||
transaction_status_service::TransactionStatusService,
|
||||
tvu::{Sockets, Tvu},
|
||||
tvu::{Sockets, Tvu, TvuConfig},
|
||||
};
|
||||
use crossbeam_channel::unbounded;
|
||||
use solana_ledger::{
|
||||
@ -76,6 +76,10 @@ pub struct ValidatorConfig {
|
||||
pub wait_for_supermajority: Option<Slot>,
|
||||
pub new_hard_forks: Option<Vec<Slot>>,
|
||||
pub trusted_validators: Option<HashSet<Pubkey>>, // None = trust all
|
||||
pub halt_on_trusted_validators_accounts_hash_mismatch: bool,
|
||||
pub accounts_hash_fault_injection_slots: u64, // 0 = no fault injection
|
||||
pub frozen_accounts: Vec<Pubkey>,
|
||||
pub no_rocksdb_compaction: bool,
|
||||
}
|
||||
|
||||
impl Default for ValidatorConfig {
|
||||
@ -99,6 +103,10 @@ impl Default for ValidatorConfig {
|
||||
wait_for_supermajority: None,
|
||||
new_hard_forks: None,
|
||||
trusted_validators: None,
|
||||
halt_on_trusted_validators_accounts_hash_mismatch: false,
|
||||
accounts_hash_fault_injection_slots: 0,
|
||||
frozen_accounts: vec![],
|
||||
no_rocksdb_compaction: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -143,7 +151,7 @@ impl Validator {
|
||||
keypair: &Arc<Keypair>,
|
||||
ledger_path: &Path,
|
||||
vote_account: &Pubkey,
|
||||
voting_keypair: &Arc<Keypair>,
|
||||
authorized_voter: &Arc<Keypair>,
|
||||
storage_keypair: &Arc<Keypair>,
|
||||
entrypoint_info_option: Option<&ContactInfo>,
|
||||
poh_verify: bool,
|
||||
@ -152,8 +160,9 @@ impl Validator {
|
||||
let id = keypair.pubkey();
|
||||
assert_eq!(id, node.info.id);
|
||||
|
||||
warn!("identity pubkey: {:?}", id);
|
||||
warn!("vote pubkey: {:?}", vote_account);
|
||||
warn!("identity: {}", id);
|
||||
warn!("vote account: {}", vote_account);
|
||||
warn!("authorized voter: {}", authorized_voter.pubkey());
|
||||
report_target_features();
|
||||
|
||||
info!("entrypoint: {:?}", entrypoint_info_option);
|
||||
@ -263,7 +272,7 @@ impl Validator {
|
||||
});
|
||||
|
||||
let (transaction_status_sender, transaction_status_service) =
|
||||
if rpc_service.is_some() && config.rpc_config.enable_get_confirmed_block {
|
||||
if rpc_service.is_some() && config.rpc_config.enable_rpc_transaction_history {
|
||||
let (transaction_status_sender, transaction_status_receiver) = unbounded();
|
||||
(
|
||||
Some(transaction_status_sender),
|
||||
@ -278,7 +287,7 @@ impl Validator {
|
||||
};
|
||||
|
||||
let (rewards_recorder_sender, rewards_recorder_service) =
|
||||
if rpc_service.is_some() && config.rpc_config.enable_get_confirmed_block {
|
||||
if rpc_service.is_some() && config.rpc_config.enable_rpc_transaction_history {
|
||||
let (rewards_recorder_sender, rewards_receiver) = unbounded();
|
||||
(
|
||||
Some(rewards_recorder_sender),
|
||||
@ -370,12 +379,6 @@ impl Validator {
|
||||
|
||||
wait_for_supermajority(config, &bank, &cluster_info);
|
||||
|
||||
let voting_keypair = if config.voting_disabled {
|
||||
None
|
||||
} else {
|
||||
Some(voting_keypair.clone())
|
||||
};
|
||||
|
||||
let poh_service = PohService::new(poh_recorder.clone(), &poh_config, &exit);
|
||||
assert_eq!(
|
||||
blockstore.new_shreds_signals.len(),
|
||||
@ -385,7 +388,11 @@ impl Validator {
|
||||
|
||||
let tvu = Tvu::new(
|
||||
vote_account,
|
||||
voting_keypair,
|
||||
if config.voting_disabled {
|
||||
None
|
||||
} else {
|
||||
Some(authorized_voter.clone())
|
||||
},
|
||||
storage_keypair,
|
||||
&bank_forks,
|
||||
&cluster_info,
|
||||
@ -417,7 +424,6 @@ impl Validator {
|
||||
blockstore.clone(),
|
||||
&storage_state,
|
||||
config.blockstream_unix_socket.as_ref(),
|
||||
config.max_ledger_slots,
|
||||
ledger_signal_receiver,
|
||||
&subscriptions,
|
||||
&poh_recorder,
|
||||
@ -425,12 +431,19 @@ impl Validator {
|
||||
&exit,
|
||||
completed_slots_receiver,
|
||||
block_commitment_cache,
|
||||
config.dev_sigverify_disabled,
|
||||
config.enable_partition.clone(),
|
||||
node.info.shred_version,
|
||||
transaction_status_sender.clone(),
|
||||
rewards_recorder_sender,
|
||||
snapshot_package_sender,
|
||||
TvuConfig {
|
||||
max_ledger_slots: config.max_ledger_slots,
|
||||
sigverify_disabled: config.dev_sigverify_disabled,
|
||||
halt_on_trusted_validators_accounts_hash_mismatch: config
|
||||
.halt_on_trusted_validators_accounts_hash_mismatch,
|
||||
shred_version: node.info.shred_version,
|
||||
trusted_validators: config.trusted_validators.clone(),
|
||||
accounts_hash_fault_injection_slots: config.accounts_hash_fault_injection_slots,
|
||||
},
|
||||
);
|
||||
|
||||
if config.dev_sigverify_disabled {
|
||||
@ -578,13 +591,15 @@ fn new_banks_from_blockstore(
|
||||
}
|
||||
}
|
||||
|
||||
let (blockstore, ledger_signal_receiver, completed_slots_receiver) =
|
||||
let (mut blockstore, ledger_signal_receiver, completed_slots_receiver) =
|
||||
Blockstore::open_with_signal(blockstore_path).expect("Failed to open ledger database");
|
||||
blockstore.set_no_compaction(config.no_rocksdb_compaction);
|
||||
|
||||
let process_options = blockstore_processor::ProcessOptions {
|
||||
poh_verify,
|
||||
dev_halt_at_slot: config.dev_halt_at_slot,
|
||||
new_hard_forks: config.new_hard_forks.clone(),
|
||||
frozen_accounts: config.frozen_accounts.clone(),
|
||||
..blockstore_processor::ProcessOptions::default()
|
||||
};
|
||||
|
||||
|
@ -47,6 +47,7 @@ mod tests {
|
||||
let bank0 = Bank::new_with_paths(
|
||||
&genesis_config_info.genesis_config,
|
||||
vec![accounts_dir.path().to_path_buf()],
|
||||
&[],
|
||||
);
|
||||
bank0.freeze();
|
||||
let mut bank_forks = BankForks::new(0, bank0);
|
||||
@ -82,6 +83,7 @@ mod tests {
|
||||
|
||||
let deserialized_bank = snapshot_utils::bank_from_archive(
|
||||
&account_paths,
|
||||
&[],
|
||||
&old_bank_forks
|
||||
.snapshot_config
|
||||
.as_ref()
|
||||
|
@ -15,7 +15,7 @@ use std::time::Instant;
|
||||
type Nodes = HashMap<Pubkey, (bool, HashSet<i32>, Receiver<(i32, bool)>)>;
|
||||
|
||||
fn num_threads() -> usize {
|
||||
sys_info::cpu_num().unwrap_or(10) as usize
|
||||
num_cpus::get()
|
||||
}
|
||||
|
||||
/// Search for the a node with the given balance
|
||||
|
@ -7,9 +7,18 @@ use jsonrpc_core_client::transports::ws;
|
||||
use log::*;
|
||||
use reqwest::{self, header::CONTENT_TYPE};
|
||||
use serde_json::{json, Value};
|
||||
use solana_client::rpc_client::get_rpc_request_str;
|
||||
use solana_client::{
|
||||
rpc_client::{get_rpc_request_str, RpcClient},
|
||||
rpc_response::Response,
|
||||
};
|
||||
use solana_core::{rpc_pubsub::gen_client::Client as PubsubClient, validator::TestValidator};
|
||||
use solana_sdk::{hash::Hash, pubkey::Pubkey, system_transaction, transaction};
|
||||
use solana_sdk::{
|
||||
commitment_config::CommitmentConfig,
|
||||
hash::Hash,
|
||||
pubkey::Pubkey,
|
||||
system_transaction,
|
||||
transaction::{self, Transaction},
|
||||
};
|
||||
use std::{
|
||||
collections::HashSet,
|
||||
fs::remove_dir_all,
|
||||
@ -17,8 +26,7 @@ use std::{
|
||||
sync::mpsc::channel,
|
||||
sync::{Arc, Mutex},
|
||||
thread::sleep,
|
||||
time::Duration,
|
||||
time::SystemTime,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
use tokio::runtime::Runtime;
|
||||
|
||||
@ -204,35 +212,43 @@ fn test_rpc_subscriptions() {
|
||||
|
||||
// Create transaction signatures to subscribe to
|
||||
let transactions_socket = UdpSocket::bind("0.0.0.0:0").unwrap();
|
||||
let mut signature_set: HashSet<String> = (0..1000)
|
||||
.map(|_| {
|
||||
let tx = system_transaction::transfer(&alice, &Pubkey::new_rand(), 1, genesis_hash);
|
||||
transactions_socket
|
||||
.send_to(&bincode::serialize(&tx).unwrap(), leader_data.tpu)
|
||||
.unwrap();
|
||||
tx.signatures[0].to_string()
|
||||
})
|
||||
let transactions: Vec<Transaction> = (0..500)
|
||||
.map(|_| system_transaction::transfer(&alice, &Pubkey::new_rand(), 1, genesis_hash))
|
||||
.collect();
|
||||
let mut signature_set: HashSet<String> = transactions
|
||||
.iter()
|
||||
.map(|tx| tx.signatures[0].to_string())
|
||||
.collect();
|
||||
|
||||
// Create the pub sub runtime
|
||||
let mut rt = Runtime::new().unwrap();
|
||||
let rpc_pubsub_url = format!("ws://{}/", leader_data.rpc_pubsub);
|
||||
let (sender, receiver) = channel::<(String, transaction::Result<()>)>();
|
||||
let sender = Arc::new(Mutex::new(sender));
|
||||
|
||||
let (status_sender, status_receiver) = channel::<(String, Response<transaction::Result<()>>)>();
|
||||
let status_sender = Arc::new(Mutex::new(status_sender));
|
||||
let (sent_sender, sent_receiver) = channel::<()>();
|
||||
let sent_sender = Arc::new(Mutex::new(sent_sender));
|
||||
|
||||
// Subscribe to all signatures
|
||||
rt.spawn({
|
||||
let connect = ws::try_connect::<PubsubClient>(&rpc_pubsub_url).unwrap();
|
||||
let signature_set = signature_set.clone();
|
||||
connect
|
||||
.and_then(move |client| {
|
||||
for sig in signature_set {
|
||||
let sender = sender.clone();
|
||||
let status_sender = status_sender.clone();
|
||||
let sent_sender = sent_sender.clone();
|
||||
tokio::spawn(
|
||||
client
|
||||
.signature_subscribe(sig.clone(), None)
|
||||
.and_then(move |sig_stream| {
|
||||
sent_sender.lock().unwrap().send(()).unwrap();
|
||||
sig_stream.for_each(move |result| {
|
||||
sender.lock().unwrap().send((sig.clone(), result)).unwrap();
|
||||
status_sender
|
||||
.lock()
|
||||
.unwrap()
|
||||
.send((sig.clone(), result))
|
||||
.unwrap();
|
||||
future::ok(())
|
||||
})
|
||||
})
|
||||
@ -246,18 +262,49 @@ fn test_rpc_subscriptions() {
|
||||
.map_err(|_| ())
|
||||
});
|
||||
|
||||
// Wait for signature subscriptions
|
||||
let deadline = Instant::now() + Duration::from_secs(2);
|
||||
(0..transactions.len()).for_each(|_| {
|
||||
sent_receiver
|
||||
.recv_timeout(deadline.saturating_duration_since(Instant::now()))
|
||||
.unwrap();
|
||||
});
|
||||
|
||||
let rpc_client = RpcClient::new_socket(leader_data.rpc);
|
||||
let transaction_count = rpc_client
|
||||
.get_transaction_count_with_commitment(CommitmentConfig::recent())
|
||||
.unwrap();
|
||||
|
||||
// Send all transactions to tpu socket for processing
|
||||
transactions.iter().for_each(|tx| {
|
||||
transactions_socket
|
||||
.send_to(&bincode::serialize(&tx).unwrap(), leader_data.tpu)
|
||||
.unwrap();
|
||||
});
|
||||
let mut x = 0;
|
||||
let now = Instant::now();
|
||||
while x < transaction_count + 500 || now.elapsed() > Duration::from_secs(5) {
|
||||
x = rpc_client
|
||||
.get_transaction_count_with_commitment(CommitmentConfig::recent())
|
||||
.unwrap();
|
||||
sleep(Duration::from_millis(200));
|
||||
}
|
||||
|
||||
// Wait for all signature subscriptions
|
||||
let now = SystemTime::now();
|
||||
let timeout = Duration::from_secs(5);
|
||||
let deadline = Instant::now() + Duration::from_secs(5);
|
||||
while !signature_set.is_empty() {
|
||||
assert!(now.elapsed().unwrap() < timeout);
|
||||
match receiver.recv_timeout(Duration::from_secs(1)) {
|
||||
let timeout = deadline.saturating_duration_since(Instant::now());
|
||||
match status_receiver.recv_timeout(timeout) {
|
||||
Ok((sig, result)) => {
|
||||
assert!(result.is_ok());
|
||||
assert!(result.value.is_ok());
|
||||
assert!(signature_set.remove(&sig));
|
||||
}
|
||||
Err(_err) => {
|
||||
eprintln!("unexpected receive timeout");
|
||||
eprintln!(
|
||||
"recv_timeout, {}/{} signatures remaining",
|
||||
signature_set.len(),
|
||||
transactions.len()
|
||||
);
|
||||
assert!(false)
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "solana-crate-features"
|
||||
version = "1.0.6"
|
||||
version = "1.0.9"
|
||||
description = "Solana Crate Features"
|
||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
|
@ -1,15 +1,19 @@
|
||||
# Table of contents
|
||||
|
||||
* [Introduction](introduction.md)
|
||||
* [Install Solana](install-solana.md)
|
||||
* [Use Solana from the Command-line](cli/README.md)
|
||||
* [Command-line Usage](cli/usage.md)
|
||||
* [Remote Wallet](remote-wallet/README.md)
|
||||
* [Install the Solana Tool Suite](install-solana.md)
|
||||
* [Command-line Guide](cli/README.md)
|
||||
* [Choose a Wallet](cli/choose-a-wallet.md)
|
||||
* [Hardware Wallets](remote-wallet/README.md)
|
||||
* [Ledger Hardware Wallet](remote-wallet/ledger.md)
|
||||
* [Paper Wallet](paper-wallet/README.md)
|
||||
* [Paper Wallet Usage](paper-wallet/usage.md)
|
||||
* [Generate Keys](cli/generate-keys.md)
|
||||
* [Send and Receive Tokens](cli/transfer-tokens.md)
|
||||
* [Delegate Stake](cli/delegate-stake.md)
|
||||
* [Offline Signing](offline-signing/README.md)
|
||||
* [Durable Transaction Nonces](offline-signing/durable-nonce.md)
|
||||
* [Command-line Reference](cli/usage.md)
|
||||
* [Develop Applications](apps/README.md)
|
||||
* [Example: Web Wallet](apps/webwallet.md)
|
||||
* [Example: Tic-Tac-Toe](apps/tictactoe.md)
|
||||
|
@ -693,24 +693,30 @@ Returns the status of a given signature. This method is similar to [confirmTrans
|
||||
|
||||
#### Parameters:
|
||||
|
||||
* `<string>` - Signature of Transaction to confirm, as base-58 encoded string
|
||||
* `<object>` - (optional) [Commitment](jsonrpc-api.md#configuring-state-commitment)
|
||||
* `<array>` - An array of transaction signatures to confirm, as base-58 encoded strings
|
||||
* `<object>` - (optional) Extended Rpc configuration, containing the following optional fields:
|
||||
* `commitment: <string>` - [Commitment](jsonrpc-api.md#configuring-state-commitment)
|
||||
* `searchTransactionHistory: <bool>` - whether to search the ledger transaction status cache, which may be expensive
|
||||
|
||||
#### Results:
|
||||
|
||||
An array of:
|
||||
|
||||
* `<null>` - Unknown transaction
|
||||
* `<object>` - Transaction status:
|
||||
* `"Ok": <null>` - Transaction was successful
|
||||
* `"Err": <ERR>` - Transaction failed with TransactionError [TransactionError definitions](https://github.com/solana-labs/solana/blob/master/sdk/src/transaction.rs#L14)
|
||||
* `<object>`
|
||||
* `slot: <u64>` - The slot the transaction was processed
|
||||
* `status: <object>` - Transaction status
|
||||
* `"Ok": <null>` - Transaction was successful
|
||||
* `"Err": <ERR>` - Transaction failed with TransactionError [TransactionError definitions](https://github.com/solana-labs/solana/blob/master/sdk/src/transaction.rs#L14)
|
||||
|
||||
#### Example:
|
||||
|
||||
```bash
|
||||
// Request
|
||||
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "method":"getSignatureStatus", "params":["5VERv8NMvzbJMEkV8xnrLkEaWRtSz9CosKDYjCJjBRnbJLgp8uirBgmQpjKhoR4tjF3ZpRzrFmBV6UjKdiSZkQUW"]}' http://localhost:8899
|
||||
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "method":"getSignatureStatus", "params":[["5VERv8NMvzbJMEkV8xnrLkEaWRtSz9CosKDYjCJjBRnbJLgp8uirBgmQpjKhoR4tjF3ZpRzrFmBV6UjKdiSZkQUW", "5j7s6NiJS3JAkvgkoc18WVAsiSaci2pxB2A6ueCJP4tprA2TFg9wSyTLeYouxPBJEMzJinENTkpA52YStRW5Dia7"]]]}' http://localhost:8899
|
||||
|
||||
// Result
|
||||
{"jsonrpc":"2.0","result":{"Ok": null},"id":1}
|
||||
{"jsonrpc":"2.0","result":[{"slot": 72, "status": {"Ok": null}}, null],"id":1}
|
||||
```
|
||||
|
||||
### getSlot
|
||||
@ -908,7 +914,7 @@ The result field will be a JSON object with the following fields:
|
||||
// Request
|
||||
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getVersion"}' http://localhost:8899
|
||||
// Result
|
||||
{"jsonrpc":"2.0","result":{"solana-core": "1.0.6"},"id":1}
|
||||
{"jsonrpc":"2.0","result":{"solana-core": "1.0.9"},"id":1}
|
||||
```
|
||||
|
||||
### getVoteAccounts
|
||||
|
@ -8,7 +8,7 @@ The [solana-cli crate](https://crates.io/crates/solana-cli) provides a command-l
|
||||
|
||||
```bash
|
||||
// Command
|
||||
$ solana address
|
||||
$ solana-keygen pubkey
|
||||
|
||||
// Return
|
||||
<PUBKEY>
|
||||
|
@ -1,5 +1,9 @@
|
||||
# Using Solana from the Command-line
|
||||
# Command-line Guide
|
||||
|
||||
This section describes the command-line tools for interacting with Solana. One
|
||||
could use these tools to send payments, stake validators, and check account
|
||||
balances.
|
||||
In this section, we will describe how to create a Solana *wallet*, how to send
|
||||
and receive tokens, and how to participate in the cluster by delegating stake.
|
||||
To interact with a Solana cluster, we will use its command-line interface, also
|
||||
known as the CLI. We use the command-line because it is the first place the
|
||||
Solana core team deploys new functionality. The command-line interface is not
|
||||
necessarily the easiest to use, but it provides the most direct, flexible, and
|
||||
secure access to your Solana accounts.
|
||||
|
65
docs/src/cli/choose-a-wallet.md
Normal file
65
docs/src/cli/choose-a-wallet.md
Normal file
@ -0,0 +1,65 @@
|
||||
# Choose a Wallet
|
||||
|
||||
Keypairs are stored in *wallets* and wallets come in many forms. A wallet might
|
||||
be a directory in your computer's file system, a piece of paper, or a
|
||||
specialized device called a *hardware wallet*. Some wallets are easier to use
|
||||
than others. Some are more secure than others. In this section, we will compare
|
||||
and contrast different types of wallets and help you choose the wallet that
|
||||
best fits your needs.
|
||||
|
||||
## File System Wallet
|
||||
|
||||
A *file system wallet*, aka an FS wallet, is a directory in your computer's
|
||||
file system. Each file in the directory holds a keypair.
|
||||
|
||||
### FS Wallet Security
|
||||
|
||||
An FS wallet is the most convenient and least secure form of wallet. It is
|
||||
convenient because the keypair is stored in a simple file. You can generate as
|
||||
many keys as you would like and trivially back them up by copying the files. It
|
||||
is insecure because the keypair files are **unencrypted**. If you are the only
|
||||
user of your computer and you are confident it is free of malware, an FS wallet
|
||||
is a fine solution for small amounts of cryptocurrency. If, however, your
|
||||
computer contains malware and is connected to the Internet, that malware may
|
||||
upload your keys and use them to take your tokens. Likewise, because the
|
||||
keypairs are stored on your computer as files, a skilled hacker with physical
|
||||
access to your computer may be able to access it. Using an encrypted hard
|
||||
drive, such as FileVault on MacOS, minimizes that risk.
|
||||
|
||||
## Paper Wallet
|
||||
|
||||
A *paper wallet* is a collection of *seed phrases* written on paper. A seed
|
||||
phrase is some number of words (typically 12 or 24) that can be used to
|
||||
regenerate a keypair on demand.
|
||||
|
||||
### Paper Wallet Security
|
||||
|
||||
In terms of convenience versus security, a paper wallet sits at the opposite
|
||||
side of the spectrum from an FS wallet. It is terribly inconvenient to use, but
|
||||
offers excellent security. That high security is further amplified when paper
|
||||
wallets are used in conjunction with
|
||||
[offline signing](../offline-signing/index.md). Custody services such as
|
||||
[Coinbase Custody](https://custody.coinbase.com/) use this combination. Paper wallets
|
||||
and custody services are an excellent way to secure a large number of tokens
|
||||
for a long period of time.
|
||||
|
||||
## Hardware Wallet
|
||||
|
||||
A hardware wallet is a small handheld device that stores keypairs and provides
|
||||
some interface for signing transactions.
|
||||
|
||||
### Hardware Wallet Security
|
||||
|
||||
A hardware wallet, such as the
|
||||
[Ledger hardware wallet](https://www.ledger.com/), offers a great blend of
|
||||
security and convenience for cryptocurrencies. It effectively automates the
|
||||
process of offline signing while retaining nearly all the convenience of an FS
|
||||
wallet.
|
||||
|
||||
## Which Wallet is Best?
|
||||
|
||||
Different people will have different needs, but if you are still unsure what
|
||||
is best for you after reading the comparisons above, go with a
|
||||
[Ledger Nano S](https://shop.ledger.com/products/ledger-nano-s). The
|
||||
[Nano S is well-integrated into Solana's tool suite](../remote-wallet/ledger.md)
|
||||
and offers an outstanding blend of security and convenience.
|
197
docs/src/cli/delegate-stake.md
Normal file
197
docs/src/cli/delegate-stake.md
Normal file
@ -0,0 +1,197 @@
|
||||
# Delegate Stake
|
||||
|
||||
After you have [received SOL](transfer-tokens.md), you might consider putting
|
||||
it to use by delegating *stake* to a validator. Stake is what we call tokens
|
||||
in a *stake account*. Solana weights validator votes by the amount of stake
|
||||
delegated to them, which gives those validators more influence in determining
|
||||
then next valid block of transactions in the blockchain. Solana then generates
|
||||
new SOL periodically to reward stakers and validators. You earn more rewards
|
||||
the more stake you delegate.
|
||||
|
||||
## Create a Stake Account
|
||||
|
||||
To delegate stake, you will need to transfer some tokens into a stake account.
|
||||
To create an account, you will need a keypair. Its public key will be used as
|
||||
the stake account address. No need for a password or encryption here; this
|
||||
keypair will be discarded right after creating the stake account.
|
||||
|
||||
```bash
|
||||
solana-keygen new --no-passphrase -o stake-account.json
|
||||
```
|
||||
|
||||
The output will contain the public key after the text `pubkey:`.
|
||||
|
||||
```text
|
||||
============================================================================
|
||||
pubkey: GKvqsuNcnwWqPzzuhLmGi4rzzh55FhJtGizkhHaEJqiV
|
||||
============================================================================
|
||||
```
|
||||
|
||||
Copy the public key and store it for safekeeping. You will need it any time you
|
||||
want to perform an action on the stake account you create next.
|
||||
|
||||
Now, create a stake account:
|
||||
|
||||
```bash
|
||||
solana create-stake-account --from <KEYPAIR> stake-account.json <AMOUNT> --stake-authority <KEYPAIR> --withdraw-authority <KEYPAIR>
|
||||
```
|
||||
|
||||
`<AMOUNT>` tokens are transferred from the account at `<KEYPAIR>` to a new
|
||||
stake account at the public key of stake-account.json.
|
||||
|
||||
The stake-account.json file can now be discarded. To authorize additional
|
||||
actions, you will use the `--stake-authority` or `withdraw-authority` keypair,
|
||||
not stake-account.json.
|
||||
|
||||
View the new stake account with the `solana stake-account` command:
|
||||
|
||||
```bash
|
||||
solana stake-account <STAKE_ACCOUNT_ADDRESS>
|
||||
```
|
||||
|
||||
The output will look similar to this:
|
||||
|
||||
```text
|
||||
Total Stake: 5000 SOL
|
||||
Stake account is undelegated
|
||||
Stake Authority: EXU95vqs93yPeCeAU7mPPu6HbRUmTFPEiGug9oCdvQ5F
|
||||
Withdraw Authority: EXU95vqs93yPeCeAU7mPPu6HbRUmTFPEiGug9oCdvQ5F
|
||||
```
|
||||
|
||||
### Set Stake and Withdraw Authorities
|
||||
|
||||
Staking commands look to keypairs to authorize certain stake account
|
||||
operations. They use the stake authority to authorize stake delegation,
|
||||
deactivating stake, splitting stake, and setting a new stake authority. They
|
||||
use the withdraw authority to authorize withdrawing stake, and when setting
|
||||
either a new stake or withdraw authority.
|
||||
|
||||
Stake and withdraw authorities can be set when creating an account via the
|
||||
`--stake-authority` and `--withdraw-authority` options, or afterward with the
|
||||
`solana stake-authorize` command. For example, to set a new stake authority,
|
||||
run:
|
||||
|
||||
```bash
|
||||
solana stake-authorize <STAKE_ACCOUNT_ADDRESS> --stake-authority <KEYPAIR> --new-stake-authority <PUBKEY>
|
||||
```
|
||||
|
||||
This will use the existing stake authority `<KEYPAIR>` to authorize a new stake
|
||||
authority `<PUBKEY>` on the stake account `<STAKE_ACCOUNT_ADDRESS>`.
|
||||
|
||||
### Advanced: Derive Stake Account Addresses
|
||||
|
||||
When you delegate stake, you delegate all tokens in the stake account to a
|
||||
single validator. To delegate to multiple validators, you will need multiple
|
||||
stake accounts. Creating a new keypair for each account and managing those
|
||||
addresses can be cumbersome. Fortunately, you can derive stake addresses using
|
||||
the `--seed` option:
|
||||
|
||||
```bash
|
||||
solana create-stake-account --from <KEYPAIR> <STAKE_ACCOUNT_KEYPAIR> --seed <STRING> <AMOUNT> --stake-authority <PUBKEY> --withdraw-authority <PUBKEY>
|
||||
```
|
||||
|
||||
`<STRING>` is an arbitrary string up to 32 bytes, but will typically be a
|
||||
number corresponding to which derived account this is. The first account might
|
||||
be "0", then "1", and so on. The public key of `<STAKE_ACCOUNT_KEYPAIR>` acts
|
||||
as the base address. The command derives a new address from the base address
|
||||
and seed string. To see what stake address the command will derive, use `solana
|
||||
create-address-with-seed`:
|
||||
|
||||
```bash
|
||||
solana create-address-with-seed --from <PUBKEY> <SEED_STRING> STAKE
|
||||
```
|
||||
|
||||
`<PUBKEY>` is the public key of the `<STAKE_ACCOUNT_KEYPAIR>` passed to
|
||||
`solana create-stake-account`.
|
||||
|
||||
The command will output a derived address, which can be used for the
|
||||
`<STAKE_ACCOUNT_ADDRESS>` argument in staking operations.
|
||||
|
||||
## Delegate Stake
|
||||
|
||||
To delegate your stake to a validator, you will need its vote account address.
|
||||
Find it by querying the cluster for the list of all validators and their vote
|
||||
accounts with the `solana validators` command:
|
||||
|
||||
```bash
|
||||
solana validators
|
||||
```
|
||||
|
||||
The first column of each row contains the validator's identity and the second
|
||||
is the vote account address. Choose a validator and use its vote account
|
||||
address in `solana delegate-stake`:
|
||||
|
||||
```bash
|
||||
solana delegate-stake --stake-authority <KEYPAIR> <STAKE_ACCOUNT_ADDRESS> <VOTE_ACCOUNT_ADDRESS>
|
||||
```
|
||||
|
||||
`<KEYPAIR>` authorizes the operation on the account with address
|
||||
`<STAKE_ACCOUNT_ADDRESS>`. The stake is delegated to the vote account with
|
||||
address `<VOTE_ACCOUNT_ADDRESS>`.
|
||||
|
||||
After delegating stake, use `solana stake-account` to observe the changes
|
||||
to the stake account:
|
||||
|
||||
```bash
|
||||
solana stake-account <STAKE_ACCOUNT_ADDRESS>
|
||||
```
|
||||
|
||||
You will see new fields "Delegated Stake" and "Delegated Vote Account Address"
|
||||
in the output. The output will look similar to this:
|
||||
|
||||
```text
|
||||
Total Stake: 5000 SOL
|
||||
Credits Observed: 147462
|
||||
Delegated Stake: 4999.99771712 SOL
|
||||
Delegated Vote Account Address: CcaHc2L43ZWjwCHART3oZoJvHLAe9hzT2DJNUpBzoTN1
|
||||
Stake activates starting from epoch: 42
|
||||
Stake Authority: EXU95vqs93yPeCeAU7mPPu6HbRUmTFPEiGug9oCdvQ5F
|
||||
Withdraw Authority: EXU95vqs93yPeCeAU7mPPu6HbRUmTFPEiGug9oCdvQ5F
|
||||
```
|
||||
|
||||
## Deactivate Stake
|
||||
|
||||
Once delegated, you can undelegate stake with the `solana deactivate-stake`
|
||||
command:
|
||||
|
||||
```bash
|
||||
solana deactivate-stake --stake-authority <KEYPAIR> <STAKE_ACCOUNT_ADDRESS>
|
||||
```
|
||||
|
||||
`<KEYPAIR>` authorizes the operation on the account with address
|
||||
`<STAKE_ACCOUNT_ADDRESS>`.
|
||||
|
||||
Note that stake takes several epochs to "cool down". Attempts to delegate stake
|
||||
in the cool down period will fail.
|
||||
|
||||
## Withdraw Stake
|
||||
|
||||
Transfer tokens out of a stake account with the `solana withdraw-stake` command:
|
||||
|
||||
```bash
|
||||
solana withdraw-stake --withdraw-authority <KEYPAIR> <STAKE_ACCOUNT_ADDRESS> <RECIPIENT_ADDRESS> <AMOUNT>
|
||||
```
|
||||
|
||||
`<STAKE_ACCOUNT_ADDRESS>` is the existing stake account, `<KEYPAIR>` is the
|
||||
withdraw authority, and `<AMOUNT>` is the number of tokens to transfer to
|
||||
`<RECIPIENT_ADDRESS>`.
|
||||
|
||||
## Split Stake
|
||||
|
||||
You may want to delegate stake to additional validators while your existing
|
||||
stake is not eligible for withdrawal. It might not be eligible because it is
|
||||
currently staked, cooling down, or locked up. To transfer tokens from an
|
||||
existing stake account to a new one, use the `solana split-stake` command:
|
||||
|
||||
```bash
|
||||
solana split-stake --stake-authority <KEYPAIR> <STAKE_ACCOUNT_ADDRESS> <NEW_STAKE_ACCOUNT_KEYPAIR> <AMOUNT>
|
||||
```
|
||||
|
||||
`<STAKE_ACCOUNT_ADDRESS>` is the existing stake account, `<KEYPAIR>` is the
|
||||
stake authority, `<NEW_STAKE_ACCOUNT_KEYPAIR>` is the keypair for the new account,
|
||||
and `<AMOUNT>` is the number of tokens to transfer to the new account.
|
||||
|
||||
To split a stake account into a derived account address, use the `--seed`
|
||||
option. See
|
||||
[Derive Stake Account Addresses](#advanced-derive-stake-account-addresses)
|
||||
for details.
|
90
docs/src/cli/generate-keys.md
Normal file
90
docs/src/cli/generate-keys.md
Normal file
@ -0,0 +1,90 @@
|
||||
# Generate a Keypair and its Public Key
|
||||
|
||||
In this section, we will generate a keypair, query it for its public key,
|
||||
and verify you control its private key. Before you begin, you will need
|
||||
to:
|
||||
|
||||
* [Install the Solana Tool Suite](../install-solana.md)
|
||||
* [Choose a wallet](choose-a-wallet.md)
|
||||
|
||||
## Generate an FS Wallet Keypair
|
||||
|
||||
Use Solana's command-line tool `solana-keygen` to generate keypair files. For
|
||||
example, run the following from a command-line shell:
|
||||
|
||||
```bash
|
||||
mkdir ~/my-solana-wallet
|
||||
solana-keygen new -o ~/my-solana-wallet/my-keypair.json
|
||||
```
|
||||
|
||||
If you view the file, you will see a long list of numbers, such as:
|
||||
|
||||
```text
|
||||
[42,200,155,187,52,228,32,9,179,129,192,196,149,41,177,47,87,228,5,19,70,82,170,6,142,114,68,85,124,34,165,216,110,186,177,254,198,143,235,59,173,59,17,250,142,32,66,162,130,62,53,252,48,33,148,38,149,17,81,154,95,178,163,164]
|
||||
```
|
||||
|
||||
This file contains your **unencrypted** keypair. In fact, even if you specify
|
||||
a password, that password applies to the recovery seed phrase, not the file. Do
|
||||
not share this file with others. Anyone with access to this file will have access
|
||||
to all tokens sent to its public key. Instead, you should share only its public
|
||||
key. To display its public key, run:
|
||||
|
||||
```bash
|
||||
solana-keygen pubkey ~/my-solana-wallet/my-keypair.json
|
||||
```
|
||||
|
||||
It will output a string of characters, such as:
|
||||
|
||||
```text
|
||||
ErRr1caKzK8L8nn4xmEWtimYRiTCAZXjBtVphuZ5vMKy
|
||||
```
|
||||
|
||||
This is the public key corresponding to the keypair in `~/my-solana-wallet/my-keypair.json`.
|
||||
To verify you hold the private key for a given public key, use `solana-keygen verify`:
|
||||
|
||||
```bash
|
||||
solana-keygen verify <PUBKEY> ~/my-solana-wallet/my-keypair.json
|
||||
```
|
||||
|
||||
where `<PUBKEY>` is the public key output from the previous command.
|
||||
The command will output "Success" if the given public key matches the
|
||||
the one in your keypair file, and "Failed" otherwise.
|
||||
|
||||
## Generate a Paper Wallet Seed Phrase
|
||||
|
||||
See [Creating a Paper Wallet](../paper-wallet/usage.md#creating-a-paper-wallet).
|
||||
|
||||
To verify you control the private key of that public key, use `solana-keygen verify`:
|
||||
|
||||
```bash
|
||||
solana-keygen verify <PUBKEY> ASK
|
||||
```
|
||||
|
||||
where `<PUBKEY>` is the keypair's public key and they keyword `ASK` tells the
|
||||
command to prompt you for the keypair's seed phrase. Note that for security
|
||||
reasons, your seed phrase will not be displayed as you type. After entering your
|
||||
seed phrase, the command will output "Success" if the given public key matches the
|
||||
keypair generated from your seed phrase, and "Failed" otherwise.
|
||||
|
||||
## Generate a Hardware Wallet Keypair
|
||||
|
||||
Keypairs are automatically derived when you query a hardware wallet with a
|
||||
[keypair URL](../remote-wallet#specify-a-hardware-wallet-key).
|
||||
|
||||
Once you have your keypair URL, use `solana-keygen pubkey` to query the hardware
|
||||
wallet for the keypair's public key:
|
||||
|
||||
```bash
|
||||
solana-keygen pubkey <KEYPAIR>
|
||||
```
|
||||
|
||||
where `<KEYPAIR>` is the keypair URL.
|
||||
|
||||
To verify you control the private key of that public key, use `solana-keygen verify`:
|
||||
|
||||
```bash
|
||||
solana-keygen verify <PUBKEY> <KEYPAIR>
|
||||
```
|
||||
|
||||
The command will output "Success" if the given public key matches the
|
||||
the one at your keypair URL, and "Failed" otherwise.
|
117
docs/src/cli/transfer-tokens.md
Normal file
117
docs/src/cli/transfer-tokens.md
Normal file
@ -0,0 +1,117 @@
|
||||
# Send and Receive Tokens
|
||||
|
||||
## Receive Tokens
|
||||
|
||||
To receive tokens, you will need an address for others to send tokens to. In
|
||||
Solana, an address is the public key of a keypair. There are a variety
|
||||
of techniques for generating keypairs. The method you choose will depend on how
|
||||
you choose to store keypairs. Keypairs are stored in wallets. Before receiving
|
||||
tokens, you will need to [choose a wallet](choose-a-wallet.md) and
|
||||
[generate keys](generate-keys.md). Once completed, you should have a public key
|
||||
for each keypair you generated. The public key is a long string of base58
|
||||
characters. Its length varies from 32 to 44 characters.
|
||||
|
||||
### Using Solana CLI
|
||||
|
||||
Before running any Solana CLI commands, let's go over some conventions that
|
||||
you will see across all commands. First, the Solana CLI is actually a collection
|
||||
of different commands for each action you might want to take. You can view the list
|
||||
of all possible commands by running:
|
||||
|
||||
```bash
|
||||
solana --help
|
||||
```
|
||||
|
||||
To zoom in on how to use a particular command, run:
|
||||
|
||||
```bash
|
||||
solana <COMMAND> --help
|
||||
```
|
||||
|
||||
where you replace the text `<COMMAND>` with the name of the command you want
|
||||
to learn more about.
|
||||
|
||||
The command's usage message will typically contain words such as `<AMOUNT>`,
|
||||
`<ACCOUNT_ADDRESS>` or `<KEYPAIR>`. Each word is a placeholder for the *type* of
|
||||
text you can execute the command with. For example, you can replace `<AMOUNT>`
|
||||
with a number such as `42` or `100.42`. You can replace `<ACCOUNT_ADDRESS>` with
|
||||
the base58 encoding of your public key. For `<KEYPAIR>`, it depends on what type
|
||||
of wallet you chose. If you chose an fs wallet, that path might be
|
||||
`~/my-solana-wallet/my-keypair.json`. If you chose a paper wallet, use the
|
||||
keyword `ASK`, and the Solana CLI will prompt you for your seed phrase. If
|
||||
you chose a hardware wallet, use your keypair URL, such as `usb://ledger?key=0`.
|
||||
|
||||
### Test-drive your Public Keys
|
||||
|
||||
Before sharing your public key with others, you may want to first ensure the
|
||||
key is valid and that you indeed hold the corresponding private key.
|
||||
|
||||
Try and *airdrop* yourself some play tokens on the developer testnet, called
|
||||
Devnet:
|
||||
|
||||
```bash
|
||||
solana airdrop 10 <RECIPIENT_ACCOUNT_ADDRESS> --url http://devnet.solana.com
|
||||
```
|
||||
|
||||
where you replace the text `<RECIPIENT_ACCOUNT_ADDRESS>` with your base58-encoded
|
||||
public key.
|
||||
|
||||
Confirm the airdrop was successful by checking the account's balance.
|
||||
It should output `10 SOL`:
|
||||
|
||||
```bash
|
||||
solana balance <ACCOUNT_ADDRESS> --url http://devnet.solana.com
|
||||
```
|
||||
|
||||
Next, prove that you own those tokens by transferring them. The Solana cluster
|
||||
will only accept the transfer if you sign the transaction with the private
|
||||
key corresponding to the sender's public key in the transaction.
|
||||
|
||||
First, we will need a public key to receive our tokens. Create a second
|
||||
keypair and record its pubkey:
|
||||
|
||||
```bash
|
||||
solana-keygen new --no-passphrase --no-outfile
|
||||
```
|
||||
|
||||
The output will contain the public key after the text `pubkey:`. Copy the
|
||||
public key. We will use it in the next step.
|
||||
|
||||
```text
|
||||
============================================================================
|
||||
pubkey: GKvqsuNcnwWqPzzuhLmGi4rzzh55FhJtGizkhHaEJqiV
|
||||
============================================================================
|
||||
```
|
||||
|
||||
```bash
|
||||
solana transfer --from <SENDER_KEYPAIR> <RECIPIENT_ACCOUNT_ADDRESS> 5 --url http://devnet.solana.com
|
||||
```
|
||||
|
||||
where you replace `<SENDER_KEYPAIR>` with the path to a keypair in your wallet,
|
||||
and replace `<RECIPIENT_ACCOUNT_ADDRESS>` with the output of `solana-keygen new` above.
|
||||
|
||||
Confirm the updated balances with `solana balance`:
|
||||
|
||||
```bash
|
||||
solana balance <ACCOUNT_ADDRESS> --url http://devnet.solana.com
|
||||
```
|
||||
|
||||
where `<ACCOUNT_ADDRESS>` is either the public key from your keypair or the
|
||||
recipient's public key.
|
||||
|
||||
## Send Tokens
|
||||
|
||||
If you already hold SOL and want to send tokens to someone, you will need
|
||||
a path to your keypair, their base58-encoded public key, and a number of
|
||||
tokens to transfer. Once you have that collected, you can transfer tokens
|
||||
with the `solana transfer` command:
|
||||
|
||||
```bash
|
||||
solana transfer --from <SENDER_KEYPAIR> <RECIPIENT_ACCOUNT_ADDRESS> <AMOUNT>
|
||||
```
|
||||
|
||||
Confirm the updated balances with `solana balance`:
|
||||
|
||||
```bash
|
||||
solana balance <ACCOUNT_ADDRESS>
|
||||
```
|
@ -171,7 +171,7 @@ $ solana send-timestamp <PUBKEY> <PROCESS_ID> --date 2018-12-24T23:59:00
|
||||
## Usage
|
||||
### solana-cli
|
||||
```text
|
||||
solana-cli 1.0.6 [channel=unknown commit=unknown]
|
||||
solana-cli 1.0.9 [channel=unknown commit=unknown]
|
||||
Blockchain, Rebuilt for Scale
|
||||
|
||||
USAGE:
|
||||
|
@ -154,7 +154,7 @@ FLAGS:
|
||||
|
||||
OPTIONS:
|
||||
-d, --data_dir <PATH> Directory to store install data [default: .../Library/Application Support/solana]
|
||||
-u, --url <URL> JSON RPC URL for the solana cluster [default: http://devnet.solana.com:8899]
|
||||
-u, --url <URL> JSON RPC URL for the solana cluster [default: http://devnet.solana.com]
|
||||
-p, --pubkey <PUBKEY> Public key of the update manifest [default: 9XX329sPuskWhH4DQh6k16c87dHKhXLBZTL3Gxmve8Gp]
|
||||
```
|
||||
|
||||
|
@ -110,7 +110,7 @@ Use the `solana config set` command to target a different cluster.
|
||||
For example, for Devnet, use:
|
||||
|
||||
```bash
|
||||
solana config set --url http://devnet.solana.com:8899
|
||||
solana config set --url http://devnet.solana.com
|
||||
```
|
||||
|
||||
## Ensure Versions Match
|
||||
|
@ -18,8 +18,7 @@ At present, the following commands support offline signing:
|
||||
* [`deactivate-stake`](../cli/usage.md#solana-deactivate-stake)
|
||||
* [`delegate-stake`](../cli/usage.md#solana-delegate-stake)
|
||||
* [`split-stake`](../cli/usage.md#solana-split-stake)
|
||||
* [`stake-authorize-staker`](../cli/usage.md#solana-stake-authorize-staker)
|
||||
* [`stake-authorize-withdrawer`](../cli/usage.md#solana-stake-authorize-withdrawer)
|
||||
* [`stake-authorize`](../cli/usage.md#solana-stake-authorize)
|
||||
* [`stake-set-lockup`](../cli/usage.md#solana-stake-set-lockup)
|
||||
* [`transfer`](../cli/usage.md#solana-transfer)
|
||||
* [`withdraw-stake`](../cli/usage.md#solana-withdraw-stake)
|
||||
@ -82,6 +81,72 @@ Output
|
||||
4vC38p4bz7XyiXrk6HtaooUqwxTWKocf45cstASGtmrD398biNJnmTcUCVEojE7wVQvgdYbjHJqRFZPpzfCQpmUN
|
||||
```
|
||||
|
||||
## Offline Signing Over Multiple Sessions
|
||||
|
||||
Offline signing can also take place over multiple sessions. In this scenario,
|
||||
pass the absent signer's public key for each role. All pubkeys that were specified,
|
||||
but no signature was generated for will be listed as absent in the offline signing
|
||||
output
|
||||
|
||||
### Example: Transfer with Two Offline Signing Sessions
|
||||
|
||||
Command (Offline Session #1)
|
||||
|
||||
```text
|
||||
solana@offline1$ solana transfer Fdri24WUGtrCXZ55nXiewAj6RM18hRHPGAjZk3o6vBut 10 \
|
||||
--blockhash 7ALDjLv56a8f6sH6upAZALQKkXyjAwwENH9GomyM8Dbc \
|
||||
--sign-only \
|
||||
--keypair fee_payer.json \
|
||||
--from 674RgFMgdqdRoVtMqSBg7mHFbrrNm1h1r721H1ZMquHL
|
||||
```
|
||||
|
||||
Output (Offline Session #1)
|
||||
|
||||
```text
|
||||
Blockhash: 7ALDjLv56a8f6sH6upAZALQKkXyjAwwENH9GomyM8Dbc
|
||||
Signers (Pubkey=Signature):
|
||||
3bo5YiRagwmRikuH6H1d2gkKef5nFZXE3gJeoHxJbPjy=ohGKvpRC46jAduwU9NW8tP91JkCT5r8Mo67Ysnid4zc76tiiV1Ho6jv3BKFSbBcr2NcPPCarmfTLSkTHsJCtdYi
|
||||
Absent Signers (Pubkey):
|
||||
674RgFMgdqdRoVtMqSBg7mHFbrrNm1h1r721H1ZMquHL
|
||||
```
|
||||
|
||||
Command (Offline Session #2)
|
||||
|
||||
```text
|
||||
solana@offline2$ solana transfer Fdri24WUGtrCXZ55nXiewAj6RM18hRHPGAjZk3o6vBut 10 \
|
||||
--blockhash 7ALDjLv56a8f6sH6upAZALQKkXyjAwwENH9GomyM8Dbc \
|
||||
--sign-only \
|
||||
--keypair from.json \
|
||||
--fee-payer 3bo5YiRagwmRikuH6H1d2gkKef5nFZXE3gJeoHxJbPjy
|
||||
```
|
||||
|
||||
Output (Offline Session #2)
|
||||
|
||||
```text
|
||||
Blockhash: 7ALDjLv56a8f6sH6upAZALQKkXyjAwwENH9GomyM8Dbc
|
||||
Signers (Pubkey=Signature):
|
||||
674RgFMgdqdRoVtMqSBg7mHFbrrNm1h1r721H1ZMquHL=3vJtnba4dKQmEAieAekC1rJnPUndBcpvqRPRMoPWqhLEMCty2SdUxt2yvC1wQW6wVUa5putZMt6kdwCaTv8gk7sQ
|
||||
Absent Signers (Pubkey):
|
||||
3bo5YiRagwmRikuH6H1d2gkKef5nFZXE3gJeoHxJbPjy
|
||||
```
|
||||
|
||||
Command (Online Submission)
|
||||
|
||||
```text
|
||||
solana@online$ solana transfer Fdri24WUGtrCXZ55nXiewAj6RM18hRHPGAjZk3o6vBut 10 \
|
||||
--blockhash 7ALDjLv56a8f6sH6upAZALQKkXyjAwwENH9GomyM8Dbc \
|
||||
--from 674RgFMgdqdRoVtMqSBg7mHFbrrNm1h1r721H1ZMquHL \
|
||||
--signer 674RgFMgdqdRoVtMqSBg7mHFbrrNm1h1r721H1ZMquHL=3vJtnba4dKQmEAieAekC1rJnPUndBcpvqRPRMoPWqhLEMCty2SdUxt2yvC1wQW6wVUa5putZMt6kdwCaTv8gk7sQ \
|
||||
--fee-payer 3bo5YiRagwmRikuH6H1d2gkKef5nFZXE3gJeoHxJbPjy \
|
||||
--signer 3bo5YiRagwmRikuH6H1d2gkKef5nFZXE3gJeoHxJbPjy=ohGKvpRC46jAduwU9NW8tP91JkCT5r8Mo67Ysnid4zc76tiiV1Ho6jv3BKFSbBcr2NcPPCarmfTLSkTHsJCtdYi
|
||||
```
|
||||
|
||||
Output (Online Submission)
|
||||
|
||||
```text
|
||||
ohGKvpRC46jAduwU9NW8tP91JkCT5r8Mo67Ysnid4zc76tiiV1Ho6jv3BKFSbBcr2NcPPCarmfTLSkTHsJCtdYi
|
||||
```
|
||||
|
||||
## Buying More Time to Sign
|
||||
|
||||
Typically a Solana transaction must be signed and accepted by the network within
|
||||
|
@ -17,6 +17,4 @@ support keypair input via seed phrases.
|
||||
To learn more about the BIP39 standard, visit the Bitcoin BIPs Github repository
|
||||
[here](https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki).
|
||||
|
||||
{% page-ref page="installation.md" %}
|
||||
|
||||
{% page-ref page="usage.md" %}
|
||||
|
@ -125,7 +125,7 @@ Command
|
||||
|
||||
```text
|
||||
solana transfer 11111111111111111111111111111111 0 --sign-only \
|
||||
--ask-seed-phrase keypair --blockhash 11111111111111111111111111111111
|
||||
--keypair ASK --blockhash 11111111111111111111111111111111
|
||||
```
|
||||
|
||||
Prompt for seed phrase
|
||||
@ -203,7 +203,7 @@ networked machine.
|
||||
Next, configure the `solana` CLI tool to connect to a particular cluster:
|
||||
|
||||
```bash
|
||||
solana config set --url <CLUSTER URL> # (i.e. http://devnet.solana.com:8899)
|
||||
solana config set --url <CLUSTER URL> # (i.e. http://devnet.solana.com)
|
||||
```
|
||||
|
||||
Finally, to check the balance, run the following command:
|
||||
@ -233,25 +233,25 @@ done < public_keys.txt
|
||||
|
||||
In order to run a validator, you will need to specify an "identity keypair"
|
||||
which will be used to fund all of the vote transactions signed by your validator.
|
||||
Rather than specifying a path with `--identity-keypair <PATH>` you can pass
|
||||
Rather than specifying a path with `--identity <PATH>` you can pass
|
||||
`ASK` to securely input the funding keypair.
|
||||
|
||||
```bash
|
||||
solana-validator --identity-keypair ASK --ledger ...
|
||||
solana-validator --identity ASK --ledger ...
|
||||
|
||||
[identity-keypair] seed phrase: 🔒
|
||||
[identity-keypair] If this seed phrase has an associated passphrase, enter it now. Otherwise, press ENTER to continue:
|
||||
[identity] seed phrase: 🔒
|
||||
[identity] If this seed phrase has an associated passphrase, enter it now. Otherwise, press ENTER to continue:
|
||||
```
|
||||
|
||||
You can use this input method for your voting keypair as well:
|
||||
|
||||
```bash
|
||||
solana-validator --identity-keypair ASK --voting-keypair ASK --ledger ...
|
||||
solana-validator --identity ASK --authorized-voter ASK --ledger ...
|
||||
|
||||
[identity-keypair] seed phrase: 🔒
|
||||
[identity-keypair] If this seed phrase has an associated passphrase, enter it now. Otherwise, press ENTER to continue:
|
||||
[voting-keypair] seed phrase: 🔒
|
||||
[voting-keypair] If this seed phrase has an associated passphrase, enter it now. Otherwise, press ENTER to continue:
|
||||
[identity] seed phrase: 🔒
|
||||
[identity] If this seed phrase has an associated passphrase, enter it now. Otherwise, press ENTER to continue:
|
||||
[authorized-voter] seed phrase: 🔒
|
||||
[authorized-voter] If this seed phrase has an associated passphrase, enter it now. Otherwise, press ENTER to continue:
|
||||
```
|
||||
|
||||
Refer to the following page for a comprehensive guide on running a validator:
|
||||
|
@ -1,12 +1,78 @@
|
||||
# Remote Wallet
|
||||
# Hardware Wallets
|
||||
|
||||
This document describes how to use a remote wallet with the Solana CLI
|
||||
tools. Currently, Solana supports:
|
||||
- Ledger hardware wallet (Nano S)
|
||||
Signing a transaction requires a private key, but storing a private
|
||||
key on your personal computer or phone leaves it subject to theft.
|
||||
Adding a password to your key adds security, but many people prefer
|
||||
to take it a step further and move their private keys to a separate
|
||||
physical device called a *hardware wallet*. A hardware wallet is a
|
||||
small handheld device that stores private keys and provides some
|
||||
interface for signing transactions.
|
||||
|
||||
## Overview
|
||||
The Solana CLI has first class support for hardware wallets. Anywhere
|
||||
you use a keypair filepath (denoted as `<KEYPAIR>` in usage docs), you
|
||||
can pass a *keypair URL* that uniquely identifies a keypair in a
|
||||
hardware wallet.
|
||||
|
||||
Solana's remote-wallet integration provides Solana CLI methods to query a
|
||||
device's BIP32 public keys and send messages to the device for secure signing.
|
||||
## Supported Hardware Wallets
|
||||
|
||||
{% page-ref page="ledger.md" %}
|
||||
The Solana CLI supports the following hardware wallets:
|
||||
- [Ledger Nano S](ledger.md)
|
||||
|
||||
## Specify a Hardware Wallet Key
|
||||
|
||||
Solana defines a keypair URL format to uniquely locate any Solana keypair on a
|
||||
hardware wallet connected to your computer.
|
||||
|
||||
The keypair URL has the following form, where square brackets denote optional
|
||||
fields:
|
||||
|
||||
```text
|
||||
usb://<MANUFACTURER>[/<WALLET_ID>][?key=<DERIVATION_PATH>]
|
||||
```
|
||||
|
||||
`WALLET_ID` is a globally unique key used to disambiguate multiple devices.
|
||||
|
||||
`DERVIATION_PATH` is used to navigate to Solana keys within your hardware wallet.
|
||||
The path has the form `<ACCOUNT>[/<CHANGE>]`, where each `ACCOUNT` and `CHANGE`
|
||||
are positive integers.
|
||||
|
||||
All derivation paths implicitly include the prefix `44'/501'`, which indicates
|
||||
the path follows the [BIP44 specifications](https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki)
|
||||
and that any derived keys are Solana keys (Coin type 501). The single quote
|
||||
indicates a "hardened" derivation. Because Solana uses Ed25519 keypairs, all
|
||||
derivations are hardened and therefore adding the quote is optional and
|
||||
unnecessary.
|
||||
|
||||
For example, a fully qualified URL for a Ledger device might be:
|
||||
|
||||
```text
|
||||
usb://ledger/BsNsvfXqQTtJnagwFWdBS7FBXgnsK8VZ5CmuznN85swK?key=0/0
|
||||
```
|
||||
|
||||
## Manage Multiple Hardware Wallets
|
||||
|
||||
It is sometimes useful to sign a transaction with keys from multiple hardware
|
||||
wallets. Signing with multiple wallets requires *fully qualified keypair URLs*.
|
||||
When the URL is not fully qualified, the Solana CLI will prompt you with
|
||||
the fully qualified URLs of all connected hardware wallets, and ask you to
|
||||
choose which wallet to use for each signature.
|
||||
|
||||
Instead of using the interactive prompts, you can generate fully qualified
|
||||
URLs using the Solana CLI `resolve-signer` command. For example, try
|
||||
connecting a Ledger Nano-S to USB, unlock it with your pin, and running the
|
||||
following command:
|
||||
|
||||
```text
|
||||
solana resolve-signer usb://ledger?key=0/0
|
||||
```
|
||||
|
||||
You will see output similar to:
|
||||
|
||||
```text
|
||||
usb://ledger/BsNsvfXqQTtJnagwFWdBS7FBXgnsK8VZ5CmuznN85swK?key=0/0
|
||||
```
|
||||
|
||||
but where `BsNsvfXqQTtJnagwFWdBS7FBXgnsK8VZ5CmuznN85swK` is your `WALLET_ID`.
|
||||
|
||||
With your fully qualified URL, you can connect multiple hardware wallets to
|
||||
the same computer and uniquely identify a keypair from any of them.
|
||||
|
@ -72,112 +72,29 @@ To fix, check the following:
|
||||
3. On your computer, run:
|
||||
|
||||
```text
|
||||
solana address --keypair usb://ledger
|
||||
solana-keygen pubkey usb://ledger
|
||||
```
|
||||
|
||||
This confirms your Ledger device is connected properly and in the correct state
|
||||
to interact with the Solana CLI. The command returns your Ledger's unique
|
||||
*wallet key*. When you have multiple Nano S devices connected to the same
|
||||
*wallet ID*. When you have multiple Nano S devices connected to the same
|
||||
computer, you can use your wallet key to specify which Ledger hardware wallet
|
||||
you want to use. Run the same command again, but this time, with its fully
|
||||
qualified URL:
|
||||
|
||||
```text
|
||||
solana address --keypair usb://ledger/nano-s/<WALLET_KEY>
|
||||
solana-keygen pubkey usb://ledger/<WALLET_ID>
|
||||
```
|
||||
|
||||
Confirm it prints the same key as when you entered just `usb://ledger`.
|
||||
where you replace `<WALLET_ID>` with the output of the first command.
|
||||
Confirm it prints the same wallet ID as before.
|
||||
|
||||
### Ledger Device URLs
|
||||
To learn more about keypair URLs, see
|
||||
[Specify A Hardware Wallet Key](README.md#specify-a-hardware-wallet-key)
|
||||
|
||||
Solana defines a format for the URL protocol "usb://" to uniquely locate any Solana key on
|
||||
any remote wallet connected to your computer.
|
||||
|
||||
The URL has the form, where square brackets denote optional fields:
|
||||
|
||||
```text
|
||||
usb://ledger[/<LEDGER_TYPE>[/<WALLET_KEY>]][?key=<DERIVATION_PATH>]
|
||||
```
|
||||
|
||||
`LEDGER_TYPE` is optional and defaults to the value "nano-s". If the value is provided,
|
||||
it must be "nano-s" without quotes, the only supported Ledger device at this time.
|
||||
|
||||
`WALLET_KEY` is used to disambiguate multiple Nano S devices. Every Ledger has
|
||||
a unique master key and from that key derives a separate unique key per app.
|
||||
|
||||
`DERVIATION_PATH` is used to navigate to Solana keys within your Ledger hardware
|
||||
wallet. The path has the form `<ACCOUNT>[/<CHANGE>]`, where each `ACCOUNT` and
|
||||
`CHANGE` are postive integers.
|
||||
|
||||
All derivation paths implicitly include the prefix `44'/501'`, which indicates
|
||||
the path follows the [BIP44 specifications](https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki)
|
||||
and that any dervied keys are Solana keys (Coin type 501). The single quote
|
||||
indicates a "hardened" derivation. Because Solana uses Ed25519 keypairs, all
|
||||
derivations are hardened and therefore adding the quote is optional and
|
||||
unnecessary.
|
||||
|
||||
For example, a complete Ledger device path might be:
|
||||
|
||||
```text
|
||||
usb://ledger/nano-s/BsNsvfXqQTtJnagwFWdBS7FBXgnsK8VZ5CmuznN85swK?key=0/0
|
||||
```
|
||||
|
||||
### Set CLI Configuration
|
||||
|
||||
Configure the `solana` CLI tool to connect to a particular cluster:
|
||||
|
||||
```bash
|
||||
solana config set --url <CLUSTER_URL> # (i.e. http://devnet.solana.com:8899)
|
||||
```
|
||||
|
||||
If you want to set a Ledger key as the default signer for CLI commands, use the
|
||||
[CLI configuration settings](../cli/usage.md#solana-config):
|
||||
|
||||
```text
|
||||
solana config set --keypair <LEDGER_URL>
|
||||
```
|
||||
|
||||
For example:
|
||||
|
||||
```text
|
||||
solana config set --keypair usb://ledger?key=0
|
||||
```
|
||||
|
||||
### Check Account Balance
|
||||
|
||||
```text
|
||||
solana balance --keypair usb://ledger?key=12345
|
||||
```
|
||||
|
||||
Or with the default signer:
|
||||
|
||||
```text
|
||||
solana balance
|
||||
```
|
||||
|
||||
### Send SOL via Ledger Device
|
||||
|
||||
```text
|
||||
solana transfer <RECIPIENT> <AMOUNT> --from <LEDGER_URL>
|
||||
```
|
||||
|
||||
Or with the default signer:
|
||||
|
||||
```text
|
||||
solana transfer <RECIPIENT> <AMOUNT>
|
||||
```
|
||||
|
||||
### Delegate Stake with Ledger Device
|
||||
|
||||
```text
|
||||
solana delegate-stake <STAKE_ACCOUNT> <VOTER_ID> --keypair <LEDGER_URL>
|
||||
```
|
||||
|
||||
Or with the default signer:
|
||||
|
||||
```text
|
||||
solana delegate-stake <STAKE_ACCOUNT> <VOTER_ID>
|
||||
```
|
||||
Read more about [sending and receiving tokens](../transfer-tokens.md) and
|
||||
[delegating stake](../delegate-stake.md). You can use your Ledger keypair URL
|
||||
anywhere you see an option or argument that accepts a `<KEYPAIR>`.
|
||||
|
||||
## Support
|
||||
|
||||
|
@ -35,13 +35,13 @@ cluster, as well as the health of the cluster:
|
||||
|
||||
```bash
|
||||
# Similar to solana-gossip, you should see your validator in the list of cluster nodes
|
||||
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getClusterNodes"}' http://devnet.solana.com:8899
|
||||
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getClusterNodes"}' http://devnet.solana.com
|
||||
# If your validator is properly voting, it should appear in the list of `current` vote accounts. If staked, `stake` should be > 0
|
||||
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getVoteAccounts"}' http://devnet.solana.com:8899
|
||||
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getVoteAccounts"}' http://devnet.solana.com
|
||||
# Returns the current leader schedule
|
||||
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getLeaderSchedule"}' http://devnet.solana.com:8899
|
||||
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getLeaderSchedule"}' http://devnet.solana.com
|
||||
# Returns info about the current epoch. slotIndex should progress on subsequent calls.
|
||||
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getEpochInfo"}' http://devnet.solana.com:8899
|
||||
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"getEpochInfo"}' http://devnet.solana.com
|
||||
```
|
||||
|
||||
|
||||
|
@ -6,11 +6,11 @@ The solana cli includes `get` and `set` configuration commands to automatically
|
||||
set the `--url` argument for cli commands. For example:
|
||||
|
||||
```bash
|
||||
solana config set --url http://devnet.solana.com:8899
|
||||
solana config set --url http://devnet.solana.com
|
||||
```
|
||||
|
||||
\(You can always override the set configuration by explicitly passing the
|
||||
`--url` argument with a command, eg: `solana --url http://beta.devnet.solana.com:8899 balance`\)
|
||||
`--url` argument with a command, eg: `solana --url http://tds.solana.com balance`\)
|
||||
|
||||
## Confirm The Testnet Is Reachable
|
||||
|
||||
@ -24,8 +24,8 @@ solana transaction-count
|
||||
Inspect the network explorer at
|
||||
[https://explorer.solana.com/](https://explorer.solana.com/) for activity.
|
||||
|
||||
View the [metrics dashboard](https://metrics.solana.com:3000/d/testnet-beta/testnet-monitor-beta?var-testnet=testnet)
|
||||
for more detail on cluster activity.
|
||||
View the [metrics dashboard](https://metrics.solana.com:3000/d/monitor/cluster-telemetry) for more
|
||||
detail on cluster activity.
|
||||
|
||||
## Confirm your Installation
|
||||
|
||||
@ -107,7 +107,7 @@ You should see the following output:
|
||||
|
||||
```text
|
||||
Wallet Config Updated: /home/solana/.config/solana/wallet/config.yml
|
||||
* url: http://devnet.solana.com:8899
|
||||
* url: http://devnet.solana.com
|
||||
* keypair: /home/solana/validator-keypair.json
|
||||
```
|
||||
|
||||
@ -137,24 +137,35 @@ Read more about the [difference between SOL and lamports here](../introduction.m
|
||||
|
||||
If you haven’t already done so, create a vote-account keypair and create the
|
||||
vote account on the network. If you have completed this step, you should see the
|
||||
“validator-vote-keypair.json” in your Solana runtime directory:
|
||||
“vote-account-keypair.json” in your Solana runtime directory:
|
||||
|
||||
```bash
|
||||
solana-keygen new -o ~/validator-vote-keypair.json
|
||||
solana-keygen new -o ~/vote-account-keypair.json
|
||||
```
|
||||
|
||||
Create your vote account on the blockchain:
|
||||
|
||||
```bash
|
||||
solana create-vote-account ~/validator-vote-keypair.json ~/validator-keypair.json
|
||||
solana create-vote-account ~/vote-account-keypair.json ~/validator-keypair.json
|
||||
```
|
||||
|
||||
## Trusted validators
|
||||
|
||||
If you know and trust other validator nodes, you can specify this on the command line with the `--trusted-validator <PUBKEY>`
|
||||
argument to `solana-validator`. You can specify multiple ones by repeating the argument `--trusted-validator <PUBKEY1> --trusted-validator <PUBKEY2>`.
|
||||
This has two effects, one is when the validator is booting, it will only ask that set of trusted nodes for downloading genesis and
|
||||
snapshots. Another is that in combination with the `--halt-on-trusted-validator-hash-mismatch` option, it will monitor the merkle
|
||||
root hash of the entire accounts state of other trusted nodes on gossip and if the hashes produce any mismatch, the validator will
|
||||
halt the node to prevent the validator from voting or processing potentially incorrect state values. At the moment, the slot that
|
||||
the validator publishes the hash on is tied to the snapshot interval. For the feature to be effective, all validators in the trusted
|
||||
set should be set to the same snapshot interval value or multiples of the same.
|
||||
|
||||
## Connect Your Validator
|
||||
|
||||
Connect to a testnet cluster by running:
|
||||
|
||||
```bash
|
||||
solana-validator --identity-keypair ~/validator-keypair.json --voting-keypair ~/validator-vote-keypair.json \
|
||||
solana-validator --identity ~/validator-keypair.json --vote-account ~/vote-account-keypair.json \
|
||||
--ledger ~/validator-ledger --rpc-port 8899 --entrypoint devnet.solana.com:8001 \
|
||||
--limit-ledger-size
|
||||
```
|
||||
|
@ -5,7 +5,7 @@ testnet participants, [https://discord.gg/pquxPsq](https://discord.gg/pquxPsq).
|
||||
|
||||
## Useful Links & Discussion
|
||||
* [Network Explorer](http://explorer.solana.com/)
|
||||
* [Testnet Metrics Dashboard](https://metrics.solana.com:3000/d/testnet-edge/testnet-monitor-edge?refresh=60s&orgId=2)
|
||||
* [Testnet Metrics Dashboard](https://metrics.solana.com:3000/d/monitor-edge/cluster-telemetry-edge?refresh=60s&orgId=2)
|
||||
* Validator chat channels
|
||||
* [\#validator-support](https://discord.gg/rZsenD) General support channel for any Validator related queries.
|
||||
* [\#tourdesol](https://discord.gg/BdujK2) Discussion and support channel for Tour de SOL participants ([What is Tour de SOL?](https://solana.com/tds/)).
|
||||
@ -14,6 +14,6 @@ testnet participants, [https://discord.gg/pquxPsq](https://discord.gg/pquxPsq).
|
||||
* [Core software repo](https://github.com/solana-labs/solana)
|
||||
* [Tour de SOL Docs](https://docs.solana.com/tour-de-sol)
|
||||
* [TdS repo](https://github.com/solana-labs/tour-de-sol)
|
||||
* [TdS metrics dashboard](https://metrics.solana.com:3000/d/testnet-edge/testnet-monitor-edge?refresh=1m&from=now-15m&to=now&var-testnet=tds&orgId=2&var-datasource=TdS%20Metrics%20%28read-only%29)
|
||||
* [TdS metrics dashboard](https://metrics.solana.com:3000/d/monitor-edge/cluster-telemetry-edge?refresh=1m&from=now-15m&to=now&var-testnet=tds)
|
||||
|
||||
Can't find what you're looking for? Send an email to ryan@solana.com or reach out to @rshea\#2622 on Discord.
|
||||
|
@ -4,16 +4,16 @@
|
||||
|
||||
Once you’ve confirmed the network is running, it’s time to connect your validator to the network.
|
||||
|
||||
If you haven’t already done so, create a vote-account keypair and create the vote account on the network. If you have completed this step, you should see the “validator-vote-keypair.json” in your Solana runtime directory:
|
||||
If you haven’t already done so, create a vote-account keypair and create the vote account on the network. If you have completed this step, you should see the “vote-account-keypair.json” in your Solana runtime directory:
|
||||
|
||||
```bash
|
||||
solana-keygen new -o ~/validator-vote-keypair.json
|
||||
solana-keygen new -o ~/vote-account-keypair.json
|
||||
```
|
||||
|
||||
Create your vote account on the blockchain:
|
||||
|
||||
```bash
|
||||
solana create-vote-account ~/validator-vote-keypair.json ~/validator-keypair.json
|
||||
solana create-vote-account ~/vote-account-keypair.json ~/validator-keypair.json
|
||||
```
|
||||
|
||||
## Connect Your Validator
|
||||
@ -25,7 +25,7 @@ export SOLANA_METRICS_CONFIG="host=https://metrics.solana.com:8086,db=tds,u=tds_
|
||||
```
|
||||
|
||||
```bash
|
||||
solana-validator --identity-keypair ~/validator-keypair.json --voting-keypair ~/validator-vote-keypair.json \
|
||||
solana-validator --identity ~/validator-keypair.json --vote-account ~/vote-account-keypair.json \
|
||||
--ledger ~/validator-ledger --rpc-port 8899 --entrypoint tds.solana.com:8001 \
|
||||
--limit-ledger-size
|
||||
```
|
||||
|
@ -6,7 +6,7 @@ description: Where to go after you've read this guide
|
||||
|
||||
* [Solana Docs](https://docs.solana.com/)
|
||||
* [Network Explorer](http://explorer.solana.com/)
|
||||
* [TdS metrics dashboard](https://metrics.solana.com:3000/d/testnet/testnet-monitor?refresh=1m&from=now-15m&to=now&orgId=2&var-datasource=Solana%20Metrics%20(read-only)&var-testnet=tds&var-hostid=All9)
|
||||
* [TdS metrics dashboard](https://metrics.solana.com:3000/d/monitor-edge/cluster-telemetry-edge?refresh=1m&from=now-15m&to=now&var-testnet=tds)
|
||||
* Validator chat channels
|
||||
* [\#validator-support](https://discord.gg/rZsenD) General support channel for any Validator related queries that don’t fall under Tour de SOL.
|
||||
* [\#tourdesol](https://discord.gg/BdujK2) Discussion and support channel for Tour de SOL participants.
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "solana-faucet"
|
||||
version = "1.0.6"
|
||||
version = "1.0.9"
|
||||
description = "Solana Faucet"
|
||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
@ -19,10 +19,10 @@ clap = "2.33"
|
||||
log = "0.4.8"
|
||||
serde = "1.0.104"
|
||||
serde_derive = "1.0.103"
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.0.6" }
|
||||
solana-logger = { path = "../logger", version = "1.0.6" }
|
||||
solana-metrics = { path = "../metrics", version = "1.0.6" }
|
||||
solana-sdk = { path = "../sdk", version = "1.0.6" }
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.0.9" }
|
||||
solana-logger = { path = "../logger", version = "1.0.9" }
|
||||
solana-metrics = { path = "../metrics", version = "1.0.9" }
|
||||
solana-sdk = { path = "../sdk", version = "1.0.9" }
|
||||
tokio = "0.1"
|
||||
tokio-codec = "0.1"
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "solana-genesis-programs"
|
||||
version = "1.0.6"
|
||||
version = "1.0.9"
|
||||
description = "Solana genesis programs"
|
||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
@ -10,16 +10,16 @@ edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
log = { version = "0.4.8" }
|
||||
solana-bpf-loader-program = { path = "../programs/bpf_loader", version = "1.0.6" }
|
||||
solana-budget-program = { path = "../programs/budget", version = "1.0.6" }
|
||||
solana-config-program = { path = "../programs/config", version = "1.0.6" }
|
||||
solana-exchange-program = { path = "../programs/exchange", version = "1.0.6" }
|
||||
solana-runtime = { path = "../runtime", version = "1.0.6" }
|
||||
solana-sdk = { path = "../sdk", version = "1.0.6" }
|
||||
solana-stake-program = { path = "../programs/stake", version = "1.0.6" }
|
||||
solana-storage-program = { path = "../programs/storage", version = "1.0.6" }
|
||||
solana-vest-program = { path = "../programs/vest", version = "1.0.6" }
|
||||
solana-vote-program = { path = "../programs/vote", version = "1.0.6" }
|
||||
solana-bpf-loader-program = { path = "../programs/bpf_loader", version = "1.0.9" }
|
||||
solana-budget-program = { path = "../programs/budget", version = "1.0.9" }
|
||||
solana-config-program = { path = "../programs/config", version = "1.0.9" }
|
||||
solana-exchange-program = { path = "../programs/exchange", version = "1.0.9" }
|
||||
solana-runtime = { path = "../runtime", version = "1.0.9" }
|
||||
solana-sdk = { path = "../sdk", version = "1.0.9" }
|
||||
solana-stake-program = { path = "../programs/stake", version = "1.0.9" }
|
||||
solana-storage-program = { path = "../programs/storage", version = "1.0.9" }
|
||||
solana-vest-program = { path = "../programs/vest", version = "1.0.9" }
|
||||
solana-vote-program = { path = "../programs/vote", version = "1.0.9" }
|
||||
|
||||
[lib]
|
||||
crate-type = ["lib"]
|
||||
|
@ -3,7 +3,7 @@ authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
edition = "2018"
|
||||
name = "solana-genesis"
|
||||
description = "Blockchain, Rebuilt for Scale"
|
||||
version = "1.0.6"
|
||||
version = "1.0.9"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
@ -15,13 +15,13 @@ chrono = "0.4"
|
||||
serde = "1.0.104"
|
||||
serde_json = "1.0.46"
|
||||
serde_yaml = "0.8.11"
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.0.6" }
|
||||
solana-genesis-programs = { path = "../genesis-programs", version = "1.0.6" }
|
||||
solana-ledger = { path = "../ledger", version = "1.0.6" }
|
||||
solana-sdk = { path = "../sdk", version = "1.0.6" }
|
||||
solana-stake-program = { path = "../programs/stake", version = "1.0.6" }
|
||||
solana-storage-program = { path = "../programs/storage", version = "1.0.6" }
|
||||
solana-vote-program = { path = "../programs/vote", version = "1.0.6" }
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.0.9" }
|
||||
solana-genesis-programs = { path = "../genesis-programs", version = "1.0.9" }
|
||||
solana-ledger = { path = "../ledger", version = "1.0.9" }
|
||||
solana-sdk = { path = "../sdk", version = "1.0.9" }
|
||||
solana-stake-program = { path = "../programs/stake", version = "1.0.9" }
|
||||
solana-storage-program = { path = "../programs/storage", version = "1.0.9" }
|
||||
solana-vote-program = { path = "../programs/vote", version = "1.0.9" }
|
||||
tempfile = "3.1.0"
|
||||
|
||||
[[bin]]
|
||||
|
@ -181,7 +181,7 @@ fn main() -> Result<(), Box<dyn error::Error>> {
|
||||
.validator(is_pubkey_or_keypair)
|
||||
.help(
|
||||
"Path to file containing the pubkey authorized to manage the bootstrap \
|
||||
validator's stake [default: --bootstrap-validator-pubkey]",
|
||||
validator's stake [default: --bootstrap-validator IDENTITY_PUBKEY]",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
@ -467,8 +467,9 @@ fn main() -> Result<(), Box<dyn error::Error>> {
|
||||
Account::new(bootstrap_validator_lamports, 0, &system_program::id()),
|
||||
);
|
||||
|
||||
let vote_account = vote_state::create_account(
|
||||
&vote_pubkey,
|
||||
let vote_account = vote_state::create_account_with_authorized(
|
||||
&identity_pubkey,
|
||||
&identity_pubkey,
|
||||
&identity_pubkey,
|
||||
100,
|
||||
VoteState::get_rent_exempt_reserve(&rent).max(1),
|
||||
|
@ -3,19 +3,19 @@ authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
edition = "2018"
|
||||
name = "solana-gossip"
|
||||
description = "Blockchain, Rebuilt for Scale"
|
||||
version = "1.0.6"
|
||||
version = "1.0.9"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
|
||||
[dependencies]
|
||||
clap = "2.33.0"
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.0.6" }
|
||||
solana-core = { path = "../core", version = "1.0.6" }
|
||||
solana-client = { path = "../client", version = "1.0.6" }
|
||||
solana-logger = { path = "../logger", version = "1.0.6" }
|
||||
solana-net-utils = { path = "../net-utils", version = "1.0.6" }
|
||||
solana-sdk = { path = "../sdk", version = "1.0.6" }
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.0.9" }
|
||||
solana-core = { path = "../core", version = "1.0.9" }
|
||||
solana-client = { path = "../client", version = "1.0.9" }
|
||||
solana-logger = { path = "../logger", version = "1.0.9" }
|
||||
solana-net-utils = { path = "../net-utils", version = "1.0.9" }
|
||||
solana-sdk = { path = "../sdk", version = "1.0.9" }
|
||||
|
||||
|
||||
|
||||
|
@ -3,7 +3,7 @@ authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
edition = "2018"
|
||||
name = "solana-install"
|
||||
description = "The solana cluster software installer"
|
||||
version = "1.0.6"
|
||||
version = "1.0.9"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
@ -24,11 +24,11 @@ reqwest = { version = "0.10.1", default-features = false, features = ["blocking"
|
||||
serde = "1.0.104"
|
||||
serde_derive = "1.0.103"
|
||||
serde_yaml = "0.8.11"
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.0.6" }
|
||||
solana-client = { path = "../client", version = "1.0.6" }
|
||||
solana-config-program = { path = "../programs/config", version = "1.0.6" }
|
||||
solana-logger = { path = "../logger", version = "1.0.6" }
|
||||
solana-sdk = { path = "../sdk", version = "1.0.6" }
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.0.9" }
|
||||
solana-client = { path = "../client", version = "1.0.9" }
|
||||
solana-config-program = { path = "../programs/config", version = "1.0.9" }
|
||||
solana-logger = { path = "../logger", version = "1.0.9" }
|
||||
solana-sdk = { path = "../sdk", version = "1.0.9" }
|
||||
semver = "0.9.0"
|
||||
tar = "0.4.26"
|
||||
tempdir = "0.3.7"
|
||||
|
@ -1,4 +1,4 @@
|
||||
pub const JSON_RPC_URL: &str = "http://devnet.solana.com:8899";
|
||||
pub const JSON_RPC_URL: &str = "http://devnet.solana.com";
|
||||
|
||||
lazy_static! {
|
||||
pub static ref CONFIG_FILE: Option<String> = {
|
||||
@ -20,12 +20,3 @@ lazy_static! {
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
pub fn update_manifest_pubkey(target: &str) -> Option<&str> {
|
||||
match target {
|
||||
"x86_64-apple-darwin" => Some("GRUP8YUGASLdu2gBwHstFgeVH28qppfuCaTzq5Yo7wRo"), // SOLANA_INSTALL_UPDATE_MANIFEST_KEYPAIR_x86_64_apple_darwin
|
||||
"x86_64-unknown-linux-gnu" => Some("FnKt2ES9iUJkjoprf2rL62xxBAxZLVgyA4SFexPGotFE"), // SOLANA_INSTALL_UPDATE_MANIFEST_KEYPAIR_x86_64_unknown_linux_gnu
|
||||
"x86_64-pc-windows-msvc" => Some("2Lrj5xDCHDmqwCgGwjVqAUUM84vLpj5dReYeoXL9vSXV"), // SOLANA_INSTALL_UPDATE_MANIFEST_KEYPAIR_x86_64_pc_windows_msvc
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
@ -105,21 +105,16 @@ pub fn main() -> Result<(), String> {
|
||||
.long("no-modify-path")
|
||||
.help("Don't configure the PATH environment variable"),
|
||||
)
|
||||
.arg({
|
||||
let arg = Arg::with_name("update_manifest_pubkey")
|
||||
.arg(
|
||||
Arg::with_name("update_manifest_pubkey")
|
||||
.short("p")
|
||||
.long("pubkey")
|
||||
.value_name("PUBKEY")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.validator(is_pubkey)
|
||||
.help("Public key of the update manifest");
|
||||
|
||||
match defaults::update_manifest_pubkey(build_env::TARGET) {
|
||||
Some(default_value) => arg.default_value(default_value),
|
||||
None => arg,
|
||||
}
|
||||
})
|
||||
.help("Public key of the update manifest"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("explicit_release")
|
||||
.value_name("release")
|
||||
@ -308,21 +303,16 @@ pub fn main_init() -> Result<(), String> {
|
||||
.long("no-modify-path")
|
||||
.help("Don't configure the PATH environment variable"),
|
||||
)
|
||||
.arg({
|
||||
let arg = Arg::with_name("update_manifest_pubkey")
|
||||
.arg(
|
||||
Arg::with_name("update_manifest_pubkey")
|
||||
.short("p")
|
||||
.long("pubkey")
|
||||
.value_name("PUBKEY")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.validator(is_pubkey)
|
||||
.help("Public key of the update manifest");
|
||||
|
||||
match defaults::update_manifest_pubkey(build_env::TARGET) {
|
||||
Some(default_value) => arg.default_value(default_value),
|
||||
None => arg,
|
||||
}
|
||||
})
|
||||
.help("Public key of the update manifest"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("explicit_release")
|
||||
.value_name("release")
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "solana-keygen"
|
||||
version = "1.0.6"
|
||||
version = "1.0.9"
|
||||
description = "Solana key generation utility"
|
||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
@ -13,10 +13,10 @@ bs58 = "0.3.0"
|
||||
clap = "2.33"
|
||||
dirs = "2.0.2"
|
||||
num_cpus = "1.12.0"
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.0.6" }
|
||||
solana-cli-config = { path = "../cli-config", version = "1.0.6" }
|
||||
solana-remote-wallet = { path = "../remote-wallet", version = "1.0.6" }
|
||||
solana-sdk = { path = "../sdk", version = "1.0.6" }
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.0.9" }
|
||||
solana-cli-config = { path = "../cli-config", version = "1.0.9" }
|
||||
solana-remote-wallet = { path = "../remote-wallet", version = "1.0.9" }
|
||||
solana-sdk = { path = "../sdk", version = "1.0.9" }
|
||||
tiny-bip39 = "0.7.0"
|
||||
|
||||
[[bin]]
|
||||
|
@ -200,7 +200,7 @@ fn main() -> Result<(), Box<dyn error::Error>> {
|
||||
let arg = Arg::with_name("config_file")
|
||||
.short("C")
|
||||
.long("config")
|
||||
.value_name("PATH")
|
||||
.value_name("FILEPATH")
|
||||
.takes_value(true)
|
||||
.global(true)
|
||||
.help("Configuration file to use");
|
||||
@ -216,7 +216,7 @@ fn main() -> Result<(), Box<dyn error::Error>> {
|
||||
.arg(
|
||||
Arg::with_name("pubkey")
|
||||
.index(1)
|
||||
.value_name("BASE58_PUBKEY")
|
||||
.value_name("PUBKEY")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.help("Public key"),
|
||||
@ -224,9 +224,9 @@ fn main() -> Result<(), Box<dyn error::Error>> {
|
||||
.arg(
|
||||
Arg::with_name("keypair")
|
||||
.index(2)
|
||||
.value_name("PATH")
|
||||
.value_name("KEYPAIR")
|
||||
.takes_value(true)
|
||||
.help("Path to keypair file"),
|
||||
.help("Filepath or URL to a keypair"),
|
||||
)
|
||||
)
|
||||
.subcommand(
|
||||
@ -237,7 +237,7 @@ fn main() -> Result<(), Box<dyn error::Error>> {
|
||||
Arg::with_name("outfile")
|
||||
.short("o")
|
||||
.long("outfile")
|
||||
.value_name("PATH")
|
||||
.value_name("FILEPATH")
|
||||
.takes_value(true)
|
||||
.help("Path to generated file"),
|
||||
)
|
||||
@ -252,7 +252,7 @@ fn main() -> Result<(), Box<dyn error::Error>> {
|
||||
.long("word-count")
|
||||
.possible_values(&["12", "15", "18", "21", "24"])
|
||||
.default_value("12")
|
||||
.value_name("NUM")
|
||||
.value_name("NUMBER")
|
||||
.takes_value(true)
|
||||
.help("Specify the number of words that will be present in the generated seed phrase"),
|
||||
)
|
||||
@ -321,9 +321,9 @@ fn main() -> Result<(), Box<dyn error::Error>> {
|
||||
.arg(
|
||||
Arg::with_name("keypair")
|
||||
.index(1)
|
||||
.value_name("PATH")
|
||||
.value_name("KEYPAIR")
|
||||
.takes_value(true)
|
||||
.help("Path to keypair file or remote wallet"),
|
||||
.help("Filepath or URL to a keypair"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(SKIP_SEED_PHRASE_VALIDATION_ARG.name)
|
||||
@ -334,7 +334,7 @@ fn main() -> Result<(), Box<dyn error::Error>> {
|
||||
Arg::with_name("outfile")
|
||||
.short("o")
|
||||
.long("outfile")
|
||||
.value_name("PATH")
|
||||
.value_name("FILEPATH")
|
||||
.takes_value(true)
|
||||
.help("Path to generated file"),
|
||||
)
|
||||
@ -361,7 +361,7 @@ fn main() -> Result<(), Box<dyn error::Error>> {
|
||||
Arg::with_name("outfile")
|
||||
.short("o")
|
||||
.long("outfile")
|
||||
.value_name("PATH")
|
||||
.value_name("FILEPATH")
|
||||
.takes_value(true)
|
||||
.help("Path to generated file"),
|
||||
)
|
||||
|
@ -3,7 +3,7 @@ authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
edition = "2018"
|
||||
name = "solana-ledger-tool"
|
||||
description = "Blockchain, Rebuilt for Scale"
|
||||
version = "1.0.6"
|
||||
version = "1.0.9"
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
license = "Apache-2.0"
|
||||
homepage = "https://solana.com/"
|
||||
@ -14,12 +14,12 @@ clap = "2.33.0"
|
||||
histogram = "*"
|
||||
serde_json = "1.0.46"
|
||||
serde_yaml = "0.8.11"
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.0.6" }
|
||||
solana-ledger = { path = "../ledger", version = "1.0.6" }
|
||||
solana-logger = { path = "../logger", version = "1.0.6" }
|
||||
solana-runtime = { path = "../runtime", version = "1.0.6" }
|
||||
solana-sdk = { path = "../sdk", version = "1.0.6" }
|
||||
solana-vote-program = { path = "../programs/vote", version = "1.0.6" }
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.0.9" }
|
||||
solana-ledger = { path = "../ledger", version = "1.0.9" }
|
||||
solana-logger = { path = "../logger", version = "1.0.9" }
|
||||
solana-runtime = { path = "../runtime", version = "1.0.9" }
|
||||
solana-sdk = { path = "../sdk", version = "1.0.9" }
|
||||
solana-vote-program = { path = "../programs/vote", version = "1.0.9" }
|
||||
tempfile = "3.1.0"
|
||||
|
||||
[dev-dependencies]
|
||||
|
@ -35,14 +35,15 @@ enum LedgerOutputMethod {
|
||||
Json,
|
||||
}
|
||||
|
||||
fn output_slot(blockstore: &Blockstore, slot: Slot, method: &LedgerOutputMethod) {
|
||||
fn output_slot(
|
||||
blockstore: &Blockstore,
|
||||
slot: Slot,
|
||||
method: &LedgerOutputMethod,
|
||||
) -> Result<(), String> {
|
||||
println!("Slot Meta {:?}", blockstore.meta(slot));
|
||||
let entries = blockstore
|
||||
.get_slot_entries(slot, 0, None)
|
||||
.unwrap_or_else(|err| {
|
||||
eprintln!("Failed to load entries for slot {}: {:?}", slot, err);
|
||||
exit(1);
|
||||
});
|
||||
.map_err(|err| format!("Failed to load entries for slot {}: {}", slot, err))?;
|
||||
|
||||
for (entry_index, entry) in entries.iter().enumerate() {
|
||||
match method {
|
||||
@ -115,6 +116,7 @@ fn output_slot(blockstore: &Blockstore, slot: Slot, method: &LedgerOutputMethod)
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn output_ledger(blockstore: Blockstore, starting_slot: Slot, method: LedgerOutputMethod) {
|
||||
@ -140,7 +142,9 @@ fn output_ledger(blockstore: Blockstore, starting_slot: Slot, method: LedgerOutp
|
||||
}
|
||||
}
|
||||
|
||||
output_slot(&blockstore, slot, &method);
|
||||
if let Err(err) = output_slot(&blockstore, slot, &method) {
|
||||
eprintln!("{}", err);
|
||||
}
|
||||
}
|
||||
|
||||
if method == LedgerOutputMethod::Json {
|
||||
@ -616,7 +620,21 @@ fn main() {
|
||||
.takes_value(true)
|
||||
.multiple(true)
|
||||
.required(true)
|
||||
.help("List of slots to print"),
|
||||
.help("Slots to print"),
|
||||
)
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("set-dead-slot")
|
||||
.about("Mark one or more slots dead")
|
||||
.arg(
|
||||
Arg::with_name("slots")
|
||||
.index(1)
|
||||
.value_name("SLOTS")
|
||||
.validator(is_slot)
|
||||
.takes_value(true)
|
||||
.multiple(true)
|
||||
.required(true)
|
||||
.help("Slots to mark dead"),
|
||||
)
|
||||
)
|
||||
.subcommand(
|
||||
@ -824,13 +842,12 @@ fn main() {
|
||||
}
|
||||
("slot", Some(arg_matches)) => {
|
||||
let slots = values_t_or_exit!(arg_matches, "slots", Slot);
|
||||
let blockstore = open_blockstore(&ledger_path);
|
||||
for slot in slots {
|
||||
println!("Slot {}", slot);
|
||||
output_slot(
|
||||
&open_blockstore(&ledger_path),
|
||||
slot,
|
||||
&LedgerOutputMethod::Print,
|
||||
);
|
||||
if let Err(err) = output_slot(&blockstore, slot, &LedgerOutputMethod::Print) {
|
||||
eprintln!("{}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
("json", Some(arg_matches)) => {
|
||||
@ -841,6 +858,16 @@ fn main() {
|
||||
LedgerOutputMethod::Json,
|
||||
);
|
||||
}
|
||||
("set-dead-slot", Some(arg_matches)) => {
|
||||
let slots = values_t_or_exit!(arg_matches, "slots", Slot);
|
||||
let blockstore = open_blockstore(&ledger_path);
|
||||
for slot in slots {
|
||||
match blockstore.set_dead_slot(slot) {
|
||||
Ok(_) => println!("Slot {} dead", slot),
|
||||
Err(err) => eprintln!("Failed to set slot {} dead slot: {}", slot, err),
|
||||
}
|
||||
}
|
||||
}
|
||||
("verify", Some(arg_matches)) => {
|
||||
let process_options = ProcessOptions {
|
||||
dev_halt_at_slot: value_t!(arg_matches, "halt_at_slot", Slot).ok(),
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user