Transaction format changes toward Credit-Only accounts (#4386)
* Add num_readonly_accounts slice * Impl programs in account_keys * Emulate current account-loading functionality using program-account_keys (breaks exchange_program_api tests) * Fix test * Add temporary exchange faucet id * Update chacha golden * Split num_credit_only_accounts into separate fields * Improve readability * Move message field constants into Message * Add MessageHeader struct and fixup comments
This commit is contained in:
parent
c121498b5b
commit
99d2428041
|
@ -154,7 +154,7 @@ mod tests {
|
||||||
hasher.hash(&buf[..size]);
|
hasher.hash(&buf[..size]);
|
||||||
|
|
||||||
// golden needs to be updated if blob stuff changes....
|
// golden needs to be updated if blob stuff changes....
|
||||||
let golden: Hash = "5Pz5KQyNht2nqkJhVd8F9zTFxzoDvbQSzaxQbtCPiyCo"
|
let golden: Hash = "9xb2Asf7UK5G8WqPwsvzo5xwLi4dixBSDiYKCtYRikA"
|
||||||
.parse()
|
.parse()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,9 @@
|
||||||
|
|
||||||
use crate::packet::{Packet, Packets};
|
use crate::packet::{Packet, Packets};
|
||||||
use crate::result::Result;
|
use crate::result::Result;
|
||||||
|
use bincode::serialized_size;
|
||||||
use solana_metrics::inc_new_counter_debug;
|
use solana_metrics::inc_new_counter_debug;
|
||||||
|
use solana_sdk::message::MessageHeader;
|
||||||
use solana_sdk::pubkey::Pubkey;
|
use solana_sdk::pubkey::Pubkey;
|
||||||
use solana_sdk::short_vec::decode_len;
|
use solana_sdk::short_vec::decode_len;
|
||||||
use solana_sdk::signature::Signature;
|
use solana_sdk::signature::Signature;
|
||||||
|
@ -16,9 +18,6 @@ use std::mem::size_of;
|
||||||
|
|
||||||
type TxOffsets = (Vec<u32>, Vec<u32>, Vec<u32>, Vec<u32>, Vec<Vec<u32>>);
|
type TxOffsets = (Vec<u32>, Vec<u32>, Vec<u32>, Vec<u32>, Vec<Vec<u32>>);
|
||||||
|
|
||||||
// The serialized size of Message::num_required_signatures.
|
|
||||||
const NUM_REQUIRED_SIGNATURES_SIZE: usize = 1;
|
|
||||||
|
|
||||||
#[cfg(feature = "cuda")]
|
#[cfg(feature = "cuda")]
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
struct Elems {
|
struct Elems {
|
||||||
|
@ -116,7 +115,8 @@ pub fn get_packet_offsets(packet: &Packet, current_offset: u32) -> (u32, u32, u3
|
||||||
|
|
||||||
let sig_start = current_offset as usize + sig_size;
|
let sig_start = current_offset as usize + sig_size;
|
||||||
let msg_start = current_offset as usize + msg_start_offset;
|
let msg_start = current_offset as usize + msg_start_offset;
|
||||||
let pubkey_start = msg_start + NUM_REQUIRED_SIGNATURES_SIZE + pubkey_size;
|
let pubkey_start =
|
||||||
|
msg_start + serialized_size(&MessageHeader::default()).unwrap() as usize + pubkey_size;
|
||||||
|
|
||||||
(
|
(
|
||||||
sig_len as u32,
|
sig_len as u32,
|
||||||
|
@ -389,19 +389,19 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_get_packet_offsets() {
|
fn test_get_packet_offsets() {
|
||||||
assert_eq!(get_packet_offsets_from_tx(test_tx(), 0), (1, 1, 64, 2));
|
assert_eq!(get_packet_offsets_from_tx(test_tx(), 0), (1, 1, 64, 4));
|
||||||
assert_eq!(get_packet_offsets_from_tx(test_tx(), 100), (1, 1, 64, 2));
|
assert_eq!(get_packet_offsets_from_tx(test_tx(), 100), (1, 1, 64, 4));
|
||||||
|
|
||||||
// Ensure we're not indexing packet by the `current_offset` parameter.
|
// Ensure we're not indexing packet by the `current_offset` parameter.
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
get_packet_offsets_from_tx(test_tx(), 1_000_000),
|
get_packet_offsets_from_tx(test_tx(), 1_000_000),
|
||||||
(1, 1, 64, 2)
|
(1, 1, 64, 4)
|
||||||
);
|
);
|
||||||
|
|
||||||
// Ensure we're returning sig_len, not sig_size.
|
// Ensure we're returning sig_len, not sig_size.
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
get_packet_offsets_from_tx(test_multisig_tx(), 0),
|
get_packet_offsets_from_tx(test_multisig_tx(), 0),
|
||||||
(2, 1, 128, 2)
|
(2, 1, 128, 4)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -382,7 +382,10 @@ mod tests {
|
||||||
let message = tx.message();
|
let message = tx.message();
|
||||||
|
|
||||||
assert_eq!(tx.signatures.len(), 1);
|
assert_eq!(tx.signatures.len(), 1);
|
||||||
assert_eq!(message.account_keys, vec![mint_pubkey, to]);
|
assert_eq!(
|
||||||
|
message.account_keys,
|
||||||
|
vec![mint_pubkey, to, Pubkey::default()]
|
||||||
|
);
|
||||||
assert_eq!(message.recent_blockhash, blockhash);
|
assert_eq!(message.recent_blockhash, blockhash);
|
||||||
|
|
||||||
assert_eq!(message.instructions.len(), 1);
|
assert_eq!(message.instructions.len(), 1);
|
||||||
|
|
|
@ -209,8 +209,9 @@ mod tests {
|
||||||
let mut message = Message::new(vec![instruction]);
|
let mut message = Message::new(vec![instruction]);
|
||||||
|
|
||||||
// Attack! Part 2: Point the instruction to the expected, but unsigned, key.
|
// Attack! Part 2: Point the instruction to the expected, but unsigned, key.
|
||||||
message.account_keys.push(alice_pubkey);
|
message.account_keys.insert(3, alice_pubkey);
|
||||||
message.instructions[0].accounts[0] = 3;
|
message.instructions[0].accounts[0] = 3;
|
||||||
|
message.instructions[0].program_ids_index = 4;
|
||||||
|
|
||||||
// Ensure the transaction fails because of the unsigned key.
|
// Ensure the transaction fails because of the unsigned key.
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -257,8 +258,9 @@ mod tests {
|
||||||
let mut message = Message::new(vec![instruction]);
|
let mut message = Message::new(vec![instruction]);
|
||||||
|
|
||||||
// Attack! Part 2: Point the instruction to the expected, but unsigned, key.
|
// Attack! Part 2: Point the instruction to the expected, but unsigned, key.
|
||||||
message.account_keys.push(alice_pubkey);
|
message.account_keys.insert(3, alice_pubkey);
|
||||||
message.instructions[0].accounts[0] = 3;
|
message.instructions[0].accounts[0] = 3;
|
||||||
|
message.instructions[0].program_ids_index = 4;
|
||||||
|
|
||||||
// Ensure the transaction fails because of the unsigned key.
|
// Ensure the transaction fails because of the unsigned key.
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
use crate::exchange_instruction::*;
|
use crate::exchange_instruction::*;
|
||||||
use crate::exchange_state::*;
|
use crate::exchange_state::*;
|
||||||
use crate::id;
|
use crate::faucet_id;
|
||||||
use log::*;
|
use log::*;
|
||||||
use solana_metrics::inc_new_counter_info;
|
use solana_metrics::inc_new_counter_info;
|
||||||
use solana_sdk::account::KeyedAccount;
|
use solana_sdk::account::KeyedAccount;
|
||||||
|
@ -192,7 +192,7 @@ impl ExchangeProcessor {
|
||||||
let mut to_account =
|
let mut to_account =
|
||||||
Self::deserialize_account(&keyed_accounts[TO_ACCOUNT_INDEX].account.data)?;
|
Self::deserialize_account(&keyed_accounts[TO_ACCOUNT_INDEX].account.data)?;
|
||||||
|
|
||||||
if &id() == keyed_accounts[FROM_ACCOUNT_INDEX].unsigned_key() {
|
if &faucet_id() == keyed_accounts[FROM_ACCOUNT_INDEX].unsigned_key() {
|
||||||
to_account.tokens[token] += tokens;
|
to_account.tokens[token] += tokens;
|
||||||
} else {
|
} else {
|
||||||
let state: ExchangeState =
|
let state: ExchangeState =
|
||||||
|
@ -460,7 +460,7 @@ pub fn process_instruction(
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::exchange_instruction;
|
use crate::{exchange_instruction, id};
|
||||||
use solana_runtime::bank::Bank;
|
use solana_runtime::bank::Bank;
|
||||||
use solana_runtime::bank_client::BankClient;
|
use solana_runtime::bank_client::BankClient;
|
||||||
use solana_sdk::client::SyncClient;
|
use solana_sdk::client::SyncClient;
|
||||||
|
@ -588,8 +588,13 @@ mod test {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn transfer(client: &BankClient, owner: &Keypair, to: &Pubkey, token: Token, tokens: u64) {
|
fn transfer(client: &BankClient, owner: &Keypair, to: &Pubkey, token: Token, tokens: u64) {
|
||||||
let instruction =
|
let instruction = exchange_instruction::transfer_request(
|
||||||
exchange_instruction::transfer_request(&owner.pubkey(), to, &id(), token, tokens);
|
&owner.pubkey(),
|
||||||
|
to,
|
||||||
|
&faucet_id(),
|
||||||
|
token,
|
||||||
|
tokens,
|
||||||
|
);
|
||||||
client
|
client
|
||||||
.send_instruction(owner, instruction)
|
.send_instruction(owner, instruction)
|
||||||
.expect(&format!("{}:{}", line!(), file!()));
|
.expect(&format!("{}:{}", line!(), file!()));
|
||||||
|
@ -664,8 +669,13 @@ mod test {
|
||||||
|
|
||||||
let new = create_token_account(&client, &owner);
|
let new = create_token_account(&client, &owner);
|
||||||
|
|
||||||
let instruction =
|
let instruction = exchange_instruction::transfer_request(
|
||||||
exchange_instruction::transfer_request(&owner.pubkey(), &new, &id(), Token::A, 42);
|
&owner.pubkey(),
|
||||||
|
&new,
|
||||||
|
&faucet_id(),
|
||||||
|
Token::A,
|
||||||
|
42,
|
||||||
|
);
|
||||||
client
|
client
|
||||||
.send_instruction(&owner, instruction)
|
.send_instruction(&owner, instruction)
|
||||||
.expect(&format!("{}:{}", line!(), file!()));
|
.expect(&format!("{}:{}", line!(), file!()));
|
||||||
|
|
|
@ -11,3 +11,22 @@ pub const EXCHANGE_PROGRAM_ID: [u8; 32] = [
|
||||||
];
|
];
|
||||||
|
|
||||||
solana_sdk::solana_program_id!(EXCHANGE_PROGRAM_ID);
|
solana_sdk::solana_program_id!(EXCHANGE_PROGRAM_ID);
|
||||||
|
|
||||||
|
pub const EXCHANGE_FAUCET_ID: [u8; 32] = [
|
||||||
|
3, 147, 111, 103, 210, 47, 23, 11, 176, 29, 147, 89, 237, 155, 21, 62, 107, 105, 157, 1, 98,
|
||||||
|
204, 206, 211, 54, 212, 79, 15, 160, 0, 0, 0,
|
||||||
|
];
|
||||||
|
|
||||||
|
pub fn faucet_id() -> solana_sdk::pubkey::Pubkey {
|
||||||
|
solana_sdk::pubkey::Pubkey::new(&EXCHANGE_FAUCET_ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
#[test]
|
||||||
|
fn exchange_faucet_id() {
|
||||||
|
let ids = [("ExchangeFaucet11111111111111111111111111111", faucet_id())];
|
||||||
|
assert!(ids.iter().all(|(name, id)| *name == id.to_string()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -180,11 +180,13 @@ impl Accounts {
|
||||||
// If a fee can pay for execution then the program will be scheduled
|
// If a fee can pay for execution then the program will be scheduled
|
||||||
let mut called_accounts: Vec<Account> = vec![];
|
let mut called_accounts: Vec<Account> = vec![];
|
||||||
for key in &message.account_keys {
|
for key in &message.account_keys {
|
||||||
called_accounts.push(
|
if !message.program_ids().contains(&key) {
|
||||||
AccountsDB::load(storage, ancestors, accounts_index, key)
|
called_accounts.push(
|
||||||
.map(|(account, _)| account)
|
AccountsDB::load(storage, ancestors, accounts_index, key)
|
||||||
.unwrap_or_default(),
|
.map(|(account, _)| account)
|
||||||
);
|
.unwrap_or_default(),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if called_accounts.is_empty() || called_accounts[0].lamports == 0 {
|
if called_accounts.is_empty() || called_accounts[0].lamports == 0 {
|
||||||
error_counters.account_not_found += 1;
|
error_counters.account_not_found += 1;
|
||||||
|
@ -255,11 +257,11 @@ impl Accounts {
|
||||||
.instructions
|
.instructions
|
||||||
.iter()
|
.iter()
|
||||||
.map(|ix| {
|
.map(|ix| {
|
||||||
if message.program_ids().len() <= ix.program_ids_index as usize {
|
if message.account_keys.len() <= ix.program_ids_index as usize {
|
||||||
error_counters.account_not_found += 1;
|
error_counters.account_not_found += 1;
|
||||||
return Err(TransactionError::AccountNotFound);
|
return Err(TransactionError::AccountNotFound);
|
||||||
}
|
}
|
||||||
let program_id = message.program_ids()[ix.program_ids_index as usize];
|
let program_id = message.account_keys[ix.program_ids_index as usize];
|
||||||
Self::load_executable_accounts(
|
Self::load_executable_accounts(
|
||||||
storage,
|
storage,
|
||||||
ancestors,
|
ancestors,
|
||||||
|
@ -469,9 +471,11 @@ impl Accounts {
|
||||||
let rv = txs
|
let rv = txs
|
||||||
.iter()
|
.iter()
|
||||||
.map(|tx| {
|
.map(|tx| {
|
||||||
|
let message = tx.borrow().message();
|
||||||
Self::lock_account(
|
Self::lock_account(
|
||||||
(&mut self.account_locks.lock().unwrap(), parent_record_locks),
|
(&mut self.account_locks.lock().unwrap(), parent_record_locks),
|
||||||
&tx.borrow().message().account_keys,
|
&message.account_keys[..(message.account_keys.len()
|
||||||
|
- message.header.num_credit_only_unsigned_accounts as usize)],
|
||||||
&mut error_counters,
|
&mut error_counters,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
@ -493,7 +497,12 @@ impl Accounts {
|
||||||
{
|
{
|
||||||
let record_locks = self.record_locks.lock().unwrap();
|
let record_locks = self.record_locks.lock().unwrap();
|
||||||
for tx in txs {
|
for tx in txs {
|
||||||
Self::lock_record_account(&record_locks.0, &tx.borrow().message().account_keys);
|
let message = tx.borrow().message();
|
||||||
|
Self::lock_record_account(
|
||||||
|
&record_locks.0,
|
||||||
|
&message.account_keys[..(message.account_keys.len()
|
||||||
|
- message.header.num_credit_only_unsigned_accounts as usize)],
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -684,7 +693,10 @@ mod tests {
|
||||||
|
|
||||||
assert_eq!(error_counters.account_not_found, 1);
|
assert_eq!(error_counters.account_not_found, 1);
|
||||||
assert_eq!(loaded_accounts.len(), 1);
|
assert_eq!(loaded_accounts.len(), 1);
|
||||||
assert_eq!(loaded_accounts[0], Err(TransactionError::AccountNotFound));
|
assert_eq!(
|
||||||
|
loaded_accounts[0],
|
||||||
|
Err(TransactionError::ProgramAccountNotFound)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -736,7 +748,7 @@ mod tests {
|
||||||
let account = Account::new(2, 1, &Pubkey::default());
|
let account = Account::new(2, 1, &Pubkey::default());
|
||||||
accounts.push((key1, account));
|
accounts.push((key1, account));
|
||||||
|
|
||||||
let instructions = vec![CompiledInstruction::new(0, &(), vec![0, 1])];
|
let instructions = vec![CompiledInstruction::new(2, &(), vec![0, 1])];
|
||||||
let tx = Transaction::new_with_compiled_instructions(
|
let tx = Transaction::new_with_compiled_instructions(
|
||||||
&[&keypair],
|
&[&keypair],
|
||||||
&[key1],
|
&[key1],
|
||||||
|
@ -807,7 +819,7 @@ mod tests {
|
||||||
account.owner = key5;
|
account.owner = key5;
|
||||||
accounts.push((key6, account));
|
accounts.push((key6, account));
|
||||||
|
|
||||||
let instructions = vec![CompiledInstruction::new(0, &(), vec![0])];
|
let instructions = vec![CompiledInstruction::new(1, &(), vec![0])];
|
||||||
let tx = Transaction::new_with_compiled_instructions(
|
let tx = Transaction::new_with_compiled_instructions(
|
||||||
&[&keypair],
|
&[&keypair],
|
||||||
&[],
|
&[],
|
||||||
|
@ -918,8 +930,8 @@ mod tests {
|
||||||
accounts.push((key3, account));
|
accounts.push((key3, account));
|
||||||
|
|
||||||
let instructions = vec![
|
let instructions = vec![
|
||||||
CompiledInstruction::new(0, &(), vec![0]),
|
|
||||||
CompiledInstruction::new(1, &(), vec![0]),
|
CompiledInstruction::new(1, &(), vec![0]),
|
||||||
|
CompiledInstruction::new(2, &(), vec![0]),
|
||||||
];
|
];
|
||||||
let tx = Transaction::new_with_compiled_instructions(
|
let tx = Transaction::new_with_compiled_instructions(
|
||||||
&[&keypair],
|
&[&keypair],
|
||||||
|
|
|
@ -125,7 +125,7 @@ impl MessageProcessor {
|
||||||
program_accounts: &mut [&mut Account],
|
program_accounts: &mut [&mut Account],
|
||||||
tick_height: u64,
|
tick_height: u64,
|
||||||
) -> Result<(), InstructionError> {
|
) -> Result<(), InstructionError> {
|
||||||
let program_id = instruction.program_id(message.program_ids());
|
let program_id = instruction.program_id(&message.account_keys);
|
||||||
|
|
||||||
let mut keyed_accounts = create_keyed_accounts(executable_accounts);
|
let mut keyed_accounts = create_keyed_accounts(executable_accounts);
|
||||||
let mut keyed_accounts2: Vec<_> = instruction
|
let mut keyed_accounts2: Vec<_> = instruction
|
||||||
|
@ -134,7 +134,7 @@ impl MessageProcessor {
|
||||||
.map(|&index| {
|
.map(|&index| {
|
||||||
let index = index as usize;
|
let index = index as usize;
|
||||||
let key = &message.account_keys[index];
|
let key = &message.account_keys[index];
|
||||||
(key, index < message.num_required_signatures as usize)
|
(key, index < message.header.num_required_signatures as usize)
|
||||||
})
|
})
|
||||||
.zip(program_accounts.iter_mut())
|
.zip(program_accounts.iter_mut())
|
||||||
.map(|((key, is_signer), account)| KeyedAccount::new(key, is_signer, account))
|
.map(|((key, is_signer), account)| KeyedAccount::new(key, is_signer, account))
|
||||||
|
@ -173,7 +173,7 @@ impl MessageProcessor {
|
||||||
program_accounts: &mut [&mut Account],
|
program_accounts: &mut [&mut Account],
|
||||||
tick_height: u64,
|
tick_height: u64,
|
||||||
) -> Result<(), InstructionError> {
|
) -> Result<(), InstructionError> {
|
||||||
let program_id = instruction.program_id(message.program_ids());
|
let program_id = instruction.program_id(&message.account_keys);
|
||||||
// TODO: the runtime should be checking read/write access to memory
|
// TODO: the runtime should be checking read/write access to memory
|
||||||
// we are trusting the hard-coded programs not to clobber or allocate
|
// we are trusting the hard-coded programs not to clobber or allocate
|
||||||
let pre_total: u64 = program_accounts.iter().map(|a| a.lamports).sum();
|
let pre_total: u64 = program_accounts.iter().map(|a| a.lamports).sum();
|
||||||
|
@ -221,7 +221,8 @@ impl MessageProcessor {
|
||||||
tick_height: u64,
|
tick_height: u64,
|
||||||
) -> Result<(), TransactionError> {
|
) -> Result<(), TransactionError> {
|
||||||
for (instruction_index, instruction) in message.instructions.iter().enumerate() {
|
for (instruction_index, instruction) in message.instructions.iter().enumerate() {
|
||||||
let executable_accounts = &mut loaders[instruction.program_ids_index as usize];
|
let executable_accounts = &mut loaders
|
||||||
|
[message.program_index_in_program_ids(instruction.program_ids_index) as usize];
|
||||||
let mut program_accounts = get_subset_unchecked_mut(accounts, &instruction.accounts)
|
let mut program_accounts = get_subset_unchecked_mut(accounts, &instruction.accounts)
|
||||||
.map_err(|err| TransactionError::InstructionError(instruction_index as u8, err))?;
|
.map_err(|err| TransactionError::InstructionError(instruction_index as u8, err))?;
|
||||||
self.execute_instruction(
|
self.execute_instruction(
|
||||||
|
|
|
@ -14,7 +14,7 @@ impl FeeCalculator {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn calculate_fee(&self, message: &Message) -> u64 {
|
pub fn calculate_fee(&self, message: &Message) -> u64 {
|
||||||
self.lamports_per_signature * u64::from(message.num_required_signatures)
|
self.lamports_per_signature * u64::from(message.header.num_required_signatures)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -107,7 +107,7 @@ impl AccountMeta {
|
||||||
/// An instruction to execute a program
|
/// An instruction to execute a program
|
||||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
|
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
|
||||||
pub struct CompiledInstruction {
|
pub struct CompiledInstruction {
|
||||||
/// Index into the transaction program ids array indicating the program account that executes this instruction
|
/// Index into the transaction keys array indicating the program account that executes this instruction
|
||||||
pub program_ids_index: u8,
|
pub program_ids_index: u8,
|
||||||
/// Ordered indices into the transaction keys array indicating which accounts to pass to the program
|
/// Ordered indices into the transaction keys array indicating which accounts to pass to the program
|
||||||
#[serde(with = "short_vec")]
|
#[serde(with = "short_vec")]
|
||||||
|
|
|
@ -10,11 +10,7 @@ fn position(keys: &[Pubkey], key: &Pubkey) -> u8 {
|
||||||
keys.iter().position(|k| k == key).unwrap() as u8
|
keys.iter().position(|k| k == key).unwrap() as u8
|
||||||
}
|
}
|
||||||
|
|
||||||
fn compile_instruction(
|
fn compile_instruction(ix: Instruction, keys: &[Pubkey]) -> CompiledInstruction {
|
||||||
ix: Instruction,
|
|
||||||
keys: &[Pubkey],
|
|
||||||
program_ids: &[Pubkey],
|
|
||||||
) -> CompiledInstruction {
|
|
||||||
let accounts: Vec<_> = ix
|
let accounts: Vec<_> = ix
|
||||||
.accounts
|
.accounts
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -22,19 +18,15 @@ fn compile_instruction(
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
CompiledInstruction {
|
CompiledInstruction {
|
||||||
program_ids_index: position(program_ids, &ix.program_ids_index),
|
program_ids_index: position(keys, &ix.program_ids_index),
|
||||||
data: ix.data.clone(),
|
data: ix.data.clone(),
|
||||||
accounts,
|
accounts,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn compile_instructions(
|
fn compile_instructions(ixs: Vec<Instruction>, keys: &[Pubkey]) -> Vec<CompiledInstruction> {
|
||||||
ixs: Vec<Instruction>,
|
|
||||||
keys: &[Pubkey],
|
|
||||||
program_ids: &[Pubkey],
|
|
||||||
) -> Vec<CompiledInstruction> {
|
|
||||||
ixs.into_iter()
|
ixs.into_iter()
|
||||||
.map(|ix| compile_instruction(ix, keys, program_ids))
|
.map(|ix| compile_instruction(ix, keys))
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,12 +70,27 @@ fn get_program_ids(instructions: &[Instruction]) -> Vec<Pubkey> {
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
|
#[derive(Serialize, Deserialize, Default, Debug, PartialEq, Eq, Clone)]
|
||||||
pub struct Message {
|
pub struct MessageHeader {
|
||||||
/// The number of signatures required for this message to be considered valid. The
|
/// The number of signatures required for this message to be considered valid. The
|
||||||
/// signatures must match the first `num_required_signatures` of `account_keys`.
|
/// signatures must match the first `num_required_signatures` of `account_keys`.
|
||||||
pub num_required_signatures: u8,
|
pub num_required_signatures: u8,
|
||||||
|
|
||||||
|
/// The last num_credit_only_signed_accounts of the signed keys are credit-only accounts.
|
||||||
|
/// Programs may process multiple transactions that add lamports to the same credit-only
|
||||||
|
/// account within a single PoH entry, but are not permitted to debit lamports or modify
|
||||||
|
/// account data. Transactions targeting the same debit account are evaluated sequentially.
|
||||||
|
pub num_credit_only_signed_accounts: u8,
|
||||||
|
|
||||||
|
/// The last num_credit_only_unsigned_accounts of the unsigned keys are credit-only accounts.
|
||||||
|
pub num_credit_only_unsigned_accounts: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
|
||||||
|
pub struct Message {
|
||||||
|
/// The message header, identifying signed and credit-only `account_keys`
|
||||||
|
pub header: MessageHeader,
|
||||||
|
|
||||||
/// All the account keys used by this transaction
|
/// All the account keys used by this transaction
|
||||||
#[serde(with = "short_vec")]
|
#[serde(with = "short_vec")]
|
||||||
pub account_keys: Vec<Pubkey>,
|
pub account_keys: Vec<Pubkey>,
|
||||||
|
@ -91,10 +98,6 @@ pub struct Message {
|
||||||
/// The id of a recent ledger entry.
|
/// The id of a recent ledger entry.
|
||||||
pub recent_blockhash: Hash,
|
pub recent_blockhash: Hash,
|
||||||
|
|
||||||
/// All the program id keys used to execute this transaction's instructions
|
|
||||||
#[serde(with = "short_vec")]
|
|
||||||
program_ids: Vec<Pubkey>,
|
|
||||||
|
|
||||||
/// Programs that will be executed in sequence and committed in one atomic transaction if all
|
/// Programs that will be executed in sequence and committed in one atomic transaction if all
|
||||||
/// succeed.
|
/// succeed.
|
||||||
#[serde(with = "short_vec")]
|
#[serde(with = "short_vec")]
|
||||||
|
@ -104,16 +107,20 @@ pub struct Message {
|
||||||
impl Message {
|
impl Message {
|
||||||
pub fn new_with_compiled_instructions(
|
pub fn new_with_compiled_instructions(
|
||||||
num_required_signatures: u8,
|
num_required_signatures: u8,
|
||||||
|
num_credit_only_signed_accounts: u8,
|
||||||
|
num_credit_only_unsigned_accounts: u8,
|
||||||
account_keys: Vec<Pubkey>,
|
account_keys: Vec<Pubkey>,
|
||||||
recent_blockhash: Hash,
|
recent_blockhash: Hash,
|
||||||
program_ids: Vec<Pubkey>,
|
|
||||||
instructions: Vec<CompiledInstruction>,
|
instructions: Vec<CompiledInstruction>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
num_required_signatures,
|
header: MessageHeader {
|
||||||
|
num_required_signatures,
|
||||||
|
num_credit_only_signed_accounts,
|
||||||
|
num_credit_only_unsigned_accounts,
|
||||||
|
},
|
||||||
account_keys,
|
account_keys,
|
||||||
recent_blockhash,
|
recent_blockhash,
|
||||||
program_ids,
|
|
||||||
instructions,
|
instructions,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -126,19 +133,28 @@ impl Message {
|
||||||
let program_ids = get_program_ids(&instructions);
|
let program_ids = get_program_ids(&instructions);
|
||||||
let (mut signed_keys, unsigned_keys) = get_keys(&instructions, payer);
|
let (mut signed_keys, unsigned_keys) = get_keys(&instructions, payer);
|
||||||
let num_required_signatures = signed_keys.len() as u8;
|
let num_required_signatures = signed_keys.len() as u8;
|
||||||
|
let num_credit_only_signed_accounts = 0;
|
||||||
|
let num_credit_only_unsigned_accounts = program_ids.len() as u8;
|
||||||
signed_keys.extend(&unsigned_keys);
|
signed_keys.extend(&unsigned_keys);
|
||||||
let instructions = compile_instructions(instructions, &signed_keys, &program_ids);
|
signed_keys.extend(&program_ids);
|
||||||
|
let instructions = compile_instructions(instructions, &signed_keys);
|
||||||
Self::new_with_compiled_instructions(
|
Self::new_with_compiled_instructions(
|
||||||
num_required_signatures,
|
num_required_signatures,
|
||||||
|
num_credit_only_signed_accounts,
|
||||||
|
num_credit_only_unsigned_accounts,
|
||||||
signed_keys,
|
signed_keys,
|
||||||
Hash::default(),
|
Hash::default(),
|
||||||
program_ids,
|
|
||||||
instructions,
|
instructions,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn program_ids(&self) -> &[Pubkey] {
|
pub fn program_ids(&self) -> &[Pubkey] {
|
||||||
&self.program_ids
|
&self.account_keys
|
||||||
|
[self.account_keys.len() - self.header.num_credit_only_unsigned_accounts as usize..]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn program_index_in_program_ids(&self, index: u8) -> u8 {
|
||||||
|
index - (self.account_keys.len() as u8 - self.header.num_credit_only_unsigned_accounts)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -293,16 +309,16 @@ mod tests {
|
||||||
let id0 = Pubkey::default();
|
let id0 = Pubkey::default();
|
||||||
let ix = Instruction::new(program_id, &0, vec![AccountMeta::new(id0, false)]);
|
let ix = Instruction::new(program_id, &0, vec![AccountMeta::new(id0, false)]);
|
||||||
let message = Message::new(vec![ix]);
|
let message = Message::new(vec![ix]);
|
||||||
assert_eq!(message.num_required_signatures, 0);
|
assert_eq!(message.header.num_required_signatures, 0);
|
||||||
|
|
||||||
let ix = Instruction::new(program_id, &0, vec![AccountMeta::new(id0, true)]);
|
let ix = Instruction::new(program_id, &0, vec![AccountMeta::new(id0, true)]);
|
||||||
let message = Message::new(vec![ix]);
|
let message = Message::new(vec![ix]);
|
||||||
assert_eq!(message.num_required_signatures, 1);
|
assert_eq!(message.header.num_required_signatures, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_message_kitchen_sink() {
|
fn test_message_kitchen_sink() {
|
||||||
let program_id0 = Pubkey::default();
|
let program_id0 = Pubkey::new_rand();
|
||||||
let program_id1 = Pubkey::new_rand();
|
let program_id1 = Pubkey::new_rand();
|
||||||
let id0 = Pubkey::default();
|
let id0 = Pubkey::default();
|
||||||
let keypair1 = Keypair::new();
|
let keypair1 = Keypair::new();
|
||||||
|
@ -314,15 +330,15 @@ mod tests {
|
||||||
]);
|
]);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
message.instructions[0],
|
message.instructions[0],
|
||||||
CompiledInstruction::new(0, &0, vec![1])
|
CompiledInstruction::new(2, &0, vec![1])
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
message.instructions[1],
|
message.instructions[1],
|
||||||
CompiledInstruction::new(1, &0, vec![0])
|
CompiledInstruction::new(3, &0, vec![0])
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
message.instructions[2],
|
message.instructions[2],
|
||||||
CompiledInstruction::new(0, &0, vec![0])
|
CompiledInstruction::new(2, &0, vec![0])
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -334,11 +350,11 @@ mod tests {
|
||||||
|
|
||||||
let ix = Instruction::new(program_id, &0, vec![AccountMeta::new(id0, false)]);
|
let ix = Instruction::new(program_id, &0, vec![AccountMeta::new(id0, false)]);
|
||||||
let message = Message::new_with_payer(vec![ix], Some(&payer));
|
let message = Message::new_with_payer(vec![ix], Some(&payer));
|
||||||
assert_eq!(message.num_required_signatures, 1);
|
assert_eq!(message.header.num_required_signatures, 1);
|
||||||
|
|
||||||
let ix = Instruction::new(program_id, &0, vec![AccountMeta::new(id0, true)]);
|
let ix = Instruction::new(program_id, &0, vec![AccountMeta::new(id0, true)]);
|
||||||
let message = Message::new_with_payer(vec![ix], Some(&payer));
|
let message = Message::new_with_payer(vec![ix], Some(&payer));
|
||||||
assert_eq!(message.num_required_signatures, 2);
|
assert_eq!(message.header.num_required_signatures, 2);
|
||||||
|
|
||||||
let ix = Instruction::new(
|
let ix = Instruction::new(
|
||||||
program_id,
|
program_id,
|
||||||
|
@ -346,7 +362,7 @@ mod tests {
|
||||||
vec![AccountMeta::new(payer, true), AccountMeta::new(id0, true)],
|
vec![AccountMeta::new(payer, true), AccountMeta::new(id0, true)],
|
||||||
);
|
);
|
||||||
let message = Message::new_with_payer(vec![ix], Some(&payer));
|
let message = Message::new_with_payer(vec![ix], Some(&payer));
|
||||||
assert_eq!(message.num_required_signatures, 2);
|
assert_eq!(message.header.num_required_signatures, 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -67,7 +67,7 @@ pub struct Transaction {
|
||||||
impl Transaction {
|
impl Transaction {
|
||||||
pub fn new_unsigned(message: Message) -> Self {
|
pub fn new_unsigned(message: Message) -> Self {
|
||||||
Self {
|
Self {
|
||||||
signatures: vec![Signature::default(); message.num_required_signatures as usize],
|
signatures: vec![Signature::default(); message.header.num_required_signatures as usize],
|
||||||
message,
|
message,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -115,11 +115,13 @@ impl Transaction {
|
||||||
.map(|keypair| (*keypair).pubkey())
|
.map(|keypair| (*keypair).pubkey())
|
||||||
.collect();
|
.collect();
|
||||||
account_keys.extend_from_slice(keys);
|
account_keys.extend_from_slice(keys);
|
||||||
|
account_keys.extend(&program_ids);
|
||||||
let message = Message::new_with_compiled_instructions(
|
let message = Message::new_with_compiled_instructions(
|
||||||
from_keypairs.len() as u8,
|
from_keypairs.len() as u8,
|
||||||
|
0,
|
||||||
|
program_ids.len() as u8,
|
||||||
account_keys,
|
account_keys,
|
||||||
Hash::default(),
|
Hash::default(),
|
||||||
program_ids,
|
|
||||||
instructions,
|
instructions,
|
||||||
);
|
);
|
||||||
Transaction::new(from_keypairs, message, recent_blockhash)
|
Transaction::new(from_keypairs, message, recent_blockhash)
|
||||||
|
@ -175,7 +177,7 @@ impl Transaction {
|
||||||
/// Check keys and keypair lengths, then sign this transaction.
|
/// Check keys and keypair lengths, then sign this transaction.
|
||||||
pub fn sign<T: KeypairUtil>(&mut self, keypairs: &[&T], recent_blockhash: Hash) {
|
pub fn sign<T: KeypairUtil>(&mut self, keypairs: &[&T], recent_blockhash: Hash) {
|
||||||
let signed_keys =
|
let signed_keys =
|
||||||
&self.message.account_keys[0..self.message.num_required_signatures as usize];
|
&self.message.account_keys[0..self.message.header.num_required_signatures as usize];
|
||||||
for (i, keypair) in keypairs.iter().enumerate() {
|
for (i, keypair) in keypairs.iter().enumerate() {
|
||||||
assert_eq!(keypair.pubkey(), signed_keys[i], "keypair-pubkey mismatch");
|
assert_eq!(keypair.pubkey(), signed_keys[i], "keypair-pubkey mismatch");
|
||||||
}
|
}
|
||||||
|
@ -188,7 +190,7 @@ impl Transaction {
|
||||||
/// clear any prior signatures and update recent_blockhash
|
/// clear any prior signatures and update recent_blockhash
|
||||||
pub fn partial_sign<T: KeypairUtil>(&mut self, keypairs: &[&T], recent_blockhash: Hash) {
|
pub fn partial_sign<T: KeypairUtil>(&mut self, keypairs: &[&T], recent_blockhash: Hash) {
|
||||||
let signed_keys =
|
let signed_keys =
|
||||||
&self.message.account_keys[0..self.message.num_required_signatures as usize];
|
&self.message.account_keys[0..self.message.header.num_required_signatures as usize];
|
||||||
|
|
||||||
// if you change the blockhash, you're re-signing...
|
// if you change the blockhash, you're re-signing...
|
||||||
if recent_blockhash != self.message.recent_blockhash {
|
if recent_blockhash != self.message.recent_blockhash {
|
||||||
|
@ -218,7 +220,7 @@ impl Transaction {
|
||||||
pub fn verify_refs(&self) -> bool {
|
pub fn verify_refs(&self) -> bool {
|
||||||
let message = self.message();
|
let message = self.message();
|
||||||
for instruction in &message.instructions {
|
for instruction in &message.instructions {
|
||||||
if (instruction.program_ids_index as usize) >= message.program_ids().len() {
|
if (instruction.program_ids_index as usize) >= message.account_keys.len() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
for account_index in &instruction.accounts {
|
for account_index in &instruction.accounts {
|
||||||
|
@ -304,7 +306,7 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_refs_invalid_account() {
|
fn test_refs_invalid_account() {
|
||||||
let key = Keypair::new();
|
let key = Keypair::new();
|
||||||
let instructions = vec![CompiledInstruction::new(0, &(), vec![1])];
|
let instructions = vec![CompiledInstruction::new(0, &(), vec![2])];
|
||||||
let tx = Transaction::new_with_compiled_instructions(
|
let tx = Transaction::new_with_compiled_instructions(
|
||||||
&[&key],
|
&[&key],
|
||||||
&[],
|
&[],
|
||||||
|
@ -380,18 +382,18 @@ mod tests {
|
||||||
|
|
||||||
let len_size = 1;
|
let len_size = 1;
|
||||||
let num_required_sigs_size = 1;
|
let num_required_sigs_size = 1;
|
||||||
|
let num_credit_only_accounts_size = 2;
|
||||||
let blockhash_size = size_of::<Hash>();
|
let blockhash_size = size_of::<Hash>();
|
||||||
let expected_transaction_size = len_size
|
let expected_transaction_size = len_size
|
||||||
+ (tx.signatures.len() * size_of::<Signature>())
|
+ (tx.signatures.len() * size_of::<Signature>())
|
||||||
+ num_required_sigs_size
|
+ num_required_sigs_size
|
||||||
|
+ num_credit_only_accounts_size
|
||||||
+ len_size
|
+ len_size
|
||||||
+ (tx.message.account_keys.len() * size_of::<Pubkey>())
|
+ (tx.message.account_keys.len() * size_of::<Pubkey>())
|
||||||
+ blockhash_size
|
+ blockhash_size
|
||||||
+ len_size
|
+ len_size
|
||||||
+ (tx.message.program_ids().len() * size_of::<Pubkey>())
|
|
||||||
+ len_size
|
|
||||||
+ expected_instruction_size;
|
+ expected_instruction_size;
|
||||||
assert_eq!(expected_transaction_size, 214);
|
assert_eq!(expected_transaction_size, 215);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
serialized_size(&tx).unwrap() as usize,
|
serialized_size(&tx).unwrap() as usize,
|
||||||
|
@ -407,16 +409,16 @@ mod tests {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
serialize(&create_sample_transaction()).unwrap(),
|
serialize(&create_sample_transaction()).unwrap(),
|
||||||
vec![
|
vec![
|
||||||
1, 134, 84, 186, 62, 126, 175, 48, 6, 80, 185, 139, 108, 109, 157, 213, 17, 249, 3,
|
1, 71, 59, 9, 187, 190, 129, 150, 165, 21, 33, 158, 72, 87, 110, 144, 120, 79, 238,
|
||||||
79, 83, 21, 89, 242, 148, 51, 140, 115, 77, 161, 134, 116, 136, 206, 171, 239, 236,
|
132, 134, 105, 39, 102, 116, 209, 29, 229, 154, 36, 105, 44, 172, 118, 131, 22,
|
||||||
240, 19, 73, 217, 152, 60, 159, 170, 41, 104, 29, 217, 93, 65, 139, 191, 202, 181,
|
124, 131, 179, 142, 176, 27, 117, 160, 89, 102, 224, 204, 1, 252, 141, 2, 136, 0,
|
||||||
77, 246, 26, 15, 156, 186, 66, 32, 139, 6, 1, 2, 156, 227, 116, 193, 215, 38, 142,
|
37, 218, 225, 129, 92, 154, 250, 59, 97, 178, 10, 1, 0, 1, 3, 156, 227, 116, 193,
|
||||||
22, 8, 14, 229, 239, 119, 93, 5, 218, 161, 35, 3, 33, 0, 36, 100, 158, 252, 33,
|
215, 38, 142, 22, 8, 14, 229, 239, 119, 93, 5, 218, 161, 35, 3, 33, 0, 36, 100,
|
||||||
161, 97, 185, 62, 89, 99, 1, 1, 1, 4, 5, 6, 7, 8, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
|
158, 252, 33, 161, 97, 185, 62, 89, 99, 1, 1, 1, 4, 5, 6, 7, 8, 9, 9, 9, 9, 9, 9,
|
||||||
9, 9, 9, 9, 9, 8, 7, 6, 5, 4, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
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,
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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,
|
||||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9, 8, 7, 6, 5, 4, 2, 2, 2, 1, 0, 2, 0, 1, 3,
|
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,
|
||||||
1, 2, 3
|
2, 0, 1, 3, 1, 2, 3
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -498,7 +500,7 @@ mod tests {
|
||||||
tx.sign(&[&keypair0], Hash::default());
|
tx.sign(&[&keypair0], Hash::default());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
tx.message.instructions[0],
|
tx.message.instructions[0],
|
||||||
CompiledInstruction::new(0, &0, vec![0])
|
CompiledInstruction::new(1, &0, vec![0])
|
||||||
);
|
);
|
||||||
assert!(tx.is_signed());
|
assert!(tx.is_signed());
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue