Browse Source

Generic Transaction Trait (#1918)

Andrew Fitzgerald 1 year ago
parent
commit
a191a77e22

+ 7 - 0
Cargo.lock

@@ -7514,6 +7514,13 @@ dependencies = [
  "prost-types",
 ]
 
+[[package]]
+name = "solana-svm-transaction"
+version = "2.1.0"
+dependencies = [
+ "solana-sdk",
+]
+
 [[package]]
 name = "solana-system-program"
 version = "2.1.0"

+ 2 - 0
Cargo.toml

@@ -112,6 +112,7 @@ members = [
     "streamer",
     "svm",
     "svm-conformance",
+    "svm-transaction",
     "test-validator",
     "thin-client",
     "timings",
@@ -403,6 +404,7 @@ solana-storage-proto = { path = "storage-proto", version = "=2.1.0" }
 solana-streamer = { path = "streamer", version = "=2.1.0" }
 solana-svm = { path = "svm", version = "=2.1.0" }
 solana-svm-conformance = { path = "svm-conformance", version = "=2.1.0" }
+solana-svm-transaction = { path = "svm-transaction", version = "=2.1.0" }
 solana-system-program = { path = "programs/system", version = "=2.1.0" }
 solana-test-validator = { path = "test-validator", version = "=2.1.0" }
 solana-thin-client = { path = "thin-client", version = "=2.1.0" }

+ 13 - 0
svm-transaction/Cargo.toml

@@ -0,0 +1,13 @@
+[package]
+name = "solana-svm-transaction"
+description = "Solana SVM Transaction"
+documentation = "https://docs.rs/solana-svm-transaction"
+version = { workspace = true }
+authors = { workspace = true }
+repository = { workspace = true }
+homepage = { workspace = true }
+license = { workspace = true }
+edition = { workspace = true }
+
+[dependencies]
+solana-sdk = { workspace = true }

+ 24 - 0
svm-transaction/src/instruction.rs

@@ -0,0 +1,24 @@
+use solana_sdk::instruction::CompiledInstruction;
+
+/// A non-owning version of [`CompiledInstruction`] that references
+/// slices of account indexes and data.
+// `program_id_index` is still owned, as it is a simple u8.
+#[derive(Debug, PartialEq, Eq, Clone)]
+pub struct SVMInstruction<'a> {
+    /// Index into the transaction keys array indicating the program account that executes this instruction.
+    pub program_id_index: u8,
+    /// Ordered indices into the transaction keys array indicating which accounts to pass to the program.
+    pub accounts: &'a [u8],
+    /// The program input data.
+    pub data: &'a [u8],
+}
+
+impl<'a> From<&'a CompiledInstruction> for SVMInstruction<'a> {
+    fn from(ix: &'a CompiledInstruction) -> Self {
+        Self {
+            program_id_index: ix.program_id_index,
+            accounts: ix.accounts.as_slice(),
+            data: ix.data.as_slice(),
+        }
+    }
+}

+ 4 - 0
svm-transaction/src/lib.rs

@@ -0,0 +1,4 @@
+pub mod instruction;
+pub mod message_address_table_lookup;
+pub mod svm_message;
+pub mod svm_transaction;

+ 23 - 0
svm-transaction/src/message_address_table_lookup.rs

@@ -0,0 +1,23 @@
+use solana_sdk::{message::v0, pubkey::Pubkey};
+
+/// A non-owning version of [`v0::MessageAddressTableLookup`].
+/// This simply references the data in the original message.
+#[derive(Debug, PartialEq, Eq, Clone)]
+pub struct SVMMessageAddressTableLookup<'a> {
+    /// Address lookup table account key
+    pub account_key: &'a Pubkey,
+    /// List of indexes used to load writable account addresses
+    pub writable_indexes: &'a [u8],
+    /// List of indexes used to load readonly account addresses
+    pub readonly_indexes: &'a [u8],
+}
+
+impl<'a> From<&'a v0::MessageAddressTableLookup> for SVMMessageAddressTableLookup<'a> {
+    fn from(lookup: &'a v0::MessageAddressTableLookup) -> Self {
+        Self {
+            account_key: &lookup.account_key,
+            writable_indexes: &lookup.writable_indexes,
+            readonly_indexes: &lookup.readonly_indexes,
+        }
+    }
+}

+ 66 - 0
svm-transaction/src/svm_message.rs

