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:
parent
e790d0fc53
commit
3114c199bd
|
@ -5505,6 +5505,7 @@ dependencies = [
|
|||
"serial_test",
|
||||
"soketto",
|
||||
"solana-account-decoder",
|
||||
"solana-address-lookup-table-program",
|
||||
"solana-client",
|
||||
"solana-entry",
|
||||
"solana-faucet",
|
||||
|
|
|
@ -2042,6 +2042,7 @@ pub fn process_transaction_history(
|
|||
RpcTransactionConfig {
|
||||
encoding: Some(UiTransactionEncoding::Base64),
|
||||
commitment: Some(CommitmentConfig::confirmed()),
|
||||
max_supported_transaction_version: None,
|
||||
},
|
||||
) {
|
||||
Ok(confirmed_transaction) => {
|
||||
|
|
|
@ -559,6 +559,7 @@ pub fn process_confirm(
|
|||
RpcTransactionConfig {
|
||||
encoding: Some(UiTransactionEncoding::Base64),
|
||||
commitment: Some(CommitmentConfig::confirmed()),
|
||||
max_supported_transaction_version: None,
|
||||
},
|
||||
) {
|
||||
Ok(confirmed_transaction) => {
|
||||
|
|
|
@ -15,7 +15,7 @@ use {
|
|||
solana_ledger::{blockstore::Blockstore, get_tmp_ledger_path},
|
||||
solana_rpc::{
|
||||
optimistically_confirmed_bank_tracker::OptimisticallyConfirmedBank,
|
||||
rpc::create_test_transactions_and_populate_blockstore,
|
||||
rpc::{create_test_transaction_entries, populate_blockstore_for_tests},
|
||||
rpc_pubsub_service::{PubSubConfig, PubSubService},
|
||||
rpc_subscriptions::RpcSubscriptions,
|
||||
},
|
||||
|
@ -36,7 +36,9 @@ use {
|
|||
},
|
||||
solana_streamer::socket::SocketAddrSpace,
|
||||
solana_test_validator::TestValidator,
|
||||
solana_transaction_status::{ConfirmedBlock, TransactionDetails, UiTransactionEncoding},
|
||||
solana_transaction_status::{
|
||||
BlockEncodingOptions, ConfirmedBlock, TransactionDetails, UiTransactionEncoding,
|
||||
},
|
||||
std::{
|
||||
collections::HashSet,
|
||||
net::{IpAddr, SocketAddr},
|
||||
|
@ -230,9 +232,12 @@ fn test_block_subscription() {
|
|||
let max_complete_transaction_status_slot = Arc::new(AtomicU64::new(blockstore.max_root()));
|
||||
bank.transfer(rent_exempt_amount, &alice, &keypair2.pubkey())
|
||||
.unwrap();
|
||||
let _confirmed_block_signatures = create_test_transactions_and_populate_blockstore(
|
||||
vec![&alice, &keypair1, &keypair2, &keypair3],
|
||||
0,
|
||||
populate_blockstore_for_tests(
|
||||
create_test_transaction_entries(
|
||||
vec![&alice, &keypair1, &keypair2, &keypair3],
|
||||
bank.clone(),
|
||||
)
|
||||
.0,
|
||||
bank,
|
||||
blockstore.clone(),
|
||||
max_complete_transaction_status_slot,
|
||||
|
@ -270,6 +275,7 @@ fn test_block_subscription() {
|
|||
encoding: Some(UiTransactionEncoding::Json),
|
||||
transaction_details: Some(TransactionDetails::Signatures),
|
||||
show_rewards: None,
|
||||
max_supported_transaction_version: None,
|
||||
}),
|
||||
)
|
||||
.unwrap();
|
||||
|
@ -281,14 +287,17 @@ fn test_block_subscription() {
|
|||
match maybe_actual {
|
||||
Ok(actual) => {
|
||||
let versioned_block = blockstore.get_complete_block(slot, false).unwrap();
|
||||
let legacy_block = ConfirmedBlock::from(versioned_block)
|
||||
.into_legacy_block()
|
||||
let confirmed_block = ConfirmedBlock::from(versioned_block);
|
||||
let block = confirmed_block
|
||||
.encode_with_options(
|
||||
UiTransactionEncoding::Json,
|
||||
BlockEncodingOptions {
|
||||
transaction_details: TransactionDetails::Signatures,
|
||||
show_rewards: false,
|
||||
max_supported_transaction_version: None,
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
let block = legacy_block.configure(
|
||||
UiTransactionEncoding::Json,
|
||||
TransactionDetails::Signatures,
|
||||
false,
|
||||
);
|
||||
assert_eq!(actual.value.slot, slot);
|
||||
assert!(block.eq(&actual.value.block.unwrap()));
|
||||
}
|
||||
|
|
|
@ -28,7 +28,7 @@ use {
|
|||
pubkey::Pubkey,
|
||||
signature::Signature,
|
||||
sysvar::epoch_schedule::EpochSchedule,
|
||||
transaction::{self, Transaction, TransactionError},
|
||||
transaction::{self, Transaction, TransactionError, TransactionVersion},
|
||||
},
|
||||
solana_transaction_status::{
|
||||
EncodedConfirmedBlock, EncodedConfirmedTransactionWithStatusMeta, EncodedTransaction,
|
||||
|
@ -192,6 +192,7 @@ impl RpcSender for MockSender {
|
|||
"getTransaction" => serde_json::to_value(EncodedConfirmedTransactionWithStatusMeta {
|
||||
slot: 2,
|
||||
transaction: EncodedTransactionWithStatusMeta {
|
||||
version: Some(TransactionVersion::LEGACY),
|
||||
transaction: EncodedTransaction::Json(
|
||||
UiTransaction {
|
||||
signatures: vec!["3AsdoALgZFuq2oUVWrDYhg2pNeaLJKPLf8hU2mQ6U8qJxeJ6hsrPVpMn9ma39DtfYCrDQSvngWRP8NnTpEhezJpE".to_string()],
|
||||
|
@ -213,6 +214,7 @@ impl RpcSender for MockSender {
|
|||
accounts: vec![0, 1],
|
||||
data: "3Bxs49DitAvXtoDR".to_string(),
|
||||
}],
|
||||
address_table_lookups: None,
|
||||
})
|
||||
}),
|
||||
meta: Some(UiTransactionStatusMeta {
|
||||
|
@ -226,6 +228,7 @@ impl RpcSender for MockSender {
|
|||
pre_token_balances: None,
|
||||
post_token_balances: None,
|
||||
rewards: None,
|
||||
loaded_addresses: None,
|
||||
}),
|
||||
},
|
||||
block_time: Some(1628633791),
|
||||
|
@ -381,6 +384,7 @@ impl RpcSender for MockSender {
|
|||
UiTransactionEncoding::Base58,
|
||||
),
|
||||
meta: None,
|
||||
version: Some(TransactionVersion::LEGACY),
|
||||
}],
|
||||
rewards: Rewards::new(),
|
||||
block_time: None,
|
||||
|
|
|
@ -2411,6 +2411,7 @@ impl RpcClient {
|
|||
/// transaction_details: Some(TransactionDetails::None),
|
||||
/// rewards: Some(true),
|
||||
/// commitment: None,
|
||||
/// max_supported_transaction_version: Some(0),
|
||||
/// };
|
||||
/// let block = rpc_client.get_block_with_config(
|
||||
/// slot,
|
||||
|
@ -3051,6 +3052,7 @@ impl RpcClient {
|
|||
/// let config = RpcTransactionConfig {
|
||||
/// encoding: Some(UiTransactionEncoding::Json),
|
||||
/// commitment: Some(CommitmentConfig::confirmed()),
|
||||
/// max_supported_transaction_version: Some(0),
|
||||
/// };
|
||||
/// let transaction = rpc_client.get_transaction_with_config(
|
||||
/// &signature,
|
||||
|
|
|
@ -2034,6 +2034,7 @@ impl RpcClient {
|
|||
/// transaction_details: Some(TransactionDetails::None),
|
||||
/// rewards: Some(true),
|
||||
/// commitment: None,
|
||||
/// max_supported_transaction_version: Some(0),
|
||||
/// };
|
||||
/// let block = rpc_client.get_block_with_config(
|
||||
/// slot,
|
||||
|
@ -2596,6 +2597,7 @@ impl RpcClient {
|
|||
/// let config = RpcTransactionConfig {
|
||||
/// encoding: Some(UiTransactionEncoding::Json),
|
||||
/// commitment: Some(CommitmentConfig::confirmed()),
|
||||
/// max_supported_transaction_version: Some(0),
|
||||
/// };
|
||||
/// let transaction = rpc_client.get_transaction_with_config(
|
||||
/// &signature,
|
||||
|
|
|
@ -197,6 +197,7 @@ pub struct RpcBlockSubscribeConfig {
|
|||
pub encoding: Option<UiTransactionEncoding>,
|
||||
pub transaction_details: Option<TransactionDetails>,
|
||||
pub show_rewards: Option<bool>,
|
||||
pub max_supported_transaction_version: Option<u8>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
|
||||
|
@ -248,6 +249,7 @@ pub struct RpcBlockConfig {
|
|||
pub rewards: Option<bool>,
|
||||
#[serde(flatten)]
|
||||
pub commitment: Option<CommitmentConfig>,
|
||||
pub max_supported_transaction_version: Option<u8>,
|
||||
}
|
||||
|
||||
impl EncodingConfig for RpcBlockConfig {
|
||||
|
@ -288,6 +290,7 @@ pub struct RpcTransactionConfig {
|
|||
pub encoding: Option<UiTransactionEncoding>,
|
||||
#[serde(flatten)]
|
||||
pub commitment: Option<CommitmentConfig>,
|
||||
pub max_supported_transaction_version: Option<u8>,
|
||||
}
|
||||
|
||||
impl EncodingConfig for RpcTransactionConfig {
|
||||
|
|
|
@ -3,6 +3,7 @@ use {
|
|||
crate::rpc_response::RpcSimulateTransactionResult,
|
||||
jsonrpc_core::{Error, ErrorCode},
|
||||
solana_sdk::clock::Slot,
|
||||
solana_transaction_status::EncodeError,
|
||||
thiserror::Error,
|
||||
};
|
||||
|
||||
|
@ -59,7 +60,7 @@ pub enum RpcCustomError {
|
|||
#[error("BlockStatusNotAvailableYet")]
|
||||
BlockStatusNotAvailableYet { slot: Slot },
|
||||
#[error("UnsupportedTransactionVersion")]
|
||||
UnsupportedTransactionVersion,
|
||||
UnsupportedTransactionVersion(u8),
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
|
@ -68,6 +69,16 @@ pub struct NodeUnhealthyErrorData {
|
|||
pub num_slots_behind: Option<Slot>,
|
||||
}
|
||||
|
||||
impl From<EncodeError> for RpcCustomError {
|
||||
fn from(err: EncodeError) -> Self {
|
||||
match err {
|
||||
EncodeError::UnsupportedTransactionVersion(version) => {
|
||||
Self::UnsupportedTransactionVersion(version)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RpcCustomError> for Error {
|
||||
fn from(e: RpcCustomError) -> Self {
|
||||
match e {
|
||||
|
@ -172,9 +183,9 @@ impl From<RpcCustomError> for Error {
|
|||
message: format!("Block status not yet available for slot {}", slot),
|
||||
data: None,
|
||||
},
|
||||
RpcCustomError::UnsupportedTransactionVersion => Self {
|
||||
RpcCustomError::UnsupportedTransactionVersion(version) => Self {
|
||||
code: ErrorCode::ServerError(JSON_RPC_SERVER_ERROR_UNSUPPORTED_TRANSACTION_VERSION),
|
||||
message: "Versioned transactions are not supported".to_string(),
|
||||
message: format!("Transaction version ({}) is not supported", version),
|
||||
data: None,
|
||||
},
|
||||
}
|
||||
|
|
|
@ -71,6 +71,7 @@ impl From<RpcConfirmedBlockConfig> for RpcBlockConfig {
|
|||
transaction_details: config.transaction_details,
|
||||
rewards: config.rewards,
|
||||
commitment: config.commitment,
|
||||
max_supported_transaction_version: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -98,6 +99,7 @@ impl From<RpcConfirmedTransactionConfig> for RpcTransactionConfig {
|
|||
Self {
|
||||
encoding: config.encoding,
|
||||
commitment: config.commitment,
|
||||
max_supported_transaction_version: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -432,8 +432,8 @@ pub enum RpcBlockUpdateError {
|
|||
#[error("block store error")]
|
||||
BlockStoreError,
|
||||
|
||||
#[error("unsupported transaction version")]
|
||||
UnsupportedTransactionVersion,
|
||||
#[error("unsupported transaction version ({0})")]
|
||||
UnsupportedTransactionVersion(u8),
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
|
|
|
@ -3384,9 +3384,7 @@ mod tests {
|
|||
account_address: Pubkey,
|
||||
address_lookup_table: AddressLookupTable<'static>,
|
||||
) -> AccountSharedData {
|
||||
let mut data = Vec::new();
|
||||
address_lookup_table.serialize_for_tests(&mut data).unwrap();
|
||||
|
||||
let data = address_lookup_table.serialize_for_tests().unwrap();
|
||||
let mut account =
|
||||
AccountSharedData::new(1, data.len(), &solana_address_lookup_table_program::id());
|
||||
account.set_data(data);
|
||||
|
|
|
@ -3129,7 +3129,7 @@ pub mod tests {
|
|||
},
|
||||
solana_rpc::{
|
||||
optimistically_confirmed_bank_tracker::OptimisticallyConfirmedBank,
|
||||
rpc::create_test_transactions_and_populate_blockstore,
|
||||
rpc::{create_test_transaction_entries, populate_blockstore_for_tests},
|
||||
},
|
||||
solana_runtime::{
|
||||
accounts_background_service::AbsRequestSender,
|
||||
|
@ -3998,15 +3998,18 @@ pub mod tests {
|
|||
let bank1 = Arc::new(Bank::new_from_parent(&bank0, &Pubkey::default(), 1));
|
||||
let slot = bank1.slot();
|
||||
|
||||
let mut test_signatures_iter = create_test_transactions_and_populate_blockstore(
|
||||
let (entries, test_signatures) = create_test_transaction_entries(
|
||||
vec![&mint_keypair, &keypair1, &keypair2, &keypair3],
|
||||
bank0.slot(),
|
||||
bank1.clone(),
|
||||
);
|
||||
populate_blockstore_for_tests(
|
||||
entries,
|
||||
bank1,
|
||||
blockstore.clone(),
|
||||
Arc::new(AtomicU64::default()),
|
||||
)
|
||||
.into_iter();
|
||||
);
|
||||
|
||||
let mut test_signatures_iter = test_signatures.into_iter();
|
||||
let confirmed_block = blockstore.get_rooted_block(slot, false).unwrap();
|
||||
let actual_tx_results: Vec<_> = confirmed_block
|
||||
.transactions
|
||||
|
|
|
@ -389,6 +389,7 @@ Returns identity and transaction information about a confirmed block in the ledg
|
|||
- (optional) `transactionDetails: <string>` - level of transaction detail to return, either "full", "signatures", or "none". If parameter not provided, the default detail level is "full".
|
||||
- (optional) `rewards: bool` - whether to populate the `rewards` array. If parameter not provided, the default includes rewards.
|
||||
- (optional) [Commitment](jsonrpc-api.md#configuring-state-commitment); "processed" is not supported. If parameter not provided, the default is "finalized".
|
||||
- (optional) `maxSupportedTransactionVersion: <number>` - set the max transaction version to return in responses. If the requested block contains a transaction with a higher version, an error will be returned.
|
||||
|
||||
#### Results:
|
||||
|
||||
|
@ -413,6 +414,10 @@ The result field will be an object with the following fields:
|
|||
- DEPRECATED: `status: <object>` - Transaction status
|
||||
- `"Ok": <null>` - Transaction was successful
|
||||
- `"Err": <ERR>` - Transaction failed with TransactionError
|
||||
- `loadedAddresses: <object|undefined>` - Transaction addresses loaded from address lookup tables. Undefined if `maxSupportedTransactionVersion` is not set in request params.
|
||||
- `writable: <array[string]>` - Ordered list of base-58 encoded addresses for writable loaded accounts
|
||||
- `readonly: <array[string]>` - Ordered list of base-58 encoded addresses for readonly loaded accounts
|
||||
- `version: <"legacy"|number|undefined>` - Transaction version. Undefined if `maxSupportedTransactionVersion` is not set in request params.
|
||||
- `signatures: <array>` - present if "signatures" are requested for transaction details; an array of signatures strings, corresponding to the transaction order in the block
|
||||
- `rewards: <array>` - present if rewards are requested; an array of JSON objects containing:
|
||||
- `pubkey: <string>` - The public key, as base-58 encoded string, of the account that received the reward
|
||||
|
@ -559,6 +564,10 @@ The JSON structure of a transaction is defined as follows:
|
|||
- `programIdIndex: <number>` - Index into the `message.accountKeys` array indicating the program account that executes this instruction.
|
||||
- `accounts: <array[number]>` - List of ordered indices into the `message.accountKeys` array indicating which accounts to pass to the program.
|
||||
- `data: <string>` - The program input data encoded in a base-58 string.
|
||||
- `addressTableLookups: <array[object]|undefined>` - List of address table lookups used by a transaction to dynamically load addresses from on-chain address lookup tables. Undefined if `maxSupportedTransactionVersion` is not set.
|
||||
- `accountKey: <string>` - base-58 encoded public key for an address lookup table account.
|
||||
- `writableIndexes: <array[number]>` - List of indices used to load addresses of writable accounts from a lookup table.
|
||||
- `readonlyIndexes: <array[number]>` - List of indices used to load addresses of readonly accounts from a lookup table.
|
||||
|
||||
#### Inner Instructions Structure
|
||||
|
||||
|
@ -2313,7 +2322,7 @@ Returns the slot leaders for a given slot range
|
|||
|
||||
#### Results:
|
||||
|
||||
- `<array<string>>` - Node identity public keys as base-58 encoded strings
|
||||
- `<array[string]>` - Node identity public keys as base-58 encoded strings
|
||||
|
||||
#### Example:
|
||||
|
||||
|
@ -2847,6 +2856,7 @@ Returns transaction details for a confirmed transaction
|
|||
- (optional) `encoding: <string>` - encoding for each returned Transaction, either "json", "jsonParsed", "base58" (_slow_), "base64". If parameter not provided, the default encoding is "json".
|
||||
"jsonParsed" encoding attempts to use program-specific instruction parsers to return more human-readable and explicit data in the `transaction.message.instructions` list. If "jsonParsed" is requested but a parser cannot be found, the instruction falls back to regular JSON encoding (`accounts`, `data`, and `programIdIndex` fields).
|
||||
- (optional) [Commitment](jsonrpc-api.md#configuring-state-commitment); "processed" is not supported. If parameter not provided, the default is "finalized".
|
||||
- (optional) `maxSupportedTransactionVersion: <number>` - set the max transaction version to return in responses. If the requested transaction is a higher version, an error will be returned.
|
||||
|
||||
#### Results:
|
||||
|
||||
|
@ -2873,6 +2883,10 @@ Returns transaction details for a confirmed transaction
|
|||
- `postBalance: <u64>` - account balance in lamports after the reward was applied
|
||||
- `rewardType: <string>` - type of reward: currently only "rent", other types may be added in the future
|
||||
- `commission: <u8|undefined>` - vote account commission when the reward was credited, only present for voting and staking rewards
|
||||
- `loadedAddresses: <object|undefined>` - Transaction addresses loaded from address lookup tables. Undefined if `maxSupportedTransactionVersion` is not set in request params.
|
||||
- `writable: <array[string]>` - Ordered list of base-58 encoded addresses for writable loaded accounts
|
||||
- `readonly: <array[string]>` - Ordered list of base-58 encoded addresses for readonly loaded accounts
|
||||
- `version: <"legacy"|number|undefined>` - Transaction version. Undefined if `maxSupportedTransactionVersion` is not set in request params.
|
||||
|
||||
#### Example:
|
||||
|
||||
|
|
|
@ -16,7 +16,10 @@ use {
|
|||
},
|
||||
solana_ledger::{blockstore::Blockstore, blockstore_db::AccessType},
|
||||
solana_sdk::{clock::Slot, pubkey::Pubkey, signature::Signature},
|
||||
solana_transaction_status::{Encodable, LegacyConfirmedBlock, UiTransactionEncoding},
|
||||
solana_transaction_status::{
|
||||
BlockEncodingOptions, Encodable, EncodeError, LegacyConfirmedBlock, TransactionDetails,
|
||||
UiTransactionEncoding,
|
||||
},
|
||||
std::{
|
||||
collections::HashSet,
|
||||
path::Path,
|
||||
|
@ -72,12 +75,26 @@ async fn block(slot: Slot, output_format: OutputFormat) -> Result<(), Box<dyn st
|
|||
.map_err(|err| format!("Failed to connect to storage: {:?}", err))?;
|
||||
|
||||
let confirmed_block = bigtable.get_confirmed_block(slot).await?;
|
||||
let legacy_block = confirmed_block
|
||||
.into_legacy_block()
|
||||
.ok_or_else(|| "Failed to read versioned transaction in block".to_string())?;
|
||||
let encoded_block = confirmed_block
|
||||
.encode_with_options(
|
||||
UiTransactionEncoding::Base64,
|
||||
BlockEncodingOptions {
|
||||
transaction_details: TransactionDetails::Full,
|
||||
show_rewards: true,
|
||||
max_supported_transaction_version: None,
|
||||
},
|
||||
)
|
||||
.map_err(|err| match err {
|
||||
EncodeError::UnsupportedTransactionVersion(version) => {
|
||||
format!(
|
||||
"Failed to process unsupported transaction version ({}) in block",
|
||||
version
|
||||
)
|
||||
}
|
||||
})?;
|
||||
|
||||
let cli_block = CliBlock {
|
||||
encoded_confirmed_block: legacy_block.encode(UiTransactionEncoding::Base64),
|
||||
encoded_confirmed_block: encoded_block.into(),
|
||||
slot,
|
||||
};
|
||||
println!("{}", output_format.formatted_string(&cli_block));
|
||||
|
|
|
@ -76,9 +76,7 @@ pub async fn add_lookup_table_account(
|
|||
account_address: Pubkey,
|
||||
address_lookup_table: AddressLookupTable<'static>,
|
||||
) -> AccountSharedData {
|
||||
let mut data = Vec::new();
|
||||
address_lookup_table.serialize_for_tests(&mut data).unwrap();
|
||||
|
||||
let data = address_lookup_table.serialize_for_tests().unwrap();
|
||||
let rent = context.banks_client.get_rent().await.unwrap();
|
||||
let rent_exempt_balance = rent.minimum_balance(data.len());
|
||||
|
||||
|
|
|
@ -178,13 +178,13 @@ impl<'a> AddressLookupTable<'a> {
|
|||
}
|
||||
|
||||
/// Serialize an address table including its addresses
|
||||
pub fn serialize_for_tests(self, data: &mut Vec<u8>) -> Result<(), InstructionError> {
|
||||
data.resize(LOOKUP_TABLE_META_SIZE, 0);
|
||||
Self::overwrite_meta_data(data, self.meta)?;
|
||||
pub fn serialize_for_tests(self) -> Result<Vec<u8>, InstructionError> {
|
||||
let mut data = vec![0; LOOKUP_TABLE_META_SIZE];
|
||||
Self::overwrite_meta_data(&mut data, self.meta)?;
|
||||
self.addresses.iter().for_each(|address| {
|
||||
data.extend_from_slice(address.as_ref());
|
||||
});
|
||||
Ok(())
|
||||
Ok(data)
|
||||
}
|
||||
|
||||
/// Efficiently deserialize an address table without allocating
|
||||
|
@ -352,9 +352,8 @@ mod tests {
|
|||
fn test_case(num_addresses: usize) {
|
||||
let lookup_table_meta = LookupTableMeta::new_for_tests();
|
||||
let address_table = AddressLookupTable::new_for_tests(lookup_table_meta, num_addresses);
|
||||
let mut address_table_data = Vec::new();
|
||||
AddressLookupTable::serialize_for_tests(address_table.clone(), &mut address_table_data)
|
||||
.unwrap();
|
||||
let address_table_data =
|
||||
AddressLookupTable::serialize_for_tests(address_table.clone()).unwrap();
|
||||
assert_eq!(
|
||||
AddressLookupTable::deserialize(&address_table_data).unwrap(),
|
||||
address_table,
|
||||
|
|
|
@ -56,6 +56,7 @@ tokio-util = { version = "0.6", features = ["codec", "compat"] }
|
|||
|
||||
[dev-dependencies]
|
||||
serial_test = "0.6.0"
|
||||
solana-address-lookup-table-program = { path = "../programs/address-lookup-table", version = "=1.10.1" }
|
||||
solana-net-utils = { path = "../net-utils", version = "=1.10.1" }
|
||||
solana-stake-program = { path = "../programs/stake", version = "=1.10.1" }
|
||||
symlink = "0.1.0"
|
||||
|
|
301
rpc/src/rpc.rs
301
rpc/src/rpc.rs
|
@ -29,6 +29,7 @@ use {
|
|||
},
|
||||
rpc_response::{Response as RpcResponse, *},
|
||||
},
|
||||
solana_entry::entry::Entry,
|
||||
solana_faucet::faucet::request_airdrop_transaction,
|
||||
solana_gossip::{cluster_info::ClusterInfo, contact_info::ContactInfo},
|
||||
solana_ledger::{
|
||||
|
@ -69,8 +70,7 @@ use {
|
|||
system_instruction,
|
||||
sysvar::stake_history,
|
||||
transaction::{
|
||||
self, DisabledAddressLoader, SanitizedTransaction, TransactionError,
|
||||
VersionedTransaction,
|
||||
self, AddressLoader, SanitizedTransaction, TransactionError, VersionedTransaction,
|
||||
},
|
||||
},
|
||||
solana_send_transaction_service::{
|
||||
|
@ -80,9 +80,9 @@ use {
|
|||
solana_storage_bigtable::Error as StorageError,
|
||||
solana_streamer::socket::SocketAddrSpace,
|
||||
solana_transaction_status::{
|
||||
ConfirmedBlock, ConfirmedTransactionStatusWithSignature,
|
||||
ConfirmedTransactionWithStatusMeta, Encodable, EncodedConfirmedTransactionWithStatusMeta,
|
||||
Reward, RewardType, TransactionConfirmationStatus, TransactionStatus, UiConfirmedBlock,
|
||||
BlockEncodingOptions, ConfirmedBlock, ConfirmedTransactionStatusWithSignature,
|
||||
ConfirmedTransactionWithStatusMeta, EncodedConfirmedTransactionWithStatusMeta, Reward,
|
||||
RewardType, TransactionConfirmationStatus, TransactionStatus, UiConfirmedBlock,
|
||||
UiTransactionEncoding,
|
||||
},
|
||||
solana_vote_program::vote_state::{VoteState, MAX_LOCKOUT_HISTORY},
|
||||
|
@ -991,8 +991,11 @@ impl JsonRpcRequestProcessor {
|
|||
.map(|config| config.convert_to_current())
|
||||
.unwrap_or_default();
|
||||
let encoding = config.encoding.unwrap_or(UiTransactionEncoding::Json);
|
||||
let transaction_details = config.transaction_details.unwrap_or_default();
|
||||
let show_rewards = config.rewards.unwrap_or(true);
|
||||
let encoding_options = BlockEncodingOptions {
|
||||
transaction_details: config.transaction_details.unwrap_or_default(),
|
||||
show_rewards: config.rewards.unwrap_or(true),
|
||||
max_supported_transaction_version: config.max_supported_transaction_version,
|
||||
};
|
||||
let commitment = config.commitment.unwrap_or_default();
|
||||
check_is_at_least_confirmed(commitment)?;
|
||||
|
||||
|
@ -1007,31 +1010,29 @@ impl JsonRpcRequestProcessor {
|
|||
self.check_status_is_complete(slot)?;
|
||||
let result = self.blockstore.get_rooted_block(slot, true);
|
||||
self.check_blockstore_root(&result, slot)?;
|
||||
let configure_block = |confirmed_block: ConfirmedBlock| {
|
||||
let legacy_block = confirmed_block
|
||||
.into_legacy_block()
|
||||
.ok_or(RpcCustomError::UnsupportedTransactionVersion)?;
|
||||
let mut confirmed_block =
|
||||
legacy_block.configure(encoding, transaction_details, show_rewards);
|
||||
let encode_block = |confirmed_block: ConfirmedBlock| -> Result<UiConfirmedBlock> {
|
||||
let mut encoded_block = confirmed_block
|
||||
.encode_with_options(encoding, encoding_options)
|
||||
.map_err(RpcCustomError::from)?;
|
||||
if slot == 0 {
|
||||
confirmed_block.block_time = Some(self.genesis_creation_time());
|
||||
confirmed_block.block_height = Some(0);
|
||||
encoded_block.block_time = Some(self.genesis_creation_time());
|
||||
encoded_block.block_height = Some(0);
|
||||
}
|
||||
Ok(confirmed_block)
|
||||
Ok(encoded_block)
|
||||
};
|
||||
if result.is_err() {
|
||||
if let Some(bigtable_ledger_storage) = &self.bigtable_ledger_storage {
|
||||
let bigtable_result =
|
||||
bigtable_ledger_storage.get_confirmed_block(slot).await;
|
||||
self.check_bigtable_result(&bigtable_result)?;
|
||||
return bigtable_result.ok().map(configure_block).transpose();
|
||||
return bigtable_result.ok().map(encode_block).transpose();
|
||||
}
|
||||
}
|
||||
self.check_slot_cleaned_up(&result, slot)?;
|
||||
return result
|
||||
.ok()
|
||||
.map(ConfirmedBlock::from)
|
||||
.map(configure_block)
|
||||
.map(encode_block)
|
||||
.transpose();
|
||||
} else if commitment.is_confirmed() {
|
||||
// Check if block is confirmed
|
||||
|
@ -1042,27 +1043,26 @@ impl JsonRpcRequestProcessor {
|
|||
return result
|
||||
.ok()
|
||||
.map(ConfirmedBlock::from)
|
||||
.map(|confirmed_block| -> Result<UiConfirmedBlock> {
|
||||
let mut legacy_block = confirmed_block
|
||||
.into_legacy_block()
|
||||
.ok_or(RpcCustomError::UnsupportedTransactionVersion)?;
|
||||
|
||||
if legacy_block.block_time.is_none()
|
||||
|| legacy_block.block_height.is_none()
|
||||
.map(|mut confirmed_block| -> Result<UiConfirmedBlock> {
|
||||
if confirmed_block.block_time.is_none()
|
||||
|| confirmed_block.block_height.is_none()
|
||||
{
|
||||
let r_bank_forks = self.bank_forks.read().unwrap();
|
||||
let bank = r_bank_forks.get(slot).cloned();
|
||||
if let Some(bank) = bank {
|
||||
if legacy_block.block_time.is_none() {
|
||||
legacy_block.block_time = Some(bank.clock().unix_timestamp);
|
||||
if confirmed_block.block_time.is_none() {
|
||||
confirmed_block.block_time =
|
||||
Some(bank.clock().unix_timestamp);
|
||||
}
|
||||
if legacy_block.block_height.is_none() {
|
||||
legacy_block.block_height = Some(bank.block_height());
|
||||
if confirmed_block.block_height.is_none() {
|
||||
confirmed_block.block_height = Some(bank.block_height());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(legacy_block.configure(encoding, transaction_details, show_rewards))
|
||||
Ok(confirmed_block
|
||||
.encode_with_options(encoding, encoding_options)
|
||||
.map_err(RpcCustomError::from)?)
|
||||
})
|
||||
.transpose();
|
||||
}
|
||||
|
@ -1384,6 +1384,7 @@ impl JsonRpcRequestProcessor {
|
|||
.map(|config| config.convert_to_current())
|
||||
.unwrap_or_default();
|
||||
let encoding = config.encoding.unwrap_or(UiTransactionEncoding::Json);
|
||||
let max_supported_transaction_version = config.max_supported_transaction_version;
|
||||
let commitment = config.commitment.unwrap_or_default();
|
||||
check_is_at_least_confirmed(commitment)?;
|
||||
|
||||
|
@ -1399,10 +1400,7 @@ impl JsonRpcRequestProcessor {
|
|||
|
||||
let encode_transaction =
|
||||
|confirmed_tx_with_meta: ConfirmedTransactionWithStatusMeta| -> Result<EncodedConfirmedTransactionWithStatusMeta> {
|
||||
let legacy_tx_with_meta = confirmed_tx_with_meta.into_legacy_confirmed_transaction()
|
||||
.ok_or(RpcCustomError::UnsupportedTransactionVersion)?;
|
||||
|
||||
Ok(legacy_tx_with_meta.encode(encoding))
|
||||
Ok(confirmed_tx_with_meta.encode(encoding, max_supported_transaction_version).map_err(RpcCustomError::from)?)
|
||||
};
|
||||
|
||||
match confirmed_transaction.unwrap_or(None) {
|
||||
|
@ -3478,7 +3476,7 @@ pub mod rpc_full {
|
|||
.preflight_commitment
|
||||
.map(|commitment| CommitmentConfig { commitment });
|
||||
let preflight_bank = &*meta.bank(preflight_commitment);
|
||||
let transaction = sanitize_transaction(unsanitized_tx)?;
|
||||
let transaction = sanitize_transaction(unsanitized_tx, preflight_bank)?;
|
||||
let signature = *transaction.signature();
|
||||
|
||||
let mut last_valid_block_height = preflight_bank
|
||||
|
@ -3586,7 +3584,7 @@ pub mod rpc_full {
|
|||
.set_recent_blockhash(bank.last_blockhash());
|
||||
}
|
||||
|
||||
let transaction = sanitize_transaction(unsanitized_tx)?;
|
||||
let transaction = sanitize_transaction(unsanitized_tx, bank)?;
|
||||
if config.sig_verify {
|
||||
verify_transaction(&transaction, &bank.feature_set)?;
|
||||
}
|
||||
|
@ -4271,9 +4269,12 @@ where
|
|||
.map(|output| (wire_output, output))
|
||||
}
|
||||
|
||||
fn sanitize_transaction(transaction: VersionedTransaction) -> Result<SanitizedTransaction> {
|
||||
fn sanitize_transaction(
|
||||
transaction: VersionedTransaction,
|
||||
address_loader: &impl AddressLoader,
|
||||
) -> Result<SanitizedTransaction> {
|
||||
let message_hash = transaction.message.hash();
|
||||
SanitizedTransaction::try_create(transaction, message_hash, None, &DisabledAddressLoader)
|
||||
SanitizedTransaction::try_create(transaction, message_hash, None, address_loader)
|
||||
.map_err(|err| Error::invalid_params(format!("invalid transaction: {}", err)))
|
||||
}
|
||||
|
||||
|
@ -4284,22 +4285,18 @@ pub(crate) fn create_validator_exit(exit: &Arc<AtomicBool>) -> Arc<RwLock<Exit>>
|
|||
Arc::new(RwLock::new(validator_exit))
|
||||
}
|
||||
|
||||
// Used for tests
|
||||
pub fn create_test_transactions_and_populate_blockstore(
|
||||
pub fn create_test_transaction_entries(
|
||||
keypairs: Vec<&Keypair>,
|
||||
previous_slot: Slot,
|
||||
bank: Arc<Bank>,
|
||||
blockstore: Arc<Blockstore>,
|
||||
max_complete_transaction_status_slot: Arc<AtomicU64>,
|
||||
) -> Vec<Signature> {
|
||||
) -> (Vec<Entry>, Vec<Signature>) {
|
||||
let mint_keypair = keypairs[0];
|
||||
let keypair1 = keypairs[1];
|
||||
let keypair2 = keypairs[2];
|
||||
let keypair3 = keypairs[3];
|
||||
let slot = bank.slot();
|
||||
let blockhash = bank.confirmed_last_blockhash();
|
||||
let rent_exempt_amount = bank.get_minimum_balance_for_rent_exemption(0);
|
||||
|
||||
let mut signatures = Vec::new();
|
||||
// Generate transactions for processing
|
||||
// Successful transaction
|
||||
let success_tx = solana_sdk::system_transaction::transfer(
|
||||
|
@ -4308,7 +4305,7 @@ pub fn create_test_transactions_and_populate_blockstore(
|
|||
rent_exempt_amount,
|
||||
blockhash,
|
||||
);
|
||||
let success_signature = success_tx.signatures[0];
|
||||
signatures.push(success_tx.signatures[0]);
|
||||
let entry_1 = solana_entry::entry::next_entry(&blockhash, 1, vec![success_tx]);
|
||||
// Failed transaction, InstructionError
|
||||
let ix_error_tx = solana_sdk::system_transaction::transfer(
|
||||
|
@ -4317,12 +4314,21 @@ pub fn create_test_transactions_and_populate_blockstore(
|
|||
2 * rent_exempt_amount,
|
||||
blockhash,
|
||||
);
|
||||
let ix_error_signature = ix_error_tx.signatures[0];
|
||||
signatures.push(ix_error_tx.signatures[0]);
|
||||
let entry_2 = solana_entry::entry::next_entry(&entry_1.hash, 1, vec![ix_error_tx]);
|
||||
let entries = vec![entry_1, entry_2];
|
||||
(vec![entry_1, entry_2], signatures)
|
||||
}
|
||||
|
||||
pub fn populate_blockstore_for_tests(
|
||||
entries: Vec<Entry>,
|
||||
bank: Arc<Bank>,
|
||||
blockstore: Arc<Blockstore>,
|
||||
max_complete_transaction_status_slot: Arc<AtomicU64>,
|
||||
) {
|
||||
let slot = bank.slot();
|
||||
let parent_slot = bank.parent_slot();
|
||||
let shreds =
|
||||
solana_ledger::blockstore::entries_to_test_shreds(&entries, slot, previous_slot, true, 0);
|
||||
solana_ledger::blockstore::entries_to_test_shreds(&entries, slot, parent_slot, true, 0);
|
||||
blockstore.insert_shreds(shreds, None, false).unwrap();
|
||||
blockstore.set_roots(std::iter::once(&slot)).unwrap();
|
||||
|
||||
|
@ -4357,8 +4363,6 @@ pub fn create_test_transactions_and_populate_blockstore(
|
|||
);
|
||||
|
||||
transaction_status_service.join().unwrap();
|
||||
|
||||
vec![success_signature, ix_error_signature]
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -4377,13 +4381,16 @@ pub mod tests {
|
|||
jsonrpc_core::{futures, ErrorCode, MetaIoHandler, Output, Response, Value},
|
||||
jsonrpc_core_client::transports::local,
|
||||
serde::de::DeserializeOwned,
|
||||
solana_address_lookup_table_program::state::{AddressLookupTable, LookupTableMeta},
|
||||
solana_client::{
|
||||
rpc_custom_error::{
|
||||
JSON_RPC_SERVER_ERROR_BLOCK_NOT_AVAILABLE,
|
||||
JSON_RPC_SERVER_ERROR_TRANSACTION_HISTORY_NOT_AVAILABLE,
|
||||
JSON_RPC_SERVER_ERROR_UNSUPPORTED_TRANSACTION_VERSION,
|
||||
},
|
||||
rpc_filter::{Memcmp, MemcmpEncodedBytes},
|
||||
},
|
||||
solana_entry::entry::next_versioned_entry,
|
||||
solana_gossip::{contact_info::ContactInfo, socketaddr},
|
||||
solana_ledger::{
|
||||
blockstore_meta::PerfSample,
|
||||
|
@ -4400,11 +4407,15 @@ pub mod tests {
|
|||
fee_calculator::DEFAULT_BURN_PERCENT,
|
||||
hash::{hash, Hash},
|
||||
instruction::InstructionError,
|
||||
message::{v0, v0::MessageAddressTableLookup, MessageHeader, VersionedMessage},
|
||||
nonce, rpc_port,
|
||||
signature::{Keypair, Signer},
|
||||
slot_hashes::SlotHashes,
|
||||
system_program, system_transaction,
|
||||
timing::slot_duration_from_slots_per_year,
|
||||
transaction::{self, Transaction, TransactionError},
|
||||
transaction::{
|
||||
self, DisabledAddressLoader, Transaction, TransactionError, TransactionVersion,
|
||||
},
|
||||
},
|
||||
solana_transaction_status::{
|
||||
EncodedConfirmedBlock, EncodedTransaction, EncodedTransactionWithStatusMeta,
|
||||
|
@ -4418,7 +4429,7 @@ pub mod tests {
|
|||
solana_program::{program_option::COption, pubkey::Pubkey as SplTokenPubkey},
|
||||
state::{AccountState as TokenAccountState, Mint},
|
||||
},
|
||||
std::collections::HashMap,
|
||||
std::{borrow::Cow, collections::HashMap},
|
||||
};
|
||||
|
||||
fn spl_token_id() -> Pubkey {
|
||||
|
@ -4554,22 +4565,107 @@ pub mod tests {
|
|||
serde_json::from_str(response).expect("failed to deserialize response")
|
||||
}
|
||||
|
||||
fn overwrite_working_bank_entries(&self, entries: Vec<Entry>) {
|
||||
populate_blockstore_for_tests(
|
||||
entries,
|
||||
self.working_bank(),
|
||||
self.blockstore.clone(),
|
||||
self.max_complete_transaction_status_slot.clone(),
|
||||
);
|
||||
}
|
||||
|
||||
fn create_test_transactions_and_populate_blockstore(&self) -> Vec<Signature> {
|
||||
let mint_keypair = &self.mint_keypair;
|
||||
let keypair1 = Keypair::new();
|
||||
let keypair2 = Keypair::new();
|
||||
let keypair3 = Keypair::new();
|
||||
let bank = self.bank_forks.read().unwrap().working_bank();
|
||||
let bank = self.working_bank();
|
||||
let rent_exempt_amount = bank.get_minimum_balance_for_rent_exemption(0);
|
||||
bank.transfer(rent_exempt_amount, mint_keypair, &keypair2.pubkey())
|
||||
.unwrap();
|
||||
create_test_transactions_and_populate_blockstore(
|
||||
|
||||
let (entries, signatures) = create_test_transaction_entries(
|
||||
vec![&self.mint_keypair, &keypair1, &keypair2, &keypair3],
|
||||
0,
|
||||
bank,
|
||||
self.blockstore.clone(),
|
||||
self.max_complete_transaction_status_slot.clone(),
|
||||
)
|
||||
);
|
||||
self.overwrite_working_bank_entries(entries);
|
||||
signatures
|
||||
}
|
||||
|
||||
fn create_test_versioned_transactions_and_populate_blockstore(
|
||||
&self,
|
||||
address_table_key: Option<Pubkey>,
|
||||
) -> Vec<Signature> {
|
||||
let address_table_key =
|
||||
address_table_key.unwrap_or_else(|| self.store_address_lookup_table());
|
||||
|
||||
let bank = self.working_bank();
|
||||
let recent_blockhash = bank.confirmed_last_blockhash();
|
||||
let legacy_message = VersionedMessage::Legacy(Message {
|
||||
header: MessageHeader {
|
||||
num_required_signatures: 1,
|
||||
num_readonly_signed_accounts: 0,
|
||||
num_readonly_unsigned_accounts: 0,
|
||||
},
|
||||
recent_blockhash,
|
||||
account_keys: vec![self.mint_keypair.pubkey()],
|
||||
instructions: vec![],
|
||||
});
|
||||
let version_0_message = VersionedMessage::V0(v0::Message {
|
||||
header: MessageHeader {
|
||||
num_required_signatures: 1,
|
||||
num_readonly_signed_accounts: 0,
|
||||
num_readonly_unsigned_accounts: 0,
|
||||
},
|
||||
recent_blockhash,
|
||||
account_keys: vec![self.mint_keypair.pubkey()],
|
||||
address_table_lookups: vec![MessageAddressTableLookup {
|
||||
account_key: address_table_key,
|
||||
writable_indexes: vec![0],
|
||||
readonly_indexes: vec![],
|
||||
}],
|
||||
instructions: vec![],
|
||||
});
|
||||
|
||||
let mut signatures = Vec::new();
|
||||
let legacy_tx =
|
||||
VersionedTransaction::try_new(legacy_message, &[&self.mint_keypair]).unwrap();
|
||||
signatures.push(legacy_tx.signatures[0]);
|
||||
let version_0_tx =
|
||||
VersionedTransaction::try_new(version_0_message, &[&self.mint_keypair]).unwrap();
|
||||
signatures.push(version_0_tx.signatures[0]);
|
||||
let entry1 = next_versioned_entry(&recent_blockhash, 1, vec![legacy_tx]);
|
||||
let entry2 = next_versioned_entry(&entry1.hash, 1, vec![version_0_tx]);
|
||||
let entries = vec![entry1, entry2];
|
||||
self.overwrite_working_bank_entries(entries);
|
||||
signatures
|
||||
}
|
||||
|
||||
fn store_address_lookup_table(&self) -> Pubkey {
|
||||
let bank = self.working_bank();
|
||||
let address_table_pubkey = Pubkey::new_unique();
|
||||
let address_table_account = {
|
||||
let address_table_state = AddressLookupTable {
|
||||
meta: LookupTableMeta {
|
||||
// ensure that active address length is 1 at slot 0
|
||||
last_extended_slot_start_index: 1,
|
||||
..LookupTableMeta::default()
|
||||
},
|
||||
addresses: Cow::Owned(vec![Pubkey::new_unique()]),
|
||||
};
|
||||
let address_table_data = address_table_state.serialize_for_tests().unwrap();
|
||||
let min_balance_lamports =
|
||||
bank.get_minimum_balance_for_rent_exemption(address_table_data.len());
|
||||
AccountSharedData::create(
|
||||
min_balance_lamports,
|
||||
address_table_data,
|
||||
solana_address_lookup_table_program::id(),
|
||||
false,
|
||||
0,
|
||||
)
|
||||
};
|
||||
bank.store_account(&address_table_pubkey, &address_table_account);
|
||||
address_table_pubkey
|
||||
}
|
||||
|
||||
fn add_roots_to_blockstore(&self, mut roots: Vec<Slot>) {
|
||||
|
@ -6307,6 +6403,45 @@ pub mod tests {
|
|||
assert_eq!(result, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_block_with_versioned_tx() {
|
||||
let rpc = RpcHandler::start();
|
||||
|
||||
let bank = rpc.working_bank();
|
||||
// Slot hashes is necessary for processing versioned txs.
|
||||
bank.set_sysvar_for_tests(&SlotHashes::default());
|
||||
// Add both legacy and version #0 transactions to the block
|
||||
rpc.create_test_versioned_transactions_and_populate_blockstore(None);
|
||||
|
||||
let request = create_test_request(
|
||||
"getBlock",
|
||||
Some(json!([
|
||||
0u64,
|
||||
{"maxSupportedTransactionVersion": 0},
|
||||
])),
|
||||
);
|
||||
let result: Option<EncodedConfirmedBlock> =
|
||||
parse_success_result(rpc.handle_request_sync(request));
|
||||
let confirmed_block = result.unwrap();
|
||||
assert_eq!(confirmed_block.transactions.len(), 2);
|
||||
assert_eq!(
|
||||
confirmed_block.transactions[0].version,
|
||||
Some(TransactionVersion::LEGACY)
|
||||
);
|
||||
assert_eq!(
|
||||
confirmed_block.transactions[1].version,
|
||||
Some(TransactionVersion::Number(0))
|
||||
);
|
||||
|
||||
let request = create_test_request("getBlock", Some(json!([0u64,])));
|
||||
let response = parse_failure_response(rpc.handle_request_sync(request));
|
||||
let expected = (
|
||||
JSON_RPC_SERVER_ERROR_UNSUPPORTED_TRANSACTION_VERSION,
|
||||
String::from("Transaction version (0) is not supported"),
|
||||
);
|
||||
assert_eq!(response, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_block() {
|
||||
let mut rpc = RpcHandler::start();
|
||||
|
@ -6320,9 +6455,16 @@ pub mod tests {
|
|||
assert_eq!(confirmed_block.transactions.len(), 2);
|
||||
assert_eq!(confirmed_block.rewards, vec![]);
|
||||
|
||||
for EncodedTransactionWithStatusMeta { transaction, meta } in
|
||||
confirmed_block.transactions.into_iter()
|
||||
for EncodedTransactionWithStatusMeta {
|
||||
transaction,
|
||||
meta,
|
||||
version,
|
||||
} in confirmed_block.transactions.into_iter()
|
||||
{
|
||||
assert_eq!(
|
||||
version, None,
|
||||
"requests which don't set max_supported_transaction_version shouldn't receive a version"
|
||||
);
|
||||
if let EncodedTransaction::Json(transaction) = transaction {
|
||||
if transaction.signatures[0] == confirmed_block_signatures[0].to_string() {
|
||||
let meta = meta.unwrap();
|
||||
|
@ -6357,9 +6499,16 @@ pub mod tests {
|
|||
assert_eq!(confirmed_block.transactions.len(), 2);
|
||||
assert_eq!(confirmed_block.rewards, vec![]);
|
||||
|
||||
for EncodedTransactionWithStatusMeta { transaction, meta } in
|
||||
confirmed_block.transactions.into_iter()
|
||||
for EncodedTransactionWithStatusMeta {
|
||||
transaction,
|
||||
meta,
|
||||
version,
|
||||
} in confirmed_block.transactions.into_iter()
|
||||
{
|
||||
assert_eq!(
|
||||
version, None,
|
||||
"requests which don't set max_supported_transaction_version shouldn't receive a version"
|
||||
);
|
||||
if let EncodedTransaction::LegacyBinary(transaction) = transaction {
|
||||
let decoded_transaction: Transaction =
|
||||
deserialize(&bs58::decode(&transaction).into_vec().unwrap()).unwrap();
|
||||
|
@ -6414,6 +6563,7 @@ pub mod tests {
|
|||
transaction_details: Some(TransactionDetails::Signatures),
|
||||
rewards: Some(false),
|
||||
commitment: None,
|
||||
max_supported_transaction_version: None,
|
||||
},
|
||||
])),
|
||||
);
|
||||
|
@ -6436,6 +6586,7 @@ pub mod tests {
|
|||
transaction_details: Some(TransactionDetails::None),
|
||||
rewards: Some(true),
|
||||
commitment: None,
|
||||
max_supported_transaction_version: None,
|
||||
},
|
||||
])),
|
||||
);
|
||||
|
@ -7662,8 +7813,30 @@ pub mod tests {
|
|||
.to_string(),
|
||||
);
|
||||
assert_eq!(
|
||||
sanitize_transaction(unsanitary_versioned_tx).unwrap_err(),
|
||||
sanitize_transaction(unsanitary_versioned_tx, &DisabledAddressLoader).unwrap_err(),
|
||||
expect58
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sanitize_unsupported_transaction_version() {
|
||||
let versioned_tx = VersionedTransaction {
|
||||
signatures: vec![Signature::default()],
|
||||
message: VersionedMessage::V0(v0::Message {
|
||||
header: MessageHeader {
|
||||
num_required_signatures: 1,
|
||||
..MessageHeader::default()
|
||||
},
|
||||
account_keys: vec![Pubkey::new_unique()],
|
||||
..v0::Message::default()
|
||||
}),
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
sanitize_transaction(versioned_tx, &DisabledAddressLoader).unwrap_err(),
|
||||
Error::invalid_params(
|
||||
"invalid transaction: Transaction version is unsupported".to_string(),
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -538,6 +538,7 @@ impl RpcSolPubSubInternal for RpcSolPubSubImpl {
|
|||
},
|
||||
transaction_details: config.transaction_details.unwrap_or_default(),
|
||||
show_rewards: config.show_rewards.unwrap_or_default(),
|
||||
max_supported_transaction_version: config.max_supported_transaction_version,
|
||||
};
|
||||
self.subscribe(SubscriptionParams::Block(params))
|
||||
}
|
||||
|
|
|
@ -140,6 +140,7 @@ pub struct BlockSubscriptionParams {
|
|||
pub kind: BlockSubscriptionKind,
|
||||
pub transaction_details: TransactionDetails,
|
||||
pub show_rewards: bool,
|
||||
pub max_supported_transaction_version: Option<u8>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
|
|
|
@ -40,7 +40,9 @@ use {
|
|||
timing::timestamp,
|
||||
transaction,
|
||||
},
|
||||
solana_transaction_status::{ConfirmedBlock, LegacyConfirmedBlock},
|
||||
solana_transaction_status::{
|
||||
BlockEncodingOptions, ConfirmedBlock, EncodeError, VersionedConfirmedBlock,
|
||||
},
|
||||
std::{
|
||||
cell::RefCell,
|
||||
collections::{HashMap, VecDeque},
|
||||
|
@ -278,39 +280,48 @@ impl RpcNotifier {
|
|||
}
|
||||
|
||||
fn filter_block_result_txs(
|
||||
mut block: LegacyConfirmedBlock,
|
||||
mut block: VersionedConfirmedBlock,
|
||||
last_modified_slot: Slot,
|
||||
params: &BlockSubscriptionParams,
|
||||
) -> Option<RpcBlockUpdate> {
|
||||
) -> Result<Option<RpcBlockUpdate>, RpcBlockUpdateError> {
|
||||
block.transactions = match params.kind {
|
||||
BlockSubscriptionKind::All => block.transactions,
|
||||
BlockSubscriptionKind::MentionsAccountOrProgram(pk) => block
|
||||
.transactions
|
||||
.into_iter()
|
||||
.filter(|tx_with_meta| tx_with_meta.transaction.message.account_keys.contains(&pk))
|
||||
.filter(|tx| tx.account_keys().iter().any(|key| key == &pk))
|
||||
.collect(),
|
||||
};
|
||||
|
||||
if block.transactions.is_empty() {
|
||||
if let BlockSubscriptionKind::MentionsAccountOrProgram(_) = params.kind {
|
||||
return None;
|
||||
return Ok(None);
|
||||
}
|
||||
}
|
||||
|
||||
let block = block.configure(
|
||||
params.encoding,
|
||||
params.transaction_details,
|
||||
params.show_rewards,
|
||||
);
|
||||
let block = ConfirmedBlock::from(block)
|
||||
.encode_with_options(
|
||||
params.encoding,
|
||||
BlockEncodingOptions {
|
||||
transaction_details: params.transaction_details,
|
||||
show_rewards: params.show_rewards,
|
||||
max_supported_transaction_version: params.max_supported_transaction_version,
|
||||
},
|
||||
)
|
||||
.map_err(|err| match err {
|
||||
EncodeError::UnsupportedTransactionVersion(version) => {
|
||||
RpcBlockUpdateError::UnsupportedTransactionVersion(version)
|
||||
}
|
||||
})?;
|
||||
|
||||
// If last_modified_slot < last_notified_slot, then the last notif was for a fork.
|
||||
// That's the risk clients take when subscribing to non-finalized commitments.
|
||||
// This code lets the logic for dealing with forks live on the client side.
|
||||
Some(RpcBlockUpdate {
|
||||
Ok(Some(RpcBlockUpdate {
|
||||
slot: last_modified_slot,
|
||||
block: Some(block),
|
||||
err: None,
|
||||
})
|
||||
}))
|
||||
}
|
||||
|
||||
fn filter_account_result(
|
||||
|
@ -964,19 +975,11 @@ impl RpcSubscriptions {
|
|||
error!("get_complete_block error: {}", e);
|
||||
RpcBlockUpdateError::BlockStoreError
|
||||
})
|
||||
.and_then(|versioned_block| {
|
||||
ConfirmedBlock::from(versioned_block)
|
||||
.into_legacy_block()
|
||||
.ok_or(
|
||||
RpcBlockUpdateError::UnsupportedTransactionVersion,
|
||||
)
|
||||
});
|
||||
.and_then(|block| filter_block_result_txs(block, s, params));
|
||||
|
||||
match block_update_result {
|
||||
Ok(block_update) => {
|
||||
if let Some(block_update) =
|
||||
filter_block_result_txs(block_update, s, params)
|
||||
{
|
||||
if let Some(block_update) = block_update {
|
||||
notifier.notify(
|
||||
Response {
|
||||
context: RpcResponseContext { slot: s },
|
||||
|
@ -1189,7 +1192,7 @@ pub(crate) mod tests {
|
|||
optimistically_confirmed_bank_tracker::{
|
||||
BankNotification, OptimisticallyConfirmedBank, OptimisticallyConfirmedBankTracker,
|
||||
},
|
||||
rpc::create_test_transactions_and_populate_blockstore,
|
||||
rpc::{create_test_transaction_entries, populate_blockstore_for_tests},
|
||||
rpc_pubsub::RpcSolPubSubInternal,
|
||||
rpc_pubsub_service,
|
||||
},
|
||||
|
@ -1385,6 +1388,7 @@ pub(crate) mod tests {
|
|||
encoding: Some(UiTransactionEncoding::Json),
|
||||
transaction_details: Some(TransactionDetails::Signatures),
|
||||
show_rewards: None,
|
||||
max_supported_transaction_version: None,
|
||||
};
|
||||
let params = BlockSubscriptionParams {
|
||||
kind: BlockSubscriptionKind::All,
|
||||
|
@ -1392,6 +1396,7 @@ pub(crate) mod tests {
|
|||
encoding: config.encoding.unwrap(),
|
||||
transaction_details: config.transaction_details.unwrap(),
|
||||
show_rewards: config.show_rewards.unwrap_or_default(),
|
||||
max_supported_transaction_version: config.max_supported_transaction_version,
|
||||
};
|
||||
let sub_id = rpc.block_subscribe(filter, Some(config)).unwrap();
|
||||
|
||||
|
@ -1406,9 +1411,12 @@ pub(crate) mod tests {
|
|||
let max_complete_transaction_status_slot = Arc::new(AtomicU64::new(blockstore.max_root()));
|
||||
bank.transfer(rent_exempt_amount, &mint_keypair, &keypair2.pubkey())
|
||||
.unwrap();
|
||||
let _confirmed_block_signatures = create_test_transactions_and_populate_blockstore(
|
||||
vec![&mint_keypair, &keypair1, &keypair2, &keypair3],
|
||||
0,
|
||||
populate_blockstore_for_tests(
|
||||
create_test_transaction_entries(
|
||||
vec![&mint_keypair, &keypair1, &keypair2, &keypair3],
|
||||
bank.clone(),
|
||||
)
|
||||
.0,
|
||||
bank,
|
||||
blockstore.clone(),
|
||||
max_complete_transaction_status_slot,
|
||||
|
@ -1421,8 +1429,16 @@ pub(crate) mod tests {
|
|||
|
||||
let confirmed_block =
|
||||
ConfirmedBlock::from(blockstore.get_complete_block(slot, false).unwrap());
|
||||
let legacy_block = confirmed_block.into_legacy_block().unwrap();
|
||||
let block = legacy_block.configure(params.encoding, params.transaction_details, false);
|
||||
let block = confirmed_block
|
||||
.encode_with_options(
|
||||
params.encoding,
|
||||
BlockEncodingOptions {
|
||||
transaction_details: params.transaction_details,
|
||||
show_rewards: false,
|
||||
max_supported_transaction_version: None,
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
let expected_resp = RpcBlockUpdate {
|
||||
slot,
|
||||
block: Some(block),
|
||||
|
@ -1492,6 +1508,7 @@ pub(crate) mod tests {
|
|||
encoding: Some(UiTransactionEncoding::Json),
|
||||
transaction_details: Some(TransactionDetails::Signatures),
|
||||
show_rewards: None,
|
||||
max_supported_transaction_version: None,
|
||||
};
|
||||
let params = BlockSubscriptionParams {
|
||||
kind: BlockSubscriptionKind::MentionsAccountOrProgram(keypair1.pubkey()),
|
||||
|
@ -1499,6 +1516,7 @@ pub(crate) mod tests {
|
|||
encoding: config.encoding.unwrap(),
|
||||
transaction_details: config.transaction_details.unwrap(),
|
||||
show_rewards: config.show_rewards.unwrap_or_default(),
|
||||
max_supported_transaction_version: config.max_supported_transaction_version,
|
||||
};
|
||||
let sub_id = rpc.block_subscribe(filter, Some(config)).unwrap();
|
||||
|
||||
|
@ -1512,9 +1530,12 @@ pub(crate) mod tests {
|
|||
let max_complete_transaction_status_slot = Arc::new(AtomicU64::new(blockstore.max_root()));
|
||||
bank.transfer(rent_exempt_amount, &mint_keypair, &keypair2.pubkey())
|
||||
.unwrap();
|
||||
let _confirmed_block_signatures = create_test_transactions_and_populate_blockstore(
|
||||
vec![&mint_keypair, &keypair1, &keypair2, &keypair3],
|
||||
0,
|
||||
populate_blockstore_for_tests(
|
||||
create_test_transaction_entries(
|
||||
vec![&mint_keypair, &keypair1, &keypair2, &keypair3],
|
||||
bank.clone(),
|
||||
)
|
||||
.0,
|
||||
bank,
|
||||
blockstore.clone(),
|
||||
max_complete_transaction_status_slot,
|
||||
|
@ -1526,17 +1547,24 @@ pub(crate) mod tests {
|
|||
let actual_resp = serde_json::from_str::<serde_json::Value>(&actual_resp).unwrap();
|
||||
|
||||
// make sure it filtered out the other keypairs
|
||||
let confirmed_block =
|
||||
let mut confirmed_block =
|
||||
ConfirmedBlock::from(blockstore.get_complete_block(slot, false).unwrap());
|
||||
let mut legacy_block = confirmed_block.into_legacy_block().unwrap();
|
||||
legacy_block.transactions.retain(|tx_with_meta| {
|
||||
confirmed_block.transactions.retain(|tx_with_meta| {
|
||||
tx_with_meta
|
||||
.transaction
|
||||
.message
|
||||
.account_keys
|
||||
.contains(&keypair1.pubkey())
|
||||
.account_keys()
|
||||
.iter()
|
||||
.any(|key| key == &keypair1.pubkey())
|
||||
});
|
||||
let block = legacy_block.configure(params.encoding, params.transaction_details, false);
|
||||
let block = confirmed_block
|
||||
.encode_with_options(
|
||||
params.encoding,
|
||||
BlockEncodingOptions {
|
||||
transaction_details: params.transaction_details,
|
||||
show_rewards: false,
|
||||
max_supported_transaction_version: None,
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
let expected_resp = RpcBlockUpdate {
|
||||
slot,
|
||||
block: Some(block),
|
||||
|
@ -1594,6 +1622,7 @@ pub(crate) mod tests {
|
|||
encoding: Some(UiTransactionEncoding::Json),
|
||||
transaction_details: Some(TransactionDetails::Signatures),
|
||||
show_rewards: None,
|
||||
max_supported_transaction_version: None,
|
||||
};
|
||||
let params = BlockSubscriptionParams {
|
||||
kind: BlockSubscriptionKind::All,
|
||||
|
@ -1601,6 +1630,7 @@ pub(crate) mod tests {
|
|||
encoding: config.encoding.unwrap(),
|
||||
transaction_details: config.transaction_details.unwrap(),
|
||||
show_rewards: config.show_rewards.unwrap_or_default(),
|
||||
max_supported_transaction_version: config.max_supported_transaction_version,
|
||||
};
|
||||
let sub_id = rpc.block_subscribe(filter, Some(config)).unwrap();
|
||||
subscriptions
|
||||
|
@ -1614,9 +1644,12 @@ pub(crate) mod tests {
|
|||
let max_complete_transaction_status_slot = Arc::new(AtomicU64::new(blockstore.max_root()));
|
||||
bank.transfer(rent_exempt_amount, &mint_keypair, &keypair2.pubkey())
|
||||
.unwrap();
|
||||
let _confirmed_block_signatures = create_test_transactions_and_populate_blockstore(
|
||||
vec![&mint_keypair, &keypair1, &keypair2, &keypair3],
|
||||
0,
|
||||
populate_blockstore_for_tests(
|
||||
create_test_transaction_entries(
|
||||
vec![&mint_keypair, &keypair1, &keypair2, &keypair3],
|
||||
bank.clone(),
|
||||
)
|
||||
.0,
|
||||
bank,
|
||||
blockstore.clone(),
|
||||
max_complete_transaction_status_slot,
|
||||
|
@ -1634,8 +1667,16 @@ pub(crate) mod tests {
|
|||
|
||||
let confirmed_block =
|
||||
ConfirmedBlock::from(blockstore.get_complete_block(slot, false).unwrap());
|
||||
let legacy_block = confirmed_block.into_legacy_block().unwrap();
|
||||
let block = legacy_block.configure(params.encoding, params.transaction_details, false);
|
||||
let block = confirmed_block
|
||||
.encode_with_options(
|
||||
params.encoding,
|
||||
BlockEncodingOptions {
|
||||
transaction_details: params.transaction_details,
|
||||
show_rewards: false,
|
||||
max_supported_transaction_version: None,
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
let expected_resp = RpcBlockUpdate {
|
||||
slot,
|
||||
block: Some(block),
|
||||
|
|
|
@ -2057,14 +2057,9 @@ mod tests {
|
|||
meta: LookupTableMeta::default(),
|
||||
addresses: Cow::Owned(table_addresses.clone()),
|
||||
};
|
||||
let table_data = {
|
||||
let mut data = vec![];
|
||||
table_state.serialize_for_tests(&mut data).unwrap();
|
||||
data
|
||||
};
|
||||
AccountSharedData::create(
|
||||
1,
|
||||
table_data,
|
||||
table_state.serialize_for_tests().unwrap(),
|
||||
solana_address_lookup_table_program::id(),
|
||||
false,
|
||||
0,
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -18,7 +18,7 @@ pub mod token_balances;
|
|||
pub use {crate::extract_memos::extract_and_fmt_memos, solana_runtime::bank::RewardType};
|
||||
use {
|
||||
crate::{
|
||||
parse_accounts::{parse_accounts, ParsedAccount},
|
||||
parse_accounts::{parse_accounts, parse_static_accounts, ParsedAccount},
|
||||
parse_instruction::{parse, ParsedInstruction},
|
||||
},
|
||||
solana_account_decoder::parse_token::UiTokenAmount,
|
||||
|
@ -26,18 +26,48 @@ use {
|
|||
clock::{Slot, UnixTimestamp},
|
||||
commitment_config::CommitmentConfig,
|
||||
instruction::CompiledInstruction,
|
||||
message::{v0::LoadedAddresses, AccountKeys, Message, MessageHeader},
|
||||
message::{
|
||||
v0::{self, LoadedAddresses, LoadedMessage, MessageAddressTableLookup},
|
||||
AccountKeys, Message, MessageHeader, VersionedMessage,
|
||||
},
|
||||
pubkey::Pubkey,
|
||||
sanitize::Sanitize,
|
||||
signature::Signature,
|
||||
transaction::{Result, Transaction, TransactionError, VersionedTransaction},
|
||||
transaction::{
|
||||
Result as TransactionResult, Transaction, TransactionError, TransactionVersion,
|
||||
VersionedTransaction,
|
||||
},
|
||||
},
|
||||
std::fmt,
|
||||
thiserror::Error,
|
||||
};
|
||||
|
||||
pub struct BlockEncodingOptions {
|
||||
pub transaction_details: TransactionDetails,
|
||||
pub show_rewards: bool,
|
||||
pub max_supported_transaction_version: Option<u8>,
|
||||
}
|
||||
|
||||
#[derive(Error, Debug, PartialEq, Eq, Clone)]
|
||||
pub enum EncodeError {
|
||||
#[error("Encoding does not support transaction version {0}")]
|
||||
UnsupportedTransactionVersion(u8),
|
||||
}
|
||||
|
||||
/// Represents types that can be encoded into one of several encoding formats
|
||||
pub trait Encodable {
|
||||
type Encoded;
|
||||
fn encode(self, encoding: UiTransactionEncoding) -> Self::Encoded;
|
||||
fn encode(&self, encoding: UiTransactionEncoding) -> Self::Encoded;
|
||||
}
|
||||
|
||||
/// Represents types that can be encoded into one of several encoding formats
|
||||
pub trait EncodableWithMeta {
|
||||
type Encoded;
|
||||
fn encode_with_meta(
|
||||
&self,
|
||||
encoding: UiTransactionEncoding,
|
||||
meta: &TransactionStatusMeta,
|
||||
) -> Self::Encoded;
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||
|
@ -160,14 +190,13 @@ pub struct UiInnerInstructions {
|
|||
}
|
||||
|
||||
impl UiInnerInstructions {
|
||||
fn parse(inner_instructions: InnerInstructions, message: &Message) -> Self {
|
||||
let account_keys = AccountKeys::new(&message.account_keys, None);
|
||||
fn parse(inner_instructions: InnerInstructions, account_keys: &AccountKeys) -> Self {
|
||||
Self {
|
||||
index: inner_instructions.index,
|
||||
instructions: inner_instructions
|
||||
.instructions
|
||||
.iter()
|
||||
.map(|ix| UiInstruction::parse(ix, &account_keys))
|
||||
.map(|ix| UiInstruction::parse(ix, account_keys))
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
|
@ -221,7 +250,7 @@ impl From<TransactionTokenBalance> for UiTransactionTokenBalance {
|
|||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct TransactionStatusMeta {
|
||||
pub status: Result<()>,
|
||||
pub status: TransactionResult<()>,
|
||||
pub fee: u64,
|
||||
pub pre_balances: Vec<u64>,
|
||||
pub post_balances: Vec<u64>,
|
||||
|
@ -255,7 +284,7 @@ impl Default for TransactionStatusMeta {
|
|||
#[serde(rename_all = "camelCase")]
|
||||
pub struct UiTransactionStatusMeta {
|
||||
pub err: Option<TransactionError>,
|
||||
pub status: Result<()>, // This field is deprecated. See https://github.com/solana-labs/solana/issues/9302
|
||||
pub status: TransactionResult<()>, // This field is deprecated. See https://github.com/solana-labs/solana/issues/9302
|
||||
pub fee: u64,
|
||||
pub pre_balances: Vec<u64>,
|
||||
pub post_balances: Vec<u64>,
|
||||
|
@ -264,10 +293,38 @@ pub struct UiTransactionStatusMeta {
|
|||
pub pre_token_balances: Option<Vec<UiTransactionTokenBalance>>,
|
||||
pub post_token_balances: Option<Vec<UiTransactionTokenBalance>>,
|
||||
pub rewards: Option<Rewards>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub loaded_addresses: Option<UiLoadedAddresses>,
|
||||
}
|
||||
|
||||
/// A duplicate representation of LoadedAddresses
|
||||
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct UiLoadedAddresses {
|
||||
pub writable: Vec<String>,
|
||||
pub readonly: Vec<String>,
|
||||
}
|
||||
|
||||
impl From<&LoadedAddresses> for UiLoadedAddresses {
|
||||
fn from(loaded_addresses: &LoadedAddresses) -> Self {
|
||||
Self {
|
||||
writable: loaded_addresses
|
||||
.writable
|
||||
.iter()
|
||||
.map(ToString::to_string)
|
||||
.collect(),
|
||||
readonly: loaded_addresses
|
||||
.readonly
|
||||
.iter()
|
||||
.map(ToString::to_string)
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl UiTransactionStatusMeta {
|
||||
fn parse(meta: TransactionStatusMeta, message: &Message) -> Self {
|
||||
fn parse(meta: TransactionStatusMeta, static_keys: &[Pubkey]) -> Self {
|
||||
let account_keys = AccountKeys::new(static_keys, Some(&meta.loaded_addresses));
|
||||
Self {
|
||||
err: meta.status.clone().err(),
|
||||
status: meta.status,
|
||||
|
@ -276,7 +333,7 @@ impl UiTransactionStatusMeta {
|
|||
post_balances: meta.post_balances,
|
||||
inner_instructions: meta.inner_instructions.map(|ixs| {
|
||||
ixs.into_iter()
|
||||
.map(|ix| UiInnerInstructions::parse(ix, message))
|
||||
.map(|ix| UiInnerInstructions::parse(ix, &account_keys))
|
||||
.collect()
|
||||
}),
|
||||
log_messages: meta.log_messages,
|
||||
|
@ -287,6 +344,7 @@ impl UiTransactionStatusMeta {
|
|||
.post_token_balances
|
||||
.map(|balance| balance.into_iter().map(Into::into).collect()),
|
||||
rewards: meta.rewards,
|
||||
loaded_addresses: Some(UiLoadedAddresses::from(&meta.loaded_addresses)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -310,6 +368,7 @@ impl From<TransactionStatusMeta> for UiTransactionStatusMeta {
|
|||
.post_token_balances
|
||||
.map(|balance| balance.into_iter().map(Into::into).collect()),
|
||||
rewards: meta.rewards,
|
||||
loaded_addresses: Some(UiLoadedAddresses::from(&meta.loaded_addresses)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -326,8 +385,8 @@ pub enum TransactionConfirmationStatus {
|
|||
#[serde(rename_all = "camelCase")]
|
||||
pub struct TransactionStatus {
|
||||
pub slot: Slot,
|
||||
pub confirmations: Option<usize>, // None = rooted
|
||||
pub status: Result<()>, // legacy field
|
||||
pub confirmations: Option<usize>, // None = rooted
|
||||
pub status: TransactionResult<()>, // legacy field
|
||||
pub err: Option<TransactionError>,
|
||||
pub confirmation_status: Option<TransactionConfirmationStatus>,
|
||||
}
|
||||
|
@ -462,39 +521,21 @@ impl From<VersionedConfirmedBlock> for ConfirmedBlock {
|
|||
}
|
||||
}
|
||||
|
||||
impl Encodable for LegacyConfirmedBlock {
|
||||
type Encoded = EncodedConfirmedBlock;
|
||||
fn encode(self, encoding: UiTransactionEncoding) -> Self::Encoded {
|
||||
Self::Encoded {
|
||||
previous_blockhash: self.previous_blockhash,
|
||||
blockhash: self.blockhash,
|
||||
parent_slot: self.parent_slot,
|
||||
transactions: self
|
||||
.transactions
|
||||
.into_iter()
|
||||
.map(|tx| tx.encode(encoding))
|
||||
.collect(),
|
||||
rewards: self.rewards,
|
||||
block_time: self.block_time,
|
||||
block_height: self.block_height,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl LegacyConfirmedBlock {
|
||||
pub fn configure(
|
||||
impl ConfirmedBlock {
|
||||
pub fn encode_with_options(
|
||||
self,
|
||||
encoding: UiTransactionEncoding,
|
||||
transaction_details: TransactionDetails,
|
||||
show_rewards: bool,
|
||||
) -> UiConfirmedBlock {
|
||||
let (transactions, signatures) = match transaction_details {
|
||||
options: BlockEncodingOptions,
|
||||
) -> Result<UiConfirmedBlock, EncodeError> {
|
||||
let (transactions, signatures) = match options.transaction_details {
|
||||
TransactionDetails::Full => (
|
||||
Some(
|
||||
self.transactions
|
||||
.into_iter()
|
||||
.map(|tx_with_meta| tx_with_meta.encode(encoding))
|
||||
.collect(),
|
||||
.map(|tx_with_meta| {
|
||||
tx_with_meta.encode(encoding, options.max_supported_transaction_version)
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()?,
|
||||
),
|
||||
None,
|
||||
),
|
||||
|
@ -503,26 +544,26 @@ impl LegacyConfirmedBlock {
|
|||
Some(
|
||||
self.transactions
|
||||
.into_iter()
|
||||
.map(|tx| tx.transaction.signatures[0].to_string())
|
||||
.map(|tx_with_meta| tx_with_meta.transaction_signature().to_string())
|
||||
.collect(),
|
||||
),
|
||||
),
|
||||
TransactionDetails::None => (None, None),
|
||||
};
|
||||
UiConfirmedBlock {
|
||||
Ok(UiConfirmedBlock {
|
||||
previous_blockhash: self.previous_blockhash,
|
||||
blockhash: self.blockhash,
|
||||
parent_slot: self.parent_slot,
|
||||
transactions,
|
||||
signatures,
|
||||
rewards: if show_rewards {
|
||||
rewards: if options.show_rewards {
|
||||
Some(self.rewards)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
block_time: self.block_time,
|
||||
block_height: self.block_height,
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -568,21 +609,6 @@ pub struct UiConfirmedBlock {
|
|||
pub block_height: Option<u64>,
|
||||
}
|
||||
|
||||
impl From<EncodedConfirmedBlock> for UiConfirmedBlock {
|
||||
fn from(block: EncodedConfirmedBlock) -> Self {
|
||||
Self {
|
||||
previous_blockhash: block.previous_blockhash,
|
||||
blockhash: block.blockhash,
|
||||
parent_slot: block.parent_slot,
|
||||
transactions: Some(block.transactions),
|
||||
signatures: None,
|
||||
rewards: Some(block.rewards),
|
||||
block_time: block.block_time,
|
||||
block_height: block.block_height,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
pub enum TransactionWithStatusMeta {
|
||||
|
@ -613,6 +639,23 @@ impl TransactionWithStatusMeta {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn encode(
|
||||
self,
|
||||
encoding: UiTransactionEncoding,
|
||||
max_supported_transaction_version: Option<u8>,
|
||||
) -> Result<EncodedTransactionWithStatusMeta, EncodeError> {
|
||||
match self {
|
||||
Self::MissingMetadata(ref transaction) => Ok(EncodedTransactionWithStatusMeta {
|
||||
version: None,
|
||||
transaction: transaction.encode(encoding),
|
||||
meta: None,
|
||||
}),
|
||||
Self::Complete(tx_with_meta) => {
|
||||
tx_with_meta.encode(encoding, max_supported_transaction_version)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_legacy_transaction_with_meta(self) -> Option<LegacyTransactionWithStatusMeta> {
|
||||
match self {
|
||||
TransactionWithStatusMeta::MissingMetadata(transaction) => {
|
||||
|
@ -626,9 +669,53 @@ impl TransactionWithStatusMeta {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn account_keys(&self) -> AccountKeys {
|
||||
match self {
|
||||
Self::MissingMetadata(tx) => AccountKeys::new(&tx.message.account_keys, None),
|
||||
Self::Complete(tx_with_meta) => tx_with_meta.account_keys(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl VersionedTransactionWithStatusMeta {
|
||||
pub fn encode(
|
||||
self,
|
||||
encoding: UiTransactionEncoding,
|
||||
max_supported_transaction_version: Option<u8>,
|
||||
) -> Result<EncodedTransactionWithStatusMeta, EncodeError> {
|
||||
let version = match (
|
||||
max_supported_transaction_version,
|
||||
self.transaction.version(),
|
||||
) {
|
||||
// Set to none because old clients can't handle this field
|
||||
(None, TransactionVersion::LEGACY) => Ok(None),
|
||||
(None, TransactionVersion::Number(version)) => {
|
||||
Err(EncodeError::UnsupportedTransactionVersion(version))
|
||||
}
|
||||
(Some(_), TransactionVersion::LEGACY) => Ok(Some(TransactionVersion::LEGACY)),
|
||||
(Some(max_version), TransactionVersion::Number(version)) => {
|
||||
if version <= max_version {
|
||||
Ok(Some(TransactionVersion::Number(version)))
|
||||
} else {
|
||||
Err(EncodeError::UnsupportedTransactionVersion(version))
|
||||
}
|
||||
}
|
||||
}?;
|
||||
|
||||
Ok(EncodedTransactionWithStatusMeta {
|
||||
transaction: self.transaction.encode_with_meta(encoding, &self.meta),
|
||||
meta: Some(match encoding {
|
||||
UiTransactionEncoding::JsonParsed => UiTransactionStatusMeta::parse(
|
||||
self.meta,
|
||||
self.transaction.message.static_account_keys(),
|
||||
),
|
||||
_ => UiTransactionStatusMeta::from(self.meta),
|
||||
}),
|
||||
version,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn account_keys(&self) -> AccountKeys {
|
||||
AccountKeys::new(
|
||||
self.transaction.message.static_account_keys(),
|
||||
|
@ -636,7 +723,7 @@ impl VersionedTransactionWithStatusMeta {
|
|||
)
|
||||
}
|
||||
|
||||
pub fn into_legacy_transaction_with_meta(self) -> Option<LegacyTransactionWithStatusMeta> {
|
||||
fn into_legacy_transaction_with_meta(self) -> Option<LegacyTransactionWithStatusMeta> {
|
||||
Some(LegacyTransactionWithStatusMeta {
|
||||
transaction: self.transaction.into_legacy_transaction()?,
|
||||
meta: Some(self.meta),
|
||||
|
@ -644,26 +731,13 @@ impl VersionedTransactionWithStatusMeta {
|
|||
}
|
||||
}
|
||||
|
||||
impl Encodable for LegacyTransactionWithStatusMeta {
|
||||
type Encoded = EncodedTransactionWithStatusMeta;
|
||||
fn encode(self, encoding: UiTransactionEncoding) -> Self::Encoded {
|
||||
Self::Encoded {
|
||||
transaction: self.transaction.encode(encoding),
|
||||
meta: self.meta.map(|meta| match encoding {
|
||||
UiTransactionEncoding::JsonParsed => {
|
||||
UiTransactionStatusMeta::parse(meta, &self.transaction.message)
|
||||
}
|
||||
_ => UiTransactionStatusMeta::from(meta),
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct EncodedTransactionWithStatusMeta {
|
||||
pub transaction: EncodedTransaction,
|
||||
pub meta: Option<UiTransactionStatusMeta>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub version: Option<TransactionVersion>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
|
@ -686,17 +760,6 @@ pub struct LegacyConfirmedTransactionWithStatusMeta {
|
|||
pub block_time: Option<UnixTimestamp>,
|
||||
}
|
||||
|
||||
impl Encodable for LegacyConfirmedTransactionWithStatusMeta {
|
||||
type Encoded = EncodedConfirmedTransactionWithStatusMeta;
|
||||
fn encode(self, encoding: UiTransactionEncoding) -> Self::Encoded {
|
||||
Self::Encoded {
|
||||
slot: self.slot,
|
||||
transaction: self.tx_with_meta.encode(encoding),
|
||||
block_time: self.block_time,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ConfirmedTransactionWithStatusMeta {
|
||||
/// Downgrades a versioned confirmed transaction into a legacy
|
||||
/// confirmed transaction if it contains a legacy transaction.
|
||||
|
@ -709,6 +772,20 @@ impl ConfirmedTransactionWithStatusMeta {
|
|||
slot: self.slot,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn encode(
|
||||
self,
|
||||
encoding: UiTransactionEncoding,
|
||||
max_supported_transaction_version: Option<u8>,
|
||||
) -> Result<EncodedConfirmedTransactionWithStatusMeta, EncodeError> {
|
||||
Ok(EncodedConfirmedTransactionWithStatusMeta {
|
||||
slot: self.slot,
|
||||
transaction: self
|
||||
.tx_with_meta
|
||||
.encode(encoding, max_supported_transaction_version)?,
|
||||
block_time: self.block_time,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||
|
@ -728,9 +805,41 @@ pub enum EncodedTransaction {
|
|||
Json(UiTransaction),
|
||||
}
|
||||
|
||||
impl Encodable for &Transaction {
|
||||
impl EncodableWithMeta for VersionedTransaction {
|
||||
type Encoded = EncodedTransaction;
|
||||
fn encode(self, encoding: UiTransactionEncoding) -> Self::Encoded {
|
||||
fn encode_with_meta(
|
||||
&self,
|
||||
encoding: UiTransactionEncoding,
|
||||
meta: &TransactionStatusMeta,
|
||||
) -> Self::Encoded {
|
||||
match encoding {
|
||||
UiTransactionEncoding::Binary => EncodedTransaction::LegacyBinary(
|
||||
bs58::encode(bincode::serialize(self).unwrap()).into_string(),
|
||||
),
|
||||
UiTransactionEncoding::Base58 => EncodedTransaction::Binary(
|
||||
bs58::encode(bincode::serialize(self).unwrap()).into_string(),
|
||||
encoding,
|
||||
),
|
||||
UiTransactionEncoding::Base64 => EncodedTransaction::Binary(
|
||||
base64::encode(bincode::serialize(self).unwrap()),
|
||||
encoding,
|
||||
),
|
||||
UiTransactionEncoding::Json | UiTransactionEncoding::JsonParsed => {
|
||||
EncodedTransaction::Json(UiTransaction {
|
||||
signatures: self.signatures.iter().map(ToString::to_string).collect(),
|
||||
message: match &self.message {
|
||||
VersionedMessage::Legacy(message) => message.encode(encoding),
|
||||
VersionedMessage::V0(message) => message.encode_with_meta(encoding, meta),
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Encodable for Transaction {
|
||||
type Encoded = EncodedTransaction;
|
||||
fn encode(&self, encoding: UiTransactionEncoding) -> Self::Encoded {
|
||||
match encoding {
|
||||
UiTransactionEncoding::Binary => EncodedTransaction::LegacyBinary(
|
||||
bs58::encode(bincode::serialize(self).unwrap()).into_string(),
|
||||
|
@ -793,9 +902,9 @@ pub enum UiMessage {
|
|||
Raw(UiRawMessage),
|
||||
}
|
||||
|
||||
impl Encodable for &Message {
|
||||
impl Encodable for Message {
|
||||
type Encoded = UiMessage;
|
||||
fn encode(self, encoding: UiTransactionEncoding) -> Self::Encoded {
|
||||
fn encode(&self, encoding: UiTransactionEncoding) -> Self::Encoded {
|
||||
if encoding == UiTransactionEncoding::JsonParsed {
|
||||
let account_keys = AccountKeys::new(&self.account_keys, None);
|
||||
UiMessage::Parsed(UiParsedMessage {
|
||||
|
@ -806,6 +915,7 @@ impl Encodable for &Message {
|
|||
.iter()
|
||||
.map(|instruction| UiInstruction::parse(instruction, &account_keys))
|
||||
.collect(),
|
||||
address_table_lookups: None,
|
||||
})
|
||||
} else {
|
||||
UiMessage::Raw(UiRawMessage {
|
||||
|
@ -813,6 +923,43 @@ impl Encodable for &Message {
|
|||
account_keys: self.account_keys.iter().map(ToString::to_string).collect(),
|
||||
recent_blockhash: self.recent_blockhash.to_string(),
|
||||
instructions: self.instructions.iter().map(Into::into).collect(),
|
||||
address_table_lookups: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl EncodableWithMeta for v0::Message {
|
||||
type Encoded = UiMessage;
|
||||
fn encode_with_meta(
|
||||
&self,
|
||||
encoding: UiTransactionEncoding,
|
||||
meta: &TransactionStatusMeta,
|
||||
) -> Self::Encoded {
|
||||
if encoding == UiTransactionEncoding::JsonParsed {
|
||||
let account_keys = AccountKeys::new(&self.account_keys, Some(&meta.loaded_addresses));
|
||||
let loaded_message = LoadedMessage::new_borrowed(self, &meta.loaded_addresses);
|
||||
UiMessage::Parsed(UiParsedMessage {
|
||||
account_keys: parse_static_accounts(&loaded_message),
|
||||
recent_blockhash: self.recent_blockhash.to_string(),
|
||||
instructions: self
|
||||
.instructions
|
||||
.iter()
|
||||
.map(|instruction| UiInstruction::parse(instruction, &account_keys))
|
||||
.collect(),
|
||||
address_table_lookups: Some(
|
||||
self.address_table_lookups.iter().map(Into::into).collect(),
|
||||
),
|
||||
})
|
||||
} else {
|
||||
UiMessage::Raw(UiRawMessage {
|
||||
header: self.header,
|
||||
account_keys: self.account_keys.iter().map(ToString::to_string).collect(),
|
||||
recent_blockhash: self.recent_blockhash.to_string(),
|
||||
instructions: self.instructions.iter().map(Into::into).collect(),
|
||||
address_table_lookups: Some(
|
||||
self.address_table_lookups.iter().map(Into::into).collect(),
|
||||
),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -826,6 +973,27 @@ pub struct UiRawMessage {
|
|||
pub account_keys: Vec<String>,
|
||||
pub recent_blockhash: String,
|
||||
pub instructions: Vec<UiCompiledInstruction>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub address_table_lookups: Option<Vec<UiAddressTableLookup>>,
|
||||
}
|
||||
|
||||
/// A duplicate representation of a MessageAddressTableLookup, in raw format, for pretty JSON serialization
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct UiAddressTableLookup {
|
||||
pub account_key: String,
|
||||
pub writable_indexes: Vec<u8>,
|
||||
pub readonly_indexes: Vec<u8>,
|
||||
}
|
||||
|
||||
impl From<&MessageAddressTableLookup> for UiAddressTableLookup {
|
||||
fn from(lookup: &MessageAddressTableLookup) -> Self {
|
||||
Self {
|
||||
account_key: lookup.account_key.to_string(),
|
||||
writable_indexes: lookup.writable_indexes.clone(),
|
||||
readonly_indexes: lookup.readonly_indexes.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A duplicate representation of a Message, in parsed format, for pretty JSON serialization
|
||||
|
@ -835,6 +1003,7 @@ pub struct UiParsedMessage {
|
|||
pub account_keys: Vec<ParsedAccount>,
|
||||
pub recent_blockhash: String,
|
||||
pub instructions: Vec<UiInstruction>,
|
||||
pub address_table_lookups: Option<Vec<UiAddressTableLookup>>,
|
||||
}
|
||||
|
||||
// A serialized `Vec<TransactionByAddrInfo>` is stored in the `tx-by-addr` table. The row keys are
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use solana_sdk::message::Message;
|
||||
use solana_sdk::message::{v0::LoadedMessage, Message};
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
|
@ -20,16 +20,34 @@ pub fn parse_accounts(message: &Message) -> Vec<ParsedAccount> {
|
|||
accounts
|
||||
}
|
||||
|
||||
pub fn parse_static_accounts(message: &LoadedMessage) -> Vec<ParsedAccount> {
|
||||
let mut accounts: Vec<ParsedAccount> = vec![];
|
||||
for (i, account_key) in message.static_account_keys().iter().enumerate() {
|
||||
accounts.push(ParsedAccount {
|
||||
pubkey: account_key.to_string(),
|
||||
writable: message.is_writable(i),
|
||||
signer: message.is_signer(i),
|
||||
});
|
||||
}
|
||||
accounts
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use {super::*, solana_sdk::message::MessageHeader};
|
||||
use {
|
||||
super::*,
|
||||
solana_sdk::{
|
||||
message::{v0, v0::LoadedAddresses, MessageHeader},
|
||||
pubkey::Pubkey,
|
||||
},
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn test_parse_accounts() {
|
||||
let pubkey0 = solana_sdk::pubkey::new_rand();
|
||||
let pubkey1 = solana_sdk::pubkey::new_rand();
|
||||
let pubkey2 = solana_sdk::pubkey::new_rand();
|
||||
let pubkey3 = solana_sdk::pubkey::new_rand();
|
||||
let pubkey0 = Pubkey::new_unique();
|
||||
let pubkey1 = Pubkey::new_unique();
|
||||
let pubkey2 = Pubkey::new_unique();
|
||||
let pubkey3 = Pubkey::new_unique();
|
||||
let message = Message {
|
||||
header: MessageHeader {
|
||||
num_required_signatures: 2,
|
||||
|
@ -66,4 +84,53 @@ mod test {
|
|||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_static_accounts() {
|
||||
let pubkey0 = Pubkey::new_unique();
|
||||
let pubkey1 = Pubkey::new_unique();
|
||||
let pubkey2 = Pubkey::new_unique();
|
||||
let pubkey3 = Pubkey::new_unique();
|
||||
let message = LoadedMessage::new(
|
||||
v0::Message {
|
||||
header: MessageHeader {
|
||||
num_required_signatures: 2,
|
||||
num_readonly_signed_accounts: 1,
|
||||
num_readonly_unsigned_accounts: 1,
|
||||
},
|
||||
account_keys: vec![pubkey0, pubkey1, pubkey2, pubkey3],
|
||||
..v0::Message::default()
|
||||
},
|
||||
LoadedAddresses {
|
||||
writable: vec![Pubkey::new_unique()],
|
||||
readonly: vec![Pubkey::new_unique()],
|
||||
},
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
parse_static_accounts(&message),
|
||||
vec![
|
||||
ParsedAccount {
|
||||
pubkey: pubkey0.to_string(),
|
||||
writable: true,
|
||||
signer: true,
|
||||
},
|
||||
ParsedAccount {
|
||||
pubkey: pubkey1.to_string(),
|
||||
writable: false,
|
||||
signer: true,
|
||||
},
|
||||
ParsedAccount {
|
||||
pubkey: pubkey2.to_string(),
|
||||
writable: true,
|
||||
signer: false,
|
||||
},
|
||||
ParsedAccount {
|
||||
pubkey: pubkey3.to_string(),
|
||||
writable: false,
|
||||
signer: false,
|
||||
},
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue