Add getConfirmedSignaturesForAddress2 RPC method

This commit is contained in:
Michael Vines
2020-07-27 11:42:49 -07:00
parent 175c0e611b
commit 1b2276520b
8 changed files with 446 additions and 4 deletions

View File

@ -37,8 +37,9 @@ use solana_sdk::{
transaction::Transaction,
};
use solana_transaction_status::{
ConfirmedBlock, ConfirmedTransaction, EncodedTransaction, Rewards, TransactionStatusMeta,
TransactionWithStatusMeta, UiTransactionEncoding, UiTransactionStatusMeta,
ConfirmedBlock, ConfirmedTransaction, ConfirmedTransactionStatusWithSignature,
EncodedTransaction, Rewards, TransactionStatusMeta, TransactionWithStatusMeta, UiMessage,
UiTransactionEncoding, UiTransactionStatusMeta,
};
use solana_vote_program::{vote_instruction::VoteInstruction, vote_state::TIMESTAMP_SLOT_INTERVAL};
use std::{
@ -1921,6 +1922,127 @@ impl Blockstore {
.map(|signatures| signatures.iter().map(|(_, signature)| *signature).collect())
}
pub fn get_confirmed_signatures_for_address2(
&self,
address: Pubkey,
highest_confirmed_root: Slot,
start_after: Option<Signature>,
limit: usize,
) -> Result<Vec<ConfirmedTransactionStatusWithSignature>> {
datapoint_info!(
"blockstore-rpc-api",
(
"method",
"get_confirmed_signatures_for_address2".to_string(),
String
)
);
let (mut slot, mut start_after) = match start_after {
None => (highest_confirmed_root, None),
Some(start_after) => {
let confirmed_transaction =
self.get_confirmed_transaction(start_after, Some(UiTransactionEncoding::Json))?;
match confirmed_transaction {
None => return Ok(vec![]),
Some(ConfirmedTransaction { slot, transaction }) => {
// Ensure that `start_after` is from a transaction that contains `address`
match transaction.transaction {
EncodedTransaction::Json(ui_transaction) => {
match ui_transaction.message {
UiMessage::Raw(message) => {
let address = address.to_string();
if !message
.account_keys
.iter()
.any(|account_address| *account_address == address)
{
return Err(BlockstoreError::IO(IOError::new(
ErrorKind::Other,
format!(
"Invalid start_after signature: {}",
start_after
),
)));
}
}
_ => {
// Should never happen...
return Err(BlockstoreError::IO(IOError::new(
ErrorKind::Other,
"Unexpected transaction message encoding".to_string(),
)));
}
}
}
_ => {
// Should never happen...
return Err(BlockstoreError::IO(IOError::new(
ErrorKind::Other,
"Unexpected transaction encoding".to_string(),
)));
}
}
(slot, Some(start_after))
}
}
}
};
// 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(start_after) = start_after {
if let Some(index) = signatures
.iter()
.position(|(_slot, signature)| *signature == start_after)
{
address_signatures.append(&mut signatures.split_off(index + 1));
signatures.clear();
} else {
// Should never happen...
warn!(
"Blockstore corruption? Expected to find {} in slot {}",
start_after, slot
);
}
}
address_signatures.append(&mut signatures);
start_after = 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<Option<Rewards>> {
self.rewards_cf.get(index)
}
@ -6082,6 +6204,143 @@ 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<Entry> {
let mut entries: Vec<Entry> = 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]);
}
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]);
}
// `start_after` signature from address1 should fail
blockstore
.get_confirmed_signatures_for_address2(
address0,
highest_confirmed_root,
Some(all1[0].signature),
usize::MAX,
)
.unwrap_err();
}
Blockstore::destroy(&blockstore_path).expect("Expected successful database destruction");
}
#[test]
fn test_get_last_hash() {
let mut entries: Vec<Entry> = vec![];