@@ -0,0 +1,66 @@
+use {
+    crate::{
+        instruction::SVMInstruction, message_address_table_lookup::SVMMessageAddressTableLookup,
+    },
+    core::fmt::Debug,
+    solana_sdk::{hash::Hash, message::AccountKeys, pubkey::Pubkey},
+};
+
+mod sanitized_message;
+mod sanitized_transaction;
+
+// - Debug to support legacy logging
+pub trait SVMMessage: Debug {
+    /// Return the number of signatures in the message.
+    fn num_signatures(&self) -> u64;
+
+    /// Returns the number of requested write-locks in this message.
+    /// This does not consider if write-locks are demoted.
+    fn num_write_locks(&self) -> u64;
+
+    /// Return the recent blockhash.
+    fn recent_blockhash(&self) -> &Hash;
+
+    /// Return the number of instructions in the message.
+    fn num_instructions(&self) -> usize;
+
+    /// Return an iterator over the instructions in the message.
+    fn instructions_iter(&self) -> impl Iterator<Item = SVMInstruction>;
+
+    /// Return an iterator over the instructions in the message, paired with
+    /// the pubkey of the program.
+    fn program_instructions_iter(&self) -> impl Iterator<Item = (&Pubkey, SVMInstruction)>;
+
+    /// Return the account keys.
+    fn account_keys(&self) -> AccountKeys;
+
+    /// Return the fee-payer
+    fn fee_payer(&self) -> &Pubkey;
+
+    /// Returns `true` if the account at `index` is writable.
+    fn is_writable(&self, index: usize) -> bool;
+
+    /// Returns `true` if the account at `index` is signer.
+    fn is_signer(&self, index: usize) -> bool;
+
+    /// Returns true if the account at the specified index is invoked as a
+    /// program in top-level instructions of this message.
+    fn is_invoked(&self, key_index: usize) -> bool;
+
+    /// Returns true if the account at the specified index is an input to some
+    /// program instruction in this message.
+    fn is_instruction_account(&self, key_index: usize) -> bool {
+        if let Ok(key_index) = u8::try_from(key_index) {
+            self.instructions_iter()
+                .any(|ix| ix.accounts.contains(&key_index))
+        } else {
+            false
+        }
+    }
+
+    /// Get the number of lookup tables.
+    fn num_lookup_tables(&self) -> usize;
+
+    /// Get message address table lookups used in the message
+    fn message_address_table_lookups(&self) -> impl Iterator<Item = SVMMessageAddressTableLookup>;
+}

+ 71 - 0
svm-transaction/src/svm_message/sanitized_message.rs

@@ -0,0 +1,71 @@
+use {
+    crate::{
+        instruction::SVMInstruction, message_address_table_lookup::SVMMessageAddressTableLookup,
+        svm_message::SVMMessage,
+    },
+    solana_sdk::{
+        hash::Hash,
+        message::{AccountKeys, SanitizedMessage},
+        pubkey::Pubkey,
+    },
+};
+
+// Implement for the "reference" `SanitizedMessage` type.
+impl SVMMessage for SanitizedMessage {
+    fn num_signatures(&self) -> u64 {
+        SanitizedMessage::num_signatures(self)
+    }
+
+    fn num_write_locks(&self) -> u64 {
+        SanitizedMessage::num_write_locks(self)
+    }
+
+    fn recent_blockhash(&self) -> &Hash {
+        SanitizedMessage::recent_blockhash(self)
+    }
+
+    fn num_instructions(&self) -> usize {
+        SanitizedMessage::instructions(self).len()
+    }
+
+    fn instructions_iter(&self) -> impl Iterator<Item = SVMInstruction> {
+        SanitizedMessage::instructions(self)
+            .iter()
+            .map(SVMInstruction::from)
+    }
+
+    fn program_instructions_iter(&self) -> impl Iterator<Item = (&Pubkey, SVMInstruction)> {
+        SanitizedMessage::program_instructions_iter(self)
+            .map(|(pubkey, ix)| (pubkey, SVMInstruction::from(ix)))
+    }
+
+    fn account_keys(&self) -> AccountKeys {
+        SanitizedMessage::account_keys(self)
+    }
+
+    fn fee_payer(&self) -> &Pubkey {
+        SanitizedMessage::fee_payer(self)
+    }
+
+    fn is_writable(&self, index: usize) -> bool {
+        SanitizedMessage::is_writable(self, index)
+    }
+
+    fn is_signer(&self, index: usize) -> bool {
+        SanitizedMessage::is_signer(self, index)
+    }
+
+    fn is_invoked(&self, key_index: usize) -> bool {
+        SanitizedMessage::is_invoked(self, key_index)
+    }
+
+    fn num_lookup_tables(&self) -> usize {
+        SanitizedMessage::message_address_table_lookups(self).len()
+    }
+
+    fn message_address_table_lookups(&self) -> impl Iterator<Item = SVMMessageAddressTableLookup> {
+        SanitizedMessage::message_address_table_lookups(self)
+            .iter()
+            .map(SVMMessageAddressTableLookup::from)
+    }
+}

