Skip to content

Commit

Permalink
Populate memo in blockstore signatures-for-address (backport #19515) (#…
Browse files Browse the repository at this point in the history
…19605)

* Populate memo in blockstore signatures-for-address (#19515)

* Add TransactionMemos column family

* Traitify extract_memos

* Write TransactionMemos in TransactionStatusService

* Populate memos from column

* Dedupe and add unit test

(cherry picked from commit 5fa3e57)

# Conflicts:
#	ledger/src/blockstore.rs

* Fix conflicts

Co-authored-by: Tyera Eulberg <[email protected]>
Co-authored-by: Tyera Eulberg <[email protected]>
  • Loading branch information
3 people authored Sep 3, 2021
1 parent 959334d commit 105a891
Show file tree
Hide file tree
Showing 4 changed files with 147 additions and 18 deletions.
14 changes: 13 additions & 1 deletion ledger/src/blockstore.rs
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ pub struct Blockstore {
code_shred_cf: LedgerColumn<cf::ShredCode>,
transaction_status_cf: LedgerColumn<cf::TransactionStatus>,
address_signatures_cf: LedgerColumn<cf::AddressSignatures>,
transaction_memos_cf: LedgerColumn<cf::TransactionMemos>,
transaction_status_index_cf: LedgerColumn<cf::TransactionStatusIndex>,
active_transaction_status_index: RwLock<u64>,
rewards_cf: LedgerColumn<cf::Rewards>,
Expand Down Expand Up @@ -343,6 +344,7 @@ impl Blockstore {
let code_shred_cf = db.column();
let transaction_status_cf = db.column();
let address_signatures_cf = db.column();
let transaction_memos_cf = db.column();
let transaction_status_index_cf = db.column();
let rewards_cf = db.column();
let blocktime_cf = db.column();
Expand Down Expand Up @@ -392,6 +394,7 @@ impl Blockstore {
code_shred_cf,
transaction_status_cf,
address_signatures_cf,
transaction_memos_cf,
transaction_status_index_cf,
active_transaction_status_index: RwLock::new(active_transaction_status_index),
rewards_cf,
Expand Down Expand Up @@ -2148,6 +2151,14 @@ impl Blockstore {
Ok(())
}

pub fn read_transaction_memos(&self, signature: Signature) -> Result<Option<String>> {
self.transaction_memos_cf.get(signature)
}

pub fn write_transaction_memos(&self, signature: &Signature, memos: String) -> Result<()> {
self.transaction_memos_cf.put(*signature, &memos)
}

fn ensure_lowest_cleanup_slot(&self) -> (std::sync::RwLockReadGuard<Slot>, Slot) {
// Ensures consistent result by using lowest_cleanup_slot as the lower bound
// for reading columns that do not employ strong read consistency with slot-based
Expand Down Expand Up @@ -2631,12 +2642,13 @@ impl Blockstore {
let transaction_status =
self.get_transaction_status(signature, &confirmed_unrooted_slots)?;
let err = transaction_status.and_then(|(_slot, status)| status.status.err());
let memo = self.read_transaction_memos(signature)?;
let block_time = self.get_block_time(slot)?;
infos.push(ConfirmedTransactionStatusWithSignature {
signature,
slot,
err,
memo: None,
memo,
block_time,
});
}
Expand Down
49 changes: 49 additions & 0 deletions ledger/src/blockstore_db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ const CODE_SHRED_CF: &str = "code_shred";
const TRANSACTION_STATUS_CF: &str = "transaction_status";
/// Column family for Address Signatures
const ADDRESS_SIGNATURES_CF: &str = "address_signatures";
/// Column family for TransactionMemos
const TRANSACTION_MEMOS_CF: &str = "transaction_memos";
/// Column family for the Transaction Status Index.
/// This column family is used for tracking the active primary index for columns that for
/// query performance reasons should not be indexed by Slot.
Expand Down Expand Up @@ -163,6 +165,10 @@ pub mod columns {
/// The address signatures column
pub struct AddressSignatures;

#[derive(Debug)]
// The transaction memos column
pub struct TransactionMemos;

#[derive(Debug)]
/// The transaction status index column
pub struct TransactionStatusIndex;
Expand Down Expand Up @@ -332,6 +338,10 @@ impl Rocks {
AddressSignatures::NAME,
get_cf_options::<AddressSignatures>(&access_type, &oldest_slot),
);
let transaction_memos_cf_descriptor = ColumnFamilyDescriptor::new(
TransactionMemos::NAME,
get_cf_options::<TransactionMemos>(&access_type, &oldest_slot),
);
let transaction_status_index_cf_descriptor = ColumnFamilyDescriptor::new(
TransactionStatusIndex::NAME,
get_cf_options::<TransactionStatusIndex>(&access_type, &oldest_slot),
Expand Down Expand Up @@ -372,6 +382,7 @@ impl Rocks {
(ShredCode::NAME, shred_code_cf_descriptor),
(TransactionStatus::NAME, transaction_status_cf_descriptor),
(AddressSignatures::NAME, address_signatures_cf_descriptor),
(TransactionMemos::NAME, transaction_memos_cf_descriptor),
(
TransactionStatusIndex::NAME,
transaction_status_index_cf_descriptor,
Expand Down Expand Up @@ -494,6 +505,7 @@ impl Rocks {
ShredCode::NAME,
TransactionStatus::NAME,
AddressSignatures::NAME,
TransactionMemos::NAME,
TransactionStatusIndex::NAME,
Rewards::NAME,
Blocktime::NAME,
Expand Down Expand Up @@ -589,6 +601,10 @@ impl TypedColumn for columns::AddressSignatures {
type Type = blockstore_meta::AddressSignatureMeta;
}

impl TypedColumn for columns::TransactionMemos {
type Type = String;
}

impl TypedColumn for columns::TransactionStatusIndex {
type Type = blockstore_meta::TransactionStatusIndexMeta;
}
Expand Down Expand Up @@ -703,6 +719,37 @@ impl ColumnName for columns::AddressSignatures {
const NAME: &'static str = ADDRESS_SIGNATURES_CF;
}

impl Column for columns::TransactionMemos {
type Index = Signature;

fn key(signature: Signature) -> Vec<u8> {
let mut key = vec![0; 64]; // size_of Signature
key[0..64].clone_from_slice(&signature.as_ref()[0..64]);
key
}

fn index(key: &[u8]) -> Signature {
Signature::new(&key[0..64])
}

fn primary_index(_index: Self::Index) -> u64 {
unimplemented!()
}

fn slot(_index: Self::Index) -> Slot {
unimplemented!()
}

#[allow(clippy::wrong_self_convention)]
fn as_index(_index: u64) -> Self::Index {
Signature::default()
}
}

impl ColumnName for columns::TransactionMemos {
const NAME: &'static str = TRANSACTION_MEMOS_CF;
}

impl Column for columns::TransactionStatusIndex {
type Index = u64;

Expand Down Expand Up @@ -1364,6 +1411,7 @@ fn excludes_from_compaction(cf_name: &str) -> bool {
let no_compaction_cfs: HashSet<&'static str> = vec![
columns::TransactionStatusIndex::NAME,
columns::ProgramCosts::NAME,
columns::TransactionMemos::NAME,
]
.into_iter()
.collect();
Expand Down Expand Up @@ -1431,6 +1479,7 @@ pub mod tests {
columns::TransactionStatusIndex::NAME
));
assert!(excludes_from_compaction(columns::ProgramCosts::NAME));
assert!(excludes_from_compaction(columns::TransactionMemos::NAME));
assert!(!excludes_from_compaction("something else"));
}
}
12 changes: 10 additions & 2 deletions rpc/src/transaction_status_service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ use {
solana_runtime::bank::{
Bank, InnerInstructionsList, NonceRollbackInfo, TransactionLogMessages,
},
solana_transaction_status::{InnerInstructions, Reward, TransactionStatusMeta},
solana_transaction_status::{
extract_and_fmt_memos, InnerInstructions, Reward, TransactionStatusMeta,
},
std::{
sync::{
atomic::{AtomicBool, AtomicU64, Ordering},
Expand Down Expand Up @@ -141,6 +143,12 @@ impl TransactionStatusService {
.collect(),
);

if let Some(memos) = extract_and_fmt_memos(transaction.message()) {
blockstore
.write_transaction_memos(&transaction.signatures[0], memos)
.expect("Expect database write to succeed: TransactionMemos");
}

blockstore
.write_transaction_status(
slot,
Expand All @@ -159,7 +167,7 @@ impl TransactionStatusService {
rewards,
},
)
.expect("Expect database write to succeed");
.expect("Expect database write to succeed: TransactionStatus");
}
}
}
Expand Down
90 changes: 75 additions & 15 deletions transaction-status/src/extract_memos.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,29 +15,89 @@ pub fn spl_memo_id_v3() -> Pubkey {
Pubkey::new_from_array(spl_memo::id().to_bytes())
}

pub fn extract_and_fmt_memos(message: &Message) -> Option<String> {
let memos = extract_memos(message);
pub fn extract_and_fmt_memos<T: ExtractMemos>(message: &T) -> Option<String> {
let memos = message.extract_memos();
if memos.is_empty() {
None
} else {
Some(memos.join("; "))
}
}

fn extract_memos(message: &Message) -> Vec<String> {
let mut memos = vec![];
if message.account_keys.contains(&spl_memo_id_v1())
|| message.account_keys.contains(&spl_memo_id_v3())
{
for instruction in &message.instructions {
let program_id = message.account_keys[instruction.program_id_index as usize];
if program_id == spl_memo_id_v1() || program_id == spl_memo_id_v3() {
let memo_len = instruction.data.len();
let parsed_memo = parse_memo_data(&instruction.data)
.unwrap_or_else(|_| "(unparseable)".to_string());
memos.push(format!("[{}] {}", memo_len, parsed_memo));
fn maybe_push_parsed_memo(memos: &mut Vec<String>, program_id: Pubkey, data: &[u8]) {
if program_id == spl_memo_id_v1() || program_id == spl_memo_id_v3() {
let memo_len = data.len();
let parsed_memo = parse_memo_data(data).unwrap_or_else(|_| "(unparseable)".to_string());
memos.push(format!("[{}] {}", memo_len, parsed_memo));
}
}

pub trait ExtractMemos {
fn extract_memos(&self) -> Vec<String>;
}

impl ExtractMemos for Message {
fn extract_memos(&self) -> Vec<String> {
let mut memos = vec![];
if self.account_keys.contains(&spl_memo_id_v1())
|| self.account_keys.contains(&spl_memo_id_v3())
{
for instruction in &self.instructions {
let program_id = self.account_keys[instruction.program_id_index as usize];
maybe_push_parsed_memo(&mut memos, program_id, &instruction.data);
}
}
memos
}
}

#[cfg(test)]
mod test {
use {
super::*,
solana_sdk::{hash::Hash, instruction::CompiledInstruction},
};

#[test]
fn test_extract_memos() {
let fee_payer = Pubkey::new_unique();
let another_program_id = Pubkey::new_unique();
let memo0 = "Test memo";
let memo1 = "🦖";
let expected_memos = vec![
format!("[{}] {}", memo0.len(), memo0),
format!("[{}] {}", memo1.len(), memo1),
];
let memo_instructions = vec![
CompiledInstruction {
program_id_index: 1,
accounts: vec![],
data: memo0.as_bytes().to_vec(),
},
CompiledInstruction {
program_id_index: 2,
accounts: vec![],
data: memo1.as_bytes().to_vec(),
},
CompiledInstruction {
program_id_index: 3,
accounts: vec![],
data: memo1.as_bytes().to_vec(),
},
];
let message = Message::new_with_compiled_instructions(
1,
0,
3,
vec![
fee_payer,
spl_memo_id_v1(),
another_program_id,
spl_memo_id_v3(),
],
Hash::default(),
memo_instructions,
);
assert_eq!(message.extract_memos(), expected_memos);
}
memos
}

0 comments on commit 105a891

Please sign in to comment.