cbe_sdk/transaction/mod.rs
1//! Atomically-committed sequences of instructions.
2//!
3//! While [`Instruction`]s are the basic unit of computation in Cartallum CBE, they are
4//! submitted by clients in [`Transaction`]s containing one or more
5//! instructions, and signed by one or more [`Signer`]s. Cartallum CBE executes the
6//! instructions in a transaction in order, and only commits any changes if all
7//! instructions terminate without producing an error or exception.
8//!
9//! Transactions do not directly contain their instructions but instead include
10//! a [`Message`], a precompiled representation of a sequence of instructions.
11//! `Message`'s constructors handle the complex task of reordering the
12//! individual lists of accounts required by each instruction into a single flat
13//! list of deduplicated accounts required by the Cartallum CBE runtime. The
14//! `Transaction` type has constructors that build the `Message` so that clients
15//! don't need to interact with them directly.
16//!
17//! Prior to submission to the network, transactions must be signed by one or or
18//! more keypairs, and this signing is typically performed by an abstract
19//! [`Signer`], which may be a [`Keypair`] but may also be other types of
20//! signers including remote wallets, such as Ledger devices, as represented by
21//! the [`RemoteKeypair`] type in the [`cbe-remote-wallet`] crate.
22//!
23//! [`Signer`]: crate::signer::Signer
24//! [`Keypair`]: crate::signer::keypair::Keypair
25//! [`cbe-remote-wallet`]: https://docs.cartallum.com/cbe-remote-wallet/latest/
26//! [`RemoteKeypair`]: https://docs.cartallum.com/cbe-remote-wallet/latest/cbe_remote_wallet/remote_keypair/struct.RemoteKeypair.html
27//!
28//! Every transaction must be signed by a fee-paying account, the account from
29//! which the cost of executing the transaction is withdrawn. Other required
30//! signatures are determined by the requirements of the programs being executed
31//! by each instruction, and are conventionally specified by that program's
32//! documentation.
33//!
34//! When signing a transaction, a recent blockhash must be provided (which can
35//! be retrieved with [`RpcClient::get_latest_blockhash`]). This allows
36//! validators to drop old but unexecuted transactions; and to distinguish
37//! between accidentally duplicated transactions and intentionally duplicated
38//! transactions — any identical transactions will not be executed more
39//! than once, so updating the blockhash between submitting otherwise identical
40//! transactions makes them unique. If a client must sign a transaction long
41//! before submitting it to the network, then it can use the _[durable
42//! transaction nonce]_ mechanism instead of a recent blockhash to ensure unique
43//! transactions.
44//!
45//! [`RpcClient::get_latest_blockhash`]: https://docs.cartallum.com/cbe-rpc-client/latest/cbe_rpc_client/rpc_client/struct.RpcClient.html#method.get_latest_blockhash
46//! [durable transaction nonce]: https://docs.cartallum.com/implemented-proposals/durable-tx-nonces
47//!
48//! # Examples
49//!
50//! This example uses the [`cbe_rpc_client`] and [`anyhow`] crates.
51//!
52//! [`cbe_rpc_client`]: https://docs.cartallum.com/cbe-rpc-client
53//! [`anyhow`]: https://docs.cartallum.com/anyhow
54//!
55//! ```
56//! # use cbe_sdk::example_mocks::cbe_rpc_client;
57//! use anyhow::Result;
58//! use borsh::{BorshSerialize, BorshDeserialize};
59//! use cbe_rpc_client::rpc_client::RpcClient;
60//! use cbe_sdk::{
61//! instruction::Instruction,
62//! message::Message,
63//! pubkey::Pubkey,
64//! signature::{Keypair, Signer},
65//! transaction::Transaction,
66//! };
67//!
68//! // A custom program instruction. This would typically be defined in
69//! // another crate so it can be shared between the on-chain program and
70//! // the client.
71//! #[derive(BorshSerialize, BorshDeserialize)]
72//! enum BankInstruction {
73//! Initialize,
74//! Deposit { scoobies: u64 },
75//! Withdraw { scoobies: u64 },
76//! }
77//!
78//! fn send_initialize_tx(
79//! client: &RpcClient,
80//! program_id: Pubkey,
81//! payer: &Keypair
82//! ) -> Result<()> {
83//!
84//! let bank_instruction = BankInstruction::Initialize;
85//!
86//! let instruction = Instruction::new_with_borsh(
87//! program_id,
88//! &bank_instruction,
89//! vec![],
90//! );
91//!
92//! let blockhash = client.get_latest_blockhash()?;
93//! let mut tx = Transaction::new_signed_with_payer(
94//! &[instruction],
95//! Some(&payer.pubkey()),
96//! &[payer],
97//! blockhash,
98//! );
99//! client.send_and_confirm_transaction(&tx)?;
100//!
101//! Ok(())
102//! }
103//! #
104//! # let client = RpcClient::new(String::new());
105//! # let program_id = Pubkey::new_unique();
106//! # let payer = Keypair::new();
107//! # send_initialize_tx(&client, program_id, &payer)?;
108//! #
109//! # Ok::<(), anyhow::Error>(())
110//! ```
111
112#![cfg(feature = "full")]
113
114use {
115 crate::{
116 hash::Hash,
117 instruction::{CompiledInstruction, Instruction},
118 message::Message,
119 nonce::NONCED_TX_MARKER_IX_INDEX,
120 precompiles::verify_if_precompile,
121 program_utils::limited_deserialize,
122 pubkey::Pubkey,
123 sanitize::{Sanitize, SanitizeError},
124 short_vec,
125 signature::{Signature, SignerError},
126 signers::Signers,
127 wasm_bindgen,
128 },
129 serde::Serialize,
130 cbe_program::{system_instruction::SystemInstruction, system_program},
131 cbe_sdk::feature_set,
132 std::result,
133};
134
135mod error;
136mod sanitized;
137mod versioned;
138
139pub use {error::*, sanitized::*, versioned::*};
140
141#[derive(PartialEq, Eq, Clone, Copy, Debug)]
142pub enum TransactionVerificationMode {
143 HashOnly,
144 HashAndVerifyPrecompiles,
145 FullVerification,
146}
147
148pub type Result<T> = result::Result<T, TransactionError>;
149
150/// An atomically-commited sequence of instructions.
151///
152/// While [`Instruction`]s are the basic unit of computation in Cartallum CBE,
153/// they are submitted by clients in [`Transaction`]s containing one or
154/// more instructions, and signed by one or more [`Signer`]s.
155///
156/// [`Signer`]: crate::signer::Signer
157///
158/// See the [module documentation] for more details about transactions.
159///
160/// [module documentation]: self
161///
162/// Some constructors accept an optional `payer`, the account responsible for
163/// paying the cost of executing a transaction. In most cases, callers should
164/// specify the payer explicitly in these constructors. In some cases though,
165/// the caller is not _required_ to specify the payer, but is still allowed to:
166/// in the [`Message`] structure, the first account is always the fee-payer, so
167/// if the caller has knowledge that the first account of the constructed
168/// transaction's `Message` is both a signer and the expected fee-payer, then
169/// redundantly specifying the fee-payer is not strictly required.
170#[wasm_bindgen]
171#[frozen_abi(digest = "FZtncnS1Xk8ghHfKiXE5oGiUbw2wJhmfXQuNgQR3K6Mc")]
172#[derive(Debug, PartialEq, Default, Eq, Clone, Serialize, Deserialize, AbiExample)]
173pub struct Transaction {
174 /// A set of signatures of a serialized [`Message`], signed by the first
175 /// keys of the `Message`'s [`account_keys`], where the number of signatures
176 /// is equal to [`num_required_signatures`] of the `Message`'s
177 /// [`MessageHeader`].
178 ///
179 /// [`account_keys`]: Message::account_keys
180 /// [`MessageHeader`]: crate::message::MessageHeader
181 /// [`num_required_signatures`]: crate::message::MessageHeader::num_required_signatures
182 // NOTE: Serialization-related changes must be paired with the direct read at sigverify.
183 #[wasm_bindgen(skip)]
184 #[serde(with = "short_vec")]
185 pub signatures: Vec<Signature>,
186
187 /// The message to sign.
188 #[wasm_bindgen(skip)]
189 pub message: Message,
190}
191
192impl Sanitize for Transaction {
193 fn sanitize(&self) -> std::result::Result<(), SanitizeError> {
194 if self.message.header.num_required_signatures as usize > self.signatures.len() {
195 return Err(SanitizeError::IndexOutOfBounds);
196 }
197 if self.signatures.len() > self.message.account_keys.len() {
198 return Err(SanitizeError::IndexOutOfBounds);
199 }
200 self.message.sanitize()
201 }
202}
203
204impl Transaction {
205 /// Create an unsigned transaction from a [`Message`].
206 ///
207 /// # Examples
208 ///
209 /// This example uses the [`cbe_rpc_client`] and [`anyhow`] crates.
210 ///
211 /// [`cbe_rpc_client`]: https://docs.cartallum.com/cbe-rpc-client
212 /// [`anyhow`]: https://docs.cartallum.com/anyhow
213 ///
214 /// ```
215 /// # use cbe_sdk::example_mocks::cbe_rpc_client;
216 /// use anyhow::Result;
217 /// use borsh::{BorshSerialize, BorshDeserialize};
218 /// use cbe_rpc_client::rpc_client::RpcClient;
219 /// use cbe_sdk::{
220 /// instruction::Instruction,
221 /// message::Message,
222 /// pubkey::Pubkey,
223 /// signature::{Keypair, Signer},
224 /// transaction::Transaction,
225 /// };
226 ///
227 /// // A custom program instruction. This would typically be defined in
228 /// // another crate so it can be shared between the on-chain program and
229 /// // the client.
230 /// #[derive(BorshSerialize, BorshDeserialize)]
231 /// enum BankInstruction {
232 /// Initialize,
233 /// Deposit { scoobies: u64 },
234 /// Withdraw { scoobies: u64 },
235 /// }
236 ///
237 /// fn send_initialize_tx(
238 /// client: &RpcClient,
239 /// program_id: Pubkey,
240 /// payer: &Keypair
241 /// ) -> Result<()> {
242 ///
243 /// let bank_instruction = BankInstruction::Initialize;
244 ///
245 /// let instruction = Instruction::new_with_borsh(
246 /// program_id,
247 /// &bank_instruction,
248 /// vec![],
249 /// );
250 ///
251 /// let message = Message::new(
252 /// &[instruction],
253 /// Some(&payer.pubkey()),
254 /// );
255 ///
256 /// let mut tx = Transaction::new_unsigned(message);
257 /// let blockhash = client.get_latest_blockhash()?;
258 /// tx.sign(&[payer], blockhash);
259 /// client.send_and_confirm_transaction(&tx)?;
260 ///
261 /// Ok(())
262 /// }
263 /// #
264 /// # let client = RpcClient::new(String::new());
265 /// # let program_id = Pubkey::new_unique();
266 /// # let payer = Keypair::new();
267 /// # send_initialize_tx(&client, program_id, &payer)?;
268 /// #
269 /// # Ok::<(), anyhow::Error>(())
270 /// ```
271 pub fn new_unsigned(message: Message) -> Self {
272 Self {
273 signatures: vec![Signature::default(); message.header.num_required_signatures as usize],
274 message,
275 }
276 }
277
278 /// Create a fully-signed transaction from a [`Message`].
279 ///
280 /// # Panics
281 ///
282 /// Panics when signing fails. See [`Transaction::try_sign`] and
283 /// [`Transaction::try_partial_sign`] for a full description of failure
284 /// scenarios.
285 ///
286 /// # Examples
287 ///
288 /// This example uses the [`cbe_rpc_client`] and [`anyhow`] crates.
289 ///
290 /// [`cbe_rpc_client`]: https://docs.cartallum.com/cbe-rpc-client
291 /// [`anyhow`]: https://docs.cartallum.com/anyhow
292 ///
293 /// ```
294 /// # use cbe_sdk::example_mocks::cbe_rpc_client;
295 /// use anyhow::Result;
296 /// use borsh::{BorshSerialize, BorshDeserialize};
297 /// use cbe_rpc_client::rpc_client::RpcClient;
298 /// use cbe_sdk::{
299 /// instruction::Instruction,
300 /// message::Message,
301 /// pubkey::Pubkey,
302 /// signature::{Keypair, Signer},
303 /// transaction::Transaction,
304 /// };
305 ///
306 /// // A custom program instruction. This would typically be defined in
307 /// // another crate so it can be shared between the on-chain program and
308 /// // the client.
309 /// #[derive(BorshSerialize, BorshDeserialize)]
310 /// enum BankInstruction {
311 /// Initialize,
312 /// Deposit { scoobies: u64 },
313 /// Withdraw { scoobies: u64 },
314 /// }
315 ///
316 /// fn send_initialize_tx(
317 /// client: &RpcClient,
318 /// program_id: Pubkey,
319 /// payer: &Keypair
320 /// ) -> Result<()> {
321 ///
322 /// let bank_instruction = BankInstruction::Initialize;
323 ///
324 /// let instruction = Instruction::new_with_borsh(
325 /// program_id,
326 /// &bank_instruction,
327 /// vec![],
328 /// );
329 ///
330 /// let message = Message::new(
331 /// &[instruction],
332 /// Some(&payer.pubkey()),
333 /// );
334 ///
335 /// let blockhash = client.get_latest_blockhash()?;
336 /// let mut tx = Transaction::new(&[payer], message, blockhash);
337 /// client.send_and_confirm_transaction(&tx)?;
338 ///
339 /// Ok(())
340 /// }
341 /// #
342 /// # let client = RpcClient::new(String::new());
343 /// # let program_id = Pubkey::new_unique();
344 /// # let payer = Keypair::new();
345 /// # send_initialize_tx(&client, program_id, &payer)?;
346 /// #
347 /// # Ok::<(), anyhow::Error>(())
348 /// ```
349 pub fn new<T: Signers>(
350 from_keypairs: &T,
351 message: Message,
352 recent_blockhash: Hash,
353 ) -> Transaction {
354 let mut tx = Self::new_unsigned(message);
355 tx.sign(from_keypairs, recent_blockhash);
356 tx
357 }
358
359 /// Create an unsigned transaction from a list of [`Instruction`]s.
360 ///
361 /// `payer` is the account responsible for paying the cost of executing the
362 /// transaction. It is typically provided, but is optional in some cases.
363 /// See the [`Transaction`] docs for more.
364 ///
365 /// # Examples
366 ///
367 /// This example uses the [`cbe_rpc_client`] and [`anyhow`] crates.
368 ///
369 /// [`cbe_rpc_client`]: https://docs.cartallum.com/cbe-rpc-client
370 /// [`anyhow`]: https://docs.cartallum.com/anyhow
371 ///
372 /// ```
373 /// # use cbe_sdk::example_mocks::cbe_rpc_client;
374 /// use anyhow::Result;
375 /// use borsh::{BorshSerialize, BorshDeserialize};
376 /// use cbe_rpc_client::rpc_client::RpcClient;
377 /// use cbe_sdk::{
378 /// instruction::Instruction,
379 /// message::Message,
380 /// pubkey::Pubkey,
381 /// signature::{Keypair, Signer},
382 /// transaction::Transaction,
383 /// };
384 ///
385 /// // A custom program instruction. This would typically be defined in
386 /// // another crate so it can be shared between the on-chain program and
387 /// // the client.
388 /// #[derive(BorshSerialize, BorshDeserialize)]
389 /// enum BankInstruction {
390 /// Initialize,
391 /// Deposit { scoobies: u64 },
392 /// Withdraw { scoobies: u64 },
393 /// }
394 ///
395 /// fn send_initialize_tx(
396 /// client: &RpcClient,
397 /// program_id: Pubkey,
398 /// payer: &Keypair
399 /// ) -> Result<()> {
400 ///
401 /// let bank_instruction = BankInstruction::Initialize;
402 ///
403 /// let instruction = Instruction::new_with_borsh(
404 /// program_id,
405 /// &bank_instruction,
406 /// vec![],
407 /// );
408 ///
409 /// let mut tx = Transaction::new_with_payer(&[instruction], Some(&payer.pubkey()));
410 /// let blockhash = client.get_latest_blockhash()?;
411 /// tx.sign(&[payer], blockhash);
412 /// client.send_and_confirm_transaction(&tx)?;
413 ///
414 /// Ok(())
415 /// }
416 /// #
417 /// # let client = RpcClient::new(String::new());
418 /// # let program_id = Pubkey::new_unique();
419 /// # let payer = Keypair::new();
420 /// # send_initialize_tx(&client, program_id, &payer)?;
421 /// #
422 /// # Ok::<(), anyhow::Error>(())
423 /// ```
424 pub fn new_with_payer(instructions: &[Instruction], payer: Option<&Pubkey>) -> Self {
425 let message = Message::new(instructions, payer);
426 Self::new_unsigned(message)
427 }
428
429 /// Create a fully-signed transaction from a list of [`Instruction`]s.
430 ///
431 /// `payer` is the account responsible for paying the cost of executing the
432 /// transaction. It is typically provided, but is optional in some cases.
433 /// See the [`Transaction`] docs for more.
434 ///
435 /// # Panics
436 ///
437 /// Panics when signing fails. See [`Transaction::try_sign`] and
438 /// [`Transaction::try_partial_sign`] for a full description of failure
439 /// scenarios.
440 ///
441 /// # Examples
442 ///
443 /// This example uses the [`cbe_rpc_client`] and [`anyhow`] crates.
444 ///
445 /// [`cbe_rpc_client`]: https://docs.cartallum.com/cbe-rpc-client
446 /// [`anyhow`]: https://docs.cartallum.com/anyhow
447 ///
448 /// ```
449 /// # use cbe_sdk::example_mocks::cbe_rpc_client;
450 /// use anyhow::Result;
451 /// use borsh::{BorshSerialize, BorshDeserialize};
452 /// use cbe_rpc_client::rpc_client::RpcClient;
453 /// use cbe_sdk::{
454 /// instruction::Instruction,
455 /// message::Message,
456 /// pubkey::Pubkey,
457 /// signature::{Keypair, Signer},
458 /// transaction::Transaction,
459 /// };
460 ///
461 /// // A custom program instruction. This would typically be defined in
462 /// // another crate so it can be shared between the on-chain program and
463 /// // the client.
464 /// #[derive(BorshSerialize, BorshDeserialize)]
465 /// enum BankInstruction {
466 /// Initialize,
467 /// Deposit { scoobies: u64 },
468 /// Withdraw { scoobies: u64 },
469 /// }
470 ///
471 /// fn send_initialize_tx(
472 /// client: &RpcClient,
473 /// program_id: Pubkey,
474 /// payer: &Keypair
475 /// ) -> Result<()> {
476 ///
477 /// let bank_instruction = BankInstruction::Initialize;
478 ///
479 /// let instruction = Instruction::new_with_borsh(
480 /// program_id,
481 /// &bank_instruction,
482 /// vec![],
483 /// );
484 ///
485 /// let blockhash = client.get_latest_blockhash()?;
486 /// let mut tx = Transaction::new_signed_with_payer(
487 /// &[instruction],
488 /// Some(&payer.pubkey()),
489 /// &[payer],
490 /// blockhash,
491 /// );
492 /// client.send_and_confirm_transaction(&tx)?;
493 ///
494 /// Ok(())
495 /// }
496 /// #
497 /// # let client = RpcClient::new(String::new());
498 /// # let program_id = Pubkey::new_unique();
499 /// # let payer = Keypair::new();
500 /// # send_initialize_tx(&client, program_id, &payer)?;
501 /// #
502 /// # Ok::<(), anyhow::Error>(())
503 /// ```
504 pub fn new_signed_with_payer<T: Signers>(
505 instructions: &[Instruction],
506 payer: Option<&Pubkey>,
507 signing_keypairs: &T,
508 recent_blockhash: Hash,
509 ) -> Self {
510 let message = Message::new(instructions, payer);
511 Self::new(signing_keypairs, message, recent_blockhash)
512 }
513
514 /// Create a fully-signed transaction from pre-compiled instructions.
515 ///
516 /// # Arguments
517 ///
518 /// * `from_keypairs` - The keys used to sign the transaction.
519 /// * `keys` - The keys for the transaction. These are the program state
520 /// instances or scoobie recipient keys.
521 /// * `recent_blockhash` - The PoH hash.
522 /// * `program_ids` - The keys that identify programs used in the `instruction` vector.
523 /// * `instructions` - Instructions that will be executed atomically.
524 ///
525 /// # Panics
526 ///
527 /// Panics when signing fails. See [`Transaction::try_sign`] and for a full
528 /// description of failure conditions.
529 pub fn new_with_compiled_instructions<T: Signers>(
530 from_keypairs: &T,
531 keys: &[Pubkey],
532 recent_blockhash: Hash,
533 program_ids: Vec<Pubkey>,
534 instructions: Vec<CompiledInstruction>,
535 ) -> Self {
536 let mut account_keys = from_keypairs.pubkeys();
537 let from_keypairs_len = account_keys.len();
538 account_keys.extend_from_slice(keys);
539 account_keys.extend(&program_ids);
540 let message = Message::new_with_compiled_instructions(
541 from_keypairs_len as u8,
542 0,
543 program_ids.len() as u8,
544 account_keys,
545 Hash::default(),
546 instructions,
547 );
548 Transaction::new(from_keypairs, message, recent_blockhash)
549 }
550
551 /// Get the data for an instruction at the given index.
552 ///
553 /// The `instruction_index` corresponds to the [`instructions`] vector of
554 /// the `Transaction`'s [`Message`] value.
555 ///
556 /// [`instructions`]: Message::instructions
557 ///
558 /// # Panics
559 ///
560 /// Panics if `instruction_index` is greater than or equal to the number of
561 /// instructions in the transaction.
562 pub fn data(&self, instruction_index: usize) -> &[u8] {
563 &self.message.instructions[instruction_index].data
564 }
565
566 fn key_index(&self, instruction_index: usize, accounts_index: usize) -> Option<usize> {
567 self.message
568 .instructions
569 .get(instruction_index)
570 .and_then(|instruction| instruction.accounts.get(accounts_index))
571 .map(|&account_keys_index| account_keys_index as usize)
572 }
573
574 /// Get the `Pubkey` of an account required by one of the instructions in
575 /// the transaction.
576 ///
577 /// The `instruction_index` corresponds to the [`instructions`] vector of
578 /// the `Transaction`'s [`Message`] value; and the `account_index` to the
579 /// [`accounts`] vector of the message's [`CompiledInstruction`]s.
580 ///
581 /// [`instructions`]: Message::instructions
582 /// [`accounts`]: CompiledInstruction::accounts
583 /// [`CompiledInstruction`]: CompiledInstruction
584 ///
585 /// Returns `None` if `instruction_index` is greater than or equal to the
586 /// number of instructions in the transaction; or if `accounts_index` is
587 /// greater than or equal to the number of accounts in the instruction.
588 pub fn key(&self, instruction_index: usize, accounts_index: usize) -> Option<&Pubkey> {
589 self.key_index(instruction_index, accounts_index)
590 .and_then(|account_keys_index| self.message.account_keys.get(account_keys_index))
591 }
592
593 /// Get the `Pubkey` of a signing account required by one of the
594 /// instructions in the transaction.
595 ///
596 /// The transaction does not need to be signed for this function to return a
597 /// signing account's pubkey.
598 ///
599 /// Returns `None` if the indexed account is not required to sign the
600 /// transaction. Returns `None` if the [`signatures`] field does not contain
601 /// enough elements to hold a signature for the indexed account (this should
602 /// only be possible if `Transaction` has been manually constructed).
603 ///
604 /// [`signatures`]: Transaction::signatures
605 ///
606 /// Returns `None` if `instruction_index` is greater than or equal to the
607 /// number of instructions in the transaction; or if `accounts_index` is
608 /// greater than or equal to the number of accounts in the instruction.
609 pub fn signer_key(&self, instruction_index: usize, accounts_index: usize) -> Option<&Pubkey> {
610 match self.key_index(instruction_index, accounts_index) {
611 None => None,
612 Some(signature_index) => {
613 if signature_index >= self.signatures.len() {
614 return None;
615 }
616 self.message.account_keys.get(signature_index)
617 }
618 }
619 }
620
621 /// Return the message containing all data that should be signed.
622 pub fn message(&self) -> &Message {
623 &self.message
624 }
625
626 /// Return the serialized message data to sign.
627 pub fn message_data(&self) -> Vec<u8> {
628 self.message().serialize()
629 }
630
631 /// Sign the transaction.
632 ///
633 /// This method fully signs a transaction with all required signers, which
634 /// must be present in the `keypairs` slice. To sign with only some of the
635 /// required signers, use [`Transaction::partial_sign`].
636 ///
637 /// If `recent_blockhash` is different than recorded in the transaction message's
638 /// [`recent_blockhash`] field, then the message's `recent_blockhash` will be updated
639 /// to the provided `recent_blockhash`, and any prior signatures will be cleared.
640 ///
641 /// [`recent_blockhash`]: Message::recent_blockhash
642 ///
643 /// # Panics
644 ///
645 /// Panics when signing fails. Use [`Transaction::try_sign`] to handle the
646 /// error. See the documentation for [`Transaction::try_sign`] for a full description of
647 /// failure conditions.
648 ///
649 /// # Examples
650 ///
651 /// This example uses the [`cbe_rpc_client`] and [`anyhow`] crates.
652 ///
653 /// [`cbe_rpc_client`]: https://docs.cartallum.com/cbe-rpc-client
654 /// [`anyhow`]: https://docs.cartallum.com/anyhow
655 ///
656 /// ```
657 /// # use cbe_sdk::example_mocks::cbe_rpc_client;
658 /// use anyhow::Result;
659 /// use borsh::{BorshSerialize, BorshDeserialize};
660 /// use cbe_rpc_client::rpc_client::RpcClient;
661 /// use cbe_sdk::{
662 /// instruction::Instruction,
663 /// message::Message,
664 /// pubkey::Pubkey,
665 /// signature::{Keypair, Signer},
666 /// transaction::Transaction,
667 /// };
668 ///
669 /// // A custom program instruction. This would typically be defined in
670 /// // another crate so it can be shared between the on-chain program and
671 /// // the client.
672 /// #[derive(BorshSerialize, BorshDeserialize)]
673 /// enum BankInstruction {
674 /// Initialize,
675 /// Deposit { scoobies: u64 },
676 /// Withdraw { scoobies: u64 },
677 /// }
678 ///
679 /// fn send_initialize_tx(
680 /// client: &RpcClient,
681 /// program_id: Pubkey,
682 /// payer: &Keypair
683 /// ) -> Result<()> {
684 ///
685 /// let bank_instruction = BankInstruction::Initialize;
686 ///
687 /// let instruction = Instruction::new_with_borsh(
688 /// program_id,
689 /// &bank_instruction,
690 /// vec![],
691 /// );
692 ///
693 /// let mut tx = Transaction::new_with_payer(&[instruction], Some(&payer.pubkey()));
694 /// let blockhash = client.get_latest_blockhash()?;
695 /// tx.sign(&[payer], blockhash);
696 /// client.send_and_confirm_transaction(&tx)?;
697 ///
698 /// Ok(())
699 /// }
700 /// #
701 /// # let client = RpcClient::new(String::new());
702 /// # let program_id = Pubkey::new_unique();
703 /// # let payer = Keypair::new();
704 /// # send_initialize_tx(&client, program_id, &payer)?;
705 /// #
706 /// # Ok::<(), anyhow::Error>(())
707 /// ```
708 pub fn sign<T: Signers>(&mut self, keypairs: &T, recent_blockhash: Hash) {
709 if let Err(e) = self.try_sign(keypairs, recent_blockhash) {
710 panic!("Transaction::sign failed with error {e:?}");
711 }
712 }
713
714 /// Sign the transaction with a subset of required keys.
715 ///
716 /// Unlike [`Transaction::sign`], this method does not require all keypairs
717 /// to be provided, allowing a transaction to be signed in multiple steps.
718 ///
719 /// It is permitted to sign a transaction with the same keypair multiple
720 /// times.
721 ///
722 /// If `recent_blockhash` is different than recorded in the transaction message's
723 /// [`recent_blockhash`] field, then the message's `recent_blockhash` will be updated
724 /// to the provided `recent_blockhash`, and any prior signatures will be cleared.
725 ///
726 /// [`recent_blockhash`]: Message::recent_blockhash
727 ///
728 /// # Panics
729 ///
730 /// Panics when signing fails. Use [`Transaction::try_partial_sign`] to
731 /// handle the error. See the documentation for
732 /// [`Transaction::try_partial_sign`] for a full description of failure
733 /// conditions.
734 pub fn partial_sign<T: Signers>(&mut self, keypairs: &T, recent_blockhash: Hash) {
735 if let Err(e) = self.try_partial_sign(keypairs, recent_blockhash) {
736 panic!("Transaction::partial_sign failed with error {e:?}");
737 }
738 }
739
740 /// Sign the transaction with a subset of required keys.
741 ///
742 /// This places each of the signatures created from `keypairs` in the
743 /// corresponding position, as specified in the `positions` vector, in the
744 /// transactions [`signatures`] field. It does not verify that the signature
745 /// positions are correct.
746 ///
747 /// [`signatures`]: Transaction::signatures
748 ///
749 /// # Panics
750 ///
751 /// Panics if signing fails. Use [`Transaction::try_partial_sign_unchecked`]
752 /// to handle the error.
753 pub fn partial_sign_unchecked<T: Signers>(
754 &mut self,
755 keypairs: &T,
756 positions: Vec<usize>,
757 recent_blockhash: Hash,
758 ) {
759 if let Err(e) = self.try_partial_sign_unchecked(keypairs, positions, recent_blockhash) {
760 panic!("Transaction::partial_sign_unchecked failed with error {e:?}");
761 }
762 }
763
764 /// Sign the transaction, returning any errors.
765 ///
766 /// This method fully signs a transaction with all required signers, which
767 /// must be present in the `keypairs` slice. To sign with only some of the
768 /// required signers, use [`Transaction::try_partial_sign`].
769 ///
770 /// If `recent_blockhash` is different than recorded in the transaction message's
771 /// [`recent_blockhash`] field, then the message's `recent_blockhash` will be updated
772 /// to the provided `recent_blockhash`, and any prior signatures will be cleared.
773 ///
774 /// [`recent_blockhash`]: Message::recent_blockhash
775 ///
776 /// # Errors
777 ///
778 /// Signing will fail if some required signers are not provided in
779 /// `keypairs`; or, if the transaction has previously been partially signed,
780 /// some of the remaining required signers are not provided in `keypairs`.
781 /// In other words, the transaction must be fully signed as a result of
782 /// calling this function. The error is [`SignerError::NotEnoughSigners`].
783 ///
784 /// Signing will fail for any of the reasons described in the documentation
785 /// for [`Transaction::try_partial_sign`].
786 ///
787 /// # Examples
788 ///
789 /// This example uses the [`cbe_rpc_client`] and [`anyhow`] crates.
790 ///
791 /// [`cbe_rpc_client`]: https://docs.cartallum.com/cbe-rpc-client
792 /// [`anyhow`]: https://docs.cartallum.com/anyhow
793 ///
794 /// ```
795 /// # use cbe_sdk::example_mocks::cbe_rpc_client;
796 /// use anyhow::Result;
797 /// use borsh::{BorshSerialize, BorshDeserialize};
798 /// use cbe_rpc_client::rpc_client::RpcClient;
799 /// use cbe_sdk::{
800 /// instruction::Instruction,
801 /// message::Message,
802 /// pubkey::Pubkey,
803 /// signature::{Keypair, Signer},
804 /// transaction::Transaction,
805 /// };
806 ///
807 /// // A custom program instruction. This would typically be defined in
808 /// // another crate so it can be shared between the on-chain program and
809 /// // the client.
810 /// #[derive(BorshSerialize, BorshDeserialize)]
811 /// enum BankInstruction {
812 /// Initialize,
813 /// Deposit { scoobies: u64 },
814 /// Withdraw { scoobies: u64 },
815 /// }
816 ///
817 /// fn send_initialize_tx(
818 /// client: &RpcClient,
819 /// program_id: Pubkey,
820 /// payer: &Keypair
821 /// ) -> Result<()> {
822 ///
823 /// let bank_instruction = BankInstruction::Initialize;
824 ///
825 /// let instruction = Instruction::new_with_borsh(
826 /// program_id,
827 /// &bank_instruction,
828 /// vec![],
829 /// );
830 ///
831 /// let mut tx = Transaction::new_with_payer(&[instruction], Some(&payer.pubkey()));
832 /// let blockhash = client.get_latest_blockhash()?;
833 /// tx.try_sign(&[payer], blockhash)?;
834 /// client.send_and_confirm_transaction(&tx)?;
835 ///
836 /// Ok(())
837 /// }
838 /// #
839 /// # let client = RpcClient::new(String::new());
840 /// # let program_id = Pubkey::new_unique();
841 /// # let payer = Keypair::new();
842 /// # send_initialize_tx(&client, program_id, &payer)?;
843 /// #
844 /// # Ok::<(), anyhow::Error>(())
845 /// ```
846 pub fn try_sign<T: Signers>(
847 &mut self,
848 keypairs: &T,
849 recent_blockhash: Hash,
850 ) -> result::Result<(), SignerError> {
851 self.try_partial_sign(keypairs, recent_blockhash)?;
852
853 if !self.is_signed() {
854 Err(SignerError::NotEnoughSigners)
855 } else {
856 Ok(())
857 }
858 }
859
860 /// Sign the transaction with a subset of required keys, returning any errors.
861 ///
862 /// Unlike [`Transaction::try_sign`], this method does not require all
863 /// keypairs to be provided, allowing a transaction to be signed in multiple
864 /// steps.
865 ///
866 /// It is permitted to sign a transaction with the same keypair multiple
867 /// times.
868 ///
869 /// If `recent_blockhash` is different than recorded in the transaction message's
870 /// [`recent_blockhash`] field, then the message's `recent_blockhash` will be updated
871 /// to the provided `recent_blockhash`, and any prior signatures will be cleared.
872 ///
873 /// [`recent_blockhash`]: Message::recent_blockhash
874 ///
875 /// # Errors
876 ///
877 /// Signing will fail if
878 ///
879 /// - The transaction's [`Message`] is malformed such that the number of
880 /// required signatures recorded in its header
881 /// ([`num_required_signatures`]) is greater than the length of its
882 /// account keys ([`account_keys`]). The error is
883 /// [`SignerError::TransactionError`] where the interior
884 /// [`TransactionError`] is [`TransactionError::InvalidAccountIndex`].
885 /// - Any of the provided signers in `keypairs` is not a required signer of
886 /// the message. The error is [`SignerError::KeypairPubkeyMismatch`].
887 /// - Any of the signers is a [`Presigner`], and its provided signature is
888 /// incorrect. The error is [`SignerError::PresignerError`] where the
889 /// interior [`PresignerError`] is
890 /// [`PresignerError::VerificationFailure`].
891 /// - The signer is a [`RemoteKeypair`] and
892 /// - It does not understand the input provided ([`SignerError::InvalidInput`]).
893 /// - The device cannot be found ([`SignerError::NoDeviceFound`]).
894 /// - The user cancels the signing ([`SignerError::UserCancel`]).
895 /// - An error was encountered connecting ([`SignerError::Connection`]).
896 /// - Some device-specific protocol error occurs ([`SignerError::Protocol`]).
897 /// - Some other error occurs ([`SignerError::Custom`]).
898 ///
899 /// See the documentation for the [`cbe-remote-wallet`] crate for details
900 /// on the operation of [`RemoteKeypair`] signers.
901 ///
902 /// [`num_required_signatures`]: crate::message::MessageHeader::num_required_signatures
903 /// [`account_keys`]: Message::account_keys
904 /// [`Presigner`]: crate::signer::presigner::Presigner
905 /// [`PresignerError`]: crate::signer::presigner::PresignerError
906 /// [`PresignerError::VerificationFailure`]: crate::signer::presigner::PresignerError::VerificationFailure
907 /// [`cbe-remote-wallet`]: https://docs.cartallum.com/cbe-remote-wallet/latest/
908 /// [`RemoteKeypair`]: https://docs.cartallum.com/cbe-remote-wallet/latest/cbe_remote_wallet/remote_keypair/struct.RemoteKeypair.html
909 pub fn try_partial_sign<T: Signers>(
910 &mut self,
911 keypairs: &T,
912 recent_blockhash: Hash,
913 ) -> result::Result<(), SignerError> {
914 let positions = self.get_signing_keypair_positions(&keypairs.pubkeys())?;
915 if positions.iter().any(|pos| pos.is_none()) {
916 return Err(SignerError::KeypairPubkeyMismatch);
917 }
918 let positions: Vec<usize> = positions.iter().map(|pos| pos.unwrap()).collect();
919 self.try_partial_sign_unchecked(keypairs, positions, recent_blockhash)
920 }
921
922 /// Sign the transaction with a subset of required keys, returning any
923 /// errors.
924 ///
925 /// This places each of the signatures created from `keypairs` in the
926 /// corresponding position, as specified in the `positions` vector, in the
927 /// transactions [`signatures`] field. It does not verify that the signature
928 /// positions are correct.
929 ///
930 /// [`signatures`]: Transaction::signatures
931 ///
932 /// # Errors
933 ///
934 /// Returns an error if signing fails.
935 pub fn try_partial_sign_unchecked<T: Signers>(
936 &mut self,
937 keypairs: &T,
938 positions: Vec<usize>,
939 recent_blockhash: Hash,
940 ) -> result::Result<(), SignerError> {
941 // if you change the blockhash, you're re-signing...
942 if recent_blockhash != self.message.recent_blockhash {
943 self.message.recent_blockhash = recent_blockhash;
944 self.signatures
945 .iter_mut()
946 .for_each(|signature| *signature = Signature::default());
947 }
948
949 let signatures = keypairs.try_sign_message(&self.message_data())?;
950 for i in 0..positions.len() {
951 self.signatures[positions[i]] = signatures[i];
952 }
953 Ok(())
954 }
955
956 /// Returns a signature that is not valid for signing this transaction.
957 pub fn get_invalid_signature() -> Signature {
958 Signature::default()
959 }
960
961 /// Verifies that all signers have signed the message.
962 ///
963 /// # Errors
964 ///
965 /// Returns [`TransactionError::SignatureFailure`] on error.
966 pub fn verify(&self) -> Result<()> {
967 let message_bytes = self.message_data();
968 if !self
969 ._verify_with_results(&message_bytes)
970 .iter()
971 .all(|verify_result| *verify_result)
972 {
973 Err(TransactionError::SignatureFailure)
974 } else {
975 Ok(())
976 }
977 }
978
979 /// Verify the transaction and hash its message.
980 ///
981 /// # Errors
982 ///
983 /// Returns [`TransactionError::SignatureFailure`] on error.
984 pub fn verify_and_hash_message(&self) -> Result<Hash> {
985 let message_bytes = self.message_data();
986 if !self
987 ._verify_with_results(&message_bytes)
988 .iter()
989 .all(|verify_result| *verify_result)
990 {
991 Err(TransactionError::SignatureFailure)
992 } else {
993 Ok(Message::hash_raw_message(&message_bytes))
994 }
995 }
996
997 /// Verifies that all signers have signed the message.
998 ///
999 /// Returns a vector with the length of required signatures, where each
1000 /// element is either `true` if that signer has signed, or `false` if not.
1001 pub fn verify_with_results(&self) -> Vec<bool> {
1002 self._verify_with_results(&self.message_data())
1003 }
1004
1005 pub(crate) fn _verify_with_results(&self, message_bytes: &[u8]) -> Vec<bool> {
1006 self.signatures
1007 .iter()
1008 .zip(&self.message.account_keys)
1009 .map(|(signature, pubkey)| signature.verify(pubkey.as_ref(), message_bytes))
1010 .collect()
1011 }
1012
1013 /// Verify the precompiled programs in this transaction.
1014 pub fn verify_precompiles(&self, feature_set: &feature_set::FeatureSet) -> Result<()> {
1015 for instruction in &self.message().instructions {
1016 // The Transaction may not be sanitized at this point
1017 if instruction.program_id_index as usize >= self.message().account_keys.len() {
1018 return Err(TransactionError::AccountNotFound);
1019 }
1020 let program_id = &self.message().account_keys[instruction.program_id_index as usize];
1021
1022 verify_if_precompile(
1023 program_id,
1024 instruction,
1025 &self.message().instructions,
1026 feature_set,
1027 )
1028 .map_err(|_| TransactionError::InvalidAccountIndex)?;
1029 }
1030 Ok(())
1031 }
1032
1033 /// Get the positions of the pubkeys in `account_keys` associated with signing keypairs.
1034 ///
1035 /// [`account_keys`]: Message::account_keys
1036 pub fn get_signing_keypair_positions(&self, pubkeys: &[Pubkey]) -> Result<Vec<Option<usize>>> {
1037 if self.message.account_keys.len() < self.message.header.num_required_signatures as usize {
1038 return Err(TransactionError::InvalidAccountIndex);
1039 }
1040 let signed_keys =
1041 &self.message.account_keys[0..self.message.header.num_required_signatures as usize];
1042
1043 Ok(pubkeys
1044 .iter()
1045 .map(|pubkey| signed_keys.iter().position(|x| x == pubkey))
1046 .collect())
1047 }
1048
1049 /// Replace all the signatures and pubkeys.
1050 pub fn replace_signatures(&mut self, signers: &[(Pubkey, Signature)]) -> Result<()> {
1051 let num_required_signatures = self.message.header.num_required_signatures as usize;
1052 if signers.len() != num_required_signatures
1053 || self.signatures.len() != num_required_signatures
1054 || self.message.account_keys.len() < num_required_signatures
1055 {
1056 return Err(TransactionError::InvalidAccountIndex);
1057 }
1058
1059 signers
1060 .iter()
1061 .enumerate()
1062 .for_each(|(i, (pubkey, signature))| {
1063 self.signatures[i] = *signature;
1064 self.message.account_keys[i] = *pubkey;
1065 });
1066
1067 self.verify()
1068 }
1069
1070 pub fn is_signed(&self) -> bool {
1071 self.signatures
1072 .iter()
1073 .all(|signature| *signature != Signature::default())
1074 }
1075}
1076
1077pub fn uses_durable_nonce(tx: &Transaction) -> Option<&CompiledInstruction> {
1078 let message = tx.message();
1079 message
1080 .instructions
1081 .get(NONCED_TX_MARKER_IX_INDEX as usize)
1082 .filter(|instruction| {
1083 // Is system program
1084 matches!(
1085 message.account_keys.get(instruction.program_id_index as usize),
1086 Some(program_id) if system_program::check_id(program_id)
1087 )
1088 // Is a nonce advance instruction
1089 && matches!(
1090 limited_deserialize(&instruction.data),
1091 Ok(SystemInstruction::AdvanceNonceAccount)
1092 )
1093 // Nonce account is writable
1094 && matches!(
1095 instruction.accounts.first(),
1096 Some(index) if message.is_writable(*index as usize)
1097 )
1098 })
1099}
1100
1101#[deprecated]
1102pub fn get_nonce_pubkey_from_instruction<'a>(
1103 ix: &CompiledInstruction,
1104 tx: &'a Transaction,
1105) -> Option<&'a Pubkey> {
1106 ix.accounts.first().and_then(|idx| {
1107 let idx = *idx as usize;
1108 tx.message().account_keys.get(idx)
1109 })
1110}
1111
1112#[cfg(test)]
1113mod tests {
1114 #![allow(deprecated)]
1115
1116 use {
1117 super::*,
1118 crate::{
1119 hash::hash,
1120 instruction::AccountMeta,
1121 signature::{Keypair, Presigner, Signer},
1122 system_instruction, sysvar,
1123 },
1124 bincode::{deserialize, serialize, serialized_size},
1125 std::mem::size_of,
1126 };
1127
1128 fn get_program_id(tx: &Transaction, instruction_index: usize) -> &Pubkey {
1129 let message = tx.message();
1130 let instruction = &message.instructions[instruction_index];
1131 instruction.program_id(&message.account_keys)
1132 }
1133
1134 #[test]
1135 fn test_refs() {
1136 let key = Keypair::new();
1137 let key1 = cbe_sdk::pubkey::new_rand();
1138 let key2 = cbe_sdk::pubkey::new_rand();
1139 let prog1 = cbe_sdk::pubkey::new_rand();
1140 let prog2 = cbe_sdk::pubkey::new_rand();
1141 let instructions = vec![
1142 CompiledInstruction::new(3, &(), vec![0, 1]),
1143 CompiledInstruction::new(4, &(), vec![0, 2]),
1144 ];
1145 let tx = Transaction::new_with_compiled_instructions(
1146 &[&key],
1147 &[key1, key2],
1148 Hash::default(),
1149 vec![prog1, prog2],
1150 instructions,
1151 );
1152 assert!(tx.sanitize().is_ok());
1153
1154 assert_eq!(tx.key(0, 0), Some(&key.pubkey()));
1155 assert_eq!(tx.signer_key(0, 0), Some(&key.pubkey()));
1156
1157 assert_eq!(tx.key(1, 0), Some(&key.pubkey()));
1158 assert_eq!(tx.signer_key(1, 0), Some(&key.pubkey()));
1159
1160 assert_eq!(tx.key(0, 1), Some(&key1));
1161 assert_eq!(tx.signer_key(0, 1), None);
1162
1163 assert_eq!(tx.key(1, 1), Some(&key2));
1164 assert_eq!(tx.signer_key(1, 1), None);
1165
1166 assert_eq!(tx.key(2, 0), None);
1167 assert_eq!(tx.signer_key(2, 0), None);
1168
1169 assert_eq!(tx.key(0, 2), None);
1170 assert_eq!(tx.signer_key(0, 2), None);
1171
1172 assert_eq!(*get_program_id(&tx, 0), prog1);
1173 assert_eq!(*get_program_id(&tx, 1), prog2);
1174 }
1175
1176 #[test]
1177 fn test_refs_invalid_program_id() {
1178 let key = Keypair::new();
1179 let instructions = vec![CompiledInstruction::new(1, &(), vec![])];
1180 let tx = Transaction::new_with_compiled_instructions(
1181 &[&key],
1182 &[],
1183 Hash::default(),
1184 vec![],
1185 instructions,
1186 );
1187 assert_eq!(tx.sanitize(), Err(SanitizeError::IndexOutOfBounds));
1188 }
1189 #[test]
1190 fn test_refs_invalid_account() {
1191 let key = Keypair::new();
1192 let instructions = vec![CompiledInstruction::new(1, &(), vec![2])];
1193 let tx = Transaction::new_with_compiled_instructions(
1194 &[&key],
1195 &[],
1196 Hash::default(),
1197 vec![Pubkey::default()],
1198 instructions,
1199 );
1200 assert_eq!(*get_program_id(&tx, 0), Pubkey::default());
1201 assert_eq!(tx.sanitize(), Err(SanitizeError::IndexOutOfBounds));
1202 }
1203
1204 #[test]
1205 fn test_sanitize_txs() {
1206 let key = Keypair::new();
1207 let id0 = Pubkey::default();
1208 let program_id = cbe_sdk::pubkey::new_rand();
1209 let ix = Instruction::new_with_bincode(
1210 program_id,
1211 &0,
1212 vec![
1213 AccountMeta::new(key.pubkey(), true),
1214 AccountMeta::new(id0, true),
1215 ],
1216 );
1217 let mut tx = Transaction::new_with_payer(&[ix], Some(&key.pubkey()));
1218 let o = tx.clone();
1219 assert_eq!(tx.sanitize(), Ok(()));
1220 assert_eq!(tx.message.account_keys.len(), 3);
1221
1222 tx = o.clone();
1223 tx.message.header.num_required_signatures = 3;
1224 assert_eq!(tx.sanitize(), Err(SanitizeError::IndexOutOfBounds));
1225
1226 tx = o.clone();
1227 tx.message.header.num_readonly_signed_accounts = 4;
1228 tx.message.header.num_readonly_unsigned_accounts = 0;
1229 assert_eq!(tx.sanitize(), Err(SanitizeError::IndexOutOfBounds));
1230
1231 tx = o.clone();
1232 tx.message.header.num_readonly_signed_accounts = 2;
1233 tx.message.header.num_readonly_unsigned_accounts = 2;
1234 assert_eq!(tx.sanitize(), Err(SanitizeError::IndexOutOfBounds));
1235
1236 tx = o.clone();
1237 tx.message.header.num_readonly_signed_accounts = 0;
1238 tx.message.header.num_readonly_unsigned_accounts = 4;
1239 assert_eq!(tx.sanitize(), Err(SanitizeError::IndexOutOfBounds));
1240
1241 tx = o.clone();
1242 tx.message.instructions[0].program_id_index = 3;
1243 assert_eq!(tx.sanitize(), Err(SanitizeError::IndexOutOfBounds));
1244
1245 tx = o.clone();
1246 tx.message.instructions[0].accounts[0] = 3;
1247 assert_eq!(tx.sanitize(), Err(SanitizeError::IndexOutOfBounds));
1248
1249 tx = o.clone();
1250 tx.message.instructions[0].program_id_index = 0;
1251 assert_eq!(tx.sanitize(), Err(SanitizeError::IndexOutOfBounds));
1252
1253 tx = o.clone();
1254 tx.message.header.num_readonly_signed_accounts = 2;
1255 tx.message.header.num_readonly_unsigned_accounts = 3;
1256 tx.message.account_keys.resize(4, Pubkey::default());
1257 assert_eq!(tx.sanitize(), Err(SanitizeError::IndexOutOfBounds));
1258
1259 tx = o;
1260 tx.message.header.num_readonly_signed_accounts = 2;
1261 tx.message.header.num_required_signatures = 1;
1262 assert_eq!(tx.sanitize(), Err(SanitizeError::IndexOutOfBounds));
1263 }
1264
1265 fn create_sample_transaction() -> Transaction {
1266 let keypair = Keypair::from_bytes(&[
1267 48, 83, 2, 1, 1, 48, 5, 6, 3, 43, 101, 112, 4, 34, 4, 32, 255, 101, 36, 24, 124, 23,
1268 167, 21, 132, 204, 155, 5, 185, 58, 121, 75, 156, 227, 116, 193, 215, 38, 142, 22, 8,
1269 14, 229, 239, 119, 93, 5, 218, 161, 35, 3, 33, 0, 36, 100, 158, 252, 33, 161, 97, 185,
1270 62, 89, 99,
1271 ])
1272 .unwrap();
1273 let to = Pubkey::new(&[
1274 1, 1, 1, 4, 5, 6, 7, 8, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 8, 7, 6, 5, 4,
1275 1, 1, 1,
1276 ]);
1277
1278 let program_id = Pubkey::new(&[
1279 2, 2, 2, 4, 5, 6, 7, 8, 9, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9, 8, 7, 6, 5, 4,
1280 2, 2, 2,
1281 ]);
1282 let account_metas = vec![
1283 AccountMeta::new(keypair.pubkey(), true),
1284 AccountMeta::new(to, false),
1285 ];
1286 let instruction =
1287 Instruction::new_with_bincode(program_id, &(1u8, 2u8, 3u8), account_metas);
1288 let message = Message::new(&[instruction], Some(&keypair.pubkey()));
1289 Transaction::new(&[&keypair], message, Hash::default())
1290 }
1291
1292 #[test]
1293 fn test_transaction_serialize() {
1294 let tx = create_sample_transaction();
1295 let ser = serialize(&tx).unwrap();
1296 let deser = deserialize(&ser).unwrap();
1297 assert_eq!(tx, deser);
1298 }
1299
1300 /// Detect changes to the serialized size of payment transactions, which affects TPS.
1301 #[test]
1302 fn test_transaction_minimum_serialized_size() {
1303 let alice_keypair = Keypair::new();
1304 let alice_pubkey = alice_keypair.pubkey();
1305 let bob_pubkey = cbe_sdk::pubkey::new_rand();
1306 let ix = system_instruction::transfer(&alice_pubkey, &bob_pubkey, 42);
1307
1308 let expected_data_size = size_of::<u32>() + size_of::<u64>();
1309 assert_eq!(expected_data_size, 12);
1310 assert_eq!(
1311 ix.data.len(),
1312 expected_data_size,
1313 "unexpected system instruction size"
1314 );
1315
1316 let expected_instruction_size = 1 + 1 + ix.accounts.len() + 1 + expected_data_size;
1317 assert_eq!(expected_instruction_size, 17);
1318
1319 let message = Message::new(&[ix], Some(&alice_pubkey));
1320 assert_eq!(
1321 serialized_size(&message.instructions[0]).unwrap() as usize,
1322 expected_instruction_size,
1323 "unexpected Instruction::serialized_size"
1324 );
1325
1326 let tx = Transaction::new(&[&alice_keypair], message, Hash::default());
1327
1328 let len_size = 1;
1329 let num_required_sigs_size = 1;
1330 let num_readonly_accounts_size = 2;
1331 let blockhash_size = size_of::<Hash>();
1332 let expected_transaction_size = len_size
1333 + (tx.signatures.len() * size_of::<Signature>())
1334 + num_required_sigs_size
1335 + num_readonly_accounts_size
1336 + len_size
1337 + (tx.message.account_keys.len() * size_of::<Pubkey>())
1338 + blockhash_size
1339 + len_size
1340 + expected_instruction_size;
1341 assert_eq!(expected_transaction_size, 215);
1342
1343 assert_eq!(
1344 serialized_size(&tx).unwrap() as usize,
1345 expected_transaction_size,
1346 "unexpected serialized transaction size"
1347 );
1348 }
1349
1350 /// Detect binary changes in the serialized transaction data, which could have a downstream
1351 /// affect on SDKs and applications
1352 #[test]
1353 fn test_sdk_serialize() {
1354 assert_eq!(
1355 serialize(&create_sample_transaction()).unwrap(),
1356 vec![
1357 1, 71, 59, 9, 187, 190, 129, 150, 165, 21, 33, 158, 72, 87, 110, 144, 120, 79, 238,
1358 132, 134, 105, 39, 102, 116, 209, 29, 229, 154, 36, 105, 44, 172, 118, 131, 22,
1359 124, 131, 179, 142, 176, 27, 117, 160, 89, 102, 224, 204, 1, 252, 141, 2, 136, 0,
1360 37, 218, 225, 129, 92, 154, 250, 59, 97, 178, 10, 1, 0, 1, 3, 156, 227, 116, 193,
1361 215, 38, 142, 22, 8, 14, 229, 239, 119, 93, 5, 218, 161, 35, 3, 33, 0, 36, 100,
1362 158, 252, 33, 161, 97, 185, 62, 89, 99, 1, 1, 1, 4, 5, 6, 7, 8, 9, 9, 9, 9, 9, 9,
1363 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 8, 7, 6, 5, 4, 1, 1, 1, 2, 2, 2, 4, 5, 6, 7, 8, 9, 1,
1364 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9, 8, 7, 6, 5, 4, 2, 2, 2, 0, 0, 0, 0, 0, 0,
1365 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2,
1366 2, 0, 1, 3, 1, 2, 3
1367 ]
1368 );
1369 }
1370
1371 #[test]
1372 #[should_panic]
1373 fn test_transaction_missing_key() {
1374 let keypair = Keypair::new();
1375 let message = Message::new(&[], None);
1376 Transaction::new_unsigned(message).sign(&[&keypair], Hash::default());
1377 }
1378
1379 #[test]
1380 #[should_panic]
1381 fn test_partial_sign_mismatched_key() {
1382 let keypair = Keypair::new();
1383 let fee_payer = cbe_sdk::pubkey::new_rand();
1384 let ix = Instruction::new_with_bincode(
1385 Pubkey::default(),
1386 &0,
1387 vec![AccountMeta::new(fee_payer, true)],
1388 );
1389 let message = Message::new(&[ix], Some(&fee_payer));
1390 Transaction::new_unsigned(message).partial_sign(&[&keypair], Hash::default());
1391 }
1392
1393 #[test]
1394 fn test_partial_sign() {
1395 let keypair0 = Keypair::new();
1396 let keypair1 = Keypair::new();
1397 let keypair2 = Keypair::new();
1398 let ix = Instruction::new_with_bincode(
1399 Pubkey::default(),
1400 &0,
1401 vec![
1402 AccountMeta::new(keypair0.pubkey(), true),
1403 AccountMeta::new(keypair1.pubkey(), true),
1404 AccountMeta::new(keypair2.pubkey(), true),
1405 ],
1406 );
1407 let message = Message::new(&[ix], Some(&keypair0.pubkey()));
1408 let mut tx = Transaction::new_unsigned(message);
1409
1410 tx.partial_sign(&[&keypair0, &keypair2], Hash::default());
1411 assert!(!tx.is_signed());
1412 tx.partial_sign(&[&keypair1], Hash::default());
1413 assert!(tx.is_signed());
1414
1415 let hash = hash(&[1]);
1416 tx.partial_sign(&[&keypair1], hash);
1417 assert!(!tx.is_signed());
1418 tx.partial_sign(&[&keypair0, &keypair2], hash);
1419 assert!(tx.is_signed());
1420 }
1421
1422 #[test]
1423 #[should_panic]
1424 fn test_transaction_missing_keypair() {
1425 let program_id = Pubkey::default();
1426 let keypair0 = Keypair::new();
1427 let id0 = keypair0.pubkey();
1428 let ix = Instruction::new_with_bincode(program_id, &0, vec![AccountMeta::new(id0, true)]);
1429 let message = Message::new(&[ix], Some(&id0));
1430 Transaction::new_unsigned(message).sign(&Vec::<&Keypair>::new(), Hash::default());
1431 }
1432
1433 #[test]
1434 #[should_panic]
1435 fn test_transaction_wrong_key() {
1436 let program_id = Pubkey::default();
1437 let keypair0 = Keypair::new();
1438 let wrong_id = Pubkey::default();
1439 let ix =
1440 Instruction::new_with_bincode(program_id, &0, vec![AccountMeta::new(wrong_id, true)]);
1441 let message = Message::new(&[ix], Some(&wrong_id));
1442 Transaction::new_unsigned(message).sign(&[&keypair0], Hash::default());
1443 }
1444
1445 #[test]
1446 fn test_transaction_correct_key() {
1447 let program_id = Pubkey::default();
1448 let keypair0 = Keypair::new();
1449 let id0 = keypair0.pubkey();
1450 let ix = Instruction::new_with_bincode(program_id, &0, vec![AccountMeta::new(id0, true)]);
1451 let message = Message::new(&[ix], Some(&id0));
1452 let mut tx = Transaction::new_unsigned(message);
1453 tx.sign(&[&keypair0], Hash::default());
1454 assert_eq!(
1455 tx.message.instructions[0],
1456 CompiledInstruction::new(1, &0, vec![0])
1457 );
1458 assert!(tx.is_signed());
1459 }
1460
1461 #[test]
1462 fn test_transaction_instruction_with_duplicate_keys() {
1463 let program_id = Pubkey::default();
1464 let keypair0 = Keypair::new();
1465 let id0 = keypair0.pubkey();
1466 let id1 = cbe_sdk::pubkey::new_rand();
1467 let ix = Instruction::new_with_bincode(
1468 program_id,
1469 &0,
1470 vec![
1471 AccountMeta::new(id0, true),
1472 AccountMeta::new(id1, false),
1473 AccountMeta::new(id0, false),
1474 AccountMeta::new(id1, false),
1475 ],
1476 );
1477 let message = Message::new(&[ix], Some(&id0));
1478 let mut tx = Transaction::new_unsigned(message);
1479 tx.sign(&[&keypair0], Hash::default());
1480 assert_eq!(
1481 tx.message.instructions[0],
1482 CompiledInstruction::new(2, &0, vec![0, 1, 0, 1])
1483 );
1484 assert!(tx.is_signed());
1485 }
1486
1487 #[test]
1488 fn test_try_sign_dyn_keypairs() {
1489 let program_id = Pubkey::default();
1490 let keypair = Keypair::new();
1491 let pubkey = keypair.pubkey();
1492 let presigner_keypair = Keypair::new();
1493 let presigner_pubkey = presigner_keypair.pubkey();
1494
1495 let ix = Instruction::new_with_bincode(
1496 program_id,
1497 &0,
1498 vec![
1499 AccountMeta::new(pubkey, true),
1500 AccountMeta::new(presigner_pubkey, true),
1501 ],
1502 );
1503 let message = Message::new(&[ix], Some(&pubkey));
1504 let mut tx = Transaction::new_unsigned(message);
1505
1506 let presigner_sig = presigner_keypair.sign_message(&tx.message_data());
1507 let presigner = Presigner::new(&presigner_pubkey, &presigner_sig);
1508
1509 let signers: Vec<&dyn Signer> = vec![&keypair, &presigner];
1510
1511 let res = tx.try_sign(&signers, Hash::default());
1512 assert_eq!(res, Ok(()));
1513 assert_eq!(tx.signatures[0], keypair.sign_message(&tx.message_data()));
1514 assert_eq!(tx.signatures[1], presigner_sig);
1515
1516 // Wrong key should error, not panic
1517 let another_pubkey = cbe_sdk::pubkey::new_rand();
1518 let ix = Instruction::new_with_bincode(
1519 program_id,
1520 &0,
1521 vec![
1522 AccountMeta::new(another_pubkey, true),
1523 AccountMeta::new(presigner_pubkey, true),
1524 ],
1525 );
1526 let message = Message::new(&[ix], Some(&another_pubkey));
1527 let mut tx = Transaction::new_unsigned(message);
1528
1529 let res = tx.try_sign(&signers, Hash::default());
1530 assert!(res.is_err());
1531 assert_eq!(
1532 tx.signatures,
1533 vec![Signature::default(), Signature::default()]
1534 );
1535 }
1536
1537 fn nonced_transfer_tx() -> (Pubkey, Pubkey, Transaction) {
1538 let from_keypair = Keypair::new();
1539 let from_pubkey = from_keypair.pubkey();
1540 let nonce_keypair = Keypair::new();
1541 let nonce_pubkey = nonce_keypair.pubkey();
1542 let instructions = [
1543 system_instruction::advance_nonce_account(&nonce_pubkey, &nonce_pubkey),
1544 system_instruction::transfer(&from_pubkey, &nonce_pubkey, 42),
1545 ];
1546 let message = Message::new(&instructions, Some(&nonce_pubkey));
1547 let tx = Transaction::new(&[&from_keypair, &nonce_keypair], message, Hash::default());
1548 (from_pubkey, nonce_pubkey, tx)
1549 }
1550
1551 #[test]
1552 fn tx_uses_nonce_ok() {
1553 let (_, _, tx) = nonced_transfer_tx();
1554 assert!(uses_durable_nonce(&tx).is_some());
1555 }
1556
1557 #[test]
1558 fn tx_uses_nonce_empty_ix_fail() {
1559 assert!(uses_durable_nonce(&Transaction::default()).is_none());
1560 }
1561
1562 #[test]
1563 fn tx_uses_nonce_bad_prog_id_idx_fail() {
1564 let (_, _, mut tx) = nonced_transfer_tx();
1565 tx.message.instructions.get_mut(0).unwrap().program_id_index = 255u8;
1566 assert!(uses_durable_nonce(&tx).is_none());
1567 }
1568
1569 #[test]
1570 fn tx_uses_nonce_first_prog_id_not_nonce_fail() {
1571 let from_keypair = Keypair::new();
1572 let from_pubkey = from_keypair.pubkey();
1573 let nonce_keypair = Keypair::new();
1574 let nonce_pubkey = nonce_keypair.pubkey();
1575 let instructions = [
1576 system_instruction::transfer(&from_pubkey, &nonce_pubkey, 42),
1577 system_instruction::advance_nonce_account(&nonce_pubkey, &nonce_pubkey),
1578 ];
1579 let message = Message::new(&instructions, Some(&from_pubkey));
1580 let tx = Transaction::new(&[&from_keypair, &nonce_keypair], message, Hash::default());
1581 assert!(uses_durable_nonce(&tx).is_none());
1582 }
1583
1584 #[test]
1585 fn tx_uses_ro_nonce_account() {
1586 let from_keypair = Keypair::new();
1587 let from_pubkey = from_keypair.pubkey();
1588 let nonce_keypair = Keypair::new();
1589 let nonce_pubkey = nonce_keypair.pubkey();
1590 let account_metas = vec![
1591 AccountMeta::new_readonly(nonce_pubkey, false),
1592 #[allow(deprecated)]
1593 AccountMeta::new_readonly(sysvar::recent_blockhashes::id(), false),
1594 AccountMeta::new_readonly(nonce_pubkey, true),
1595 ];
1596 let nonce_instruction = Instruction::new_with_bincode(
1597 system_program::id(),
1598 &system_instruction::SystemInstruction::AdvanceNonceAccount,
1599 account_metas,
1600 );
1601 let tx = Transaction::new_signed_with_payer(
1602 &[nonce_instruction],
1603 Some(&from_pubkey),
1604 &[&from_keypair, &nonce_keypair],
1605 Hash::default(),
1606 );
1607 assert!(uses_durable_nonce(&tx).is_none());
1608 }
1609
1610 #[test]
1611 fn tx_uses_nonce_wrong_first_nonce_ix_fail() {
1612 let from_keypair = Keypair::new();
1613 let from_pubkey = from_keypair.pubkey();
1614 let nonce_keypair = Keypair::new();
1615 let nonce_pubkey = nonce_keypair.pubkey();
1616 let instructions = [
1617 system_instruction::withdraw_nonce_account(
1618 &nonce_pubkey,
1619 &nonce_pubkey,
1620 &from_pubkey,
1621 42,
1622 ),
1623 system_instruction::transfer(&from_pubkey, &nonce_pubkey, 42),
1624 ];
1625 let message = Message::new(&instructions, Some(&nonce_pubkey));
1626 let tx = Transaction::new(&[&from_keypair, &nonce_keypair], message, Hash::default());
1627 assert!(uses_durable_nonce(&tx).is_none());
1628 }
1629
1630 #[test]
1631 fn get_nonce_pub_from_ix_ok() {
1632 let (_, nonce_pubkey, tx) = nonced_transfer_tx();
1633 let nonce_ix = uses_durable_nonce(&tx).unwrap();
1634 assert_eq!(
1635 get_nonce_pubkey_from_instruction(nonce_ix, &tx),
1636 Some(&nonce_pubkey),
1637 );
1638 }
1639
1640 #[test]
1641 fn get_nonce_pub_from_ix_no_accounts_fail() {
1642 let (_, _, tx) = nonced_transfer_tx();
1643 let nonce_ix = uses_durable_nonce(&tx).unwrap();
1644 let mut nonce_ix = nonce_ix.clone();
1645 nonce_ix.accounts.clear();
1646 assert_eq!(get_nonce_pubkey_from_instruction(&nonce_ix, &tx), None,);
1647 }
1648
1649 #[test]
1650 fn get_nonce_pub_from_ix_bad_acc_idx_fail() {
1651 let (_, _, tx) = nonced_transfer_tx();
1652 let nonce_ix = uses_durable_nonce(&tx).unwrap();
1653 let mut nonce_ix = nonce_ix.clone();
1654 nonce_ix.accounts[0] = 255u8;
1655 assert_eq!(get_nonce_pubkey_from_instruction(&nonce_ix, &tx), None,);
1656 }
1657
1658 #[test]
1659 fn tx_keypair_pubkey_mismatch() {
1660 let from_keypair = Keypair::new();
1661 let from_pubkey = from_keypair.pubkey();
1662 let to_pubkey = Pubkey::new_unique();
1663 let instructions = [system_instruction::transfer(&from_pubkey, &to_pubkey, 42)];
1664 let mut tx = Transaction::new_with_payer(&instructions, Some(&from_pubkey));
1665 let unused_keypair = Keypair::new();
1666 let err = tx
1667 .try_partial_sign(&[&from_keypair, &unused_keypair], Hash::default())
1668 .unwrap_err();
1669 assert_eq!(err, SignerError::KeypairPubkeyMismatch);
1670 }
1671}