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:
@ -14,7 +14,7 @@ impl FeeCalculator {
|
||||
}
|
||||
|
||||
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
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
|
||||
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,
|
||||
/// Ordered indices into the transaction keys array indicating which accounts to pass to the program
|
||||
#[serde(with = "short_vec")]
|
||||
|
@ -10,11 +10,7 @@ fn position(keys: &[Pubkey], key: &Pubkey) -> u8 {
|
||||
keys.iter().position(|k| k == key).unwrap() as u8
|
||||
}
|
||||
|
||||
fn compile_instruction(
|
||||
ix: Instruction,
|
||||
keys: &[Pubkey],
|
||||
program_ids: &[Pubkey],
|
||||
) -> CompiledInstruction {
|
||||
fn compile_instruction(ix: Instruction, keys: &[Pubkey]) -> CompiledInstruction {
|
||||
let accounts: Vec<_> = ix
|
||||
.accounts
|
||||
.iter()
|
||||
@ -22,19 +18,15 @@ fn compile_instruction(
|
||||
.collect();
|
||||
|
||||
CompiledInstruction {
|
||||
program_ids_index: position(program_ids, &ix.program_ids_index),
|
||||
program_ids_index: position(keys, &ix.program_ids_index),
|
||||
data: ix.data.clone(),
|
||||
accounts,
|
||||
}
|
||||
}
|
||||
|
||||
fn compile_instructions(
|
||||
ixs: Vec<Instruction>,
|
||||
keys: &[Pubkey],
|
||||
program_ids: &[Pubkey],
|
||||
) -> Vec<CompiledInstruction> {
|
||||
fn compile_instructions(ixs: Vec<Instruction>, keys: &[Pubkey]) -> Vec<CompiledInstruction> {
|
||||
ixs.into_iter()
|
||||
.map(|ix| compile_instruction(ix, keys, program_ids))
|
||||
.map(|ix| compile_instruction(ix, keys))
|
||||
.collect()
|
||||
}
|
||||
|
||||
@ -78,12 +70,27 @@ fn get_program_ids(instructions: &[Instruction]) -> Vec<Pubkey> {
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
|
||||
pub struct Message {
|
||||
#[derive(Serialize, Deserialize, Default, Debug, PartialEq, Eq, Clone)]
|
||||
pub struct MessageHeader {
|
||||
/// The number of signatures required for this message to be considered valid. The
|
||||
/// signatures must match the first `num_required_signatures` of `account_keys`.
|
||||
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
|
||||
#[serde(with = "short_vec")]
|
||||
pub account_keys: Vec<Pubkey>,
|
||||
@ -91,10 +98,6 @@ pub struct Message {
|
||||
/// The id of a recent ledger entry.
|
||||
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
|
||||
/// succeed.
|
||||
#[serde(with = "short_vec")]
|
||||
@ -104,16 +107,20 @@ pub struct Message {
|
||||
impl Message {
|
||||
pub fn new_with_compiled_instructions(
|
||||
num_required_signatures: u8,
|
||||
num_credit_only_signed_accounts: u8,
|
||||
num_credit_only_unsigned_accounts: u8,
|
||||
account_keys: Vec<Pubkey>,
|
||||
recent_blockhash: Hash,
|
||||
program_ids: Vec<Pubkey>,
|
||||
instructions: Vec<CompiledInstruction>,
|
||||
) -> Self {
|
||||
Self {
|
||||
num_required_signatures,
|
||||
header: MessageHeader {
|
||||
num_required_signatures,
|
||||
num_credit_only_signed_accounts,
|
||||
num_credit_only_unsigned_accounts,
|
||||
},
|
||||
account_keys,
|
||||
recent_blockhash,
|
||||
program_ids,
|
||||
instructions,
|
||||
}
|
||||
}
|
||||
@ -126,19 +133,28 @@ impl Message {
|
||||
let program_ids = get_program_ids(&instructions);
|
||||
let (mut signed_keys, unsigned_keys) = get_keys(&instructions, payer);
|
||||
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);
|
||||
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(
|
||||
num_required_signatures,
|
||||
num_credit_only_signed_accounts,
|
||||
num_credit_only_unsigned_accounts,
|
||||
signed_keys,
|
||||
Hash::default(),
|
||||
program_ids,
|
||||
instructions,
|
||||
)
|
||||
}
|
||||
|
||||
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 ix = Instruction::new(program_id, &0, vec![AccountMeta::new(id0, false)]);
|
||||
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 message = Message::new(vec![ix]);
|
||||
assert_eq!(message.num_required_signatures, 1);
|
||||
assert_eq!(message.header.num_required_signatures, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_message_kitchen_sink() {
|
||||
let program_id0 = Pubkey::default();
|
||||
let program_id0 = Pubkey::new_rand();
|
||||
let program_id1 = Pubkey::new_rand();
|
||||
let id0 = Pubkey::default();
|
||||
let keypair1 = Keypair::new();
|
||||
@ -314,15 +330,15 @@ mod tests {
|
||||
]);
|
||||
assert_eq!(
|
||||
message.instructions[0],
|
||||
CompiledInstruction::new(0, &0, vec![1])
|
||||
CompiledInstruction::new(2, &0, vec![1])
|
||||
);
|
||||
assert_eq!(
|
||||
message.instructions[1],
|
||||
CompiledInstruction::new(1, &0, vec![0])
|
||||
CompiledInstruction::new(3, &0, vec![0])
|
||||
);
|
||||
assert_eq!(
|
||||
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 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 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(
|
||||
program_id,
|
||||
@ -346,7 +362,7 @@ mod tests {
|
||||
vec![AccountMeta::new(payer, true), AccountMeta::new(id0, true)],
|
||||
);
|
||||
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 {
|
||||
pub fn new_unsigned(message: Message) -> Self {
|
||||
Self {
|
||||
signatures: vec![Signature::default(); message.num_required_signatures as usize],
|
||||
signatures: vec![Signature::default(); message.header.num_required_signatures as usize],
|
||||
message,
|
||||
}
|
||||
}
|
||||
@ -115,11 +115,13 @@ impl Transaction {
|
||||
.map(|keypair| (*keypair).pubkey())
|
||||
.collect();
|
||||
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(),
|
||||
program_ids,
|
||||
instructions,
|
||||
);
|
||||
Transaction::new(from_keypairs, message, recent_blockhash)
|
||||
@ -175,7 +177,7 @@ impl Transaction {
|
||||
/// Check keys and keypair lengths, then sign this transaction.
|
||||
pub fn sign<T: KeypairUtil>(&mut self, keypairs: &[&T], recent_blockhash: Hash) {
|
||||
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() {
|
||||
assert_eq!(keypair.pubkey(), signed_keys[i], "keypair-pubkey mismatch");
|
||||
}
|
||||
@ -188,7 +190,7 @@ impl Transaction {
|
||||
/// clear any prior signatures and update recent_blockhash
|
||||
pub fn partial_sign<T: KeypairUtil>(&mut self, keypairs: &[&T], recent_blockhash: Hash) {
|
||||
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 recent_blockhash != self.message.recent_blockhash {
|
||||
@ -218,7 +220,7 @@ impl Transaction {
|
||||
pub fn verify_refs(&self) -> bool {
|
||||
let message = self.message();
|
||||
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;
|
||||
}
|
||||
for account_index in &instruction.accounts {
|
||||
@ -304,7 +306,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_refs_invalid_account() {
|
||||
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(
|
||||
&[&key],
|
||||
&[],
|
||||
@ -380,18 +382,18 @@ mod tests {
|
||||
|
||||
let len_size = 1;
|
||||
let num_required_sigs_size = 1;
|
||||
let num_credit_only_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_credit_only_accounts_size
|
||||
+ len_size
|
||||
+ (tx.message.account_keys.len() * size_of::<Pubkey>())
|
||||
+ blockhash_size
|
||||
+ len_size
|
||||
+ (tx.message.program_ids().len() * size_of::<Pubkey>())
|
||||
+ len_size
|
||||
+ expected_instruction_size;
|
||||
assert_eq!(expected_transaction_size, 214);
|
||||
assert_eq!(expected_transaction_size, 215);
|
||||
|
||||
assert_eq!(
|
||||
serialized_size(&tx).unwrap() as usize,
|
||||
@ -407,16 +409,16 @@ mod tests {
|
||||
assert_eq!(
|
||||
serialize(&create_sample_transaction()).unwrap(),
|
||||
vec![
|
||||
1, 134, 84, 186, 62, 126, 175, 48, 6, 80, 185, 139, 108, 109, 157, 213, 17, 249, 3,
|
||||
79, 83, 21, 89, 242, 148, 51, 140, 115, 77, 161, 134, 116, 136, 206, 171, 239, 236,
|
||||
240, 19, 73, 217, 152, 60, 159, 170, 41, 104, 29, 217, 93, 65, 139, 191, 202, 181,
|
||||
77, 246, 26, 15, 156, 186, 66, 32, 139, 6, 1, 2, 156, 227, 116, 193, 215, 38, 142,
|
||||
22, 8, 14, 229, 239, 119, 93, 5, 218, 161, 35, 3, 33, 0, 36, 100, 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,
|
||||
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,
|
||||
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, 1, 0, 2, 0, 1, 3,
|
||||
1, 2, 3
|
||||
1, 71, 59, 9, 187, 190, 129, 150, 165, 21, 33, 158, 72, 87, 110, 144, 120, 79, 238,
|
||||
132, 134, 105, 39, 102, 116, 209, 29, 229, 154, 36, 105, 44, 172, 118, 131, 22,
|
||||
124, 131, 179, 142, 176, 27, 117, 160, 89, 102, 224, 204, 1, 252, 141, 2, 136, 0,
|
||||
37, 218, 225, 129, 92, 154, 250, 59, 97, 178, 10, 1, 0, 1, 3, 156, 227, 116, 193,
|
||||
215, 38, 142, 22, 8, 14, 229, 239, 119, 93, 5, 218, 161, 35, 3, 33, 0, 36, 100,
|
||||
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, 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
|
||||
]
|
||||
);
|
||||
}
|
||||
@ -498,7 +500,7 @@ mod tests {
|
||||
tx.sign(&[&keypair0], Hash::default());
|
||||
assert_eq!(
|
||||
tx.message.instructions[0],
|
||||
CompiledInstruction::new(0, &0, vec![0])
|
||||
CompiledInstruction::new(1, &0, vec![0])
|
||||
);
|
||||
assert!(tx.is_signed());
|
||||
}
|
||||
|
Reference in New Issue
Block a user