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:
Justin Starry
2021-08-17 15:17:56 -07:00
committed by GitHub
parent 098e2b2de3
commit c50b01cb60
47 changed files with 2373 additions and 1049 deletions

View File

@ -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
);
}

View File

@ -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,