Store versioned transactions in the ledger, disabled by default (#19139)
* Add support for versioned transactions, but disable by default * merge conflicts * trent's feedback * bump Cargo.lock * Fix transaction error encoding * Rename legacy_transaction method * cargo clippy * Clean up casts, int arithmetic, and unused methods * Check for duplicates in sanitized message conversion * fix clippy * fix new test * Fix bpf conditional compilation for message module
This commit is contained in:
143
rpc/src/rpc.rs
143
rpc/src/rpc.rs
@ -60,15 +60,14 @@ use {
|
||||
epoch_schedule::EpochSchedule,
|
||||
exit::Exit,
|
||||
hash::Hash,
|
||||
message::Message,
|
||||
message::{Message, SanitizedMessage},
|
||||
pubkey::Pubkey,
|
||||
sanitize::Sanitize,
|
||||
signature::{Keypair, Signature, Signer},
|
||||
stake::state::StakeState,
|
||||
stake_history::StakeHistory,
|
||||
system_instruction,
|
||||
sysvar::stake_history,
|
||||
transaction::{self, Transaction, TransactionError},
|
||||
transaction::{self, SanitizedTransaction, TransactionError, VersionedTransaction},
|
||||
},
|
||||
solana_streamer::socket::SocketAddrSpace,
|
||||
solana_transaction_status::{
|
||||
@ -84,6 +83,7 @@ use {
|
||||
any::type_name,
|
||||
cmp::{max, min},
|
||||
collections::{HashMap, HashSet},
|
||||
convert::TryFrom,
|
||||
net::SocketAddr,
|
||||
str::FromStr,
|
||||
sync::{
|
||||
@ -1934,7 +1934,7 @@ impl JsonRpcRequestProcessor {
|
||||
fn get_fee_for_message(
|
||||
&self,
|
||||
blockhash: &Hash,
|
||||
message: &Message,
|
||||
message: &SanitizedMessage,
|
||||
commitment: Option<CommitmentConfig>,
|
||||
) -> Result<RpcResponse<Option<u64>>> {
|
||||
let bank = self.bank(commitment);
|
||||
@ -1944,7 +1944,7 @@ impl JsonRpcRequestProcessor {
|
||||
}
|
||||
|
||||
fn verify_transaction(
|
||||
transaction: &Transaction,
|
||||
transaction: &SanitizedTransaction,
|
||||
libsecp256k1_0_5_upgrade_enabled: bool,
|
||||
) -> Result<()> {
|
||||
if transaction.verify().is_err() {
|
||||
@ -2184,15 +2184,11 @@ fn get_token_program_id_and_mint(
|
||||
|
||||
fn _send_transaction(
|
||||
meta: JsonRpcRequestProcessor,
|
||||
transaction: Transaction,
|
||||
signature: Signature,
|
||||
wire_transaction: Vec<u8>,
|
||||
last_valid_block_height: u64,
|
||||
durable_nonce_info: Option<(Pubkey, Hash)>,
|
||||
) -> Result<String> {
|
||||
if transaction.signatures.is_empty() {
|
||||
return Err(RpcCustomError::TransactionSignatureVerificationFailure.into());
|
||||
}
|
||||
let signature = transaction.signatures[0];
|
||||
let transaction_info = TransactionInfo::new(
|
||||
signature,
|
||||
wire_transaction,
|
||||
@ -3255,9 +3251,15 @@ pub mod rpc_full {
|
||||
Error::internal_error()
|
||||
})?;
|
||||
|
||||
let signature = if !transaction.signatures.is_empty() {
|
||||
transaction.signatures[0]
|
||||
} else {
|
||||
return Err(RpcCustomError::TransactionSignatureVerificationFailure.into());
|
||||
};
|
||||
|
||||
_send_transaction(
|
||||
meta,
|
||||
transaction,
|
||||
signature,
|
||||
wire_transaction,
|
||||
last_valid_block_height,
|
||||
None,
|
||||
@ -3273,26 +3275,23 @@ pub mod rpc_full {
|
||||
debug!("send_transaction rpc request received");
|
||||
let config = config.unwrap_or_default();
|
||||
let encoding = config.encoding.unwrap_or(UiTransactionEncoding::Base58);
|
||||
let (wire_transaction, transaction) =
|
||||
decode_and_deserialize::<Transaction>(data, encoding)?;
|
||||
let (wire_transaction, unsanitized_tx) =
|
||||
decode_and_deserialize::<VersionedTransaction>(data, encoding)?;
|
||||
|
||||
let preflight_commitment = config
|
||||
.preflight_commitment
|
||||
.map(|commitment| CommitmentConfig { commitment });
|
||||
let preflight_bank = &*meta.bank(preflight_commitment);
|
||||
let transaction = sanitize_transaction(unsanitized_tx)?;
|
||||
let signature = *transaction.signature();
|
||||
|
||||
let mut last_valid_block_height = preflight_bank
|
||||
.get_blockhash_last_valid_block_height(&transaction.message.recent_blockhash)
|
||||
.get_blockhash_last_valid_block_height(transaction.message().recent_blockhash())
|
||||
.unwrap_or(0);
|
||||
|
||||
let durable_nonce_info = solana_sdk::transaction::uses_durable_nonce(&transaction)
|
||||
.and_then(|nonce_ix| {
|
||||
solana_sdk::transaction::get_nonce_pubkey_from_instruction(
|
||||
nonce_ix,
|
||||
&transaction,
|
||||
)
|
||||
})
|
||||
.map(|&pubkey| (pubkey, transaction.message.recent_blockhash));
|
||||
let durable_nonce_info = transaction
|
||||
.get_durable_nonce()
|
||||
.map(|&pubkey| (pubkey, *transaction.message().recent_blockhash()));
|
||||
if durable_nonce_info.is_some() {
|
||||
// While it uses a defined constant, this last_valid_block_height value is chosen arbitrarily.
|
||||
// It provides a fallback timeout for durable-nonce transaction retries in case of
|
||||
@ -3333,7 +3332,7 @@ pub mod rpc_full {
|
||||
logs,
|
||||
post_simulation_accounts: _,
|
||||
units_consumed,
|
||||
} = preflight_bank.simulate_transaction(&transaction)
|
||||
} = preflight_bank.simulate_transaction(transaction)
|
||||
{
|
||||
match err {
|
||||
TransactionError::BlockhashNotFound => {
|
||||
@ -3358,7 +3357,7 @@ pub mod rpc_full {
|
||||
|
||||
_send_transaction(
|
||||
meta,
|
||||
transaction,
|
||||
signature,
|
||||
wire_transaction,
|
||||
last_valid_block_height,
|
||||
durable_nonce_info,
|
||||
@ -3374,31 +3373,32 @@ pub mod rpc_full {
|
||||
debug!("simulate_transaction rpc request received");
|
||||
let config = config.unwrap_or_default();
|
||||
let encoding = config.encoding.unwrap_or(UiTransactionEncoding::Base58);
|
||||
let (_, mut transaction) = decode_and_deserialize::<Transaction>(data, encoding)?;
|
||||
let (_, mut unsanitized_tx) =
|
||||
decode_and_deserialize::<VersionedTransaction>(data, encoding)?;
|
||||
|
||||
let bank = &*meta.bank(config.commitment);
|
||||
if config.sig_verify {
|
||||
if config.replace_recent_blockhash {
|
||||
if config.replace_recent_blockhash {
|
||||
if config.sig_verify {
|
||||
return Err(Error::invalid_params(
|
||||
"sigVerify may not be used with replaceRecentBlockhash",
|
||||
));
|
||||
}
|
||||
unsanitized_tx
|
||||
.message
|
||||
.set_recent_blockhash(bank.last_blockhash());
|
||||
}
|
||||
|
||||
if let Err(e) =
|
||||
verify_transaction(&transaction, bank.libsecp256k1_0_5_upgrade_enabled())
|
||||
{
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
if config.replace_recent_blockhash {
|
||||
transaction.message.recent_blockhash = bank.last_blockhash();
|
||||
let transaction = sanitize_transaction(unsanitized_tx)?;
|
||||
if config.sig_verify {
|
||||
verify_transaction(&transaction, bank.libsecp256k1_0_5_upgrade_enabled())?;
|
||||
}
|
||||
|
||||
let TransactionSimulationResult {
|
||||
result,
|
||||
logs,
|
||||
post_simulation_accounts,
|
||||
units_consumed,
|
||||
} = bank.simulate_transaction(&transaction);
|
||||
} = bank.simulate_transaction(transaction);
|
||||
|
||||
let accounts = if let Some(config_accounts) = config.accounts {
|
||||
let accounts_encoding = config_accounts
|
||||
@ -3424,21 +3424,11 @@ pub mod rpc_full {
|
||||
accounts.push(if result.is_err() {
|
||||
None
|
||||
} else {
|
||||
(0..transaction.message.account_keys.len())
|
||||
.position(|i| {
|
||||
post_simulation_accounts
|
||||
.get(i)
|
||||
.map(|(key, _account)| *key == address)
|
||||
.unwrap_or(false)
|
||||
})
|
||||
.map(|i| {
|
||||
UiAccount::encode(
|
||||
&address,
|
||||
&post_simulation_accounts[i].1,
|
||||
accounts_encoding,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
post_simulation_accounts
|
||||
.iter()
|
||||
.find(|(key, _account)| key == &address)
|
||||
.map(|(pubkey, account)| {
|
||||
UiAccount::encode(pubkey, account, accounts_encoding, None, None)
|
||||
})
|
||||
});
|
||||
}
|
||||
@ -3616,7 +3606,11 @@ pub mod rpc_full {
|
||||
let blockhash = Hash::from_str(&blockhash)
|
||||
.map_err(|e| Error::invalid_params(format!("{:?}", e)))?;
|
||||
let (_, message) = decode_and_deserialize::<Message>(data, encoding)?;
|
||||
meta.get_fee_for_message(&blockhash, &message, commitment)
|
||||
SanitizedMessage::try_from(message)
|
||||
.map_err(|err| {
|
||||
Error::invalid_params(format!("invalid transaction message: {}", err))
|
||||
})
|
||||
.and_then(|message| meta.get_fee_for_message(&blockhash, &message, commitment))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -4002,7 +3996,7 @@ fn decode_and_deserialize<T>(
|
||||
encoding: UiTransactionEncoding,
|
||||
) -> Result<(Vec<u8>, T)>
|
||||
where
|
||||
T: serde::de::DeserializeOwned + Sanitize,
|
||||
T: serde::de::DeserializeOwned,
|
||||
{
|
||||
let wire_output = match encoding {
|
||||
UiTransactionEncoding::Base58 => {
|
||||
@ -4059,20 +4053,17 @@ where
|
||||
info!("deserialize error: {}", err);
|
||||
Error::invalid_params(&err.to_string())
|
||||
})
|
||||
.and_then(|output: T| {
|
||||
if let Err(err) = output.sanitize() {
|
||||
Err(Error::invalid_params(format!(
|
||||
"invalid {}: {}",
|
||||
type_name::<T>(),
|
||||
err
|
||||
)))
|
||||
} else {
|
||||
Ok(output)
|
||||
}
|
||||
})
|
||||
.map(|output| (wire_output, output))
|
||||
}
|
||||
|
||||
fn sanitize_transaction(transaction: VersionedTransaction) -> Result<SanitizedTransaction> {
|
||||
let message_hash = transaction.message.hash();
|
||||
SanitizedTransaction::try_create(transaction, message_hash, |_| {
|
||||
Err(TransactionError::UnsupportedVersion)
|
||||
})
|
||||
.map_err(|err| Error::invalid_params(format!("invalid transaction: {}", err)))
|
||||
}
|
||||
|
||||
pub(crate) fn create_validator_exit(exit: &Arc<AtomicBool>) -> Arc<RwLock<Exit>> {
|
||||
let mut validator_exit = Exit::default();
|
||||
let exit_ = exit.clone();
|
||||
@ -4114,7 +4105,7 @@ pub fn create_test_transactions_and_populate_blockstore(
|
||||
Hash::default(),
|
||||
);
|
||||
let entry_3 = solana_entry::entry::next_entry(&entry_2.hash, 1, vec![fail_tx]);
|
||||
let mut entries = vec![entry_1, entry_2, entry_3];
|
||||
let entries = vec![entry_1, entry_2, entry_3];
|
||||
|
||||
let shreds = solana_ledger::blockstore::entries_to_test_shreds(
|
||||
entries.clone(),
|
||||
@ -4140,7 +4131,7 @@ pub fn create_test_transactions_and_populate_blockstore(
|
||||
// that they are matched properly by get_rooted_block
|
||||
let _result = solana_ledger::blockstore_processor::process_entries(
|
||||
&bank,
|
||||
&mut entries,
|
||||
entries,
|
||||
true,
|
||||
Some(
|
||||
&solana_ledger::blockstore_processor::TransactionStatusSender {
|
||||
@ -4193,7 +4184,7 @@ pub mod tests {
|
||||
signature::{Keypair, Signer},
|
||||
system_program, system_transaction,
|
||||
timing::slot_duration_from_slots_per_year,
|
||||
transaction::{self, TransactionError},
|
||||
transaction::{self, Transaction, TransactionError},
|
||||
},
|
||||
solana_transaction_status::{
|
||||
EncodedConfirmedBlock, EncodedTransaction, EncodedTransactionWithStatusMeta,
|
||||
@ -5986,7 +5977,7 @@ pub mod tests {
|
||||
assert_eq!(
|
||||
res,
|
||||
Some(
|
||||
r#"{"jsonrpc":"2.0","error":{"code":-32602,"message":"invalid solana_sdk::transaction::Transaction: index out of bounds"},"id":1}"#.to_string(),
|
||||
r#"{"jsonrpc":"2.0","error":{"code":-32602,"message":"invalid transaction: Transaction failed to sanitize accounts offsets correctly"},"id":1}"#.to_string(),
|
||||
)
|
||||
);
|
||||
let mut bad_transaction = system_transaction::transfer(
|
||||
@ -6050,7 +6041,7 @@ pub mod tests {
|
||||
assert_eq!(
|
||||
res,
|
||||
Some(
|
||||
r#"{"jsonrpc":"2.0","error":{"code":-32602,"message":"invalid solana_sdk::transaction::Transaction: index out of bounds"},"id":1}"#.to_string(),
|
||||
r#"{"jsonrpc":"2.0","error":{"code":-32602,"message":"invalid transaction: Transaction failed to sanitize accounts offsets correctly"},"id":1}"#.to_string(),
|
||||
)
|
||||
);
|
||||
}
|
||||
@ -7795,19 +7786,25 @@ pub mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decode_and_deserialize_unsanitary() {
|
||||
fn test_sanitize_unsanitary() {
|
||||
let unsanitary_tx58 = "ju9xZWuDBX4pRxX2oZkTjxU5jB4SSTgEGhX8bQ8PURNzyzqKMPPpNvWihx8zUe\
|
||||
FfrbVNoAaEsNKZvGzAnTDy5bhNT9kt6KFCTBixpvrLCzg4M5UdFUQYrn1gdgjX\
|
||||
pLHxcaShD81xBNaFDgnA2nkkdHnKtZt4hVSfKAmw3VRZbjrZ7L2fKZBx21CwsG\
|
||||
hD6onjM2M3qZW5C8J6d1pj41MxKmZgPBSha3MyKkNLkAGFASK"
|
||||
.to_string();
|
||||
|
||||
let unsanitary_versioned_tx = decode_and_deserialize::<VersionedTransaction>(
|
||||
unsanitary_tx58,
|
||||
UiTransactionEncoding::Base58,
|
||||
)
|
||||
.unwrap()
|
||||
.1;
|
||||
let expect58 = Error::invalid_params(
|
||||
"invalid solana_sdk::transaction::Transaction: index out of bounds".to_string(),
|
||||
"invalid transaction: Transaction failed to sanitize accounts offsets correctly"
|
||||
.to_string(),
|
||||
);
|
||||
assert_eq!(
|
||||
decode_and_deserialize::<Transaction>(unsanitary_tx58, UiTransactionEncoding::Base58)
|
||||
.unwrap_err(),
|
||||
sanitize_transaction(unsanitary_versioned_tx).unwrap_err(),
|
||||
expect58
|
||||
);
|
||||
}
|
||||
|
@ -92,7 +92,7 @@ impl TransactionStatusService {
|
||||
log_messages,
|
||||
rent_debits,
|
||||
) in izip!(
|
||||
&transactions,
|
||||
transactions,
|
||||
statuses,
|
||||
balances.pre_balances,
|
||||
balances.post_balances,
|
||||
@ -100,20 +100,18 @@ impl TransactionStatusService {
|
||||
token_balances.post_token_balances,
|
||||
inner_instructions_iter,
|
||||
transaction_logs_iter,
|
||||
rent_debits.into_iter(),
|
||||
rent_debits,
|
||||
) {
|
||||
if Bank::can_commit(&status) && !transaction.signatures.is_empty() {
|
||||
if Bank::can_commit(&status) {
|
||||
let fee_calculator = nonce_rollback
|
||||
.map(|nonce_rollback| nonce_rollback.fee_calculator())
|
||||
.unwrap_or_else(|| {
|
||||
#[allow(deprecated)]
|
||||
bank.get_fee_calculator(&transaction.message().recent_blockhash)
|
||||
bank.get_fee_calculator(transaction.message().recent_blockhash())
|
||||
})
|
||||
.expect("FeeCalculator must exist");
|
||||
#[allow(deprecated)]
|
||||
let fee = fee_calculator.calculate_fee(transaction.message());
|
||||
let (writable_keys, readonly_keys) =
|
||||
transaction.message.get_account_keys_by_lock_type();
|
||||
let fee = transaction.message().calculate_fee(&fee_calculator);
|
||||
let tx_account_locks = transaction.get_account_locks();
|
||||
|
||||
let inner_instructions = inner_instructions.map(|inner_instructions| {
|
||||
inner_instructions
|
||||
@ -146,9 +144,9 @@ impl TransactionStatusService {
|
||||
blockstore
|
||||
.write_transaction_status(
|
||||
slot,
|
||||
transaction.signatures[0],
|
||||
writable_keys,
|
||||
readonly_keys,
|
||||
*transaction.signature(),
|
||||
tx_account_locks.writable,
|
||||
tx_account_locks.readonly,
|
||||
TransactionStatusMeta {
|
||||
status,
|
||||
fee,
|
||||
|
Reference in New Issue
Block a user