#![cfg_attr(feature = "frozen-abi", feature(min_specialization))]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#[cfg(feature = "serde")]
use {
serde_derive::{Deserialize, Serialize},
solana_short_vec as short_vec,
};
pub use {
solana_address::Address,
solana_instruction::{AccountMeta, Instruction},
solana_instruction_error::InstructionError,
solana_message::{compiled_instruction::CompiledInstruction, Message, VersionedMessage},
solana_signature::Signature,
solana_transaction_error::{TransactionError, TransactionResult},
};
#[cfg(feature = "bincode")]
pub use {
solana_hash::Hash,
solana_signer::{signers::Signers, SignerError},
};
use {
solana_message::inline_nonce::is_advance_nonce_instruction_data,
solana_sanitize::{Sanitize, SanitizeError},
solana_sdk_ids::system_program,
std::result,
};
pub mod sanitized;
pub mod simple_vote_transaction_checker;
pub mod versioned;
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub enum TransactionVerificationMode {
HashOnly,
HashAndVerifyPrecompiles,
FullVerification,
}
#[cfg(test)]
static_assertions::const_assert_eq!(
NONCED_TX_MARKER_IX_INDEX,
solana_nonce::NONCED_TX_MARKER_IX_INDEX
);
const NONCED_TX_MARKER_IX_INDEX: u8 = 0;
#[cfg_attr(
feature = "frozen-abi",
derive(solana_frozen_abi_macro::AbiExample),
solana_frozen_abi_macro::frozen_abi(digest = "KSndwV1Ezw3xDX3Mz4Sg2vY22dx9mGTCFzo1RxbwaV8")
)]
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
#[derive(Debug, PartialEq, Default, Eq, Clone)]
pub struct Transaction {
#[cfg_attr(feature = "serde", serde(with = "short_vec"))]
pub signatures: Vec<Signature>,
pub message: Message,
}
impl Sanitize for Transaction {
fn sanitize(&self) -> result::Result<(), SanitizeError> {
if self.message.header.num_required_signatures as usize > self.signatures.len() {
return Err(SanitizeError::IndexOutOfBounds);
}
if self.signatures.len() > self.message.account_keys.len() {
return Err(SanitizeError::IndexOutOfBounds);
}
self.message.sanitize()
}
}
impl Transaction {
pub fn new_unsigned(message: Message) -> Self {
Self {
signatures: vec![Signature::default(); message.header.num_required_signatures as usize],
message,
}
}
#[cfg(feature = "bincode")]
pub fn new<T: Signers + ?Sized>(
from_keypairs: &T,
message: Message,
recent_blockhash: Hash,
) -> Transaction {
let mut tx = Self::new_unsigned(message);
tx.sign(from_keypairs, recent_blockhash);
tx
}
pub fn new_with_payer(instructions: &[Instruction], payer: Option<&Address>) -> Self {
let message = Message::new(instructions, payer);
Self::new_unsigned(message)
}
#[cfg(feature = "bincode")]
pub fn new_signed_with_payer<T: Signers + ?Sized>(
instructions: &[Instruction],
payer: Option<&Address>,
signing_keypairs: &T,
recent_blockhash: Hash,
) -> Self {
let message = Message::new(instructions, payer);
Self::new(signing_keypairs, message, recent_blockhash)
}
#[cfg(feature = "bincode")]
pub fn new_with_compiled_instructions<T: Signers + ?Sized>(
from_keypairs: &T,
keys: &[Address],
recent_blockhash: Hash,
program_ids: Vec<Address>,
instructions: Vec<CompiledInstruction>,
) -> Self {
let mut account_keys = from_keypairs.pubkeys();
let from_keypairs_len = account_keys.len();
account_keys.extend_from_slice(keys);
account_keys.extend(&program_ids);
let message = Message::new_with_compiled_instructions(
from_keypairs_len as u8,
0,
program_ids.len() as u8,
account_keys,
Hash::default(),
instructions,
);
Transaction::new(from_keypairs, message, recent_blockhash)
}
pub fn data(&self, instruction_index: usize) -> &[u8] {
&self.message.instructions[instruction_index].data
}
fn key_index(&self, instruction_index: usize, accounts_index: usize) -> Option<usize> {
self.message
.instructions
.get(instruction_index)
.and_then(|instruction| instruction.accounts.get(accounts_index))
.map(|&account_keys_index| account_keys_index as usize)
}
pub fn key(&self, instruction_index: usize, accounts_index: usize) -> Option<&Address> {
self.key_index(instruction_index, accounts_index)
.and_then(|account_keys_index| self.message.account_keys.get(account_keys_index))
}
pub fn signer_key(&self, instruction_index: usize, accounts_index: usize) -> Option<&Address> {
match self.key_index(instruction_index, accounts_index) {
None => None,
Some(signature_index) => {
if signature_index >= self.signatures.len() {
return None;
}
self.message.account_keys.get(signature_index)
}
}
}
pub fn message(&self) -> &Message {
&self.message
}
#[cfg(feature = "bincode")]
pub fn message_data(&self) -> Vec<u8> {
self.message().serialize()
}
#[cfg(feature = "bincode")]
pub fn sign<T: Signers + ?Sized>(&mut self, keypairs: &T, recent_blockhash: Hash) {
if let Err(e) = self.try_sign(keypairs, recent_blockhash) {
panic!("Transaction::sign failed with error {e:?}");
}
}
#[cfg(feature = "bincode")]
pub fn partial_sign<T: Signers + ?Sized>(&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:?}");
}
}
#[cfg(feature = "bincode")]
pub fn partial_sign_unchecked<T: Signers + ?Sized>(
&mut self,
keypairs: &T,
positions: Vec<usize>,
recent_blockhash: Hash,
) {
if let Err(e) = self.try_partial_sign_unchecked(keypairs, positions, recent_blockhash) {
panic!("Transaction::partial_sign_unchecked failed with error {e:?}");
}
}
#[cfg(feature = "bincode")]
pub fn try_sign<T: Signers + ?Sized>(
&mut self,
keypairs: &T,
recent_blockhash: Hash,
) -> result::Result<(), SignerError> {
self.try_partial_sign(keypairs, recent_blockhash)?;
if !self.is_signed() {
Err(SignerError::NotEnoughSigners)
} else {
Ok(())
}
}
#[cfg(feature = "bincode")]
pub fn try_partial_sign<T: Signers + ?Sized>(
&mut self,
keypairs: &T,
recent_blockhash: Hash,
) -> result::Result<(), SignerError> {
let positions: Vec<usize> = self
.get_signing_keypair_positions(&keypairs.pubkeys())?
.into_iter()
.collect::<Option<_>>()
.ok_or(SignerError::KeypairPubkeyMismatch)?;
self.try_partial_sign_unchecked(keypairs, positions, recent_blockhash)
}
#[cfg(feature = "bincode")]
pub fn try_partial_sign_unchecked<T: Signers + ?Sized>(
&mut self,
keypairs: &T,
positions: Vec<usize>,
recent_blockhash: Hash,
) -> result::Result<(), SignerError> {
if recent_blockhash != self.message.recent_blockhash {
self.message.recent_blockhash = recent_blockhash;
self.signatures
.iter_mut()
.for_each(|signature| *signature = Signature::default());
}
let signatures = keypairs.try_sign_message(&self.message_data())?;
for i in 0..positions.len() {
self.signatures[positions[i]] = signatures[i];
}
Ok(())
}
pub fn get_invalid_signature() -> Signature {
Signature::default()
}
#[cfg(feature = "verify")]
pub fn verify(&self) -> TransactionResult<()> {
let message_bytes = self.message_data();
if !self
._verify_with_results(&message_bytes)
.iter()
.all(|verify_result| *verify_result)
{
Err(TransactionError::SignatureFailure)
} else {
Ok(())
}
}
#[cfg(feature = "verify")]
pub fn verify_and_hash_message(&self) -> TransactionResult<Hash> {
let message_bytes = self.message_data();
if !self
._verify_with_results(&message_bytes)
.iter()
.all(|verify_result| *verify_result)
{
Err(TransactionError::SignatureFailure)
} else {
Ok(Message::hash_raw_message(&message_bytes))
}
}
#[cfg(feature = "verify")]
pub fn verify_with_results(&self) -> Vec<bool> {
self._verify_with_results(&self.message_data())
}
#[cfg(feature = "verify")]
pub(crate) fn _verify_with_results(&self, message_bytes: &[u8]) -> Vec<bool> {
self.signatures
.iter()
.zip(&self.message.account_keys)
.map(|(signature, pubkey)| signature.verify(pubkey.as_ref(), message_bytes))
.collect()
}
pub fn get_signing_keypair_positions(
&self,
pubkeys: &[Address],
) -> TransactionResult<Vec<Option<usize>>> {
if self.message.account_keys.len() < self.message.header.num_required_signatures as usize {
return Err(TransactionError::InvalidAccountIndex);
}
let signed_keys =
&self.message.account_keys[0..self.message.header.num_required_signatures as usize];
Ok(pubkeys
.iter()
.map(|pubkey| signed_keys.iter().position(|x| x == pubkey))
.collect())
}
#[cfg(feature = "verify")]
pub fn replace_signatures(
&mut self,
signers: &[(Address, Signature)],
) -> TransactionResult<()> {
let num_required_signatures = self.message.header.num_required_signatures as usize;
if signers.len() != num_required_signatures
|| self.signatures.len() != num_required_signatures
|| self.message.account_keys.len() < num_required_signatures
{
return Err(TransactionError::InvalidAccountIndex);
}
for (index, account_key) in self
.message
.account_keys
.iter()
.enumerate()
.take(num_required_signatures)
{
if let Some((_pubkey, signature)) =
signers.iter().find(|(key, _signature)| account_key == key)
{
self.signatures[index] = *signature
} else {
return Err(TransactionError::InvalidAccountIndex);
}
}
self.verify()
}
pub fn is_signed(&self) -> bool {
self.signatures
.iter()
.all(|signature| *signature != Signature::default())
}
}
pub fn uses_durable_nonce(tx: &Transaction) -> Option<&CompiledInstruction> {
let message = tx.message();
message
.instructions
.get(NONCED_TX_MARKER_IX_INDEX as usize)
.filter(|instruction| {
matches!(
message.account_keys.get(instruction.program_id_index as usize),
Some(program_id) if system_program::check_id(program_id)
) && is_advance_nonce_instruction_data(&instruction.data)
})
}
#[cfg(test)]
mod tests {
#![allow(deprecated)]
use {
super::*,
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,
};
fn get_program_id(tx: &Transaction, instruction_index: usize) -> &Address {
let message = tx.message();
let instruction = &message.instructions[instruction_index];
instruction.program_id(&message.account_keys)
}
#[test]
fn test_refs() {
let key = Keypair::new();
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]),
];
let tx = Transaction::new_with_compiled_instructions(
&[&key],
&[key1, key2],
Hash::default(),
vec![prog1, prog2],
instructions,
);
assert!(tx.sanitize().is_ok());
assert_eq!(tx.key(0, 0), Some(&key.pubkey()));
assert_eq!(tx.signer_key(0, 0), Some(&key.pubkey()));
assert_eq!(tx.key(1, 0), Some(&key.pubkey()));
assert_eq!(tx.signer_key(1, 0), Some(&key.pubkey()));
assert_eq!(tx.key(0, 1), Some(&key1));
assert_eq!(tx.signer_key(0, 1), None);
assert_eq!(tx.key(1, 1), Some(&key2));
assert_eq!(tx.signer_key(1, 1), None);
assert_eq!(tx.key(2, 0), None);
assert_eq!(tx.signer_key(2, 0), None);
assert_eq!(tx.key(0, 2), None);
assert_eq!(tx.signer_key(0, 2), None);
assert_eq!(*get_program_id(&tx, 0), prog1);
assert_eq!(*get_program_id(&tx, 1), prog2);
}
#[test]
fn test_refs_invalid_program_id() {
let key = Keypair::new();
let instructions = vec![CompiledInstruction::new(1, &(), vec![])];
let tx = Transaction::new_with_compiled_instructions(
&[&key],
&[],
Hash::default(),
vec![],
instructions,
);
assert_eq!(tx.sanitize(), Err(SanitizeError::IndexOutOfBounds));
}
#[test]
fn test_refs_invalid_account() {
let key = Keypair::new();
let instructions = vec![CompiledInstruction::new(1, &(), vec![2])];
let tx = Transaction::new_with_compiled_instructions(
&[&key],
&[],
Hash::default(),
vec![Address::default()],
instructions,
);
assert_eq!(*get_program_id(&tx, 0), Address::default());
assert_eq!(tx.sanitize(), Err(SanitizeError::IndexOutOfBounds));
}
#[test]
fn test_sanitize_txs() {
let key = Keypair::new();
let id0 = Address::default();
let program_id = solana_pubkey::new_rand();
let ix = Instruction::new_with_bincode(
program_id,
&0,
vec![
AccountMeta::new(key.pubkey(), true),
AccountMeta::new(id0, true),
],
);
let mut tx = Transaction::new_with_payer(&[ix], Some(&key.pubkey()));
let o = tx.clone();
assert_eq!(tx.sanitize(), Ok(()));
assert_eq!(tx.message.account_keys.len(), 3);
tx = o.clone();
tx.message.header.num_required_signatures = 3;
assert_eq!(tx.sanitize(), Err(SanitizeError::IndexOutOfBounds));
tx = o.clone();
tx.message.header.num_readonly_signed_accounts = 4;
tx.message.header.num_readonly_unsigned_accounts = 0;
assert_eq!(tx.sanitize(), Err(SanitizeError::IndexOutOfBounds));
tx = o.clone();
tx.message.header.num_readonly_signed_accounts = 2;
tx.message.header.num_readonly_unsigned_accounts = 2;
assert_eq!(tx.sanitize(), Err(SanitizeError::IndexOutOfBounds));
tx = o.clone();
tx.message.header.num_readonly_signed_accounts = 0;
tx.message.header.num_readonly_unsigned_accounts = 4;
assert_eq!(tx.sanitize(), Err(SanitizeError::IndexOutOfBounds));
tx = o.clone();
tx.message.instructions[0].program_id_index = 3;
assert_eq!(tx.sanitize(), Err(SanitizeError::IndexOutOfBounds));
tx = o.clone();
tx.message.instructions[0].accounts[0] = 3;
assert_eq!(tx.sanitize(), Err(SanitizeError::IndexOutOfBounds));
tx = o.clone();
tx.message.instructions[0].program_id_index = 0;
assert_eq!(tx.sanitize(), Err(SanitizeError::IndexOutOfBounds));
tx = o.clone();
tx.message.header.num_readonly_signed_accounts = 2;
tx.message.header.num_readonly_unsigned_accounts = 3;
tx.message.account_keys.resize(4, Address::default());
assert_eq!(tx.sanitize(), Err(SanitizeError::IndexOutOfBounds));
tx = o;
tx.message.header.num_readonly_signed_accounts = 2;
tx.message.header.num_required_signatures = 1;
assert_eq!(tx.sanitize(), Err(SanitizeError::IndexOutOfBounds));
}
fn create_sample_transaction() -> Transaction {
let keypair = Keypair::try_from(
[
255, 101, 36, 24, 124, 23, 167, 21, 132, 204, 155, 5, 185, 58, 121, 75, 156, 227,
116, 193, 215, 38, 142, 22, 8, 14, 229, 239, 119, 93, 5, 218, 36, 100, 158, 252,
33, 161, 97, 185, 62, 89, 99, 195, 250, 249, 187, 189, 171, 118, 241, 90, 248, 14,
68, 219, 231, 62, 157, 5, 142, 27, 210, 117,
]
.as_ref(),
)
.unwrap();
let to = Address::from([
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,
1, 1, 1,
]);
let program_id = Address::from([
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,
2, 2, 2,
]);
let account_metas = vec![
AccountMeta::new(keypair.pubkey(), true),
AccountMeta::new(to, false),
];
let instruction =
Instruction::new_with_bincode(program_id, &(1u8, 2u8, 3u8), account_metas);
let message = Message::new(&[instruction], Some(&keypair.pubkey()));
let tx = Transaction::new(&[&keypair], message, Hash::default());
tx.verify().expect("valid sample transaction signatures");
tx
}
#[test]
fn test_transaction_serialize() {
let tx = create_sample_transaction();
let ser = serialize(&tx).unwrap();
let deser = deserialize(&ser).unwrap();
assert_eq!(tx, deser);
}
#[test]
fn test_transaction_minimum_serialized_size() {
let alice_keypair = Keypair::new();
let alice_pubkey = alice_keypair.pubkey();
let bob_pubkey = solana_pubkey::new_rand();
let ix = system_instruction::transfer(&alice_pubkey, &bob_pubkey, 42);
let expected_data_size = size_of::<u32>() + size_of::<u64>();
assert_eq!(expected_data_size, 12);
assert_eq!(
ix.data.len(),
expected_data_size,
"unexpected system instruction size"
);
let expected_instruction_size = 1 + 1 + ix.accounts.len() + 1 + expected_data_size;
assert_eq!(expected_instruction_size, 17);
let message = Message::new(&[ix], Some(&alice_pubkey));
assert_eq!(
serialized_size(&message.instructions[0]).unwrap() as usize,
expected_instruction_size,
"unexpected Instruction::serialized_size"
);
let tx = Transaction::new(&[&alice_keypair], message, Hash::default());
let len_size = 1;
let num_required_sigs_size = 1;
let num_readonly_accounts_size = 2;
let blockhash_size = size_of::<Hash>();
let expected_transaction_size = len_size
+ (tx.signatures.len() * size_of::<Signature>())
+ num_required_sigs_size
+ num_readonly_accounts_size
+ len_size
+ (tx.message.account_keys.len() * size_of::<Address>())
+ blockhash_size
+ len_size
+ expected_instruction_size;
assert_eq!(expected_transaction_size, 215);
assert_eq!(
serialized_size(&tx).unwrap() as usize,
expected_transaction_size,
"unexpected serialized transaction size"
);
}
#[test]
fn test_sdk_serialize() {
assert_eq!(
serialize(&create_sample_transaction()).unwrap(),
vec![
1, 120, 138, 162, 185, 59, 209, 241, 157, 71, 157, 74, 131, 4, 87, 54, 28, 38, 180,
222, 82, 64, 62, 61, 62, 22, 46, 17, 203, 187, 136, 62, 43, 11, 38, 235, 17, 239,
82, 240, 139, 130, 217, 227, 214, 9, 242, 141, 223, 94, 29, 184, 110, 62, 32, 87,
137, 63, 139, 100, 221, 20, 137, 4, 5, 1, 0, 1, 3, 36, 100, 158, 252, 33, 161, 97,
185, 62, 89, 99, 195, 250, 249, 187, 189, 171, 118, 241, 90, 248, 14, 68, 219, 231,
62, 157, 5, 142, 27, 210, 117, 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, 1, 1, 1, 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, 2, 2, 2, 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, 0, 0, 0, 0, 0, 0, 1, 2, 2, 0, 1,
3, 1, 2, 3
]
);
}
#[test]
#[should_panic]
fn test_transaction_missing_key() {
let keypair = Keypair::new();
let message = Message::new(&[], None);
Transaction::new_unsigned(message).sign(&[&keypair], Hash::default());
}
#[test]
#[should_panic]
fn test_partial_sign_mismatched_key() {
let keypair = Keypair::new();
let fee_payer = solana_pubkey::new_rand();
let ix = Instruction::new_with_bincode(
Address::default(),
&0,
vec![AccountMeta::new(fee_payer, true)],
);
let message = Message::new(&[ix], Some(&fee_payer));
Transaction::new_unsigned(message).partial_sign(&[&keypair], Hash::default());
}
#[test]
fn test_partial_sign() {
let keypair0 = Keypair::new();
let keypair1 = Keypair::new();
let keypair2 = Keypair::new();
let ix = Instruction::new_with_bincode(
Address::default(),
&0,
vec![
AccountMeta::new(keypair0.pubkey(), true),
AccountMeta::new(keypair1.pubkey(), true),
AccountMeta::new(keypair2.pubkey(), true),
],
);
let message = Message::new(&[ix], Some(&keypair0.pubkey()));
let mut tx = Transaction::new_unsigned(message);
tx.partial_sign(&[&keypair0, &keypair2], Hash::default());
assert!(!tx.is_signed());
tx.partial_sign(&[&keypair1], Hash::default());
assert!(tx.is_signed());
let hash = hash(&[1]);
tx.partial_sign(&[&keypair1], hash);
assert!(!tx.is_signed());
tx.partial_sign(&[&keypair0, &keypair2], hash);
assert!(tx.is_signed());
}
#[test]
#[should_panic]
fn test_transaction_missing_keypair() {
let program_id = Address::default();
let keypair0 = Keypair::new();
let id0 = keypair0.pubkey();
let ix = Instruction::new_with_bincode(program_id, &0, vec![AccountMeta::new(id0, true)]);
let message = Message::new(&[ix], Some(&id0));
Transaction::new_unsigned(message).sign(&Vec::<&Keypair>::new(), Hash::default());
}
#[test]
#[should_panic]
fn test_transaction_wrong_key() {
let program_id = Address::default();
let keypair0 = Keypair::new();
let wrong_id = Address::default();
let ix =
Instruction::new_with_bincode(program_id, &0, vec![AccountMeta::new(wrong_id, true)]);
let message = Message::new(&[ix], Some(&wrong_id));
Transaction::new_unsigned(message).sign(&[&keypair0], Hash::default());
}
#[test]
fn test_transaction_correct_key() {
let program_id = Address::default();
let keypair0 = Keypair::new();
let id0 = keypair0.pubkey();
let ix = Instruction::new_with_bincode(program_id, &0, vec![AccountMeta::new(id0, true)]);
let message = Message::new(&[ix], Some(&id0));
let mut tx = Transaction::new_unsigned(message);
tx.sign(&[&keypair0], Hash::default());
assert_eq!(
tx.message.instructions[0],
CompiledInstruction::new(1, &0, vec![0])
);
assert!(tx.is_signed());
}
#[test]
fn test_transaction_instruction_with_duplicate_keys() {
let program_id = Address::default();
let keypair0 = Keypair::new();
let id0 = keypair0.pubkey();
let id1 = solana_pubkey::new_rand();
let ix = Instruction::new_with_bincode(
program_id,
&0,
vec![
AccountMeta::new(id0, true),
AccountMeta::new(id1, false),
AccountMeta::new(id0, false),
AccountMeta::new(id1, false),
],
);
let message = Message::new(&[ix], Some(&id0));
let mut tx = Transaction::new_unsigned(message);
tx.sign(&[&keypair0], Hash::default());
assert_eq!(
tx.message.instructions[0],
CompiledInstruction::new(2, &0, vec![0, 1, 0, 1])
);
assert!(tx.is_signed());
}
#[test]
fn test_try_sign_dyn_keypairs() {
let program_id = Address::default();
let keypair = Keypair::new();
let pubkey = keypair.pubkey();
let presigner_keypair = Keypair::new();
let presigner_pubkey = presigner_keypair.pubkey();
let ix = Instruction::new_with_bincode(
program_id,
&0,
vec![
AccountMeta::new(pubkey, true),
AccountMeta::new(presigner_pubkey, true),
],
);
let message = Message::new(&[ix], Some(&pubkey));
let mut tx = Transaction::new_unsigned(message);
let presigner_sig = presigner_keypair.sign_message(&tx.message_data());
let presigner = Presigner::new(&presigner_pubkey, &presigner_sig);
let signers: Vec<&dyn Signer> = vec![&keypair, &presigner];
let res = tx.try_sign(&signers, Hash::default());
assert_eq!(res, Ok(()));
assert_eq!(tx.signatures[0], keypair.sign_message(&tx.message_data()));
assert_eq!(tx.signatures[1], presigner_sig);
let another_pubkey = solana_pubkey::new_rand();
let ix = Instruction::new_with_bincode(
program_id,
&0,
vec![
AccountMeta::new(another_pubkey, true),
AccountMeta::new(presigner_pubkey, true),
],
);
let message = Message::new(&[ix], Some(&another_pubkey));
let mut tx = Transaction::new_unsigned(message);
let res = tx.try_sign(&signers, Hash::default());
assert!(res.is_err());
assert_eq!(
tx.signatures,
vec![Signature::default(), Signature::default()]
);
}
fn nonced_transfer_tx() -> (Address, Address, Transaction) {
let from_keypair = Keypair::new();
let from_pubkey = from_keypair.pubkey();
let nonce_keypair = Keypair::new();
let nonce_pubkey = nonce_keypair.pubkey();
let instructions = [
system_instruction::advance_nonce_account(&nonce_pubkey, &nonce_pubkey),
system_instruction::transfer(&from_pubkey, &nonce_pubkey, 42),
];
let message = Message::new(&instructions, Some(&nonce_pubkey));
let tx = Transaction::new(&[&from_keypair, &nonce_keypair], message, Hash::default());
(from_pubkey, nonce_pubkey, tx)
}
#[test]
fn tx_uses_nonce_ok() {
let (_, _, tx) = nonced_transfer_tx();
assert!(uses_durable_nonce(&tx).is_some());
}
#[test]
fn tx_uses_nonce_empty_ix_fail() {
assert!(uses_durable_nonce(&Transaction::default()).is_none());
}
#[test]
fn tx_uses_nonce_bad_prog_id_idx_fail() {
let (_, _, mut tx) = nonced_transfer_tx();
tx.message.instructions.get_mut(0).unwrap().program_id_index = 255u8;
assert!(uses_durable_nonce(&tx).is_none());
}
#[test]
fn tx_uses_nonce_first_prog_id_not_nonce_fail() {
let from_keypair = Keypair::new();
let from_pubkey = from_keypair.pubkey();
let nonce_keypair = Keypair::new();
let nonce_pubkey = nonce_keypair.pubkey();
let instructions = [
system_instruction::transfer(&from_pubkey, &nonce_pubkey, 42),
system_instruction::advance_nonce_account(&nonce_pubkey, &nonce_pubkey),
];
let message = Message::new(&instructions, Some(&from_pubkey));
let tx = Transaction::new(&[&from_keypair, &nonce_keypair], message, Hash::default());
assert!(uses_durable_nonce(&tx).is_none());
}
#[test]
fn tx_uses_nonce_wrong_first_nonce_ix_fail() {
let from_keypair = Keypair::new();
let from_pubkey = from_keypair.pubkey();
let nonce_keypair = Keypair::new();
let nonce_pubkey = nonce_keypair.pubkey();
let instructions = [
system_instruction::withdraw_nonce_account(
&nonce_pubkey,
&nonce_pubkey,
&from_pubkey,
42,
),
system_instruction::transfer(&from_pubkey, &nonce_pubkey, 42),
];
let message = Message::new(&instructions, Some(&nonce_pubkey));
let tx = Transaction::new(&[&from_keypair, &nonce_keypair], message, Hash::default());
assert!(uses_durable_nonce(&tx).is_none());
}
#[test]
fn tx_keypair_pubkey_mismatch() {
let from_keypair = Keypair::new();
let from_pubkey = from_keypair.pubkey();
let to_pubkey = Address::new_unique();
let instructions = [system_instruction::transfer(&from_pubkey, &to_pubkey, 42)];
let mut tx = Transaction::new_with_payer(&instructions, Some(&from_pubkey));
let unused_keypair = Keypair::new();
let err = tx
.try_partial_sign(&[&from_keypair, &unused_keypair], Hash::default())
.unwrap_err();
assert_eq!(err, SignerError::KeypairPubkeyMismatch);
}
#[test]
fn test_unsized_signers() {
fn instructions_to_tx(
instructions: &[Instruction],
signers: Box<dyn Signers>,
) -> Transaction {
let pubkeys = signers.pubkeys();
let first_signer = pubkeys.first().expect("should exist");
let message = Message::new(instructions, Some(first_signer));
Transaction::new(signers.as_ref(), message, Hash::default())
}
let signer: Box<dyn Signer> = Box::new(Keypair::new());
let tx = instructions_to_tx(&[], Box::new(vec![signer]));
assert!(tx.is_signed());
}
#[test]
fn test_replace_signatures() {
let program_id = Address::default();
let keypair0 = Keypair::new();
let keypair1 = Keypair::new();
let pubkey0 = keypair0.pubkey();
let pubkey1 = keypair1.pubkey();
let ix = Instruction::new_with_bincode(
program_id,
&0,
vec![
AccountMeta::new(pubkey0, true),
AccountMeta::new(pubkey1, true),
],
);
let message = Message::new(&[ix], Some(&pubkey0));
let expected_account_keys = message.account_keys.clone();
let mut tx = Transaction::new_unsigned(message);
tx.sign(&[&keypair0, &keypair1], Hash::new_unique());
let signature0 = keypair0.sign_message(&tx.message_data());
let signature1 = keypair1.sign_message(&tx.message_data());
tx.replace_signatures(&[(pubkey1, signature1), (pubkey0, signature0)])
.unwrap();
assert_eq!(tx.message.account_keys, expected_account_keys);
assert_eq!(tx.signatures, &[signature0, signature1]);
}
}