Skip to content
This repository has been archived by the owner on Jan 22, 2025. It is now read-only.

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

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 11 additions & 8 deletions client/src/mock_rpc_client_request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::{
Expand Down Expand Up @@ -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)),
Expand Down
8 changes: 4 additions & 4 deletions client/src/rpc_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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> {
Expand Down
2 changes: 1 addition & 1 deletion client/src/rpc_request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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" */
Expand Down
11 changes: 9 additions & 2 deletions client/src/rpc_response.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down Expand Up @@ -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 {
Expand Down
125 changes: 65 additions & 60 deletions core/src/rpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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::{
Expand All @@ -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>,
}
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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> {
Expand Down Expand Up @@ -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>;
Expand Down Expand Up @@ -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> {
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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()
},
Expand Down Expand Up @@ -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]
Expand Down
4 changes: 2 additions & 2 deletions core/src/transaction_status_service.rs
Original file line number Diff line number Diff line change
@@ -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},
Expand Down Expand Up @@ -73,7 +73,7 @@ impl TransactionStatusService {
blockstore
.write_transaction_status(
(slot, transaction.signatures[0]),
&RpcTransactionStatus {
&RpcTransactionStatusMeta {
status,
fee,
pre_balances,
Expand Down
4 changes: 2 additions & 2 deletions core/src/validator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,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),
Expand All @@ -279,7 +279,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),
Expand Down
20 changes: 13 additions & 7 deletions docs/src/apps/jsonrpc-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading