* `solana decode-transaction` no longer panics on unsanitary transactions (cherry picked from commite9b5d65f40
) * Ensure sanitary transactions (cherry picked from commit04ce33a04e
) Co-authored-by: Michael Vines <mvines@gmail.com>
This commit is contained in:
@ -54,6 +54,7 @@ use solana_sdk::{
|
|||||||
epoch_schedule::EpochSchedule,
|
epoch_schedule::EpochSchedule,
|
||||||
hash::Hash,
|
hash::Hash,
|
||||||
pubkey::Pubkey,
|
pubkey::Pubkey,
|
||||||
|
sanitize::Sanitize,
|
||||||
signature::Signature,
|
signature::Signature,
|
||||||
stake_history::StakeHistory,
|
stake_history::StakeHistory,
|
||||||
system_instruction,
|
system_instruction,
|
||||||
@ -2868,6 +2869,16 @@ fn deserialize_transaction(
|
|||||||
info!("transaction deserialize error: {:?}", err);
|
info!("transaction deserialize error: {:?}", err);
|
||||||
Error::invalid_params(&err.to_string())
|
Error::invalid_params(&err.to_string())
|
||||||
})
|
})
|
||||||
|
.and_then(|transaction: Transaction| {
|
||||||
|
if let Err(err) = transaction.sanitize() {
|
||||||
|
Err(Error::invalid_params(format!(
|
||||||
|
"invalid transaction: {}",
|
||||||
|
err
|
||||||
|
)))
|
||||||
|
} else {
|
||||||
|
Ok(transaction)
|
||||||
|
}
|
||||||
|
})
|
||||||
.map(|transaction| (wire_transaction, transaction))
|
.map(|transaction| (wire_transaction, transaction))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -4513,7 +4524,7 @@ pub mod tests {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
res,
|
res,
|
||||||
Some(
|
Some(
|
||||||
r#"{"jsonrpc":"2.0","error":{"code":-32002,"message":"Transaction simulation failed: Transaction failed to sanitize accounts offsets correctly","data":{"err":"SanitizeFailure","logs":[]}},"id":1}"#.to_string(),
|
r#"{"jsonrpc":"2.0","error":{"code":-32602,"message":"invalid transaction: index out of bounds"},"id":1}"#.to_string(),
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
let mut bad_transaction = system_transaction::transfer(
|
let mut bad_transaction = system_transaction::transfer(
|
||||||
@ -4567,18 +4578,17 @@ pub mod tests {
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
// sendTransaction will fail due to no signer. Skip preflight so signature verification
|
// sendTransaction will fail due to sanitization failure
|
||||||
// doesn't catch it
|
|
||||||
bad_transaction.signatures.clear();
|
bad_transaction.signatures.clear();
|
||||||
let req = format!(
|
let req = format!(
|
||||||
r#"{{"jsonrpc":"2.0","id":1,"method":"sendTransaction","params":["{}", {{"skipPreflight": true}}]}}"#,
|
r#"{{"jsonrpc":"2.0","id":1,"method":"sendTransaction","params":["{}"]}}"#,
|
||||||
bs58::encode(serialize(&bad_transaction).unwrap()).into_string()
|
bs58::encode(serialize(&bad_transaction).unwrap()).into_string()
|
||||||
);
|
);
|
||||||
let res = io.handle_request_sync(&req, meta);
|
let res = io.handle_request_sync(&req, meta);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
res,
|
res,
|
||||||
Some(
|
Some(
|
||||||
r#"{"jsonrpc":"2.0","error":{"code":-32003,"message":"Transaction signature verification failure"},"id":1}"#.to_string(),
|
r#"{"jsonrpc":"2.0","error":{"code":-32602,"message":"invalid transaction: index out of bounds"},"id":1}"#.to_string(),
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -6099,7 +6109,7 @@ pub mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_deserialize_transacion_too_large_payloads_fail() {
|
fn test_deserialize_transaction_too_large_payloads_fail() {
|
||||||
// +2 because +1 still fits in base64 encoded worst-case
|
// +2 because +1 still fits in base64 encoded worst-case
|
||||||
let too_big = PACKET_DATA_SIZE + 2;
|
let too_big = PACKET_DATA_SIZE + 2;
|
||||||
let tx_ser = vec![0xffu8; too_big];
|
let tx_ser = vec![0xffu8; too_big];
|
||||||
@ -6140,4 +6150,20 @@ pub mod tests {
|
|||||||
expect
|
expect
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_deserialize_transaction_unsanitary() {
|
||||||
|
let unsanitary_tx58 = "ju9xZWuDBX4pRxX2oZkTjxU5jB4SSTgEGhX8bQ8PURNzyzqKMPPpNvWihx8zUe\
|
||||||
|
FfrbVNoAaEsNKZvGzAnTDy5bhNT9kt6KFCTBixpvrLCzg4M5UdFUQYrn1gdgjX\
|
||||||
|
pLHxcaShD81xBNaFDgnA2nkkdHnKtZt4hVSfKAmw3VRZbjrZ7L2fKZBx21CwsG\
|
||||||
|
hD6onjM2M3qZW5C8J6d1pj41MxKmZgPBSha3MyKkNLkAGFASK"
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
let expect58 =
|
||||||
|
Error::invalid_params("invalid transaction: index out of bounds".to_string());
|
||||||
|
assert_eq!(
|
||||||
|
deserialize_transaction(unsanitary_tx58, UiTransactionEncoding::Base58).unwrap_err(),
|
||||||
|
expect58
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,7 @@ use solana_sdk::{
|
|||||||
instruction::CompiledInstruction,
|
instruction::CompiledInstruction,
|
||||||
message::{Message, MessageHeader},
|
message::{Message, MessageHeader},
|
||||||
pubkey::Pubkey,
|
pubkey::Pubkey,
|
||||||
|
sanitize::Sanitize,
|
||||||
signature::Signature,
|
signature::Signature,
|
||||||
transaction::{Result, Transaction, TransactionError},
|
transaction::{Result, Transaction, TransactionError},
|
||||||
};
|
};
|
||||||
@ -507,7 +508,7 @@ impl EncodedTransaction {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn decode(&self) -> Option<Transaction> {
|
pub fn decode(&self) -> Option<Transaction> {
|
||||||
match self {
|
let transaction: Option<Transaction> = match self {
|
||||||
EncodedTransaction::Json(_) => None,
|
EncodedTransaction::Json(_) => None,
|
||||||
EncodedTransaction::LegacyBinary(blob) => bs58::decode(blob)
|
EncodedTransaction::LegacyBinary(blob) => bs58::decode(blob)
|
||||||
.into_vec()
|
.into_vec()
|
||||||
@ -525,7 +526,8 @@ impl EncodedTransaction {
|
|||||||
| UiTransactionEncoding::Json
|
| UiTransactionEncoding::Json
|
||||||
| UiTransactionEncoding::JsonParsed => None,
|
| UiTransactionEncoding::JsonParsed => None,
|
||||||
},
|
},
|
||||||
}
|
};
|
||||||
|
transaction.filter(|transaction| transaction.sanitize().is_ok())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -533,6 +535,20 @@ impl EncodedTransaction {
|
|||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_decode_invalid_transaction() {
|
||||||
|
// This transaction will not pass sanitization
|
||||||
|
let unsanitary_transaction = EncodedTransaction::Binary(
|
||||||
|
"ju9xZWuDBX4pRxX2oZkTjxU5jB4SSTgEGhX8bQ8PURNzyzqKMPPpNvWihx8zUe\
|
||||||
|
FfrbVNoAaEsNKZvGzAnTDy5bhNT9kt6KFCTBixpvrLCzg4M5UdFUQYrn1gdgjX\
|
||||||
|
pLHxcaShD81xBNaFDgnA2nkkdHnKtZt4hVSfKAmw3VRZbjrZ7L2fKZBx21CwsG\
|
||||||
|
hD6onjM2M3qZW5C8J6d1pj41MxKmZgPBSha3MyKkNLkAGFASK"
|
||||||
|
.to_string(),
|
||||||
|
UiTransactionEncoding::Base58,
|
||||||
|
);
|
||||||
|
assert!(unsanitary_transaction.decode().is_none());
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_satisfies_commitment() {
|
fn test_satisfies_commitment() {
|
||||||
let status = TransactionStatus {
|
let status = TransactionStatus {
|
||||||
|
Reference in New Issue
Block a user