diff --git a/cli/src/cli.rs b/cli/src/cli.rs index ede5c7cf0c..1755fd1636 100644 --- a/cli/src/cli.rs +++ b/cli/src/cli.rs @@ -231,8 +231,8 @@ pub enum CliCommand { TotalSupply, TransactionHistory { address: Pubkey, - end_slot: Option, // None == latest slot - slot_limit: Option, // None == search full history + before: Option, + limit: usize, }, // Nonce commands AuthorizeNonceAccount { @@ -1871,9 +1871,9 @@ pub fn process_command(config: &CliConfig) -> ProcessResult { CliCommand::TotalSupply => process_total_supply(&rpc_client, config), CliCommand::TransactionHistory { address, - end_slot, - slot_limit, - } => process_transaction_history(&rpc_client, address, *end_slot, *slot_limit), + before, + limit, + } => process_transaction_history(&rpc_client, config, address, *before, *limit), // Nonce Commands diff --git a/cli/src/cluster_query.rs b/cli/src/cluster_query.rs index 4741f01ad9..d578c78230 100644 --- a/cli/src/cluster_query.rs +++ b/cli/src/cluster_query.rs @@ -13,7 +13,6 @@ use solana_client::{ pubsub_client::{PubsubClient, SlotInfoMessage}, rpc_client::RpcClient, rpc_config::{RpcLargestAccountsConfig, RpcLargestAccountsFilter}, - rpc_request::MAX_GET_CONFIRMED_SIGNATURES_FOR_ADDRESS_SLOT_RANGE, }; use solana_remote_wallet::remote_wallet::RemoteWalletManager; use solana_sdk::{ @@ -24,6 +23,7 @@ use solana_sdk::{ message::Message, native_token::lamports_to_sol, pubkey::{self, Pubkey}, + signature::Signature, system_instruction, system_program, sysvar::{ self, @@ -267,26 +267,22 @@ impl ClusterQuerySubCommands for App<'_, '_> { .required(true), "Account address"), ) - .arg( - Arg::with_name("end_slot") - .takes_value(false) - .value_name("SLOT") - .index(2) - .validator(is_slot) - .help( - "Slot to start from [default: latest slot at maximum commitment]" - ), - ) .arg( Arg::with_name("limit") .long("limit") .takes_value(true) - .value_name("NUMBER OF SLOTS") + .value_name("LIMIT") .validator(is_slot) - .help( - "Limit the search to this many slots" - ), - ), + .default_value("1000") + .help("Maximum number of transaction signatures to return"), + ) + .arg( + Arg::with_name("before") + .long("before") + .value_name("TRANSACTION_SIGNATURE") + .takes_value(true) + .help("Start with the first signature older than this one"), + ) ) } } @@ -440,14 +436,22 @@ pub fn parse_transaction_history( wallet_manager: &mut Option>, ) -> Result { let address = pubkey_of_signer(matches, "address", wallet_manager)?.unwrap(); - let end_slot = value_t!(matches, "end_slot", Slot).ok(); - let slot_limit = value_t!(matches, "limit", u64).ok(); + + let before = match matches.value_of("before") { + Some(signature) => Some( + signature + .parse() + .map_err(|err| CliError::BadParameter(format!("Invalid signature: {}", err)))?, + ), + None => None, + }; + let limit = value_t_or_exit!(matches, "limit", usize); Ok(CliCommandInfo { command: CliCommand::TransactionHistory { address, - end_slot, - slot_limit, + before, + limit, }, signers: vec![], }) @@ -1305,41 +1309,36 @@ pub fn process_show_validators( pub fn process_transaction_history( rpc_client: &RpcClient, + config: &CliConfig, address: &Pubkey, - end_slot: Option, // None == use latest slot - slot_limit: Option, + before: Option, + limit: usize, ) -> ProcessResult { - let end_slot = { - if let Some(end_slot) = end_slot { - end_slot + let results = rpc_client.get_confirmed_signatures_for_address2_with_config( + address, + before, + Some(limit), + )?; + + let transactions_found = format!("{} transactions found", results.len()); + + for result in results { + if config.verbose { + println!( + "{} [slot={} status={}] {}", + result.signature, + result.slot, + match result.err { + None => "Confirmed".to_string(), + Some(err) => format!("Failed: {:?}", err), + }, + result.memo.unwrap_or_else(|| "".to_string()), + ); } else { - rpc_client.get_slot_with_commitment(CommitmentConfig::max())? + println!("{}", result.signature); } - }; - let mut start_slot = match slot_limit { - Some(slot_limit) => end_slot.saturating_sub(slot_limit), - None => rpc_client.minimum_ledger_slot()?, - }; - - println!( - "Transactions affecting {} within slots [{},{}]", - address, start_slot, end_slot - ); - - let mut transaction_count = 0; - while start_slot < end_slot { - let signatures = rpc_client.get_confirmed_signatures_for_address( - address, - start_slot, - (start_slot + MAX_GET_CONFIRMED_SIGNATURES_FOR_ADDRESS_SLOT_RANGE).min(end_slot), - )?; - for signature in &signatures { - println!("{}", signature); - } - transaction_count += signatures.len(); - start_slot += MAX_GET_CONFIRMED_SIGNATURES_FOR_ADDRESS_SLOT_RANGE; } - Ok(format!("{} transactions found", transaction_count)) + Ok(transactions_found) } #[cfg(test)] diff --git a/client/src/rpc_client.rs b/client/src/rpc_client.rs index ecd0becaa9..fefbbab6af 100644 --- a/client/src/rpc_client.rs +++ b/client/src/rpc_client.rs @@ -2,7 +2,10 @@ use crate::{ client_error::{ClientError, ClientErrorKind, Result as ClientResult}, http_sender::HttpSender, mock_sender::{MockSender, Mocks}, - rpc_config::{RpcLargestAccountsConfig, RpcSendTransactionConfig, RpcTokenAccountsFilter}, + rpc_config::{ + RpcGetConfirmedSignaturesForAddress2Config, RpcLargestAccountsConfig, + RpcSendTransactionConfig, RpcTokenAccountsFilter, + }, rpc_request::{RpcError, RpcRequest, TokenAccountsFilter}, rpc_response::*, rpc_sender::RpcSender, @@ -289,6 +292,32 @@ impl RpcClient { Ok(signatures) } + pub fn get_confirmed_signatures_for_address2( + &self, + address: &Pubkey, + ) -> ClientResult> { + self.get_confirmed_signatures_for_address2_with_config(address, None, None) + } + + pub fn get_confirmed_signatures_for_address2_with_config( + &self, + address: &Pubkey, + before: Option, + limit: Option, + ) -> ClientResult> { + let config = RpcGetConfirmedSignaturesForAddress2Config { + before: before.map(|signature| signature.to_string()), + limit, + }; + + let result: Vec = self.send( + RpcRequest::GetConfirmedSignaturesForAddress2, + json!([address.to_string(), config]), + )?; + + Ok(result) + } + pub fn get_confirmed_transaction( &self, signature: &Signature, diff --git a/client/src/rpc_config.rs b/client/src/rpc_config.rs index e10a3c1e3c..5722f11857 100644 --- a/client/src/rpc_config.rs +++ b/client/src/rpc_config.rs @@ -65,3 +65,10 @@ pub enum RpcTokenAccountsFilter { Mint(String), ProgramId(String), } + +#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RpcGetConfirmedSignaturesForAddress2Config { + pub before: Option, // Signature as base-58 string + pub limit: Option, +} diff --git a/client/src/rpc_request.rs b/client/src/rpc_request.rs index 50f9788fd4..f27e70e2de 100644 --- a/client/src/rpc_request.rs +++ b/client/src/rpc_request.rs @@ -14,6 +14,7 @@ pub enum RpcRequest { GetConfirmedBlock, GetConfirmedBlocks, GetConfirmedSignaturesForAddress, + GetConfirmedSignaturesForAddress2, GetConfirmedTransaction, GetEpochInfo, GetEpochSchedule, @@ -65,6 +66,7 @@ impl fmt::Display for RpcRequest { RpcRequest::GetConfirmedBlock => "getConfirmedBlock", RpcRequest::GetConfirmedBlocks => "getConfirmedBlocks", RpcRequest::GetConfirmedSignaturesForAddress => "getConfirmedSignaturesForAddress", + RpcRequest::GetConfirmedSignaturesForAddress2 => "getConfirmedSignaturesForAddress2", RpcRequest::GetConfirmedTransaction => "getConfirmedTransaction", RpcRequest::GetEpochInfo => "getEpochInfo", RpcRequest::GetEpochSchedule => "getEpochSchedule", @@ -112,6 +114,7 @@ pub const NUM_LARGEST_ACCOUNTS: usize = 20; pub const MAX_GET_SIGNATURE_STATUSES_QUERY_ITEMS: usize = 256; pub const MAX_GET_CONFIRMED_SIGNATURES_FOR_ADDRESS_SLOT_RANGE: u64 = 10_000; pub const MAX_GET_CONFIRMED_BLOCKS_RANGE: u64 = 500_000; +pub const MAX_GET_CONFIRMED_SIGNATURES_FOR_ADDRESS2_LIMIT: usize = 1_000; // Validators that are this number of slots behind are considered delinquent pub const DELINQUENT_VALIDATOR_SLOT_DISTANCE: u64 = 128; diff --git a/client/src/rpc_response.rs b/client/src/rpc_response.rs index 37d390e93d..f90447b409 100644 --- a/client/src/rpc_response.rs +++ b/client/src/rpc_response.rs @@ -6,6 +6,7 @@ use solana_sdk::{ inflation::Inflation, transaction::{Result, TransactionError}, }; +use solana_transaction_status::ConfirmedTransactionStatusWithSignature; use std::{collections::HashMap, net::SocketAddr}; pub type RpcResult = client_error::Result>; @@ -236,3 +237,29 @@ pub struct RpcTokenAccountBalance { #[serde(flatten)] pub amount: RpcTokenAmount, } + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RpcConfirmedTransactionStatusWithSignature { + pub signature: String, + pub slot: Slot, + pub err: Option, + pub memo: Option, +} + +impl From for RpcConfirmedTransactionStatusWithSignature { + fn from(value: ConfirmedTransactionStatusWithSignature) -> Self { + let ConfirmedTransactionStatusWithSignature { + signature, + slot, + err, + memo, + } = value; + Self { + signature: signature.to_string(), + slot, + err, + memo, + } + } +} diff --git a/core/src/rpc.rs b/core/src/rpc.rs index b6782f3ed1..73dca84f9b 100644 --- a/core/src/rpc.rs +++ b/core/src/rpc.rs @@ -17,6 +17,7 @@ use solana_client::{ rpc_filter::{Memcmp, MemcmpEncodedBytes, RpcFilterType}, rpc_request::{ TokenAccountsFilter, DELINQUENT_VALIDATOR_SLOT_DISTANCE, MAX_GET_CONFIRMED_BLOCKS_RANGE, + MAX_GET_CONFIRMED_SIGNATURES_FOR_ADDRESS2_LIMIT, MAX_GET_CONFIRMED_SIGNATURES_FOR_ADDRESS_SLOT_RANGE, MAX_GET_SIGNATURE_STATUSES_QUERY_ITEMS, NUM_LARGEST_ACCOUNTS, }, @@ -770,6 +771,35 @@ impl JsonRpcRequestProcessor { } } + pub fn get_confirmed_signatures_for_address2( + &self, + address: Pubkey, + before: Option, + limit: usize, + ) -> Result> { + if self.config.enable_rpc_transaction_history { + let highest_confirmed_root = self + .block_commitment_cache + .read() + .unwrap() + .highest_confirmed_root(); + + let results = self + .blockstore + .get_confirmed_signatures_for_address2( + address, + highest_confirmed_root, + before, + limit, + ) + .map_err(|err| Error::invalid_params(format!("{}", err)))?; + + Ok(results.into_iter().map(|x| x.into()).collect()) + } else { + Ok(vec![]) + } + } + pub fn get_first_available_block(&self) -> Slot { self.blockstore .get_first_available_block() @@ -1406,6 +1436,14 @@ pub trait RpcSol { end_slot: Slot, ) -> Result>; + #[rpc(meta, name = "getConfirmedSignaturesForAddress2")] + fn get_confirmed_signatures_for_address2( + &self, + meta: Self::Metadata, + address: String, + config: Option, + ) -> Result>; + #[rpc(meta, name = "getFirstAvailableBlock")] fn get_first_available_block(&self, meta: Self::Metadata) -> Result; @@ -2036,6 +2074,34 @@ impl RpcSol for RpcSolImpl { .collect()) } + fn get_confirmed_signatures_for_address2( + &self, + meta: Self::Metadata, + address: String, + config: Option, + ) -> Result> { + let address = verify_pubkey(address)?; + + let config = config.unwrap_or_default(); + let before = if let Some(before) = config.before { + Some(verify_signature(&before)?) + } else { + None + }; + let limit = config + .limit + .unwrap_or(MAX_GET_CONFIRMED_SIGNATURES_FOR_ADDRESS2_LIMIT); + + if limit == 0 || limit > MAX_GET_CONFIRMED_SIGNATURES_FOR_ADDRESS2_LIMIT { + return Err(Error::invalid_params(format!( + "Invalid limit; max {}", + MAX_GET_CONFIRMED_SIGNATURES_FOR_ADDRESS2_LIMIT + ))); + } + + meta.get_confirmed_signatures_for_address2(address, before, limit) + } + fn get_first_available_block(&self, meta: Self::Metadata) -> Result { debug!("get_first_available_block rpc request received"); Ok(meta.get_first_available_block()) diff --git a/docs/src/apps/jsonrpc-api.md b/docs/src/apps/jsonrpc-api.md index 15af0725b2..f3467d73a4 100644 --- a/docs/src/apps/jsonrpc-api.md +++ b/docs/src/apps/jsonrpc-api.md @@ -24,6 +24,7 @@ To interact with a Solana node inside a JavaScript application, use the [solana- - [getConfirmedBlock](jsonrpc-api.md#getconfirmedblock) - [getConfirmedBlocks](jsonrpc-api.md#getconfirmedblocks) - [getConfirmedSignaturesForAddress](jsonrpc-api.md#getconfirmedsignaturesforaddress) +- [getConfirmedSignaturesForAddress2](jsonrpc-api.md#getconfirmedsignaturesforaddress2) - [getConfirmedTransaction](jsonrpc-api.md#getconfirmedtransaction) - [getEpochInfo](jsonrpc-api.md#getepochinfo) - [getEpochSchedule](jsonrpc-api.md#getepochschedule) @@ -389,6 +390,8 @@ curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc": "2.0","id":1,"m ### getConfirmedSignaturesForAddress +**DEPRECATED: Please use getConfirmedSignaturesForAddress2 instead** + Returns a list of all the confirmed signatures for transactions involving an address, within a specified Slot range. Max range allowed is 10,000 Slots @@ -416,6 +419,37 @@ curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc": "2.0","id":1,"m {"jsonrpc":"2.0","result":{["35YGay1Lwjwgxe9zaH6APSHbt9gYQUCtBWTNL3aVwVGn9xTFw2fgds7qK5AL29mP63A9j3rh8KpN1TgSR62XCaby","4bJdGN8Tt2kLWZ3Fa1dpwPSEkXWWTSszPSf1rRVsCwNjxbbUdwTeiWtmi8soA26YmwnKD4aAxNp8ci1Gjpdv4gsr","4LQ14a7BYY27578Uj8LPCaVhSdJGLn9DJqnUJHpy95FMqdKf9acAhUhecPQNjNUy6VoNFUbvwYkPociFSf87cWbG"]},"id":1} ``` + +### getConfirmedSignaturesForAddress2 + +Returns confirmed signatures for transactions involving an +address backwards in time from the provided signature or most recent confirmed block + +#### Parameters: +* `` - account address as base-58 encoded string +* `` - (optional) Configuration object containing the following fields: + * `before: ` - (optional) start searching backwards from this transaction signature. + If not provided the search starts from the top of the highest max confirmed block. + * `limit: ` - (optional) maximum transaction signatures to return (between 1 and 1,000, default: 1,000). + +#### Results: +The result field will be an array of transaction signature information, ordered +from newest to oldest transaction: +* `` + * `signature: ` - transaction signature as base-58 encoded string + * `slot: ` - The slot that contains the block with the transaction + * `err: ` - Error if transaction failed, null if transaction succeeded. [TransactionError definitions](https://github.com/solana-labs/solana/blob/master/sdk/src/transaction.rs#L14) + * `memo: ` - Memo associated with the transaction, null if no memo is present + +#### Example: +```bash +// Request +curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc": "2.0","id":1,"method":"getConfirmedSignaturesForAddress2","params":["Vote111111111111111111111111111111111111111", {"limit": 1}]}' localhost:8899 + +// Result +{"jsonrpc":"2.0","result":[{"err":null,"memo":null,"signature":"5h6xBEauJ3PK6SWCZ1PGjBvj8vDdWG3KpwATGy1ARAXFSDwt8GFXM7W5Ncn16wmqokgpiKRLuS83KUxyZyv2sUYv","slot":114}],"id":1} +``` + ### getConfirmedTransaction Returns transaction details for a confirmed transaction diff --git a/ledger/src/blockstore.rs b/ledger/src/blockstore.rs index c057cb2787..be16538d71 100644 --- a/ledger/src/blockstore.rs +++ b/ledger/src/blockstore.rs @@ -37,14 +37,15 @@ use solana_sdk::{ transaction::Transaction, }; use solana_transaction_status::{ - ConfirmedBlock, ConfirmedTransaction, EncodedTransaction, Rewards, TransactionStatusMeta, - TransactionWithStatusMeta, UiTransactionEncoding, UiTransactionStatusMeta, + ConfirmedBlock, ConfirmedTransaction, ConfirmedTransactionStatusWithSignature, + EncodedTransaction, Rewards, TransactionStatusMeta, TransactionWithStatusMeta, + UiTransactionEncoding, UiTransactionStatusMeta, }; use solana_vote_program::{vote_instruction::VoteInstruction, vote_state::TIMESTAMP_SLOT_INTERVAL}; use std::{ cell::RefCell, cmp, - collections::HashMap, + collections::{HashMap, HashSet}, fs, io::{Error as IOError, ErrorKind}, path::{Path, PathBuf}, @@ -1871,7 +1872,8 @@ impl Blockstore { } // Returns all cached signatures for an address, ordered by slot that the transaction was - // processed in + // processed in. Within each slot the transactions will be ordered by signature, and NOT by + // the order in which the transactions exist in the block fn find_address_signatures( &self, pubkey: Pubkey, @@ -1921,6 +1923,121 @@ impl Blockstore { .map(|signatures| signatures.iter().map(|(_, signature)| *signature).collect()) } + pub fn get_confirmed_signatures_for_address2( + &self, + address: Pubkey, + highest_confirmed_root: Slot, + before: Option, + limit: usize, + ) -> Result> { + datapoint_info!( + "blockstore-rpc-api", + ( + "method", + "get_confirmed_signatures_for_address2".to_string(), + String + ) + ); + + // Figure the `slot` to start listing signatures at, based on the ledger location of the + // `before` signature if present. Also generate a HashSet of signatures that should + // be excluded from the results. + let (mut slot, mut excluded_signatures) = match before { + None => (highest_confirmed_root, None), + Some(before) => { + let transaction_status = self.get_transaction_status(before)?; + match transaction_status { + None => return Ok(vec![]), + Some((slot, _)) => { + let confirmed_block = self + .get_confirmed_block(slot, Some(UiTransactionEncoding::Binary)) + .map_err(|err| { + BlockstoreError::IO(IOError::new( + ErrorKind::Other, + format!("Unable to get confirmed block: {}", err), + )) + })?; + + // Load all signatures for the block + let mut slot_signatures: Vec<_> = confirmed_block + .transactions + .iter() + .filter_map(|transaction_with_meta| { + if let Some(transaction) = + transaction_with_meta.transaction.decode() + { + transaction.signatures.into_iter().next() + } else { + None + } + }) + .collect(); + + // Sort signatures as a way to entire a stable ordering within a slot, as + // `self.find_address_signatures()` is ordered by signatures ordered and + // not by block ordering + slot_signatures.sort(); + + if let Some(pos) = slot_signatures.iter().position(|&x| x == before) { + slot_signatures.truncate(pos + 1); + } + + ( + slot, + Some(slot_signatures.into_iter().collect::>()), + ) + } + } + } + }; + + // Fetch the list of signatures that affect the given address + let first_available_block = self.get_first_available_block()?; + let mut address_signatures = vec![]; + loop { + if address_signatures.len() >= limit { + address_signatures.truncate(limit); + break; + } + + let mut signatures = self.find_address_signatures(address, slot, slot)?; + if let Some(excluded_signatures) = excluded_signatures.take() { + address_signatures.extend( + signatures + .into_iter() + .filter(|(_, signature)| !excluded_signatures.contains(&signature)), + ) + } else { + address_signatures.append(&mut signatures); + } + excluded_signatures = None; + + if slot == first_available_block { + break; + } + slot -= 1; + } + address_signatures.truncate(limit); + + // Fill in the status information for each found transaction + let mut infos = vec![]; + for (slot, signature) in address_signatures.into_iter() { + let transaction_status = self.get_transaction_status(signature)?; + let err = match transaction_status { + None => None, + Some((_slot, status)) => status.status.err(), + }; + infos.push(ConfirmedTransactionStatusWithSignature { + signature, + slot, + err, + memo: None, + }); + } + + Ok(infos) + } + pub fn read_rewards(&self, index: Slot) -> Result> { self.rewards_cf.get(index) } @@ -6082,6 +6199,168 @@ pub mod tests { Blockstore::destroy(&blockstore_path).expect("Expected successful database destruction"); } + #[test] + fn test_get_confirmed_signatures_for_address2() { + let blockstore_path = get_tmp_ledger_path!(); + { + let blockstore = Blockstore::open(&blockstore_path).unwrap(); + + fn make_slot_entries_with_transaction_addresses(addresses: &[Pubkey]) -> Vec { + let mut entries: Vec = Vec::new(); + for address in addresses { + let transaction = Transaction::new_with_compiled_instructions( + &[&Keypair::new()], + &[*address], + Hash::default(), + vec![Pubkey::new_rand()], + vec![CompiledInstruction::new(1, &(), vec![0])], + ); + entries.push(next_entry_mut(&mut Hash::default(), 0, vec![transaction])); + let mut tick = create_ticks(1, 0, hash(&serialize(address).unwrap())); + entries.append(&mut tick); + } + entries + } + + let address0 = Pubkey::new_rand(); + let address1 = Pubkey::new_rand(); + + for slot in 2..=4 { + let entries = make_slot_entries_with_transaction_addresses(&[ + address0, address1, address0, address1, + ]); + let shreds = entries_to_test_shreds(entries.clone(), slot, slot - 1, true, 0); + blockstore.insert_shreds(shreds, None, false).unwrap(); + + for entry in &entries { + for transaction in &entry.transactions { + assert_eq!(transaction.signatures.len(), 1); + blockstore + .write_transaction_status( + slot, + transaction.signatures[0], + transaction.message.account_keys.iter().collect(), + vec![], + &TransactionStatusMeta::default(), + ) + .unwrap(); + } + } + } + blockstore.set_roots(&[1, 2, 3, 4]).unwrap(); + let highest_confirmed_root = 4; + + // Fetch all signatures for address 0 at once... + let all0 = blockstore + .get_confirmed_signatures_for_address2( + address0, + highest_confirmed_root, + None, + usize::MAX, + ) + .unwrap(); + assert_eq!(all0.len(), 6); + + // Fetch all signatures for address 1 at once... + let all1 = blockstore + .get_confirmed_signatures_for_address2( + address1, + highest_confirmed_root, + None, + usize::MAX, + ) + .unwrap(); + assert_eq!(all1.len(), 6); + + assert!(all0 != all1); + + // Fetch all signatures for address 0 individually + for i in 0..all0.len() { + let results = blockstore + .get_confirmed_signatures_for_address2( + address0, + highest_confirmed_root, + if i == 0 { + None + } else { + Some(all0[i - 1].signature) + }, + 1, + ) + .unwrap(); + assert_eq!(results.len(), 1); + assert_eq!(results[0], all0[i], "Unexpected result for {}", i); + } + + assert!(blockstore + .get_confirmed_signatures_for_address2( + address0, + highest_confirmed_root, + Some(all0[all0.len() - 1].signature), + 1, + ) + .unwrap() + .is_empty()); + + // Fetch all signatures for address 0, three at a time + assert!(all0.len() % 3 == 0); + for i in (0..all0.len()).step_by(3) { + let results = blockstore + .get_confirmed_signatures_for_address2( + address0, + highest_confirmed_root, + if i == 0 { + None + } else { + Some(all0[i - 1].signature) + }, + 3, + ) + .unwrap(); + assert_eq!(results.len(), 3); + assert_eq!(results[0], all0[i]); + assert_eq!(results[1], all0[i + 1]); + assert_eq!(results[2], all0[i + 2]); + } + + // Ensure that the signatures within a slot are ordered by signature + // (current limitation of the .get_confirmed_signatures_for_address2()) + for i in (0..all1.len()).step_by(2) { + let results = blockstore + .get_confirmed_signatures_for_address2( + address1, + highest_confirmed_root, + if i == 0 { + None + } else { + Some(all1[i - 1].signature) + }, + 2, + ) + .unwrap(); + assert_eq!(results.len(), 2); + assert_eq!(results[0].slot, results[1].slot); + assert!(results[0].signature <= results[1].signature); + assert_eq!(results[0], all1[i]); + assert_eq!(results[1], all1[i + 1]); + } + + // A search for address 0 with a `before` signature from address1 should also work + let results = blockstore + .get_confirmed_signatures_for_address2( + address0, + highest_confirmed_root, + Some(all1[0].signature), + usize::MAX, + ) + .unwrap(); + // The exact number of results returned is variable, based on the sort order of the + // random signatures that are generated + assert!(!results.is_empty()); + } + Blockstore::destroy(&blockstore_path).expect("Expected successful database destruction"); + } + #[test] fn test_get_last_hash() { let mut entries: Vec = vec![]; diff --git a/transaction-status/src/lib.rs b/transaction-status/src/lib.rs index d470a47575..68fde844fb 100644 --- a/transaction-status/src/lib.rs +++ b/transaction-status/src/lib.rs @@ -17,6 +17,7 @@ use solana_sdk::{ instruction::CompiledInstruction, message::MessageHeader, pubkey::Pubkey, + signature::Signature, transaction::{Result, Transaction, TransactionError}, }; @@ -125,7 +126,7 @@ impl From for UiTransactionStatusMeta { pub struct TransactionStatus { pub slot: Slot, pub confirmations: Option, // None = rooted - pub status: Result<()>, + pub status: Result<()>, // legacy field pub err: Option, } @@ -136,6 +137,15 @@ impl TransactionStatus { } } +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ConfirmedTransactionStatusWithSignature { + pub signature: Signature, + pub slot: Slot, + pub err: Option, + pub memo: Option, +} + #[derive(Debug, PartialEq, Serialize, Deserialize)] pub struct Reward { pub pubkey: String,