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