+ 63 - 0
svm-transaction/src/svm_message/sanitized_transaction.rs

@@ -0,0 +1,63 @@
+use {
+    crate::{
+        instruction::SVMInstruction, message_address_table_lookup::SVMMessageAddressTableLookup,
+        svm_message::SVMMessage,
+    },
+    solana_sdk::{
+        hash::Hash, message::AccountKeys, pubkey::Pubkey, transaction::SanitizedTransaction,
+    },
+};
+
+impl SVMMessage for SanitizedTransaction {
+    fn num_signatures(&self) -> u64 {
+        SVMMessage::num_signatures(SanitizedTransaction::message(self))
+    }
+
+    fn num_write_locks(&self) -> u64 {
+        SVMMessage::num_write_locks(SanitizedTransaction::message(self))
+    }
+
+    fn recent_blockhash(&self) -> &Hash {
+        SVMMessage::recent_blockhash(SanitizedTransaction::message(self))
+    }
+
+    fn num_instructions(&self) -> usize {
+        SVMMessage::num_instructions(SanitizedTransaction::message(self))
+    }
+
+    fn instructions_iter(&self) -> impl Iterator<Item = SVMInstruction> {
+        SVMMessage::instructions_iter(SanitizedTransaction::message(self))
+    }
+
+    fn program_instructions_iter(&self) -> impl Iterator<Item = (&Pubkey, SVMInstruction)> {
+        SVMMessage::program_instructions_iter(SanitizedTransaction::message(self))
+    }
+
+    fn account_keys(&self) -> AccountKeys {
+        SVMMessage::account_keys(SanitizedTransaction::message(self))
+    }
+
+    fn fee_payer(&self) -> &Pubkey {
+        SVMMessage::fee_payer(SanitizedTransaction::message(self))
+    }
+
+    fn is_writable(&self, index: usize) -> bool {
+        SVMMessage::is_writable(SanitizedTransaction::message(self), index)
+    }
+
+    fn is_signer(&self, index: usize) -> bool {
+        SVMMessage::is_signer(SanitizedTransaction::message(self), index)
+    }
+
+    fn is_invoked(&self, key_index: usize) -> bool {
+        SVMMessage::is_invoked(SanitizedTransaction::message(self), key_index)
+    }
+
+    fn num_lookup_tables(&self) -> usize {
+        SVMMessage::num_lookup_tables(SanitizedTransaction::message(self))
+    }
+
+    fn message_address_table_lookups(&self) -> impl Iterator<Item = SVMMessageAddressTableLookup> {
+        SVMMessage::message_address_table_lookups(SanitizedTransaction::message(self))
+    }
+}

+ 11 - 0
svm-transaction/src/svm_transaction.rs

@@ -0,0 +1,11 @@
+use {crate::svm_message::SVMMessage, solana_sdk::signature::Signature};
+
+mod sanitized_transaction;
+
+pub trait SVMTransaction: SVMMessage {
+    /// Get the first signature of the message.
+    fn signature(&self) -> &Signature;
+
+    /// Get all the signatures of the message.
+    fn signatures(&self) -> &[Signature];
+}

+ 14 - 0
svm-transaction/src/svm_transaction/sanitized_transaction.rs

@@ -0,0 +1,14 @@
+use {
+    crate::svm_transaction::SVMTransaction,
+    solana_sdk::{signature::Signature, transaction::SanitizedTransaction},
+};
+
+impl SVMTransaction for SanitizedTransaction {
+    fn signature(&self) -> &Signature {
+        SanitizedTransaction::signature(self)
+    }
+
+    fn signatures(&self) -> &[Signature] {
+        SanitizedTransaction::signatures(self)
+    }
+}