Rpc: enable getConfirmedSignaturesForAddress2 to return confirmed (not yet finalized) data (#16281)

* Update blockstore method to allow return of unfinalized signature

* Support confirmed sigs in getConfirmedSignaturesForAddress2

* Add deprecated comments

* Update docs

* Enable confirmed transaction-history in cli

* Return real confirmation_status; fill in not-yet-finalized block time if possible
This commit is contained in:
Tyera Eulberg 2021-03-31 22:35:57 -06:00 committed by GitHub
parent 18bd47dbe1
commit da27acabcc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 411 additions and 48 deletions

View File

@ -24,9 +24,9 @@ use solana_client::{
pubsub_client::PubsubClient,
rpc_client::{GetConfirmedSignaturesForAddress2Config, RpcClient},
rpc_config::{
RpcAccountInfoConfig, RpcConfirmedBlockConfig, RpcLargestAccountsConfig,
RpcLargestAccountsFilter, RpcProgramAccountsConfig, RpcTransactionLogsConfig,
RpcTransactionLogsFilter,
RpcAccountInfoConfig, RpcConfirmedBlockConfig, RpcConfirmedTransactionConfig,
RpcLargestAccountsConfig, RpcLargestAccountsFilter, RpcProgramAccountsConfig,
RpcTransactionLogsConfig, RpcTransactionLogsFilter,
},
rpc_filter,
rpc_response::SlotInfo,
@ -1826,6 +1826,7 @@ pub fn process_transaction_history(
before,
until,
limit: Some(limit),
commitment: Some(CommitmentConfig::confirmed()),
},
)?;
@ -1842,9 +1843,13 @@ pub fn process_transaction_history(
Some(block_time) =>
format!("timestamp={} ", unix_timestamp_to_string(block_time)),
},
match result.err {
None => "Confirmed".to_string(),
Some(err) => format!("Failed: {:?}", err),
if let Some(err) = result.err {
format!("Failed: {:?}", err)
} else {
match result.confirmation_status {
None => "Finalized".to_string(),
Some(status) => format!("{:?}", status),
}
},
result.memo.unwrap_or_else(|| "".to_string()),
);
@ -1854,9 +1859,13 @@ pub fn process_transaction_history(
if show_transactions {
if let Ok(signature) = result.signature.parse::<Signature>() {
match rpc_client
.get_confirmed_transaction(&signature, UiTransactionEncoding::Base64)
{
match rpc_client.get_confirmed_transaction_with_config(
&signature,
RpcConfirmedTransactionConfig {
encoding: Some(UiTransactionEncoding::Base64),
commitment: Some(CommitmentConfig::confirmed()),
},
) {
Ok(confirmed_transaction) => {
println_transaction(
&confirmed_transaction

View File

@ -652,6 +652,7 @@ impl RpcClient {
before: config.before.map(|signature| signature.to_string()),
until: config.until.map(|signature| signature.to_string()),
limit: config.limit,
commitment: config.commitment,
};
let result: Vec<RpcConfirmedTransactionStatusWithSignature> = self.send(
@ -1632,6 +1633,7 @@ pub struct GetConfirmedSignaturesForAddress2Config {
pub before: Option<Signature>,
pub until: Option<Signature>,
pub limit: Option<usize>,
pub commitment: Option<CommitmentConfig>,
}
fn new_spinner_progress_bar() -> ProgressBar {

View File

@ -109,6 +109,8 @@ pub struct RpcGetConfirmedSignaturesForAddress2Config {
pub before: Option<String>, // Signature as base-58 string
pub until: Option<String>, // Signature as base-58 string
pub limit: Option<usize>,
#[serde(flatten)]
pub commitment: Option<CommitmentConfig>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]

View File

@ -7,7 +7,9 @@ use {
inflation::Inflation,
transaction::{Result, TransactionError},
},
solana_transaction_status::ConfirmedTransactionStatusWithSignature,
solana_transaction_status::{
ConfirmedTransactionStatusWithSignature, TransactionConfirmationStatus,
},
std::{collections::HashMap, fmt, net::SocketAddr},
};
@ -348,6 +350,7 @@ pub struct RpcConfirmedTransactionStatusWithSignature {
pub err: Option<TransactionError>,
pub memo: Option<String>,
pub block_time: Option<UnixTimestamp>,
pub confirmation_status: Option<TransactionConfirmationStatus>,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
@ -374,6 +377,7 @@ impl From<ConfirmedTransactionStatusWithSignature> for RpcConfirmedTransactionSt
err,
memo,
block_time,
confirmation_status: None,
}
}
}

View File

@ -1163,23 +1163,27 @@ impl JsonRpcRequestProcessor {
mut before: Option<Signature>,
until: Option<Signature>,
mut limit: usize,
commitment: Option<CommitmentConfig>,
) -> Result<Vec<RpcConfirmedTransactionStatusWithSignature>> {
let commitment = commitment.unwrap_or_default();
check_is_at_least_confirmed(commitment)?;
if self.config.enable_rpc_transaction_history {
let highest_confirmed_root = self
.block_commitment_cache
.read()
.unwrap()
.highest_confirmed_root();
let highest_slot = if commitment.is_confirmed() {
let confirmed_bank = self.bank(Some(CommitmentConfig::confirmed()));
confirmed_bank.slot()
} else {
highest_confirmed_root
};
let mut results = self
.blockstore
.get_confirmed_signatures_for_address2(
address,
highest_confirmed_root,
before,
until,
limit,
)
.get_confirmed_signatures_for_address2(address, highest_slot, before, until, limit)
.map_err(|err| Error::invalid_params(format!("{}", err)))?;
if results.len() < limit {
@ -1208,7 +1212,24 @@ impl JsonRpcRequestProcessor {
}
}
Ok(results.into_iter().map(|x| x.into()).collect())
Ok(results
.into_iter()
.map(|x| {
let mut item: RpcConfirmedTransactionStatusWithSignature = x.into();
if item.slot <= highest_confirmed_root {
item.confirmation_status = Some(TransactionConfirmationStatus::Finalized);
} else {
item.confirmation_status = Some(TransactionConfirmationStatus::Confirmed);
if item.block_time.is_none() {
let r_bank_forks = self.bank_forks.read().unwrap();
item.block_time = r_bank_forks
.get(item.slot)
.map(|bank| bank.clock().unix_timestamp);
}
}
item
})
.collect())
} else {
Ok(vec![])
}
@ -2341,6 +2362,7 @@ pub mod rpc_full {
config: Option<RpcEncodingConfigWrapper<RpcConfirmedTransactionConfig>>,
) -> Result<Option<EncodedConfirmedTransaction>>;
// DEPRECATED
#[rpc(meta, name = "getConfirmedSignaturesForAddress")]
fn get_confirmed_signatures_for_address(
&self,
@ -3087,7 +3109,13 @@ pub mod rpc_full {
)));
}
meta.get_confirmed_signatures_for_address2(address, before, until, limit)
meta.get_confirmed_signatures_for_address2(
address,
before,
until,
limit,
config.commitment,
)
}
fn get_first_available_block(&self, meta: Self::Metadata) -> Result<Slot> {

View File

@ -803,6 +803,7 @@ address backwards in time from the provided signature or most recent confirmed b
* `before: <string>` - (optional) start searching backwards from this transaction signature.
If not provided the search starts from the top of the highest max confirmed block.
* `until: <string>` - (optional) search until this transaction signature, if found before limit reached.
* (optional) [Commitment](jsonrpc-api.md#configuring-state-commitment); "processed" is not supported. If parameter not provided, the default is "finalized".
#### Results:
The result field will be an array of transaction signature information, ordered

View File

@ -2112,6 +2112,8 @@ impl Blockstore {
// Returns all rooted signatures for an address, ordered by slot that the transaction was
// 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
//
// DEPRECATED
fn find_address_signatures(
&self,
pubkey: Pubkey,
@ -2143,6 +2145,40 @@ impl Blockstore {
Ok(signatures)
}
// Returns all signatures for an address in a particular slot, regardless of whether that slot
// has been rooted. The transactions will be ordered by signature, and NOT by the order in
// which the transactions exist in the block
fn find_address_signatures_for_slot(
&self,
pubkey: Pubkey,
slot: Slot,
) -> Result<Vec<(Slot, Signature)>> {
let mut signatures: Vec<(Slot, Signature)> = vec![];
for transaction_status_cf_primary_index in 0..=1 {
let index_iterator = self.address_signatures_cf.iter(IteratorMode::From(
(
transaction_status_cf_primary_index,
pubkey,
slot,
Signature::default(),
),
IteratorDirection::Forward,
))?;
for ((i, address, transaction_slot, signature), _) in index_iterator {
if i != transaction_status_cf_primary_index
|| transaction_slot > slot
|| address != pubkey
{
break;
}
signatures.push((slot, signature));
}
}
signatures.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap().then(a.1.cmp(&b.1)));
Ok(signatures)
}
// DEPRECATED
pub fn get_confirmed_signatures_for_address(
&self,
pubkey: Pubkey,
@ -2164,7 +2200,7 @@ impl Blockstore {
pub fn get_confirmed_signatures_for_address2(
&self,
address: Pubkey,
highest_confirmed_root: Slot,
highest_slot: Slot, // highest_confirmed_root or highest_confirmed_slot
before: Option<Signature>,
until: Option<Signature>,
limit: usize,
@ -2177,28 +2213,31 @@ impl Blockstore {
String
)
);
let confirmed_unrooted_slots: Vec<_> = AncestorIterator::new_inclusive(highest_slot, self)
.filter(|&slot| slot > self.last_root())
.collect();
// 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 get_before_slot_timer = Measure::start("get_before_slot_timer");
let (slot, mut before_excluded_signatures) = match before {
None => (highest_confirmed_root, None),
None => (highest_slot, None),
Some(before) => {
let transaction_status = self.get_transaction_status(before, &[])?;
let transaction_status =
self.get_transaction_status(before, &confirmed_unrooted_slots)?;
match transaction_status {
None => return Ok(vec![]),
Some((slot, _)) => {
let confirmed_block =
self.get_rooted_block(slot, false).map_err(|err| {
BlockstoreError::Io(IoError::new(
ErrorKind::Other,
format!("Unable to get confirmed block: {}", err),
))
})?;
let block = self.get_complete_block(slot, false).map_err(|err| {
BlockstoreError::Io(IoError::new(
ErrorKind::Other,
format!("Unable to get block: {}", err),
))
})?;
// Load all signatures for the block
let mut slot_signatures: Vec<_> = confirmed_block
let mut slot_signatures: Vec<_> = block
.transactions
.into_iter()
.filter_map(|transaction_with_meta| {
@ -2236,20 +2275,20 @@ impl Blockstore {
let (lowest_slot, until_excluded_signatures) = match until {
None => (0, HashSet::new()),
Some(until) => {
let transaction_status = self.get_transaction_status(until, &[])?;
let transaction_status =
self.get_transaction_status(until, &confirmed_unrooted_slots)?;
match transaction_status {
None => (0, HashSet::new()),
Some((slot, _)) => {
let confirmed_block =
self.get_rooted_block(slot, false).map_err(|err| {
BlockstoreError::Io(IoError::new(
ErrorKind::Other,
format!("Unable to get confirmed block: {}", err),
))
})?;
let block = self.get_complete_block(slot, false).map_err(|err| {
BlockstoreError::Io(IoError::new(
ErrorKind::Other,
format!("Unable to get block: {}", err),
))
})?;
// Load all signatures for the block
let mut slot_signatures: Vec<_> = confirmed_block
let mut slot_signatures: Vec<_> = block
.transactions
.into_iter()
.filter_map(|transaction_with_meta| {
@ -2284,7 +2323,7 @@ impl Blockstore {
// Get signatures in `slot`
let mut get_initial_slot_timer = Measure::start("get_initial_slot_timer");
let mut signatures = self.find_address_signatures(address, slot, slot)?;
let mut signatures = self.find_address_signatures_for_slot(address, slot)?;
signatures.reverse();
if let Some(excluded_signatures) = before_excluded_signatures.take() {
address_signatures.extend(
@ -2324,7 +2363,7 @@ impl Blockstore {
&& key_address == address
&& slot >= first_available_block
{
if self.is_root(slot) {
if self.is_root(slot) || confirmed_unrooted_slots.contains(&slot) {
address_signatures.push((slot, signature));
}
continue;
@ -2336,7 +2375,7 @@ impl Blockstore {
// Handle slots that cross primary indexes
if next_max_slot >= lowest_slot {
let mut signatures =
self.find_address_signatures(address, next_max_slot, next_max_slot)?;
self.find_address_signatures_for_slot(address, next_max_slot)?;
signatures.reverse();
address_signatures.append(&mut signatures);
}
@ -2362,7 +2401,7 @@ impl Blockstore {
&& key_address == address
&& slot >= first_available_block
{
if self.is_root(slot) {
if self.is_root(slot) || confirmed_unrooted_slots.contains(&slot) {
address_signatures.push((slot, signature));
}
continue;
@ -2381,7 +2420,8 @@ impl Blockstore {
let mut get_status_info_timer = Measure::start("get_status_info_timer");
let mut infos = vec![];
for (slot, signature) in address_signatures.into_iter() {
let transaction_status = self.get_transaction_status(signature, &[])?;
let transaction_status =
self.get_transaction_status(signature, &confirmed_unrooted_slots)?;
let err = match transaction_status {
None => None,
Some((_slot, status)) => status.status.err(),
@ -6821,6 +6861,96 @@ pub mod tests {
Blockstore::destroy(&blockstore_path).expect("Expected successful database destruction");
}
#[test]
fn test_find_address_signatures_for_slot() {
let blockstore_path = get_tmp_ledger_path!();
{
let blockstore = Blockstore::open(&blockstore_path).unwrap();
let address0 = solana_sdk::pubkey::new_rand();
let address1 = solana_sdk::pubkey::new_rand();
let slot1 = 1;
for x in 1..5 {
let signature = Signature::new(&[x; 64]);
blockstore
.write_transaction_status(
slot1,
signature,
vec![&address0],
vec![&address1],
TransactionStatusMeta::default(),
)
.unwrap();
}
let slot2 = 2;
for x in 5..7 {
let signature = Signature::new(&[x; 64]);
blockstore
.write_transaction_status(
slot2,
signature,
vec![&address0],
vec![&address1],
TransactionStatusMeta::default(),
)
.unwrap();
}
// Purge to freeze index 0
blockstore.run_purge(0, 1, PurgeType::PrimaryIndex).unwrap();
for x in 7..9 {
let signature = Signature::new(&[x; 64]);
blockstore
.write_transaction_status(
slot2,
signature,
vec![&address0],
vec![&address1],
TransactionStatusMeta::default(),
)
.unwrap();
}
let slot3 = 3;
for x in 9..13 {
let signature = Signature::new(&[x; 64]);
blockstore
.write_transaction_status(
slot3,
signature,
vec![&address0],
vec![&address1],
TransactionStatusMeta::default(),
)
.unwrap();
}
blockstore.set_roots(&[slot1]).unwrap();
let slot1_signatures = blockstore
.find_address_signatures_for_slot(address0, 1)
.unwrap();
for (i, (slot, signature)) in slot1_signatures.iter().enumerate() {
assert_eq!(*slot, slot1);
assert_eq!(*signature, Signature::new(&[i as u8 + 1; 64]));
}
let slot2_signatures = blockstore
.find_address_signatures_for_slot(address0, 2)
.unwrap();
for (i, (slot, signature)) in slot2_signatures.iter().enumerate() {
assert_eq!(*slot, slot2);
assert_eq!(*signature, Signature::new(&[i as u8 + 5; 64]));
}
let slot3_signatures = blockstore
.find_address_signatures_for_slot(address0, 3)
.unwrap();
for (i, (slot, signature)) in slot3_signatures.iter().enumerate() {
assert_eq!(*slot, slot3);
assert_eq!(*signature, Signature::new(&[i as u8 + 9; 64]));
}
}
}
#[test]
fn test_get_confirmed_signatures_for_address2() {
let blockstore_path = get_tmp_ledger_path!();
@ -6873,11 +7003,36 @@ pub mod tests {
}
}
}
// Add 2 slots that both descend from slot 8
for slot in 9..=10 {
let entries = make_slot_entries_with_transaction_addresses(&[
address0, address1, address0, address1,
]);
let shreds = entries_to_test_shreds(entries.clone(), slot, 8, true, 0);
blockstore.insert_shreds(shreds, None, false).unwrap();
for entry in entries.iter() {
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();
}
}
}
// Leave one slot unrooted to test only returns confirmed signatures
blockstore.set_roots(&[1, 2, 4, 5, 6, 7, 8]).unwrap();
let highest_confirmed_root = 8;
// Fetch all signatures for address 0 at once...
// Fetch all rooted signatures for address 0 at once...
let all0 = blockstore
.get_confirmed_signatures_for_address2(
address0,
@ -6889,7 +7044,7 @@ pub mod tests {
.unwrap();
assert_eq!(all0.len(), 12);
// Fetch all signatures for address 1 at once...
// Fetch all rooted signatures for address 1 at once...
let all1 = blockstore
.get_confirmed_signatures_for_address2(
address1,
@ -6901,8 +7056,6 @@ pub mod tests {
.unwrap();
assert_eq!(all1.len(), 12);
assert!(all0 != all1);
// Fetch all signatures for address 0 individually
for i in 0..all0.len() {
let results = blockstore
@ -7035,6 +7188,170 @@ pub mod tests {
)
.unwrap();
assert!(results2.len() < results.len());
// Duplicate all tests using confirmed signatures
let highest_confirmed_slot = 10;
// Fetch all signatures for address 0 at once...
let all0 = blockstore
.get_confirmed_signatures_for_address2(
address0,
highest_confirmed_slot,
None,
None,
usize::MAX,
)
.unwrap();
assert_eq!(all0.len(), 14);
// Fetch all signatures for address 1 at once...
let all1 = blockstore
.get_confirmed_signatures_for_address2(
address1,
highest_confirmed_slot,
None,
None,
usize::MAX,
)
.unwrap();
assert_eq!(all1.len(), 14);
// Fetch all signatures for address 0 individually
for i in 0..all0.len() {
let results = blockstore
.get_confirmed_signatures_for_address2(
address0,
highest_confirmed_slot,
if i == 0 {
None
} else {
Some(all0[i - 1].signature)
},
None,
1,
)
.unwrap();
assert_eq!(results.len(), 1);
assert_eq!(results[0], all0[i], "Unexpected result for {}", i);
}
// Fetch all signatures for address 0 individually using `until`
for i in 0..all0.len() {
let results = blockstore
.get_confirmed_signatures_for_address2(
address0,
highest_confirmed_slot,
if i == 0 {
None
} else {
Some(all0[i - 1].signature)
},
if i == all0.len() - 1 || i == all0.len() {
None
} else {
Some(all0[i + 1].signature)
},
10,
)
.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_slot,
Some(all0[all0.len() - 1].signature),
None,
1,
)
.unwrap()
.is_empty());
assert!(blockstore
.get_confirmed_signatures_for_address2(
address0,
highest_confirmed_slot,
None,
Some(all0[0].signature),
2,
)
.unwrap()
.is_empty());
// Fetch all signatures for address 0, three at a time
assert!(all0.len() % 3 == 2);
for i in (0..all0.len()).step_by(3) {
let results = blockstore
.get_confirmed_signatures_for_address2(
address0,
highest_confirmed_slot,
if i == 0 {
None
} else {
Some(all0[i - 1].signature)
},
None,
3,
)
.unwrap();
if i < 12 {
assert_eq!(results.len(), 3);
assert_eq!(results[2], all0[i + 2]);
} else {
assert_eq!(results.len(), 2);
}
assert_eq!(results[0], all0[i]);
assert_eq!(results[1], all0[i + 1]);
}
// Ensure that the signatures within a slot are reverse 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_slot,
if i == 0 {
None
} else {
Some(all1[i - 1].signature)
},
None,
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 `before` and/or `until` signatures from address1 should also work
let results = blockstore
.get_confirmed_signatures_for_address2(
address0,
highest_confirmed_slot,
Some(all1[0].signature),
None,
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());
let results2 = blockstore
.get_confirmed_signatures_for_address2(
address0,
highest_confirmed_slot,
Some(all1[0].signature),
Some(all1[4].signature),
usize::MAX,
)
.unwrap();
assert!(results2.len() < results.len());
}
Blockstore::destroy(&blockstore_path).expect("Expected successful database destruction");
}