Browse Source

Airgap`TransactionError` type from blockstore (#6434)

* Add `StoredTransaction`

* Nits from review
Steven Luscher 5 months ago
parent
commit
d849be8cb2
4 changed files with 135 additions and 2 deletions
  1. 1 0
      Cargo.lock
  2. 1 0
      storage-proto/Cargo.toml
  3. 15 1
      storage-proto/src/convert.rs
  4. 118 1
      storage-proto/src/lib.rs

+ 1 - 0
Cargo.lock

@@ -10670,6 +10670,7 @@ dependencies = [
  "solana-transaction-context",
  "solana-transaction-error",
  "solana-transaction-status",
+ "test-case",
  "tonic-build",
 ]
 

+ 1 - 0
storage-proto/Cargo.toml

@@ -43,3 +43,4 @@ protobuf-src = { workspace = true }
 
 [dev-dependencies]
 enum-iterator = { workspace = true }
+test-case = { workspace = true }

+ 15 - 1
storage-proto/src/convert.rs

@@ -1,5 +1,5 @@
 use {
-    crate::{StoredExtendedRewards, StoredTransactionStatusMeta},
+    crate::{StoredExtendedRewards, StoredTransactionError, StoredTransactionStatusMeta},
     solana_account_decoder::parse_token::{real_number_string_trimmed, UiTokenAmount},
     solana_hash::{Hash, HASH_BYTES},
     solana_instruction::error::InstructionError,
@@ -286,6 +286,20 @@ impl From<generated::Transaction> for VersionedTransaction {
     }
 }
 
+impl From<TransactionError> for generated::TransactionError {
+    fn from(value: TransactionError) -> Self {
+        let stored_error = StoredTransactionError::from(value).0;
+        Self { err: stored_error }
+    }
+}
+
+impl From<generated::TransactionError> for TransactionError {
+    fn from(value: generated::TransactionError) -> Self {
+        let stored_error = StoredTransactionError(value.err);
+        stored_error.into()
+    }
+}
+
 impl From<LegacyMessage> for generated::Message {
     fn from(message: LegacyMessage) -> Self {
         Self {

+ 118 - 1
storage-proto/src/lib.rs

@@ -7,7 +7,7 @@ use {
     solana_message::v0::LoadedAddresses,
     solana_serde::default_on_eof,
     solana_transaction_context::TransactionReturnData,
-    solana_transaction_error::TransactionResult as Result,
+    solana_transaction_error::{TransactionError, TransactionResult as Result},
     solana_transaction_status::{
         InnerInstructions, Reward, RewardType, TransactionStatusMeta, TransactionTokenBalance,
     },
@@ -109,6 +109,22 @@ impl From<UiTokenAmount> for StoredTokenAmount {
     }
 }
 
+struct StoredTransactionError(Vec<u8>);
+
+impl From<StoredTransactionError> for TransactionError {
+    fn from(value: StoredTransactionError) -> Self {
+        let bytes = value.0;
+        bincode::deserialize(&bytes).expect("transaction error to deserialize from bytes")
+    }
+}
+
+impl From<TransactionError> for StoredTransactionError {
+    fn from(value: TransactionError) -> Self {
+        let bytes = bincode::serialize(&value).expect("transaction error to serialize to bytes");
+        StoredTransactionError(bytes)
+    }
+}
+
 #[derive(Serialize, Deserialize)]
 pub struct StoredTransactionTokenBalance {
     pub account_index: u8,
@@ -265,3 +281,104 @@ impl TryFrom<TransactionStatusMeta> for StoredTransactionStatusMeta {
         })
     }
 }
+
+#[cfg(test)]
+mod tests {
+    use {
+        crate::StoredTransactionError, solana_instruction::error::InstructionError,
+        solana_transaction_error::TransactionError, test_case::test_case,
+    };
+
+    #[test_case(TransactionError::InsufficientFundsForFee; "Named variant error")]
+    #[test_case(TransactionError::InsufficientFundsForRent { account_index: 42 }; "Struct variant error")]
+    #[test_case(TransactionError::DuplicateInstruction(42); "Single-value tuple variant error")]
+    #[test_case(TransactionError::InstructionError(42, InstructionError::Custom(0xdeadbeef)); "`InstructionError`")]
+    fn test_serialize_transaction_error_to_stored_transaction_error_round_trip(
+        err: TransactionError,
+    ) {
+        let serialized: StoredTransactionError = err.clone().into();
+        let deserialized: TransactionError = serialized.into();
+        assert_eq!(deserialized, err);
+    }
+
+    #[test_case(
+        vec![4, 0, 0, 0,  /* Fourth enum variant - `InsufficientFundsForFee` */],
+        TransactionError::InsufficientFundsForFee;
+        "Named variant error"
+    )]
+    #[test_case(
+        vec![
+            31, 0, 0, 0,  /* Thirty-first enum variant - `InsufficientFundsForRent` */
+            42, /* Account index */
+        ],
+        TransactionError::InsufficientFundsForRent { account_index: 42 };
+        "Struct variant error"
+    )]
+    #[test_case(
+        vec![
+            30, 0, 0, 0,  /* Thirtieth enum variant - `DuplicateInstruction` */
+            42, /* Instruction index */
+        ],
+        TransactionError::DuplicateInstruction(42);
+        "Single-value tuple variant error"
+    )]
+    #[test_case(
+        vec![
+            8, 0, 0, 0,  /* Eighth enum variant - `InstructionError` */
+            42, /* Outer instruction index */
+            25, 0, 0, 0, /* InstructionError::Custom */
+            /* 0xdeadbeef */
+            239, 190, 173, 222,
+        ],
+        TransactionError::InstructionError(42, InstructionError::Custom(0xdeadbeef));
+        "`InstructionError`"
+    )]
+    fn test_deserialize_stored_transaction_error(
+        stored_bytes: Vec<u8>,
+        expected_transaction_error: TransactionError,
+    ) {
+        let stored_transaction = StoredTransactionError(stored_bytes);
+        let deserialized: TransactionError = stored_transaction.into();
+        assert_eq!(deserialized, expected_transaction_error);
+    }
+
+    #[test_case(
+        vec![4, 0, 0, 0,  /* Fourth enum variant - `InsufficientFundsForFee` */],
+        TransactionError::InsufficientFundsForFee;
+        "Named variant error"
+    )]
+    #[test_case(
+        vec![
+            31, 0, 0, 0,  /* Thirty-first enum variant - `InsufficientFundsForRent` */
+            42, /* Account index */
+        ],
+        TransactionError::InsufficientFundsForRent { account_index: 42 };
+        "Struct variant error"
+    )]
+    #[test_case(
+        vec![
+            30, 0, 0, 0,  /* Thirtieth enum variant - `DuplicateInstruction` */
+            42, /* Instruction index */
+        ],
+        TransactionError::DuplicateInstruction(42);
+        "Single-value tuple variant error"
+    )]
+    #[test_case(
+        vec![
+            8, 0, 0, 0,  /* Eighth enum variant - `InstructionError` */
+            42, /* Outer instruction index */
+            25, 0, 0, 0, /* InstructionError::Custom */
+            /* 0xdeadbeef */
+            239, 190, 173, 222,
+        ],
+        TransactionError::InstructionError(42, InstructionError::Custom(0xdeadbeef));
+        "`InstructionError`"
+    )]
+    fn test_seserialize_stored_transaction_error(
+        expected_serialized_bytes: Vec<u8>,
+        transaction_error: TransactionError,
+    ) {
+        let StoredTransactionError(serialized_bytes) = transaction_error.into();
+        assert_eq!(serialized_bytes, expected_serialized_bytes);
+    }
+}