v1.2 instruction and account decoding backports (#10939)

* Revert "Rpc: add filter to getProgramAccounts (#10888) (#10932)"

This reverts commit 9311a6e356.

* Add jsonParsed option for EncodedTransactions; add memo parser (#10711)

* Add jsonParsed option for EncodedTransactions; add memo parser

* Use kebab case for program names

* Add account-key parsing

* Add parse test

* Update transaction encoding docs (#10833)

* Add account-decoder utilities (#10846)

* Fix comment and make less pub

* Add account-decoder crate and use to decode vote and system (nonce) accounts

* Update docs

* Rename RpcAccount struct

* s/Rpc/Display

* Call it jsonParsed and update docs

* Revert "s/Rpc/Display"

This reverts commit 6e7149f503.

* s/Rpc/Ui

* Add tests

* Ui more things

* Comments

* Update struct prefixes to Ui (#10874)

* Update comments

* Use Ui prefix

* Rpc: add filter to getProgramAccounts (#10888)

* Add RpcFilterType, and implement CompareBytes for getProgramAccounts

* Accept bytes in bs58

* Rename to memcmp

* Add Memcmp optional encoding field

* Add dataSize filter

* Update docs

* Clippy

* Simplify tests that don't need to test account contents; add multiple-filter tests
This commit is contained in:
Tyera Eulberg
2020-07-07 09:35:35 -06:00
committed by GitHub
parent 9311a6e356
commit f7fb5aebac
28 changed files with 870 additions and 220 deletions

169
Cargo.lock generated
View File

@ -3691,6 +3691,23 @@ dependencies = [
"winapi 0.3.8", "winapi 0.3.8",
] ]
[[package]]
name = "solana-account-decoder"
version = "1.2.9"
dependencies = [
"Inflector",
"bincode",
"bs58 0.3.1",
"lazy_static",
"serde",
"serde_derive",
"serde_json",
"solana-sdk 1.2.9",
"solana-vote-program",
"spl-memo",
"thiserror",
]
[[package]] [[package]]
name = "solana-accounts-bench" name = "solana-accounts-bench"
version = "1.2.9" version = "1.2.9"
@ -3703,7 +3720,7 @@ dependencies = [
"solana-logger", "solana-logger",
"solana-measure", "solana-measure",
"solana-runtime", "solana-runtime",
"solana-sdk", "solana-sdk 1.2.9",
] ]
[[package]] [[package]]
@ -3722,7 +3739,7 @@ dependencies = [
"solana-measure", "solana-measure",
"solana-perf", "solana-perf",
"solana-runtime", "solana-runtime",
"solana-sdk", "solana-sdk 1.2.9",
"solana-streamer", "solana-streamer",
"solana-version", "solana-version",
] ]
@ -3751,7 +3768,7 @@ dependencies = [
"solana-metrics", "solana-metrics",
"solana-net-utils", "solana-net-utils",
"solana-runtime", "solana-runtime",
"solana-sdk", "solana-sdk 1.2.9",
"solana-version", "solana-version",
] ]
@ -3792,7 +3809,7 @@ dependencies = [
"solana-move-loader-program", "solana-move-loader-program",
"solana-net-utils", "solana-net-utils",
"solana-runtime", "solana-runtime",
"solana-sdk", "solana-sdk 1.2.9",
"solana-version", "solana-version",
] ]
@ -3809,7 +3826,7 @@ dependencies = [
"rand 0.7.3", "rand 0.7.3",
"solana-logger", "solana-logger",
"solana-runtime", "solana-runtime",
"solana-sdk", "solana-sdk 1.2.9",
"solana_rbpf", "solana_rbpf",
"thiserror", "thiserror",
] ]
@ -3825,7 +3842,7 @@ dependencies = [
"num-traits", "num-traits",
"serde", "serde",
"serde_derive", "serde_derive",
"solana-sdk", "solana-sdk 1.2.9",
] ]
[[package]] [[package]]
@ -3840,7 +3857,7 @@ dependencies = [
"serde", "serde",
"serde_derive", "serde_derive",
"solana-runtime", "solana-runtime",
"solana-sdk", "solana-sdk 1.2.9",
"thiserror", "thiserror",
] ]
@ -3852,7 +3869,7 @@ dependencies = [
"clap", "clap",
"rpassword", "rpassword",
"solana-remote-wallet", "solana-remote-wallet",
"solana-sdk", "solana-sdk 1.2.9",
"thiserror", "thiserror",
"tiny-bip39", "tiny-bip39",
"url 2.1.1", "url 2.1.1",
@ -3880,6 +3897,7 @@ dependencies = [
"serde", "serde",
"serde_derive", "serde_derive",
"serde_json", "serde_json",
"solana-account-decoder",
"solana-budget-program", "solana-budget-program",
"solana-clap-utils", "solana-clap-utils",
"solana-cli-config", "solana-cli-config",
@ -3891,7 +3909,7 @@ dependencies = [
"solana-net-utils", "solana-net-utils",
"solana-remote-wallet", "solana-remote-wallet",
"solana-runtime", "solana-runtime",
"solana-sdk", "solana-sdk 1.2.9",
"solana-stake-program", "solana-stake-program",
"solana-transaction-status", "solana-transaction-status",
"solana-version", "solana-version",
@ -3930,9 +3948,10 @@ dependencies = [
"serde", "serde",
"serde_derive", "serde_derive",
"serde_json", "serde_json",
"solana-account-decoder",
"solana-logger", "solana-logger",
"solana-net-utils", "solana-net-utils",
"solana-sdk", "solana-sdk 1.2.9",
"solana-transaction-status", "solana-transaction-status",
"solana-vote-program", "solana-vote-program",
"thiserror", "thiserror",
@ -3950,7 +3969,7 @@ dependencies = [
"serde", "serde",
"serde_derive", "serde_derive",
"solana-logger", "solana-logger",
"solana-sdk", "solana-sdk 1.2.9",
] ]
[[package]] [[package]]
@ -3989,6 +4008,7 @@ dependencies = [
"serde_json", "serde_json",
"serial_test", "serial_test",
"serial_test_derive", "serial_test_derive",
"solana-account-decoder",
"solana-bpf-loader-program", "solana-bpf-loader-program",
"solana-budget-program", "solana-budget-program",
"solana-clap-utils", "solana-clap-utils",
@ -4004,7 +4024,7 @@ dependencies = [
"solana-perf", "solana-perf",
"solana-rayon-threadlimit", "solana-rayon-threadlimit",
"solana-runtime", "solana-runtime",
"solana-sdk", "solana-sdk 1.2.9",
"solana-stake-program", "solana-stake-program",
"solana-streamer", "solana-streamer",
"solana-sys-tuner", "solana-sys-tuner",
@ -4060,7 +4080,7 @@ dependencies = [
"solana-logger", "solana-logger",
"solana-net-utils", "solana-net-utils",
"solana-runtime", "solana-runtime",
"solana-sdk", "solana-sdk 1.2.9",
"solana-version", "solana-version",
] ]
@ -4074,7 +4094,7 @@ dependencies = [
"log 0.4.8", "log 0.4.8",
"reqwest", "reqwest",
"solana-ledger", "solana-ledger",
"solana-sdk", "solana-sdk 1.2.9",
"tar", "tar",
] ]
@ -4091,7 +4111,7 @@ dependencies = [
"solana-logger", "solana-logger",
"solana-metrics", "solana-metrics",
"solana-runtime", "solana-runtime",
"solana-sdk", "solana-sdk 1.2.9",
"thiserror", "thiserror",
] ]
@ -4100,7 +4120,7 @@ name = "solana-failure-program"
version = "1.2.9" version = "1.2.9"
dependencies = [ dependencies = [
"solana-runtime", "solana-runtime",
"solana-sdk", "solana-sdk 1.2.9",
] ]
[[package]] [[package]]
@ -4117,7 +4137,7 @@ dependencies = [
"solana-clap-utils", "solana-clap-utils",
"solana-logger", "solana-logger",
"solana-metrics", "solana-metrics",
"solana-sdk", "solana-sdk 1.2.9",
"solana-version", "solana-version",
"tokio 0.1.22", "tokio 0.1.22",
"tokio-codec", "tokio-codec",
@ -4137,7 +4157,7 @@ dependencies = [
"solana-genesis-programs", "solana-genesis-programs",
"solana-ledger", "solana-ledger",
"solana-logger", "solana-logger",
"solana-sdk", "solana-sdk 1.2.9",
"solana-stake-program", "solana-stake-program",
"solana-version", "solana-version",
"solana-vote-program", "solana-vote-program",
@ -4153,7 +4173,7 @@ dependencies = [
"solana-budget-program", "solana-budget-program",
"solana-exchange-program", "solana-exchange-program",
"solana-runtime", "solana-runtime",
"solana-sdk", "solana-sdk 1.2.9",
"solana-vest-program", "solana-vest-program",
] ]
@ -4167,7 +4187,7 @@ dependencies = [
"solana-core", "solana-core",
"solana-logger", "solana-logger",
"solana-net-utils", "solana-net-utils",
"solana-sdk", "solana-sdk 1.2.9",
"solana-version", "solana-version",
] ]
@ -4195,7 +4215,7 @@ dependencies = [
"solana-client", "solana-client",
"solana-config-program", "solana-config-program",
"solana-logger", "solana-logger",
"solana-sdk", "solana-sdk 1.2.9",
"solana-version", "solana-version",
"tar", "tar",
"tempdir", "tempdir",
@ -4215,7 +4235,7 @@ dependencies = [
"solana-clap-utils", "solana-clap-utils",
"solana-cli-config", "solana-cli-config",
"solana-remote-wallet", "solana-remote-wallet",
"solana-sdk", "solana-sdk 1.2.9",
"solana-version", "solana-version",
"tiny-bip39", "tiny-bip39",
] ]
@ -4260,7 +4280,7 @@ dependencies = [
"solana-perf", "solana-perf",
"solana-rayon-threadlimit", "solana-rayon-threadlimit",
"solana-runtime", "solana-runtime",
"solana-sdk", "solana-sdk 1.2.9",
"solana-stake-program", "solana-stake-program",
"solana-transaction-status", "solana-transaction-status",
"solana-vote-program", "solana-vote-program",
@ -4291,7 +4311,7 @@ dependencies = [
"solana-ledger", "solana-ledger",
"solana-logger", "solana-logger",
"solana-runtime", "solana-runtime",
"solana-sdk", "solana-sdk 1.2.9",
"solana-stake-program", "solana-stake-program",
"solana-transaction-status", "solana-transaction-status",
"solana-version", "solana-version",
@ -4308,7 +4328,7 @@ dependencies = [
"solana-logger", "solana-logger",
"solana-move-loader-program", "solana-move-loader-program",
"solana-runtime", "solana-runtime",
"solana-sdk", "solana-sdk 1.2.9",
"solana_libra_types", "solana_libra_types",
] ]
@ -4333,7 +4353,7 @@ dependencies = [
"solana-logger", "solana-logger",
"solana-rayon-threadlimit", "solana-rayon-threadlimit",
"solana-runtime", "solana-runtime",
"solana-sdk", "solana-sdk 1.2.9",
"solana-stake-program", "solana-stake-program",
"solana-vest-program", "solana-vest-program",
"solana-vote-program", "solana-vote-program",
@ -4370,7 +4390,7 @@ dependencies = [
"jemallocator", "jemallocator",
"log 0.4.8", "log 0.4.8",
"solana-metrics", "solana-metrics",
"solana-sdk", "solana-sdk 1.2.9",
] ]
[[package]] [[package]]
@ -4379,7 +4399,7 @@ version = "1.2.9"
dependencies = [ dependencies = [
"fast-math", "fast-math",
"hex 0.4.2", "hex 0.4.2",
"solana-sdk", "solana-sdk 1.2.9",
] ]
[[package]] [[package]]
@ -4394,7 +4414,7 @@ dependencies = [
"reqwest", "reqwest",
"serial_test", "serial_test",
"serial_test_derive", "serial_test_derive",
"solana-sdk", "solana-sdk 1.2.9",
] ]
[[package]] [[package]]
@ -4411,7 +4431,7 @@ dependencies = [
"serde_derive", "serde_derive",
"serde_json", "serde_json",
"solana-logger", "solana-logger",
"solana-sdk", "solana-sdk 1.2.9",
"solana_libra_bytecode_verifier", "solana_libra_bytecode_verifier",
"solana_libra_canonical_serialization", "solana_libra_canonical_serialization",
"solana_libra_compiler", "solana_libra_compiler",
@ -4464,7 +4484,7 @@ version = "1.2.9"
dependencies = [ dependencies = [
"log 0.4.8", "log 0.4.8",
"solana-logger", "solana-logger",
"solana-sdk", "solana-sdk 1.2.9",
] ]
[[package]] [[package]]
@ -4484,7 +4504,7 @@ dependencies = [
"num-derive 0.3.0", "num-derive 0.3.0",
"num-traits", "num-traits",
"solana-runtime", "solana-runtime",
"solana-sdk", "solana-sdk 1.2.9",
"thiserror", "thiserror",
] ]
@ -4506,7 +4526,7 @@ dependencies = [
"solana-logger", "solana-logger",
"solana-metrics", "solana-metrics",
"solana-rayon-threadlimit", "solana-rayon-threadlimit",
"solana-sdk", "solana-sdk 1.2.9",
] ]
[[package]] [[package]]
@ -4526,7 +4546,7 @@ dependencies = [
"solana-metrics", "solana-metrics",
"solana-net-utils", "solana-net-utils",
"solana-notifier", "solana-notifier",
"solana-sdk", "solana-sdk 1.2.9",
"solana-stake-program", "solana-stake-program",
"tar", "tar",
] ]
@ -4552,7 +4572,7 @@ dependencies = [
"num-traits", "num-traits",
"parking_lot 0.10.2", "parking_lot 0.10.2",
"semver", "semver",
"solana-sdk", "solana-sdk 1.2.9",
"thiserror", "thiserror",
"url 2.1.1", "url 2.1.1",
] ]
@ -4586,7 +4606,7 @@ dependencies = [
"solana-metrics", "solana-metrics",
"solana-noop-program", "solana-noop-program",
"solana-rayon-threadlimit", "solana-rayon-threadlimit",
"solana-sdk", "solana-sdk 1.2.9",
"solana-stake-program", "solana-stake-program",
"solana-vote-program", "solana-vote-program",
"tempfile", "tempfile",
@ -4601,6 +4621,30 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "solana-sdk"
version = "1.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14b628fa500e0b83df3e96f7cc21dc998d8841a994f1c2109475273e6448afd4"
dependencies = [
"bincode",
"bs58 0.3.1",
"bv",
"hex 0.4.2",
"hmac",
"itertools 0.9.0",
"log 0.4.8",
"num-derive 0.3.0",
"num-traits",
"pbkdf2",
"serde",
"serde_bytes",
"serde_derive",
"sha2",
"solana-sdk-macro 1.2.4",
"thiserror",
]
[[package]] [[package]]
name = "solana-sdk" name = "solana-sdk"
version = "1.2.9" version = "1.2.9"
@ -4630,11 +4674,23 @@ dependencies = [
"sha2", "sha2",
"solana-crate-features", "solana-crate-features",
"solana-logger", "solana-logger",
"solana-sdk-macro", "solana-sdk-macro 1.2.9",
"thiserror", "thiserror",
"tiny-bip39", "tiny-bip39",
] ]
[[package]]
name = "solana-sdk-macro"
version = "1.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da5f311e7735323eb0ad348c68170c2503a2c56cfa1a261646d8182b373fa670"
dependencies = [
"bs58 0.3.1",
"proc-macro2 1.0.18",
"quote 1.0.7",
"syn 1.0.33",
]
[[package]] [[package]]
name = "solana-sdk-macro" name = "solana-sdk-macro"
version = "1.2.9" version = "1.2.9"
@ -4655,7 +4711,7 @@ dependencies = [
"solana-client", "solana-client",
"solana-remote-wallet", "solana-remote-wallet",
"solana-runtime", "solana-runtime",
"solana-sdk", "solana-sdk 1.2.9",
"solana-stake-program", "solana-stake-program",
] ]
@ -4677,7 +4733,7 @@ dependencies = [
"solana-local-cluster", "solana-local-cluster",
"solana-logger", "solana-logger",
"solana-metrics", "solana-metrics",
"solana-sdk", "solana-sdk 1.2.9",
"solana-stake-program", "solana-stake-program",
"solana-transaction-status", "solana-transaction-status",
"solana-version", "solana-version",
@ -4697,7 +4753,7 @@ dependencies = [
"solana-logger", "solana-logger",
"solana-metrics", "solana-metrics",
"solana-notifier", "solana-notifier",
"solana-sdk", "solana-sdk 1.2.9",
"solana-stake-program", "solana-stake-program",
"solana-transaction-status", "solana-transaction-status",
] ]
@ -4715,7 +4771,7 @@ dependencies = [
"solana-config-program", "solana-config-program",
"solana-logger", "solana-logger",
"solana-metrics", "solana-metrics",
"solana-sdk", "solana-sdk 1.2.9",
"solana-vote-program", "solana-vote-program",
"thiserror", "thiserror",
] ]
@ -4731,7 +4787,7 @@ dependencies = [
"solana-measure", "solana-measure",
"solana-metrics", "solana-metrics",
"solana-perf", "solana-perf",
"solana-sdk", "solana-sdk 1.2.9",
"thiserror", "thiserror",
] ]
@ -4770,7 +4826,7 @@ dependencies = [
"solana-core", "solana-core",
"solana-remote-wallet", "solana-remote-wallet",
"solana-runtime", "solana-runtime",
"solana-sdk", "solana-sdk 1.2.9",
"solana-stake-program", "solana-stake-program",
"solana-transaction-status", "solana-transaction-status",
"tempfile", "tempfile",
@ -4781,11 +4837,15 @@ dependencies = [
name = "solana-transaction-status" name = "solana-transaction-status"
version = "1.2.9" version = "1.2.9"
dependencies = [ dependencies = [
"Inflector",
"bincode", "bincode",
"bs58 0.3.1", "bs58 0.3.1",
"lazy_static",
"serde", "serde",
"serde_derive", "serde_derive",
"solana-sdk", "serde_json",
"solana-sdk 1.2.9",
"spl-memo",
] ]
[[package]] [[package]]
@ -4819,7 +4879,7 @@ dependencies = [
"solana-net-utils", "solana-net-utils",
"solana-perf", "solana-perf",
"solana-runtime", "solana-runtime",
"solana-sdk", "solana-sdk 1.2.9",
"solana-version", "solana-version",
"solana-vote-program", "solana-vote-program",
"solana-vote-signer", "solana-vote-signer",
@ -4831,7 +4891,7 @@ version = "1.2.9"
dependencies = [ dependencies = [
"serde", "serde",
"serde_derive", "serde_derive",
"solana-sdk", "solana-sdk 1.2.9",
] ]
[[package]] [[package]]
@ -4846,7 +4906,7 @@ dependencies = [
"serde_derive", "serde_derive",
"solana-config-program", "solana-config-program",
"solana-runtime", "solana-runtime",
"solana-sdk", "solana-sdk 1.2.9",
"thiserror", "thiserror",
] ]
@ -4861,7 +4921,7 @@ dependencies = [
"serde", "serde",
"serde_derive", "serde_derive",
"solana-metrics", "solana-metrics",
"solana-sdk", "solana-sdk 1.2.9",
"thiserror", "thiserror",
] ]
@ -4877,7 +4937,7 @@ dependencies = [
"serde_json", "serde_json",
"solana-clap-utils", "solana-clap-utils",
"solana-metrics", "solana-metrics",
"solana-sdk", "solana-sdk 1.2.9",
"solana-version", "solana-version",
] ]
@ -4895,7 +4955,7 @@ dependencies = [
"solana-logger", "solana-logger",
"solana-metrics", "solana-metrics",
"solana-notifier", "solana-notifier",
"solana-sdk", "solana-sdk 1.2.9",
"solana-transaction-status", "solana-transaction-status",
"solana-version", "solana-version",
"solana-vote-program", "solana-vote-program",
@ -5268,6 +5328,15 @@ version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
[[package]]
name = "spl-memo"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22e6b954ac8b1df3f0bbb6ad1f21607be304f3cc9914bb9107c44b2065c8479e"
dependencies = [
"solana-sdk 1.2.4",
]
[[package]] [[package]]
name = "stable_deref_trait" name = "stable_deref_trait"
version = "1.1.1" version = "1.1.1"

View File

@ -52,6 +52,7 @@ members = [
"sys-tuner", "sys-tuner",
"tokens", "tokens",
"transaction-status", "transaction-status",
"account-decoder",
"upload-perf", "upload-perf",
"net-utils", "net-utils",
"version", "version",

View File

@ -0,0 +1,25 @@
[package]
name = "solana-account-decoder"
version = "1.2.9"
description = "Solana account decoder"
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
repository = "https://github.com/solana-labs/solana"
homepage = "https://solana.com/"
license = "Apache-2.0"
edition = "2018"
[dependencies]
bincode = "1.2.1"
bs58 = "0.3.1"
Inflector = "0.11.4"
lazy_static = "1.4.0"
solana-sdk = { path = "../sdk", version = "1.2.9" }
solana-vote-program = { path = "../programs/vote", version = "1.2.9" }
spl-memo = "1.0.0"
serde = "1.0.112"
serde_derive = "1.0.103"
serde_json = "1.0.54"
thiserror = "1.0"
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]

View File

@ -0,0 +1,80 @@
#[macro_use]
extern crate lazy_static;
#[macro_use]
extern crate serde_derive;
pub mod parse_account_data;
pub mod parse_nonce;
pub mod parse_vote;
use crate::parse_account_data::parse_account_data;
use serde_json::Value;
use solana_sdk::{account::Account, clock::Epoch, pubkey::Pubkey};
use std::str::FromStr;
/// A duplicate representation of an Account for pretty JSON serialization
#[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(rename_all = "camelCase")]
pub struct UiAccount {
pub lamports: u64,
pub data: UiAccountData,
pub owner: String,
pub executable: bool,
pub rent_epoch: Epoch,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase", untagged)]
pub enum UiAccountData {
Binary(String),
Json(Value),
}
impl From<Vec<u8>> for UiAccountData {
fn from(data: Vec<u8>) -> Self {
Self::Binary(bs58::encode(data).into_string())
}
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
#[serde(rename_all = "camelCase")]
pub enum UiAccountEncoding {
Binary,
JsonParsed,
}
impl UiAccount {
pub fn encode(account: Account, encoding: UiAccountEncoding) -> Self {
let data = match encoding {
UiAccountEncoding::Binary => account.data.into(),
UiAccountEncoding::JsonParsed => {
if let Ok(parsed_data) = parse_account_data(&account.owner, &account.data) {
UiAccountData::Json(parsed_data)
} else {
account.data.into()
}
}
};
UiAccount {
lamports: account.lamports,
data,
owner: account.owner.to_string(),
executable: account.executable,
rent_epoch: account.rent_epoch,
}
}
pub fn decode(&self) -> Option<Account> {
let data = match &self.data {
UiAccountData::Json(_) => None,
UiAccountData::Binary(blob) => bs58::decode(blob).into_vec().ok(),
}?;
Some(Account {
lamports: self.lamports,
data,
owner: Pubkey::from_str(&self.owner).ok()?,
executable: self.executable,
rent_epoch: self.rent_epoch,
})
}
}

View File

@ -0,0 +1,80 @@
use crate::{parse_nonce::parse_nonce, parse_vote::parse_vote};
use inflector::Inflector;
use serde_json::{json, Value};
use solana_sdk::{instruction::InstructionError, pubkey::Pubkey, system_program};
use std::{collections::HashMap, str::FromStr};
use thiserror::Error;
lazy_static! {
static ref SYSTEM_PROGRAM_ID: Pubkey =
Pubkey::from_str(&system_program::id().to_string()).unwrap();
static ref VOTE_PROGRAM_ID: Pubkey =
Pubkey::from_str(&solana_vote_program::id().to_string()).unwrap();
pub static ref PARSABLE_PROGRAM_IDS: HashMap<Pubkey, ParsableAccount> = {
let mut m = HashMap::new();
m.insert(*SYSTEM_PROGRAM_ID, ParsableAccount::Nonce);
m.insert(*VOTE_PROGRAM_ID, ParsableAccount::Vote);
m
};
}
#[derive(Error, Debug)]
pub enum ParseAccountError {
#[error("Program not parsable")]
ProgramNotParsable,
#[error("Instruction error")]
InstructionError(#[from] InstructionError),
#[error("Serde json error")]
SerdeJsonError(#[from] serde_json::error::Error),
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum ParsableAccount {
Nonce,
Vote,
}
pub fn parse_account_data(program_id: &Pubkey, data: &[u8]) -> Result<Value, ParseAccountError> {
let program_name = PARSABLE_PROGRAM_IDS
.get(program_id)
.ok_or_else(|| ParseAccountError::ProgramNotParsable)?;
let parsed_json = match program_name {
ParsableAccount::Nonce => serde_json::to_value(parse_nonce(data)?)?,
ParsableAccount::Vote => serde_json::to_value(parse_vote(data)?)?,
};
Ok(json!({
format!("{:?}", program_name).to_kebab_case(): parsed_json
}))
}
#[cfg(test)]
mod test {
use super::*;
use solana_sdk::nonce::{
state::{Data, Versions},
State,
};
use solana_vote_program::vote_state::{VoteState, VoteStateVersions};
#[test]
fn test_parse_account_data() {
let other_program = Pubkey::new_rand();
let data = vec![0; 4];
assert!(parse_account_data(&other_program, &data).is_err());
let vote_state = VoteState::default();
let mut vote_account_data: Vec<u8> = vec![0; VoteState::size_of()];
let versioned = VoteStateVersions::Current(Box::new(vote_state));
VoteState::serialize(&versioned, &mut vote_account_data).unwrap();
let parsed = parse_account_data(&solana_vote_program::id(), &vote_account_data).unwrap();
assert!(parsed.as_object().unwrap().contains_key("vote"));
let nonce_data = Versions::new_current(State::Initialized(Data::default()));
let nonce_account_data = bincode::serialize(&nonce_data).unwrap();
let parsed = parse_account_data(&system_program::id(), &nonce_account_data).unwrap();
assert!(parsed.as_object().unwrap().contains_key("nonce"));
}
}

View File

@ -0,0 +1,66 @@
use crate::parse_account_data::ParseAccountError;
use solana_sdk::{
fee_calculator::FeeCalculator,
instruction::InstructionError,
nonce::{state::Versions, State},
};
pub fn parse_nonce(data: &[u8]) -> Result<UiNonceState, ParseAccountError> {
let nonce_state: Versions = bincode::deserialize(data)
.map_err(|_| ParseAccountError::from(InstructionError::InvalidAccountData))?;
let nonce_state = nonce_state.convert_to_current();
match nonce_state {
State::Uninitialized => Ok(UiNonceState::Uninitialized),
State::Initialized(data) => Ok(UiNonceState::Initialized(UiNonceData {
authority: data.authority.to_string(),
blockhash: data.blockhash.to_string(),
fee_calculator: data.fee_calculator,
})),
}
}
/// A duplicate representation of NonceState for pretty JSON serialization
#[derive(Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub enum UiNonceState {
Uninitialized,
Initialized(UiNonceData),
}
#[derive(Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct UiNonceData {
pub authority: String,
pub blockhash: String,
pub fee_calculator: FeeCalculator,
}
#[cfg(test)]
mod test {
use super::*;
use solana_sdk::{
hash::Hash,
nonce::{
state::{Data, Versions},
State,
},
pubkey::Pubkey,
};
#[test]
fn test_parse_nonce() {
let nonce_data = Versions::new_current(State::Initialized(Data::default()));
let nonce_account_data = bincode::serialize(&nonce_data).unwrap();
assert_eq!(
parse_nonce(&nonce_account_data).unwrap(),
UiNonceState::Initialized(UiNonceData {
authority: Pubkey::default().to_string(),
blockhash: Hash::default().to_string(),
fee_calculator: FeeCalculator::default(),
}),
);
let bad_data = vec![0; 4];
assert!(parse_nonce(&bad_data).is_err());
}
}

View File

@ -0,0 +1,134 @@
use crate::parse_account_data::ParseAccountError;
use solana_sdk::{
clock::{Epoch, Slot},
pubkey::Pubkey,
};
use solana_vote_program::vote_state::{BlockTimestamp, Lockout, VoteState};
pub fn parse_vote(data: &[u8]) -> Result<UiVoteState, ParseAccountError> {
let mut vote_state = VoteState::deserialize(data).map_err(ParseAccountError::from)?;
let epoch_credits = vote_state
.epoch_credits()
.iter()
.map(|(epoch, credits, previous_credits)| UiEpochCredits {
epoch: *epoch,
credits: *credits,
previous_credits: *previous_credits,
})
.collect();
let votes = vote_state
.votes
.iter()
.map(|lockout| UiLockout {
slot: lockout.slot,
confirmation_count: lockout.confirmation_count,
})
.collect();
let authorized_voters = vote_state
.authorized_voters()
.iter()
.map(|(epoch, authorized_voter)| UiAuthorizedVoters {
epoch: *epoch,
authorized_voter: authorized_voter.to_string(),
})
.collect();
let prior_voters = vote_state
.prior_voters()
.buf()
.iter()
.filter(|(pubkey, _, _)| pubkey != &Pubkey::default())
.map(
|(authorized_pubkey, epoch_of_last_authorized_switch, target_epoch)| UiPriorVoters {
authorized_pubkey: authorized_pubkey.to_string(),
epoch_of_last_authorized_switch: *epoch_of_last_authorized_switch,
target_epoch: *target_epoch,
},
)
.collect();
Ok(UiVoteState {
node_pubkey: vote_state.node_pubkey.to_string(),
authorized_withdrawer: vote_state.authorized_withdrawer.to_string(),
commission: vote_state.commission,
votes,
root_slot: vote_state.root_slot,
authorized_voters,
prior_voters,
epoch_credits,
last_timestamp: vote_state.last_timestamp,
})
}
/// A duplicate representation of VoteState for pretty JSON serialization
#[derive(Debug, Serialize, Deserialize, Default, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct UiVoteState {
node_pubkey: String,
authorized_withdrawer: String,
commission: u8,
votes: Vec<UiLockout>,
root_slot: Option<Slot>,
authorized_voters: Vec<UiAuthorizedVoters>,
prior_voters: Vec<UiPriorVoters>,
epoch_credits: Vec<UiEpochCredits>,
last_timestamp: BlockTimestamp,
}
#[derive(Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
struct UiLockout {
slot: Slot,
confirmation_count: u32,
}
impl From<&Lockout> for UiLockout {
fn from(lockout: &Lockout) -> Self {
Self {
slot: lockout.slot,
confirmation_count: lockout.confirmation_count,
}
}
}
#[derive(Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
struct UiAuthorizedVoters {
epoch: Epoch,
authorized_voter: String,
}
#[derive(Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
struct UiPriorVoters {
authorized_pubkey: String,
epoch_of_last_authorized_switch: Epoch,
target_epoch: Epoch,
}
#[derive(Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
struct UiEpochCredits {
epoch: Epoch,
credits: u64,
previous_credits: u64,
}
#[cfg(test)]
mod test {
use super::*;
use solana_vote_program::vote_state::VoteStateVersions;
#[test]
fn test_parse_vote() {
let vote_state = VoteState::default();
let mut vote_account_data: Vec<u8> = vec![0; VoteState::size_of()];
let versioned = VoteStateVersions::Current(Box::new(vote_state));
VoteState::serialize(&versioned, &mut vote_account_data).unwrap();
let mut expected_vote_state = UiVoteState::default();
expected_vote_state.node_pubkey = Pubkey::default().to_string();
expected_vote_state.authorized_withdrawer = Pubkey::default().to_string();
assert_eq!(parse_vote(&vote_account_data).unwrap(), expected_vote_state,);
let bad_data = vec![0; 4];
assert!(parse_vote(&bad_data).is_err());
}
}

View File

@ -27,6 +27,7 @@ reqwest = { version = "0.10.4", default-features = false, features = ["blocking"
serde = "1.0.110" serde = "1.0.110"
serde_derive = "1.0.103" serde_derive = "1.0.103"
serde_json = "1.0.53" serde_json = "1.0.53"
solana-account-decoder = { path = "../account-decoder", version = "1.2.9" }
solana-budget-program = { path = "../programs/budget", version = "1.2.9" } solana-budget-program = { path = "../programs/budget", version = "1.2.9" }
solana-clap-utils = { path = "../clap-utils", version = "1.2.9" } solana-clap-utils = { path = "../clap-utils", version = "1.2.9" }
solana-cli-config = { path = "../cli-config", version = "1.2.9" } solana-cli-config = { path = "../cli-config", version = "1.2.9" }

View File

@ -15,6 +15,7 @@ use clap::{App, AppSettings, Arg, ArgMatches, SubCommand};
use log::*; use log::*;
use num_traits::FromPrimitive; use num_traits::FromPrimitive;
use serde_json::{self, json, Value}; use serde_json::{self, json, Value};
use solana_account_decoder::{UiAccount, UiAccountEncoding};
use solana_budget_program::budget_instruction::{self, BudgetError}; use solana_budget_program::budget_instruction::{self, BudgetError};
use solana_clap_utils::{ use solana_clap_utils::{
commitment::{commitment_arg_with_default, COMMITMENT_ARG}, commitment::{commitment_arg_with_default, COMMITMENT_ARG},
@ -28,7 +29,7 @@ use solana_client::{
client_error::{ClientError, ClientErrorKind, Result as ClientResult}, client_error::{ClientError, ClientErrorKind, Result as ClientResult},
rpc_client::RpcClient, rpc_client::RpcClient,
rpc_config::{RpcLargestAccountsFilter, RpcSendTransactionConfig}, rpc_config::{RpcLargestAccountsFilter, RpcSendTransactionConfig},
rpc_response::{RpcAccount, RpcKeyedAccount}, rpc_response::RpcKeyedAccount,
}; };
#[cfg(not(test))] #[cfg(not(test))]
use solana_faucet::faucet::request_airdrop_transaction; use solana_faucet::faucet::request_airdrop_transaction;
@ -57,7 +58,7 @@ use solana_stake_program::{
stake_instruction::LockupArgs, stake_instruction::LockupArgs,
stake_state::{Lockup, StakeAuthorize}, stake_state::{Lockup, StakeAuthorize},
}; };
use solana_transaction_status::{EncodedTransaction, TransactionEncoding}; use solana_transaction_status::{EncodedTransaction, UiTransactionEncoding};
use solana_vote_program::vote_state::VoteAuthorize; use solana_vote_program::vote_state::VoteAuthorize;
use std::{ use std::{
error, error,
@ -1173,7 +1174,7 @@ fn process_confirm(
if let Some(transaction_status) = status { if let Some(transaction_status) = status {
if config.verbose { if config.verbose {
match rpc_client match rpc_client
.get_confirmed_transaction(signature, TransactionEncoding::Binary) .get_confirmed_transaction(signature, UiTransactionEncoding::Binary)
{ {
Ok(confirmed_transaction) => { Ok(confirmed_transaction) => {
println!( println!(
@ -1226,7 +1227,7 @@ fn process_show_account(
let cli_account = CliAccount { let cli_account = CliAccount {
keyed_account: RpcKeyedAccount { keyed_account: RpcKeyedAccount {
pubkey: account_pubkey.to_string(), pubkey: account_pubkey.to_string(),
account: RpcAccount::encode(account), account: UiAccount::encode(account, UiAccountEncoding::Binary),
}, },
use_lamports_unit, use_lamports_unit,
}; };

View File

@ -5,7 +5,7 @@ use solana_sdk::{
hash::Hash, native_token::lamports_to_sol, program_utils::limited_deserialize, hash::Hash, native_token::lamports_to_sol, program_utils::limited_deserialize,
transaction::Transaction, transaction::Transaction,
}; };
use solana_transaction_status::RpcTransactionStatusMeta; use solana_transaction_status::UiTransactionStatusMeta;
use std::{fmt, io}; use std::{fmt, io};
// Pretty print a "name value" // Pretty print a "name value"
@ -68,7 +68,7 @@ pub fn println_signers(
pub fn write_transaction<W: io::Write>( pub fn write_transaction<W: io::Write>(
w: &mut W, w: &mut W,
transaction: &Transaction, transaction: &Transaction,
transaction_status: &Option<RpcTransactionStatusMeta>, transaction_status: &Option<UiTransactionStatusMeta>,
prefix: &str, prefix: &str,
) -> io::Result<()> { ) -> io::Result<()> {
let message = &transaction.message; let message = &transaction.message;
@ -191,7 +191,7 @@ pub fn write_transaction<W: io::Write>(
pub fn println_transaction( pub fn println_transaction(
transaction: &Transaction, transaction: &Transaction,
transaction_status: &Option<RpcTransactionStatusMeta>, transaction_status: &Option<UiTransactionStatusMeta>,
prefix: &str, prefix: &str,
) { ) {
let mut w = Vec::new(); let mut w = Vec::new();

View File

@ -106,9 +106,10 @@ mod tests {
use crate::{nonce::nonce_arg, offline::blockhash_query::BlockhashQuery}; use crate::{nonce::nonce_arg, offline::blockhash_query::BlockhashQuery};
use clap::App; use clap::App;
use serde_json::{self, json, Value}; use serde_json::{self, json, Value};
use solana_account_decoder::{UiAccount, UiAccountEncoding};
use solana_client::{ use solana_client::{
rpc_request::RpcRequest, rpc_request::RpcRequest,
rpc_response::{Response, RpcAccount, RpcFeeCalculator, RpcResponseContext}, rpc_response::{Response, RpcFeeCalculator, RpcResponseContext},
}; };
use solana_sdk::{ use solana_sdk::{
account::Account, fee_calculator::FeeCalculator, hash::hash, nonce, system_program, account::Account, fee_calculator::FeeCalculator, hash::hash, nonce, system_program,
@ -344,7 +345,7 @@ mod tests {
) )
.unwrap(); .unwrap();
let nonce_pubkey = Pubkey::new(&[4u8; 32]); let nonce_pubkey = Pubkey::new(&[4u8; 32]);
let rpc_nonce_account = RpcAccount::encode(nonce_account); let rpc_nonce_account = UiAccount::encode(nonce_account, UiAccountEncoding::Binary);
let get_account_response = json!(Response { let get_account_response = json!(Response {
context: RpcResponseContext { slot: 1 }, context: RpcResponseContext { slot: 1 },
value: json!(Some(rpc_nonce_account)), value: json!(Some(rpc_nonce_account)),

View File

@ -19,9 +19,10 @@ reqwest = { version = "0.10.4", default-features = false, features = ["blocking"
serde = "1.0.110" serde = "1.0.110"
serde_derive = "1.0.103" serde_derive = "1.0.103"
serde_json = "1.0.53" serde_json = "1.0.53"
solana-transaction-status = { path = "../transaction-status", version = "1.2.9" } solana-account-decoder = { path = "../account-decoder", version = "1.2.9" }
solana-net-utils = { path = "../net-utils", version = "1.2.9" } solana-net-utils = { path = "../net-utils", version = "1.2.9" }
solana-sdk = { path = "../sdk", version = "1.2.9" } solana-sdk = { path = "../sdk", version = "1.2.9" }
solana-transaction-status = { path = "../transaction-status", version = "1.2.9" }
solana-vote-program = { path = "../programs/vote", version = "1.2.9" } solana-vote-program = { path = "../programs/vote", version = "1.2.9" }
thiserror = "1.0" thiserror = "1.0"
tungstenite = "0.10.1" tungstenite = "0.10.1"

View File

@ -11,6 +11,7 @@ use bincode::serialize;
use indicatif::{ProgressBar, ProgressStyle}; use indicatif::{ProgressBar, ProgressStyle};
use log::*; use log::*;
use serde_json::{json, Value}; use serde_json::{json, Value};
use solana_account_decoder::UiAccount;
use solana_sdk::{ use solana_sdk::{
account::Account, account::Account,
clock::{ clock::{
@ -28,7 +29,7 @@ use solana_sdk::{
transaction::{self, Transaction}, transaction::{self, Transaction},
}; };
use solana_transaction_status::{ use solana_transaction_status::{
ConfirmedBlock, ConfirmedTransaction, TransactionEncoding, TransactionStatus, ConfirmedBlock, ConfirmedTransaction, TransactionStatus, UiTransactionEncoding,
}; };
use solana_vote_program::vote_state::MAX_LOCKOUT_HISTORY; use solana_vote_program::vote_state::MAX_LOCKOUT_HISTORY;
use std::{ use std::{
@ -238,13 +239,13 @@ impl RpcClient {
} }
pub fn get_confirmed_block(&self, slot: Slot) -> ClientResult<ConfirmedBlock> { pub fn get_confirmed_block(&self, slot: Slot) -> ClientResult<ConfirmedBlock> {
self.get_confirmed_block_with_encoding(slot, TransactionEncoding::Json) self.get_confirmed_block_with_encoding(slot, UiTransactionEncoding::Json)
} }
pub fn get_confirmed_block_with_encoding( pub fn get_confirmed_block_with_encoding(
&self, &self,
slot: Slot, slot: Slot,
encoding: TransactionEncoding, encoding: UiTransactionEncoding,
) -> ClientResult<ConfirmedBlock> { ) -> ClientResult<ConfirmedBlock> {
self.send(RpcRequest::GetConfirmedBlock, json!([slot, encoding])) self.send(RpcRequest::GetConfirmedBlock, json!([slot, encoding]))
} }
@ -285,7 +286,7 @@ impl RpcClient {
pub fn get_confirmed_transaction( pub fn get_confirmed_transaction(
&self, &self,
signature: &Signature, signature: &Signature,
encoding: TransactionEncoding, encoding: UiTransactionEncoding,
) -> ClientResult<ConfirmedTransaction> { ) -> ClientResult<ConfirmedTransaction> {
self.send( self.send(
RpcRequest::GetConfirmedTransaction, RpcRequest::GetConfirmedTransaction,
@ -452,9 +453,9 @@ impl RpcClient {
let Response { let Response {
context, context,
value: rpc_account, value: rpc_account,
} = serde_json::from_value::<Response<Option<RpcAccount>>>(result_json)?; } = serde_json::from_value::<Response<Option<UiAccount>>>(result_json)?;
trace!("Response account {:?} {:?}", pubkey, rpc_account); trace!("Response account {:?} {:?}", pubkey, rpc_account);
let account = rpc_account.and_then(|rpc_account| rpc_account.decode().ok()); let account = rpc_account.and_then(|rpc_account| rpc_account.decode());
Ok(Response { Ok(Response {
context, context,
value: account, value: account,

View File

@ -1,4 +1,5 @@
use crate::rpc_filter::RpcFilterType; use crate::rpc_filter::RpcFilterType;
use solana_account_decoder::UiAccountEncoding;
use solana_sdk::{clock::Epoch, commitment_config::CommitmentConfig}; use solana_sdk::{clock::Epoch, commitment_config::CommitmentConfig};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
@ -44,8 +45,16 @@ pub struct RpcStakeConfig {
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)] #[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct RpcProgramAccountsConfig { pub struct RpcAccountInfoConfig {
pub filters: Option<Vec<RpcFilterType>>, pub encoding: Option<UiAccountEncoding>,
#[serde(flatten)] #[serde(flatten)]
pub commitment: Option<CommitmentConfig>, pub commitment: Option<CommitmentConfig>,
} }
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RpcProgramAccountsConfig {
pub filters: Option<Vec<RpcFilterType>>,
#[serde(flatten)]
pub account_config: RpcAccountInfoConfig,
}

View File

@ -1,13 +1,12 @@
use crate::{client_error, rpc_request::RpcError}; use crate::client_error;
use solana_account_decoder::UiAccount;
use solana_sdk::{ use solana_sdk::{
account::Account,
clock::{Epoch, Slot}, clock::{Epoch, Slot},
fee_calculator::{FeeCalculator, FeeRateGovernor}, fee_calculator::{FeeCalculator, FeeRateGovernor},
inflation::Inflation, inflation::Inflation,
pubkey::Pubkey,
transaction::{Result, TransactionError}, transaction::{Result, TransactionError},
}; };
use std::{collections::HashMap, net::SocketAddr, str::FromStr}; use std::{collections::HashMap, net::SocketAddr};
pub type RpcResult<T> = client_error::Result<Response<T>>; pub type RpcResult<T> = client_error::Result<Response<T>>;
@ -91,7 +90,7 @@ pub struct RpcInflationRate {
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct RpcKeyedAccount { pub struct RpcKeyedAccount {
pub pubkey: String, pub pubkey: String,
pub account: RpcAccount, pub account: UiAccount,
} }
#[derive(Serialize, Deserialize, Clone, Debug)] #[derive(Serialize, Deserialize, Clone, Debug)]
@ -100,43 +99,6 @@ pub struct RpcSignatureResult {
pub err: Option<TransactionError>, pub err: Option<TransactionError>,
} }
/// A duplicate representation of a Message for pretty JSON serialization
#[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(rename_all = "camelCase")]
pub struct RpcAccount {
pub lamports: u64,
pub data: String,
pub owner: String,
pub executable: bool,
pub rent_epoch: Epoch,
}
impl RpcAccount {
pub fn encode(account: Account) -> Self {
RpcAccount {
lamports: account.lamports,
data: bs58::encode(account.data.clone()).into_string(),
owner: account.owner.to_string(),
executable: account.executable,
rent_epoch: account.rent_epoch,
}
}
pub fn decode(&self) -> std::result::Result<Account, RpcError> {
Ok(Account {
lamports: self.lamports,
data: bs58::decode(self.data.clone()).into_vec().map_err(|_| {
RpcError::RpcRequestError("Could not parse encoded account data".to_string())
})?,
owner: Pubkey::from_str(&self.owner).map_err(|_| {
RpcError::RpcRequestError("Could not parse encoded account owner".to_string())
})?,
executable: self.executable,
rent_epoch: self.rent_epoch,
})
}
}
#[derive(Serialize, Deserialize, Clone, Debug)] #[derive(Serialize, Deserialize, Clone, Debug)]
pub struct RpcContactInfo { pub struct RpcContactInfo {
/// Pubkey of the node as a base-58 string /// Pubkey of the node as a base-58 string

View File

@ -42,11 +42,11 @@ regex = "1.3.7"
serde = "1.0.110" serde = "1.0.110"
serde_derive = "1.0.103" serde_derive = "1.0.103"
serde_json = "1.0.53" serde_json = "1.0.53"
solana-account-decoder = { path = "../account-decoder", version = "1.2.9" }
solana-bpf-loader-program = { path = "../programs/bpf_loader", version = "1.2.9" } solana-bpf-loader-program = { path = "../programs/bpf_loader", version = "1.2.9" }
solana-budget-program = { path = "../programs/budget", version = "1.2.9" } solana-budget-program = { path = "../programs/budget", version = "1.2.9" }
solana-clap-utils = { path = "../clap-utils", version = "1.2.9" } solana-clap-utils = { path = "../clap-utils", version = "1.2.9" }
solana-client = { path = "../client", version = "1.2.9" } solana-client = { path = "../client", version = "1.2.9" }
solana-transaction-status = { path = "../transaction-status", version = "1.2.9" }
solana-faucet = { path = "../faucet", version = "1.2.9" } solana-faucet = { path = "../faucet", version = "1.2.9" }
solana-genesis-programs = { path = "../genesis-programs", version = "1.2.9" } solana-genesis-programs = { path = "../genesis-programs", version = "1.2.9" }
solana-ledger = { path = "../ledger", version = "1.2.9" } solana-ledger = { path = "../ledger", version = "1.2.9" }
@ -60,10 +60,11 @@ solana-runtime = { path = "../runtime", version = "1.2.9" }
solana-sdk = { path = "../sdk", version = "1.2.9" } solana-sdk = { path = "../sdk", version = "1.2.9" }
solana-stake-program = { path = "../programs/stake", version = "1.2.9" } solana-stake-program = { path = "../programs/stake", version = "1.2.9" }
solana-streamer = { path = "../streamer", version = "1.2.9" } solana-streamer = { path = "../streamer", version = "1.2.9" }
solana-sys-tuner = { path = "../sys-tuner", version = "1.2.9" }
solana-transaction-status = { path = "../transaction-status", version = "1.2.9" }
solana-version = { path = "../version", version = "1.2.9" } solana-version = { path = "../version", version = "1.2.9" }
solana-vote-program = { path = "../programs/vote", version = "1.2.9" } solana-vote-program = { path = "../programs/vote", version = "1.2.9" }
solana-vote-signer = { path = "../vote-signer", version = "1.2.9" } solana-vote-signer = { path = "../vote-signer", version = "1.2.9" }
solana-sys-tuner = { path = "../sys-tuner", version = "1.2.9" }
tempfile = "3.1.0" tempfile = "3.1.0"
thiserror = "1.0" thiserror = "1.0"
tokio = "0.1" tokio = "0.1"

View File

@ -13,6 +13,7 @@ use crate::{
use bincode::serialize; use bincode::serialize;
use jsonrpc_core::{Error, Metadata, Result}; use jsonrpc_core::{Error, Metadata, Result};
use jsonrpc_derive::rpc; use jsonrpc_derive::rpc;
use solana_account_decoder::{UiAccount, UiAccountEncoding};
use solana_client::{ use solana_client::{
rpc_config::*, rpc_config::*,
rpc_filter::RpcFilterType, rpc_filter::RpcFilterType,
@ -45,7 +46,7 @@ use solana_sdk::{
}; };
use solana_stake_program::stake_state::StakeState; use solana_stake_program::stake_state::StakeState;
use solana_transaction_status::{ use solana_transaction_status::{
ConfirmedBlock, ConfirmedTransaction, TransactionEncoding, TransactionStatus, ConfirmedBlock, ConfirmedTransaction, TransactionStatus, UiTransactionEncoding,
}; };
use solana_vote_program::vote_state::{VoteState, MAX_LOCKOUT_HISTORY}; use solana_vote_program::vote_state::{VoteState, MAX_LOCKOUT_HISTORY};
use std::{ use std::{
@ -160,10 +161,16 @@ impl JsonRpcRequestProcessor {
pub fn get_account_info( pub fn get_account_info(
&self, &self,
pubkey: &Pubkey, pubkey: &Pubkey,
commitment: Option<CommitmentConfig>, config: Option<RpcAccountInfoConfig>,
) -> Result<RpcResponse<Option<RpcAccount>>> { ) -> Result<RpcResponse<Option<UiAccount>>> {
let bank = self.bank(commitment)?; let config = config.unwrap_or_default();
new_response(&bank, bank.get_account(pubkey).map(RpcAccount::encode)) let bank = self.bank(config.commitment)?;
let encoding = config.encoding.unwrap_or(UiAccountEncoding::Binary);
new_response(
&bank,
bank.get_account(pubkey)
.map(|account| UiAccount::encode(account, encoding)),
)
} }
pub fn get_minimum_balance_for_rent_exemption( pub fn get_minimum_balance_for_rent_exemption(
@ -179,11 +186,13 @@ impl JsonRpcRequestProcessor {
pub fn get_program_accounts( pub fn get_program_accounts(
&self, &self,
program_id: &Pubkey, program_id: &Pubkey,
commitment: Option<CommitmentConfig>, config: Option<RpcAccountInfoConfig>,
filters: Vec<RpcFilterType>, filters: Vec<RpcFilterType>,
) -> Result<Vec<RpcKeyedAccount>> { ) -> Result<Vec<RpcKeyedAccount>> {
Ok(self let config = config.unwrap_or_default();
.bank(commitment)? let bank = self.bank(config.commitment)?;
let encoding = config.encoding.unwrap_or(UiAccountEncoding::Binary);
Ok(bank
.get_program_accounts(Some(&program_id)) .get_program_accounts(Some(&program_id))
.into_iter() .into_iter()
.filter(|(_, account)| { .filter(|(_, account)| {
@ -194,7 +203,7 @@ impl JsonRpcRequestProcessor {
}) })
.map(|(pubkey, account)| RpcKeyedAccount { .map(|(pubkey, account)| RpcKeyedAccount {
pubkey: pubkey.to_string(), pubkey: pubkey.to_string(),
account: RpcAccount::encode(account), account: UiAccount::encode(account, encoding.clone()),
}) })
.collect()) .collect())
} }
@ -493,7 +502,7 @@ impl JsonRpcRequestProcessor {
pub fn get_confirmed_block( pub fn get_confirmed_block(
&self, &self,
slot: Slot, slot: Slot,
encoding: Option<TransactionEncoding>, encoding: Option<UiTransactionEncoding>,
) -> Result<Option<ConfirmedBlock>> { ) -> Result<Option<ConfirmedBlock>> {
if self.config.enable_rpc_transaction_history if self.config.enable_rpc_transaction_history
&& slot && slot
@ -662,7 +671,7 @@ impl JsonRpcRequestProcessor {
pub fn get_confirmed_transaction( pub fn get_confirmed_transaction(
&self, &self,
signature: Signature, signature: Signature,
encoding: Option<TransactionEncoding>, encoding: Option<UiTransactionEncoding>,
) -> Result<Option<ConfirmedTransaction>> { ) -> Result<Option<ConfirmedTransaction>> {
if self.config.enable_rpc_transaction_history { if self.config.enable_rpc_transaction_history {
Ok(self Ok(self
@ -847,8 +856,8 @@ pub trait RpcSol {
&self, &self,
meta: Self::Metadata, meta: Self::Metadata,
pubkey_str: String, pubkey_str: String,
commitment: Option<CommitmentConfig>, config: Option<RpcAccountInfoConfig>,
) -> Result<RpcResponse<Option<RpcAccount>>>; ) -> Result<RpcResponse<Option<UiAccount>>>;
#[rpc(meta, name = "getProgramAccounts")] #[rpc(meta, name = "getProgramAccounts")]
fn get_program_accounts( fn get_program_accounts(
@ -1042,7 +1051,7 @@ pub trait RpcSol {
&self, &self,
meta: Self::Metadata, meta: Self::Metadata,
slot: Slot, slot: Slot,
encoding: Option<TransactionEncoding>, encoding: Option<UiTransactionEncoding>,
) -> Result<Option<ConfirmedBlock>>; ) -> Result<Option<ConfirmedBlock>>;
#[rpc(meta, name = "getBlockTime")] #[rpc(meta, name = "getBlockTime")]
@ -1061,7 +1070,7 @@ pub trait RpcSol {
&self, &self,
meta: Self::Metadata, meta: Self::Metadata,
signature_str: String, signature_str: String,
encoding: Option<TransactionEncoding>, encoding: Option<UiTransactionEncoding>,
) -> Result<Option<ConfirmedTransaction>>; ) -> Result<Option<ConfirmedTransaction>>;
#[rpc(meta, name = "getConfirmedSignaturesForAddress")] #[rpc(meta, name = "getConfirmedSignaturesForAddress")]
@ -1104,11 +1113,11 @@ impl RpcSol for RpcSolImpl {
&self, &self,
meta: Self::Metadata, meta: Self::Metadata,
pubkey_str: String, pubkey_str: String,
commitment: Option<CommitmentConfig>, config: Option<RpcAccountInfoConfig>,
) -> Result<RpcResponse<Option<RpcAccount>>> { ) -> Result<RpcResponse<Option<UiAccount>>> {
debug!("get_account_info rpc request received: {:?}", pubkey_str); debug!("get_account_info rpc request received: {:?}", pubkey_str);
let pubkey = verify_pubkey(pubkey_str)?; let pubkey = verify_pubkey(pubkey_str)?;
meta.get_account_info(&pubkey, commitment) meta.get_account_info(&pubkey, config)
} }
fn get_minimum_balance_for_rent_exemption( fn get_minimum_balance_for_rent_exemption(
@ -1135,15 +1144,18 @@ impl RpcSol for RpcSolImpl {
program_id_str program_id_str
); );
let program_id = verify_pubkey(program_id_str)?; let program_id = verify_pubkey(program_id_str)?;
let (commitment, filters) = if let Some(config) = config { let (config, filters) = if let Some(config) = config {
(config.commitment, config.filters.unwrap_or_default()) (
Some(config.account_config),
config.filters.unwrap_or_default(),
)
} else { } else {
(None, vec![]) (None, vec![])
}; };
for filter in &filters { for filter in &filters {
verify_filter(filter)?; verify_filter(filter)?;
} }
meta.get_program_accounts(&program_id, commitment, filters) meta.get_program_accounts(&program_id, config, filters)
} }
fn get_inflation_governor( fn get_inflation_governor(
@ -1548,7 +1560,7 @@ impl RpcSol for RpcSolImpl {
&self, &self,
meta: Self::Metadata, meta: Self::Metadata,
slot: Slot, slot: Slot,
encoding: Option<TransactionEncoding>, encoding: Option<UiTransactionEncoding>,
) -> Result<Option<ConfirmedBlock>> { ) -> Result<Option<ConfirmedBlock>> {
meta.get_confirmed_block(slot, encoding) meta.get_confirmed_block(slot, encoding)
} }
@ -1570,7 +1582,7 @@ impl RpcSol for RpcSolImpl {
&self, &self,
meta: Self::Metadata, meta: Self::Metadata,
signature_str: String, signature_str: String,
encoding: Option<TransactionEncoding>, encoding: Option<UiTransactionEncoding>,
) -> Result<Option<ConfirmedTransaction>> { ) -> Result<Option<ConfirmedTransaction>> {
let signature = verify_signature(&signature_str)?; let signature = verify_signature(&signature_str)?;
meta.get_confirmed_transaction(signature, encoding) meta.get_confirmed_transaction(signature, encoding)
@ -1679,7 +1691,7 @@ pub mod tests {
system_instruction, system_program, system_transaction, system_instruction, system_program, system_transaction,
transaction::{self, TransactionError}, transaction::{self, TransactionError},
}; };
use solana_transaction_status::{EncodedTransaction, TransactionWithStatusMeta}; use solana_transaction_status::{EncodedTransaction, TransactionWithStatusMeta, UiMessage};
use solana_vote_program::{ use solana_vote_program::{
vote_instruction, vote_instruction,
vote_state::{Vote, VoteInit, MAX_LOCKOUT_HISTORY}, vote_state::{Vote, VoteInit, MAX_LOCKOUT_HISTORY},
@ -3416,7 +3428,11 @@ pub mod tests {
if let EncodedTransaction::Json(transaction) = transaction { if let EncodedTransaction::Json(transaction) = transaction {
if transaction.signatures[0] == confirmed_block_signatures[0].to_string() { if transaction.signatures[0] == confirmed_block_signatures[0].to_string() {
let meta = meta.unwrap(); let meta = meta.unwrap();
assert_eq!(transaction.message.recent_blockhash, blockhash.to_string()); let transaction_recent_blockhash = match transaction.message {
UiMessage::Parsed(message) => message.recent_blockhash,
UiMessage::Raw(message) => message.recent_blockhash,
};
assert_eq!(transaction_recent_blockhash, blockhash.to_string());
assert_eq!(meta.status, Ok(())); assert_eq!(meta.status, Ok(()));
assert_eq!(meta.err, None); assert_eq!(meta.err, None);
} else if transaction.signatures[0] == confirmed_block_signatures[1].to_string() { } else if transaction.signatures[0] == confirmed_block_signatures[1].to_string() {

View File

@ -4,9 +4,8 @@ use crate::rpc_subscriptions::{RpcSubscriptions, RpcVote, SlotInfo};
use jsonrpc_core::{Error, ErrorCode, Result}; use jsonrpc_core::{Error, ErrorCode, Result};
use jsonrpc_derive::rpc; use jsonrpc_derive::rpc;
use jsonrpc_pubsub::{typed::Subscriber, Session, SubscriptionId}; use jsonrpc_pubsub::{typed::Subscriber, Session, SubscriptionId};
use solana_client::rpc_response::{ use solana_account_decoder::UiAccount;
Response as RpcResponse, RpcAccount, RpcKeyedAccount, RpcSignatureResult, use solana_client::rpc_response::{Response as RpcResponse, RpcKeyedAccount, RpcSignatureResult};
};
#[cfg(test)] #[cfg(test)]
use solana_ledger::{bank_forks::BankForks, blockstore::Blockstore}; use solana_ledger::{bank_forks::BankForks, blockstore::Blockstore};
use solana_sdk::{ use solana_sdk::{
@ -37,7 +36,7 @@ pub trait RpcSolPubSub {
fn account_subscribe( fn account_subscribe(
&self, &self,
meta: Self::Metadata, meta: Self::Metadata,
subscriber: Subscriber<RpcResponse<RpcAccount>>, subscriber: Subscriber<RpcResponse<UiAccount>>,
pubkey_str: String, pubkey_str: String,
commitment: Option<CommitmentConfig>, commitment: Option<CommitmentConfig>,
); );
@ -177,7 +176,7 @@ impl RpcSolPubSub for RpcSolPubSubImpl {
fn account_subscribe( fn account_subscribe(
&self, &self,
_meta: Self::Metadata, _meta: Self::Metadata,
subscriber: Subscriber<RpcResponse<RpcAccount>>, subscriber: Subscriber<RpcResponse<UiAccount>>,
pubkey_str: String, pubkey_str: String,
commitment: Option<CommitmentConfig>, commitment: Option<CommitmentConfig>,
) { ) {

View File

@ -8,8 +8,9 @@ use jsonrpc_pubsub::{
SubscriptionId, SubscriptionId,
}; };
use serde::Serialize; use serde::Serialize;
use solana_account_decoder::{UiAccount, UiAccountEncoding};
use solana_client::rpc_response::{ use solana_client::rpc_response::{
Response, RpcAccount, RpcKeyedAccount, RpcResponseContext, RpcSignatureResult, Response, RpcKeyedAccount, RpcResponseContext, RpcSignatureResult,
}; };
use solana_ledger::{bank_forks::BankForks, blockstore::Blockstore}; use solana_ledger::{bank_forks::BankForks, blockstore::Blockstore};
use solana_runtime::bank::Bank; use solana_runtime::bank::Bank;
@ -92,7 +93,7 @@ struct SubscriptionData<S> {
last_notified_slot: RwLock<Slot>, last_notified_slot: RwLock<Slot>,
} }
type RpcAccountSubscriptions = type RpcAccountSubscriptions =
RwLock<HashMap<Pubkey, HashMap<SubscriptionId, SubscriptionData<Response<RpcAccount>>>>>; RwLock<HashMap<Pubkey, HashMap<SubscriptionId, SubscriptionData<Response<UiAccount>>>>>;
type RpcProgramSubscriptions = type RpcProgramSubscriptions =
RwLock<HashMap<Pubkey, HashMap<SubscriptionId, SubscriptionData<Response<RpcKeyedAccount>>>>>; RwLock<HashMap<Pubkey, HashMap<SubscriptionId, SubscriptionData<Response<RpcKeyedAccount>>>>>;
type RpcSignatureSubscriptions = RwLock< type RpcSignatureSubscriptions = RwLock<
@ -227,12 +228,18 @@ impl RpcNotifier {
fn filter_account_result( fn filter_account_result(
result: Option<(Account, Slot)>, result: Option<(Account, Slot)>,
last_notified_slot: Slot, last_notified_slot: Slot,
) -> (Box<dyn Iterator<Item = RpcAccount>>, Slot) { ) -> (Box<dyn Iterator<Item = UiAccount>>, Slot) {
if let Some((account, fork)) = result { if let Some((account, fork)) = result {
// If fork < last_notified_slot this means that we last notified for a fork // If fork < last_notified_slot this means that we last notified for a fork
// and should notify that the account state has been reverted. // and should notify that the account state has been reverted.
if fork != last_notified_slot { if fork != last_notified_slot {
return (Box::new(iter::once(RpcAccount::encode(account))), fork); return (
Box::new(iter::once(UiAccount::encode(
account,
UiAccountEncoding::Binary,
))),
fork,
);
} }
} }
(Box::new(iter::empty()), last_notified_slot) (Box::new(iter::empty()), last_notified_slot)
@ -262,7 +269,7 @@ fn filter_program_results(
.into_iter() .into_iter()
.map(|(pubkey, account)| RpcKeyedAccount { .map(|(pubkey, account)| RpcKeyedAccount {
pubkey: pubkey.to_string(), pubkey: pubkey.to_string(),
account: RpcAccount::encode(account), account: UiAccount::encode(account, UiAccountEncoding::Binary),
}), }),
), ),
last_notified_slot, last_notified_slot,
@ -456,7 +463,7 @@ impl RpcSubscriptions {
pubkey: Pubkey, pubkey: Pubkey,
commitment: Option<CommitmentConfig>, commitment: Option<CommitmentConfig>,
sub_id: SubscriptionId, sub_id: SubscriptionId,
subscriber: Subscriber<Response<RpcAccount>>, subscriber: Subscriber<Response<UiAccount>>,
) { ) {
let commitment_level = commitment let commitment_level = commitment
.unwrap_or_else(CommitmentConfig::single) .unwrap_or_else(CommitmentConfig::single)

View File

@ -7,9 +7,10 @@ use jsonrpc_core_client::transports::ws;
use log::*; use log::*;
use reqwest::{self, header::CONTENT_TYPE}; use reqwest::{self, header::CONTENT_TYPE};
use serde_json::{json, Value}; use serde_json::{json, Value};
use solana_account_decoder::UiAccount;
use solana_client::{ use solana_client::{
rpc_client::{get_rpc_request_str, RpcClient}, rpc_client::{get_rpc_request_str, RpcClient},
rpc_response::{Response, RpcAccount, RpcSignatureResult}, rpc_response::{Response, RpcSignatureResult},
}; };
use solana_core::contact_info::ContactInfo; use solana_core::contact_info::ContactInfo;
use solana_core::{rpc_pubsub::gen_client::Client as PubsubClient, validator::TestValidator}; use solana_core::{rpc_pubsub::gen_client::Client as PubsubClient, validator::TestValidator};
@ -172,7 +173,7 @@ fn test_rpc_subscriptions() {
// Track when subscriptions are ready // Track when subscriptions are ready
let (ready_sender, ready_receiver) = channel::<()>(); let (ready_sender, ready_receiver) = channel::<()>();
// Track account notifications are received // Track account notifications are received
let (account_sender, account_receiver) = channel::<Response<RpcAccount>>(); let (account_sender, account_receiver) = channel::<Response<UiAccount>>();
// Track when status notifications are received // Track when status notifications are received
let (status_sender, status_receiver) = channel::<(String, Response<RpcSignatureResult>)>(); let (status_sender, status_receiver) = channel::<(String, Response<RpcSignatureResult>)>();

View File

@ -138,7 +138,10 @@ Returns all information associated with the account of provided Pubkey
#### Parameters: #### Parameters:
* `<string>` - Pubkey of account to query, as base-58 encoded string * `<string>` - Pubkey of account to query, as base-58 encoded string
* `<object>` - (optional) [Commitment](jsonrpc-api.md#configuring-state-commitment) * `<object>` - (optional) Configuration object containing the following optional fields:
* (optional) [Commitment](jsonrpc-api.md#configuring-state-commitment)
* (optional) `encoding: <string>` - encoding for Account data, either "binary" or jsonParsed". If parameter not provided, the default encoding is binary.
Parsed-JSON encoding attempts to use program-specific state parsers to return more human-readable and explicit account state data. If parsed-JSON is requested but a parser cannot be found, the field falls back to binary encoding, detectable when the `data` field is type `<string>`.
#### Results: #### Results:
@ -148,7 +151,7 @@ The result will be an RpcResponse JSON object with `value` equal to:
* `<object>` - otherwise, a JSON object containing: * `<object>` - otherwise, a JSON object containing:
* `lamports: <u64>`, number of lamports assigned to this account, as a u64 * `lamports: <u64>`, number of lamports assigned to this account, as a u64
* `owner: <string>`, base-58 encoded Pubkey of the program this account has been assigned to * `owner: <string>`, base-58 encoded Pubkey of the program this account has been assigned to
* `data: <string>`, base-58 encoded data associated with the account * `data: <string|object>`, data associated with the account, either as base-58 encoded binary data or JSON format `{<program>: <state>}`, depending on encoding parameter
* `executable: <bool>`, boolean indicating if the account contains a program \(and is strictly read-only\) * `executable: <bool>`, boolean indicating if the account contains a program \(and is strictly read-only\)
* `rentEpoch`: <u64>, the epoch at which this account will next owe rent, as u64 * `rentEpoch`: <u64>, the epoch at which this account will next owe rent, as u64
@ -156,10 +159,16 @@ The result will be an RpcResponse JSON object with `value` equal to:
```bash ```bash
// Request // Request
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "method":"getAccountInfo", "params":["2gVkYWexTHR5Hb2aLeQN3tnngvWzisFKXDUPrgMHpdST"]}' http://localhost:8899 curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "method":"getAccountInfo", "params":["4fYNw3dojWmQ4dXtSGE9epjRGy9pFSx62YypT7avPYvA"]}' http://localhost:8899
// Result // Result
{"jsonrpc":"2.0","result":{"context":{"slot":1},"value":{"executable":false,"owner":"4uQeVj5tqViQh7yWWGStvkEG1Zmhx6uasJtWCJziofM","lamports":1,"data":"Joig2k8Ax4JPMpWhXRyc2jMa7Wejz4X1xqVi3i7QRkmVj1ChUgNc4VNpGUQePJGBAui3c6886peU9GEbjsyeANN8JGStprwLbLwcw5wpPjuQQb9mwrjVmoDQBjj3MzZKgeHn6wmnQ5k8DBFuoCYKWWsJfH2gv9FvCzrN6K1CRcQZzF","rentEpoch":2}},"id":1} {"jsonrpc":"2.0","result":{"context":{"slot":1},"value":{"data":"11116bv5nS2h3y12kD1yUKeMZvGcKLSjQgX6BeV7u1FrjeJcKfsHRTPuR3oZ1EioKtYGiYxpxMG5vpbZLsbcBYBEmZZcMKaSoGx9JZeAuWf","executable":false,"lamports":1000000000,"owner":"11111111111111111111111111111111","rentEpoch":2}},"id":1}
// Request
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "method":"getAccountInfo", "params":["4fYNw3dojWmQ4dXtSGE9epjRGy9pFSx62YypT7avPYvA",{"encoding":"json"}]}' http://localhost:8899
// Result
{"jsonrpc":"2.0","result":{"context":{"slot":1},"value":{"data":{"nonce":{"initialized":{"authority":"Bbqg1M4YVVfbhEzwA9SpC9FhsaG83YMTYoR4a8oTDLX","blockhash":"3xLP3jK6dVJwpeGeTDYTwdDK3TKchUf1gYYGHa4sF3XJ","feeCalculator":{"lamportsPerSignature":5000}}}},"executable":false,"lamports":1000000000,"owner":"11111111111111111111111111111111","rentEpoch":2}},"id":1}
``` ```
### getBalance ### getBalance
@ -280,7 +289,8 @@ Returns identity and transaction information about a confirmed block in the ledg
#### Parameters: #### Parameters:
* `<u64>` - slot, as u64 integer * `<u64>` - slot, as u64 integer
* `<string>` - (optional) encoding for each returned Transaction, either "json" or "binary". If not provided, the default encoding is JSON. * `<string>` - (optional) encoding for each returned Transaction, either "json", "jsonParsed", or "binary". If parameter not provided, the default encoding is JSON.
Parsed-JSON encoding attempts to use program-specific instruction parsers to return more human-readable and explicit data in the `transaction.message.instructions` list. If parsed-JSON is requested but a parser cannot be found, the instruction falls back to regular JSON encoding (`accounts`, `data`, and `programIdIndex` fields).
#### Results: #### Results:
@ -399,7 +409,8 @@ Returns transaction details for a confirmed transaction
#### Parameters: #### Parameters:
* `<string>` - transaction signature as base-58 encoded string * `<string>` - transaction signature as base-58 encoded string
* `<string>` - (optional) encoding for the returned Transaction, either "json" or "binary". If not provided, the default encoding is JSON. * `<string>` - (optional) encoding for the returned Transaction, either "json", "jsonParsed", or "binary". If parameter not provided, the default encoding is JSON.
Parsed-JSON encoding attempts to use program-specific instruction parsers to return more human-readable and explicit data in the `transaction.message.instructions` list. If parsed-JSON is requested but a parser cannot be found, the instruction falls back to regular JSON encoding (`accounts`, `data`, and `programIdIndex` fields).
#### Results: #### Results:
@ -778,6 +789,8 @@ Returns all accounts owned by the provided program Pubkey
* `<string>` - Pubkey of program, as base-58 encoded string * `<string>` - Pubkey of program, as base-58 encoded string
* `<object>` - (optional) Configuration object containing the following optional fields: * `<object>` - (optional) Configuration object containing the following optional fields:
* (optional) [Commitment](jsonrpc-api.md#configuring-state-commitment) * (optional) [Commitment](jsonrpc-api.md#configuring-state-commitment)
* (optional) `encoding: <string>` - encoding for Account data, either "binary" or jsonParsed". If parameter not provided, the default encoding is binary.
Parsed-JSON encoding attempts to use program-specific state parsers to return more human-readable and explicit account state data. If parsed-JSON is requested but a parser cannot be found, the field falls back to binary encoding, detectable when the `data` field is type `<string>`.
* (optional) `filters: <array>` - filter results using various [filter objects](jsonrpc-api.md#filters); account must meet all filter criteria to be included in results * (optional) `filters: <array>` - filter results using various [filter objects](jsonrpc-api.md#filters); account must meet all filter criteria to be included in results
##### Filters: ##### Filters:
@ -795,7 +808,7 @@ The result field will be an array of JSON objects, which will contain:
* `account: <object>` - a JSON object, with the following sub fields: * `account: <object>` - a JSON object, with the following sub fields:
* `lamports: <u64>`, number of lamports assigned to this account, as a u64 * `lamports: <u64>`, number of lamports assigned to this account, as a u64
* `owner: <string>`, base-58 encoded Pubkey of the program this account has been assigned to * `owner: <string>`, base-58 encoded Pubkey of the program this account has been assigned to
* `data: <string>`, base-58 encoded data associated with the account `data: <string|object>`, data associated with the account, either as base-58 encoded binary data or JSON format `{<program>: <state>}`, depending on encoding parameter
* `executable: <bool>`, boolean indicating if the account contains a program \(and is strictly read-only\) * `executable: <bool>`, boolean indicating if the account contains a program \(and is strictly read-only\)
* `rentEpoch`: <u64>, the epoch at which this account will next owe rent, as u64 * `rentEpoch`: <u64>, the epoch at which this account will next owe rent, as u64

View File

@ -37,8 +37,8 @@ use solana_sdk::{
transaction::Transaction, transaction::Transaction,
}; };
use solana_transaction_status::{ use solana_transaction_status::{
ConfirmedBlock, ConfirmedTransaction, EncodedTransaction, Rewards, RpcTransactionStatusMeta, ConfirmedBlock, ConfirmedTransaction, EncodedTransaction, Rewards, TransactionStatusMeta,
TransactionEncoding, TransactionStatusMeta, TransactionWithStatusMeta, TransactionWithStatusMeta, UiTransactionEncoding, UiTransactionStatusMeta,
}; };
use solana_vote_program::{vote_instruction::VoteInstruction, vote_state::TIMESTAMP_SLOT_INTERVAL}; use solana_vote_program::{vote_instruction::VoteInstruction, vote_state::TIMESTAMP_SLOT_INTERVAL};
use std::{ use std::{
@ -1580,7 +1580,7 @@ impl Blockstore {
pub fn get_confirmed_block( pub fn get_confirmed_block(
&self, &self,
slot: Slot, slot: Slot,
encoding: Option<TransactionEncoding>, encoding: Option<UiTransactionEncoding>,
) -> Result<ConfirmedBlock> { ) -> Result<ConfirmedBlock> {
datapoint_info!( datapoint_info!(
"blockstore-rpc-api", "blockstore-rpc-api",
@ -1592,7 +1592,7 @@ impl Blockstore {
if *lowest_cleanup_slot > 0 && *lowest_cleanup_slot >= slot { if *lowest_cleanup_slot > 0 && *lowest_cleanup_slot >= slot {
return Err(BlockstoreError::SlotCleanedUp); return Err(BlockstoreError::SlotCleanedUp);
} }
let encoding = encoding.unwrap_or(TransactionEncoding::Json); let encoding = encoding.unwrap_or(UiTransactionEncoding::Json);
if self.is_root(slot) { if self.is_root(slot) {
let slot_meta_cf = self.db.column::<cf::SlotMeta>(); let slot_meta_cf = self.db.column::<cf::SlotMeta>();
let slot_meta = match slot_meta_cf.get(slot)? { let slot_meta = match slot_meta_cf.get(slot)? {
@ -1643,7 +1643,7 @@ impl Blockstore {
fn map_transactions_to_statuses<'a>( fn map_transactions_to_statuses<'a>(
&self, &self,
slot: Slot, slot: Slot,
encoding: TransactionEncoding, encoding: UiTransactionEncoding,
iterator: impl Iterator<Item = Transaction> + 'a, iterator: impl Iterator<Item = Transaction> + 'a,
) -> Vec<TransactionWithStatusMeta> { ) -> Vec<TransactionWithStatusMeta> {
iterator iterator
@ -1655,7 +1655,7 @@ impl Blockstore {
meta: self meta: self
.read_transaction_status((signature, slot)) .read_transaction_status((signature, slot))
.expect("Expect database get to succeed") .expect("Expect database get to succeed")
.map(RpcTransactionStatusMeta::from), .map(UiTransactionStatusMeta::from),
} }
}) })
.collect() .collect()
@ -1825,7 +1825,7 @@ impl Blockstore {
pub fn get_confirmed_transaction( pub fn get_confirmed_transaction(
&self, &self,
signature: Signature, signature: Signature,
encoding: Option<TransactionEncoding>, encoding: Option<UiTransactionEncoding>,
) -> Result<Option<ConfirmedTransaction>> { ) -> Result<Option<ConfirmedTransaction>> {
datapoint_info!( datapoint_info!(
"blockstore-rpc-api", "blockstore-rpc-api",
@ -1834,7 +1834,7 @@ impl Blockstore {
if let Some((slot, status)) = self.get_transaction_status(signature.clone())? { if let Some((slot, status)) = self.get_transaction_status(signature.clone())? {
let transaction = self.find_transaction_in_slot(slot, signature)? let transaction = self.find_transaction_in_slot(slot, signature)?
.expect("Transaction to exist in slot entries if it exists in statuses and hasn't been cleaned up"); .expect("Transaction to exist in slot entries if it exists in statuses and hasn't been cleaned up");
let encoding = encoding.unwrap_or(TransactionEncoding::Json); let encoding = encoding.unwrap_or(UiTransactionEncoding::Json);
let encoded_transaction = EncodedTransaction::encode(transaction, encoding); let encoded_transaction = EncodedTransaction::encode(transaction, encoding);
Ok(Some(ConfirmedTransaction { Ok(Some(ConfirmedTransaction {
slot, slot,
@ -5164,7 +5164,7 @@ pub mod tests {
.put_meta_bytes(slot - 1, &serialize(&parent_meta).unwrap()) .put_meta_bytes(slot - 1, &serialize(&parent_meta).unwrap())
.unwrap(); .unwrap();
let expected_transactions: Vec<(Transaction, Option<RpcTransactionStatusMeta>)> = entries let expected_transactions: Vec<(Transaction, Option<UiTransactionStatusMeta>)> = entries
.iter() .iter()
.cloned() .cloned()
.filter(|entry| !entry.is_tick()) .filter(|entry| !entry.is_tick())
@ -5228,7 +5228,7 @@ pub mod tests {
.iter() .iter()
.cloned() .cloned()
.map(|(tx, meta)| TransactionWithStatusMeta { .map(|(tx, meta)| TransactionWithStatusMeta {
transaction: EncodedTransaction::encode(tx, TransactionEncoding::Json), transaction: EncodedTransaction::encode(tx, UiTransactionEncoding::Json),
meta, meta,
}) })
.collect(), .collect(),
@ -5249,7 +5249,7 @@ pub mod tests {
.iter() .iter()
.cloned() .cloned()
.map(|(tx, meta)| TransactionWithStatusMeta { .map(|(tx, meta)| TransactionWithStatusMeta {
transaction: EncodedTransaction::encode(tx, TransactionEncoding::Json), transaction: EncodedTransaction::encode(tx, UiTransactionEncoding::Json),
meta, meta,
}) })
.collect(), .collect(),
@ -5866,7 +5866,7 @@ pub mod tests {
blockstore.insert_shreds(shreds, None, false).unwrap(); blockstore.insert_shreds(shreds, None, false).unwrap();
blockstore.set_roots(&[slot - 1, slot]).unwrap(); blockstore.set_roots(&[slot - 1, slot]).unwrap();
let expected_transactions: Vec<(Transaction, Option<RpcTransactionStatusMeta>)> = entries let expected_transactions: Vec<(Transaction, Option<UiTransactionStatusMeta>)> = entries
.iter() .iter()
.cloned() .cloned()
.filter(|entry| !entry.is_tick()) .filter(|entry| !entry.is_tick())
@ -5909,7 +5909,7 @@ pub mod tests {
for (transaction, status) in expected_transactions.clone() { for (transaction, status) in expected_transactions.clone() {
let signature = transaction.signatures[0]; let signature = transaction.signatures[0];
let encoded_transaction = let encoded_transaction =
EncodedTransaction::encode(transaction, TransactionEncoding::Json); EncodedTransaction::encode(transaction, UiTransactionEncoding::Json);
let expected_transaction = ConfirmedTransaction { let expected_transaction = ConfirmedTransaction {
slot, slot,
transaction: TransactionWithStatusMeta { transaction: TransactionWithStatusMeta {
@ -6137,7 +6137,7 @@ pub mod tests {
let map = blockstore.map_transactions_to_statuses( let map = blockstore.map_transactions_to_statuses(
slot, slot,
TransactionEncoding::Json, UiTransactionEncoding::Json,
transactions.into_iter(), transactions.into_iter(),
); );
assert_eq!(map.len(), 5); assert_eq!(map.len(), 5);

View File

@ -7,7 +7,7 @@ use solana_sdk::{
pubkey::Pubkey, signature::Signature, transaction::Transaction, pubkey::Pubkey, signature::Signature, transaction::Transaction,
}; };
use solana_stake_program::{stake_instruction::StakeInstruction, stake_state::Lockup}; use solana_stake_program::{stake_instruction::StakeInstruction, stake_state::Lockup};
use solana_transaction_status::{ConfirmedBlock, RpcTransactionStatusMeta, TransactionEncoding}; use solana_transaction_status::{ConfirmedBlock, UiTransactionEncoding, UiTransactionStatusMeta};
use std::{collections::HashMap, thread::sleep, time::Duration}; use std::{collections::HashMap, thread::sleep, time::Duration};
pub type PubkeyString = String; pub type PubkeyString = String;
@ -65,7 +65,7 @@ impl AccountsInfo {
fn process_transaction( fn process_transaction(
slot: Slot, slot: Slot,
transaction: &Transaction, transaction: &Transaction,
meta: &RpcTransactionStatusMeta, meta: &UiTransactionStatusMeta,
accounts: &mut HashMap<PubkeyString, AccountInfo>, accounts: &mut HashMap<PubkeyString, AccountInfo>,
) { ) {
let mut last_instruction = true; let mut last_instruction = true;
@ -289,7 +289,7 @@ fn load_blocks(
let mut blocks = vec![]; let mut blocks = vec![];
for slot in slots.into_iter() { for slot in slots.into_iter() {
let block = let block =
rpc_client.get_confirmed_block_with_encoding(slot, TransactionEncoding::Binary)?; rpc_client.get_confirmed_block_with_encoding(slot, UiTransactionEncoding::Binary)?;
blocks.push((slot, block)); blocks.push((slot, block));
} }
Ok(blocks) Ok(blocks)

View File

@ -11,9 +11,13 @@ edition = "2018"
[dependencies] [dependencies]
bincode = "1.2.1" bincode = "1.2.1"
bs58 = "0.3.1" bs58 = "0.3.1"
Inflector = "0.11.4"
lazy_static = "1.4.0"
solana-sdk = { path = "../sdk", version = "1.2.9" } solana-sdk = { path = "../sdk", version = "1.2.9" }
spl-memo = "1.0.0"
serde = "1.0.110" serde = "1.0.110"
serde_derive = "1.0.103" serde_derive = "1.0.103"
serde_json = "1.0.54"
[package.metadata.docs.rs] [package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"] targets = ["x86_64-unknown-linux-gnu"]

View File

@ -1,22 +1,48 @@
#[macro_use] #[macro_use]
extern crate lazy_static;
#[macro_use]
extern crate serde_derive; extern crate serde_derive;
pub mod parse_accounts;
pub mod parse_instruction;
use crate::{parse_accounts::parse_accounts, parse_instruction::parse};
use serde_json::Value;
use solana_sdk::{ use solana_sdk::{
clock::Slot, clock::Slot,
commitment_config::CommitmentConfig, commitment_config::CommitmentConfig,
instruction::CompiledInstruction,
message::MessageHeader, message::MessageHeader,
transaction::{Result, Transaction, TransactionError}, transaction::{Result, Transaction, TransactionError},
}; };
/// A duplicate representation of a Message for pretty JSON serialization /// A duplicate representation of an Instruction for pretty JSON serialization
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase", untagged)]
pub enum UiInstruction {
Compiled(UiCompiledInstruction),
Parsed(Value),
}
/// A duplicate representation of a CompiledInstruction for pretty JSON serialization
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct RpcCompiledInstruction { pub struct UiCompiledInstruction {
pub program_id_index: u8, pub program_id_index: u8,
pub accounts: Vec<u8>, pub accounts: Vec<u8>,
pub data: String, pub data: String,
} }
impl From<&CompiledInstruction> for UiCompiledInstruction {
fn from(instruction: &CompiledInstruction) -> Self {
Self {
program_id_index: instruction.program_id_index,
accounts: instruction.accounts.clone(),
data: bs58::encode(instruction.data.clone()).into_string(),
}
}
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct TransactionStatusMeta { pub struct TransactionStatusMeta {
@ -37,9 +63,10 @@ impl Default for TransactionStatusMeta {
} }
} }
/// A duplicate representation of TransactionStatusMeta with `err` field
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct RpcTransactionStatusMeta { pub struct UiTransactionStatusMeta {
pub err: Option<TransactionError>, pub err: Option<TransactionError>,
pub status: Result<()>, // This field is deprecated. See https://github.com/solana-labs/solana/issues/9302 pub status: Result<()>, // This field is deprecated. See https://github.com/solana-labs/solana/issues/9302
pub fee: u64, pub fee: u64,
@ -47,7 +74,7 @@ pub struct RpcTransactionStatusMeta {
pub post_balances: Vec<u64>, pub post_balances: Vec<u64>,
} }
impl From<TransactionStatusMeta> for RpcTransactionStatusMeta { impl From<TransactionStatusMeta> for UiTransactionStatusMeta {
fn from(meta: TransactionStatusMeta) -> Self { fn from(meta: TransactionStatusMeta) -> Self {
Self { Self {
err: meta.status.clone().err(), err: meta.status.clone().err(),
@ -104,76 +131,112 @@ pub struct ConfirmedTransaction {
/// A duplicate representation of a Transaction for pretty JSON serialization /// A duplicate representation of a Transaction for pretty JSON serialization
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct RpcTransaction { pub struct UiTransaction {
pub signatures: Vec<String>, pub signatures: Vec<String>,
pub message: RpcMessage, pub message: UiMessage,
} }
/// A duplicate representation of a Message for pretty JSON serialization #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase", untagged)]
pub enum UiMessage {
Parsed(UiParsedMessage),
Raw(UiRawMessage),
}
/// A duplicate representation of a Message, in raw format, for pretty JSON serialization
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct RpcMessage { pub struct UiRawMessage {
pub header: MessageHeader, pub header: MessageHeader,
pub account_keys: Vec<String>, pub account_keys: Vec<String>,
pub recent_blockhash: String, pub recent_blockhash: String,
pub instructions: Vec<RpcCompiledInstruction>, pub instructions: Vec<UiCompiledInstruction>,
}
/// A duplicate representation of a Message, in parsed format, for pretty JSON serialization
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct UiParsedMessage {
pub account_keys: Value,
pub recent_blockhash: String,
pub instructions: Vec<UiInstruction>,
} }
#[derive(Debug, PartialEq, Serialize, Deserialize)] #[derive(Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct TransactionWithStatusMeta { pub struct TransactionWithStatusMeta {
pub transaction: EncodedTransaction, pub transaction: EncodedTransaction,
pub meta: Option<RpcTransactionStatusMeta>, pub meta: Option<UiTransactionStatusMeta>,
} }
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] #[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub enum TransactionEncoding { pub enum UiTransactionEncoding {
Binary, Binary,
Json, Json,
JsonParsed,
} }
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase", untagged)] #[serde(rename_all = "camelCase", untagged)]
pub enum EncodedTransaction { pub enum EncodedTransaction {
Binary(String), Binary(String),
Json(RpcTransaction), Json(UiTransaction),
} }
impl EncodedTransaction { impl EncodedTransaction {
pub fn encode(transaction: Transaction, encoding: TransactionEncoding) -> Self { pub fn encode(transaction: Transaction, encoding: UiTransactionEncoding) -> Self {
if encoding == TransactionEncoding::Json { match encoding {
EncodedTransaction::Json(RpcTransaction { UiTransactionEncoding::Binary => EncodedTransaction::Binary(
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 {
EncodedTransaction::Binary(
bs58::encode(bincode::serialize(&transaction).unwrap()).into_string(), bs58::encode(bincode::serialize(&transaction).unwrap()).into_string(),
) ),
_ => {
let message = if encoding == UiTransactionEncoding::Json {
UiMessage::Raw(UiRawMessage {
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| instruction.into())
.collect(),
})
} else {
UiMessage::Parsed(UiParsedMessage {
account_keys: parse_accounts(&transaction.message),
recent_blockhash: transaction.message.recent_blockhash.to_string(),
instructions: transaction
.message
.instructions
.iter()
.map(|instruction| {
let program_id =
instruction.program_id(&transaction.message.account_keys);
if let Some(parsed_instruction) = parse(program_id, instruction) {
UiInstruction::Parsed(parsed_instruction)
} else {
UiInstruction::Compiled(instruction.into())
}
})
.collect(),
})
};
EncodedTransaction::Json(UiTransaction {
signatures: transaction
.signatures
.iter()
.map(|sig| sig.to_string())
.collect(),
message,
})
}
} }
} }
pub fn decode(&self) -> Option<Transaction> { pub fn decode(&self) -> Option<Transaction> {

View File

@ -0,0 +1,56 @@
use serde_json::{json, Value};
use solana_sdk::message::Message;
type AccountAttributes = Vec<AccountAttribute>;
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
enum AccountAttribute {
Signer,
Writable,
}
pub fn parse_accounts(message: &Message) -> Value {
let mut accounts: Vec<Value> = vec![];
for (i, account_key) in message.account_keys.iter().enumerate() {
let mut attributes: AccountAttributes = vec![];
if message.is_writable(i) {
attributes.push(AccountAttribute::Writable);
}
if message.is_signer(i) {
attributes.push(AccountAttribute::Signer);
}
accounts.push(json!({ account_key.to_string(): attributes }));
}
json!(accounts)
}
#[cfg(test)]
mod test {
use super::*;
use solana_sdk::{message::MessageHeader, pubkey::Pubkey};
#[test]
fn test_parse_accounts() {
let pubkey0 = Pubkey::new_rand();
let pubkey1 = Pubkey::new_rand();
let pubkey2 = Pubkey::new_rand();
let pubkey3 = Pubkey::new_rand();
let mut message = Message::default();
message.header = MessageHeader {
num_required_signatures: 2,
num_readonly_signed_accounts: 1,
num_readonly_unsigned_accounts: 1,
};
message.account_keys = vec![pubkey0, pubkey1, pubkey2, pubkey3];
let expected_json = json!([
{pubkey0.to_string(): ["writable", "signer"]},
{pubkey1.to_string(): ["signer"]},
{pubkey2.to_string(): ["writable"]},
{pubkey3.to_string(): []},
]);
assert_eq!(parse_accounts(&message), expected_json);
}
}

View File

@ -0,0 +1,59 @@
use inflector::Inflector;
use serde_json::{json, Value};
use solana_sdk::{instruction::CompiledInstruction, pubkey::Pubkey};
use std::{
collections::HashMap,
str::{from_utf8, FromStr},
};
lazy_static! {
static ref MEMO_PROGRAM_ID: Pubkey = Pubkey::from_str(&spl_memo::id().to_string()).unwrap();
static ref PARSABLE_PROGRAM_IDS: HashMap<Pubkey, ParsableProgram> = {
let mut m = HashMap::new();
m.insert(*MEMO_PROGRAM_ID, ParsableProgram::SplMemo);
m
};
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
enum ParsableProgram {
SplMemo,
}
pub fn parse(program_id: &Pubkey, instruction: &CompiledInstruction) -> Option<Value> {
PARSABLE_PROGRAM_IDS.get(program_id).map(|program_name| {
let parsed_json = match program_name {
ParsableProgram::SplMemo => parse_memo(instruction),
};
json!({ format!("{:?}", program_name).to_kebab_case(): parsed_json })
})
}
fn parse_memo(instruction: &CompiledInstruction) -> Value {
Value::String(from_utf8(&instruction.data).unwrap().to_string())
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_parse() {
let memo_instruction = CompiledInstruction {
program_id_index: 0,
accounts: vec![],
data: vec![240, 159, 166, 150],
};
let expected_json = json!({
"spl-memo": "🦖"
});
assert_eq!(
parse(&MEMO_PROGRAM_ID, &memo_instruction),
Some(expected_json)
);
let non_parsable_program_id = Pubkey::new(&[1; 32]);
assert_eq!(parse(&non_parsable_program_id, &memo_instruction), None);
}
}

View File

@ -15,7 +15,7 @@ use solana_sdk::{
clock::Slot, hash::Hash, native_token::lamports_to_sol, program_utils::limited_deserialize, clock::Slot, hash::Hash, native_token::lamports_to_sol, program_utils::limited_deserialize,
pubkey::Pubkey, pubkey::Pubkey,
}; };
use solana_transaction_status::{ConfirmedBlock, TransactionEncoding}; use solana_transaction_status::{ConfirmedBlock, UiTransactionEncoding};
use solana_vote_program::vote_instruction::VoteInstruction; use solana_vote_program::vote_instruction::VoteInstruction;
use std::{ use std::{
error, error,
@ -229,7 +229,7 @@ fn load_blocks(
let mut blocks = vec![]; let mut blocks = vec![];
for slot in slots.into_iter() { for slot in slots.into_iter() {
let block = let block =
rpc_client.get_confirmed_block_with_encoding(slot, TransactionEncoding::Binary)?; rpc_client.get_confirmed_block_with_encoding(slot, UiTransactionEncoding::Binary)?;
blocks.push((slot, block)); blocks.push((slot, block));
} }
Ok(blocks) Ok(blocks)