Update getSignatureStatus: support multiple signatures, include slot in each response item (#9022)

* Rename enable-rpc-get-confirmed-block

* Rename RpcTransactionStatus -> RpcTransactionStatusMeta

* Return simplified RpcTransactionStatus; Add support for multiple transactions

* Update docs

* typo
This commit is contained in:
Tyera Eulberg 2020-03-23 11:25:39 -06:00 committed by GitHub
parent 1a5b01676d
commit 1b8f9e75dd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 136 additions and 120 deletions

View File

@ -2,7 +2,7 @@ use crate::{
client_error::Result,
generic_rpc_client_request::GenericRpcClientRequest,
rpc_request::RpcRequest,
rpc_response::{Response, RpcResponseContext},
rpc_response::{Response, RpcResponseContext, RpcTransactionStatus},
};
use serde_json::{Number, Value};
use solana_sdk::{
@ -87,19 +87,22 @@ impl GenericRpcClientRequest for MockRpcClientRequest {
value: serde_json::to_value(FeeRateGovernor::default()).unwrap(),
})?,
RpcRequest::GetSignatureStatus => {
let response: Option<transaction::Result<()>> = if self.url == "account_in_use" {
Some(Err(TransactionError::AccountInUse))
let status: transaction::Result<()> = if self.url == "account_in_use" {
Err(TransactionError::AccountInUse)
} else if self.url == "instruction_error" {
Some(Err(TransactionError::InstructionError(
Err(TransactionError::InstructionError(
0,
InstructionError::UninitializedAccount,
)))
} else if self.url == "sig_not_found" {
))
} else {
Ok(())
};
let status = if self.url == "sig_not_found" {
None
} else {
Some(Ok(()))
Some(RpcTransactionStatus { status, slot: 1 })
};
serde_json::to_value(response).unwrap()
serde_json::to_value(vec![status])?
}
RpcRequest::GetTransactionCount => Value::Number(Number::from(1234)),
RpcRequest::GetSlot => Value::Number(Number::from(0)),

View File

@ -7,7 +7,7 @@ use crate::{
rpc_response::{
Response, RpcAccount, RpcBlockhashFeeCalculator, RpcConfirmedBlock, RpcContactInfo,
RpcEpochInfo, RpcFeeCalculator, RpcFeeRateGovernor, RpcIdentity, RpcKeyedAccount,
RpcLeaderSchedule, RpcResult, RpcVersionInfo, RpcVoteAccountStatus,
RpcLeaderSchedule, RpcResult, RpcTransactionStatus, RpcVersionInfo, RpcVoteAccountStatus,
},
};
use bincode::serialize;
@ -120,12 +120,12 @@ impl RpcClient {
) -> ClientResult<Option<transaction::Result<()>>> {
let signature_status = self.client.send(
&RpcRequest::GetSignatureStatus,
json!([signature.to_string(), commitment_config]),
json!([[signature.to_string()], commitment_config]),
5,
)?;
let result: Option<transaction::Result<()>> =
let result: Vec<Option<RpcTransactionStatus>> =
serde_json::from_value(signature_status).unwrap();
Ok(result)
Ok(result[0].clone().map(|status_meta| status_meta.status))
}
pub fn get_slot(&self) -> ClientResult<Slot> {

View File

@ -97,7 +97,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" */

View File

@ -52,7 +52,7 @@ pub struct RpcConfirmedBlock {
#[serde(rename_all = "camelCase")]
pub struct RpcTransactionWithStatusMeta {
pub transaction: RpcEncodedTransaction,
pub meta: Option<RpcTransactionStatus>,
pub meta: Option<RpcTransactionStatusMeta>,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
@ -136,13 +136,20 @@ pub struct RpcCompiledInstruction {
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RpcTransactionStatus {
pub struct RpcTransactionStatusMeta {
pub status: Result<()>,
pub fee: u64,
pub pre_balances: Vec<u64>,
pub post_balances: Vec<u64>,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RpcTransactionStatus {
pub slot: Slot,
pub status: Result<()>,
}
#[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(rename_all = "camelCase")]
pub struct RpcBlockhashFeeCalculator {

View File

@ -7,13 +7,7 @@ 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,
@ -29,7 +23,7 @@ use solana_sdk::{
pubkey::Pubkey,
signature::Signature,
timing::slot_duration_from_slots_per_year,
transaction::{self, Transaction},
transaction::Transaction,
};
use solana_vote_program::vote_state::{VoteState, MAX_LOCKOUT_HISTORY};
use std::{
@ -52,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>,
}
@ -374,7 +368,7 @@ impl JsonRpcRequestProcessor {
slot: Slot,
encoding: Option<RpcTransactionEncoding>,
) -> Result<Option<RpcConfirmedBlock>> {
if self.config.enable_get_confirmed_block {
if self.config.enable_rpc_transaction_history {
Ok(self.blockstore.get_confirmed_block(slot, encoding).ok())
} else {
Ok(None)
@ -422,6 +416,27 @@ impl JsonRpcRequestProcessor {
.ok()
.unwrap_or(None))
}
pub fn get_signature_status(
&self,
signatures: Vec<Signature>,
commitment: Option<CommitmentConfig>,
) -> Result<Vec<Option<RpcTransactionStatus>>> {
let mut statuses: Vec<Option<RpcTransactionStatus>> = vec![];
let bank = self.bank(commitment);
for signature in signatures {
let status = bank.get_signature_confirmation_status(&signature).map(
|SignatureConfirmationStatus { slot, status, .. }| RpcTransactionStatus {
slot,
status,
},
);
statuses.push(status);
}
Ok(statuses)
}
}
fn get_tpu_addr(cluster_info: &Arc<RwLock<ClusterInfo>>) -> Result<SocketAddr> {
@ -548,9 +563,9 @@ pub trait RpcSol {
fn get_signature_status(
&self,
meta: Self::Metadata,
signature_str: String,
signature_strs: Vec<String>,
commitment: Option<CommitmentConfig>,
) -> Result<Option<transaction::Result<()>>>;
) -> Result<Vec<Option<RpcTransactionStatus>>>;
#[rpc(meta, name = "getSlot")]
fn get_slot(&self, meta: Self::Metadata, commitment: Option<CommitmentConfig>) -> Result<u64>;
@ -894,11 +909,17 @@ impl RpcSol for RpcSolImpl {
fn get_signature_status(
&self,
meta: Self::Metadata,
signature_str: String,
signature_strs: Vec<String>,
commitment: Option<CommitmentConfig>,
) -> Result<Option<transaction::Result<()>>> {
self.get_signature_confirmation(meta, signature_str, commitment)
.map(|res| res.map(|x| x.status))
) -> Result<Vec<Option<RpcTransactionStatus>>> {
let mut signatures: Vec<Signature> = vec![];
for signature_str in signature_strs {
signatures.push(verify_signature(&signature_str)?);
}
meta.request_processor
.read()
.unwrap()
.get_signature_status(signatures, commitment)
}
fn get_slot(&self, meta: Self::Metadata, commitment: Option<CommitmentConfig>) -> Result<u64> {
@ -1210,7 +1231,7 @@ pub mod tests {
rpc_port,
signature::{Keypair, Signer},
system_transaction,
transaction::TransactionError,
transaction::{self, TransactionError},
};
use solana_vote_program::{
vote_instruction,
@ -1339,7 +1360,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()
},
@ -1783,66 +1804,50 @@ pub mod tests {
meta,
blockhash,
alice,
confirmed_block_signatures,
..
} = start_rpc_handler_with_tx(&bob_pubkey);
let tx = system_transaction::transfer(&alice, &bob_pubkey, 20, blockhash);
let req = format!(
r#"{{"jsonrpc":"2.0","id":1,"method":"getSignatureStatus","params":["{}"]}}"#,
tx.signatures[0]
r#"{{"jsonrpc":"2.0","id":1,"method":"getSignatureStatus","params":[["{}"]]}}"#,
confirmed_block_signatures[0]
);
let res = io.handle_request_sync(&req, meta.clone());
let expected_res: Option<transaction::Result<()>> = Some(Ok(()));
let expected = json!({
"jsonrpc": "2.0",
"result": expected_res,
"id": 1
});
let expected: Response =
serde_json::from_value(expected).expect("expected response deserialization");
let result: Response = serde_json::from_str(&res.expect("actual response"))
.expect("actual response deserialization");
assert_eq!(expected, result);
let expected_res: transaction::Result<()> = Ok(());
let json: Value = serde_json::from_str(&res.unwrap()).unwrap();
let result: Vec<Option<RpcTransactionStatus>> =
serde_json::from_value(json["result"].clone())
.expect("actual response deserialization");
assert_eq!(expected_res, result[0].as_ref().unwrap().status);
// Test getSignatureStatus request on unprocessed tx
let tx = system_transaction::transfer(&alice, &bob_pubkey, 10, blockhash);
let req = format!(
r#"{{"jsonrpc":"2.0","id":1,"method":"getSignatureStatus","params":["{}"]}}"#,
r#"{{"jsonrpc":"2.0","id":1,"method":"getSignatureStatus","params":[["{}"]]}}"#,
tx.signatures[0]
);
let res = io.handle_request_sync(&req, meta.clone());
let expected_res: Option<String> = None;
let expected = json!({
"jsonrpc": "2.0",
"result": expected_res,
"id": 1
});
let expected: Response =
serde_json::from_value(expected).expect("expected response deserialization");
let result: Response = serde_json::from_str(&res.expect("actual response"))
.expect("actual response deserialization");
assert_eq!(expected, result);
let json: Value = serde_json::from_str(&res.unwrap()).unwrap();
let result: Vec<Option<RpcTransactionStatus>> =
serde_json::from_value(json["result"].clone())
.expect("actual response deserialization");
assert!(result[0].is_none());
// Test getSignatureStatus request on a TransactionError
let tx = system_transaction::transfer(&alice, &bob_pubkey, std::u64::MAX, blockhash);
let req = format!(
r#"{{"jsonrpc":"2.0","id":1,"method":"getSignatureStatus","params":["{}"]}}"#,
tx.signatures[0]
r#"{{"jsonrpc":"2.0","id":1,"method":"getSignatureStatus","params":[["{}"]]}}"#,
confirmed_block_signatures[1]
);
let res = io.handle_request_sync(&req, meta);
let expected_res: Option<transaction::Result<()>> = Some(Err(
TransactionError::InstructionError(0, InstructionError::CustomError(1)),
let res = io.handle_request_sync(&req, meta.clone());
let expected_res: transaction::Result<()> = Err(TransactionError::InstructionError(
0,
InstructionError::CustomError(1),
));
let expected = json!({
"jsonrpc": "2.0",
"result": expected_res,
"id": 1
});
let expected: Response =
serde_json::from_value(expected).expect("expected response deserialization");
let result: Response = serde_json::from_str(&res.expect("actual response"))
.expect("actual response deserialization");
assert_eq!(expected, result);
let json: Value = serde_json::from_str(&res.unwrap()).unwrap();
let result: Vec<Option<RpcTransactionStatus>> =
serde_json::from_value(json["result"].clone())
.expect("actual response deserialization");
assert_eq!(expected_res, result[0].as_ref().unwrap().status);
}
#[test]

View File

@ -1,5 +1,5 @@
use crossbeam_channel::{Receiver, RecvTimeoutError};
use solana_client::rpc_response::RpcTransactionStatus;
use solana_client::rpc_response::RpcTransactionStatusMeta;
use solana_ledger::{blockstore::Blockstore, blockstore_processor::TransactionStatusBatch};
use solana_runtime::{
bank::{Bank, HashAgeKind},
@ -73,7 +73,7 @@ impl TransactionStatusService {
blockstore
.write_transaction_status(
(slot, transaction.signatures[0]),
&RpcTransactionStatus {
&RpcTransactionStatusMeta {
status,
fee,
pre_balances,

View File

@ -266,7 +266,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),
@ -281,7 +281,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),

View File

@ -693,24 +693,30 @@ Returns the status of a given signature. This method is similar to [confirmTrans
#### Parameters:
* `<string>` - Signature of Transaction to confirm, as base-58 encoded string
* `<object>` - (optional) [Commitment](jsonrpc-api.md#configuring-state-commitment)
* `<array>` - An array of transaction signatures to confirm, as base-58 encoded strings
* `<object>` - (optional) Extended Rpc configuration, containing the following optional fields:
* `commitment: <string>` - [Commitment](jsonrpc-api.md#configuring-state-commitment)
* `searchTransactionHistory: <bool>` - whether to search the ledger transaction status cache, which may be expensive
#### Results:
An array of:
* `<null>` - Unknown transaction
* `<object>` - Transaction status:
* `"Ok": <null>` - Transaction was successful
* `"Err": <ERR>` - Transaction failed with TransactionError [TransactionError definitions](https://github.com/solana-labs/solana/blob/master/sdk/src/transaction.rs#L14)
* `<object>`
* `slot: <u64>` - The slot the transaction was processed
* `status: <object>` - Transaction status
* `"Ok": <null>` - Transaction was successful
* `"Err": <ERR>` - Transaction failed with TransactionError [TransactionError definitions](https://github.com/solana-labs/solana/blob/master/sdk/src/transaction.rs#L14)
#### Example:
```bash
// Request
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "method":"getSignatureStatus", "params":["5VERv8NMvzbJMEkV8xnrLkEaWRtSz9CosKDYjCJjBRnbJLgp8uirBgmQpjKhoR4tjF3ZpRzrFmBV6UjKdiSZkQUW"]}' http://localhost:8899
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "method":"getSignatureStatus", "params":[["5VERv8NMvzbJMEkV8xnrLkEaWRtSz9CosKDYjCJjBRnbJLgp8uirBgmQpjKhoR4tjF3ZpRzrFmBV6UjKdiSZkQUW", "5j7s6NiJS3JAkvgkoc18WVAsiSaci2pxB2A6ueCJP4tprA2TFg9wSyTLeYouxPBJEMzJinENTkpA52YStRW5Dia7"]]]}' http://localhost:8899
// Result
{"jsonrpc":"2.0","result":{"Ok": null},"id":1}
{"jsonrpc":"2.0","result":[{"slot": 72, "status": {"Ok": null}}, null],"id":1}
```
### getSlot

View File

@ -24,7 +24,7 @@ use rayon::{
use rocksdb::DBRawIterator;
use solana_client::rpc_response::{
RpcConfirmedBlock, RpcEncodedTransaction, RpcRewards, RpcTransactionEncoding,
RpcTransactionStatus, RpcTransactionWithStatusMeta,
RpcTransactionStatusMeta, RpcTransactionWithStatusMeta,
};
use solana_measure::measure::Measure;
use solana_metrics::{datapoint_debug, datapoint_error};
@ -1501,7 +1501,7 @@ impl Blockstore {
pub fn write_transaction_status(
&self,
index: (Slot, Signature),
status: &RpcTransactionStatus,
status: &RpcTransactionStatusMeta,
) -> Result<()> {
self.transaction_status_cf.put(index, status)
}
@ -4829,7 +4829,7 @@ pub mod tests {
.put_meta_bytes(slot - 1, &serialize(&parent_meta).unwrap())
.unwrap();
let expected_transactions: Vec<(Transaction, Option<RpcTransactionStatus>)> = entries
let expected_transactions: Vec<(Transaction, Option<RpcTransactionStatusMeta>)> = entries
.iter()
.cloned()
.filter(|entry| !entry.is_tick())
@ -4846,7 +4846,7 @@ pub mod tests {
.transaction_status_cf
.put(
(slot, signature),
&RpcTransactionStatus {
&RpcTransactionStatusMeta {
status: Ok(()),
fee: 42,
pre_balances: pre_balances.clone(),
@ -4858,7 +4858,7 @@ pub mod tests {
.transaction_status_cf
.put(
(slot + 1, signature),
&RpcTransactionStatus {
&RpcTransactionStatusMeta {
status: Ok(()),
fee: 42,
pre_balances: pre_balances.clone(),
@ -4868,7 +4868,7 @@ pub mod tests {
.unwrap();
(
transaction,
Some(RpcTransactionStatus {
Some(RpcTransactionStatusMeta {
status: Ok(()),
fee: 42,
pre_balances,
@ -5131,7 +5131,7 @@ pub mod tests {
assert!(transaction_status_cf
.put(
(0, Signature::default()),
&RpcTransactionStatus {
&RpcTransactionStatusMeta {
status: solana_sdk::transaction::Result::<()>::Err(
TransactionError::AccountNotFound
),
@ -5143,7 +5143,7 @@ pub mod tests {
.is_ok());
// result found
let RpcTransactionStatus {
let RpcTransactionStatusMeta {
status,
fee,
pre_balances,
@ -5161,7 +5161,7 @@ pub mod tests {
assert!(transaction_status_cf
.put(
(9, Signature::default()),
&RpcTransactionStatus {
&RpcTransactionStatusMeta {
status: solana_sdk::transaction::Result::<()>::Ok(()),
fee: 9u64,
pre_balances: pre_balances_vec.clone(),
@ -5171,7 +5171,7 @@ pub mod tests {
.is_ok());
// result found
let RpcTransactionStatus {
let RpcTransactionStatusMeta {
status,
fee,
pre_balances,
@ -5226,7 +5226,7 @@ pub mod tests {
transaction_status_cf
.put(
(slot, transaction.signatures[0]),
&RpcTransactionStatus {
&RpcTransactionStatusMeta {
status: solana_sdk::transaction::Result::<()>::Err(
TransactionError::AccountNotFound,
),

View File

@ -10,7 +10,7 @@ use rocksdb::{
};
use serde::de::DeserializeOwned;
use serde::Serialize;
use solana_client::rpc_response::{RpcRewards, RpcTransactionStatus};
use solana_client::rpc_response::{RpcRewards, RpcTransactionStatusMeta};
use solana_sdk::{clock::Slot, signature::Signature};
use std::{collections::HashMap, fs, marker::PhantomData, path::Path, sync::Arc};
use thiserror::Error;
@ -269,7 +269,7 @@ pub trait TypedColumn: Column {
}
impl TypedColumn for columns::TransactionStatus {
type Type = RpcTransactionStatus;
type Type = RpcTransactionStatusMeta;
}
pub trait SlotColumn<Index = u64> {}

View File

@ -42,7 +42,7 @@ while [[ -n $1 ]]; do
elif [[ $1 = --no-rocksdb-compaction ]]; then
args+=("$1")
shift
elif [[ $1 = --enable-rpc-get-confirmed-block ]]; then
elif [[ $1 = --enable-rpc-transaction-history ]]; then
args+=("$1")
shift
elif [[ $1 = --skip-poh-verify ]]; then

View File

@ -132,7 +132,7 @@ while [[ -n $1 ]]; do
elif [[ $1 = --no-rocksdb-compaction ]]; then
args+=("$1")
shift
elif [[ $1 = --enable-rpc-get-confirmed-block ]]; then
elif [[ $1 = --enable-rpc-transaction-history ]]; then
args+=("$1")
shift
elif [[ $1 = --skip-poh-verify ]]; then

View File

@ -290,7 +290,7 @@ EOF
--blockstream /tmp/solana-blockstream.sock
--no-voting
--dev-no-sigverify
--enable-rpc-get-confirmed-block
--enable-rpc-transaction-history
)
else
if [[ -n $internalNodesLamports ]]; then

2
run.sh
View File

@ -94,7 +94,7 @@ args=(
--rpc-faucet-address 127.0.0.1:9900
--log -
--enable-rpc-exit
--enable-rpc-get-confirmed-block
--enable-rpc-transaction-history
--init-complete-file "$dataDir"/init-completed
)
solana-validator "${args[@]}" &

View File

@ -117,20 +117,15 @@ impl<T: Serialize + Clone> StatusCache<T> {
trace!("get_signature_status_slow: trying {}", blockhash);
if let Some((forkid, res)) = self.get_signature_status(sig, blockhash, ancestors) {
trace!("get_signature_status_slow: got {}", forkid);
return ancestors
let confirmations = ancestors
.get(&forkid)
.map(|id| SignatureConfirmationStatus {
slot: forkid,
confirmations: *id,
status: res.clone(),
})
.or_else(|| {
Some(SignatureConfirmationStatus {
slot: forkid,
confirmations: ancestors.len(),
status: res,
})
});
.copied()
.unwrap_or_else(|| ancestors.len());
return Some(SignatureConfirmationStatus {
slot: forkid,
confirmations,
status: res,
});
}
}
None

View File

@ -502,10 +502,10 @@ pub fn main() {
.help("Enable the JSON RPC 'setLogFilter' API. Only enable in a debug environment"),
)
.arg(
Arg::with_name("enable_rpc_get_confirmed_block")
.long("enable-rpc-get-confirmed-block")
Arg::with_name("enable_rpc_transaction_history")
.long("enable-rpc-transaction-history")
.takes_value(false)
.help("Enable the JSON RPC 'getConfirmedBlock' API. This will cause an increase in disk usage and IOPS"),
.help("Enable historical transaction info over JSON RPC, including the 'getConfirmedBlock' API. This will cause an increase in disk usage and IOPS"),
)
.arg(
Arg::with_name("rpc_faucet_addr")
@ -742,7 +742,7 @@ pub fn main() {
rpc_config: JsonRpcConfig {
enable_validator_exit: matches.is_present("enable_rpc_exit"),
enable_set_log_filter: matches.is_present("enable_rpc_set_log_filter"),
enable_get_confirmed_block: matches.is_present("enable_rpc_get_confirmed_block"),
enable_rpc_transaction_history: matches.is_present("enable_rpc_transaction_history"),
identity_pubkey: identity_keypair.pubkey(),
faucet_addr: matches.value_of("rpc_faucet_addr").map(|address| {
solana_net_utils::parse_host_port(address).expect("failed to parse faucet address")