Add test_rpc_send_transaction_preflight
This commit is contained in:
105
core/src/rpc.rs
105
core/src/rpc.rs
@ -722,7 +722,7 @@ fn run_transaction_simulation(
|
|||||||
bank: &Bank,
|
bank: &Bank,
|
||||||
transactions: &[Transaction],
|
transactions: &[Transaction],
|
||||||
) -> transaction::Result<()> {
|
) -> transaction::Result<()> {
|
||||||
assert!(bank.is_frozen());
|
assert!(bank.is_frozen(), "simulation bank must be frozen");
|
||||||
|
|
||||||
let batch = bank.prepare_batch(transactions, None);
|
let batch = bank.prepare_batch(transactions, None);
|
||||||
let (_loaded_accounts, executed, _retryable_transactions, _transaction_count, _signature_count) =
|
let (_loaded_accounts, executed, _retryable_transactions, _transaction_count, _signature_count) =
|
||||||
@ -1456,8 +1456,10 @@ impl RpcSol for RpcSolImpl {
|
|||||||
let bank = &*meta.request_processor.read().unwrap().bank(None)?;
|
let bank = &*meta.request_processor.read().unwrap().bank(None)?;
|
||||||
if let Err(err) = run_transaction_simulation(&bank, &[transaction]) {
|
if let Err(err) = run_transaction_simulation(&bank, &[transaction]) {
|
||||||
// Note: it's possible that the transaction simulation failed but the actual
|
// Note: it's possible that the transaction simulation failed but the actual
|
||||||
// transaction would succeed. In these cases the user should use the
|
// transaction would succeed, such as when a transaction depends on an earlier
|
||||||
// config.skip_preflight flag
|
// transaction that has yet to reach max confirmations. In these cases the user
|
||||||
|
// should use the config.skip_preflight flag, and potentially in the future
|
||||||
|
// additional controls over what bank is used for preflight should be exposed.
|
||||||
return Err(RpcCustomError::SendTransactionPreflightFailure {
|
return Err(RpcCustomError::SendTransactionPreflightFailure {
|
||||||
message: format!("Transaction simulation failed: {}", err),
|
message: format!("Transaction simulation failed: {}", err),
|
||||||
}
|
}
|
||||||
@ -2852,6 +2854,103 @@ pub mod tests {
|
|||||||
assert_eq!(error["code"], ErrorCode::InvalidParams.code());
|
assert_eq!(error["code"], ErrorCode::InvalidParams.code());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_rpc_send_transaction_preflight() {
|
||||||
|
let exit = Arc::new(AtomicBool::new(false));
|
||||||
|
let validator_exit = create_validator_exit(&exit);
|
||||||
|
let ledger_path = get_tmp_ledger_path!();
|
||||||
|
let blockstore = Arc::new(Blockstore::open(&ledger_path).unwrap());
|
||||||
|
let block_commitment_cache = Arc::new(RwLock::new(
|
||||||
|
BlockCommitmentCache::default_with_blockstore(blockstore.clone()),
|
||||||
|
));
|
||||||
|
let bank_forks = new_bank_forks().0;
|
||||||
|
let health = RpcHealth::stub();
|
||||||
|
|
||||||
|
// Freeze bank 0 to prevent a panic in `run_transaction_simulation()`
|
||||||
|
bank_forks.write().unwrap().get(0).unwrap().freeze();
|
||||||
|
|
||||||
|
let mut io = MetaIoHandler::default();
|
||||||
|
let rpc = RpcSolImpl;
|
||||||
|
io.extend_with(rpc.to_delegate());
|
||||||
|
let meta = Meta {
|
||||||
|
request_processor: {
|
||||||
|
let request_processor = JsonRpcRequestProcessor::new(
|
||||||
|
JsonRpcConfig::default(),
|
||||||
|
bank_forks,
|
||||||
|
block_commitment_cache,
|
||||||
|
blockstore,
|
||||||
|
validator_exit,
|
||||||
|
health.clone(),
|
||||||
|
);
|
||||||
|
Arc::new(RwLock::new(request_processor))
|
||||||
|
},
|
||||||
|
cluster_info: Arc::new(ClusterInfo::new_with_invalid_keypair(
|
||||||
|
ContactInfo::new_with_socketaddr(&socketaddr!("127.0.0.1:1234")),
|
||||||
|
)),
|
||||||
|
genesis_hash: Hash::default(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut bad_transaction =
|
||||||
|
system_transaction::transfer(&Keypair::new(), &Pubkey::default(), 42, Hash::default());
|
||||||
|
|
||||||
|
// sendTransaction will fail because the blockhash is invalid
|
||||||
|
let req = format!(
|
||||||
|
r#"{{"jsonrpc":"2.0","id":1,"method":"sendTransaction","params":["{}"]}}"#,
|
||||||
|
bs58::encode(serialize(&bad_transaction).unwrap()).into_string()
|
||||||
|
);
|
||||||
|
let res = io.handle_request_sync(&req, meta.clone());
|
||||||
|
assert_eq!(
|
||||||
|
res,
|
||||||
|
Some(
|
||||||
|
r#"{"jsonrpc":"2.0","error":{"code":-32002,"message":"Transaction simulation failed: Blockhash not found"},"id":1}"#.to_string(),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// sendTransaction will fail due to poor node health
|
||||||
|
health.stub_set_health_status(Some(RpcHealthStatus::Behind));
|
||||||
|
let req = format!(
|
||||||
|
r#"{{"jsonrpc":"2.0","id":1,"method":"sendTransaction","params":["{}"]}}"#,
|
||||||
|
bs58::encode(serialize(&bad_transaction).unwrap()).into_string()
|
||||||
|
);
|
||||||
|
let res = io.handle_request_sync(&req, meta.clone());
|
||||||
|
assert_eq!(
|
||||||
|
res,
|
||||||
|
Some(
|
||||||
|
r#"{"jsonrpc":"2.0","error":{"code":-32002,"message":"RPC node is unhealthy, unable to simulate transaction"},"id":1}"#.to_string(),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
health.stub_set_health_status(None);
|
||||||
|
|
||||||
|
// sendTransaction will fail due to invalid signature
|
||||||
|
bad_transaction.signatures[0] = Signature::default();
|
||||||
|
|
||||||
|
let req = format!(
|
||||||
|
r#"{{"jsonrpc":"2.0","id":1,"method":"sendTransaction","params":["{}"]}}"#,
|
||||||
|
bs58::encode(serialize(&bad_transaction).unwrap()).into_string()
|
||||||
|
);
|
||||||
|
let res = io.handle_request_sync(&req, meta.clone());
|
||||||
|
assert_eq!(
|
||||||
|
res,
|
||||||
|
Some(
|
||||||
|
r#"{"jsonrpc":"2.0","error":{"code":-32002,"message":"Transaction signature verification failed"},"id":1}"#.to_string(),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// sendTransaction will now succeed because skipPreflight=true even though it's a bad
|
||||||
|
// transaction
|
||||||
|
let req = format!(
|
||||||
|
r#"{{"jsonrpc":"2.0","id":1,"method":"sendTransaction","params":["{}", {{"skipPreflight": true}}]}}"#,
|
||||||
|
bs58::encode(serialize(&bad_transaction).unwrap()).into_string()
|
||||||
|
);
|
||||||
|
let res = io.handle_request_sync(&req, meta);
|
||||||
|
assert_eq!(
|
||||||
|
res,
|
||||||
|
Some(
|
||||||
|
r#"{"jsonrpc":"2.0","result":"1111111111111111111111111111111111111111111111111111111111111111","id":1}"#.to_string(),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_rpc_get_tpu_addr() {
|
fn test_rpc_get_tpu_addr() {
|
||||||
let cluster_info = Arc::new(ClusterInfo::new_with_invalid_keypair(
|
let cluster_info = Arc::new(ClusterInfo::new_with_invalid_keypair(
|
||||||
|
@ -6,7 +6,7 @@ use std::{
|
|||||||
sync::Arc,
|
sync::Arc,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(PartialEq)]
|
#[derive(PartialEq, Clone, Copy)]
|
||||||
pub enum RpcHealthStatus {
|
pub enum RpcHealthStatus {
|
||||||
Ok,
|
Ok,
|
||||||
Behind, // Validator is behind its trusted validators
|
Behind, // Validator is behind its trusted validators
|
||||||
@ -17,6 +17,8 @@ pub struct RpcHealth {
|
|||||||
trusted_validators: Option<HashSet<Pubkey>>,
|
trusted_validators: Option<HashSet<Pubkey>>,
|
||||||
health_check_slot_distance: u64,
|
health_check_slot_distance: u64,
|
||||||
override_health_check: Arc<AtomicBool>,
|
override_health_check: Arc<AtomicBool>,
|
||||||
|
#[cfg(test)]
|
||||||
|
stub_health_status: std::sync::RwLock<Option<RpcHealthStatus>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RpcHealth {
|
impl RpcHealth {
|
||||||
@ -31,10 +33,19 @@ impl RpcHealth {
|
|||||||
trusted_validators,
|
trusted_validators,
|
||||||
health_check_slot_distance,
|
health_check_slot_distance,
|
||||||
override_health_check,
|
override_health_check,
|
||||||
|
#[cfg(test)]
|
||||||
|
stub_health_status: std::sync::RwLock::new(None),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn check(&self) -> RpcHealthStatus {
|
pub fn check(&self) -> RpcHealthStatus {
|
||||||
|
#[cfg(test)]
|
||||||
|
{
|
||||||
|
if let Some(stub_health_status) = *self.stub_health_status.read().unwrap() {
|
||||||
|
return stub_health_status;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if self.override_health_check.load(Ordering::Relaxed) {
|
if self.override_health_check.load(Ordering::Relaxed) {
|
||||||
RpcHealthStatus::Ok
|
RpcHealthStatus::Ok
|
||||||
} else if let Some(trusted_validators) = &self.trusted_validators {
|
} else if let Some(trusted_validators) = &self.trusted_validators {
|
||||||
@ -101,4 +112,9 @@ impl RpcHealth {
|
|||||||
Arc::new(AtomicBool::new(false)),
|
Arc::new(AtomicBool::new(false)),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
pub(crate) fn stub_set_health_status(&self, stub_health_status: Option<RpcHealthStatus>) {
|
||||||
|
*self.stub_health_status.write().unwrap() = stub_health_status;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user