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:
@ -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!(
|
||||
|
@ -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));
|
||||
|
@ -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(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
Reference in New Issue
Block a user