solana_geyser_plugin_postgres/postgres_client/
postgres_client_transaction.rs

1/// Module responsible for handling persisting transaction data to the PostgreSQL
2/// database.
3use {
4    crate::{
5        geyser_plugin_postgres::{GeyserPluginPostgresConfig, GeyserPluginPostgresError},
6        postgres_client::{DbWorkItem, ParallelPostgresClient, SimplePostgresClient},
7    },
8    chrono::Utc,
9    log::*,
10    postgres::{Client, Statement},
11    postgres_types::{FromSql, ToSql},
12    solana_geyser_plugin_interface::geyser_plugin_interface::{
13        GeyserPluginError, ReplicaTransactionInfo,
14    },
15    solana_runtime::bank::RewardType,
16    solana_sdk::{
17        instruction::CompiledInstruction,
18        message::{
19            v0::{self, LoadedAddresses, MessageAddressTableLookup},
20            Message, MessageHeader, SanitizedMessage,
21        },
22        transaction::TransactionError,
23    },
24    solana_transaction_status::{
25        InnerInstructions, Reward, TransactionStatusMeta, TransactionTokenBalance,
26    },
27};
28
29const MAX_TRANSACTION_STATUS_LEN: usize = 256;
30
31#[derive(Clone, Debug, FromSql, ToSql)]
32#[postgres(name = "CompiledInstruction")]
33pub struct DbCompiledInstruction {
34    pub program_id_index: i16,
35    pub accounts: Vec<i16>,
36    pub data: Vec<u8>,
37}
38
39#[derive(Clone, Debug, FromSql, ToSql)]
40#[postgres(name = "InnerInstructions")]
41pub struct DbInnerInstructions {
42    pub index: i16,
43    pub instructions: Vec<DbCompiledInstruction>,
44}
45
46#[derive(Clone, Debug, FromSql, ToSql)]
47#[postgres(name = "TransactionTokenBalance")]
48pub struct DbTransactionTokenBalance {
49    pub account_index: i16,
50    pub mint: String,
51    pub ui_token_amount: Option<f64>,
52    pub owner: String,
53}
54
55#[derive(Clone, Debug, FromSql, ToSql, PartialEq)]
56#[postgres(name = "RewardType")]
57pub enum DbRewardType {
58    Fee,
59    Rent,
60    Staking,
61    Voting,
62}
63
64#[derive(Clone, Debug, FromSql, ToSql)]
65#[postgres(name = "Reward")]
66pub struct DbReward {
67    pub pubkey: String,
68    pub lamports: i64,
69    pub post_balance: i64,
70    pub reward_type: Option<DbRewardType>,
71    pub commission: Option<i16>,
72}
73
74#[derive(Clone, Debug, FromSql, ToSql)]
75#[postgres(name = "TransactionStatusMeta")]
76pub struct DbTransactionStatusMeta {
77    pub error: Option<DbTransactionError>,
78    pub fee: i64,
79    pub pre_balances: Vec<i64>,
80    pub post_balances: Vec<i64>,
81    pub inner_instructions: Option<Vec<DbInnerInstructions>>,
82    pub log_messages: Option<Vec<String>>,
83    pub pre_token_balances: Option<Vec<DbTransactionTokenBalance>>,
84    pub post_token_balances: Option<Vec<DbTransactionTokenBalance>>,
85    pub rewards: Option<Vec<DbReward>>,
86}
87
88#[derive(Clone, Debug, FromSql, ToSql)]
89#[postgres(name = "TransactionMessageHeader")]
90pub struct DbTransactionMessageHeader {
91    pub num_required_signatures: i16,
92    pub num_readonly_signed_accounts: i16,
93    pub num_readonly_unsigned_accounts: i16,
94}
95
96#[derive(Clone, Debug, FromSql, ToSql)]
97#[postgres(name = "TransactionMessage")]
98pub struct DbTransactionMessage {
99    pub header: DbTransactionMessageHeader,
100    pub account_keys: Vec<Vec<u8>>,
101    pub recent_blockhash: Vec<u8>,
102    pub instructions: Vec<DbCompiledInstruction>,
103}
104
105#[derive(Clone, Debug, FromSql, ToSql)]
106#[postgres(name = "TransactionMessageAddressTableLookup")]
107pub struct DbTransactionMessageAddressTableLookup {
108    pub account_key: Vec<u8>,
109    pub writable_indexes: Vec<i16>,
110    pub readonly_indexes: Vec<i16>,
111}
112
113#[derive(Clone, Debug, FromSql, ToSql)]
114#[postgres(name = "TransactionMessageV0")]
115pub struct DbTransactionMessageV0 {
116    pub header: DbTransactionMessageHeader,
117    pub account_keys: Vec<Vec<u8>>,
118    pub recent_blockhash: Vec<u8>,
119    pub instructions: Vec<DbCompiledInstruction>,
120    pub address_table_lookups: Vec<DbTransactionMessageAddressTableLookup>,
121}
122
123#[derive(Clone, Debug, FromSql, ToSql)]
124#[postgres(name = "LoadedAddresses")]
125pub struct DbLoadedAddresses {
126    pub writable: Vec<Vec<u8>>,
127    pub readonly: Vec<Vec<u8>>,
128}
129
130#[derive(Clone, Debug, FromSql, ToSql)]
131#[postgres(name = "LoadedMessageV0")]
132pub struct DbLoadedMessageV0 {
133    pub message: DbTransactionMessageV0,
134    pub loaded_addresses: DbLoadedAddresses,
135}
136
137pub struct DbTransaction {
138    pub signature: Vec<u8>,
139    pub is_vote: bool,
140    pub slot: i64,
141    pub message_type: i16,
142    pub legacy_message: Option<DbTransactionMessage>,
143    pub v0_loaded_message: Option<DbLoadedMessageV0>,
144    pub message_hash: Vec<u8>,
145    pub meta: DbTransactionStatusMeta,
146    pub signatures: Vec<Vec<u8>>,
147}
148
149pub struct LogTransactionRequest {
150    pub transaction_info: DbTransaction,
151}
152
153impl From<&MessageAddressTableLookup> for DbTransactionMessageAddressTableLookup {
154    fn from(address_table_lookup: &MessageAddressTableLookup) -> Self {
155        Self {
156            account_key: address_table_lookup.account_key.as_ref().to_vec(),
157            writable_indexes: address_table_lookup
158                .writable_indexes
159                .iter()
160                .map(|idx| *idx as i16)
161                .collect(),
162            readonly_indexes: address_table_lookup
163                .readonly_indexes
164                .iter()
165                .map(|idx| *idx as i16)
166                .collect(),
167        }
168    }
169}
170
171impl From<&LoadedAddresses> for DbLoadedAddresses {
172    fn from(loaded_addresses: &LoadedAddresses) -> Self {
173        Self {
174            writable: loaded_addresses
175                .writable
176                .iter()
177                .map(|pubkey| pubkey.as_ref().to_vec())
178                .collect(),
179            readonly: loaded_addresses
180                .readonly
181                .iter()
182                .map(|pubkey| pubkey.as_ref().to_vec())
183                .collect(),
184        }
185    }
186}
187
188impl From<&MessageHeader> for DbTransactionMessageHeader {
189    fn from(header: &MessageHeader) -> Self {
190        Self {
191            num_required_signatures: header.num_required_signatures as i16,
192            num_readonly_signed_accounts: header.num_readonly_signed_accounts as i16,
193            num_readonly_unsigned_accounts: header.num_readonly_unsigned_accounts as i16,
194        }
195    }
196}
197
198impl From<&CompiledInstruction> for DbCompiledInstruction {
199    fn from(instruction: &CompiledInstruction) -> Self {
200        Self {
201            program_id_index: instruction.program_id_index as i16,
202            accounts: instruction
203                .accounts
204                .iter()
205                .map(|account_idx| *account_idx as i16)
206                .collect(),
207            data: instruction.data.clone(),
208        }
209    }
210}
211
212impl From<&Message> for DbTransactionMessage {
213    fn from(message: &Message) -> Self {
214        Self {
215            header: DbTransactionMessageHeader::from(&message.header),
216            account_keys: message
217                .account_keys
218                .iter()
219                .map(|key| key.as_ref().to_vec())
220                .collect(),
221            recent_blockhash: message.recent_blockhash.as_ref().to_vec(),
222            instructions: message
223                .instructions
224                .iter()
225                .map(DbCompiledInstruction::from)
226                .collect(),
227        }
228    }
229}
230
231impl From<&v0::Message> for DbTransactionMessageV0 {
232    fn from(message: &v0::Message) -> Self {
233        Self {
234            header: DbTransactionMessageHeader::from(&message.header),
235            account_keys: message
236                .account_keys
237                .iter()
238                .map(|key| key.as_ref().to_vec())
239                .collect(),
240            recent_blockhash: message.recent_blockhash.as_ref().to_vec(),
241            instructions: message
242                .instructions
243                .iter()
244                .map(DbCompiledInstruction::from)
245                .collect(),
246            address_table_lookups: message
247                .address_table_lookups
248                .iter()
249                .map(DbTransactionMessageAddressTableLookup::from)
250                .collect(),
251        }
252    }
253}
254
255impl<'a> From<&v0::LoadedMessage<'a>> for DbLoadedMessageV0 {
256    fn from(message: &v0::LoadedMessage) -> Self {
257        Self {
258            message: DbTransactionMessageV0::from(&message.message as &v0::Message),
259            loaded_addresses: DbLoadedAddresses::from(
260                &message.loaded_addresses as &LoadedAddresses,
261            ),
262        }
263    }
264}
265
266impl From<&InnerInstructions> for DbInnerInstructions {
267    fn from(instructions: &InnerInstructions) -> Self {
268        Self {
269            index: instructions.index as i16,
270            instructions: instructions
271                .instructions
272                .iter()
273                .map(DbCompiledInstruction::from)
274                .collect(),
275        }
276    }
277}
278
279impl From<&RewardType> for DbRewardType {
280    fn from(reward_type: &RewardType) -> Self {
281        match reward_type {
282            RewardType::Fee => Self::Fee,
283            RewardType::Rent => Self::Rent,
284            RewardType::Staking => Self::Staking,
285            RewardType::Voting => Self::Voting,
286        }
287    }
288}
289
290fn get_reward_type(reward: &Option<RewardType>) -> Option<DbRewardType> {
291    reward.as_ref().map(DbRewardType::from)
292}
293
294impl From<&Reward> for DbReward {
295    fn from(reward: &Reward) -> Self {
296        Self {
297            pubkey: reward.pubkey.clone(),
298            lamports: reward.lamports as i64,
299            post_balance: reward.post_balance as i64,
300            reward_type: get_reward_type(&reward.reward_type),
301            commission: reward
302                .commission
303                .as_ref()
304                .map(|commission| *commission as i16),
305        }
306    }
307}
308
309#[derive(Clone, Debug, FromSql, ToSql, PartialEq)]
310#[postgres(name = "TransactionErrorCode")]
311pub enum DbTransactionErrorCode {
312    AccountInUse,
313    AccountLoadedTwice,
314    AccountNotFound,
315    ProgramAccountNotFound,
316    InsufficientFundsForFee,
317    InvalidAccountForFee,
318    AlreadyProcessed,
319    BlockhashNotFound,
320    InstructionError,
321    CallChainTooDeep,
322    MissingSignatureForFee,
323    InvalidAccountIndex,
324    SignatureFailure,
325    InvalidProgramForExecution,
326    SanitizeFailure,
327    ClusterMaintenance,
328    AccountBorrowOutstanding,
329    WouldExceedMaxAccountCostLimit,
330    WouldExceedMaxBlockCostLimit,
331    UnsupportedVersion,
332    InvalidWritableAccount,
333    WouldExceedMaxAccountDataCostLimit,
334    TooManyAccountLocks,
335    AddressLookupTableNotFound,
336    InvalidAddressLookupTableOwner,
337    InvalidAddressLookupTableData,
338    InvalidAddressLookupTableIndex,
339    InvalidRentPayingAccount,
340    WouldExceedMaxVoteCostLimit,
341    WouldExceedAccountDataBlockLimit,
342    WouldExceedAccountDataTotalLimit,
343}
344
345impl From<&TransactionError> for DbTransactionErrorCode {
346    fn from(err: &TransactionError) -> Self {
347        match err {
348            TransactionError::AccountInUse => Self::AccountInUse,
349            TransactionError::AccountLoadedTwice => Self::AccountLoadedTwice,
350            TransactionError::AccountNotFound => Self::AccountNotFound,
351            TransactionError::ProgramAccountNotFound => Self::ProgramAccountNotFound,
352            TransactionError::InsufficientFundsForFee => Self::InsufficientFundsForFee,
353            TransactionError::InvalidAccountForFee => Self::InvalidAccountForFee,
354            TransactionError::AlreadyProcessed => Self::AlreadyProcessed,
355            TransactionError::BlockhashNotFound => Self::BlockhashNotFound,
356            TransactionError::InstructionError(_idx, _error) => Self::InstructionError,
357            TransactionError::CallChainTooDeep => Self::CallChainTooDeep,
358            TransactionError::MissingSignatureForFee => Self::MissingSignatureForFee,
359            TransactionError::InvalidAccountIndex => Self::InvalidAccountIndex,
360            TransactionError::SignatureFailure => Self::SignatureFailure,
361            TransactionError::InvalidProgramForExecution => Self::InvalidProgramForExecution,
362            TransactionError::SanitizeFailure => Self::SanitizeFailure,
363            TransactionError::ClusterMaintenance => Self::ClusterMaintenance,
364            TransactionError::AccountBorrowOutstanding => Self::AccountBorrowOutstanding,
365            TransactionError::WouldExceedMaxAccountCostLimit => {
366                Self::WouldExceedMaxAccountCostLimit
367            }
368            TransactionError::WouldExceedMaxBlockCostLimit => Self::WouldExceedMaxBlockCostLimit,
369            TransactionError::UnsupportedVersion => Self::UnsupportedVersion,
370            TransactionError::InvalidWritableAccount => Self::InvalidWritableAccount,
371            TransactionError::WouldExceedAccountDataBlockLimit => {
372                Self::WouldExceedAccountDataBlockLimit
373            }
374            TransactionError::WouldExceedAccountDataTotalLimit => {
375                Self::WouldExceedAccountDataTotalLimit
376            }
377            TransactionError::TooManyAccountLocks => Self::TooManyAccountLocks,
378            TransactionError::AddressLookupTableNotFound => Self::AddressLookupTableNotFound,
379            TransactionError::InvalidAddressLookupTableOwner => {
380                Self::InvalidAddressLookupTableOwner
381            }
382            TransactionError::InvalidAddressLookupTableData => Self::InvalidAddressLookupTableData,
383            TransactionError::InvalidAddressLookupTableIndex => {
384                Self::InvalidAddressLookupTableIndex
385            }
386            TransactionError::InvalidRentPayingAccount => Self::InvalidRentPayingAccount,
387            TransactionError::WouldExceedMaxVoteCostLimit => Self::WouldExceedMaxVoteCostLimit,
388        }
389    }
390}
391
392#[derive(Clone, Debug, FromSql, ToSql, PartialEq)]
393#[postgres(name = "TransactionError")]
394pub struct DbTransactionError {
395    error_code: DbTransactionErrorCode,
396    error_detail: Option<String>,
397}
398
399fn get_transaction_error(result: &Result<(), TransactionError>) -> Option<DbTransactionError> {
400    if result.is_ok() {
401        return None;
402    }
403
404    let error = result.as_ref().err().unwrap();
405    Some(DbTransactionError {
406        error_code: DbTransactionErrorCode::from(error),
407        error_detail: {
408            if let TransactionError::InstructionError(idx, instruction_error) = error {
409                let mut error_detail = format!(
410                    "InstructionError: idx ({}), error: ({})",
411                    idx, instruction_error
412                );
413                if error_detail.len() > MAX_TRANSACTION_STATUS_LEN {
414                    error_detail = error_detail
415                        .to_string()
416                        .split_off(MAX_TRANSACTION_STATUS_LEN);
417                }
418                Some(error_detail)
419            } else {
420                None
421            }
422        },
423    })
424}
425
426impl From<&TransactionTokenBalance> for DbTransactionTokenBalance {
427    fn from(token_balance: &TransactionTokenBalance) -> Self {
428        Self {
429            account_index: token_balance.account_index as i16,
430            mint: token_balance.mint.clone(),
431            ui_token_amount: token_balance.ui_token_amount.ui_amount,
432            owner: token_balance.owner.clone(),
433        }
434    }
435}
436
437impl From<&TransactionStatusMeta> for DbTransactionStatusMeta {
438    fn from(meta: &TransactionStatusMeta) -> Self {
439        Self {
440            error: get_transaction_error(&meta.status),
441            fee: meta.fee as i64,
442            pre_balances: meta
443                .pre_balances
444                .iter()
445                .map(|balance| *balance as i64)
446                .collect(),
447            post_balances: meta
448                .post_balances
449                .iter()
450                .map(|balance| *balance as i64)
451                .collect(),
452            inner_instructions: meta
453                .inner_instructions
454                .as_ref()
455                .map(|instructions| instructions.iter().map(DbInnerInstructions::from).collect()),
456            log_messages: meta.log_messages.clone(),
457            pre_token_balances: meta.pre_token_balances.as_ref().map(|balances| {
458                balances
459                    .iter()
460                    .map(DbTransactionTokenBalance::from)
461                    .collect()
462            }),
463            post_token_balances: meta.post_token_balances.as_ref().map(|balances| {
464                balances
465                    .iter()
466                    .map(DbTransactionTokenBalance::from)
467                    .collect()
468            }),
469            rewards: meta
470                .rewards
471                .as_ref()
472                .map(|rewards| rewards.iter().map(DbReward::from).collect()),
473        }
474    }
475}
476
477fn build_db_transaction(slot: u64, transaction_info: &ReplicaTransactionInfo) -> DbTransaction {
478    DbTransaction {
479        signature: transaction_info.signature.as_ref().to_vec(),
480        is_vote: transaction_info.is_vote,
481        slot: slot as i64,
482        message_type: match transaction_info.transaction.message() {
483            SanitizedMessage::Legacy(_) => 0,
484            SanitizedMessage::V0(_) => 1,
485        },
486        legacy_message: match transaction_info.transaction.message() {
487            SanitizedMessage::Legacy(legacy_message) => {
488                Some(DbTransactionMessage::from(legacy_message))
489            }
490            _ => None,
491        },
492        v0_loaded_message: match transaction_info.transaction.message() {
493            SanitizedMessage::V0(loaded_message) => Some(DbLoadedMessageV0::from(loaded_message)),
494            _ => None,
495        },
496        signatures: transaction_info
497            .transaction
498            .signatures()
499            .iter()
500            .map(|signature| signature.as_ref().to_vec())
501            .collect(),
502        message_hash: transaction_info
503            .transaction
504            .message_hash()
505            .as_ref()
506            .to_vec(),
507        meta: DbTransactionStatusMeta::from(transaction_info.transaction_status_meta),
508    }
509}
510
511impl SimplePostgresClient {
512    pub(crate) fn build_transaction_info_upsert_statement(
513        client: &mut Client,
514        config: &GeyserPluginPostgresConfig,
515    ) -> Result<Statement, GeyserPluginError> {
516        let stmt = "INSERT INTO transaction AS txn (signature, is_vote, slot, message_type, legacy_message, \
517        v0_loaded_message, signatures, message_hash, meta, updated_on) \
518        VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) \
519        ON CONFLICT (slot, signature) DO UPDATE SET is_vote=excluded.is_vote, \
520        message_type=excluded.message_type, \
521        legacy_message=excluded.legacy_message, \
522        v0_loaded_message=excluded.v0_loaded_message, \
523        signatures=excluded.signatures, \
524        message_hash=excluded.message_hash, \
525        meta=excluded.meta, \
526        updated_on=excluded.updated_on";
527
528        let stmt = client.prepare(stmt);
529
530        match stmt {
531            Err(err) => {
532                return Err(GeyserPluginError::Custom(Box::new(GeyserPluginPostgresError::DataSchemaError {
533                    msg: format!(
534                        "Error in preparing for the transaction update PostgreSQL database: ({}) host: {:?} user: {:?} config: {:?}",
535                        err, config.host, config.user, config
536                    ),
537                })));
538            }
539            Ok(stmt) => Ok(stmt),
540        }
541    }
542
543    pub(crate) fn log_transaction_impl(
544        &mut self,
545        transaction_log_info: LogTransactionRequest,
546    ) -> Result<(), GeyserPluginError> {
547        let client = self.client.get_mut().unwrap();
548        let statement = &client.update_transaction_log_stmt;
549        let client = &mut client.client;
550        let updated_on = Utc::now().naive_utc();
551
552        let transaction_info = transaction_log_info.transaction_info;
553        let result = client.query(
554            statement,
555            &[
556                &transaction_info.signature,
557                &transaction_info.is_vote,
558                &transaction_info.slot,
559                &transaction_info.message_type,
560                &transaction_info.legacy_message,
561                &transaction_info.v0_loaded_message,
562                &transaction_info.signatures,
563                &transaction_info.message_hash,
564                &transaction_info.meta,
565                &updated_on,
566            ],
567        );
568
569        if let Err(err) = result {
570            let msg = format!(
571                "Failed to persist the update of transaction info to the PostgreSQL database. Error: {:?}",
572                err
573            );
574            error!("{}", msg);
575            return Err(GeyserPluginError::AccountsUpdateError { msg });
576        }
577
578        Ok(())
579    }
580}
581
582impl ParallelPostgresClient {
583    fn build_transaction_request(
584        slot: u64,
585        transaction_info: &ReplicaTransactionInfo,
586    ) -> LogTransactionRequest {
587        LogTransactionRequest {
588            transaction_info: build_db_transaction(slot, transaction_info),
589        }
590    }
591
592    pub fn log_transaction_info(
593        &mut self,
594        transaction_info: &ReplicaTransactionInfo,
595        slot: u64,
596    ) -> Result<(), GeyserPluginError> {
597        let wrk_item = DbWorkItem::LogTransaction(Box::new(Self::build_transaction_request(
598            slot,
599            transaction_info,
600        )));
601
602        if let Err(err) = self.sender.send(wrk_item) {
603            return Err(GeyserPluginError::SlotStatusUpdateError {
604                msg: format!("Failed to update the transaction, error: {:?}", err),
605            });
606        }
607        Ok(())
608    }
609}
610
611#[cfg(test)]
612pub(crate) mod tests {
613    use {
614        super::*,
615        solana_account_decoder::parse_token::UiTokenAmount,
616        solana_sdk::{
617            hash::Hash,
618            message::VersionedMessage,
619            pubkey::Pubkey,
620            sanitize::Sanitize,
621            signature::{Keypair, Signature, Signer},
622            system_transaction,
623            transaction::{
624                SanitizedTransaction, SimpleAddressLoader, Transaction, VersionedTransaction,
625            },
626        },
627    };
628
629    fn check_compiled_instruction_equality(
630        compiled_instruction: &CompiledInstruction,
631        db_compiled_instruction: &DbCompiledInstruction,
632    ) {
633        assert_eq!(
634            compiled_instruction.program_id_index,
635            db_compiled_instruction.program_id_index as u8
636        );
637        assert_eq!(
638            compiled_instruction.accounts.len(),
639            db_compiled_instruction.accounts.len()
640        );
641        assert_eq!(
642            compiled_instruction.data.len(),
643            db_compiled_instruction.data.len()
644        );
645
646        for i in 0..compiled_instruction.accounts.len() {
647            assert_eq!(
648                compiled_instruction.accounts[i],
649                db_compiled_instruction.accounts[i] as u8
650            )
651        }
652        for i in 0..compiled_instruction.data.len() {
653            assert_eq!(
654                compiled_instruction.data[i],
655                db_compiled_instruction.data[i] as u8
656            )
657        }
658    }
659
660    #[test]
661    fn test_transform_compiled_instruction() {
662        let compiled_instruction = CompiledInstruction {
663            program_id_index: 0,
664            accounts: vec![1, 2, 3],
665            data: vec![4, 5, 6],
666        };
667
668        let db_compiled_instruction = DbCompiledInstruction::from(&compiled_instruction);
669        check_compiled_instruction_equality(&compiled_instruction, &db_compiled_instruction);
670    }
671
672    fn check_inner_instructions_equality(
673        inner_instructions: &InnerInstructions,
674        db_inner_instructions: &DbInnerInstructions,
675    ) {
676        assert_eq!(inner_instructions.index, db_inner_instructions.index as u8);
677        assert_eq!(
678            inner_instructions.instructions.len(),
679            db_inner_instructions.instructions.len()
680        );
681
682        for i in 0..inner_instructions.instructions.len() {
683            check_compiled_instruction_equality(
684                &inner_instructions.instructions[i],
685                &db_inner_instructions.instructions[i],
686            )
687        }
688    }
689
690    #[test]
691    fn test_transform_inner_instructions() {
692        let inner_instructions = InnerInstructions {
693            index: 0,
694            instructions: vec![
695                CompiledInstruction {
696                    program_id_index: 0,
697                    accounts: vec![1, 2, 3],
698                    data: vec![4, 5, 6],
699                },
700                CompiledInstruction {
701                    program_id_index: 1,
702                    accounts: vec![12, 13, 14],
703                    data: vec![24, 25, 26],
704                },
705            ],
706        };
707
708        let db_inner_instructions = DbInnerInstructions::from(&inner_instructions);
709        check_inner_instructions_equality(&inner_instructions, &db_inner_instructions);
710    }
711
712    fn check_address_table_lookups_equality(
713        address_table_lookups: &MessageAddressTableLookup,
714        db_address_table_lookups: &DbTransactionMessageAddressTableLookup,
715    ) {
716        assert_eq!(
717            address_table_lookups.writable_indexes.len(),
718            db_address_table_lookups.writable_indexes.len()
719        );
720        assert_eq!(
721            address_table_lookups.readonly_indexes.len(),
722            db_address_table_lookups.readonly_indexes.len()
723        );
724
725        for i in 0..address_table_lookups.writable_indexes.len() {
726            assert_eq!(
727                address_table_lookups.writable_indexes[i],
728                db_address_table_lookups.writable_indexes[i] as u8
729            )
730        }
731        for i in 0..address_table_lookups.readonly_indexes.len() {
732            assert_eq!(
733                address_table_lookups.readonly_indexes[i],
734                db_address_table_lookups.readonly_indexes[i] as u8
735            )
736        }
737    }
738
739    #[test]
740    fn test_transform_address_table_lookups() {
741        let address_table_lookups = MessageAddressTableLookup {
742            account_key: Pubkey::new_unique(),
743            writable_indexes: vec![1, 2, 3],
744            readonly_indexes: vec![4, 5, 6],
745        };
746
747        let db_address_table_lookups =
748            DbTransactionMessageAddressTableLookup::from(&address_table_lookups);
749        check_address_table_lookups_equality(&address_table_lookups, &db_address_table_lookups);
750    }
751
752    fn check_reward_equality(reward: &Reward, db_reward: &DbReward) {
753        assert_eq!(reward.pubkey, db_reward.pubkey);
754        assert_eq!(reward.lamports, db_reward.lamports);
755        assert_eq!(reward.post_balance, db_reward.post_balance as u64);
756        assert_eq!(get_reward_type(&reward.reward_type), db_reward.reward_type);
757        assert_eq!(
758            reward.commission,
759            db_reward
760                .commission
761                .as_ref()
762                .map(|commission| *commission as u8)
763        );
764    }
765
766    #[test]
767    fn test_transform_reward() {
768        let reward = Reward {
769            pubkey: Pubkey::new_unique().to_string(),
770            lamports: 1234,
771            post_balance: 45678,
772            reward_type: Some(RewardType::Fee),
773            commission: Some(12),
774        };
775
776        let db_reward = DbReward::from(&reward);
777        check_reward_equality(&reward, &db_reward);
778    }
779
780    fn check_transaction_token_balance_equality(
781        transaction_token_balance: &TransactionTokenBalance,
782        db_transaction_token_balance: &DbTransactionTokenBalance,
783    ) {
784        assert_eq!(
785            transaction_token_balance.account_index,
786            db_transaction_token_balance.account_index as u8
787        );
788        assert_eq!(
789            transaction_token_balance.mint,
790            db_transaction_token_balance.mint
791        );
792        assert_eq!(
793            transaction_token_balance.ui_token_amount.ui_amount,
794            db_transaction_token_balance.ui_token_amount
795        );
796        assert_eq!(
797            transaction_token_balance.owner,
798            db_transaction_token_balance.owner
799        );
800    }
801
802    #[test]
803    fn test_transform_transaction_token_balance() {
804        let transaction_token_balance = TransactionTokenBalance {
805            account_index: 3,
806            mint: Pubkey::new_unique().to_string(),
807            ui_token_amount: UiTokenAmount {
808                ui_amount: Some(0.42),
809                decimals: 2,
810                amount: "42".to_string(),
811                ui_amount_string: "0.42".to_string(),
812            },
813            owner: Pubkey::new_unique().to_string(),
814        };
815
816        let db_transaction_token_balance =
817            DbTransactionTokenBalance::from(&transaction_token_balance);
818
819        check_transaction_token_balance_equality(
820            &transaction_token_balance,
821            &db_transaction_token_balance,
822        );
823    }
824
825    fn check_token_balances(
826        token_balances: &Option<Vec<TransactionTokenBalance>>,
827        db_token_balances: &Option<Vec<DbTransactionTokenBalance>>,
828    ) {
829        assert_eq!(
830            token_balances
831                .as_ref()
832                .map(|token_balances| token_balances.len()),
833            db_token_balances
834                .as_ref()
835                .map(|token_balances| token_balances.len()),
836        );
837
838        if token_balances.is_some() {
839            for i in 0..token_balances.as_ref().unwrap().len() {
840                check_transaction_token_balance_equality(
841                    &token_balances.as_ref().unwrap()[i],
842                    &db_token_balances.as_ref().unwrap()[i],
843                );
844            }
845        }
846    }
847
848    fn check_transaction_status_meta(
849        transaction_status_meta: &TransactionStatusMeta,
850        db_transaction_status_meta: &DbTransactionStatusMeta,
851    ) {
852        assert_eq!(
853            get_transaction_error(&transaction_status_meta.status),
854            db_transaction_status_meta.error
855        );
856        assert_eq!(
857            transaction_status_meta.fee,
858            db_transaction_status_meta.fee as u64
859        );
860        assert_eq!(
861            transaction_status_meta.pre_balances.len(),
862            db_transaction_status_meta.pre_balances.len()
863        );
864
865        for i in 0..transaction_status_meta.pre_balances.len() {
866            assert_eq!(
867                transaction_status_meta.pre_balances[i],
868                db_transaction_status_meta.pre_balances[i] as u64
869            );
870        }
871        assert_eq!(
872            transaction_status_meta.post_balances.len(),
873            db_transaction_status_meta.post_balances.len()
874        );
875        for i in 0..transaction_status_meta.post_balances.len() {
876            assert_eq!(
877                transaction_status_meta.post_balances[i],
878                db_transaction_status_meta.post_balances[i] as u64
879            );
880        }
881        assert_eq!(
882            transaction_status_meta
883                .inner_instructions
884                .as_ref()
885                .map(|inner_instructions| inner_instructions.len()),
886            db_transaction_status_meta
887                .inner_instructions
888                .as_ref()
889                .map(|inner_instructions| inner_instructions.len()),
890        );
891
892        if transaction_status_meta.inner_instructions.is_some() {
893            for i in 0..transaction_status_meta
894                .inner_instructions
895                .as_ref()
896                .unwrap()
897                .len()
898            {
899                check_inner_instructions_equality(
900                    &transaction_status_meta.inner_instructions.as_ref().unwrap()[i],
901                    &db_transaction_status_meta
902                        .inner_instructions
903                        .as_ref()
904                        .unwrap()[i],
905                );
906            }
907        }
908
909        assert_eq!(
910            transaction_status_meta
911                .log_messages
912                .as_ref()
913                .map(|log_messages| log_messages.len()),
914            db_transaction_status_meta
915                .log_messages
916                .as_ref()
917                .map(|log_messages| log_messages.len()),
918        );
919
920        if transaction_status_meta.log_messages.is_some() {
921            for i in 0..transaction_status_meta.log_messages.as_ref().unwrap().len() {
922                assert_eq!(
923                    &transaction_status_meta.log_messages.as_ref().unwrap()[i],
924                    &db_transaction_status_meta.log_messages.as_ref().unwrap()[i]
925                );
926            }
927        }
928
929        check_token_balances(
930            &transaction_status_meta.pre_token_balances,
931            &db_transaction_status_meta.pre_token_balances,
932        );
933
934        check_token_balances(
935            &transaction_status_meta.post_token_balances,
936            &db_transaction_status_meta.post_token_balances,
937        );
938
939        assert_eq!(
940            transaction_status_meta
941                .rewards
942                .as_ref()
943                .map(|rewards| rewards.len()),
944            db_transaction_status_meta
945                .rewards
946                .as_ref()
947                .map(|rewards| rewards.len()),
948        );
949
950        if transaction_status_meta.rewards.is_some() {
951            for i in 0..transaction_status_meta.rewards.as_ref().unwrap().len() {
952                check_reward_equality(
953                    &transaction_status_meta.rewards.as_ref().unwrap()[i],
954                    &db_transaction_status_meta.rewards.as_ref().unwrap()[i],
955                );
956            }
957        }
958    }
959
960    fn build_transaction_status_meta() -> TransactionStatusMeta {
961        TransactionStatusMeta {
962            status: Ok(()),
963            fee: 23456,
964            pre_balances: vec![11, 22, 33],
965            post_balances: vec![44, 55, 66],
966            inner_instructions: Some(vec![InnerInstructions {
967                index: 0,
968                instructions: vec![
969                    CompiledInstruction {
970                        program_id_index: 0,
971                        accounts: vec![1, 2, 3],
972                        data: vec![4, 5, 6],
973                    },
974                    CompiledInstruction {
975                        program_id_index: 1,
976                        accounts: vec![12, 13, 14],
977                        data: vec![24, 25, 26],
978                    },
979                ],
980            }]),
981            log_messages: Some(vec!["message1".to_string(), "message2".to_string()]),
982            pre_token_balances: Some(vec![
983                TransactionTokenBalance {
984                    account_index: 3,
985                    mint: Pubkey::new_unique().to_string(),
986                    ui_token_amount: UiTokenAmount {
987                        ui_amount: Some(0.42),
988                        decimals: 2,
989                        amount: "42".to_string(),
990                        ui_amount_string: "0.42".to_string(),
991                    },
992                    owner: Pubkey::new_unique().to_string(),
993                },
994                TransactionTokenBalance {
995                    account_index: 2,
996                    mint: Pubkey::new_unique().to_string(),
997                    ui_token_amount: UiTokenAmount {
998                        ui_amount: Some(0.38),
999                        decimals: 2,
1000                        amount: "38".to_string(),
1001                        ui_amount_string: "0.38".to_string(),
1002                    },
1003                    owner: Pubkey::new_unique().to_string(),
1004                },
1005            ]),
1006            post_token_balances: Some(vec![
1007                TransactionTokenBalance {
1008                    account_index: 3,
1009                    mint: Pubkey::new_unique().to_string(),
1010                    ui_token_amount: UiTokenAmount {
1011                        ui_amount: Some(0.82),
1012                        decimals: 2,
1013                        amount: "82".to_string(),
1014                        ui_amount_string: "0.82".to_string(),
1015                    },
1016                    owner: Pubkey::new_unique().to_string(),
1017                },
1018                TransactionTokenBalance {
1019                    account_index: 2,
1020                    mint: Pubkey::new_unique().to_string(),
1021                    ui_token_amount: UiTokenAmount {
1022                        ui_amount: Some(0.48),
1023                        decimals: 2,
1024                        amount: "48".to_string(),
1025                        ui_amount_string: "0.48".to_string(),
1026                    },
1027                    owner: Pubkey::new_unique().to_string(),
1028                },
1029            ]),
1030            rewards: Some(vec![
1031                Reward {
1032                    pubkey: Pubkey::new_unique().to_string(),
1033                    lamports: 1234,
1034                    post_balance: 45678,
1035                    reward_type: Some(RewardType::Fee),
1036                    commission: Some(12),
1037                },
1038                Reward {
1039                    pubkey: Pubkey::new_unique().to_string(),
1040                    lamports: 234,
1041                    post_balance: 324,
1042                    reward_type: Some(RewardType::Staking),
1043                    commission: Some(11),
1044                },
1045            ]),
1046            loaded_addresses: LoadedAddresses {
1047                writable: vec![Pubkey::new_unique(), Pubkey::new_unique()],
1048                readonly: vec![Pubkey::new_unique(), Pubkey::new_unique()],
1049            },
1050        }
1051    }
1052
1053    #[test]
1054    fn test_transform_transaction_status_meta() {
1055        let transaction_status_meta = build_transaction_status_meta();
1056        let db_transaction_status_meta = DbTransactionStatusMeta::from(&transaction_status_meta);
1057        check_transaction_status_meta(&transaction_status_meta, &db_transaction_status_meta);
1058    }
1059
1060    fn check_message_header_equality(
1061        message_header: &MessageHeader,
1062        db_message_header: &DbTransactionMessageHeader,
1063    ) {
1064        assert_eq!(
1065            message_header.num_readonly_signed_accounts,
1066            db_message_header.num_readonly_signed_accounts as u8
1067        );
1068        assert_eq!(
1069            message_header.num_readonly_unsigned_accounts,
1070            db_message_header.num_readonly_unsigned_accounts as u8
1071        );
1072        assert_eq!(
1073            message_header.num_required_signatures,
1074            db_message_header.num_required_signatures as u8
1075        );
1076    }
1077
1078    #[test]
1079    fn test_transform_transaction_message_header() {
1080        let message_header = MessageHeader {
1081            num_readonly_signed_accounts: 1,
1082            num_readonly_unsigned_accounts: 2,
1083            num_required_signatures: 3,
1084        };
1085
1086        let db_message_header = DbTransactionMessageHeader::from(&message_header);
1087        check_message_header_equality(&message_header, &db_message_header)
1088    }
1089
1090    fn check_transaction_message_equality(message: &Message, db_message: &DbTransactionMessage) {
1091        check_message_header_equality(&message.header, &db_message.header);
1092        assert_eq!(message.account_keys.len(), db_message.account_keys.len());
1093        for i in 0..message.account_keys.len() {
1094            assert_eq!(message.account_keys[i].as_ref(), db_message.account_keys[i]);
1095        }
1096        assert_eq!(message.instructions.len(), db_message.instructions.len());
1097        for i in 0..message.instructions.len() {
1098            check_compiled_instruction_equality(
1099                &message.instructions[i],
1100                &db_message.instructions[i],
1101            );
1102        }
1103    }
1104
1105    fn build_message() -> Message {
1106        Message {
1107            header: MessageHeader {
1108                num_readonly_signed_accounts: 11,
1109                num_readonly_unsigned_accounts: 12,
1110                num_required_signatures: 13,
1111            },
1112            account_keys: vec![Pubkey::new_unique(), Pubkey::new_unique()],
1113            recent_blockhash: Hash::new_unique(),
1114            instructions: vec![
1115                CompiledInstruction {
1116                    program_id_index: 0,
1117                    accounts: vec![1, 2, 3],
1118                    data: vec![4, 5, 6],
1119                },
1120                CompiledInstruction {
1121                    program_id_index: 3,
1122                    accounts: vec![11, 12, 13],
1123                    data: vec![14, 15, 16],
1124                },
1125            ],
1126        }
1127    }
1128
1129    #[test]
1130    fn test_transform_transaction_message() {
1131        let message = build_message();
1132
1133        let db_message = DbTransactionMessage::from(&message);
1134        check_transaction_message_equality(&message, &db_message);
1135    }
1136
1137    fn check_transaction_message_v0_equality(
1138        message: &v0::Message,
1139        db_message: &DbTransactionMessageV0,
1140    ) {
1141        check_message_header_equality(&message.header, &db_message.header);
1142        assert_eq!(message.account_keys.len(), db_message.account_keys.len());
1143        for i in 0..message.account_keys.len() {
1144            assert_eq!(message.account_keys[i].as_ref(), db_message.account_keys[i]);
1145        }
1146        assert_eq!(message.instructions.len(), db_message.instructions.len());
1147        for i in 0..message.instructions.len() {
1148            check_compiled_instruction_equality(
1149                &message.instructions[i],
1150                &db_message.instructions[i],
1151            );
1152        }
1153        assert_eq!(
1154            message.address_table_lookups.len(),
1155            db_message.address_table_lookups.len()
1156        );
1157        for i in 0..message.address_table_lookups.len() {
1158            check_address_table_lookups_equality(
1159                &message.address_table_lookups[i],
1160                &db_message.address_table_lookups[i],
1161            );
1162        }
1163    }
1164
1165    fn build_transaction_message_v0() -> v0::Message {
1166        v0::Message {
1167            header: MessageHeader {
1168                num_readonly_signed_accounts: 2,
1169                num_readonly_unsigned_accounts: 2,
1170                num_required_signatures: 3,
1171            },
1172            account_keys: vec![
1173                Pubkey::new_unique(),
1174                Pubkey::new_unique(),
1175                Pubkey::new_unique(),
1176                Pubkey::new_unique(),
1177                Pubkey::new_unique(),
1178            ],
1179            recent_blockhash: Hash::new_unique(),
1180            instructions: vec![
1181                CompiledInstruction {
1182                    program_id_index: 1,
1183                    accounts: vec![1, 2, 3],
1184                    data: vec![4, 5, 6],
1185                },
1186                CompiledInstruction {
1187                    program_id_index: 2,
1188                    accounts: vec![0, 1, 2],
1189                    data: vec![14, 15, 16],
1190                },
1191            ],
1192            address_table_lookups: vec![
1193                MessageAddressTableLookup {
1194                    account_key: Pubkey::new_unique(),
1195                    writable_indexes: vec![0],
1196                    readonly_indexes: vec![1, 2],
1197                },
1198                MessageAddressTableLookup {
1199                    account_key: Pubkey::new_unique(),
1200                    writable_indexes: vec![1],
1201                    readonly_indexes: vec![0, 2],
1202                },
1203            ],
1204        }
1205    }
1206
1207    #[test]
1208    fn test_transform_transaction_message_v0() {
1209        let message = build_transaction_message_v0();
1210
1211        let db_message = DbTransactionMessageV0::from(&message);
1212        check_transaction_message_v0_equality(&message, &db_message);
1213    }
1214
1215    fn check_loaded_addresses(
1216        loaded_addresses: &LoadedAddresses,
1217        db_loaded_addresses: &DbLoadedAddresses,
1218    ) {
1219        assert_eq!(
1220            loaded_addresses.writable.len(),
1221            db_loaded_addresses.writable.len()
1222        );
1223        for i in 0..loaded_addresses.writable.len() {
1224            assert_eq!(
1225                loaded_addresses.writable[i].as_ref(),
1226                db_loaded_addresses.writable[i]
1227            );
1228        }
1229
1230        assert_eq!(
1231            loaded_addresses.readonly.len(),
1232            db_loaded_addresses.readonly.len()
1233        );
1234        for i in 0..loaded_addresses.readonly.len() {
1235            assert_eq!(
1236                loaded_addresses.readonly[i].as_ref(),
1237                db_loaded_addresses.readonly[i]
1238            );
1239        }
1240    }
1241
1242    fn check_loaded_message_v0_equality(
1243        message: &v0::LoadedMessage,
1244        db_message: &DbLoadedMessageV0,
1245    ) {
1246        check_transaction_message_v0_equality(&message.message, &db_message.message);
1247        check_loaded_addresses(&message.loaded_addresses, &db_message.loaded_addresses);
1248    }
1249
1250    #[test]
1251    fn test_transform_loaded_message_v0() {
1252        let message = v0::LoadedMessage::new(
1253            build_transaction_message_v0(),
1254            LoadedAddresses {
1255                writable: vec![Pubkey::new_unique(), Pubkey::new_unique()],
1256                readonly: vec![Pubkey::new_unique(), Pubkey::new_unique()],
1257            },
1258        );
1259
1260        let db_message = DbLoadedMessageV0::from(&message);
1261        check_loaded_message_v0_equality(&message, &db_message);
1262    }
1263
1264    fn check_transaction(
1265        slot: u64,
1266        transaction: &ReplicaTransactionInfo,
1267        db_transaction: &DbTransaction,
1268    ) {
1269        assert_eq!(transaction.signature.as_ref(), db_transaction.signature);
1270        assert_eq!(transaction.is_vote, db_transaction.is_vote);
1271        assert_eq!(slot, db_transaction.slot as u64);
1272        match transaction.transaction.message() {
1273            SanitizedMessage::Legacy(message) => {
1274                assert_eq!(db_transaction.message_type, 0);
1275                check_transaction_message_equality(
1276                    message,
1277                    db_transaction.legacy_message.as_ref().unwrap(),
1278                );
1279            }
1280            SanitizedMessage::V0(message) => {
1281                assert_eq!(db_transaction.message_type, 1);
1282                check_loaded_message_v0_equality(
1283                    message,
1284                    db_transaction.v0_loaded_message.as_ref().unwrap(),
1285                );
1286            }
1287        }
1288
1289        assert_eq!(
1290            transaction.transaction.signatures().len(),
1291            db_transaction.signatures.len()
1292        );
1293
1294        for i in 0..transaction.transaction.signatures().len() {
1295            assert_eq!(
1296                transaction.transaction.signatures()[i].as_ref(),
1297                db_transaction.signatures[i]
1298            );
1299        }
1300
1301        assert_eq!(
1302            transaction.transaction.message_hash().as_ref(),
1303            db_transaction.message_hash
1304        );
1305
1306        check_transaction_status_meta(transaction.transaction_status_meta, &db_transaction.meta);
1307    }
1308
1309    fn build_test_transaction_legacy() -> Transaction {
1310        let keypair1 = Keypair::new();
1311        let pubkey1 = keypair1.pubkey();
1312        let zero = Hash::default();
1313        system_transaction::transfer(&keypair1, &pubkey1, 42, zero)
1314    }
1315
1316    #[test]
1317    fn test_build_db_transaction_legacy() {
1318        let signature = Signature::new(&[1u8; 64]);
1319
1320        let message_hash = Hash::new_unique();
1321        let transaction = build_test_transaction_legacy();
1322
1323        let transaction = VersionedTransaction::from(transaction);
1324
1325        let transaction = SanitizedTransaction::try_create(
1326            transaction,
1327            message_hash,
1328            Some(true),
1329            SimpleAddressLoader::Disabled,
1330        )
1331        .unwrap();
1332
1333        let transaction_status_meta = build_transaction_status_meta();
1334        let transaction_info = ReplicaTransactionInfo {
1335            signature: &signature,
1336            is_vote: false,
1337            transaction: &transaction,
1338            transaction_status_meta: &transaction_status_meta,
1339        };
1340
1341        let slot = 54;
1342        let db_transaction = build_db_transaction(slot, &transaction_info);
1343        check_transaction(slot, &transaction_info, &db_transaction);
1344    }
1345
1346    fn build_test_transaction_v0() -> VersionedTransaction {
1347        VersionedTransaction {
1348            signatures: vec![
1349                Signature::new(&[1u8; 64]),
1350                Signature::new(&[2u8; 64]),
1351                Signature::new(&[3u8; 64]),
1352            ],
1353            message: VersionedMessage::V0(build_transaction_message_v0()),
1354        }
1355    }
1356
1357    #[test]
1358    fn test_build_db_transaction_v0() {
1359        let signature = Signature::new(&[1u8; 64]);
1360
1361        let message_hash = Hash::new_unique();
1362        let transaction = build_test_transaction_v0();
1363
1364        transaction.sanitize().unwrap();
1365
1366        let transaction = SanitizedTransaction::try_create(
1367            transaction,
1368            message_hash,
1369            Some(true),
1370            SimpleAddressLoader::Enabled(LoadedAddresses {
1371                writable: vec![Pubkey::new_unique(), Pubkey::new_unique()],
1372                readonly: vec![Pubkey::new_unique(), Pubkey::new_unique()],
1373            }),
1374        )
1375        .unwrap();
1376
1377        let transaction_status_meta = build_transaction_status_meta();
1378        let transaction_info = ReplicaTransactionInfo {
1379            signature: &signature,
1380            is_vote: true,
1381            transaction: &transaction,
1382            transaction_status_meta: &transaction_status_meta,
1383        };
1384
1385        let slot = 54;
1386        let db_transaction = build_db_transaction(slot, &transaction_info);
1387        check_transaction(slot, &transaction_info, &db_transaction);
1388    }
1389}