Add RPC support for versioned transactions (#22530)

* Add RPC support for versioned transactions

* fix doc tests

* Add rpc test for versioned txs

* Switch to preflight bank
This commit is contained in:
Justin Starry
2022-03-08 15:20:34 +08:00
committed by GitHub
parent e790d0fc53
commit 3114c199bd
29 changed files with 864 additions and 310 deletions

View File

@ -24,7 +24,7 @@ pub enum SanitizedMessage {
/// Sanitized legacy message
Legacy(LegacyMessage),
/// Sanitized version #0 message with dynamically loaded addresses
V0(v0::LoadedMessage),
V0(v0::LoadedMessage<'static>),
}
#[derive(PartialEq, Debug, Error, Eq, Clone)]
@ -69,7 +69,7 @@ impl SanitizedMessage {
pub fn header(&self) -> &MessageHeader {
match self {
Self::Legacy(message) => &message.header,
Self::V0(message) => &message.header,
Self::V0(loaded_msg) => &loaded_msg.message.header,
}
}
@ -93,7 +93,7 @@ impl SanitizedMessage {
pub fn recent_blockhash(&self) -> &Hash {
match self {
Self::Legacy(message) => &message.recent_blockhash,
Self::V0(message) => &message.recent_blockhash,
Self::V0(loaded_msg) => &loaded_msg.message.recent_blockhash,
}
}
@ -102,7 +102,7 @@ impl SanitizedMessage {
pub fn instructions(&self) -> &[CompiledInstruction] {
match self {
Self::Legacy(message) => &message.instructions,
Self::V0(message) => &message.instructions,
Self::V0(loaded_msg) => &loaded_msg.message.instructions,
}
}
@ -111,11 +111,7 @@ impl SanitizedMessage {
pub fn program_instructions_iter(
&self,
) -> impl Iterator<Item = (&Pubkey, &CompiledInstruction)> {
match self {
Self::Legacy(message) => message.instructions.iter(),
Self::V0(message) => message.instructions.iter(),
}
.map(move |ix| {
self.instructions().iter().map(move |ix| {
(
self.account_keys()
.get(usize::from(ix.program_id_index))
@ -347,8 +343,8 @@ mod tests {
assert_eq!(legacy_message.num_readonly_accounts(), 2);
let v0_message = SanitizedMessage::V0(v0::LoadedMessage {
message: v0::Message {
let v0_message = SanitizedMessage::V0(v0::LoadedMessage::new(
v0::Message {
header: MessageHeader {
num_required_signatures: 2,
num_readonly_signed_accounts: 1,
@ -357,11 +353,11 @@ mod tests {
account_keys: vec![key0, key1, key2, key3],
..v0::Message::default()
},
loaded_addresses: LoadedAddresses {
LoadedAddresses {
writable: vec![key4],
readonly: vec![key5],
},
});
));
assert_eq!(v0_message.num_readonly_accounts(), 3);
}
@ -414,8 +410,8 @@ mod tests {
})
.unwrap();
let v0_message = SanitizedMessage::V0(v0::LoadedMessage {
message: v0::Message {
let v0_message = SanitizedMessage::V0(v0::LoadedMessage::new(
v0::Message {
header: MessageHeader {
num_required_signatures: 1,
num_readonly_signed_accounts: 0,
@ -424,11 +420,11 @@ mod tests {
account_keys: vec![key0, key1],
..v0::Message::default()
},
loaded_addresses: LoadedAddresses {
LoadedAddresses {
writable: vec![key2],
readonly: vec![program_id],
},
});
));
for message in vec![legacy_message, v0_message] {
assert_eq!(

View File

@ -5,23 +5,16 @@ use {
pubkey::Pubkey,
sysvar,
},
std::{collections::HashSet, ops::Deref},
std::{borrow::Cow, collections::HashSet},
};
/// Combination of a version #0 message and its loaded addresses
#[derive(Debug, Clone)]
pub struct LoadedMessage {
pub struct LoadedMessage<'a> {
/// Message which loaded a collection of lookup table addresses
pub message: v0::Message,
pub message: Cow<'a, v0::Message>,
/// Addresses loaded with on-chain address lookup tables
pub loaded_addresses: LoadedAddresses,
}
impl Deref for LoadedMessage {
type Target = v0::Message;
fn deref(&self) -> &Self::Target {
&self.message
}
pub loaded_addresses: Cow<'a, LoadedAddresses>,
}
/// Collection of addresses loaded from on-chain lookup tables, split
@ -59,10 +52,29 @@ impl LoadedAddresses {
}
}
impl LoadedMessage {
/// Returns the list of account keys that are loaded for this message.
impl<'a> LoadedMessage<'a> {
pub fn new(message: v0::Message, loaded_addresses: LoadedAddresses) -> Self {
Self {
message: Cow::Owned(message),
loaded_addresses: Cow::Owned(loaded_addresses),
}
}
pub fn new_borrowed(message: &'a v0::Message, loaded_addresses: &'a LoadedAddresses) -> Self {
Self {
message: Cow::Borrowed(message),
loaded_addresses: Cow::Borrowed(loaded_addresses),
}
}
/// Returns the full list of static and dynamic account keys that are loaded for this message.
pub fn account_keys(&self) -> AccountKeys {
AccountKeys::new(&self.account_keys, Some(&self.loaded_addresses))
AccountKeys::new(&self.message.account_keys, Some(&self.loaded_addresses))
}
/// Returns the list of static account keys that are loaded for this message.
pub fn static_account_keys(&self) -> &[Pubkey] {
&self.message.account_keys
}
/// Returns true if any account keys are duplicates
@ -107,6 +119,10 @@ impl LoadedMessage {
false
}
pub fn is_signer(&self, i: usize) -> bool {
i < self.message.header.num_required_signatures as usize
}
/// Returns true if the account at the specified index is called as a program by an instruction
pub fn is_key_called_as_program(&self, key_index: usize) -> bool {
if let Ok(key_index) = u8::try_from(key_index) {
@ -135,7 +151,7 @@ mod tests {
itertools::Itertools,
};
fn check_test_loaded_message() -> (LoadedMessage, [Pubkey; 6]) {
fn check_test_loaded_message() -> (LoadedMessage<'static>, [Pubkey; 6]) {
let key0 = Pubkey::new_unique();
let key1 = Pubkey::new_unique();
let key2 = Pubkey::new_unique();
@ -143,8 +159,8 @@ mod tests {
let key4 = Pubkey::new_unique();
let key5 = Pubkey::new_unique();
let message = LoadedMessage {
message: v0::Message {
let message = LoadedMessage::new(
v0::Message {
header: MessageHeader {
num_required_signatures: 2,
num_readonly_signed_accounts: 1,
@ -153,11 +169,11 @@ mod tests {
account_keys: vec![key0, key1, key2, key3],
..v0::Message::default()
},
loaded_addresses: LoadedAddresses {
LoadedAddresses {
writable: vec![key4],
readonly: vec![key5],
},
};
);
(message, [key0, key1, key2, key3, key4, key5])
}
@ -171,15 +187,17 @@ mod tests {
#[test]
fn test_has_duplicates_with_dupe_keys() {
let create_message_with_dupe_keys = |mut keys: Vec<Pubkey>| LoadedMessage {
message: v0::Message {
account_keys: keys.split_off(2),
..v0::Message::default()
},
loaded_addresses: LoadedAddresses {
writable: keys.split_off(2),
readonly: keys,
},
let create_message_with_dupe_keys = |mut keys: Vec<Pubkey>| {
LoadedMessage::new(
v0::Message {
account_keys: keys.split_off(2),
..v0::Message::default()
},
LoadedAddresses {
writable: keys.split_off(2),
readonly: keys,
},
)
};
let key0 = Pubkey::new_unique();
@ -212,11 +230,11 @@ mod tests {
fn test_is_writable() {
let mut message = check_test_loaded_message().0;
message.message.account_keys[0] = sysvar::clock::id();
message.message.to_mut().account_keys[0] = sysvar::clock::id();
assert!(message.is_writable_index(0));
assert!(!message.is_writable(0));
message.message.account_keys[0] = system_program::id();
message.message.to_mut().account_keys[0] = system_program::id();
assert!(message.is_writable_index(0));
assert!(!message.is_writable(0));
}
@ -226,8 +244,8 @@ mod tests {
let key0 = Pubkey::new_unique();
let key1 = Pubkey::new_unique();
let key2 = Pubkey::new_unique();
let message = LoadedMessage {
message: v0::Message {
let message = LoadedMessage::new(
v0::Message {
header: MessageHeader {
num_required_signatures: 1,
num_readonly_signed_accounts: 0,
@ -241,11 +259,11 @@ mod tests {
}],
..v0::Message::default()
},
loaded_addresses: LoadedAddresses {
LoadedAddresses {
writable: vec![key1, key2],
readonly: vec![],
},
};
);
assert!(message.is_writable_index(2));
assert!(!message.is_writable(2));

View File

@ -65,10 +65,11 @@ impl SanitizedTransaction {
let signatures = tx.signatures;
let message = match tx.message {
VersionedMessage::Legacy(message) => SanitizedMessage::Legacy(message),
VersionedMessage::V0(message) => SanitizedMessage::V0(v0::LoadedMessage {
loaded_addresses: address_loader.load_addresses(&message.address_table_lookups)?,
message,
}),
VersionedMessage::V0(message) => {
let loaded_addresses =
address_loader.load_addresses(&message.address_table_lookups)?;
SanitizedMessage::V0(v0::LoadedMessage::new(message, loaded_addresses))
}
};
let is_simple_vote_tx = is_simple_vote_tx.unwrap_or_else(|| {
@ -139,7 +140,7 @@ impl SanitizedTransaction {
match &self.message {
SanitizedMessage::V0(sanitized_msg) => VersionedTransaction {
signatures,
message: VersionedMessage::V0(sanitized_msg.message.clone()),
message: VersionedMessage::V0(v0::Message::clone(&sanitized_msg.message)),
},
SanitizedMessage::Legacy(message) => VersionedTransaction {
signatures,
@ -191,7 +192,7 @@ impl SanitizedTransaction {
pub fn get_loaded_addresses(&self) -> LoadedAddresses {
match &self.message {
SanitizedMessage::Legacy(_) => LoadedAddresses::default(),
SanitizedMessage::V0(message) => message.loaded_addresses.clone(),
SanitizedMessage::V0(message) => LoadedAddresses::clone(&message.loaded_addresses),
}
}
@ -204,7 +205,7 @@ impl SanitizedTransaction {
fn message_data(&self) -> Vec<u8> {
match &self.message {
SanitizedMessage::Legacy(message) => message.serialize(),
SanitizedMessage::V0(message) => message.serialize(),
SanitizedMessage::V0(loaded_msg) => loaded_msg.message.serialize(),
}
}

View File

@ -17,6 +17,24 @@ use {
std::cmp::Ordering,
};
/// Type that serializes to the string "legacy"
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum Legacy {
Legacy,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase", untagged)]
pub enum TransactionVersion {
Legacy(Legacy),
Number(u8),
}
impl TransactionVersion {
pub const LEGACY: Self = Self::Legacy(Legacy::Legacy);
}
// NOTE: Serialization-related changes must be paired with the direct read at sigverify.
/// An atomic transaction
#[derive(Debug, PartialEq, Default, Eq, Clone, Serialize, Deserialize, AbiExample)]
@ -93,6 +111,14 @@ impl VersionedTransaction {
})
}
/// Returns the version of the transaction
pub fn version(&self) -> TransactionVersion {
match self.message {
VersionedMessage::Legacy(_) => TransactionVersion::LEGACY,
VersionedMessage::V0(_) => TransactionVersion::Number(0),
}
}
/// Returns a legacy transaction if the transaction message is legacy.
pub fn into_legacy_transaction(self) -> Option<Transaction> {
match self.message {