Compare commits
90 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
8778ecaed5 | ||
|
a02542ada3 | ||
|
ea17c6883f | ||
|
706306645b | ||
|
da9e930788 | ||
|
8b8e066bbe | ||
|
3473350b62 | ||
|
59d7eb5216 | ||
|
55ba934137 | ||
|
4c3dcb7f7e | ||
|
3a879db8af | ||
|
d2107270ea | ||
|
007afe22d0 | ||
|
93a1d10e15 | ||
|
d57a7c8f21 | ||
|
6db39829c8 | ||
|
ec76826493 | ||
|
d4ddb6265b | ||
|
7a8233d7ca | ||
|
95bc051129 | ||
|
02bcf4f8e2 | ||
|
4b0d4e9834 | ||
|
bf4cdc091a | ||
|
5f2cf2b44d | ||
|
03aae5eb5f | ||
|
5f1ce81fbc | ||
|
fc582aa57c | ||
|
13676e9614 | ||
|
7636a0521f | ||
|
ad06354a18 | ||
|
953cb93e44 | ||
|
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 |
5
.buildkite/env/secrets.ejson
vendored
5
.buildkite/env/secrets.ejson
vendored
@@ -7,9 +7,6 @@
|
||||
"GITHUB_TOKEN": "EJ[1:yGpTmjdbyjW2kjgIHkFoJv7Ue7EbUvUbqHyw6anGgWg=:Vq2dkGTOzfEpRht0BAGHFp/hDogMvXJe:tFXHg1epVt2mq9hkuc5sRHe+KAnVREi/p8S+IZu67XRyzdiA/nGak1k860FXYuuzuaE0QWekaEc=]",
|
||||
"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=]"
|
||||
"INFLUX_USERNAME": "EJ[1:yGpTmjdbyjW2kjgIHkFoJv7Ue7EbUvUbqHyw6anGgWg=:R7BNmQjfeqoGDAFTJu9bYTGHol2NgnYN:Q2tOT/EBcFvhFk+DKLKmVU7tLCpVC3Ui]"
|
||||
}
|
||||
}
|
||||
|
@@ -1,5 +1,6 @@
|
||||
os:
|
||||
- osx
|
||||
- windows
|
||||
|
||||
language: rust
|
||||
rust:
|
||||
|
901
Cargo.lock
generated
901
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -49,6 +49,7 @@ members = [
|
||||
"sdk-c",
|
||||
"scripts",
|
||||
"sys-tuner",
|
||||
"transaction-status",
|
||||
"upload-perf",
|
||||
"net-utils",
|
||||
"vote-signer",
|
||||
|
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "solana-archiver-lib"
|
||||
version = "1.0.6"
|
||||
version = "1.0.12"
|
||||
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.12" }
|
||||
solana-storage-program = { path = "../programs/storage", version = "1.0.12" }
|
||||
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.12" }
|
||||
solana-chacha = { path = "../chacha", version = "1.0.12" }
|
||||
solana-chacha-sys = { path = "../chacha-sys", version = "1.0.12" }
|
||||
solana-ledger = { path = "../ledger", version = "1.0.12" }
|
||||
solana-logger = { path = "../logger", version = "1.0.12" }
|
||||
solana-perf = { path = "../perf", version = "1.0.12" }
|
||||
solana-sdk = { path = "../sdk", version = "1.0.12" }
|
||||
solana-core = { path = "../core", version = "1.0.12" }
|
||||
solana-archiver-utils = { path = "../archiver-utils", version = "1.0.12" }
|
||||
solana-metrics = { path = "../metrics", version = "1.0.12" }
|
||||
|
||||
[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.12"
|
||||
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.12" }
|
||||
solana-chacha-sys = { path = "../chacha-sys", version = "1.0.12" }
|
||||
solana-ledger = { path = "../ledger", version = "1.0.12" }
|
||||
solana-logger = { path = "../logger", version = "1.0.12" }
|
||||
solana-perf = { path = "../perf", version = "1.0.12" }
|
||||
solana-sdk = { path = "../sdk", version = "1.0.12" }
|
||||
|
||||
[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.12"
|
||||
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.12" }
|
||||
solana-core = { path = "../core", version = "1.0.12" }
|
||||
solana-logger = { path = "../logger", version = "1.0.12" }
|
||||
solana-metrics = { path = "../metrics", version = "1.0.12" }
|
||||
solana-archiver-lib = { path = "../archiver-lib", version = "1.0.12" }
|
||||
solana-net-utils = { path = "../net-utils", version = "1.0.12" }
|
||||
solana-sdk = { path = "../sdk", version = "1.0.12" }
|
||||
|
||||
|
@@ -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.12"
|
||||
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.12" }
|
||||
solana-ledger = { path = "../ledger", version = "1.0.12" }
|
||||
solana-logger = { path = "../logger", version = "1.0.12" }
|
||||
solana-runtime = { path = "../runtime", version = "1.0.12" }
|
||||
solana-measure = { path = "../measure", version = "1.0.12" }
|
||||
solana-sdk = { path = "../sdk", version = "1.0.12" }
|
||||
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.12"
|
||||
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.12" }
|
||||
solana-core = { path = "../core", version = "1.0.12" }
|
||||
solana-genesis = { path = "../genesis", version = "1.0.12" }
|
||||
solana-client = { path = "../client", version = "1.0.12" }
|
||||
solana-faucet = { path = "../faucet", version = "1.0.12" }
|
||||
solana-exchange-program = { path = "../programs/exchange", version = "1.0.12" }
|
||||
solana-logger = { path = "../logger", version = "1.0.12" }
|
||||
solana-metrics = { path = "../metrics", version = "1.0.12" }
|
||||
solana-net-utils = { path = "../net-utils", version = "1.0.12" }
|
||||
solana-runtime = { path = "../runtime", version = "1.0.12" }
|
||||
solana-sdk = { path = "../sdk", version = "1.0.12" }
|
||||
|
||||
[dev-dependencies]
|
||||
solana-local-cluster = { path = "../local-cluster", version = "1.0.6" }
|
||||
solana-local-cluster = { path = "../local-cluster", version = "1.0.12" }
|
||||
|
@@ -2,14 +2,14 @@
|
||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
edition = "2018"
|
||||
name = "solana-bench-streamer"
|
||||
version = "1.0.6"
|
||||
version = "1.0.12"
|
||||
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.12" }
|
||||
solana-core = { path = "../core", version = "1.0.12" }
|
||||
solana-logger = { path = "../logger", version = "1.0.12" }
|
||||
solana-net-utils = { path = "../net-utils", version = "1.0.12" }
|
||||
|
@@ -2,7 +2,7 @@
|
||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
edition = "2018"
|
||||
name = "solana-bench-tps"
|
||||
version = "1.0.6"
|
||||
version = "1.0.12"
|
||||
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.12" }
|
||||
solana-core = { path = "../core", version = "1.0.12" }
|
||||
solana-genesis = { path = "../genesis", version = "1.0.12" }
|
||||
solana-client = { path = "../client", version = "1.0.12" }
|
||||
solana-faucet = { path = "../faucet", version = "1.0.12" }
|
||||
solana-librapay = { path = "../programs/librapay", version = "1.0.12", optional = true }
|
||||
solana-logger = { path = "../logger", version = "1.0.12" }
|
||||
solana-metrics = { path = "../metrics", version = "1.0.12" }
|
||||
solana-measure = { path = "../measure", version = "1.0.12" }
|
||||
solana-net-utils = { path = "../net-utils", version = "1.0.12" }
|
||||
solana-runtime = { path = "../runtime", version = "1.0.12" }
|
||||
solana-sdk = { path = "../sdk", version = "1.0.12" }
|
||||
solana-move-loader-program = { path = "../programs/move_loader", version = "1.0.12", 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.12" }
|
||||
|
||||
[features]
|
||||
move = ["solana-librapay", "solana-move-loader-program"]
|
||||
|
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "solana-chacha-cuda"
|
||||
version = "1.0.6"
|
||||
version = "1.0.12"
|
||||
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.12" }
|
||||
solana-chacha = { path = "../chacha", version = "1.0.12" }
|
||||
solana-ledger = { path = "../ledger", version = "1.0.12" }
|
||||
solana-logger = { path = "../logger", version = "1.0.12" }
|
||||
solana-perf = { path = "../perf", version = "1.0.12" }
|
||||
solana-sdk = { path = "../sdk", version = "1.0.12" }
|
||||
|
||||
[dev-dependencies]
|
||||
hex-literal = "0.2.1"
|
||||
|
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "solana-chacha-sys"
|
||||
version = "1.0.6"
|
||||
version = "1.0.12"
|
||||
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.12"
|
||||
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.12" }
|
||||
solana-ledger = { path = "../ledger", version = "1.0.12" }
|
||||
solana-logger = { path = "../logger", version = "1.0.12" }
|
||||
solana-perf = { path = "../perf", version = "1.0.12" }
|
||||
solana-sdk = { path = "../sdk", version = "1.0.12" }
|
||||
|
||||
[dev-dependencies]
|
||||
hex-literal = "0.2.1"
|
||||
|
8
ci/_
8
ci/_
@@ -5,7 +5,13 @@
|
||||
# |source| me
|
||||
#
|
||||
|
||||
base_dir=$(realpath --strip "$(dirname "$0")/..")
|
||||
|
||||
_() {
|
||||
echo "--- $*"
|
||||
if [[ $(pwd) = $base_dir ]]; then
|
||||
echo "--- $*"
|
||||
else
|
||||
echo "--- $* (wd: $(pwd))"
|
||||
fi
|
||||
"$@"
|
||||
}
|
||||
|
@@ -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.12"
|
||||
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.12" }
|
||||
solana-sdk = { path = "../sdk", version = "1.0.12" }
|
||||
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.12"
|
||||
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.12"
|
||||
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.12" }
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.0.12" }
|
||||
solana-cli-config = { path = "../cli-config", version = "1.0.12" }
|
||||
solana-client = { path = "../client", version = "1.0.12" }
|
||||
solana-config-program = { path = "../programs/config", version = "1.0.12" }
|
||||
solana-faucet = { path = "../faucet", version = "1.0.12" }
|
||||
solana-logger = { path = "../logger", version = "1.0.12" }
|
||||
solana-net-utils = { path = "../net-utils", version = "1.0.12" }
|
||||
solana-remote-wallet = { path = "../remote-wallet", version = "1.0.12" }
|
||||
solana-runtime = { path = "../runtime", version = "1.0.12" }
|
||||
solana-sdk = { path = "../sdk", version = "1.0.12" }
|
||||
solana-stake-program = { path = "../programs/stake", version = "1.0.12" }
|
||||
solana-storage-program = { path = "../programs/storage", version = "1.0.12" }
|
||||
solana-vote-program = { path = "../programs/vote", version = "1.0.12" }
|
||||
solana-vote-signer = { path = "../vote-signer", version = "1.0.12" }
|
||||
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.12" }
|
||||
solana-budget-program = { path = "../programs/budget", version = "1.0.12" }
|
||||
tempfile = "3.1.0"
|
||||
|
||||
[[bin]]
|
||||
|
373
cli/src/cli.rs
373
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(),
|
||||
};
|
||||
|
||||
@@ -1156,6 +1181,7 @@ fn process_show_account(
|
||||
);
|
||||
println_name_value("Owner:", &account.owner.to_string());
|
||||
println_name_value("Executable:", &account.executable.to_string());
|
||||
println_name_value("Rent Epoch:", &account.rent_epoch.to_string());
|
||||
|
||||
if let Some(output_file) = output_file {
|
||||
let mut f = File::create(output_file)?;
|
||||
@@ -1231,7 +1257,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 +1267,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 +1316,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 +1332,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 +1357,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 +1403,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 +1447,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 +1471,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 +1514,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 +1530,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 +1553,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 +1815,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 +1825,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 +1928,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 +1936,7 @@ pub fn process_command(config: &CliConfig) -> ProcessResult {
|
||||
&rpc_client,
|
||||
config,
|
||||
seed,
|
||||
&node_pubkey,
|
||||
*identity_account,
|
||||
authorized_voter,
|
||||
authorized_withdrawer,
|
||||
*commission,
|
||||
@@ -1917,6 +1952,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 +1978,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 +2046,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 +2154,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 +2170,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 +2226,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 +2250,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 +2262,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 +2280,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 +2293,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 +2318,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 +2337,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 +2349,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 +2399,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 +2439,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 +2467,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 +2486,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 +2502,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 +2534,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 +2740,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 +2793,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 +3184,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 +3206,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 +3428,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 +3448,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 +3666,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.12"
|
||||
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,10 @@ 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.12" }
|
||||
solana-sdk = { path = "../sdk", version = "1.0.12" }
|
||||
solana-transaction-status = { path = "../transaction-status", version = "1.0.12" }
|
||||
solana-vote-program = { path = "../programs/vote", version = "1.0.12" }
|
||||
thiserror = "1.0"
|
||||
tungstenite = "0.10.1"
|
||||
url = "2.1.1"
|
||||
@@ -28,4 +31,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.12" }
|
||||
|
@@ -10,6 +10,7 @@ use solana_sdk::{
|
||||
instruction::InstructionError,
|
||||
transaction::{self, TransactionError},
|
||||
};
|
||||
use solana_transaction_status::TransactionStatus;
|
||||
use std::{collections::HashMap, sync::RwLock};
|
||||
|
||||
pub const PUBKEY: &str = "7RoSF9fUmdphVCpabEoefH81WwrW7orsWonXWqTXkKV8";
|
||||
@@ -87,19 +88,29 @@ 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(TransactionStatus {
|
||||
status,
|
||||
slot: 1,
|
||||
confirmations: Some(0),
|
||||
})
|
||||
};
|
||||
serde_json::to_value(response).unwrap()
|
||||
serde_json::to_value(Response {
|
||||
context: RpcResponseContext { slot: 1 },
|
||||
value: vec![status],
|
||||
})?
|
||||
}
|
||||
RpcRequest::GetTransactionCount => Value::Number(Number::from(1234)),
|
||||
RpcRequest::GetSlot => Value::Number(Number::from(0)),
|
||||
|
@@ -1,16 +1,17 @@
|
||||
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,
|
||||
rpc_request::{RpcError, RpcRequest},
|
||||
rpc_response::{
|
||||
Response, RpcAccount, RpcBlockhashFeeCalculator, RpcConfirmedBlock, RpcContactInfo,
|
||||
RpcEpochInfo, RpcFeeCalculator, RpcFeeRateGovernor, RpcIdentity, RpcKeyedAccount,
|
||||
RpcLeaderSchedule, RpcResult, RpcVersionInfo, RpcVoteAccountStatus,
|
||||
Response, RpcAccount, RpcBlockhashFeeCalculator, RpcContactInfo, RpcEpochInfo,
|
||||
RpcFeeCalculator, RpcFeeRateGovernor, RpcIdentity, RpcKeyedAccount, RpcLeaderSchedule,
|
||||
RpcResult, RpcVersionInfo, RpcVoteAccountStatus,
|
||||
},
|
||||
};
|
||||
use bincode::serialize;
|
||||
use indicatif::{ProgressBar, ProgressStyle};
|
||||
use log::*;
|
||||
use serde_json::{json, Value};
|
||||
use solana_sdk::{
|
||||
@@ -26,9 +27,12 @@ use solana_sdk::{
|
||||
signers::Signers,
|
||||
transaction::{self, Transaction, TransactionError},
|
||||
};
|
||||
use solana_transaction_status::{ConfirmedBlock, TransactionStatus};
|
||||
use solana_vote_program::vote_state::MAX_LOCKOUT_HISTORY;
|
||||
use std::{
|
||||
error,
|
||||
net::SocketAddr,
|
||||
str::FromStr,
|
||||
thread::sleep,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
@@ -117,12 +121,14 @@ 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: Response<Vec<Option<TransactionStatus>>> =
|
||||
serde_json::from_value(signature_status).unwrap();
|
||||
Ok(result)
|
||||
Ok(result.value[0]
|
||||
.clone()
|
||||
.map(|status_meta| status_meta.status))
|
||||
}
|
||||
|
||||
pub fn get_slot(&self) -> ClientResult<Slot> {
|
||||
@@ -169,7 +175,7 @@ impl RpcClient {
|
||||
.map_err(|err| ClientError::new_with_command(err.into(), "GetClusterNodes"))
|
||||
}
|
||||
|
||||
pub fn get_confirmed_block(&self, slot: Slot) -> ClientResult<RpcConfirmedBlock> {
|
||||
pub fn get_confirmed_block(&self, slot: Slot) -> ClientResult<ConfirmedBlock> {
|
||||
let response = self
|
||||
.client
|
||||
.send(&RpcRequest::GetConfirmedBlock, json!([slot]), 0)
|
||||
@@ -919,14 +925,118 @@ impl RpcClient {
|
||||
let response = self
|
||||
.client
|
||||
.send(
|
||||
&RpcRequest::GetNumBlocksSinceSignatureConfirmation,
|
||||
json!([signature.to_string(), CommitmentConfig::recent().ok()]),
|
||||
&RpcRequest::GetSignatureStatus,
|
||||
json!([[signature.to_string()], CommitmentConfig::recent().ok()]),
|
||||
1,
|
||||
)
|
||||
.map_err(|err| err.into_with_command("GetNumBlocksSinceSignatureConfirmation"))?;
|
||||
serde_json::from_value(response).map_err(|err| {
|
||||
ClientError::new_with_command(err.into(), "GetNumBlocksSinceSignatureConfirmation")
|
||||
})
|
||||
.map_err(|err| err.into_with_command("GetSignatureStatus"))?;
|
||||
let result: Response<Vec<Option<TransactionStatus>>> =
|
||||
serde_json::from_value(response).unwrap();
|
||||
|
||||
let confirmations = result.value[0]
|
||||
.clone()
|
||||
.ok_or_else(|| {
|
||||
ClientError::new_with_command(
|
||||
ClientErrorKind::Custom("signature not found".to_string()),
|
||||
"GetSignatureStatus",
|
||||
)
|
||||
})?
|
||||
.confirmations
|
||||
.unwrap_or(MAX_LOCKOUT_HISTORY + 1);
|
||||
Ok(confirmations)
|
||||
}
|
||||
|
||||
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> {
|
||||
@@ -944,6 +1054,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)
|
||||
|
@@ -18,7 +18,6 @@ pub enum RpcRequest {
|
||||
GetIdentity,
|
||||
GetInflation,
|
||||
GetLeaderSchedule,
|
||||
GetNumBlocksSinceSignatureConfirmation,
|
||||
GetProgramAccounts,
|
||||
GetRecentBlockhash,
|
||||
GetFeeCalculatorForBlockhash,
|
||||
@@ -60,9 +59,6 @@ impl RpcRequest {
|
||||
RpcRequest::GetIdentity => "getIdentity",
|
||||
RpcRequest::GetInflation => "getInflation",
|
||||
RpcRequest::GetLeaderSchedule => "getLeaderSchedule",
|
||||
RpcRequest::GetNumBlocksSinceSignatureConfirmation => {
|
||||
"getNumBlocksSinceSignatureConfirmation"
|
||||
}
|
||||
RpcRequest::GetProgramAccounts => "getProgramAccounts",
|
||||
RpcRequest::GetRecentBlockhash => "getRecentBlockhash",
|
||||
RpcRequest::GetFeeCalculatorForBlockhash => "getFeeCalculatorForBlockhash",
|
||||
@@ -95,7 +91,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" */
|
||||
|
@@ -1,12 +1,10 @@
|
||||
use crate::{client_error, rpc_request::RpcError};
|
||||
use bincode::serialize;
|
||||
use solana_sdk::{
|
||||
account::Account,
|
||||
clock::{Epoch, Slot},
|
||||
fee_calculator::{FeeCalculator, FeeRateGovernor},
|
||||
message::MessageHeader,
|
||||
pubkey::Pubkey,
|
||||
transaction::{Result, Transaction},
|
||||
transaction::Result,
|
||||
};
|
||||
use std::{collections::HashMap, net::SocketAddr, str::FromStr};
|
||||
|
||||
@@ -30,119 +28,6 @@ pub struct RpcBlockCommitment<T> {
|
||||
pub total_stake: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct RpcReward {
|
||||
pub pubkey: String,
|
||||
pub lamports: i64,
|
||||
}
|
||||
|
||||
pub type RpcRewards = Vec<RpcReward>;
|
||||
|
||||
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RpcConfirmedBlock {
|
||||
pub previous_blockhash: String,
|
||||
pub blockhash: String,
|
||||
pub parent_slot: Slot,
|
||||
pub transactions: Vec<RpcTransactionWithStatusMeta>,
|
||||
pub rewards: RpcRewards,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RpcTransactionWithStatusMeta {
|
||||
pub transaction: RpcEncodedTransaction,
|
||||
pub meta: Option<RpcTransactionStatus>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum RpcTransactionEncoding {
|
||||
Binary,
|
||||
Json,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase", untagged)]
|
||||
pub enum RpcEncodedTransaction {
|
||||
Binary(String),
|
||||
Json(RpcTransaction),
|
||||
}
|
||||
|
||||
impl RpcEncodedTransaction {
|
||||
pub fn encode(transaction: Transaction, encoding: RpcTransactionEncoding) -> Self {
|
||||
if encoding == RpcTransactionEncoding::Json {
|
||||
RpcEncodedTransaction::Json(RpcTransaction {
|
||||
signatures: transaction
|
||||
.signatures
|
||||
.iter()
|
||||
.map(|sig| sig.to_string())
|
||||
.collect(),
|
||||
message: RpcMessage {
|
||||
header: transaction.message.header,
|
||||
account_keys: transaction
|
||||
.message
|
||||
.account_keys
|
||||
.iter()
|
||||
.map(|pubkey| pubkey.to_string())
|
||||
.collect(),
|
||||
recent_blockhash: transaction.message.recent_blockhash.to_string(),
|
||||
instructions: transaction
|
||||
.message
|
||||
.instructions
|
||||
.iter()
|
||||
.map(|instruction| RpcCompiledInstruction {
|
||||
program_id_index: instruction.program_id_index,
|
||||
accounts: instruction.accounts.clone(),
|
||||
data: bs58::encode(instruction.data.clone()).into_string(),
|
||||
})
|
||||
.collect(),
|
||||
},
|
||||
})
|
||||
} else {
|
||||
RpcEncodedTransaction::Binary(
|
||||
bs58::encode(serialize(&transaction).unwrap()).into_string(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A duplicate representation of a Transaction for pretty JSON serialization
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RpcTransaction {
|
||||
pub signatures: Vec<String>,
|
||||
pub message: RpcMessage,
|
||||
}
|
||||
|
||||
/// A duplicate representation of a Message for pretty JSON serialization
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RpcMessage {
|
||||
pub header: MessageHeader,
|
||||
pub account_keys: Vec<String>,
|
||||
pub recent_blockhash: String,
|
||||
pub instructions: Vec<RpcCompiledInstruction>,
|
||||
}
|
||||
|
||||
/// A duplicate representation of a Message for pretty JSON serialization
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RpcCompiledInstruction {
|
||||
pub program_id_index: u8,
|
||||
pub accounts: Vec<u8>,
|
||||
pub data: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RpcTransactionStatus {
|
||||
pub status: Result<()>,
|
||||
pub fee: u64,
|
||||
pub pre_balances: Vec<u64>,
|
||||
pub post_balances: Vec<u64>,
|
||||
}
|
||||
|
||||
#[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.12"
|
||||
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,34 @@ 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.12" }
|
||||
solana-clap-utils = { path = "../clap-utils", version = "1.0.12" }
|
||||
solana-client = { path = "../client", version = "1.0.12" }
|
||||
solana-transaction-status = { path = "../transaction-status", version = "1.0.12" }
|
||||
solana-faucet = { path = "../faucet", version = "1.0.12" }
|
||||
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.12" }
|
||||
solana-logger = { path = "../logger", version = "1.0.12" }
|
||||
solana-merkle-tree = { path = "../merkle-tree", version = "1.0.12" }
|
||||
solana-metrics = { path = "../metrics", version = "1.0.12" }
|
||||
solana-measure = { path = "../measure", version = "1.0.12" }
|
||||
solana-net-utils = { path = "../net-utils", version = "1.0.12" }
|
||||
solana-chacha-cuda = { path = "../chacha-cuda", version = "1.0.12" }
|
||||
solana-perf = { path = "../perf", version = "1.0.12" }
|
||||
solana-runtime = { path = "../runtime", version = "1.0.12" }
|
||||
solana-sdk = { path = "../sdk", version = "1.0.12" }
|
||||
solana-stake-program = { path = "../programs/stake", version = "1.0.12" }
|
||||
solana-storage-program = { path = "../programs/storage", version = "1.0.12" }
|
||||
solana-vote-program = { path = "../programs/vote", version = "1.0.12" }
|
||||
solana-vote-signer = { path = "../vote-signer", version = "1.0.12" }
|
||||
solana-sys-tuner = { path = "../sys-tuner", version = "1.0.12" }
|
||||
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.12" }
|
||||
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,
|
||||
));
|
||||
}
|
||||
}
|
@@ -1017,7 +1017,6 @@ mod tests {
|
||||
};
|
||||
use crossbeam_channel::unbounded;
|
||||
use itertools::Itertools;
|
||||
use solana_client::rpc_response::{RpcEncodedTransaction, RpcTransactionWithStatusMeta};
|
||||
use solana_ledger::{
|
||||
blockstore::entries_to_test_shreds,
|
||||
entry::{next_entry, Entry, EntrySlice},
|
||||
@@ -1030,6 +1029,7 @@ mod tests {
|
||||
system_transaction,
|
||||
transaction::TransactionError,
|
||||
};
|
||||
use solana_transaction_status::{EncodedTransaction, TransactionWithStatusMeta};
|
||||
use std::{sync::atomic::Ordering, thread::sleep};
|
||||
|
||||
#[test]
|
||||
@@ -1975,10 +1975,10 @@ mod tests {
|
||||
let confirmed_block = blockstore.get_confirmed_block(bank.slot(), None).unwrap();
|
||||
assert_eq!(confirmed_block.transactions.len(), 3);
|
||||
|
||||
for RpcTransactionWithStatusMeta { transaction, meta } in
|
||||
for TransactionWithStatusMeta { transaction, meta } in
|
||||
confirmed_block.transactions.into_iter()
|
||||
{
|
||||
if let RpcEncodedTransaction::Json(transaction) = transaction {
|
||||
if let EncodedTransaction::Json(transaction) = transaction {
|
||||
if transaction.signatures[0] == success_signature.to_string() {
|
||||
assert_eq!(meta.unwrap().status, Ok(()));
|
||||
} else if transaction.signatures[0] == ix_error_signature.to_string() {
|
||||
|
@@ -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));
|
||||
|
@@ -1,6 +1,8 @@
|
||||
//! The `ledger_cleanup_service` drops older ledger data to limit disk space usage
|
||||
|
||||
use solana_ledger::blockstore::Blockstore;
|
||||
use solana_ledger::blockstore_db::Result as BlockstoreResult;
|
||||
use solana_measure::measure::Measure;
|
||||
use solana_metrics::datapoint_debug;
|
||||
use solana_sdk::clock::Slot;
|
||||
use std::string::ToString;
|
||||
@@ -11,13 +13,22 @@ use std::thread;
|
||||
use std::thread::{Builder, JoinHandle};
|
||||
use std::time::Duration;
|
||||
|
||||
// - To try and keep the RocksDB size under 512GB:
|
||||
// Seeing about 1600b/shred, using 2000b/shred for margin, so 250m shreds can be stored in 512gb.
|
||||
// at 5k shreds/slot at 50k tps, this is 500k slots (~5.5 hours).
|
||||
// At idle, 60 shreds/slot this is about 4m slots (18 days)
|
||||
// This is chosen to allow enough time for
|
||||
// - To try and keep the RocksDB size under 512GB at 50k tps (100 slots take ~2GB).
|
||||
// - A validator to download a snapshot from a peer and boot from it
|
||||
// - To make sure that if a validator needs to reboot from its own snapshot, it has enough slots locally
|
||||
// to catch back up to where it was when it stopped
|
||||
pub const DEFAULT_MAX_LEDGER_SLOTS: u64 = 270_000;
|
||||
// Remove a fixed number of slots at a time, it's more efficient than doing it one-by-one
|
||||
pub const DEFAULT_MAX_LEDGER_SHREDS: u64 = 250_000_000;
|
||||
|
||||
// Check for removing slots at this interval so we don't purge too often
|
||||
// and starve other blockstore users.
|
||||
pub const DEFAULT_PURGE_SLOT_INTERVAL: u64 = 512;
|
||||
|
||||
// Remove a limited number of slots at a time, so the operation
|
||||
// does not take too long and block other blockstore users.
|
||||
pub const DEFAULT_PURGE_BATCH_SIZE: u64 = 256;
|
||||
|
||||
pub struct LedgerCleanupService {
|
||||
@@ -36,7 +47,7 @@ impl LedgerCleanupService {
|
||||
max_ledger_slots
|
||||
);
|
||||
let exit = exit.clone();
|
||||
let mut next_purge_batch = max_ledger_slots;
|
||||
let mut last_purge_slot = 0;
|
||||
let t_cleanup = Builder::new()
|
||||
.name("solana-ledger-cleanup".to_string())
|
||||
.spawn(move || loop {
|
||||
@@ -47,7 +58,8 @@ impl LedgerCleanupService {
|
||||
&new_root_receiver,
|
||||
&blockstore,
|
||||
max_ledger_slots,
|
||||
&mut next_purge_batch,
|
||||
&mut last_purge_slot,
|
||||
DEFAULT_PURGE_SLOT_INTERVAL,
|
||||
) {
|
||||
match e {
|
||||
RecvTimeoutError::Disconnected => break,
|
||||
@@ -59,45 +71,123 @@ impl LedgerCleanupService {
|
||||
Self { t_cleanup }
|
||||
}
|
||||
|
||||
fn find_slots_to_clean(
|
||||
blockstore: &Arc<Blockstore>,
|
||||
root: Slot,
|
||||
max_ledger_shreds: u64,
|
||||
) -> (u64, Slot, Slot) {
|
||||
let mut shreds = Vec::new();
|
||||
let mut iterate_time = Measure::start("iterate_time");
|
||||
let mut total_shreds = 0;
|
||||
let mut first_slot = 0;
|
||||
for (i, (slot, meta)) in blockstore.slot_meta_iterator(0).unwrap().enumerate() {
|
||||
if i == 0 {
|
||||
first_slot = slot;
|
||||
debug!("purge: searching from slot: {}", slot);
|
||||
}
|
||||
// Not exact since non-full slots will have holes
|
||||
total_shreds += meta.received;
|
||||
shreds.push((slot, meta.received));
|
||||
if slot > root {
|
||||
break;
|
||||
}
|
||||
}
|
||||
iterate_time.stop();
|
||||
info!(
|
||||
"checking for ledger purge: max_shreds: {} slots: {} total_shreds: {} {}",
|
||||
max_ledger_shreds,
|
||||
shreds.len(),
|
||||
total_shreds,
|
||||
iterate_time
|
||||
);
|
||||
if (total_shreds as u64) < max_ledger_shreds {
|
||||
return (0, 0, 0);
|
||||
}
|
||||
let mut cur_shreds = 0;
|
||||
let mut lowest_slot_to_clean = shreds[0].0;
|
||||
for (slot, num_shreds) in shreds.iter().rev() {
|
||||
cur_shreds += *num_shreds as u64;
|
||||
if cur_shreds > max_ledger_shreds {
|
||||
lowest_slot_to_clean = *slot;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
(cur_shreds, lowest_slot_to_clean, first_slot)
|
||||
}
|
||||
|
||||
fn cleanup_ledger(
|
||||
new_root_receiver: &Receiver<Slot>,
|
||||
blockstore: &Arc<Blockstore>,
|
||||
max_ledger_slots: u64,
|
||||
next_purge_batch: &mut u64,
|
||||
max_ledger_shreds: u64,
|
||||
last_purge_slot: &mut u64,
|
||||
purge_interval: u64,
|
||||
) -> Result<(), RecvTimeoutError> {
|
||||
let disk_utilization_pre = blockstore.storage_size();
|
||||
|
||||
let root = new_root_receiver.recv_timeout(Duration::from_secs(1))?;
|
||||
|
||||
// Notify blockstore of impending purge
|
||||
if root > *next_purge_batch {
|
||||
//cleanup
|
||||
let lowest_slot = root - max_ledger_slots;
|
||||
*blockstore.lowest_cleanup_slot.write().unwrap() = lowest_slot;
|
||||
blockstore.purge_slots(0, Some(lowest_slot));
|
||||
*next_purge_batch += DEFAULT_PURGE_BATCH_SIZE;
|
||||
let mut root = new_root_receiver.recv_timeout(Duration::from_secs(1))?;
|
||||
// Get the newest root
|
||||
while let Ok(new_root) = new_root_receiver.try_recv() {
|
||||
root = new_root;
|
||||
}
|
||||
|
||||
let disk_utilization_post = blockstore.storage_size();
|
||||
|
||||
if let (Ok(disk_utilization_pre), Ok(disk_utilization_post)) =
|
||||
(disk_utilization_pre, disk_utilization_post)
|
||||
{
|
||||
datapoint_debug!(
|
||||
"ledger_disk_utilization",
|
||||
("disk_utilization_pre", disk_utilization_pre as i64, i64),
|
||||
("disk_utilization_post", disk_utilization_post as i64, i64),
|
||||
(
|
||||
"disk_utilization_delta",
|
||||
(disk_utilization_pre as i64 - disk_utilization_post as i64),
|
||||
i64
|
||||
)
|
||||
if root - *last_purge_slot > purge_interval {
|
||||
let disk_utilization_pre = blockstore.storage_size();
|
||||
info!(
|
||||
"purge: new root: {} last_purge: {} purge_interval: {} disk: {:?}",
|
||||
root, last_purge_slot, purge_interval, disk_utilization_pre
|
||||
);
|
||||
*last_purge_slot = root;
|
||||
|
||||
let (num_shreds_to_clean, lowest_slot_to_clean, mut first_slot) =
|
||||
Self::find_slots_to_clean(blockstore, root, max_ledger_shreds);
|
||||
|
||||
if num_shreds_to_clean > 0 {
|
||||
debug!(
|
||||
"cleaning up to: {} shreds: {} first: {}",
|
||||
lowest_slot_to_clean, num_shreds_to_clean, first_slot
|
||||
);
|
||||
loop {
|
||||
let current_lowest =
|
||||
std::cmp::min(lowest_slot_to_clean, first_slot + DEFAULT_PURGE_BATCH_SIZE);
|
||||
|
||||
let mut slot_update_time = Measure::start("slot_update");
|
||||
*blockstore.lowest_cleanup_slot.write().unwrap() = current_lowest;
|
||||
slot_update_time.stop();
|
||||
|
||||
let mut clean_time = Measure::start("ledger_clean");
|
||||
blockstore.purge_slots(first_slot, Some(current_lowest));
|
||||
clean_time.stop();
|
||||
|
||||
debug!(
|
||||
"ledger purge {} -> {}: {} {}",
|
||||
first_slot, current_lowest, slot_update_time, clean_time
|
||||
);
|
||||
first_slot += DEFAULT_PURGE_BATCH_SIZE;
|
||||
if current_lowest == lowest_slot_to_clean {
|
||||
break;
|
||||
}
|
||||
thread::sleep(Duration::from_millis(500));
|
||||
}
|
||||
}
|
||||
|
||||
let disk_utilization_post = blockstore.storage_size();
|
||||
|
||||
Self::report_disk_metrics(disk_utilization_pre, disk_utilization_post);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn report_disk_metrics(pre: BlockstoreResult<u64>, post: BlockstoreResult<u64>) {
|
||||
if let (Ok(pre), Ok(post)) = (pre, post) {
|
||||
datapoint_debug!(
|
||||
"ledger_disk_utilization",
|
||||
("disk_utilization_pre", pre as i64, i64),
|
||||
("disk_utilization_post", post as i64, i64),
|
||||
("disk_utilization_delta", (pre as i64 - post as i64), i64)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn join(self) -> thread::Result<()> {
|
||||
self.t_cleanup.join()
|
||||
}
|
||||
@@ -111,6 +201,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_cleanup() {
|
||||
solana_logger::setup();
|
||||
let blockstore_path = get_tmp_ledger_path!();
|
||||
let blockstore = Blockstore::open(&blockstore_path).unwrap();
|
||||
let (shreds, _) = make_many_slot_entries(0, 50, 5);
|
||||
@@ -118,10 +209,10 @@ mod tests {
|
||||
let blockstore = Arc::new(blockstore);
|
||||
let (sender, receiver) = channel();
|
||||
|
||||
//send a signal to kill slots 0-40
|
||||
let mut next_purge_slot = 0;
|
||||
//send a signal to kill all but 5 shreds, which will be in the newest slots
|
||||
let mut last_purge_slot = 0;
|
||||
sender.send(50).unwrap();
|
||||
LedgerCleanupService::cleanup_ledger(&receiver, &blockstore, 10, &mut next_purge_slot)
|
||||
LedgerCleanupService::cleanup_ledger(&receiver, &blockstore, 5, &mut last_purge_slot, 10)
|
||||
.unwrap();
|
||||
|
||||
//check that 0-40 don't exist
|
||||
@@ -134,6 +225,62 @@ mod tests {
|
||||
Blockstore::destroy(&blockstore_path).expect("Expected successful database destruction");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cleanup_speed() {
|
||||
solana_logger::setup();
|
||||
let blockstore_path = get_tmp_ledger_path!();
|
||||
let mut blockstore = Blockstore::open(&blockstore_path).unwrap();
|
||||
blockstore.set_no_compaction(true);
|
||||
let blockstore = Arc::new(blockstore);
|
||||
let (sender, receiver) = channel();
|
||||
|
||||
let mut first_insert = Measure::start("first_insert");
|
||||
let initial_slots = 50;
|
||||
let initial_entries = 5;
|
||||
let (shreds, _) = make_many_slot_entries(0, initial_slots, initial_entries);
|
||||
blockstore.insert_shreds(shreds, None, false).unwrap();
|
||||
first_insert.stop();
|
||||
info!("{}", first_insert);
|
||||
|
||||
let mut last_purge_slot = 0;
|
||||
let mut slot = initial_slots;
|
||||
let mut num_slots = 6;
|
||||
for _ in 0..5 {
|
||||
let mut insert_time = Measure::start("insert time");
|
||||
let batch_size = 2;
|
||||
let batches = num_slots / batch_size;
|
||||
for i in 0..batches {
|
||||
let (shreds, _) = make_many_slot_entries(slot + i * batch_size, batch_size, 5);
|
||||
blockstore.insert_shreds(shreds, None, false).unwrap();
|
||||
if i % 100 == 0 {
|
||||
info!("inserting..{} of {}", i, batches);
|
||||
}
|
||||
}
|
||||
insert_time.stop();
|
||||
|
||||
let mut time = Measure::start("purge time");
|
||||
sender.send(slot + num_slots).unwrap();
|
||||
LedgerCleanupService::cleanup_ledger(
|
||||
&receiver,
|
||||
&blockstore,
|
||||
initial_slots,
|
||||
&mut last_purge_slot,
|
||||
10,
|
||||
)
|
||||
.unwrap();
|
||||
time.stop();
|
||||
info!(
|
||||
"slot: {} size: {} {} {}",
|
||||
slot, num_slots, insert_time, time
|
||||
);
|
||||
slot += num_slots;
|
||||
num_slots *= 2;
|
||||
}
|
||||
|
||||
drop(blockstore);
|
||||
Blockstore::destroy(&blockstore_path).expect("Expected successful database destruction");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_compaction() {
|
||||
let blockstore_path = get_tmp_ledger_path!();
|
||||
@@ -142,7 +289,7 @@ mod tests {
|
||||
let n = 10_000;
|
||||
let batch_size = 100;
|
||||
let batches = n / batch_size;
|
||||
let max_ledger_slots = 100;
|
||||
let max_ledger_shreds = 100;
|
||||
|
||||
for i in 0..batches {
|
||||
let (shreds, _) = make_many_slot_entries(i * batch_size, batch_size, 1);
|
||||
@@ -158,8 +305,9 @@ mod tests {
|
||||
LedgerCleanupService::cleanup_ledger(
|
||||
&receiver,
|
||||
&blockstore,
|
||||
max_ledger_slots,
|
||||
max_ledger_shreds,
|
||||
&mut next_purge_batch,
|
||||
10,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -170,7 +318,7 @@ mod tests {
|
||||
assert!(u2 < u1, "insufficient compaction! pre={},post={}", u1, u2,);
|
||||
|
||||
// check that early slots don't exist
|
||||
let max_slot = n - max_ledger_slots;
|
||||
let max_slot = n - max_ledger_shreds - 1;
|
||||
blockstore
|
||||
.slot_meta_iterator(0)
|
||||
.unwrap()
|
||||
|
@@ -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,8 +334,9 @@ impl ReplayStage {
|
||||
&root_bank_sender,
|
||||
total_staked,
|
||||
&lockouts_sender,
|
||||
&snapshot_package_sender,
|
||||
&accounts_hash_sender,
|
||||
&latest_root_senders,
|
||||
&subscriptions,
|
||||
)?;
|
||||
}
|
||||
datapoint_debug!(
|
||||
@@ -395,7 +397,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 +569,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,8 +606,9 @@ 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>],
|
||||
subscriptions: &Arc<RpcSubscriptions>,
|
||||
) -> Result<()> {
|
||||
if bank.is_empty() {
|
||||
inc_new_counter_info!("replay_stage-voted_empty_bank", 1);
|
||||
@@ -622,7 +634,8 @@ 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);
|
||||
subscriptions.notify_roots(rooted_slots);
|
||||
latest_root_senders.iter().for_each(|s| {
|
||||
if let Err(e) = s.send(new_root) {
|
||||
trace!("latest root send failed: {:?}", e);
|
||||
@@ -949,12 +962,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());
|
||||
}
|
||||
@@ -1056,9 +1069,7 @@ pub(crate) mod tests {
|
||||
transaction_status_service::TransactionStatusService,
|
||||
};
|
||||
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,
|
||||
@@ -1081,6 +1092,7 @@ pub(crate) mod tests {
|
||||
transaction::TransactionError,
|
||||
};
|
||||
use solana_stake_program::stake_state;
|
||||
use solana_transaction_status::{EncodedTransaction, TransactionWithStatusMeta};
|
||||
use solana_vote_program::{
|
||||
vote_state::{self, Vote, VoteState, VoteStateVersions},
|
||||
vote_transaction,
|
||||
@@ -1890,10 +1902,10 @@ pub(crate) mod tests {
|
||||
let confirmed_block = blockstore.get_confirmed_block(slot, None).unwrap();
|
||||
assert_eq!(confirmed_block.transactions.len(), 3);
|
||||
|
||||
for RpcTransactionWithStatusMeta { transaction, meta } in
|
||||
for TransactionWithStatusMeta { transaction, meta } in
|
||||
confirmed_block.transactions.into_iter()
|
||||
{
|
||||
if let RpcEncodedTransaction::Json(transaction) = transaction {
|
||||
if let EncodedTransaction::Json(transaction) = transaction {
|
||||
if transaction.signatures[0] == signatures[0].to_string() {
|
||||
assert_eq!(meta.unwrap().status, Ok(()));
|
||||
} else if transaction.signatures[0] == signatures[1].to_string() {
|
||||
|
@@ -1,7 +1,7 @@
|
||||
use crossbeam_channel::{Receiver, RecvTimeoutError, Sender};
|
||||
use solana_client::rpc_response::RpcReward;
|
||||
use solana_ledger::blockstore::Blockstore;
|
||||
use solana_sdk::{clock::Slot, pubkey::Pubkey};
|
||||
use solana_transaction_status::Reward;
|
||||
use std::{
|
||||
sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
@@ -49,7 +49,7 @@ impl RewardsRecorderService {
|
||||
let (slot, rewards) = rewards_receiver.recv_timeout(Duration::from_secs(1))?;
|
||||
let rpc_rewards = rewards
|
||||
.into_iter()
|
||||
.map(|(pubkey, lamports)| RpcReward {
|
||||
.map(|(pubkey, lamports)| Reward {
|
||||
pubkey: pubkey.to_string(),
|
||||
lamports,
|
||||
})
|
||||
|
231
core/src/rpc.rs
231
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,8 +22,9 @@ use solana_sdk::{
|
||||
pubkey::Pubkey,
|
||||
signature::Signature,
|
||||
timing::slot_duration_from_slots_per_year,
|
||||
transaction::{self, Transaction},
|
||||
transaction::Transaction,
|
||||
};
|
||||
use solana_transaction_status::{ConfirmedBlock, TransactionEncoding, TransactionStatus};
|
||||
use solana_vote_program::vote_state::{VoteState, MAX_LOCKOUT_HISTORY};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
@@ -51,7 +46,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 +197,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),
|
||||
}
|
||||
}
|
||||
@@ -219,19 +216,6 @@ impl JsonRpcRequestProcessor {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_signature_confirmation_status(
|
||||
&self,
|
||||
signature: Signature,
|
||||
commitment: Option<CommitmentConfig>,
|
||||
) -> Option<RpcSignatureConfirmation> {
|
||||
self.bank(commitment)
|
||||
.get_signature_confirmation_status(&signature)
|
||||
.map(|(confirmations, status)| RpcSignatureConfirmation {
|
||||
confirmations,
|
||||
status,
|
||||
})
|
||||
}
|
||||
|
||||
fn get_slot(&self, commitment: Option<CommitmentConfig>) -> Result<u64> {
|
||||
Ok(self.bank(commitment).slot())
|
||||
}
|
||||
@@ -363,9 +347,9 @@ impl JsonRpcRequestProcessor {
|
||||
pub fn get_confirmed_block(
|
||||
&self,
|
||||
slot: Slot,
|
||||
encoding: Option<RpcTransactionEncoding>,
|
||||
) -> Result<Option<RpcConfirmedBlock>> {
|
||||
if self.config.enable_get_confirmed_block {
|
||||
encoding: Option<TransactionEncoding>,
|
||||
) -> Result<Option<ConfirmedBlock>> {
|
||||
if self.config.enable_rpc_transaction_history {
|
||||
Ok(self.blockstore.get_confirmed_block(slot, encoding).ok())
|
||||
} else {
|
||||
Ok(None)
|
||||
@@ -413,6 +397,39 @@ impl JsonRpcRequestProcessor {
|
||||
.ok()
|
||||
.unwrap_or(None))
|
||||
}
|
||||
|
||||
pub fn get_signature_status(
|
||||
&self,
|
||||
signatures: Vec<Signature>,
|
||||
commitment: Option<CommitmentConfig>,
|
||||
) -> RpcResponse<Vec<Option<TransactionStatus>>> {
|
||||
let mut statuses: Vec<Option<TransactionStatus>> = vec![];
|
||||
|
||||
let bank = self.bank(commitment);
|
||||
|
||||
for signature in signatures {
|
||||
let status = bank.get_signature_confirmation_status(&signature).map(
|
||||
|SignatureConfirmationStatus {
|
||||
slot,
|
||||
status,
|
||||
confirmations,
|
||||
}| TransactionStatus {
|
||||
slot,
|
||||
status,
|
||||
confirmations: if confirmations <= MAX_LOCKOUT_HISTORY {
|
||||
Some(confirmations)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
},
|
||||
);
|
||||
statuses.push(status);
|
||||
}
|
||||
Ok(Response {
|
||||
context: RpcResponseContext { slot: bank.slot() },
|
||||
value: statuses,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn get_tpu_addr(cluster_info: &Arc<RwLock<ClusterInfo>>) -> Result<SocketAddr> {
|
||||
@@ -539,9 +556,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<()>>>;
|
||||
) -> RpcResponse<Vec<Option<TransactionStatus>>>;
|
||||
|
||||
#[rpc(meta, name = "getSlot")]
|
||||
fn get_slot(&self, meta: Self::Metadata, commitment: Option<CommitmentConfig>) -> Result<u64>;
|
||||
@@ -608,22 +625,6 @@ pub trait RpcSol {
|
||||
#[rpc(meta, name = "validatorExit")]
|
||||
fn validator_exit(&self, meta: Self::Metadata) -> Result<bool>;
|
||||
|
||||
#[rpc(meta, name = "getNumBlocksSinceSignatureConfirmation")]
|
||||
fn get_num_blocks_since_signature_confirmation(
|
||||
&self,
|
||||
meta: Self::Metadata,
|
||||
signature_str: String,
|
||||
commitment: Option<CommitmentConfig>,
|
||||
) -> Result<Option<usize>>;
|
||||
|
||||
#[rpc(meta, name = "getSignatureConfirmation")]
|
||||
fn get_signature_confirmation(
|
||||
&self,
|
||||
meta: Self::Metadata,
|
||||
signature_str: String,
|
||||
commitment: Option<CommitmentConfig>,
|
||||
) -> Result<Option<RpcSignatureConfirmation>>;
|
||||
|
||||
#[rpc(meta, name = "getIdentity")]
|
||||
fn get_identity(&self, meta: Self::Metadata) -> Result<RpcIdentity>;
|
||||
|
||||
@@ -638,8 +639,8 @@ pub trait RpcSol {
|
||||
&self,
|
||||
meta: Self::Metadata,
|
||||
slot: Slot,
|
||||
encoding: Option<RpcTransactionEncoding>,
|
||||
) -> Result<Option<RpcConfirmedBlock>>;
|
||||
encoding: Option<TransactionEncoding>,
|
||||
) -> Result<Option<ConfirmedBlock>>;
|
||||
|
||||
#[rpc(meta, name = "getBlockTime")]
|
||||
fn get_block_time(&self, meta: Self::Metadata, slot: Slot) -> Result<Option<UnixTimestamp>>;
|
||||
@@ -885,45 +886,23 @@ 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))
|
||||
) -> RpcResponse<Vec<Option<TransactionStatus>>> {
|
||||
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> {
|
||||
meta.request_processor.read().unwrap().get_slot(commitment)
|
||||
}
|
||||
|
||||
fn get_num_blocks_since_signature_confirmation(
|
||||
&self,
|
||||
meta: Self::Metadata,
|
||||
signature_str: String,
|
||||
commitment: Option<CommitmentConfig>,
|
||||
) -> Result<Option<usize>> {
|
||||
self.get_signature_confirmation(meta, signature_str, commitment)
|
||||
.map(|res| res.map(|x| x.confirmations))
|
||||
}
|
||||
|
||||
fn get_signature_confirmation(
|
||||
&self,
|
||||
meta: Self::Metadata,
|
||||
signature_str: String,
|
||||
commitment: Option<CommitmentConfig>,
|
||||
) -> Result<Option<RpcSignatureConfirmation>> {
|
||||
debug!(
|
||||
"get_signature_confirmation rpc request received: {:?}",
|
||||
signature_str
|
||||
);
|
||||
let signature = verify_signature(&signature_str)?;
|
||||
Ok(meta
|
||||
.request_processor
|
||||
.read()
|
||||
.unwrap()
|
||||
.get_signature_confirmation_status(signature, commitment))
|
||||
}
|
||||
|
||||
fn get_transaction_count(
|
||||
&self,
|
||||
meta: Self::Metadata,
|
||||
@@ -1010,7 +989,9 @@ impl RpcSol for RpcSolImpl {
|
||||
.request_processor
|
||||
.read()
|
||||
.unwrap()
|
||||
.get_signature_confirmation_status(signature, commitment.clone())
|
||||
.get_signature_status(vec![signature], commitment.clone())?
|
||||
.value[0]
|
||||
.clone()
|
||||
.map(|x| x.status);
|
||||
|
||||
if signature_status == Some(Ok(())) {
|
||||
@@ -1152,8 +1133,8 @@ impl RpcSol for RpcSolImpl {
|
||||
&self,
|
||||
meta: Self::Metadata,
|
||||
slot: Slot,
|
||||
encoding: Option<RpcTransactionEncoding>,
|
||||
) -> Result<Option<RpcConfirmedBlock>> {
|
||||
encoding: Option<TransactionEncoding>,
|
||||
) -> Result<Option<ConfirmedBlock>> {
|
||||
meta.request_processor
|
||||
.read()
|
||||
.unwrap()
|
||||
@@ -1188,7 +1169,6 @@ pub mod tests {
|
||||
};
|
||||
use bincode::deserialize;
|
||||
use jsonrpc_core::{MetaIoHandler, Output, Response, Value};
|
||||
use solana_client::rpc_response::{RpcEncodedTransaction, RpcTransactionWithStatusMeta};
|
||||
use solana_ledger::{
|
||||
blockstore::entries_to_test_shreds, blockstore_processor::fill_blockstore_slot_with_ticks,
|
||||
entry::next_entry_mut, get_tmp_ledger_path,
|
||||
@@ -1200,8 +1180,9 @@ pub mod tests {
|
||||
rpc_port,
|
||||
signature::{Keypair, Signer},
|
||||
system_transaction,
|
||||
transaction::TransactionError,
|
||||
transaction::{self, TransactionError},
|
||||
};
|
||||
use solana_transaction_status::{EncodedTransaction, TransactionWithStatusMeta};
|
||||
use solana_vote_program::{
|
||||
vote_instruction,
|
||||
vote_state::{Vote, VoteInit, MAX_LOCKOUT_HISTORY},
|
||||
@@ -1329,7 +1310,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 +1754,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: Option<TransactionStatus> =
|
||||
serde_json::from_value(json["result"]["value"][0].clone())
|
||||
.expect("actual response deserialization");
|
||||
assert_eq!(expected_res, result.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: Option<TransactionStatus> =
|
||||
serde_json::from_value(json["result"]["value"][0].clone())
|
||||
.expect("actual response deserialization");
|
||||
assert!(result.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: Option<TransactionStatus> =
|
||||
serde_json::from_value(json["result"]["value"][0].clone())
|
||||
.expect("actual response deserialization");
|
||||
assert_eq!(expected_res, result.as_ref().unwrap().status);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -2271,15 +2236,15 @@ pub mod tests {
|
||||
let res = io.handle_request_sync(&req, meta.clone());
|
||||
let result: Value = serde_json::from_str(&res.expect("actual response"))
|
||||
.expect("actual response deserialization");
|
||||
let confirmed_block: Option<RpcConfirmedBlock> =
|
||||
let confirmed_block: Option<ConfirmedBlock> =
|
||||
serde_json::from_value(result["result"].clone()).unwrap();
|
||||
let confirmed_block = confirmed_block.unwrap();
|
||||
assert_eq!(confirmed_block.transactions.len(), 3);
|
||||
|
||||
for RpcTransactionWithStatusMeta { transaction, meta } in
|
||||
for TransactionWithStatusMeta { transaction, meta } in
|
||||
confirmed_block.transactions.into_iter()
|
||||
{
|
||||
if let RpcEncodedTransaction::Json(transaction) = transaction {
|
||||
if let EncodedTransaction::Json(transaction) = transaction {
|
||||
if transaction.signatures[0] == confirmed_block_signatures[0].to_string() {
|
||||
assert_eq!(transaction.message.recent_blockhash, blockhash.to_string());
|
||||
assert_eq!(meta.unwrap().status, Ok(()));
|
||||
@@ -2303,15 +2268,15 @@ pub mod tests {
|
||||
let res = io.handle_request_sync(&req, meta);
|
||||
let result: Value = serde_json::from_str(&res.expect("actual response"))
|
||||
.expect("actual response deserialization");
|
||||
let confirmed_block: Option<RpcConfirmedBlock> =
|
||||
let confirmed_block: Option<ConfirmedBlock> =
|
||||
serde_json::from_value(result["result"].clone()).unwrap();
|
||||
let confirmed_block = confirmed_block.unwrap();
|
||||
assert_eq!(confirmed_block.transactions.len(), 3);
|
||||
|
||||
for RpcTransactionWithStatusMeta { transaction, meta } in
|
||||
for TransactionWithStatusMeta { transaction, meta } in
|
||||
confirmed_block.transactions.into_iter()
|
||||
{
|
||||
if let RpcEncodedTransaction::Binary(transaction) = transaction {
|
||||
if let EncodedTransaction::Binary(transaction) = transaction {
|
||||
let decoded_transaction: Transaction =
|
||||
deserialize(&bs58::decode(&transaction).into_vec().unwrap()).unwrap();
|
||||
if decoded_transaction.signatures[0] == confirmed_block_signatures[0] {
|
||||
|
@@ -4,8 +4,8 @@ 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_sdk::{pubkey::Pubkey, signature::Signature, transaction};
|
||||
use solana_client::rpc_response::{Response as RpcResponse, RpcAccount, RpcKeyedAccount};
|
||||
use solana_sdk::{clock::Slot, pubkey::Pubkey, signature::Signature, transaction};
|
||||
use std::sync::{atomic, Arc};
|
||||
|
||||
// Suppress needless_return due to
|
||||
@@ -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>,
|
||||
);
|
||||
@@ -102,6 +102,18 @@ pub trait RpcSolPubSub {
|
||||
name = "slotUnsubscribe"
|
||||
)]
|
||||
fn slot_unsubscribe(&self, meta: Option<Self::Metadata>, id: SubscriptionId) -> Result<bool>;
|
||||
|
||||
// Get notification when a new root is set
|
||||
#[pubsub(subscription = "rootNotification", subscribe, name = "rootSubscribe")]
|
||||
fn root_subscribe(&self, meta: Self::Metadata, subscriber: Subscriber<Slot>);
|
||||
|
||||
// Unsubscribe from slot notification subscription.
|
||||
#[pubsub(
|
||||
subscription = "rootNotification",
|
||||
unsubscribe,
|
||||
name = "rootUnsubscribe"
|
||||
)]
|
||||
fn root_unsubscribe(&self, meta: Option<Self::Metadata>, id: SubscriptionId) -> Result<bool>;
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
@@ -133,7 +145,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>,
|
||||
) {
|
||||
@@ -142,10 +154,12 @@ impl RpcSolPubSub for RpcSolPubSubImpl {
|
||||
let id = self.uid.fetch_add(1, atomic::Ordering::Relaxed);
|
||||
let sub_id = SubscriptionId::Number(id as u64);
|
||||
info!("account_subscribe: account={:?} id={:?}", pubkey, sub_id);
|
||||
let sink = subscriber.assign_id(sub_id.clone()).unwrap();
|
||||
|
||||
self.subscriptions
|
||||
.add_account_subscription(&pubkey, confirmations, &sub_id, &sink)
|
||||
self.subscriptions.add_account_subscription(
|
||||
pubkey,
|
||||
confirmations,
|
||||
sub_id,
|
||||
subscriber,
|
||||
)
|
||||
}
|
||||
Err(e) => subscriber.reject(e).unwrap(),
|
||||
}
|
||||
@@ -171,7 +185,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>,
|
||||
) {
|
||||
@@ -180,10 +194,12 @@ impl RpcSolPubSub for RpcSolPubSubImpl {
|
||||
let id = self.uid.fetch_add(1, atomic::Ordering::Relaxed);
|
||||
let sub_id = SubscriptionId::Number(id as u64);
|
||||
info!("program_subscribe: account={:?} id={:?}", pubkey, sub_id);
|
||||
let sink = subscriber.assign_id(sub_id.clone()).unwrap();
|
||||
|
||||
self.subscriptions
|
||||
.add_program_subscription(&pubkey, confirmations, &sub_id, &sink)
|
||||
self.subscriptions.add_program_subscription(
|
||||
pubkey,
|
||||
confirmations,
|
||||
sub_id,
|
||||
subscriber,
|
||||
)
|
||||
}
|
||||
Err(e) => subscriber.reject(e).unwrap(),
|
||||
}
|
||||
@@ -209,7 +225,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>,
|
||||
) {
|
||||
@@ -222,13 +238,11 @@ impl RpcSolPubSub for RpcSolPubSubImpl {
|
||||
"signature_subscribe: signature={:?} id={:?}",
|
||||
signature, sub_id
|
||||
);
|
||||
let sink = subscriber.assign_id(sub_id.clone()).unwrap();
|
||||
|
||||
self.subscriptions.add_signature_subscription(
|
||||
&signature,
|
||||
signature,
|
||||
confirmations,
|
||||
&sub_id,
|
||||
&sink,
|
||||
sub_id,
|
||||
subscriber,
|
||||
);
|
||||
}
|
||||
Err(e) => subscriber.reject(e).unwrap(),
|
||||
@@ -257,9 +271,7 @@ impl RpcSolPubSub for RpcSolPubSubImpl {
|
||||
let id = self.uid.fetch_add(1, atomic::Ordering::Relaxed);
|
||||
let sub_id = SubscriptionId::Number(id as u64);
|
||||
info!("slot_subscribe: id={:?}", sub_id);
|
||||
let sink = subscriber.assign_id(sub_id.clone()).unwrap();
|
||||
|
||||
self.subscriptions.add_slot_subscription(&sub_id, &sink);
|
||||
self.subscriptions.add_slot_subscription(sub_id, subscriber);
|
||||
}
|
||||
|
||||
fn slot_unsubscribe(&self, _meta: Option<Self::Metadata>, id: SubscriptionId) -> Result<bool> {
|
||||
@@ -274,6 +286,27 @@ impl RpcSolPubSub for RpcSolPubSubImpl {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn root_subscribe(&self, _meta: Self::Metadata, subscriber: Subscriber<Slot>) {
|
||||
info!("root_subscribe");
|
||||
let id = self.uid.fetch_add(1, atomic::Ordering::Relaxed);
|
||||
let sub_id = SubscriptionId::Number(id as u64);
|
||||
info!("root_subscribe: id={:?}", sub_id);
|
||||
self.subscriptions.add_root_subscription(sub_id, subscriber);
|
||||
}
|
||||
|
||||
fn root_unsubscribe(&self, _meta: Option<Self::Metadata>, id: SubscriptionId) -> Result<bool> {
|
||||
info!("root_unsubscribe");
|
||||
if self.subscriptions.remove_root_subscription(&id) {
|
||||
Ok(true)
|
||||
} else {
|
||||
Err(Error {
|
||||
code: ErrorCode::InvalidParams,
|
||||
message: "Invalid Request: Subscription id does not exist".into(),
|
||||
data: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -338,15 +371,20 @@ mod tests {
|
||||
process_transaction_and_notify(&bank_forks, &tx, &rpc.subscriptions).unwrap();
|
||||
|
||||
// Test signature confirmation notification
|
||||
let response = robust_poll_or_panic(receiver);
|
||||
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,17 +500,20 @@ 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,
|
||||
}
|
||||
});
|
||||
|
||||
let response = robust_poll_or_panic(receiver);
|
||||
let (response, _) = robust_poll_or_panic(receiver);
|
||||
assert_eq!(serde_json::to_string(&expected).unwrap(), response);
|
||||
|
||||
let tx = system_transaction::transfer(&alice, &witness.pubkey(), 1, blockhash);
|
||||
@@ -609,16 +650,19 @@ 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,
|
||||
}
|
||||
});
|
||||
let response = robust_poll_or_panic(receiver);
|
||||
let (response, _) = robust_poll_or_panic(receiver);
|
||||
assert_eq!(serde_json::to_string(&expected).unwrap(), response);
|
||||
}
|
||||
|
||||
@@ -631,7 +675,7 @@ mod tests {
|
||||
|
||||
rpc.subscriptions.notify_slot(0, 0, 0);
|
||||
// Test slot confirmation notification
|
||||
let response = robust_poll_or_panic(receiver);
|
||||
let (response, _) = robust_poll_or_panic(receiver);
|
||||
let expected_res = SlotInfo {
|
||||
parent: 0,
|
||||
slot: 0,
|
||||
@@ -653,7 +697,7 @@ mod tests {
|
||||
let (subscriber, _id_receiver, receiver) = Subscriber::new_test("slotNotification");
|
||||
rpc.slot_subscribe(session, subscriber);
|
||||
rpc.subscriptions.notify_slot(0, 0, 0);
|
||||
let response = robust_poll_or_panic(receiver);
|
||||
let (response, _) = robust_poll_or_panic(receiver);
|
||||
let expected_res = SlotInfo {
|
||||
parent: 0,
|
||||
slot: 0,
|
||||
|
@@ -2,9 +2,12 @@
|
||||
|
||||
use core::hash::Hash;
|
||||
use jsonrpc_core::futures::Future;
|
||||
use jsonrpc_pubsub::{typed::Sink, SubscriptionId};
|
||||
use jsonrpc_pubsub::{
|
||||
typed::{Sink, Subscriber},
|
||||
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::{
|
||||
@@ -35,12 +38,14 @@ pub struct SlotInfo {
|
||||
|
||||
enum NotificationEntry {
|
||||
Slot(SlotInfo),
|
||||
Root(Slot),
|
||||
Bank((Slot, Arc<RwLock<BankForks>>)),
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for NotificationEntry {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
match self {
|
||||
NotificationEntry::Root(root) => write!(f, "Root({})", root),
|
||||
NotificationEntry::Slot(slot_info) => write!(f, "Slot({:?})", slot_info),
|
||||
NotificationEntry::Bank((current_slot, _)) => {
|
||||
write!(f, "Bank({{current_slot: {:?}}})", current_slot)
|
||||
@@ -50,37 +55,43 @@ 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>>>;
|
||||
type RpcRootSubscriptions = RwLock<HashMap<SubscriptionId, Sink<Slot>>>;
|
||||
|
||||
fn add_subscription<K, S>(
|
||||
subscriptions: &mut HashMap<K, HashMap<SubscriptionId, (Sink<S>, Confirmations)>>,
|
||||
hashmap_key: &K,
|
||||
hashmap_key: K,
|
||||
confirmations: Option<Confirmations>,
|
||||
sub_id: &SubscriptionId,
|
||||
sink: &Sink<S>,
|
||||
sub_id: SubscriptionId,
|
||||
subscriber: Subscriber<S>,
|
||||
) where
|
||||
K: Eq + Hash + Clone + Copy,
|
||||
K: Eq + Hash,
|
||||
S: Clone,
|
||||
{
|
||||
let sink = subscriber.assign_id(sub_id.clone()).unwrap();
|
||||
let confirmations = confirmations.unwrap_or(0);
|
||||
let confirmations = if confirmations > MAX_LOCKOUT_HISTORY {
|
||||
MAX_LOCKOUT_HISTORY
|
||||
} else {
|
||||
confirmations
|
||||
};
|
||||
if let Some(current_hashmap) = subscriptions.get_mut(hashmap_key) {
|
||||
current_hashmap.insert(sub_id.clone(), (sink.clone(), confirmations));
|
||||
if let Some(current_hashmap) = subscriptions.get_mut(&hashmap_key) {
|
||||
current_hashmap.insert(sub_id, (sink, confirmations));
|
||||
return;
|
||||
}
|
||||
let mut hashmap = HashMap::new();
|
||||
hashmap.insert(sub_id.clone(), (sink.clone(), confirmations));
|
||||
subscriptions.insert(*hashmap_key, hashmap);
|
||||
hashmap.insert(sub_id, (sink, confirmations));
|
||||
subscriptions.insert(hashmap_key, hashmap);
|
||||
}
|
||||
|
||||
fn remove_subscription<K, S>(
|
||||
@@ -88,13 +99,13 @@ fn remove_subscription<K, S>(
|
||||
sub_id: &SubscriptionId,
|
||||
) -> bool
|
||||
where
|
||||
K: Eq + Hash + Clone + Copy,
|
||||
K: Eq + Hash,
|
||||
S: Clone,
|
||||
{
|
||||
let mut found = false;
|
||||
subscriptions.retain(|_, v| {
|
||||
v.retain(|k, _| {
|
||||
let retain = *k != *sub_id;
|
||||
let retain = k != sub_id;
|
||||
if !retain {
|
||||
found = true;
|
||||
}
|
||||
@@ -105,8 +116,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 +143,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 +158,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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -213,6 +227,7 @@ pub struct RpcSubscriptions {
|
||||
program_subscriptions: Arc<RpcProgramSubscriptions>,
|
||||
signature_subscriptions: Arc<RpcSignatureSubscriptions>,
|
||||
slot_subscriptions: Arc<RpcSlotSubscriptions>,
|
||||
root_subscriptions: Arc<RpcRootSubscriptions>,
|
||||
notification_sender: Arc<Mutex<Sender<NotificationEntry>>>,
|
||||
t_cleanup: Option<JoinHandle<()>>,
|
||||
notifier_runtime: Option<Runtime>,
|
||||
@@ -244,6 +259,7 @@ impl RpcSubscriptions {
|
||||
let program_subscriptions = Arc::new(RpcProgramSubscriptions::default());
|
||||
let signature_subscriptions = Arc::new(RpcSignatureSubscriptions::default());
|
||||
let slot_subscriptions = Arc::new(RpcSlotSubscriptions::default());
|
||||
let root_subscriptions = Arc::new(RpcRootSubscriptions::default());
|
||||
let notification_sender = Arc::new(Mutex::new(notification_sender));
|
||||
|
||||
let exit_clone = exit.clone();
|
||||
@@ -251,6 +267,7 @@ impl RpcSubscriptions {
|
||||
let program_subscriptions_clone = program_subscriptions.clone();
|
||||
let signature_subscriptions_clone = signature_subscriptions.clone();
|
||||
let slot_subscriptions_clone = slot_subscriptions.clone();
|
||||
let root_subscriptions_clone = root_subscriptions.clone();
|
||||
|
||||
let notifier_runtime = RuntimeBuilder::new()
|
||||
.core_threads(1)
|
||||
@@ -270,6 +287,7 @@ impl RpcSubscriptions {
|
||||
program_subscriptions_clone,
|
||||
signature_subscriptions_clone,
|
||||
slot_subscriptions_clone,
|
||||
root_subscriptions_clone,
|
||||
);
|
||||
})
|
||||
.unwrap();
|
||||
@@ -279,6 +297,7 @@ impl RpcSubscriptions {
|
||||
program_subscriptions,
|
||||
signature_subscriptions,
|
||||
slot_subscriptions,
|
||||
root_subscriptions,
|
||||
notification_sender,
|
||||
notifier_runtime: Some(notifier_runtime),
|
||||
t_cleanup: Some(t_cleanup),
|
||||
@@ -337,7 +356,7 @@ impl RpcSubscriptions {
|
||||
signature,
|
||||
current_slot,
|
||||
bank_forks,
|
||||
Bank::get_signature_status,
|
||||
Bank::get_signature_status_processed_since_parent,
|
||||
filter_signature_result,
|
||||
notifier,
|
||||
);
|
||||
@@ -351,13 +370,19 @@ impl RpcSubscriptions {
|
||||
|
||||
pub fn add_account_subscription(
|
||||
&self,
|
||||
pubkey: &Pubkey,
|
||||
pubkey: Pubkey,
|
||||
confirmations: Option<Confirmations>,
|
||||
sub_id: &SubscriptionId,
|
||||
sink: &Sink<RpcAccount>,
|
||||
sub_id: SubscriptionId,
|
||||
subscriber: Subscriber<Response<RpcAccount>>,
|
||||
) {
|
||||
let mut subscriptions = self.account_subscriptions.write().unwrap();
|
||||
add_subscription(&mut subscriptions, pubkey, confirmations, sub_id, sink);
|
||||
add_subscription(
|
||||
&mut subscriptions,
|
||||
pubkey,
|
||||
confirmations,
|
||||
sub_id,
|
||||
subscriber,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn remove_account_subscription(&self, id: &SubscriptionId) -> bool {
|
||||
@@ -367,13 +392,19 @@ impl RpcSubscriptions {
|
||||
|
||||
pub fn add_program_subscription(
|
||||
&self,
|
||||
program_id: &Pubkey,
|
||||
program_id: Pubkey,
|
||||
confirmations: Option<Confirmations>,
|
||||
sub_id: &SubscriptionId,
|
||||
sink: &Sink<RpcKeyedAccount>,
|
||||
sub_id: SubscriptionId,
|
||||
subscriber: Subscriber<Response<RpcKeyedAccount>>,
|
||||
) {
|
||||
let mut subscriptions = self.program_subscriptions.write().unwrap();
|
||||
add_subscription(&mut subscriptions, program_id, confirmations, sub_id, sink);
|
||||
add_subscription(
|
||||
&mut subscriptions,
|
||||
program_id,
|
||||
confirmations,
|
||||
sub_id,
|
||||
subscriber,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn remove_program_subscription(&self, id: &SubscriptionId) -> bool {
|
||||
@@ -383,13 +414,19 @@ impl RpcSubscriptions {
|
||||
|
||||
pub fn add_signature_subscription(
|
||||
&self,
|
||||
signature: &Signature,
|
||||
signature: Signature,
|
||||
confirmations: Option<Confirmations>,
|
||||
sub_id: &SubscriptionId,
|
||||
sink: &Sink<transaction::Result<()>>,
|
||||
sub_id: SubscriptionId,
|
||||
subscriber: Subscriber<Response<transaction::Result<()>>>,
|
||||
) {
|
||||
let mut subscriptions = self.signature_subscriptions.write().unwrap();
|
||||
add_subscription(&mut subscriptions, signature, confirmations, sub_id, sink);
|
||||
add_subscription(
|
||||
&mut subscriptions,
|
||||
signature,
|
||||
confirmations,
|
||||
sub_id,
|
||||
subscriber,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn remove_signature_subscription(&self, id: &SubscriptionId) -> bool {
|
||||
@@ -403,9 +440,10 @@ impl RpcSubscriptions {
|
||||
self.enqueue_notification(NotificationEntry::Bank((current_slot, bank_forks.clone())));
|
||||
}
|
||||
|
||||
pub fn add_slot_subscription(&self, sub_id: &SubscriptionId, sink: &Sink<SlotInfo>) {
|
||||
pub fn add_slot_subscription(&self, sub_id: SubscriptionId, subscriber: Subscriber<SlotInfo>) {
|
||||
let sink = subscriber.assign_id(sub_id.clone()).unwrap();
|
||||
let mut subscriptions = self.slot_subscriptions.write().unwrap();
|
||||
subscriptions.insert(sub_id.clone(), sink.clone());
|
||||
subscriptions.insert(sub_id, sink);
|
||||
}
|
||||
|
||||
pub fn remove_slot_subscription(&self, id: &SubscriptionId) -> bool {
|
||||
@@ -417,6 +455,24 @@ impl RpcSubscriptions {
|
||||
self.enqueue_notification(NotificationEntry::Slot(SlotInfo { slot, parent, root }));
|
||||
}
|
||||
|
||||
pub fn add_root_subscription(&self, sub_id: SubscriptionId, subscriber: Subscriber<Slot>) {
|
||||
let sink = subscriber.assign_id(sub_id.clone()).unwrap();
|
||||
let mut subscriptions = self.root_subscriptions.write().unwrap();
|
||||
subscriptions.insert(sub_id, sink);
|
||||
}
|
||||
|
||||
pub fn remove_root_subscription(&self, id: &SubscriptionId) -> bool {
|
||||
let mut subscriptions = self.root_subscriptions.write().unwrap();
|
||||
subscriptions.remove(id).is_some()
|
||||
}
|
||||
|
||||
pub fn notify_roots(&self, mut rooted_slots: Vec<Slot>) {
|
||||
rooted_slots.sort();
|
||||
rooted_slots.into_iter().for_each(|root| {
|
||||
self.enqueue_notification(NotificationEntry::Root(root));
|
||||
});
|
||||
}
|
||||
|
||||
fn enqueue_notification(&self, notification_entry: NotificationEntry) {
|
||||
match self
|
||||
.notification_sender
|
||||
@@ -442,6 +498,7 @@ impl RpcSubscriptions {
|
||||
program_subscriptions: Arc<RpcProgramSubscriptions>,
|
||||
signature_subscriptions: Arc<RpcSignatureSubscriptions>,
|
||||
slot_subscriptions: Arc<RpcSlotSubscriptions>,
|
||||
root_subscriptions: Arc<RpcRootSubscriptions>,
|
||||
) {
|
||||
loop {
|
||||
if exit.load(Ordering::Relaxed) {
|
||||
@@ -455,6 +512,12 @@ impl RpcSubscriptions {
|
||||
notifier.notify(slot_info, sink);
|
||||
}
|
||||
}
|
||||
NotificationEntry::Root(root) => {
|
||||
let subscriptions = root_subscriptions.read().unwrap();
|
||||
for (_, sink) in subscriptions.iter() {
|
||||
notifier.notify(root, sink);
|
||||
}
|
||||
}
|
||||
NotificationEntry::Bank((current_slot, bank_forks)) => {
|
||||
let pubkeys: Vec<_> = {
|
||||
let subs = account_subscriptions.read().unwrap();
|
||||
@@ -534,33 +597,39 @@ impl RpcSubscriptions {
|
||||
pub(crate) mod tests {
|
||||
use super::*;
|
||||
use crate::genesis_utils::{create_genesis_config, GenesisConfigInfo};
|
||||
use jsonrpc_core::futures;
|
||||
use jsonrpc_core::futures::{self, stream::Stream};
|
||||
use jsonrpc_pubsub::typed::Subscriber;
|
||||
use solana_budget_program;
|
||||
use solana_sdk::{
|
||||
signature::{Keypair, Signer},
|
||||
system_transaction,
|
||||
};
|
||||
use tokio::prelude::{Async, Stream};
|
||||
use std::{fmt::Debug, sync::mpsc::channel, time::Instant};
|
||||
use tokio::{prelude::FutureExt, runtime::Runtime, timer::Delay};
|
||||
|
||||
pub(crate) fn robust_poll<T>(
|
||||
mut receiver: futures::sync::mpsc::Receiver<T>,
|
||||
) -> Result<T, RecvTimeoutError> {
|
||||
const INITIAL_DELAY_MS: u64 = RECEIVE_DELAY_MILLIS * 2;
|
||||
pub(crate) fn robust_poll_or_panic<T: Debug + Send + 'static>(
|
||||
receiver: futures::sync::mpsc::Receiver<T>,
|
||||
) -> (T, futures::sync::mpsc::Receiver<T>) {
|
||||
let (inner_sender, inner_receiver) = channel();
|
||||
let mut rt = Runtime::new().unwrap();
|
||||
rt.spawn(futures::lazy(|| {
|
||||
let recv_timeout = receiver
|
||||
.into_future()
|
||||
.timeout(Duration::from_millis(RECEIVE_DELAY_MILLIS))
|
||||
.map(move |result| match result {
|
||||
(Some(value), receiver) => {
|
||||
inner_sender.send((value, receiver)).expect("send error")
|
||||
}
|
||||
(None, _) => panic!("unexpected end of stream"),
|
||||
})
|
||||
.map_err(|err| panic!("stream error {:?}", err));
|
||||
|
||||
std::thread::sleep(Duration::from_millis(INITIAL_DELAY_MS));
|
||||
for _i in 0..5 {
|
||||
let found = receiver.poll();
|
||||
if let Ok(Async::Ready(Some(result))) = found {
|
||||
return Ok(result);
|
||||
}
|
||||
std::thread::sleep(Duration::from_millis(RECEIVE_DELAY_MILLIS));
|
||||
}
|
||||
Err(RecvTimeoutError::Timeout)
|
||||
}
|
||||
|
||||
pub(crate) fn robust_poll_or_panic<T>(receiver: futures::sync::mpsc::Receiver<T>) -> T {
|
||||
robust_poll(receiver).unwrap_or_else(|err| panic!("expected response! {}", err))
|
||||
const INITIAL_DELAY_MS: u64 = RECEIVE_DELAY_MILLIS * 2;
|
||||
Delay::new(Instant::now() + Duration::from_millis(INITIAL_DELAY_MS))
|
||||
.and_then(|_| recv_timeout)
|
||||
.map_err(|err| panic!("timer error {:?}", err))
|
||||
}));
|
||||
inner_receiver.recv().expect("recv error")
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -593,10 +662,9 @@ pub(crate) mod tests {
|
||||
let (subscriber, _id_receiver, transport_receiver) =
|
||||
Subscriber::new_test("accountNotification");
|
||||
let sub_id = SubscriptionId::Number(0 as u64);
|
||||
let sink = subscriber.assign_id(sub_id.clone()).unwrap();
|
||||
let exit = Arc::new(AtomicBool::new(false));
|
||||
let subscriptions = RpcSubscriptions::new(&exit);
|
||||
subscriptions.add_account_subscription(&alice.pubkey(), None, &sub_id, &sink);
|
||||
subscriptions.add_account_subscription(alice.pubkey(), None, sub_id.clone(), subscriber);
|
||||
|
||||
assert!(subscriptions
|
||||
.account_subscriptions
|
||||
@@ -605,11 +673,25 @@ pub(crate) mod tests {
|
||||
.contains_key(&alice.pubkey()));
|
||||
|
||||
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 (response, _) = robust_poll_or_panic(transport_receiver);
|
||||
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
|
||||
@@ -649,10 +731,14 @@ pub(crate) mod tests {
|
||||
let (subscriber, _id_receiver, transport_receiver) =
|
||||
Subscriber::new_test("programNotification");
|
||||
let sub_id = SubscriptionId::Number(0 as u64);
|
||||
let sink = subscriber.assign_id(sub_id.clone()).unwrap();
|
||||
let exit = Arc::new(AtomicBool::new(false));
|
||||
let subscriptions = RpcSubscriptions::new(&exit);
|
||||
subscriptions.add_program_subscription(&solana_budget_program::id(), None, &sub_id, &sink);
|
||||
subscriptions.add_program_subscription(
|
||||
solana_budget_program::id(),
|
||||
None,
|
||||
sub_id.clone(),
|
||||
subscriber,
|
||||
);
|
||||
|
||||
assert!(subscriptions
|
||||
.program_subscriptions
|
||||
@@ -661,12 +747,28 @@ pub(crate) mod tests {
|
||||
.contains_key(&solana_budget_program::id()));
|
||||
|
||||
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 (response, _) = robust_poll_or_panic(transport_receiver);
|
||||
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 +787,117 @@ 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 (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 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 exit = Arc::new(AtomicBool::new(false));
|
||||
let subscriptions = RpcSubscriptions::new(&exit);
|
||||
subscriptions.add_signature_subscription(&signature, None, &sub_id, &sink.clone());
|
||||
|
||||
let (past_bank_sub, _id_receiver, past_bank_recv) =
|
||||
Subscriber::new_test("signatureNotification");
|
||||
let (processed_sub, _id_receiver, processed_recv) =
|
||||
Subscriber::new_test("signatureNotification");
|
||||
subscriptions.add_signature_subscription(
|
||||
¬_ready_signature,
|
||||
None,
|
||||
&remaining_sub_id,
|
||||
&sink.clone(),
|
||||
past_bank_tx.signatures[0],
|
||||
Some(0),
|
||||
SubscriptionId::Number(1 as u64),
|
||||
Subscriber::new_test("signatureNotification").0,
|
||||
);
|
||||
subscriptions.add_signature_subscription(
|
||||
past_bank_tx.signatures[0],
|
||||
Some(1),
|
||||
SubscriptionId::Number(2 as u64),
|
||||
past_bank_sub,
|
||||
);
|
||||
subscriptions.add_signature_subscription(
|
||||
processed_tx.signatures[0],
|
||||
Some(0),
|
||||
SubscriptionId::Number(3 as u64),
|
||||
processed_sub,
|
||||
);
|
||||
subscriptions.add_signature_subscription(
|
||||
unprocessed_tx.signatures[0],
|
||||
Some(0),
|
||||
SubscriptionId::Number(4 as u64),
|
||||
Subscriber::new_test("signatureNotification").0,
|
||||
);
|
||||
|
||||
{
|
||||
let sig_subs = subscriptions.signature_subscriptions.read().unwrap();
|
||||
assert!(sig_subs.contains_key(&signature));
|
||||
assert!(sig_subs.contains_key(¬_ready_signature));
|
||||
assert_eq!(sig_subs.get(&past_bank_tx.signatures[0]).unwrap().len(), 2);
|
||||
assert!(sig_subs.contains_key(&unprocessed_tx.signatures[0]));
|
||||
assert!(sig_subs.contains_key(&processed_tx.signatures[0]));
|
||||
}
|
||||
|
||||
subscriptions.notify_subscribers(0, &bank_forks);
|
||||
let response = robust_poll_or_panic(transport_receiver);
|
||||
subscriptions.notify_subscribers(1, &bank_forks);
|
||||
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": 2,
|
||||
}
|
||||
});
|
||||
let (response, _) = robust_poll_or_panic(past_bank_recv);
|
||||
assert_eq!(serde_json::to_string(&expected).unwrap(), response);
|
||||
|
||||
let expected = json!({
|
||||
"jsonrpc": "2.0",
|
||||
"method": "signatureNotification",
|
||||
"params": {
|
||||
"result": {
|
||||
"context": { "slot": 1 },
|
||||
"value": expected_res,
|
||||
},
|
||||
"subscription": 3,
|
||||
}
|
||||
});
|
||||
let (response, _) = robust_poll_or_panic(processed_recv);
|
||||
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
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -752,10 +905,9 @@ pub(crate) mod tests {
|
||||
let (subscriber, _id_receiver, transport_receiver) =
|
||||
Subscriber::new_test("slotNotification");
|
||||
let sub_id = SubscriptionId::Number(0 as u64);
|
||||
let sink = subscriber.assign_id(sub_id.clone()).unwrap();
|
||||
let exit = Arc::new(AtomicBool::new(false));
|
||||
let subscriptions = RpcSubscriptions::new(&exit);
|
||||
subscriptions.add_slot_subscription(&sub_id, &sink);
|
||||
subscriptions.add_slot_subscription(sub_id.clone(), subscriber);
|
||||
|
||||
assert!(subscriptions
|
||||
.slot_subscriptions
|
||||
@@ -764,7 +916,7 @@ pub(crate) mod tests {
|
||||
.contains_key(&sub_id));
|
||||
|
||||
subscriptions.notify_slot(0, 0, 0);
|
||||
let response = robust_poll_or_panic(transport_receiver);
|
||||
let (response, _) = robust_poll_or_panic(transport_receiver);
|
||||
let expected_res = SlotInfo {
|
||||
parent: 0,
|
||||
slot: 0,
|
||||
@@ -786,29 +938,68 @@ pub(crate) mod tests {
|
||||
.contains_key(&sub_id));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_check_root_subscribe() {
|
||||
let (subscriber, _id_receiver, mut transport_receiver) =
|
||||
Subscriber::new_test("rootNotification");
|
||||
let sub_id = SubscriptionId::Number(0 as u64);
|
||||
let exit = Arc::new(AtomicBool::new(false));
|
||||
let subscriptions = RpcSubscriptions::new(&exit);
|
||||
subscriptions.add_root_subscription(sub_id.clone(), subscriber);
|
||||
|
||||
assert!(subscriptions
|
||||
.root_subscriptions
|
||||
.read()
|
||||
.unwrap()
|
||||
.contains_key(&sub_id));
|
||||
|
||||
subscriptions.notify_roots(vec![2, 1, 3]);
|
||||
|
||||
for expected_root in 1..=3 {
|
||||
let (response, receiver) = robust_poll_or_panic(transport_receiver);
|
||||
transport_receiver = receiver;
|
||||
let expected_res_str =
|
||||
serde_json::to_string(&serde_json::to_value(expected_root).unwrap()).unwrap();
|
||||
let expected = format!(
|
||||
r#"{{"jsonrpc":"2.0","method":"rootNotification","params":{{"result":{},"subscription":0}}}}"#,
|
||||
expected_res_str
|
||||
);
|
||||
assert_eq!(expected, response);
|
||||
}
|
||||
|
||||
subscriptions.remove_root_subscription(&sub_id);
|
||||
assert!(!subscriptions
|
||||
.root_subscriptions
|
||||
.read()
|
||||
.unwrap()
|
||||
.contains_key(&sub_id));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_and_remove_subscription() {
|
||||
let (subscriber, _id_receiver, _transport_receiver) = Subscriber::new_test("notification");
|
||||
let sink = subscriber
|
||||
.assign_id(SubscriptionId::String("test".to_string()))
|
||||
.unwrap();
|
||||
let mut subscriptions: HashMap<u64, HashMap<SubscriptionId, (Sink<()>, Confirmations)>> =
|
||||
HashMap::new();
|
||||
|
||||
let num_keys = 5;
|
||||
let mut next_id: u64 = 0;
|
||||
for _ in 0..num_keys {
|
||||
let key = next_id;
|
||||
let sub_id = SubscriptionId::Number(next_id);
|
||||
add_subscription(&mut subscriptions, &key, None, &sub_id, &sink.clone());
|
||||
next_id += 1;
|
||||
for key in 0..num_keys {
|
||||
let (subscriber, _id_receiver, _transport_receiver) =
|
||||
Subscriber::new_test("notification");
|
||||
let sub_id = SubscriptionId::Number(key);
|
||||
add_subscription(&mut subscriptions, key, None, sub_id, subscriber);
|
||||
}
|
||||
|
||||
// Add another subscription to the "0" key
|
||||
let sub_id = SubscriptionId::Number(next_id);
|
||||
add_subscription(&mut subscriptions, &0, None, &sub_id, &sink.clone());
|
||||
let (subscriber, _id_receiver, _transport_receiver) = Subscriber::new_test("notification");
|
||||
let extra_sub_id = SubscriptionId::Number(num_keys);
|
||||
add_subscription(
|
||||
&mut subscriptions,
|
||||
0,
|
||||
None,
|
||||
extra_sub_id.clone(),
|
||||
subscriber,
|
||||
);
|
||||
|
||||
assert_eq!(subscriptions.len(), num_keys);
|
||||
assert_eq!(subscriptions.len(), num_keys as usize);
|
||||
assert_eq!(subscriptions.get(&0).unwrap().len(), 2);
|
||||
assert_eq!(subscriptions.get(&1).unwrap().len(), 1);
|
||||
|
||||
@@ -816,18 +1007,15 @@ pub(crate) mod tests {
|
||||
remove_subscription(&mut subscriptions, &SubscriptionId::Number(0)),
|
||||
true
|
||||
);
|
||||
assert_eq!(subscriptions.len(), num_keys);
|
||||
assert_eq!(subscriptions.len(), num_keys as usize);
|
||||
assert_eq!(subscriptions.get(&0).unwrap().len(), 1);
|
||||
assert_eq!(
|
||||
remove_subscription(&mut subscriptions, &SubscriptionId::Number(0)),
|
||||
false
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
remove_subscription(&mut subscriptions, &SubscriptionId::Number(next_id)),
|
||||
true
|
||||
);
|
||||
assert_eq!(subscriptions.len(), num_keys - 1);
|
||||
assert_eq!(remove_subscription(&mut subscriptions, &extra_sub_id), true);
|
||||
assert_eq!(subscriptions.len(), (num_keys - 1) as usize);
|
||||
assert!(subscriptions.get(&0).is_none());
|
||||
}
|
||||
}
|
||||
|
@@ -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,10 +1,10 @@
|
||||
use crossbeam_channel::{Receiver, RecvTimeoutError};
|
||||
use solana_client::rpc_response::RpcTransactionStatus;
|
||||
use solana_ledger::{blockstore::Blockstore, blockstore_processor::TransactionStatusBatch};
|
||||
use solana_runtime::{
|
||||
bank::{Bank, HashAgeKind},
|
||||
nonce_utils,
|
||||
};
|
||||
use solana_transaction_status::TransactionStatusMeta;
|
||||
use std::{
|
||||
sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
@@ -73,7 +73,7 @@ impl TransactionStatusService {
|
||||
blockstore
|
||||
.write_transaction_status(
|
||||
(slot, transaction.signatures[0]),
|
||||
&RpcTransactionStatus {
|
||||
&TransactionStatusMeta {
|
||||
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..100)
|
||||
.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 mut 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 now = Instant::now();
|
||||
let expected_transaction_count = transaction_count + transactions.len() as u64;
|
||||
while transaction_count < expected_transaction_count && now.elapsed() < Duration::from_secs(5) {
|
||||
transaction_count = 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.12"
|
||||
description = "Solana Crate Features"
|
||||
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||
repository = "https://github.com/solana-labs/solana"
|
||||
|
@@ -1,15 +1,23 @@
|
||||
# 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)
|
||||
* [Ledger Hardware Wallet](remote-wallet/ledger.md)
|
||||
* [Paper Wallet](paper-wallet/README.md)
|
||||
* [Paper Wallet Usage](paper-wallet/usage.md)
|
||||
* [Wallet Guide](wallet/README.md)
|
||||
* [App Wallets](wallet/app-wallets.md)
|
||||
* [Trust Wallet](wallet/trust-wallet.md)
|
||||
* [Ledger Live](wallet/ledger-live.md)
|
||||
* [Command-line Wallets](wallet/cli-wallets.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/paper-wallet-usage.md)
|
||||
* [Command Line Guide](cli/README.md)
|
||||
* [Install the Solana Command Line Tool Suite](cli/install-solana-cli-tools.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)
|
||||
|
@@ -31,10 +31,8 @@ To interact with a Solana node inside a JavaScript application, use the [solana-
|
||||
* [getInflation](jsonrpc-api.md#getinflation)
|
||||
* [getLeaderSchedule](jsonrpc-api.md#getleaderschedule)
|
||||
* [getMinimumBalanceForRentExemption](jsonrpc-api.md#getminimumbalanceforrentexemption)
|
||||
* [getNumBlocksSinceSignatureConfirmation](jsonrpc-api.md#getnumblockssincesignatureconfirmation)
|
||||
* [getProgramAccounts](jsonrpc-api.md#getprogramaccounts)
|
||||
* [getRecentBlockhash](jsonrpc-api.md#getrecentblockhash)
|
||||
* [getSignatureConfirmation](jsonrpc-api.md#getsignatureconfirmation)
|
||||
* [getSignatureStatus](jsonrpc-api.md#getsignaturestatus)
|
||||
* [getSlot](jsonrpc-api.md#getslot)
|
||||
* [getSlotLeader](jsonrpc-api.md#getslotleader)
|
||||
@@ -298,11 +296,11 @@ The result field will be an object with the following fields:
|
||||
* `previousBlockhash: <string>` - the blockhash of this block's parent, as base-58 encoded string
|
||||
* `parentSlot: <u64>` - the slot index of this block's parent
|
||||
* `transactions: <array>` - an array of JSON objects containing:
|
||||
* `transaction: <object|string>` - [Transaction](transaction-api.md) object, either in JSON format or base-58 encoded binary data, depending on encoding parameter
|
||||
* `transaction: <object|string>` - [Transaction](#transaction-structure) object, either in JSON format or base-58 encoded binary data, depending on encoding parameter
|
||||
* `meta: <object>` - transaction status metadata object, containing `null` or:
|
||||
* `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)
|
||||
* `"Err": <ERR>` - Transaction failed with TransactionError [TransactionError definitions](https://github.com/solana-labs/solana/blob/master/sdk/src/transaction.rs#L18)
|
||||
* `fee: <u64>` - fee this transaction was charged, as u64 integer
|
||||
* `preBalances: <array>` - array of u64 account balances from before the transaction was processed
|
||||
* `postBalances: <array>` - array of u64 account balances after the transaction was processed
|
||||
@@ -326,6 +324,25 @@ curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc": "2.0","id":1,"m
|
||||
{"jsonrpc":"2.0","result":{"blockhash":"Gp3t5bfDsJv1ovP8cB1SuRhXVuoTqDv7p3tymyubYg5","parentSlot":429,"previousBlockhash":"EFejToxii1L5aUF2NrK9dsbAEmZSNyN5nsipmZHQR1eA","transactions":[{"transaction":"81UZJt4dh4Do66jDhrgkQudS8J2N6iG3jaVav7gJrqJSFY4Ug53iA9JFJZh2gxKWcaFdLJwhHx9mRdg9JwDAWB4ywiu5154CRwXV4FMdnPLg7bhxRLwhhYaLsVgMF5AyNRcTzjCVoBvqFgDU7P8VEKDEiMvD3qxzm1pLZVxDG1LTQpT3Dz4Uviv4KQbFQNuC22KupBoyHFB7Zh6KFdMqux4M9PvhoqcoJsJKwXjWpKu7xmEKnnrSbfLadkgjBmmjhW3fdTrFvnhQdTkhtdJxUL1xS9GMuJQer8YgSKNtUXB1eXZQwXU8bU2BjYkZE6Q5Xww8hu9Z4E4Mo4QsooVtHoP6BM3NKw8zjVbWfoCQqxTrwuSzrNCWCWt58C24LHecH67CTt2uXbYSviixvrYkK7A3t68BxTJcF1dXJitEPTFe2ceTkauLJqrJgnER4iUrsjr26T8YgWvpY9wkkWFSviQW6wV5RASTCUasVEcrDiaKj8EQMkgyDoe9HyKitSVg67vMWJFpUXpQobseWJUs5FTWWzmfHmFp8FZ","meta":{"fee":1000,"postBalances":[499999972500,15298080,1,1,1],"preBalances":[499999990500,15298080,1,1,1],"status":{"Ok":null}}}]},"id":1}
|
||||
```
|
||||
|
||||
#### Transaction Structure
|
||||
|
||||
Transactions are quite different from those on other blockchains. Be sure to review [Anatomy of a Transaction](transaction.md) to learn about transactions on Solana.
|
||||
|
||||
The JSON structure of a transaction is defined as follows:
|
||||
|
||||
* `signatures: <array[string]>` - A list of base-58 encoded signatures applied to the transaction. The list is always of length `message.header.numRequiredSignatures`, and the signature at index `i` corresponds to the public key at index `i` in `message.account_keys`.
|
||||
* `message: <object>` - Defines the content of the transaction.
|
||||
* `accountKeys: <array[string]>` - List of base-58 encoded public keys used by the transaction, including by the instructions and for signatures. The first `message.header.numRequiredSignatures` public keys must sign the transaction.
|
||||
* `header: <object>` - Details the account types and signatures required by the transaction.
|
||||
* `numRequiredSignatures: <number>` - The total number of signatures required to make the transaction valid. The signatures must match the first `numRequiredSignatures` of `message.account_keys`.
|
||||
* `numReadonlySignedAccounts: <number>` - The last `numReadonlySignedAccounts` of the signed keys are read-only accounts. Programs may process multiple transactions that load read-only accounts within a single PoH entry, but are not permitted to credit or debit lamports or modify account data. Transactions targeting the same read-write account are evaluated sequentially.
|
||||
* `numReadonlyUnsignedAccounts: <number>` - The last `numReadonlyUnsignedAccounts` of the unsigned keys are read-only accounts.
|
||||
* `recentBlockhash: <string>` - A base-58 encoded hash of a recent block in the ledger used to prevent transaction duplication and to give transactions lifetimes.
|
||||
* `instructions: <array[object]>` - List of program instructions that will be executed in sequence and committed in one atomic transaction if all succeed.
|
||||
* `programIdIndex: <number>` - Index into the `message.accountKeys` array indicating the program account that executes this instruction.
|
||||
* `accounts: <array[number]>` - List of ordered indices into the `message.accountKeys` array indicating which accounts to pass to the program.
|
||||
* `data: <string>` - The program input data encoded in a base-58 string.
|
||||
|
||||
### getConfirmedBlocks
|
||||
|
||||
Returns a list of confirmed blocks
|
||||
@@ -580,29 +597,6 @@ curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "
|
||||
{"jsonrpc":"2.0","result":500,"id":1}
|
||||
```
|
||||
|
||||
### getNumBlocksSinceSignatureConfirmation
|
||||
|
||||
Returns the current number of blocks since signature has been confirmed.
|
||||
|
||||
#### Parameters:
|
||||
|
||||
* `<string>` - Signature of Transaction to confirm, as base-58 encoded string
|
||||
* `<object>` - (optional) [Commitment](jsonrpc-api.md#configuring-state-commitment)
|
||||
|
||||
#### Results:
|
||||
|
||||
* `<u64>` - count, or null if signature not found
|
||||
|
||||
#### Example:
|
||||
|
||||
```bash
|
||||
// Request
|
||||
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "method":"getNumBlocksSinceSignatureConfirmation", "params":["5VERv8NMvzbJMEkV8xnrLkEaWRtSz9CosKDYjCJjBRnbJLgp8uirBgmQpjKhoR4tjF3ZpRzrFmBV6UjKdiSZkQUW"]}' http://localhost:8899
|
||||
|
||||
// Result
|
||||
{"jsonrpc":"2.0","result":8,"id":1}
|
||||
```
|
||||
|
||||
### getProgramAccounts
|
||||
|
||||
Returns all accounts owned by the provided program Pubkey
|
||||
@@ -660,20 +654,30 @@ curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "m
|
||||
{"jsonrpc":"2.0","result":{"context":{"slot":1},"value":{"blockhash":"CSymwgTNX1j3E4qhKfJAUE41nBWEwXufoYryPbkde5RR","feeCalculator":{"burnPercent":50,"lamportsPerSignature":5000,"maxLamportsPerSignature":10000,"minLamportsPerSignature":5000,"targetLamportsPerSignature":1000,"targetSignaturesPerSlot":20000}}},"id":1}
|
||||
```
|
||||
|
||||
### getSignatureConfirmation
|
||||
### getSignatureStatus
|
||||
|
||||
Returns the status of a given signature. This method is similar to [confirmTransaction](jsonrpc-api.md#confirmtransaction) but provides more resolution for error events.
|
||||
|
||||
Returns the status and number of confirmations of a given signature.
|
||||
#### 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 RpcResponse containing a JSON object consisting of an array of TransactionStatus objects.
|
||||
|
||||
* `RpcResponse<object>` - RpcResponse JSON object with `value` field:
|
||||
|
||||
An array of:
|
||||
|
||||
* `<null>` - Unknown transaction
|
||||
* `<object>` - Transaction confirmations and status:
|
||||
* `confirmations: <u64>` - count of confirmations since transaction was processed
|
||||
* `status: <object>` -
|
||||
* `<object>`
|
||||
* `slot: <u64>` - The slot the transaction was processed
|
||||
* `confirmations: <usize | null>` - Number of blocks since signature confirmation, null if rooted
|
||||
* `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)
|
||||
|
||||
@@ -681,36 +685,13 @@ Returns the status and number of confirmations of a given signature.
|
||||
|
||||
```bash
|
||||
// Request
|
||||
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "method":"getSignatureConfirmation", "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":{"confirmations":12,"status":{"Ok": null}},"id":1}
|
||||
```
|
||||
{"jsonrpc":"2.0","result":{"context":{"slot":82},"value":[{"slot": 72, "confirmations": 10, "status": {"Ok": null}}, null]},"id":1}
|
||||
|
||||
### getSignatureStatus
|
||||
|
||||
Returns the status of a given signature. This method is similar to [confirmTransaction](jsonrpc-api.md#confirmtransaction) but provides more resolution for error events.
|
||||
|
||||
#### Parameters:
|
||||
|
||||
* `<string>` - Signature of Transaction to confirm, as base-58 encoded string
|
||||
* `<object>` - (optional) [Commitment](jsonrpc-api.md#configuring-state-commitment)
|
||||
|
||||
#### Results:
|
||||
|
||||
* `<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)
|
||||
|
||||
#### Example:
|
||||
|
||||
```bash
|
||||
// Request
|
||||
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "method":"getSignatureStatus", "params":["5VERv8NMvzbJMEkV8xnrLkEaWRtSz9CosKDYjCJjBRnbJLgp8uirBgmQpjKhoR4tjF3ZpRzrFmBV6UjKdiSZkQUW"]}' http://localhost:8899
|
||||
|
||||
// Result
|
||||
{"jsonrpc":"2.0","result":{"Ok": null},"id":1}
|
||||
// Result, first transaction rooted
|
||||
{"jsonrpc":"2.0","result":{"context":{"slot":82},"value":[{"slot": 48, "confirmations": null, "status": {"Ok": null}}, null]},"id":1}
|
||||
```
|
||||
|
||||
### getSlot
|
||||
@@ -908,7 +889,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.12"},"id":1}
|
||||
```
|
||||
|
||||
### getVoteAccounts
|
||||
@@ -929,7 +910,7 @@ The result field will be a JSON object of `current` and `delinquent` accounts, e
|
||||
* `epochVoteAccount: <bool>` - bool, whether the vote account is staked for this epoch
|
||||
* `commission: <number>`, percentage (0-100) of rewards payout owed to the vote account
|
||||
* `lastVote: <u64>` - Most recent slot voted on by this vote account
|
||||
* `epochCredits: <array>` - History of how many credits earned by the end of each epoch, as an array of arrays containing: [epoch, credits, previousCredits]
|
||||
* `epochCredits: <array>` - History of how many credits earned by the end of each epoch, as an array of arrays containing: `[epoch, credits, previousCredits]`
|
||||
|
||||
#### Example:
|
||||
|
||||
@@ -1274,7 +1255,7 @@ None
|
||||
|
||||
### slotUnsubscribe
|
||||
|
||||
Unsubscribe from signature confirmation notification
|
||||
Unsubscribe from slot notifications
|
||||
|
||||
#### Parameters:
|
||||
|
||||
@@ -1293,3 +1274,55 @@ Unsubscribe from signature confirmation notification
|
||||
// Result
|
||||
{"jsonrpc": "2.0","result": true,"id": 1}
|
||||
```
|
||||
|
||||
### rootSubscribe
|
||||
|
||||
Subscribe to receive notification anytime a new root is set by the validator.
|
||||
|
||||
#### Parameters:
|
||||
|
||||
None
|
||||
|
||||
#### Results:
|
||||
|
||||
* `integer` - subscription id \(needed to unsubscribe\)
|
||||
|
||||
#### Example:
|
||||
|
||||
```bash
|
||||
// Request
|
||||
{"jsonrpc":"2.0", "id":1, "method":"rootSubscribe"}
|
||||
|
||||
// Result
|
||||
{"jsonrpc": "2.0","result": 0,"id": 1}
|
||||
```
|
||||
|
||||
#### Notification Format:
|
||||
|
||||
The result is the latest root slot number.
|
||||
|
||||
```bash
|
||||
{"jsonrpc": "2.0","method": "rootNotification", "params": {"result":42,"subscription":0}}
|
||||
```
|
||||
|
||||
### rootUnsubscribe
|
||||
|
||||
Unsubscribe from root notifications
|
||||
|
||||
#### Parameters:
|
||||
|
||||
* `<integer>` - subscription id to cancel
|
||||
|
||||
#### Results:
|
||||
|
||||
* `<bool>` - unsubscribe success message
|
||||
|
||||
#### Example:
|
||||
|
||||
```bash
|
||||
// Request
|
||||
{"jsonrpc":"2.0", "id":1, "method":"rootUnsubscribe", "params":[0]}
|
||||
|
||||
// Result
|
||||
{"jsonrpc": "2.0","result": true,"id": 1}
|
||||
```
|
||||
|
@@ -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,11 @@
|
||||
# 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 use the Solana command-line tools to
|
||||
create a *wallet*, to send and receive SOL tokens, and 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.
|
||||
|
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-cli-tools.md)
|
||||
* [Choose a Command-line wallet](../wallet/cli-wallets.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/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/README.md#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.
|
@@ -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
|
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](../wallet/cli-wallets.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.12 [channel=unknown commit=unknown]
|
||||
Blockchain, Rebuilt for Scale
|
||||
|
||||
USAGE:
|
||||
|
@@ -10,7 +10,7 @@ These protocol-based rewards, to be distributed to participating validation and
|
||||
|
||||
Transaction fees are market-based participant-to-participant transfers, attached to network interactions as a necessary motivation and compensation for the inclusion and execution of a proposed transaction \(be it a state execution or proof-of-replication verification\). A mechanism for long-term economic stability and forking protection through partial burning of each transaction fee is also discussed below.
|
||||
|
||||
A high-level schematic of Solana’s crypto-economic design is shown below in **Figure 1**. The specifics of validation-client economics are described in sections: [Validation-client Economics](ed_validation_client_economics/), [State-validation Protocol-based Rewards](ed_validation_client_economics/ed_vce_state_validation_protocol_based_rewards.md), [State-validation Transaction Fees](ed_validation_client_economics/ed_vce_state_validation_transaction_fees.md) and [Replication-validation Transaction Fees](ed_validation_client_economics/ed_vce_replication_validation_transaction_fees.md). Also, the section titled [Validation Stake Delegation](ed_validation_client_economics/ed_vce_validation_stake_delegation.md) closes with a discussion of validator delegation opportunities and marketplace. Additionally, in [Storage Rent Economics](ed_storage_rent_economics.md), we describe an implementation of storage rent to account for the externality costs of maintaining the active state of the ledger. [Replication-client Economics](ed_replication_client_economics/) will review the Solana network design for global ledger storage/redundancy and archiver-client economics \([Storage-replication rewards](ed_replication_client_economics/ed_rce_storage_replication_rewards.md)\) along with an archiver-to-validator delegation mechanism designed to aide participant on-boarding into the Solana economy discussed in [Replication-client Reward Auto-delegation](ed_replication_client_economics/ed_rce_replication_client_reward_auto_delegation.md). An outline of features for an MVP economic design is discussed in the [Economic Design MVP](ed_mvp.md) section. Finally, in [Attack Vectors](ed_attack_vectors.md), various attack vectors will be described and potential vulnerabilities explored and parameterized.
|
||||
A high-level schematic of Solana’s crypto-economic design is shown below in **Figure 1**. The specifics of validation-client economics are described in sections: [Validation-client Economics](ed_validation_client_economics/README.md), [State-validation Protocol-based Rewards](ed_validation_client_economics/ed_vce_state_validation_protocol_based_rewards.md), [State-validation Transaction Fees](ed_validation_client_economics/ed_vce_state_validation_transaction_fees.md) and [Replication-validation Transaction Fees](ed_validation_client_economics/ed_vce_replication_validation_transaction_fees.md). Also, the section titled [Validation Stake Delegation](ed_validation_client_economics/ed_vce_validation_stake_delegation.md) closes with a discussion of validator delegation opportunities and marketplace. Additionally, in [Storage Rent Economics](ed_storage_rent_economics.md), we describe an implementation of storage rent to account for the externality costs of maintaining the active state of the ledger. [Replication-client Economics](ed_replication_client_economics/README.md) will review the Solana network design for global ledger storage/redundancy and archiver-client economics \([Storage-replication rewards](ed_replication_client_economics/ed_rce_storage_replication_rewards.md)\) along with an archiver-to-validator delegation mechanism designed to aide participant on-boarding into the Solana economy discussed in [Replication-client Reward Auto-delegation](ed_replication_client_economics/ed_rce_replication_client_reward_auto_delegation.md). An outline of features for an MVP economic design is discussed in the [Economic Design MVP](ed_mvp.md) section. Finally, in [Attack Vectors](ed_attack_vectors.md), various attack vectors will be described and potential vulnerabilities explored and parameterized.
|
||||
|
||||

|
||||
|
||||
|
@@ -2,7 +2,7 @@
|
||||
|
||||
**Subject to change.**
|
||||
|
||||
The preceding sections, outlined in the [Economic Design Overview](./), describe a long-term vision of a sustainable Solana economy. Of course, we don't expect the final implementation to perfectly match what has been described above. We intend to fully engage with network stakeholders throughout the implementation phases \(i.e. pre-testnet, testnet, mainnet\) to ensure the system supports, and is representative of, the various network participants' interests. The first step toward this goal, however, is outlining a some desired MVP economic features to be available for early pre-testnet and testnet participants. Below is a rough sketch outlining basic economic functionality from which a more complete and functional system can be developed.
|
||||
The preceding sections, outlined in the [Economic Design Overview](../README.md), describe a long-term vision of a sustainable Solana economy. Of course, we don't expect the final implementation to perfectly match what has been described above. We intend to fully engage with network stakeholders throughout the implementation phases \(i.e. pre-testnet, testnet, mainnet\) to ensure the system supports, and is representative of, the various network participants' interests. The first step toward this goal, however, is outlining a some desired MVP economic features to be available for early pre-testnet and testnet participants. Below is a rough sketch outlining basic economic functionality from which a more complete and functional system can be developed.
|
||||
|
||||
## MVP Economic Features
|
||||
|
||||
|
@@ -4,7 +4,7 @@
|
||||
|
||||
Validator-clients have two functional roles in the Solana network:
|
||||
|
||||
* Validate \(vote\) the current global state of that PoH along with any Proofs-of-Replication \(see [Replication Client Economics](../ed_replication_client_economics/)\) that they are eligible to validate.
|
||||
* Validate \(vote\) the current global state of that PoH along with any Proofs-of-Replication \(see [Replication Client Economics](../ed_replication_client_economics/README.md)\) that they are eligible to validate.
|
||||
* Be elected as ‘leader’ on a stake-weighted round-robin schedule during which time they are responsible for collecting outstanding transactions and Proofs-of-Replication and incorporating them into the PoH, thus updating the global state of the network and providing chain continuity.
|
||||
|
||||
Validator-client rewards for these services are to be distributed at the end of each Solana epoch. As previously discussed, compensation for validator-clients is provided via a protocol-based annual inflation rate dispersed in proportion to the stake-weight of each validator \(see below\) along with leader-claimed transaction fees available during each leader rotation. I.e. during the time a given validator-client is elected as leader, it has the opportunity to keep a portion of each transaction fee, less a protocol-specified amount that is destroyed \(see [Validation-client State Transaction Fees](ed_vce_state_validation_transaction_fees.md)\). PoRep transaction fees are also collected by the leader client and validator PoRep rewards are distributed in proportion to the number of validated PoReps less the number of PoReps that mismatch an archiver's challenge. \(see [Replication-client Transaction Fees](ed_vce_replication_validation_transaction_fees.md)\)
|
||||
|
@@ -24,7 +24,7 @@ Running a Solana validation-client required relatively modest upfront hardware c
|
||||
Despite the low-barrier to entry as a validation-client, from a capital investment perspective, as in any developing economy, there will be much opportunity and need for trusted validation services as evidenced by node reliability, UX/UI, APIs and other software accessibility tools. Additionally, although Solana’s validator node startup costs are nominal when compared to similar networks, they may still be somewhat restrictive for some potential participants. In the spirit of developing a true decentralized, permissionless network, these interested parties still have two options to become involved in the Solana network/economy:
|
||||
|
||||
1. Delegation of previously acquired tokens with a reliable validation node to earn a portion of interest generated
|
||||
2. Provide local storage space as a replication-client and receive rewards by submitting Proof-of-Replication \(see [Replication-client Economics](../ed_replication_client_economics/)\).
|
||||
2. Provide local storage space as a replication-client and receive rewards by submitting Proof-of-Replication \(see [Replication-client Economics](../ed_replication_client_economics/README.md)\).
|
||||
|
||||
a. This participant has the additional option to directly delegate their earned storage rewards \([Replication-client Reward Auto-delegation](../ed_replication_client_economics/ed_rce_replication_client_reward_auto_delegation.md)\)
|
||||
|
||||
|
@@ -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]
|
||||
```
|
||||
|
||||
|
@@ -20,7 +20,7 @@ Slot leaders and validators use a PoH Recorder for both estimating slot height a
|
||||
|
||||
### PoH Recorder when Validating
|
||||
|
||||
The PoH Recorder acts as a simple VDF when validating. It tells the validator when it needs to switch to the slot leader role. Every time the validator votes on a fork, it should use the fork's latest [blockhash](terminology.md#blockhash) to re-seed the VDF. Re-seeding solves two problems. First, it synchronizes its VDF to the leader's, allowing it to more accurately determine when its leader slot begins. Second, if the previous leader goes down, all wallclock time is accounted for in the next leader's PoH stream. For example, if one block is missing when the leader starts, the block it produces should have a PoH duration of two blocks. The longer duration ensures the following leader isn't attempting to snip all the transactions from the previous leader's slot.
|
||||
The PoH Recorder acts as a simple VDF when validating. It tells the validator when it needs to switch to the slot leader role. Every time the validator votes on a fork, it should use the fork's latest [blockhash](../terminology.md#blockhash) to re-seed the VDF. Re-seeding solves two problems. First, it synchronizes its VDF to the leader's, allowing it to more accurately determine when its leader slot begins. Second, if the previous leader goes down, all wallclock time is accounted for in the next leader's PoH stream. For example, if one block is missing when the leader starts, the block it produces should have a PoH duration of two blocks. The longer duration ensures the following leader isn't attempting to snip all the transactions from the previous leader's slot.
|
||||
|
||||
### PoH Recorder when Leading
|
||||
|
||||
|
@@ -50,7 +50,7 @@ Solana's trustless sense of time and ordering provided by its PoH data structure
|
||||
|
||||
## Penalties
|
||||
|
||||
As discussed in the [Economic Design](../implemented-proposals/ed_overview/) section, annual validator interest rates are to be specified as a function of total percentage of circulating supply that has been staked. The cluster rewards validators who are online and actively participating in the validation process throughout the entirety of their _validation period_. For validators that go offline/fail to validate transactions during this period, their annual reward is effectively reduced.
|
||||
As discussed in the [Economic Design](../implemented-proposals/ed_overview/README.md) section, annual validator interest rates are to be specified as a function of total percentage of circulating supply that has been staked. The cluster rewards validators who are online and actively participating in the validation process throughout the entirety of their _validation period_. For validators that go offline/fail to validate transactions during this period, their annual reward is effectively reduced.
|
||||
|
||||
Similarly, we may consider an algorithmic reduction in a validator's active amount staked amount in the case that they are offline. I.e. if a validator is inactive for some amount of time, either due to a partition or otherwise, the amount of their stake that is considered ‘active’ \(eligible to earn rewards\) may be reduced. This design would be structured to help long-lived partitions to eventually reach finality on their respective chains as the % of non-voting total stake is reduced over time until a supermajority can be achieved by the active validators in each partition. Similarly, upon re-engaging, the ‘active’ amount staked will come back online at some defined rate. Different rates of stake reduction may be considered depending on the size of the partition/active set.
|
||||
|
||||
|
@@ -4,7 +4,7 @@ Some security models require keeping signing keys, and thus the signing
|
||||
process, separated from transaction creation and network broadcast. Examples
|
||||
include:
|
||||
* Collecting signatures from geographically disparate signers in a
|
||||
[multi-signature scheme](../api-reference/cli.md#multiple-witnesses)
|
||||
[multi-signature scheme](../cli/usage.md#multiple-witnesses)
|
||||
* Signing transactions using an [airgapped](https://en.wikipedia.org/wiki/Air_gap_(networking))
|
||||
signing device
|
||||
|
||||
@@ -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
|
||||
|
@@ -8,7 +8,7 @@ about in the [proposal](../implemented-proposals/durable-tx-nonces.md).
|
||||
## Usage Examples
|
||||
|
||||
Full usage details for durable nonce CLI commands can be found in the
|
||||
[CLI reference](../api-reference/cli.md).
|
||||
[CLI reference](../cli/usage.md).
|
||||
|
||||
### Nonce Authority
|
||||
|
||||
@@ -47,12 +47,12 @@ solana create-nonce-account nonce-keypair.json 1
|
||||
|
||||
{% hint style="info" %}
|
||||
To keep the keypair entirely offline, use the [Paper Wallet](../paper-wallet/README.md)
|
||||
keypair generation [instructions](../paper-wallet/usage.md#seed-phrase-generation.md)
|
||||
keypair generation [instructions](../paper-wallet/paper-wallet-usage.md#seed-phrase-generation.md)
|
||||
instead
|
||||
{% endhint %}
|
||||
|
||||
{% hint style="info" %}
|
||||
[Full usage documentation](../api-reference/cli.md#solana-create-nonce-account)
|
||||
[Full usage documentation](../cli/usage.md#solana-create-nonce-account)
|
||||
{% endhint %}
|
||||
|
||||
### Querying the Stored Nonce Value
|
||||
@@ -74,7 +74,7 @@ solana nonce nonce-keypair.json
|
||||
```
|
||||
|
||||
{% hint style="info" %}
|
||||
[Full usage documentation](../api-reference/cli.md#solana-get-nonce)
|
||||
[Full usage documentation](../cli/usage.md#solana-get-nonce)
|
||||
{% endhint %}
|
||||
|
||||
### Advancing the Stored Nonce Value
|
||||
@@ -95,7 +95,7 @@ solana new-nonce nonce-keypair.json
|
||||
```
|
||||
|
||||
{% hint style="info" %}
|
||||
[Full usage documentation](../api-reference/cli.md#solana-new-nonce)
|
||||
[Full usage documentation](../cli/usage.md#solana-new-nonce)
|
||||
{% endhint %}
|
||||
|
||||
### Display Nonce Account
|
||||
@@ -117,7 +117,7 @@ nonce: DZar6t2EaCFQTbUP4DHKwZ1wT8gCPW2aRfkVWhydkBvS
|
||||
```
|
||||
|
||||
{% hint style="info" %}
|
||||
[Full usage documentation](../api-reference/cli.md#solana-nonce-account)
|
||||
[Full usage documentation](../cli/usage.md#solana-nonce-account)
|
||||
{% endhint %}
|
||||
|
||||
### Withdraw Funds from a Nonce Account
|
||||
@@ -141,7 +141,7 @@ Close a nonce account by withdrawing the full balance
|
||||
{% endhint %}
|
||||
|
||||
{% hint style="info" %}
|
||||
[Full usage documentation](../api-reference/cli.md#solana-withdraw-from-nonce-account)
|
||||
[Full usage documentation](../cli/usage.md#solana-withdraw-from-nonce-account)
|
||||
{% endhint %}
|
||||
|
||||
### Assign a New Authority to a Nonce Account
|
||||
@@ -161,7 +161,7 @@ solana authorize-nonce-account nonce-keypair.json nonce-authority.json
|
||||
```
|
||||
|
||||
{% hint style="info" %}
|
||||
[Full usage documentation](../api-reference/cli.md#solana-authorize-nonce-account)
|
||||
[Full usage documentation](../cli/usage.md#solana-authorize-nonce-account)
|
||||
{% endhint %}
|
||||
|
||||
## Other Commands Supporting Durable Nonces
|
||||
@@ -172,9 +172,9 @@ supported.
|
||||
* `--nonce-authority`, specifies an optional [nonce authority](#nonce-authority)
|
||||
|
||||
The following subcommands have received this treatment so far
|
||||
* [`pay`](../api-reference/cli.md#solana-pay)
|
||||
* [`delegate-stake`](../api-reference/cli.md#solana-delegate-stake)
|
||||
* [`deactivate-stake`](../api-reference/cli.md#solana-deactivate-stake)
|
||||
* [`pay`](../cli/usage.md#solana-pay)
|
||||
* [`delegate-stake`](../cli/usage.md#solana-delegate-stake)
|
||||
* [`deactivate-stake`](../cli/usage.md#solana-deactivate-stake)
|
||||
|
||||
### Example Pay Using Durable Nonce
|
||||
|
||||
|
@@ -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" %}
|
||||
|
@@ -12,7 +12,7 @@ protect against this scenario.
|
||||
|
||||
## Before You Begin
|
||||
|
||||
- [Install the Solana command-line tools](../install-solana.md)
|
||||
- [Install the Solana command-line tools](../cli/install-solana-cli-tools.md)
|
||||
|
||||
### Check your installation
|
||||
|
||||
@@ -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:
|
@@ -80,8 +80,8 @@ hard forks should be approached.
|
||||
|
||||
We use some degree of macro machinery to automatically generate unit tests
|
||||
and calculate a digest from ABI items. This is doable by clever use of
|
||||
`serde::Serialize` ([1]) and `any::typename` ([2]). For a precedent for similar
|
||||
implementation, `ink` from the Parity Technologies [3] could be informational.
|
||||
`serde::Serialize` (`[1]`) and `any::typename` (`[2]`). For a precedent for similar
|
||||
implementation, `ink` from the Parity Technologies `[3]` could be informational.
|
||||
|
||||
# References
|
||||
|
||||
|
@@ -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.
|
||||
|
@@ -6,7 +6,7 @@ secure transaction signing.
|
||||
|
||||
## Before You Begin
|
||||
|
||||
- [Install the Solana command-line tools](../install-solana.md)
|
||||
- [Install the Solana command-line tools](../cli/install-solana-cli-tools.md)
|
||||
- [Initialize your Ledger Nano S](https://support.ledger.com/hc/en-us/articles/360000613793)
|
||||
- [Install the latest device firmware](https://support.ledgerwallet.com/hc/en-us/articles/360002731113-Update-Ledger-Nano-S-firmware)
|
||||
|
||||
@@ -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](../cli/transfer-tokens.md) and
|
||||
[delegating stake](../cli/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
|
||||
```
|
||||
|
||||
|
||||
|
@@ -33,7 +33,7 @@ Here are our recommendations for low, medium, and high end machine specification
|
||||
## Software
|
||||
|
||||
* We build and run on Ubuntu 18.04. Some users have had trouble when running on Ubuntu 16.04
|
||||
* See [Installing Solana](../install-solana.md) for the current Solana software release.
|
||||
* See [Installing Solana](../cli/install-solana-cli-tools.md) for the current Solana software release.
|
||||
|
||||
Be sure to ensure that the machine used is not behind a residential NAT to avoid
|
||||
NAT traversal issues. A cloud-hosted machine works best. **Ensure that IP ports 8000 through 10000 are not blocked for Internet inbound and outbound traffic.**
|
||||
|
@@ -69,7 +69,7 @@ account.
|
||||
This is a normal transaction so the standard transaction fee will apply. The
|
||||
transaction fee range is defined by the genesis block. The actual fee will
|
||||
fluctuate based on transaction load. You can determine the current fee via the
|
||||
[RPC API “getRecentBlockhash”](../api-reference/jsonrpc-api.md#getrecentblockhash)
|
||||
[RPC API “getRecentBlockhash”](../apps/jsonrpc-api.md#getrecentblockhash)
|
||||
before submitting a transaction.
|
||||
|
||||
Learn more about [transaction fees here](../implemented-proposals/transaction-fees.md).
|
||||
|
@@ -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.
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user