Add `SanitizedTransaction` builder to SVM (#442)

Add SanitizedTransaction builder
This commit is contained in:
Lucas Steuernagel 2024-03-27 10:00:04 -03:00 committed by GitHub
parent 80d3200f4a
commit 9cd90751f0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 195 additions and 95 deletions

View File

@ -1,7 +1,7 @@
#![cfg(test)]
use {
crate::mock_bank::MockBankCallback,
crate::{mock_bank::MockBankCallback, transaction_builder::SanitizedTransactionBuilder},
solana_bpf_loader_program::syscalls::{
SyscallAbort, SyscallGetClockSysvar, SyscallInvokeSignedRust, SyscallLog, SyscallMemcpy,
SyscallMemset, SyscallSetReturnData,
@ -26,13 +26,12 @@ use {
epoch_schedule::EpochSchedule,
fee::FeeStructure,
hash::Hash,
instruction::CompiledInstruction,
message::{Message, MessageHeader},
instruction::AccountMeta,
native_loader,
pubkey::Pubkey,
signature::Signature,
sysvar::SysvarId,
transaction::{SanitizedTransaction, Transaction, TransactionError},
transaction::{SanitizedTransaction, TransactionError},
},
solana_svm::{
account_loader::TransactionCheckResult,
@ -44,6 +43,7 @@ use {
},
std::{
cmp::Ordering,
collections::HashMap,
env,
fs::{self, File},
io::Read,
@ -54,6 +54,7 @@ use {
// This module contains the implementation of TransactionProcessingCallback
mod mock_bank;
mod transaction_builder;
const BPF_LOADER_NAME: &str = "solana_bpf_loader_program";
const SYSTEM_PROGRAM_NAME: &str = "system_program";
@ -228,33 +229,18 @@ fn load_program(name: String) -> Vec<u8> {
fn prepare_transactions(
mock_bank: &mut MockBankCallback,
) -> (Vec<SanitizedTransaction>, Vec<TransactionCheckResult>) {
let mut transaction_builder = SanitizedTransactionBuilder::default();
let mut all_transactions = Vec::new();
let mut transaction_checks = Vec::new();
// A transaction that works without any account
let key1 = Pubkey::new_unique();
let fee_payer = Pubkey::new_unique();
let message = Message {
account_keys: vec![fee_payer, key1],
header: MessageHeader {
num_required_signatures: 1,
num_readonly_signed_accounts: 0,
num_readonly_unsigned_accounts: 0,
},
instructions: vec![CompiledInstruction {
program_id_index: 1,
accounts: vec![],
data: vec![],
}],
recent_blockhash: Hash::default(),
};
transaction_builder.create_instruction(key1, Vec::new(), HashMap::new(), Vec::new());
let transaction = Transaction {
signatures: vec![Signature::new_unique()],
message,
};
let sanitized_transaction =
SanitizedTransaction::try_from_legacy_transaction(transaction).unwrap();
transaction_builder.build(Hash::default(), (fee_payer, Signature::new_unique()));
all_transactions.push(sanitized_transaction);
transaction_checks.push((Ok(()), None, Some(20)));
@ -283,36 +269,32 @@ fn prepare_transactions(
let recipient = Pubkey::new_unique();
let fee_payer = Pubkey::new_unique();
let system_account = Pubkey::from([0u8; 32]);
let message = Message {
account_keys: vec![
fee_payer,
sender,
transfer_program_account,
recipient,
system_account,
],
header: MessageHeader {
// The signers must appear in the `account_keys` vector in positions whose index is
// less than `num_required_signatures`
num_required_signatures: 2,
num_readonly_signed_accounts: 0,
num_readonly_unsigned_accounts: 0,
},
instructions: vec![CompiledInstruction {
program_id_index: 2,
accounts: vec![1, 3, 4],
data: vec![0, 0, 0, 0, 0, 0, 0, 10],
}],
recent_blockhash: Hash::default(),
};
let transaction = Transaction {
signatures: vec![Signature::new_unique(), Signature::new_unique()],
message,
};
transaction_builder.create_instruction(
transfer_program_account,
vec![
AccountMeta {
pubkey: sender,
is_signer: true,
is_writable: true,
},
AccountMeta {
pubkey: recipient,
is_signer: false,
is_writable: true,
},
AccountMeta {
pubkey: system_account,
is_signer: false,
is_writable: false,
},
],
HashMap::from([(sender, Signature::new_unique())]),
vec![0, 0, 0, 0, 0, 0, 0, 10],
);
let sanitized_transaction =
SanitizedTransaction::try_from_legacy_transaction(transaction).unwrap();
transaction_builder.build(Hash::default(), (fee_payer, Signature::new_unique()));
all_transactions.push(sanitized_transaction);
transaction_checks.push((Ok(()), None, Some(20)));
@ -355,27 +337,11 @@ fn prepare_transactions(
// A program that utilizes a Sysvar
let program_account = Pubkey::new_unique();
let fee_payer = Pubkey::new_unique();
let message = Message {
account_keys: vec![fee_payer, program_account],
header: MessageHeader {
num_required_signatures: 1,
num_readonly_signed_accounts: 0,
num_readonly_unsigned_accounts: 0,
},
instructions: vec![CompiledInstruction {
program_id_index: 1,
accounts: vec![],
data: vec![],
}],
recent_blockhash: Hash::default(),
};
transaction_builder.create_instruction(program_account, Vec::new(), HashMap::new(), Vec::new());
let transaction = Transaction {
signatures: vec![Signature::new_unique()],
message,
};
let sanitized_transaction =
SanitizedTransaction::try_from_legacy_transaction(transaction).unwrap();
transaction_builder.build(Hash::default(), (fee_payer, Signature::new_unique()));
all_transactions.push(sanitized_transaction);
transaction_checks.push((Ok(()), None, Some(20)));
@ -407,33 +373,31 @@ fn prepare_transactions(
while data.len() < 8 {
data.insert(0, 0);
}
let message = Message {
account_keys: vec![
fee_payer,
sender,
transfer_program_account,
recipient,
system_account,
transaction_builder.create_instruction(
transfer_program_account,
vec![
AccountMeta {
pubkey: sender,
is_signer: true,
is_writable: true,
},
AccountMeta {
pubkey: recipient,
is_signer: false,
is_writable: true,
},
AccountMeta {
pubkey: system_account,
is_signer: false,
is_writable: false,
},
],
header: MessageHeader {
num_required_signatures: 2,
num_readonly_signed_accounts: 0,
num_readonly_unsigned_accounts: 0,
},
instructions: vec![CompiledInstruction {
program_id_index: 2,
accounts: vec![1, 3, 4],
data,
}],
recent_blockhash: Hash::default(),
};
let transaction = Transaction {
signatures: vec![Signature::new_unique(), Signature::new_unique()],
message,
};
HashMap::from([(sender, Signature::new_unique())]),
data,
);
let sanitized_transaction =
SanitizedTransaction::try_from_legacy_transaction(transaction).unwrap();
transaction_builder.build(Hash::default(), (fee_payer, Signature::new_unique()));
all_transactions.push(sanitized_transaction.clone());
transaction_checks.push((Ok(()), None, Some(20)));
@ -531,7 +495,7 @@ fn svm_integration() {
.is_ok());
// The SVM does not commit the account changes in MockBank
let recipient_key = transactions[1].message().account_keys()[3];
let recipient_key = transactions[1].message().account_keys()[2];
let recipient_data = result.loaded_transactions[1]
.0
.as_ref()

View File

@ -0,0 +1,136 @@
use {
solana_sdk::{
hash::Hash,
instruction::{AccountMeta, CompiledInstruction},
message::{Message, MessageHeader},
pubkey::Pubkey,
signature::Signature,
transaction::{SanitizedTransaction, Transaction},
},
std::collections::HashMap,
};
#[derive(Default)]
pub struct SanitizedTransactionBuilder {
instructions: Vec<InnerInstruction>,
num_required_signatures: u8,
num_readonly_signed_accounts: u8,
num_readonly_unsigned_accounts: u8,
}
struct InnerInstruction {
program_id: Pubkey,
accounts: Vec<Pubkey>,
signatures: HashMap<Pubkey, Signature>,
data: Vec<u8>,
}
impl SanitizedTransactionBuilder {
pub fn create_instruction(
&mut self,
program_id: Pubkey,
// The fee payer and the program id shall not appear in the accounts vector
accounts: Vec<AccountMeta>,
signatures: HashMap<Pubkey, Signature>,
data: Vec<u8>,
) {
self.num_required_signatures = self
.num_required_signatures
.saturating_add(signatures.len() as u8);
let instruction = InnerInstruction {
program_id,
accounts: accounts
.iter()
.map(|meta| {
if !meta.is_writable {
if meta.is_signer {
self.num_readonly_signed_accounts =
self.num_readonly_signed_accounts.saturating_add(1);
} else {
self.num_readonly_unsigned_accounts =
self.num_readonly_unsigned_accounts.saturating_add(1);
}
}
meta.pubkey
})
.collect(),
signatures,
data,
};
self.instructions.push(instruction);
}
pub fn build(
&mut self,
block_hash: Hash,
fee_payer: (Pubkey, Signature),
) -> SanitizedTransaction {
let mut message = Message {
account_keys: vec![],
header: MessageHeader {
// The fee payer always requires a signature so +1
num_required_signatures: self.num_required_signatures.saturating_add(1),
num_readonly_signed_accounts: self.num_readonly_signed_accounts,
num_readonly_unsigned_accounts: self.num_readonly_unsigned_accounts,
},
instructions: vec![],
recent_blockhash: block_hash,
};
let mut signatures = Vec::new();
let mut positions: HashMap<Pubkey, usize> = HashMap::new();
message.account_keys.push(fee_payer.0);
signatures.push(fee_payer.1);
for item in &self.instructions {
for (key, value) in &item.signatures {
signatures.push(*value);
positions.insert(*key, message.account_keys.len());
message.account_keys.push(*key);
}
}
let mut instructions: Vec<InnerInstruction> = Vec::new();
// Clean up
std::mem::swap(&mut instructions, &mut self.instructions);
self.num_required_signatures = 0;
self.num_readonly_signed_accounts = 0;
self.num_readonly_unsigned_accounts = 0;
for item in instructions {
let accounts = item
.accounts
.iter()
.map(|key| {
if let Some(idx) = positions.get(key) {
*idx as u8
} else {
push_and_return_index(*key, &mut message.account_keys)
}
})
.collect::<Vec<u8>>();
let instruction = CompiledInstruction {
program_id_index: push_and_return_index(item.program_id, &mut message.account_keys),
accounts,
data: item.data,
};
message.instructions.push(instruction);
}
let transaction = Transaction {
signatures,
message,
};
SanitizedTransaction::try_from_legacy_transaction(transaction).unwrap()
}
}
fn push_and_return_index(value: Pubkey, vector: &mut Vec<Pubkey>) -> u8 {
vector.push(value);
vector.len().saturating_sub(1) as u8
}