From c7ba1994acdbb85a2415611b741242cc8d582bc5 Mon Sep 17 00:00:00 2001 From: Justin Starry Date: Thu, 2 Apr 2020 02:30:58 +0800 Subject: [PATCH] Undo getSignatureStatus breaking change, add getSignatureStatuses (#9228) automerge --- client/src/mock_rpc_client_request.rs | 15 ++++ client/src/rpc_client.rs | 12 +-- client/src/rpc_request.rs | 2 + core/src/rpc.rs | 114 ++++++++++++++++++++++++-- docs/src/apps/jsonrpc-api.md | 27 ++++++ 5 files changed, 158 insertions(+), 12 deletions(-) diff --git a/client/src/mock_rpc_client_request.rs b/client/src/mock_rpc_client_request.rs index e1ebccb86..787f845ac 100644 --- a/client/src/mock_rpc_client_request.rs +++ b/client/src/mock_rpc_client_request.rs @@ -88,6 +88,21 @@ impl GenericRpcClientRequest for MockRpcClientRequest { value: serde_json::to_value(FeeRateGovernor::default()).unwrap(), })?, RpcRequest::GetSignatureStatus => { + let response: Option> = if self.url == "account_in_use" { + Some(Err(TransactionError::AccountInUse)) + } else if self.url == "instruction_error" { + Some(Err(TransactionError::InstructionError( + 0, + InstructionError::UninitializedAccount, + ))) + } else if self.url == "sig_not_found" { + None + } else { + Some(Ok(())) + }; + serde_json::to_value(response).unwrap() + } + RpcRequest::GetSignatureStatuses => { let status: transaction::Result<()> = if self.url == "account_in_use" { Err(TransactionError::AccountInUse) } else if self.url == "instruction_error" { diff --git a/client/src/rpc_client.rs b/client/src/rpc_client.rs index f3c649449..f3999c0c7 100644 --- a/client/src/rpc_client.rs +++ b/client/src/rpc_client.rs @@ -119,13 +119,13 @@ impl RpcClient { commitment_config: CommitmentConfig, ) -> ClientResult>> { let signature_status = self.client.send( - &RpcRequest::GetSignatureStatus, + &RpcRequest::GetSignatureStatuses, json!([[signature.to_string()], commitment_config]), 5, )?; let result: Response>> = serde_json::from_value(signature_status) - .map_err(|err| ClientError::new_with_command(err.into(), "GetSignatureStatus"))?; + .map_err(|err| ClientError::new_with_command(err.into(), "GetSignatureStatuses"))?; Ok(result.value[0] .clone() .map(|status_meta| status_meta.status)) @@ -949,20 +949,20 @@ impl RpcClient { let response = self .client .send( - &RpcRequest::GetSignatureStatus, + &RpcRequest::GetSignatureStatuses, json!([[signature.to_string()], CommitmentConfig::recent().ok()]), 1, ) - .map_err(|err| err.into_with_command("GetSignatureStatus"))?; + .map_err(|err| err.into_with_command("GetSignatureStatuses"))?; let result: Response>> = serde_json::from_value(response) - .map_err(|err| ClientError::new_with_command(err.into(), "GetSignatureStatus"))?; + .map_err(|err| ClientError::new_with_command(err.into(), "GetSignatureStatuses"))?; let confirmations = result.value[0] .clone() .ok_or_else(|| { ClientError::new_with_command( ClientErrorKind::Custom("signature not found".to_string()), - "GetSignatureStatus", + "GetSignatureStatuses", ) })? .confirmations diff --git a/client/src/rpc_request.rs b/client/src/rpc_request.rs index fddde675f..c6414866b 100644 --- a/client/src/rpc_request.rs +++ b/client/src/rpc_request.rs @@ -23,6 +23,7 @@ pub enum RpcRequest { GetFeeCalculatorForBlockhash, GetFeeRateGovernor, GetSignatureStatus, + GetSignatureStatuses, GetSlot, GetSlotLeader, GetStorageTurn, @@ -65,6 +66,7 @@ impl RpcRequest { RpcRequest::GetFeeCalculatorForBlockhash => "getFeeCalculatorForBlockhash", RpcRequest::GetFeeRateGovernor => "getFeeRateGovernor", RpcRequest::GetSignatureStatus => "getSignatureStatus", + RpcRequest::GetSignatureStatuses => "getSignatureStatuses", RpcRequest::GetSlot => "getSlot", RpcRequest::GetSlotLeader => "getSlotLeader", RpcRequest::GetStorageTurn => "getStorageTurn", diff --git a/core/src/rpc.rs b/core/src/rpc.rs index 973bb17bd..403550c04 100644 --- a/core/src/rpc.rs +++ b/core/src/rpc.rs @@ -23,7 +23,7 @@ use solana_sdk::{ pubkey::Pubkey, signature::Signature, timing::slot_duration_from_slots_per_year, - transaction::Transaction, + transaction::{self, Transaction}, }; use solana_transaction_status::{ConfirmedBlock, TransactionEncoding, TransactionStatus}; use solana_vote_program::vote_state::{VoteState, MAX_LOCKOUT_HISTORY}; @@ -398,6 +398,16 @@ impl JsonRpcRequestProcessor { } pub fn get_signature_status( + &self, + signature: Signature, + commitment: Option, + ) -> Option> { + self.bank(commitment) + .get_signature_status_slot(&signature) + .map(|(_, status)| status) + } + + pub fn get_signature_statuses( &self, signatures: Vec, commitment: Option, @@ -556,6 +566,14 @@ pub trait RpcSol { #[rpc(meta, name = "getSignatureStatus")] fn get_signature_status( + &self, + meta: Self::Metadata, + signature_str: String, + commitment: Option, + ) -> Result>>; + + #[rpc(meta, name = "getSignatureStatuses")] + fn get_signature_statuses( &self, meta: Self::Metadata, signature_strs: Vec, @@ -886,6 +904,20 @@ impl RpcSol for RpcSolImpl { } fn get_signature_status( + &self, + meta: Self::Metadata, + signature_str: String, + commitment: Option, + ) -> Result>> { + let signature = verify_signature(&signature_str)?; + Ok(meta + .request_processor + .read() + .unwrap() + .get_signature_status(signature, commitment)) + } + + fn get_signature_statuses( &self, meta: Self::Metadata, signature_strs: Vec, @@ -898,7 +930,7 @@ impl RpcSol for RpcSolImpl { meta.request_processor .read() .unwrap() - .get_signature_status(signatures, commitment) + .get_signature_statuses(signatures, commitment) } fn get_slot(&self, meta: Self::Metadata, commitment: Option) -> Result { @@ -991,7 +1023,7 @@ impl RpcSol for RpcSolImpl { .request_processor .read() .unwrap() - .get_signature_status(vec![signature], commitment.clone())? + .get_signature_statuses(vec![signature], commitment.clone())? .value[0] .clone() .map(|x| x.status); @@ -1757,6 +1789,76 @@ pub mod tests { #[test] fn test_rpc_get_signature_status() { + let bob_pubkey = Pubkey::new_rand(); + let RpcHandler { + io, + meta, + blockhash, + alice, + .. + } = 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] + ); + let res = io.handle_request_sync(&req, meta.clone()); + let expected_res: Option> = 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); + + // 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":["{}"]}}"#, + tx.signatures[0] + ); + let res = io.handle_request_sync(&req, meta.clone()); + let expected_res: Option = 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); + + // 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] + ); + let res = io.handle_request_sync(&req, meta); + let expected_res: Option> = Some(Err( + TransactionError::InstructionError(0, InstructionError::Custom(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); + } + + #[test] + fn test_rpc_get_signature_statuses() { let bob_pubkey = Pubkey::new_rand(); let RpcHandler { io, @@ -1768,7 +1870,7 @@ pub mod tests { } = start_rpc_handler_with_tx(&bob_pubkey); let req = format!( - r#"{{"jsonrpc":"2.0","id":1,"method":"getSignatureStatus","params":[["{}"]]}}"#, + r#"{{"jsonrpc":"2.0","id":1,"method":"getSignatureStatuses","params":[["{}"]]}}"#, confirmed_block_signatures[0] ); let res = io.handle_request_sync(&req, meta.clone()); @@ -1784,7 +1886,7 @@ pub mod tests { // 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":"getSignatureStatuses","params":[["{}"]]}}"#, tx.signatures[0] ); let res = io.handle_request_sync(&req, meta.clone()); @@ -1796,7 +1898,7 @@ pub mod tests { // Test getSignatureStatus request on a TransactionError let req = format!( - r#"{{"jsonrpc":"2.0","id":1,"method":"getSignatureStatus","params":[["{}"]]}}"#, + r#"{{"jsonrpc":"2.0","id":1,"method":"getSignatureStatuses","params":[["{}"]]}}"#, confirmed_block_signatures[1] ); let res = io.handle_request_sync(&req, meta.clone()); diff --git a/docs/src/apps/jsonrpc-api.md b/docs/src/apps/jsonrpc-api.md index 7008c3ca3..4ccce41b8 100644 --- a/docs/src/apps/jsonrpc-api.md +++ b/docs/src/apps/jsonrpc-api.md @@ -34,6 +34,7 @@ To interact with a Solana node inside a JavaScript application, use the [solana- * [getProgramAccounts](jsonrpc-api.md#getprogramaccounts) * [getRecentBlockhash](jsonrpc-api.md#getrecentblockhash) * [getSignatureStatus](jsonrpc-api.md#getsignaturestatus) +* [getSignatureStatuses](jsonrpc-api.md#getsignaturestatuses) * [getSlot](jsonrpc-api.md#getslot) * [getSlotLeader](jsonrpc-api.md#getslotleader) * [getSlotsPerSegment](jsonrpc-api.md#getslotspersegment) @@ -660,6 +661,32 @@ Returns the status of a given signature. This method is similar to [confirmTrans #### Parameters: +* `` - Signature of Transaction to confirm, as base-58 encoded string +* `` - (optional) [Commitment](jsonrpc-api.md#configuring-state-commitment) + +#### Results: + +* `` - Unknown transaction +* `` - Transaction status: + * `"Ok": ` - Transaction was successful + * `"Err": ` - Transaction failed with TransactionError [TransactionError definitions](https://github.com/solana-labs/solana/blob/master/sdk/src/transaction.rs#L14) + +#### Example: + +```bash +// Request +curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "method":"getSignatureStatus", "params":["5VERv8NMvzbJMEkV8xnrLkEaWRtSz9CosKDYjCJjBRnbJLgp8uirBgmQpjKhoR4tjF3ZpRzrFmBV6UjKdiSZkQUW"]}' http://localhost:8899 + +// Result +{"jsonrpc":"2.0","result":{"Ok": null},"id":1} +``` + +### getSignatureStatuses + +Returns the statuses of a list of signatures. This method is similar to [confirmTransaction](jsonrpc-api.md#confirmtransaction) but provides more resolution for error events. This method only searches the recent status cache of signatures, which retains all active slots plus `MAX_RECENT_BLOCKHASHES` rooted slots. + +#### Parameters: + * `` - An array of transaction signatures to confirm, as base-58 encoded strings * `` - (optional) Extended Rpc configuration, containing the following optional fields: * `commitment: ` - [Commitment](jsonrpc-api.md#configuring-state-commitment)