From e4e232c702c8120a8b9864d62cefd9a035b351c3 Mon Sep 17 00:00:00 2001 From: Kevin Heavey Date: Wed, 4 Dec 2024 05:57:47 +0400 Subject: [PATCH] Extract solana-transaction from solana-sdk (#3634) * extract transaction crate * make serde and bincode optional * fix frozen-abi support * missing dep for frozen-abi * update digest * fix circular dep * fmt * missing feature activation * add doc_auto_cfg * add "wasm32-unknown-unknown" to doc targets * make solana-feature-set optional * inline some consts * fix wasm support * update expected error * update expected error * missing #![cfg(feature = "full")] * update digest * update digest * remove solana-program (except for dev deps) * add wasm32-unknown-unknown to doc targets * update docs links * tighten deps * remove extraneous commit * use explicit link --- Cargo.lock | 40 +++ Cargo.toml | 2 + gossip/src/protocol.rs | 2 +- programs/sbf/Cargo.lock | 27 ++ rpc/src/rpc.rs | 12 +- sdk/Cargo.toml | 11 +- sdk/src/lib.rs | 7 +- sdk/src/transaction.rs | 21 ++ sdk/src/wasm/transaction.rs | 57 +--- sdk/transaction/Cargo.toml | 89 ++++++ .../mod.rs => transaction/src/lib.rs} | 253 ++++++++++-------- .../src}/sanitized.rs | 69 ++--- .../src/simple_vote_transaction_checker.rs | 9 +- .../src}/versioned/mod.rs | 89 +++--- .../src}/versioned/sanitized.rs | 12 +- sdk/transaction/src/wasm.rs | 55 ++++ svm/examples/Cargo.lock | 27 ++ 17 files changed, 514 insertions(+), 268 deletions(-) create mode 100644 sdk/src/transaction.rs create mode 100644 sdk/transaction/Cargo.toml rename sdk/{src/transaction/mod.rs => transaction/src/lib.rs} (90%) rename sdk/{src/transaction => transaction/src}/sanitized.rs (91%) rename sdk/{ => transaction}/src/simple_vote_transaction_checker.rs (84%) rename sdk/{src/transaction => transaction/src}/versioned/mod.rs (86%) rename sdk/{src/transaction => transaction/src}/versioned/sanitized.rs (88%) create mode 100644 sdk/transaction/src/wasm.rs diff --git a/Cargo.lock b/Cargo.lock index d183082c043e28..ef0a4cefc53964 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8558,6 +8558,7 @@ dependencies = [ "solana-signer", "solana-sysvar", "solana-time-utils", + "solana-transaction", "solana-transaction-context", "solana-transaction-error", "static_assertions", @@ -9278,6 +9279,45 @@ dependencies = [ "tokio-util 0.7.12", ] +[[package]] +name = "solana-transaction" +version = "2.2.0" +dependencies = [ + "anyhow", + "bincode", + "borsh 1.5.3", + "serde", + "serde_derive", + "solana-bincode", + "solana-feature-set", + "solana-frozen-abi", + "solana-frozen-abi-macro", + "solana-hash", + "solana-instruction", + "solana-keypair", + "solana-logger", + "solana-message", + "solana-nonce", + "solana-packet", + "solana-precompiles", + "solana-presigner", + "solana-program", + "solana-pubkey", + "solana-reserved-account-keys", + "solana-sanitize", + "solana-sdk", + "solana-sdk-ids", + "solana-sha256-hasher", + "solana-short-vec", + "solana-signature", + "solana-signer", + "solana-system-interface", + "solana-transaction", + "solana-transaction-error", + "static_assertions", + "wasm-bindgen", +] + [[package]] name = "solana-transaction-context" version = "2.2.0" diff --git a/Cargo.toml b/Cargo.toml index d8cd457ade48d6..62a73ccff7d2a0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -176,6 +176,7 @@ members = [ "sdk/sysvar", "sdk/sysvar-id", "sdk/time-utils", + "sdk/transaction", "sdk/transaction-context", "sdk/transaction-error", "send-transaction-service", @@ -573,6 +574,7 @@ solana-sysvar = { path = "sdk/sysvar", version = "=2.2.0" } solana-sysvar-id = { path = "sdk/sysvar-id", version = "=2.2.0" } solana-test-validator = { path = "test-validator", version = "=2.2.0" } solana-thin-client = { path = "thin-client", version = "=2.2.0" } +solana-transaction = { path = "sdk/transaction", version = "=2.2.0" } solana-transaction-error = { path = "sdk/transaction-error", version = "=2.2.0" } solana-tpu-client = { path = "tpu-client", version = "=2.2.0", default-features = false } solana-tpu-client-next = { path = "tpu-client-next", version = "=2.2.0" } diff --git a/gossip/src/protocol.rs b/gossip/src/protocol.rs index 5467e758e4f15b..d897f6d73b6209 100644 --- a/gossip/src/protocol.rs +++ b/gossip/src/protocol.rs @@ -46,7 +46,7 @@ pub(crate) const PULL_RESPONSE_MIN_SERIALIZED_SIZE: usize = 161; #[cfg_attr( feature = "frozen-abi", derive(AbiExample, AbiEnumVisitor), - frozen_abi(digest = "ESDND6D3FcRyA6UTUpDVDcS4AkESc5E6UtZWSbT7G8e8") + frozen_abi(digest = "CBR9G92mpd1WSXEmiH6dAKHziLjJky9aYWPw6S5WmJkG") )] #[derive(Serialize, Deserialize, Debug)] #[allow(clippy::large_enum_variant)] diff --git a/programs/sbf/Cargo.lock b/programs/sbf/Cargo.lock index b5e9d8fe056f67..aaeecdf807a0f1 100644 --- a/programs/sbf/Cargo.lock +++ b/programs/sbf/Cargo.lock @@ -7255,6 +7255,7 @@ dependencies = [ "solana-signature", "solana-signer", "solana-time-utils", + "solana-transaction", "solana-transaction-context", "solana-transaction-error", "thiserror 2.0.3", @@ -7785,6 +7786,32 @@ dependencies = [ "tokio-util 0.7.1", ] +[[package]] +name = "solana-transaction" +version = "2.2.0" +dependencies = [ + "bincode", + "serde", + "serde_derive", + "solana-bincode", + "solana-feature-set", + "solana-hash", + "solana-instruction", + "solana-keypair", + "solana-message", + "solana-precompiles", + "solana-pubkey", + "solana-reserved-account-keys", + "solana-sanitize", + "solana-sdk-ids", + "solana-short-vec", + "solana-signature", + "solana-signer", + "solana-system-interface", + "solana-transaction-error", + "wasm-bindgen", +] + [[package]] name = "solana-transaction-context" version = "2.2.0" diff --git a/rpc/src/rpc.rs b/rpc/src/rpc.rs index b479536de0eaf5..1d433df29f3850 100644 --- a/rpc/src/rpc.rs +++ b/rpc/src/rpc.rs @@ -8532,7 +8532,7 @@ pub mod tests { decode_and_deserialize::(tx58, TransactionBinaryEncoding::Base58) .unwrap_err(), Error::invalid_params(format!( - "base58 encoded solana_sdk::transaction::Transaction too large: {tx58_len} bytes (max: encoded/raw {MAX_BASE58_SIZE}/{PACKET_DATA_SIZE})", + "base58 encoded solana_transaction::Transaction too large: {tx58_len} bytes (max: encoded/raw {MAX_BASE58_SIZE}/{PACKET_DATA_SIZE})", ) )); @@ -8542,7 +8542,7 @@ pub mod tests { decode_and_deserialize::(tx64, TransactionBinaryEncoding::Base64) .unwrap_err(), Error::invalid_params(format!( - "base64 encoded solana_sdk::transaction::Transaction too large: {tx64_len} bytes (max: encoded/raw {MAX_BASE64_SIZE}/{PACKET_DATA_SIZE})", + "base64 encoded solana_transaction::Transaction too large: {tx64_len} bytes (max: encoded/raw {MAX_BASE64_SIZE}/{PACKET_DATA_SIZE})", ) )); @@ -8553,7 +8553,7 @@ pub mod tests { decode_and_deserialize::(tx58, TransactionBinaryEncoding::Base58) .unwrap_err(), Error::invalid_params(format!( - "decoded solana_sdk::transaction::Transaction too large: {too_big} bytes (max: {PACKET_DATA_SIZE} bytes)" + "decoded solana_transaction::Transaction too large: {too_big} bytes (max: {PACKET_DATA_SIZE} bytes)" )) ); @@ -8562,7 +8562,7 @@ pub mod tests { decode_and_deserialize::(tx64, TransactionBinaryEncoding::Base64) .unwrap_err(), Error::invalid_params(format!( - "decoded solana_sdk::transaction::Transaction too large: {too_big} bytes (max: {PACKET_DATA_SIZE} bytes)" + "decoded solana_transaction::Transaction too large: {too_big} bytes (max: {PACKET_DATA_SIZE} bytes)" )) ); @@ -8572,7 +8572,7 @@ pub mod tests { decode_and_deserialize::(tx64.clone(), TransactionBinaryEncoding::Base64) .unwrap_err(), Error::invalid_params( - "failed to deserialize solana_sdk::transaction::Transaction: invalid value: \ + "failed to deserialize solana_transaction::Transaction: invalid value: \ continue signal on byte-three, expected a terminal signal on or before byte-three" .to_string() ) @@ -8590,7 +8590,7 @@ pub mod tests { decode_and_deserialize::(tx58.clone(), TransactionBinaryEncoding::Base58) .unwrap_err(), Error::invalid_params( - "failed to deserialize solana_sdk::transaction::Transaction: invalid value: \ + "failed to deserialize solana_transaction::Transaction: invalid value: \ continue signal on byte-three, expected a terminal signal on or before byte-three" .to_string() ) diff --git a/sdk/Cargo.toml b/sdk/Cargo.toml index 91a828853f51fe..d645e470a37825 100644 --- a/sdk/Cargo.toml +++ b/sdk/Cargo.toml @@ -47,6 +47,7 @@ full = [ "dep:solana-seed-derivable", "dep:solana-seed-phrase", "dep:solana-signer", + "dep:solana-transaction", "dep:solana-transaction-error", ] borsh = [ @@ -60,6 +61,7 @@ dev-context-only-utils = [ "solana-account/dev-context-only-utils", "solana-compute-budget-interface/dev-context-only-utils", "solana-rent-debits/dev-context-only-utils", + "solana-transaction/dev-context-only-utils", "solana-transaction-context/dev-context-only-utils", ] frozen-abi = [ @@ -74,7 +76,8 @@ frozen-abi = [ "solana-reward-info/frozen-abi", "solana-short-vec/frozen-abi", "solana-signature/frozen-abi", - "solana-transaction-error/frozen-abi", + "solana-transaction/frozen-abi", + "solana-transaction-error/frozen-abi" ] # Enables the "vendored" feature of openssl inside of secp256r1-program openssl-vendored = ["solana-precompiles/openssl-vendored"] @@ -173,6 +176,12 @@ solana-signature = { workspace = true, features = [ ], optional = true } solana-signer = { workspace = true, optional = true } solana-time-utils = { workspace = true } +solana-transaction = { workspace = true, features = [ + "blake3", + "precompiles", + "serde", + "verify" +], optional = true } solana-transaction-context = { workspace = true, features = ["bincode"] } solana-transaction-error = { workspace = true, features = [ "serde", diff --git a/sdk/src/lib.rs b/sdk/src/lib.rs index b51f88e7c6ef56..ebcccb13eb8e4a 100644 --- a/sdk/src/lib.rs +++ b/sdk/src/lib.rs @@ -94,7 +94,6 @@ pub mod rpc_port; pub mod shred_version; pub mod signature; pub mod signer; -pub mod simple_vote_transaction_checker; pub mod system_transaction; pub mod transaction; pub mod transport; @@ -203,6 +202,12 @@ pub use solana_serde_varint as serde_varint; pub use solana_short_vec as short_vec; #[deprecated(since = "2.2.0", note = "Use `solana-time-utils` crate instead")] pub use solana_time_utils as timing; +#[cfg(feature = "full")] +#[deprecated( + since = "2.2.0", + note = "Use `solana_transaction::simple_vote_transaction_checker` instead" +)] +pub use solana_transaction::simple_vote_transaction_checker; #[deprecated( since = "2.2.0", note = "Use `solana-transaction-context` crate instead" diff --git a/sdk/src/transaction.rs b/sdk/src/transaction.rs new file mode 100644 index 00000000000000..7e4864a3d3f748 --- /dev/null +++ b/sdk/src/transaction.rs @@ -0,0 +1,21 @@ +#![cfg(feature = "full")] +#[deprecated(since = "2.2.0", note = "Use solana_transaction_error crate instead")] +pub use solana_transaction_error::{ + AddressLoaderError, SanitizeMessageError, TransactionError, TransactionResult as Result, + TransportError, TransportResult, +}; +#[deprecated(since = "2.2.0", note = "Use solana_transaction crate instead")] +pub use { + solana_program::message::{AddressLoader, SimpleAddressLoader}, + solana_transaction::{ + sanitized::{ + MessageHash, SanitizedTransaction, TransactionAccountLocks, MAX_TX_ACCOUNT_LOCKS, + }, + uses_durable_nonce, + versioned::{ + sanitized::SanitizedVersionedTransaction, Legacy, TransactionVersion, + VersionedTransaction, + }, + Transaction, TransactionVerificationMode, + }, +}; diff --git a/sdk/src/wasm/transaction.rs b/sdk/src/wasm/transaction.rs index 208a76bfa2d415..5c5da471a7878a 100644 --- a/sdk/src/wasm/transaction.rs +++ b/sdk/src/wasm/transaction.rs @@ -1,54 +1,3 @@ -//! `Transaction` Javascript interface -#![cfg(target_arch = "wasm32")] -#![allow(non_snake_case)] -use { - crate::{hash::Hash, message::Message, signer::keypair::Keypair, transaction::Transaction}, - solana_program::{ - pubkey::Pubkey, - wasm::{display_to_jsvalue, instructions::Instructions}, - }, - wasm_bindgen::prelude::*, -}; - -#[wasm_bindgen] -impl Transaction { - /// Create a new `Transaction` - #[wasm_bindgen(constructor)] - pub fn constructor(instructions: Instructions, payer: Option) -> Transaction { - let instructions: Vec<_> = instructions.into(); - Transaction::new_with_payer(&instructions, payer.as_ref()) - } - - /// Return a message containing all data that should be signed. - #[wasm_bindgen(js_name = message)] - pub fn js_message(&self) -> Message { - self.message.clone() - } - - /// Return the serialized message data to sign. - pub fn messageData(&self) -> Box<[u8]> { - self.message_data().into() - } - - /// Verify the transaction - #[wasm_bindgen(js_name = verify)] - pub fn js_verify(&self) -> Result<(), JsValue> { - self.verify().map_err(display_to_jsvalue) - } - - pub fn partialSign(&mut self, keypair: &Keypair, recent_blockhash: &Hash) { - self.partial_sign(&[keypair], *recent_blockhash); - } - - pub fn isSigned(&self) -> bool { - self.is_signed() - } - - pub fn toBytes(&self) -> Box<[u8]> { - bincode::serialize(self).unwrap().into() - } - - pub fn fromBytes(bytes: &[u8]) -> Result { - bincode::deserialize(bytes).map_err(display_to_jsvalue) - } -} +//! This module is empty but has not yet been removed because that would +//! technically be a breaking change. There was never anything to import +//! from here. diff --git a/sdk/transaction/Cargo.toml b/sdk/transaction/Cargo.toml new file mode 100644 index 00000000000000..b36009e92c5266 --- /dev/null +++ b/sdk/transaction/Cargo.toml @@ -0,0 +1,89 @@ +[package] +name = "solana-transaction" +description = "Solana transaction-types" +documentation = "https://docs.rs/solana-transaction" +version = { workspace = true } +authors = { workspace = true } +repository = { workspace = true } +homepage = { workspace = true } +license = { workspace = true } +edition = { workspace = true } + +[dependencies] +bincode = { workspace = true, optional = true } +serde = { workspace = true, optional = true } +serde_derive = { workspace = true, optional = true } +solana-bincode = { workspace = true, optional = true } +solana-feature-set = { workspace = true, optional = true } +solana-frozen-abi = { workspace = true, optional = true } +solana-frozen-abi-macro = { workspace = true, optional = true } +solana-hash = { workspace = true } +solana-instruction = { workspace = true } +solana-logger = { workspace = true, optional = true } +solana-message = { workspace = true } +solana-precompiles = { workspace = true, optional = true } +solana-pubkey = { workspace = true } +solana-reserved-account-keys = { workspace = true, optional = true } +solana-sanitize = { workspace = true } +solana-sdk-ids = { workspace = true } +solana-short-vec = { workspace = true, optional = true } +solana-signature = { workspace = true } +solana-signer = { workspace = true, optional = true } +solana-system-interface = { workspace = true, optional = true, features = ["bincode"] } +solana-transaction-error = { workspace = true } + +[target.'cfg(target_arch = "wasm32")'.dependencies] +solana-keypair = { workspace = true } +wasm-bindgen = { workspace = true } + +[dev-dependencies] +anyhow = { workspace = true } +bincode = { workspace = true } +borsh = { workspace = true } +solana-hash = { workspace = true } +solana-instruction = { workspace = true, features = ["borsh"] } +solana-keypair = { workspace = true } +solana-nonce = { workspace = true } +solana-packet = { workspace = true } +solana-presigner = { workspace = true } +solana-program = { workspace = true, default-features = false } +solana-pubkey = { workspace = true, features = ["rand"] } +solana-sdk = { path = ".." } +solana-sha256-hasher = { workspace = true } +solana-transaction = { path = ".", features = ["dev-context-only-utils"] } +static_assertions = { workspace = true } + +[features] +bincode = [ + "dep:bincode", + "dep:solana-bincode", + "dep:solana-signer", + "dep:solana-system-interface", + "serde", + "solana-message/bincode", +] +blake3 = [ + "dep:solana-reserved-account-keys", + "bincode", + "solana-message/blake3", +] +dev-context-only-utils = ["blake3", "precompiles", "serde", "verify"] +frozen-abi = [ + "dep:solana-frozen-abi", + "dep:solana-frozen-abi-macro", + "dep:solana-logger", +] +precompiles = ["dep:solana-feature-set", "dep:solana-precompiles"] +serde = [ + "dep:serde", + "dep:serde_derive", + "dep:solana-short-vec", + "solana-message/serde", + "solana-signature/serde", +] +verify = ["blake3", "solana-signature/verify"] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu", "wasm32-unknown-unknown"] +all-features = true +rustdoc-args = ["--cfg=docsrs"] diff --git a/sdk/src/transaction/mod.rs b/sdk/transaction/src/lib.rs similarity index 90% rename from sdk/src/transaction/mod.rs rename to sdk/transaction/src/lib.rs index 7e898bf2477e00..43a913843269d3 100644 --- a/sdk/src/transaction/mod.rs +++ b/sdk/transaction/src/lib.rs @@ -1,3 +1,5 @@ +#![cfg_attr(feature = "frozen-abi", feature(min_specialization))] +#![cfg_attr(docsrs, feature(doc_auto_cfg))] //! Atomically-committed sequences of instructions. //! //! While [`Instruction`]s are the basic unit of computation in Solana, they are @@ -20,8 +22,8 @@ //! signers including remote wallets, such as Ledger devices, as represented by //! the [`RemoteKeypair`] type in the [`solana-remote-wallet`] crate. //! -//! [`Signer`]: crate::signer::Signer -//! [`Keypair`]: crate::signer::keypair::Keypair +//! [`Signer`]: https://docs.rs/solana-signer/latest/solana_signer/trait.Signer.html +//! [`Keypair`]: https://docs.rs/solana-keypair/latest/solana_keypair/struct.Keypair.html //! [`solana-remote-wallet`]: https://docs.rs/solana-remote-wallet/latest/ //! [`RemoteKeypair`]: https://docs.rs/solana-remote-wallet/latest/solana_remote_wallet/remote_keypair/struct.RemoteKeypair.html //! @@ -56,14 +58,13 @@ //! # use solana_sdk::example_mocks::solana_rpc_client; //! use anyhow::Result; //! use borsh::{BorshSerialize, BorshDeserialize}; +//! use solana_instruction::Instruction; +//! use solana_keypair::Keypair; +//! use solana_message::Message; +//! use solana_pubkey::Pubkey; //! use solana_rpc_client::rpc_client::RpcClient; -//! use solana_sdk::{ -//! instruction::Instruction, -//! message::Message, -//! pubkey::Pubkey, -//! signature::{Keypair, Signer}, -//! transaction::Transaction, -//! }; +//! use solana_signer::Signer; +//! use solana_transaction::Transaction; //! //! // A custom program instruction. This would typically be defined in //! // another crate so it can be shared between the on-chain program and @@ -109,43 +110,36 @@ //! # Ok::<(), anyhow::Error>(()) //! ``` -#![cfg(feature = "full")] - #[cfg(target_arch = "wasm32")] -use crate::wasm_bindgen; +use wasm_bindgen::prelude::wasm_bindgen; +#[cfg(feature = "serde")] use { - crate::{ - hash::Hash, - instruction::{CompiledInstruction, Instruction}, - message::Message, - nonce::NONCED_TX_MARKER_IX_INDEX, - precompiles::verify_if_precompile, - program_utils::limited_deserialize, - pubkey::Pubkey, - signature::{Signature, SignerError}, - signers::Signers, - }, - serde::Serialize, - solana_feature_set as feature_set, - solana_program::{system_instruction::SystemInstruction, system_program}, - solana_sanitize::{Sanitize, SanitizeError}, + serde_derive::{Deserialize, Serialize}, solana_short_vec as short_vec, +}; +#[cfg(feature = "bincode")] +use { + solana_bincode::limited_deserialize, + solana_hash::Hash, + solana_message::compiled_instruction::CompiledInstruction, + solana_sdk_ids::system_program, + solana_signer::{signers::Signers, SignerError}, + solana_system_interface::instruction::SystemInstruction, +}; +use { + solana_instruction::Instruction, + solana_message::Message, + solana_pubkey::Pubkey, + solana_sanitize::{Sanitize, SanitizeError}, + solana_signature::Signature, + solana_transaction_error::{TransactionError, TransactionResult as Result}, std::result, }; -mod sanitized; -mod versioned; - -#[deprecated( - since = "2.2.0", - note = "Use `solana_transaction_error::TransactionResult` instead" -)] -pub use solana_transaction_error::TransactionResult as Result; -#[deprecated(since = "2.1.0", note = "Use solana_transaction_error crate instead")] -pub use solana_transaction_error::{ - AddressLoaderError, SanitizeMessageError, TransactionError, TransportError, TransportResult, -}; -pub use {sanitized::*, versioned::*}; +pub mod sanitized; +pub mod simple_vote_transaction_checker; +pub mod versioned; +mod wasm; #[derive(PartialEq, Eq, Clone, Copy, Debug)] pub enum TransactionVerificationMode { @@ -154,13 +148,27 @@ pub enum TransactionVerificationMode { FullVerification, } +// inlined to avoid solana-nonce dep +#[cfg(test)] +static_assertions::const_assert_eq!( + NONCED_TX_MARKER_IX_INDEX, + solana_nonce::NONCED_TX_MARKER_IX_INDEX +); +#[cfg(feature = "bincode")] +const NONCED_TX_MARKER_IX_INDEX: u8 = 0; +// inlined to avoid solana-packet dep +#[cfg(test)] +static_assertions::const_assert_eq!(PACKET_DATA_SIZE, solana_packet::PACKET_DATA_SIZE); +#[cfg(feature = "bincode")] +const PACKET_DATA_SIZE: usize = 1280 - 40 - 8; + /// An atomically-committed sequence of instructions. /// /// While [`Instruction`]s are the basic unit of computation in Solana, /// they are submitted by clients in [`Transaction`]s containing one or /// more instructions, and signed by one or more [`Signer`]s. /// -/// [`Signer`]: crate::signer::Signer +/// [`Signer`]: https://docs.rs/solana-signer/latest/solana_signer/trait.Signer.html /// /// See the [module documentation] for more details about transactions. /// @@ -177,21 +185,22 @@ pub enum TransactionVerificationMode { #[cfg(not(target_arch = "wasm32"))] #[cfg_attr( feature = "frozen-abi", - derive(AbiExample), - frozen_abi(digest = "76BDTr3Xm3VP7h4eSiw6pZHKc5yYewDufyia3Yedh6GG") + derive(solana_frozen_abi_macro::AbiExample), + solana_frozen_abi_macro::frozen_abi(digest = "76BDTr3Xm3VP7h4eSiw6pZHKc5yYewDufyia3Yedh6GG") )] -#[derive(Debug, PartialEq, Default, Eq, Clone, Serialize, Deserialize)] +#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] +#[derive(Debug, PartialEq, Default, Eq, Clone)] pub struct Transaction { /// A set of signatures of a serialized [`Message`], signed by the first /// keys of the `Message`'s [`account_keys`], where the number of signatures /// is equal to [`num_required_signatures`] of the `Message`'s /// [`MessageHeader`]. /// - /// [`account_keys`]: Message::account_keys - /// [`MessageHeader`]: crate::message::MessageHeader - /// [`num_required_signatures`]: crate::message::MessageHeader::num_required_signatures + /// [`account_keys`]: https://docs.rs/solana-message/latest/solana_message/legacy/struct.Message.html#structfield.account_keys + /// [`MessageHeader`]: https://docs.rs/solana-message/latest/solana_message/struct.MessageHeader.html + /// [`num_required_signatures`]: https://docs.rs/solana-message/latest/solana_message/struct.MessageHeader.html#structfield.num_required_signatures // NOTE: Serialization-related changes must be paired with the direct read at sigverify. - #[serde(with = "short_vec")] + #[cfg_attr(feature = "serde", serde(with = "short_vec"))] pub signatures: Vec, /// The message to sign. @@ -208,10 +217,11 @@ pub struct Transaction { derive(AbiExample), frozen_abi(digest = "H7xQFcd1MtMv9QKZWGatBAXwhg28tpeX59P3s8ZZLAY4") )] -#[derive(Debug, PartialEq, Default, Eq, Clone, Serialize, Deserialize)] +#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] +#[derive(Debug, PartialEq, Default, Eq, Clone)] pub struct Transaction { #[wasm_bindgen(skip)] - #[serde(with = "short_vec")] + #[cfg_attr(feature = "serde", serde(with = "short_vec"))] pub signatures: Vec, #[wasm_bindgen(skip)] @@ -219,7 +229,7 @@ pub struct Transaction { } impl Sanitize for Transaction { - fn sanitize(&self) -> std::result::Result<(), SanitizeError> { + fn sanitize(&self) -> result::Result<(), SanitizeError> { if self.message.header.num_required_signatures as usize > self.signatures.len() { return Err(SanitizeError::IndexOutOfBounds); } @@ -244,14 +254,13 @@ impl Transaction { /// # use solana_sdk::example_mocks::solana_rpc_client; /// use anyhow::Result; /// use borsh::{BorshSerialize, BorshDeserialize}; + /// use solana_instruction::Instruction; + /// use solana_keypair::Keypair; + /// use solana_message::Message; + /// use solana_pubkey::Pubkey; /// use solana_rpc_client::rpc_client::RpcClient; - /// use solana_sdk::{ - /// instruction::Instruction, - /// message::Message, - /// pubkey::Pubkey, - /// signature::{Keypair, Signer}, - /// transaction::Transaction, - /// }; + /// use solana_signer::Signer; + /// use solana_transaction::Transaction; /// /// // A custom program instruction. This would typically be defined in /// // another crate so it can be shared between the on-chain program and @@ -323,14 +332,13 @@ impl Transaction { /// # use solana_sdk::example_mocks::solana_rpc_client; /// use anyhow::Result; /// use borsh::{BorshSerialize, BorshDeserialize}; + /// use solana_instruction::Instruction; + /// use solana_keypair::Keypair; + /// use solana_message::Message; + /// use solana_pubkey::Pubkey; /// use solana_rpc_client::rpc_client::RpcClient; - /// use solana_sdk::{ - /// instruction::Instruction, - /// message::Message, - /// pubkey::Pubkey, - /// signature::{Keypair, Signer}, - /// transaction::Transaction, - /// }; + /// use solana_signer::Signer; + /// use solana_transaction::Transaction; /// /// // A custom program instruction. This would typically be defined in /// // another crate so it can be shared between the on-chain program and @@ -375,6 +383,7 @@ impl Transaction { /// # /// # Ok::<(), anyhow::Error>(()) /// ``` + #[cfg(feature = "bincode")] pub fn new( from_keypairs: &T, message: Message, @@ -402,14 +411,13 @@ impl Transaction { /// # use solana_sdk::example_mocks::solana_rpc_client; /// use anyhow::Result; /// use borsh::{BorshSerialize, BorshDeserialize}; + /// use solana_instruction::Instruction; + /// use solana_keypair::Keypair; + /// use solana_message::Message; + /// use solana_pubkey::Pubkey; /// use solana_rpc_client::rpc_client::RpcClient; - /// use solana_sdk::{ - /// instruction::Instruction, - /// message::Message, - /// pubkey::Pubkey, - /// signature::{Keypair, Signer}, - /// transaction::Transaction, - /// }; + /// use solana_signer::Signer; + /// use solana_transaction::Transaction; /// /// // A custom program instruction. This would typically be defined in /// // another crate so it can be shared between the on-chain program and @@ -478,14 +486,13 @@ impl Transaction { /// # use solana_sdk::example_mocks::solana_rpc_client; /// use anyhow::Result; /// use borsh::{BorshSerialize, BorshDeserialize}; + /// use solana_instruction::Instruction; + /// use solana_keypair::Keypair; + /// use solana_message::Message; + /// use solana_pubkey::Pubkey; /// use solana_rpc_client::rpc_client::RpcClient; - /// use solana_sdk::{ - /// instruction::Instruction, - /// message::Message, - /// pubkey::Pubkey, - /// signature::{Keypair, Signer}, - /// transaction::Transaction, - /// }; + /// use solana_signer::Signer; + /// use solana_transaction::Transaction; /// /// // A custom program instruction. This would typically be defined in /// // another crate so it can be shared between the on-chain program and @@ -530,6 +537,7 @@ impl Transaction { /// # /// # Ok::<(), anyhow::Error>(()) /// ``` + #[cfg(feature = "bincode")] pub fn new_signed_with_payer( instructions: &[Instruction], payer: Option<&Pubkey>, @@ -555,6 +563,7 @@ impl Transaction { /// /// Panics when signing fails. See [`Transaction::try_sign`] and for a full /// description of failure conditions. + #[cfg(feature = "bincode")] pub fn new_with_compiled_instructions( from_keypairs: &T, keys: &[Pubkey], @@ -652,6 +661,7 @@ impl Transaction { &self.message } + #[cfg(feature = "bincode")] /// Return the serialized message data to sign. pub fn message_data(&self) -> Vec { self.message().serialize() @@ -686,14 +696,13 @@ impl Transaction { /// # use solana_sdk::example_mocks::solana_rpc_client; /// use anyhow::Result; /// use borsh::{BorshSerialize, BorshDeserialize}; + /// use solana_instruction::Instruction; + /// use solana_keypair::Keypair; + /// use solana_message::Message; + /// use solana_pubkey::Pubkey; /// use solana_rpc_client::rpc_client::RpcClient; - /// use solana_sdk::{ - /// instruction::Instruction, - /// message::Message, - /// pubkey::Pubkey, - /// signature::{Keypair, Signer}, - /// transaction::Transaction, - /// }; + /// use solana_signer::Signer; + /// use solana_transaction::Transaction; /// /// // A custom program instruction. This would typically be defined in /// // another crate so it can be shared between the on-chain program and @@ -734,6 +743,7 @@ impl Transaction { /// # /// # Ok::<(), anyhow::Error>(()) /// ``` + #[cfg(feature = "bincode")] pub fn sign(&mut self, keypairs: &T, recent_blockhash: Hash) { if let Err(e) = self.try_sign(keypairs, recent_blockhash) { panic!("Transaction::sign failed with error {e:?}"); @@ -760,6 +770,7 @@ impl Transaction { /// handle the error. See the documentation for /// [`Transaction::try_partial_sign`] for a full description of failure /// conditions. + #[cfg(feature = "bincode")] pub fn partial_sign(&mut self, keypairs: &T, recent_blockhash: Hash) { if let Err(e) = self.try_partial_sign(keypairs, recent_blockhash) { panic!("Transaction::partial_sign failed with error {e:?}"); @@ -779,6 +790,7 @@ impl Transaction { /// /// Panics if signing fails. Use [`Transaction::try_partial_sign_unchecked`] /// to handle the error. + #[cfg(feature = "bincode")] pub fn partial_sign_unchecked( &mut self, keypairs: &T, @@ -824,14 +836,13 @@ impl Transaction { /// # use solana_sdk::example_mocks::solana_rpc_client; /// use anyhow::Result; /// use borsh::{BorshSerialize, BorshDeserialize}; + /// use solana_instruction::Instruction; + /// use solana_keypair::Keypair; + /// use solana_message::Message; + /// use solana_pubkey::Pubkey; /// use solana_rpc_client::rpc_client::RpcClient; - /// use solana_sdk::{ - /// instruction::Instruction, - /// message::Message, - /// pubkey::Pubkey, - /// signature::{Keypair, Signer}, - /// transaction::Transaction, - /// }; + /// use solana_signer::Signer; + /// use solana_transaction::Transaction; /// /// // A custom program instruction. This would typically be defined in /// // another crate so it can be shared between the on-chain program and @@ -872,6 +883,7 @@ impl Transaction { /// # /// # Ok::<(), anyhow::Error>(()) /// ``` + #[cfg(feature = "bincode")] pub fn try_sign( &mut self, keypairs: &T, @@ -928,13 +940,14 @@ impl Transaction { /// See the documentation for the [`solana-remote-wallet`] crate for details /// on the operation of [`RemoteKeypair`] signers. /// - /// [`num_required_signatures`]: crate::message::MessageHeader::num_required_signatures - /// [`account_keys`]: Message::account_keys - /// [`Presigner`]: crate::signer::presigner::Presigner - /// [`PresignerError`]: crate::signer::presigner::PresignerError - /// [`PresignerError::VerificationFailure`]: crate::signer::presigner::PresignerError::VerificationFailure + /// [`num_required_signatures`]: https://docs.rs/solana-message/latest/solana_message/struct.MessageHeader.html#structfield.num_required_signatures + /// [`account_keys`]: https://docs.rs/solana-message/latest/solana_message/legacy/struct.Message.html#structfield.account_keys + /// [`Presigner`]: https://docs.rs/solana-presigner/latest/solana_presigner/struct.Presigner.html + /// [`PresignerError`]: https://docs.rs/solana-signer/latest/solana_signer/enum.PresignerError.html + /// [`PresignerError::VerificationFailure`]: https://docs.rs/solana-signer/latest/solana_signer/enum.PresignerError.html#variant.WrongSize /// [`solana-remote-wallet`]: https://docs.rs/solana-remote-wallet/latest/ /// [`RemoteKeypair`]: https://docs.rs/solana-remote-wallet/latest/solana_remote_wallet/remote_keypair/struct.RemoteKeypair.html + #[cfg(feature = "bincode")] pub fn try_partial_sign( &mut self, keypairs: &T, @@ -961,6 +974,7 @@ impl Transaction { /// # Errors /// /// Returns an error if signing fails. + #[cfg(feature = "bincode")] pub fn try_partial_sign_unchecked( &mut self, keypairs: &T, @@ -987,6 +1001,7 @@ impl Transaction { Signature::default() } + #[cfg(feature = "verify")] /// Verifies that all signers have signed the message. /// /// # Errors @@ -1005,6 +1020,7 @@ impl Transaction { } } + #[cfg(feature = "verify")] /// Verify the transaction and hash its message. /// /// # Errors @@ -1023,6 +1039,7 @@ impl Transaction { } } + #[cfg(feature = "verify")] /// Verifies that all signers have signed the message. /// /// Returns a vector with the length of required signatures, where each @@ -1031,6 +1048,7 @@ impl Transaction { self._verify_with_results(&self.message_data()) } + #[cfg(feature = "verify")] pub(crate) fn _verify_with_results(&self, message_bytes: &[u8]) -> Vec { self.signatures .iter() @@ -1039,8 +1057,9 @@ impl Transaction { .collect() } + #[cfg(feature = "precompiles")] /// Verify the precompiled programs in this transaction. - pub fn verify_precompiles(&self, feature_set: &feature_set::FeatureSet) -> Result<()> { + pub fn verify_precompiles(&self, feature_set: &solana_feature_set::FeatureSet) -> Result<()> { for instruction in &self.message().instructions { // The Transaction may not be sanitized at this point if instruction.program_id_index as usize >= self.message().account_keys.len() { @@ -1048,7 +1067,7 @@ impl Transaction { } let program_id = &self.message().account_keys[instruction.program_id_index as usize]; - verify_if_precompile( + solana_precompiles::verify_if_precompile( program_id, instruction, &self.message().instructions, @@ -1075,6 +1094,7 @@ impl Transaction { .collect()) } + #[cfg(feature = "verify")] /// Replace all the signatures and pubkeys. pub fn replace_signatures(&mut self, signers: &[(Pubkey, Signature)]) -> Result<()> { let num_required_signatures = self.message.header.num_required_signatures as usize; @@ -1111,6 +1131,7 @@ impl Transaction { } } +#[cfg(feature = "bincode")] /// Returns true if transaction begins with an advance nonce instruction. pub fn uses_durable_nonce(tx: &Transaction) -> Option<&CompiledInstruction> { let message = tx.message(); @@ -1125,7 +1146,7 @@ pub fn uses_durable_nonce(tx: &Transaction) -> Option<&CompiledInstruction> { ) // Is a nonce advance instruction && matches!( - limited_deserialize(&instruction.data), + limited_deserialize(&instruction.data, PACKET_DATA_SIZE as u64), Ok(SystemInstruction::AdvanceNonceAccount) ) }) @@ -1137,13 +1158,13 @@ mod tests { use { super::*, - crate::{ - hash::hash, - instruction::AccountMeta, - signature::{Keypair, Presigner, Signer}, - system_instruction, - }, bincode::{deserialize, serialize, serialized_size}, + solana_instruction::AccountMeta, + solana_keypair::Keypair, + solana_presigner::Presigner, + solana_sha256_hasher::hash, + solana_signer::Signer, + solana_system_interface::instruction as system_instruction, std::mem::size_of, }; @@ -1156,10 +1177,10 @@ mod tests { #[test] fn test_refs() { let key = Keypair::new(); - let key1 = solana_sdk::pubkey::new_rand(); - let key2 = solana_sdk::pubkey::new_rand(); - let prog1 = solana_sdk::pubkey::new_rand(); - let prog2 = solana_sdk::pubkey::new_rand(); + let key1 = solana_pubkey::new_rand(); + let key2 = solana_pubkey::new_rand(); + let prog1 = solana_pubkey::new_rand(); + let prog2 = solana_pubkey::new_rand(); let instructions = vec![ CompiledInstruction::new(3, &(), vec![0, 1]), CompiledInstruction::new(4, &(), vec![0, 2]), @@ -1227,7 +1248,7 @@ mod tests { fn test_sanitize_txs() { let key = Keypair::new(); let id0 = Pubkey::default(); - let program_id = solana_sdk::pubkey::new_rand(); + let program_id = solana_pubkey::new_rand(); let ix = Instruction::new_with_bincode( program_id, &0, @@ -1326,7 +1347,7 @@ mod tests { fn test_transaction_minimum_serialized_size() { let alice_keypair = Keypair::new(); let alice_pubkey = alice_keypair.pubkey(); - let bob_pubkey = solana_sdk::pubkey::new_rand(); + let bob_pubkey = solana_pubkey::new_rand(); let ix = system_instruction::transfer(&alice_pubkey, &bob_pubkey, 42); let expected_data_size = size_of::() + size_of::(); @@ -1404,7 +1425,7 @@ mod tests { #[should_panic] fn test_partial_sign_mismatched_key() { let keypair = Keypair::new(); - let fee_payer = solana_sdk::pubkey::new_rand(); + let fee_payer = solana_pubkey::new_rand(); let ix = Instruction::new_with_bincode( Pubkey::default(), &0, @@ -1487,7 +1508,7 @@ mod tests { let program_id = Pubkey::default(); let keypair0 = Keypair::new(); let id0 = keypair0.pubkey(); - let id1 = solana_sdk::pubkey::new_rand(); + let id1 = solana_pubkey::new_rand(); let ix = Instruction::new_with_bincode( program_id, &0, @@ -1538,7 +1559,7 @@ mod tests { assert_eq!(tx.signatures[1], presigner_sig); // Wrong key should error, not panic - let another_pubkey = solana_sdk::pubkey::new_rand(); + let another_pubkey = solana_pubkey::new_rand(); let ix = Instruction::new_with_bincode( program_id, &0, diff --git a/sdk/src/transaction/sanitized.rs b/sdk/transaction/src/sanitized.rs similarity index 91% rename from sdk/src/transaction/sanitized.rs rename to sdk/transaction/src/sanitized.rs index af21811d2bbd2b..2d0f3639c2c222 100644 --- a/sdk/src/transaction/sanitized.rs +++ b/sdk/transaction/src/sanitized.rs @@ -1,28 +1,22 @@ -#![cfg(feature = "full")] - -pub use crate::message::{AddressLoader, SimpleAddressLoader}; use { - super::SanitizedVersionedTransaction, - crate::{ - hash::Hash, - message::{ - legacy, - v0::{self, LoadedAddresses}, - LegacyMessage, SanitizedMessage, VersionedMessage, - }, - precompiles::verify_if_precompile, - pubkey::Pubkey, - reserved_account_keys::ReservedAccountKeys, - signature::Signature, - simple_vote_transaction_checker::is_simple_vote_transaction, - transaction::{Result, Transaction, VersionedTransaction}, + crate::versioned::{sanitized::SanitizedVersionedTransaction, VersionedTransaction}, + solana_hash::Hash, + solana_message::{ + legacy, + v0::{self, LoadedAddresses}, + AddressLoader, LegacyMessage, SanitizedMessage, SanitizedVersionedMessage, + VersionedMessage, }, - solana_feature_set as feature_set, - solana_program::{instruction::InstructionError, message::SanitizedVersionedMessage}, - solana_sanitize::Sanitize, - solana_transaction_error::TransactionError, + solana_pubkey::Pubkey, + solana_signature::Signature, + solana_transaction_error::{TransactionError, TransactionResult as Result}, std::collections::HashSet, }; +#[cfg(feature = "blake3")] +use { + crate::Transaction, solana_reserved_account_keys::ReservedAccountKeys, + solana_sanitize::Sanitize, +}; /// Maximum number of accounts that a transaction may lock. /// 128 was chosen because it is the minimum number of accounts @@ -96,6 +90,7 @@ impl SanitizedTransaction { }) } + #[cfg(feature = "blake3")] /// Create a sanitized transaction from an un-sanitized versioned /// transaction. If the input transaction uses address tables, attempt to /// lookup the address for each table index. @@ -107,8 +102,11 @@ impl SanitizedTransaction { reserved_account_keys: &HashSet, ) -> Result { let sanitized_versioned_tx = SanitizedVersionedTransaction::try_from(tx)?; - let is_simple_vote_tx = is_simple_vote_tx - .unwrap_or_else(|| is_simple_vote_transaction(&sanitized_versioned_tx)); + let is_simple_vote_tx = is_simple_vote_tx.unwrap_or_else(|| { + crate::simple_vote_transaction_checker::is_simple_vote_transaction( + &sanitized_versioned_tx, + ) + }); let message_hash = match message_hash.into() { MessageHash::Compute => sanitized_versioned_tx.message.message.hash(), MessageHash::Precomputed(hash) => hash, @@ -123,6 +121,7 @@ impl SanitizedTransaction { } /// Create a sanitized transaction from a legacy transaction + #[cfg(feature = "blake3")] pub fn try_from_legacy_transaction( tx: Transaction, reserved_account_keys: &HashSet, @@ -141,6 +140,7 @@ impl SanitizedTransaction { } /// Create a sanitized transaction from a legacy transaction. Used for tests only. + #[cfg(feature = "blake3")] pub fn from_transaction_for_tests(tx: Transaction) -> Self { Self::try_from_legacy_transaction(tx, &ReservedAccountKeys::empty_key_set()).unwrap() } @@ -255,10 +255,12 @@ impl SanitizedTransaction { } /// If the transaction uses a durable nonce, return the pubkey of the nonce account + #[cfg(feature = "bincode")] pub fn get_durable_nonce(&self) -> Option<&Pubkey> { self.message.get_durable_nonce() } + #[cfg(feature = "verify")] /// Return the serialized message data to sign. fn message_data(&self) -> Vec { match &self.message { @@ -267,6 +269,7 @@ impl SanitizedTransaction { } } + #[cfg(feature = "verify")] /// Verify the transaction signatures pub fn verify(&self) -> Result<()> { let message_bytes = self.message_data(); @@ -283,12 +286,13 @@ impl SanitizedTransaction { } } + #[cfg(feature = "precompiles")] /// Verify the precompiled programs in this transaction - pub fn verify_precompiles(&self, feature_set: &feature_set::FeatureSet) -> Result<()> { + pub fn verify_precompiles(&self, feature_set: &solana_feature_set::FeatureSet) -> Result<()> { for (index, (program_id, instruction)) in self.message.program_instructions_iter().enumerate() { - verify_if_precompile( + solana_precompiles::verify_if_precompile( program_id, instruction, self.message().instructions(), @@ -297,7 +301,7 @@ impl SanitizedTransaction { .map_err(|err| { TransactionError::InstructionError( index as u8, - InstructionError::Custom(err as u32), + solana_instruction::error::InstructionError::Custom(err as u32), ) })?; } @@ -338,14 +342,11 @@ impl SanitizedTransaction { mod tests { use { super::*, - crate::{ - reserved_account_keys::ReservedAccountKeys, - signer::{keypair::Keypair, Signer}, - }, - solana_program::{ - message::MessageHeader, - vote::{self, state::Vote}, - }, + solana_keypair::Keypair, + solana_message::{MessageHeader, SimpleAddressLoader}, + solana_program::vote::{self, state::Vote}, + solana_reserved_account_keys::ReservedAccountKeys, + solana_signer::Signer, }; #[test] diff --git a/sdk/src/simple_vote_transaction_checker.rs b/sdk/transaction/src/simple_vote_transaction_checker.rs similarity index 84% rename from sdk/src/simple_vote_transaction_checker.rs rename to sdk/transaction/src/simple_vote_transaction_checker.rs index cc8ccc02f2484f..bece183670d274 100644 --- a/sdk/src/simple_vote_transaction_checker.rs +++ b/sdk/transaction/src/simple_vote_transaction_checker.rs @@ -1,9 +1,6 @@ -#![cfg(feature = "full")] - use { - crate::{message::VersionedMessage, transaction::SanitizedVersionedTransaction}, - solana_pubkey::Pubkey, - solana_signature::Signature, + crate::versioned::sanitized::SanitizedVersionedTransaction, solana_message::VersionedMessage, + solana_pubkey::Pubkey, solana_signature::Signature, }; /// Simple vote transaction meets these conditions: @@ -46,6 +43,6 @@ pub fn is_simple_vote_transaction_impl<'a>( && instruction_programs .next() .xor(instruction_programs.next()) - .map(|program_id| program_id == &solana_sdk::vote::program::ID) + .map(|program_id| program_id == &solana_sdk_ids::vote::ID) .unwrap_or(false) } diff --git a/sdk/src/transaction/versioned/mod.rs b/sdk/transaction/src/versioned/mod.rs similarity index 86% rename from sdk/src/transaction/versioned/mod.rs rename to sdk/transaction/src/versioned/mod.rs index c9a30ab5680486..21f8be087b3608 100644 --- a/sdk/src/transaction/versioned/mod.rs +++ b/sdk/transaction/src/versioned/mod.rs @@ -1,42 +1,41 @@ //! Defines a transaction which supports multiple versions of messages. -#![cfg(feature = "full")] - use { - crate::{ - hash::Hash, - message::VersionedMessage, - signature::Signature, - signer::SignerError, - signers::Signers, - transaction::{Result, Transaction}, - }, - serde::Serialize, - solana_sanitize::SanitizeError, + crate::Transaction, solana_message::VersionedMessage, solana_sanitize::SanitizeError, + solana_signature::Signature, std::cmp::Ordering, +}; +#[cfg(feature = "serde")] +use { + serde_derive::{Deserialize, Serialize}, solana_short_vec as short_vec, - solana_transaction_error::TransactionError, - std::cmp::Ordering, }; - -mod sanitized; - -pub use sanitized::*; +#[cfg(feature = "bincode")] use { - crate::program_utils::limited_deserialize, - solana_program::{ - nonce::NONCED_TX_MARKER_IX_INDEX, system_instruction::SystemInstruction, system_program, - }, + solana_bincode::limited_deserialize, + solana_sdk_ids::system_program, + solana_signer::{signers::Signers, SignerError}, + solana_system_interface::instruction::SystemInstruction, }; +pub mod sanitized; + /// Type that serializes to the string "legacy" -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] +#[cfg_attr( + feature = "serde", + derive(Deserialize, Serialize), + serde(rename_all = "camelCase") +)] +#[derive(Clone, Debug, PartialEq, Eq)] pub enum Legacy { Legacy, } -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase", untagged)] +#[cfg_attr( + feature = "serde", + derive(Deserialize, Serialize), + serde(rename_all = "camelCase", untagged) +)] +#[derive(Clone, Debug, PartialEq, Eq)] pub enum TransactionVersion { Legacy(Legacy), Number(u8), @@ -48,11 +47,12 @@ impl TransactionVersion { // NOTE: Serialization-related changes must be paired with the direct read at sigverify. /// An atomic transaction -#[cfg_attr(feature = "frozen-abi", derive(AbiExample))] -#[derive(Debug, PartialEq, Default, Eq, Clone, Serialize, Deserialize)] +#[cfg_attr(feature = "frozen-abi", derive(solana_frozen_abi_macro::AbiExample))] +#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] +#[derive(Debug, PartialEq, Default, Eq, Clone)] pub struct VersionedTransaction { /// List of signatures - #[serde(with = "short_vec")] + #[cfg_attr(feature = "serde", serde(with = "short_vec"))] pub signatures: Vec, /// Message to sign. pub message: VersionedMessage, @@ -70,6 +70,7 @@ impl From for VersionedTransaction { impl VersionedTransaction { /// Signs a versioned message and if successful, returns a signed /// transaction. + #[cfg(feature = "bincode")] pub fn try_new( message: VersionedMessage, keypairs: &T, @@ -170,26 +171,31 @@ impl VersionedTransaction { } } + #[cfg(feature = "verify")] /// Verify the transaction and hash its message - pub fn verify_and_hash_message(&self) -> Result { + pub fn verify_and_hash_message( + &self, + ) -> solana_transaction_error::TransactionResult { let message_bytes = self.message.serialize(); if !self ._verify_with_results(&message_bytes) .iter() .all(|verify_result| *verify_result) { - Err(TransactionError::SignatureFailure) + Err(solana_transaction_error::TransactionError::SignatureFailure) } else { Ok(VersionedMessage::hash_raw_message(&message_bytes)) } } + #[cfg(feature = "verify")] /// Verify the transaction and return a list of verification results pub fn verify_with_results(&self) -> Vec { let message_bytes = self.message.serialize(); self._verify_with_results(&message_bytes) } + #[cfg(feature = "verify")] fn _verify_with_results(&self, message_bytes: &[u8]) -> Vec { self.signatures .iter() @@ -198,12 +204,13 @@ impl VersionedTransaction { .collect() } + #[cfg(feature = "bincode")] /// Returns true if transaction begins with an advance nonce instruction. pub fn uses_durable_nonce(&self) -> bool { let message = &self.message; message .instructions() - .get(NONCED_TX_MARKER_IX_INDEX as usize) + .get(crate::NONCED_TX_MARKER_IX_INDEX as usize) .filter(|instruction| { // Is system program matches!( @@ -212,7 +219,7 @@ impl VersionedTransaction { ) // Is a nonce advance instruction && matches!( - limited_deserialize(&instruction.data), + limited_deserialize(&instruction.data, crate::PACKET_DATA_SIZE as u64,), Ok(SystemInstruction::AdvanceNonceAccount) ) }) @@ -224,15 +231,13 @@ impl VersionedTransaction { mod tests { use { super::*, - crate::{ - message::Message as LegacyMessage, - signer::{keypair::Keypair, Signer}, - system_instruction, - }, - solana_program::{ - instruction::{AccountMeta, Instruction}, - pubkey::Pubkey, - }, + solana_hash::Hash, + solana_instruction::{AccountMeta, Instruction}, + solana_keypair::Keypair, + solana_message::Message as LegacyMessage, + solana_pubkey::Pubkey, + solana_signer::Signer, + solana_system_interface::instruction as system_instruction, }; #[test] diff --git a/sdk/src/transaction/versioned/sanitized.rs b/sdk/transaction/src/versioned/sanitized.rs similarity index 88% rename from sdk/src/transaction/versioned/sanitized.rs rename to sdk/transaction/src/versioned/sanitized.rs index 6243292fce3526..aec6033c377ffa 100644 --- a/sdk/src/transaction/versioned/sanitized.rs +++ b/sdk/transaction/src/versioned/sanitized.rs @@ -1,6 +1,6 @@ use { - super::VersionedTransaction, crate::signature::Signature, - solana_program::message::SanitizedVersionedMessage, solana_sanitize::SanitizeError, + crate::versioned::VersionedTransaction, solana_message::SanitizedVersionedMessage, + solana_sanitize::SanitizeError, solana_signature::Signature, }; /// Wraps a sanitized `VersionedTransaction` to provide a safe API @@ -42,11 +42,9 @@ impl SanitizedVersionedTransaction { mod tests { use { super::*, - solana_program::{ - hash::Hash, - message::{v0, VersionedMessage}, - pubkey::Pubkey, - }, + solana_hash::Hash, + solana_message::{v0, VersionedMessage}, + solana_pubkey::Pubkey, }; #[test] diff --git a/sdk/transaction/src/wasm.rs b/sdk/transaction/src/wasm.rs new file mode 100644 index 00000000000000..cdfc5792b3e569 --- /dev/null +++ b/sdk/transaction/src/wasm.rs @@ -0,0 +1,55 @@ +//! `Transaction` Javascript interface +#![cfg(target_arch = "wasm32")] +#![allow(non_snake_case)] +use { + crate::Transaction, solana_hash::Hash, solana_instruction::wasm::Instructions, + solana_keypair::Keypair, solana_message::Message, solana_pubkey::Pubkey, + wasm_bindgen::prelude::*, +}; + +#[wasm_bindgen] +impl Transaction { + /// Create a new `Transaction` + #[wasm_bindgen(constructor)] + pub fn constructor(instructions: Instructions, payer: Option) -> Transaction { + let instructions: Vec<_> = instructions.into(); + Transaction::new_with_payer(&instructions, payer.as_ref()) + } + + /// Return a message containing all data that should be signed. + #[wasm_bindgen(js_name = message)] + pub fn js_message(&self) -> Message { + self.message.clone() + } + + /// Return the serialized message data to sign. + pub fn messageData(&self) -> Box<[u8]> { + self.message_data().into() + } + + #[cfg(feature = "verify")] + /// Verify the transaction + #[wasm_bindgen(js_name = verify)] + pub fn js_verify(&self) -> Result<(), JsValue> { + self.verify() + .map_err(|x| std::string::ToString::to_string(&x).into()) + } + + pub fn partialSign(&mut self, keypair: &Keypair, recent_blockhash: &Hash) { + self.partial_sign(&[keypair], *recent_blockhash); + } + + pub fn isSigned(&self) -> bool { + self.is_signed() + } + + #[cfg(feature = "bincode")] + pub fn toBytes(&self) -> Box<[u8]> { + bincode::serialize(self).unwrap().into() + } + + #[cfg(feature = "bincode")] + pub fn fromBytes(bytes: &[u8]) -> Result { + bincode::deserialize(bytes).map_err(|x| std::string::ToString::to_string(&x).into()) + } +} diff --git a/svm/examples/Cargo.lock b/svm/examples/Cargo.lock index 870abcd3eedb7a..6ff602aca54f48 100644 --- a/svm/examples/Cargo.lock +++ b/svm/examples/Cargo.lock @@ -6583,6 +6583,7 @@ dependencies = [ "solana-signature", "solana-signer", "solana-time-utils", + "solana-transaction", "solana-transaction-context", "solana-transaction-error", "thiserror 2.0.3", @@ -7130,6 +7131,32 @@ dependencies = [ "tokio-util 0.7.12", ] +[[package]] +name = "solana-transaction" +version = "2.2.0" +dependencies = [ + "bincode", + "serde", + "serde_derive", + "solana-bincode", + "solana-feature-set", + "solana-hash", + "solana-instruction", + "solana-keypair", + "solana-message", + "solana-precompiles", + "solana-pubkey", + "solana-reserved-account-keys", + "solana-sanitize", + "solana-sdk-ids", + "solana-short-vec", + "solana-signature", + "solana-signer", + "solana-system-interface", + "solana-transaction-error", + "wasm-bindgen", +] + [[package]] name = "solana-transaction-context" version = "2.2.0"