diff --git a/CHANGELOG.md b/CHANGELOG.md index 011f6f9bc53..a98b4bb362a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,6 +33,7 @@ Release channels have their own copy of this changelog: * The `TransactionStatus`, `TransactionMemos`, and `AddressSignatures` columns were updated in v1.18 to write a new key format. The old key format will no no longer be supported for fallback reads as of v4.0 +* `getSignaturesForAddress` returns an error with code `-32020` if the `before` or `until` signatures are not found, rather than a successful response with an empty array #### Changes * Added `--enable-scheduler-bindings` which binds an IPC server at `/scheduler_bindings.ipc` for external schedulers to connect to. * Added `clientId` field to each node in `getClusterNodes` response diff --git a/ledger/src/blockstore.rs b/ledger/src/blockstore.rs index 71c42b076e1..12d273dbd47 100644 --- a/ledger/src/blockstore.rs +++ b/ledger/src/blockstore.rs @@ -123,6 +123,7 @@ type CompletedRanges = Vec>; pub struct SignatureInfosForAddress { pub infos: Vec, pub found_before: bool, + pub found_until: bool, } #[derive(Error, Debug)] @@ -3555,20 +3556,24 @@ impl Blockstore { // Generate a HashSet of signatures that should be excluded from the results based on // `until` signature let mut get_until_slot_timer = Measure::start("get_until_slot_timer"); - let (lowest_slot, until_excluded_signatures) = match until { - None => (first_available_block, HashSet::new()), + let (lowest_slot, until_excluded_signatures, found_until) = match until { + None => (first_available_block, HashSet::new(), false), Some(until) => { let transaction_status = self.get_transaction_status(until, &confirmed_unrooted_slots)?; match transaction_status { - None => (first_available_block, HashSet::new()), + None => (first_available_block, HashSet::new(), false), Some((slot, _)) => { let mut slot_signatures = self.get_block_signatures_rev(slot)?; if let Some(pos) = slot_signatures.iter().position(|&x| x == until) { slot_signatures = slot_signatures.split_off(pos); } - (slot, slot_signatures.into_iter().collect::>()) + ( + slot, + slot_signatures.into_iter().collect::>(), + true, + ) } } } @@ -3678,6 +3683,7 @@ impl Blockstore { Ok(SignatureInfosForAddress { infos, found_before: true, // if `before` signature was not found, this method returned early + found_until, }) } diff --git a/rpc-client-api/src/custom_error.rs b/rpc-client-api/src/custom_error.rs index cb5ec6d4b15..620ae497268 100644 --- a/rpc-client-api/src/custom_error.rs +++ b/rpc-client-api/src/custom_error.rs @@ -28,6 +28,7 @@ pub const JSON_RPC_SERVER_ERROR_MIN_CONTEXT_SLOT_NOT_REACHED: i64 = -32016; pub const JSON_RPC_SERVER_ERROR_EPOCH_REWARDS_PERIOD_ACTIVE: i64 = -32017; pub const JSON_RPC_SERVER_ERROR_SLOT_NOT_EPOCH_BOUNDARY: i64 = -32018; pub const JSON_RPC_SERVER_ERROR_LONG_TERM_STORAGE_UNREACHABLE: i64 = -32019; +pub const JSON_RPC_SERVER_ERROR_FILTER_TRANSACTION_NOT_FOUND: i64 = -32020; #[derive(Error, Debug)] #[allow(clippy::large_enum_variant)] @@ -80,6 +81,8 @@ pub enum RpcCustomError { SlotNotEpochBoundary { slot: Slot }, #[error("LongTermStorageUnreachable")] LongTermStorageUnreachable, + #[error("FilterTransactionNotFound")] + FilterTransactionNotFound { signature: String }, } #[derive(Debug, Serialize, Deserialize)] @@ -259,6 +262,11 @@ impl From for Error { message: "Failed to query long-term storage; please try again".to_string(), data: None, }, + RpcCustomError::FilterTransactionNotFound { signature } => Self { + code: ErrorCode::ServerError(JSON_RPC_SERVER_ERROR_FILTER_TRANSACTION_NOT_FOUND), + message: format!("Transaction {signature} not found"), + data: None, + }, } } } diff --git a/rpc/src/rpc.rs b/rpc/src/rpc.rs index 0b2a034fd95..520ed5b4c70 100644 --- a/rpc/src/rpc.rs +++ b/rpc/src/rpc.rs @@ -1848,6 +1848,7 @@ impl JsonRpcRequestProcessor { let SignatureInfosForAddress { infos: mut results, found_before, + found_until, } = self .blockstore .get_confirmed_signatures_for_address2(address, highest_slot, before, until, limit) @@ -1874,7 +1875,7 @@ impl JsonRpcRequestProcessor { .collect() }; - if results.len() < limit { + if results.len() < limit || !found_until { if let Some(bigtable_ledger_storage) = &self.bigtable_ledger_storage { let mut bigtable_before = before; if !results.is_empty() { @@ -1890,7 +1891,7 @@ impl JsonRpcRequestProcessor { .get_signature_status(&bigtable_before.unwrap()) .await { - Err(StorageError::SignatureNotFound) => { + Err(StorageError::SignatureNotFound(_)) => { bigtable_before = None; } Err(err) => { @@ -1924,12 +1925,38 @@ impl JsonRpcRequestProcessor { } } } - Err(StorageError::SignatureNotFound) => {} + Err(StorageError::SignatureNotFound(not_found_signature)) => { + // bigtable_before is checked above + // SignatureNotFound means the blockstore before was not found, or the until signature was never found. + return Err(RpcCustomError::FilterTransactionNotFound { + signature: not_found_signature.to_string(), + } + .into()); + } Err(err) => { warn!("Failed to query Bigtable: {err:?}"); return Err(RpcCustomError::LongTermStorageUnreachable.into()); } } + } else { + // Long-term storage is not enabled. + // Return an error to the user if either before/until were provided but not found. + if !found_before { + if let Some(signature) = before { + return Err(RpcCustomError::FilterTransactionNotFound { + signature: signature.to_string(), + } + .into()); + } + } + if !found_until { + if let Some(signature) = until { + return Err(RpcCustomError::FilterTransactionNotFound { + signature: signature.to_string(), + } + .into()); + } + } } } diff --git a/storage-bigtable/src/lib.rs b/storage-bigtable/src/lib.rs index fe5f8fcf929..322a55ab399 100644 --- a/storage-bigtable/src/lib.rs +++ b/storage-bigtable/src/lib.rs @@ -60,7 +60,7 @@ pub enum Error { BlockNotFound(Slot), #[error("Signature not found")] - SignatureNotFound, + SignatureNotFound(Signature), #[error("tokio error")] TokioJoinError(JoinError), @@ -633,7 +633,7 @@ impl LedgerStorage { .get_bincode_cell::("tx", signature.to_string()) .await .map_err(|err| match err { - bigtable::Error::RowNotFound => Error::SignatureNotFound, + bigtable::Error::RowNotFound => Error::SignatureNotFound(*signature), _ => err.into(), })?; Ok(transaction_info.into()) @@ -713,7 +713,7 @@ impl LedgerStorage { .get_bincode_cell("tx", signature.to_string()) .await .map_err(|err| match err { - bigtable::Error::RowNotFound => Error::SignatureNotFound, + bigtable::Error::RowNotFound => Error::SignatureNotFound(*signature), _ => err.into(), })?; @@ -772,7 +772,7 @@ impl LedgerStorage { .get_bincode_cell("tx", before_signature.to_string()) .await .map_err(|err| match err { - bigtable::Error::RowNotFound => Error::SignatureNotFound, + bigtable::Error::RowNotFound => Error::SignatureNotFound(*before_signature), _ => err.into(), })?; @@ -788,7 +788,7 @@ impl LedgerStorage { .get_bincode_cell("tx", until_signature.to_string()) .await .map_err(|err| match err { - bigtable::Error::RowNotFound => Error::SignatureNotFound, + bigtable::Error::RowNotFound => Error::SignatureNotFound(*until_signature), _ => err.into(), })?;