Update getSignatureStatuses to return historical statuses (#9314)
automerge
This commit is contained in:
parent
b28ec430e4
commit
acf64f8476
|
@ -94,7 +94,7 @@ impl GenericRpcClientRequest for MockRpcClientRequest {
|
|||
Some(TransactionStatus {
|
||||
status,
|
||||
slot: 1,
|
||||
confirmations: Some(0),
|
||||
confirmations: None,
|
||||
err,
|
||||
})
|
||||
};
|
||||
|
|
|
@ -77,13 +77,13 @@ impl RpcClient {
|
|||
signature: &Signature,
|
||||
commitment_config: CommitmentConfig,
|
||||
) -> RpcResult<bool> {
|
||||
let Response { context, value } =
|
||||
self.get_signature_statuses_with_commitment(&[*signature], commitment_config)?;
|
||||
let Response { context, value } = self.get_signature_statuses(&[*signature])?;
|
||||
|
||||
Ok(Response {
|
||||
context,
|
||||
value: value[0]
|
||||
.as_ref()
|
||||
.filter(|result| result.satisfies_commitment(commitment_config))
|
||||
.map(|result| result.status.is_ok())
|
||||
.unwrap_or_default(),
|
||||
})
|
||||
|
@ -112,17 +112,14 @@ impl RpcClient {
|
|||
self.get_signature_status_with_commitment(signature, CommitmentConfig::default())
|
||||
}
|
||||
|
||||
pub fn get_signature_statuses_with_commitment(
|
||||
pub fn get_signature_statuses(
|
||||
&self,
|
||||
signatures: &[Signature],
|
||||
commitment_config: CommitmentConfig,
|
||||
) -> RpcResult<Vec<Option<TransactionStatus>>> {
|
||||
let signatures: Vec<_> = signatures.iter().map(|s| s.to_string()).collect();
|
||||
let signature_status = self.client.send(
|
||||
&RpcRequest::GetSignatureStatuses,
|
||||
json!([&signatures, commitment_config]),
|
||||
5,
|
||||
)?;
|
||||
let signature_status =
|
||||
self.client
|
||||
.send(&RpcRequest::GetSignatureStatuses, json!([signatures]), 5)?;
|
||||
Ok(serde_json::from_value(signature_status)
|
||||
.map_err(|err| ClientError::new_with_command(err.into(), "GetSignatureStatuses"))?)
|
||||
}
|
||||
|
@ -134,7 +131,7 @@ impl RpcClient {
|
|||
) -> ClientResult<Option<transaction::Result<()>>> {
|
||||
let signature_status = self.client.send(
|
||||
&RpcRequest::GetSignatureStatuses,
|
||||
json!([[signature.to_string()], commitment_config]),
|
||||
json!([[signature.to_string()]]),
|
||||
5,
|
||||
)?;
|
||||
let result: Response<Vec<Option<TransactionStatus>>> =
|
||||
|
@ -142,6 +139,7 @@ impl RpcClient {
|
|||
.map_err(|err| ClientError::new_with_command(err.into(), "GetSignatureStatuses"))?;
|
||||
Ok(result.value[0]
|
||||
.clone()
|
||||
.filter(|result| result.satisfies_commitment(commitment_config))
|
||||
.map(|status_meta| status_meta.status))
|
||||
}
|
||||
|
||||
|
@ -957,7 +955,7 @@ impl RpcClient {
|
|||
.client
|
||||
.send(
|
||||
&RpcRequest::GetSignatureStatuses,
|
||||
json!([[signature.to_string()], CommitmentConfig::recent().ok()]),
|
||||
json!([[signature.to_string()]]),
|
||||
1,
|
||||
)
|
||||
.map_err(|err| err.into_with_command("GetSignatureStatuses"))?;
|
||||
|
|
|
@ -36,6 +36,8 @@ use std::{
|
|||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
const MAX_QUERY_ITEMS: usize = 256;
|
||||
|
||||
type RpcResponse<T> = Result<Response<T>>;
|
||||
|
||||
fn new_response<T>(bank: &Bank, value: T) -> RpcResponse<T> {
|
||||
|
@ -52,6 +54,12 @@ pub struct JsonRpcConfig {
|
|||
pub faucet_addr: Option<SocketAddr>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RpcSignatureStatusConfig {
|
||||
pub search_transaction_history: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct JsonRpcRequestProcessor {
|
||||
bank_forks: Arc<RwLock<BankForks>>,
|
||||
|
@ -425,17 +433,37 @@ impl JsonRpcRequestProcessor {
|
|||
.map(|(_, status)| status)
|
||||
}
|
||||
|
||||
pub fn get_signature_statuses_with_commitment(
|
||||
pub fn get_signature_statuses(
|
||||
&self,
|
||||
signatures: Vec<Signature>,
|
||||
commitment: Option<CommitmentConfig>,
|
||||
config: Option<RpcSignatureStatusConfig>,
|
||||
) -> RpcResponse<Vec<Option<TransactionStatus>>> {
|
||||
let mut statuses: Vec<Option<TransactionStatus>> = vec![];
|
||||
|
||||
let bank = self.bank(commitment);
|
||||
let search_transaction_history = config
|
||||
.map(|x| x.search_transaction_history)
|
||||
.unwrap_or(false);
|
||||
let bank = self.bank(Some(CommitmentConfig::recent()));
|
||||
|
||||
for signature in signatures {
|
||||
let status = self.get_transaction_status(signature, &bank);
|
||||
let status = if let Some(status) = self.get_transaction_status(signature, &bank) {
|
||||
Some(status)
|
||||
} else if self.config.enable_rpc_transaction_history && search_transaction_history {
|
||||
self.blockstore
|
||||
.get_transaction_status(signature)
|
||||
.map_err(|_| Error::internal_error())?
|
||||
.map(|(slot, status_meta)| {
|
||||
let err = status_meta.status.clone().err();
|
||||
TransactionStatus {
|
||||
slot,
|
||||
status: status_meta.status,
|
||||
confirmations: None,
|
||||
err,
|
||||
}
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
statuses.push(status);
|
||||
}
|
||||
Ok(Response {
|
||||
|
@ -611,11 +639,11 @@ pub trait RpcSol {
|
|||
fn get_fee_rate_governor(&self, meta: Self::Metadata) -> RpcResponse<RpcFeeRateGovernor>;
|
||||
|
||||
#[rpc(meta, name = "getSignatureStatuses")]
|
||||
fn get_signature_statuses_with_commitment(
|
||||
fn get_signature_statuses(
|
||||
&self,
|
||||
meta: Self::Metadata,
|
||||
signature_strs: Vec<String>,
|
||||
commitment: Option<CommitmentConfig>,
|
||||
config: Option<RpcSignatureStatusConfig>,
|
||||
) -> RpcResponse<Vec<Option<TransactionStatus>>>;
|
||||
|
||||
#[rpc(meta, name = "getSlot")]
|
||||
|
@ -973,12 +1001,18 @@ impl RpcSol for RpcSolImpl {
|
|||
.get_signature_status(signature, commitment))
|
||||
}
|
||||
|
||||
fn get_signature_statuses_with_commitment(
|
||||
fn get_signature_statuses(
|
||||
&self,
|
||||
meta: Self::Metadata,
|
||||
signature_strs: Vec<String>,
|
||||
commitment: Option<CommitmentConfig>,
|
||||
config: Option<RpcSignatureStatusConfig>,
|
||||
) -> RpcResponse<Vec<Option<TransactionStatus>>> {
|
||||
if signature_strs.len() > MAX_QUERY_ITEMS {
|
||||
return Err(Error::invalid_params(format!(
|
||||
"Too many inputs provided; max {}",
|
||||
MAX_QUERY_ITEMS
|
||||
)));
|
||||
}
|
||||
let mut signatures: Vec<Signature> = vec![];
|
||||
for signature_str in signature_strs {
|
||||
signatures.push(verify_signature(&signature_str)?);
|
||||
|
@ -986,7 +1020,7 @@ impl RpcSol for RpcSolImpl {
|
|||
meta.request_processor
|
||||
.read()
|
||||
.unwrap()
|
||||
.get_signature_statuses_with_commitment(signatures, commitment)
|
||||
.get_signature_statuses(signatures, config)
|
||||
}
|
||||
|
||||
fn get_slot(&self, meta: Self::Metadata, commitment: Option<CommitmentConfig>) -> Result<u64> {
|
||||
|
@ -1079,9 +1113,10 @@ impl RpcSol for RpcSolImpl {
|
|||
.request_processor
|
||||
.read()
|
||||
.unwrap()
|
||||
.get_signature_statuses_with_commitment(vec![signature], commitment.clone())?
|
||||
.get_signature_statuses(vec![signature], None)?
|
||||
.value[0]
|
||||
.clone()
|
||||
.filter(|result| result.satisfies_commitment(commitment.unwrap_or_default()))
|
||||
.map(|x| x.status);
|
||||
|
||||
if signature_status == Some(Ok(())) {
|
||||
|
|
|
@ -633,13 +633,16 @@ curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "m
|
|||
|
||||
### getSignatureStatuses
|
||||
|
||||
Returns the statuses of a list of signatures. This method only searches the recent status cache of signatures, which retains statuses for all active slots plus `MAX_RECENT_BLOCKHASHES` rooted slots.
|
||||
Returns the statuses of a list of signatures. Unless the
|
||||
`searchTransactionHistory` configuration parameter is included, this method only
|
||||
searches the recent status cache of signatures, which retains statuses for all
|
||||
active slots plus `MAX_RECENT_BLOCKHASHES` rooted slots.
|
||||
|
||||
#### Parameters:
|
||||
|
||||
* `<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)
|
||||
* `<object>` - (optional) Configuration object containing the following field:
|
||||
* `searchTransactionHistory: <bool>` - if true, a Solana node will search its ledger cache for any signatures not found in the recent status cache
|
||||
|
||||
#### Results:
|
||||
|
||||
|
@ -662,7 +665,10 @@ An array of:
|
|||
|
||||
```bash
|
||||
// Request
|
||||
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "method":"getSignatureStatuses", "params":[["5VERv8NMvzbJMEkV8xnrLkEaWRtSz9CosKDYjCJjBRnbJLgp8uirBgmQpjKhoR4tjF3ZpRzrFmBV6UjKdiSZkQUW", "5j7s6NiJS3JAkvgkoc18WVAsiSaci2pxB2A6ueCJP4tprA2TFg9wSyTLeYouxPBJEMzJinENTkpA52YStRW5Dia7"]]]}' http://localhost:8899
|
||||
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "method":"getSignatureStatuses", "params":[["5VERv8NMvzbJMEkV8xnrLkEaWRtSz9CosKDYjCJjBRnbJLgp8uirBgmQpjKhoR4tjF3ZpRzrFmBV6UjKdiSZkQUW", "5j7s6NiJS3JAkvgkoc18WVAsiSaci2pxB2A6ueCJP4tprA2TFg9wSyTLeYouxPBJEMzJinENTkpA52YStRW5Dia7"]]}' http://localhost:8899
|
||||
|
||||
// Request with configuration
|
||||
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "method":"getSignatureStatuses", "params":[["5VERv8NMvzbJMEkV8xnrLkEaWRtSz9CosKDYjCJjBRnbJLgp8uirBgmQpjKhoR4tjF3ZpRzrFmBV6UjKdiSZkQUW"], {"searchTransactionHistory": true}]}' http://localhost:8899
|
||||
|
||||
// Result
|
||||
{"jsonrpc":"2.0","result":{"context":{"slot":82},"value":[{"slot": 72, "confirmations": 10, "err": null, "status": {"Ok": null}}, null]},"id":1}
|
||||
|
|
|
@ -1644,6 +1644,41 @@ impl Blockstore {
|
|||
self.transaction_status_cf.put(index, status)
|
||||
}
|
||||
|
||||
// Returns a transaction status if it was processed in a root, as well as a loop counter for
|
||||
// unit testing
|
||||
fn get_transaction_status_with_counter(
|
||||
&self,
|
||||
signature: Signature,
|
||||
) -> Result<(Option<(Slot, TransactionStatusMeta)>, u64)> {
|
||||
let mut counter = 0;
|
||||
for transaction_status_cf_primary_index in 0..=1 {
|
||||
let index_iterator = self.transaction_status_cf.iter(IteratorMode::From(
|
||||
(transaction_status_cf_primary_index, signature, 0),
|
||||
IteratorDirection::Forward,
|
||||
))?;
|
||||
for ((_, sig, slot), data) in index_iterator {
|
||||
counter += 1;
|
||||
if sig != signature {
|
||||
break;
|
||||
}
|
||||
if self.is_root(slot) {
|
||||
let status: TransactionStatusMeta = deserialize(&data)?;
|
||||
return Ok((Some((slot, status)), counter));
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok((None, counter))
|
||||
}
|
||||
|
||||
/// Returns a transaction status if it was processed in a root
|
||||
pub fn get_transaction_status(
|
||||
&self,
|
||||
signature: Signature,
|
||||
) -> Result<Option<(Slot, TransactionStatusMeta)>> {
|
||||
self.get_transaction_status_with_counter(signature)
|
||||
.map(|(status, _)| status)
|
||||
}
|
||||
|
||||
pub fn read_rewards(&self, index: Slot) -> Result<Option<Rewards>> {
|
||||
self.rewards_cf.get(index)
|
||||
}
|
||||
|
@ -5596,6 +5631,127 @@ pub mod tests {
|
|||
Blockstore::destroy(&blockstore_path).expect("Expected successful database destruction");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_transaction_status() {
|
||||
let blockstore_path = get_tmp_ledger_path!();
|
||||
{
|
||||
let blockstore = Blockstore::open(&blockstore_path).unwrap();
|
||||
let transaction_status_cf = blockstore.db.column::<cf::TransactionStatus>();
|
||||
|
||||
let pre_balances_vec = vec![1, 2, 3];
|
||||
let post_balances_vec = vec![3, 2, 1];
|
||||
let status = TransactionStatusMeta {
|
||||
status: solana_sdk::transaction::Result::<()>::Ok(()),
|
||||
fee: 42u64,
|
||||
pre_balances: pre_balances_vec.clone(),
|
||||
post_balances: post_balances_vec.clone(),
|
||||
};
|
||||
|
||||
let signature1 = Signature::new(&[1u8; 64]);
|
||||
let signature2 = Signature::new(&[2u8; 64]);
|
||||
let signature3 = Signature::new(&[3u8; 64]);
|
||||
let signature4 = Signature::new(&[4u8; 64]);
|
||||
let signature5 = Signature::new(&[5u8; 64]);
|
||||
let signature6 = Signature::new(&[6u8; 64]);
|
||||
|
||||
// Initialize index 0, including:
|
||||
// signature2 in non-root and root,
|
||||
// signature4 in 2 non-roots,
|
||||
// extra entries
|
||||
transaction_status_cf
|
||||
.put((0, signature2.clone(), 1), &status)
|
||||
.unwrap();
|
||||
|
||||
transaction_status_cf
|
||||
.put((0, signature2.clone(), 2), &status)
|
||||
.unwrap();
|
||||
|
||||
transaction_status_cf
|
||||
.put((0, signature4.clone(), 0), &status)
|
||||
.unwrap();
|
||||
|
||||
transaction_status_cf
|
||||
.put((0, signature4.clone(), 1), &status)
|
||||
.unwrap();
|
||||
|
||||
transaction_status_cf
|
||||
.put((0, signature5.clone(), 0), &status)
|
||||
.unwrap();
|
||||
|
||||
transaction_status_cf
|
||||
.put((0, signature5.clone(), 1), &status)
|
||||
.unwrap();
|
||||
|
||||
// Initialize index 1, including:
|
||||
// signature4 in non-root and root,
|
||||
// extra entries
|
||||
transaction_status_cf
|
||||
.put((1, signature4.clone(), 1), &status)
|
||||
.unwrap();
|
||||
|
||||
transaction_status_cf
|
||||
.put((1, signature4.clone(), 2), &status)
|
||||
.unwrap();
|
||||
|
||||
transaction_status_cf
|
||||
.put((1, signature5.clone(), 0), &status)
|
||||
.unwrap();
|
||||
|
||||
transaction_status_cf
|
||||
.put((1, signature5.clone(), 1), &status)
|
||||
.unwrap();
|
||||
|
||||
blockstore.set_roots(&[2]).unwrap();
|
||||
|
||||
// Signature exists, root found in index 0
|
||||
if let (Some((slot, _status)), counter) = blockstore
|
||||
.get_transaction_status_with_counter(signature2)
|
||||
.unwrap()
|
||||
{
|
||||
assert_eq!(slot, 2);
|
||||
assert_eq!(counter, 2);
|
||||
}
|
||||
|
||||
// Signature exists, root found in index 1
|
||||
if let (Some((slot, _status)), counter) = blockstore
|
||||
.get_transaction_status_with_counter(signature4)
|
||||
.unwrap()
|
||||
{
|
||||
assert_eq!(slot, 2);
|
||||
assert_eq!(counter, 5);
|
||||
}
|
||||
|
||||
// Signature exists, no root found
|
||||
let (status, counter) = blockstore
|
||||
.get_transaction_status_with_counter(signature5)
|
||||
.unwrap();
|
||||
assert_eq!(status, None);
|
||||
assert_eq!(counter, 5);
|
||||
|
||||
// Signature does not exist, smaller than existing entries
|
||||
let (status, counter) = blockstore
|
||||
.get_transaction_status_with_counter(signature1)
|
||||
.unwrap();
|
||||
assert_eq!(status, None);
|
||||
assert_eq!(counter, 2);
|
||||
|
||||
// Signature does not exist, between existing entries
|
||||
let (status, counter) = blockstore
|
||||
.get_transaction_status_with_counter(signature3)
|
||||
.unwrap();
|
||||
assert_eq!(status, None);
|
||||
assert_eq!(counter, 2);
|
||||
|
||||
// Signature does not exist, larger than existing entries
|
||||
let (status, counter) = blockstore
|
||||
.get_transaction_status_with_counter(signature6)
|
||||
.unwrap();
|
||||
assert_eq!(status, None);
|
||||
assert_eq!(counter, 1);
|
||||
}
|
||||
Blockstore::destroy(&blockstore_path).expect("Expected successful database destruction");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_last_hash() {
|
||||
let mut entries: Vec<Entry> = vec![];
|
||||
|
|
|
@ -4,6 +4,7 @@ extern crate serde_derive;
|
|||
use bincode;
|
||||
use solana_sdk::{
|
||||
clock::Slot,
|
||||
commitment_config::CommitmentConfig,
|
||||
message::MessageHeader,
|
||||
transaction::{Result, Transaction, TransactionError},
|
||||
};
|
||||
|
@ -68,6 +69,13 @@ pub struct TransactionStatus {
|
|||
pub err: Option<TransactionError>,
|
||||
}
|
||||
|
||||
impl TransactionStatus {
|
||||
pub fn satisfies_commitment(&self, commitment_config: CommitmentConfig) -> bool {
|
||||
(commitment_config == CommitmentConfig::default() && self.confirmations.is_none())
|
||||
|| commitment_config == CommitmentConfig::recent()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Reward {
|
||||
pub pubkey: String,
|
||||
|
@ -171,3 +179,31 @@ impl EncodedTransaction {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_satisfies_commitment() {
|
||||
let status = TransactionStatus {
|
||||
slot: 0,
|
||||
confirmations: None,
|
||||
status: Ok(()),
|
||||
err: None,
|
||||
};
|
||||
|
||||
assert!(status.satisfies_commitment(CommitmentConfig::default()));
|
||||
assert!(status.satisfies_commitment(CommitmentConfig::recent()));
|
||||
|
||||
let status = TransactionStatus {
|
||||
slot: 0,
|
||||
confirmations: Some(10),
|
||||
status: Ok(()),
|
||||
err: None,
|
||||
};
|
||||
|
||||
assert!(!status.satisfies_commitment(CommitmentConfig::default()));
|
||||
assert!(status.satisfies_commitment(CommitmentConfig::recent()));
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue