Add preflight checks to sendTransaction RPC method
This commit is contained in:
parent
27e2e3665a
commit
189aa7962e
@ -6,7 +6,13 @@ pub struct RpcSignatureStatusConfig {
|
||||
pub search_transaction_history: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RpcSendTransactionConfig {
|
||||
pub skip_preflight: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RpcSimulateTransactionConfig {
|
||||
pub sig_verify: bool,
|
||||
|
@ -713,6 +713,19 @@ fn verify_signature(input: &str) -> Result<Signature> {
|
||||
.map_err(|e| Error::invalid_params(format!("{:?}", e)))
|
||||
}
|
||||
|
||||
/// Run transactions against a frozen bank without committing the results
|
||||
fn run_transaction_simulation(
|
||||
bank: &Bank,
|
||||
transactions: &[Transaction],
|
||||
) -> transaction::Result<()> {
|
||||
assert!(bank.is_frozen());
|
||||
|
||||
let batch = bank.prepare_batch(transactions, None);
|
||||
let (_loaded_accounts, executed, _retryable_transactions, _transaction_count, _signature_count) =
|
||||
bank.load_and_execute_transactions(&batch, solana_sdk::clock::MAX_PROCESSING_AGE);
|
||||
executed[0].0.clone().map(|_| ())
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Meta {
|
||||
pub request_processor: Arc<RwLock<JsonRpcRequestProcessor>>,
|
||||
@ -904,7 +917,12 @@ pub trait RpcSol {
|
||||
) -> Result<String>;
|
||||
|
||||
#[rpc(meta, name = "sendTransaction")]
|
||||
fn send_transaction(&self, meta: Self::Metadata, data: String) -> Result<String>;
|
||||
fn send_transaction(
|
||||
&self,
|
||||
meta: Self::Metadata,
|
||||
data: String,
|
||||
config: Option<RpcSendTransactionConfig>,
|
||||
) -> Result<String>;
|
||||
|
||||
#[rpc(meta, name = "simulateTransaction")]
|
||||
fn simulate_transaction(
|
||||
@ -1406,8 +1424,36 @@ impl RpcSol for RpcSolImpl {
|
||||
}
|
||||
}
|
||||
|
||||
fn send_transaction(&self, meta: Self::Metadata, data: String) -> Result<String> {
|
||||
fn send_transaction(
|
||||
&self,
|
||||
meta: Self::Metadata,
|
||||
data: String,
|
||||
config: Option<RpcSendTransactionConfig>,
|
||||
) -> Result<String> {
|
||||
let config = config.unwrap_or_default();
|
||||
let (wire_transaction, transaction) = deserialize_bs58_transaction(data)?;
|
||||
let signature = transaction.signatures[0].to_string();
|
||||
|
||||
if !config.skip_preflight {
|
||||
if transaction.verify().is_err() {
|
||||
return Err(RpcCustomError::SendTransactionPreflightFailure {
|
||||
message: "Transaction signature verification failed".into(),
|
||||
}
|
||||
.into());
|
||||
}
|
||||
|
||||
let bank = &*meta.request_processor.read().unwrap().bank(None)?;
|
||||
if let Err(err) = run_transaction_simulation(&bank, &[transaction]) {
|
||||
// Note: it's possible that the transaction simulation failed but the actual
|
||||
// transaction would succeed. In these cases the user should use the
|
||||
// config.skip_preflight flag
|
||||
return Err(RpcCustomError::SendTransactionPreflightFailure {
|
||||
message: format!("Transaction simulation failed: {}", err),
|
||||
}
|
||||
.into());
|
||||
}
|
||||
}
|
||||
|
||||
let transactions_socket = UdpSocket::bind("0.0.0.0:0").unwrap();
|
||||
let tpu_addr = get_tpu_addr(&meta.cluster_info)?;
|
||||
transactions_socket
|
||||
@ -1416,7 +1462,6 @@ impl RpcSol for RpcSolImpl {
|
||||
info!("send_transaction: send_to error: {:?}", err);
|
||||
Error::internal_error()
|
||||
})?;
|
||||
let signature = transaction.signatures[0].to_string();
|
||||
trace!(
|
||||
"send_transaction: sent {} bytes, signature={}",
|
||||
wire_transaction.len(),
|
||||
@ -1432,10 +1477,7 @@ impl RpcSol for RpcSolImpl {
|
||||
config: Option<RpcSimulateTransactionConfig>,
|
||||
) -> RpcResponse<TransactionStatus> {
|
||||
let (_, transaction) = deserialize_bs58_transaction(data)?;
|
||||
let config = config.unwrap_or(RpcSimulateTransactionConfig { sig_verify: false });
|
||||
|
||||
let bank = &*meta.request_processor.read().unwrap().bank(None)?;
|
||||
assert!(bank.is_frozen());
|
||||
let config = config.unwrap_or_default();
|
||||
|
||||
let mut result = if config.sig_verify {
|
||||
transaction.verify()
|
||||
@ -1443,17 +1485,10 @@ impl RpcSol for RpcSolImpl {
|
||||
Ok(())
|
||||
};
|
||||
|
||||
let bank = &*meta.request_processor.read().unwrap().bank(None)?;
|
||||
|
||||
if result.is_ok() {
|
||||
let transactions = [transaction];
|
||||
let batch = bank.prepare_batch(&transactions, None);
|
||||
let (
|
||||
_loaded_accounts,
|
||||
executed,
|
||||
_retryable_transactions,
|
||||
_transaction_count,
|
||||
_signature_count,
|
||||
) = bank.load_and_execute_transactions(&batch, solana_sdk::clock::MAX_PROCESSING_AGE);
|
||||
result = executed[0].0.clone();
|
||||
result = run_transaction_simulation(&bank, &[transaction]);
|
||||
}
|
||||
|
||||
new_response(
|
||||
|
@ -3,6 +3,7 @@ use solana_sdk::clock::Slot;
|
||||
|
||||
const JSON_RPC_SERVER_ERROR_0: i64 = -32000;
|
||||
const JSON_RPC_SERVER_ERROR_1: i64 = -32001;
|
||||
const JSON_RPC_SERVER_ERROR_2: i64 = -32002;
|
||||
|
||||
pub enum RpcCustomError {
|
||||
NonexistentClusterRoot {
|
||||
@ -13,6 +14,9 @@ pub enum RpcCustomError {
|
||||
slot: Slot,
|
||||
first_available_block: Slot,
|
||||
},
|
||||
SendTransactionPreflightFailure {
|
||||
message: String,
|
||||
},
|
||||
}
|
||||
|
||||
impl From<RpcCustomError> for Error {
|
||||
@ -40,6 +44,11 @@ impl From<RpcCustomError> for Error {
|
||||
),
|
||||
data: None,
|
||||
},
|
||||
RpcCustomError::SendTransactionPreflightFailure { message } => Self {
|
||||
code: ErrorCode::ServerError(JSON_RPC_SERVER_ERROR_2),
|
||||
message,
|
||||
data: None,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1065,11 +1065,20 @@ curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "m
|
||||
|
||||
### sendTransaction
|
||||
|
||||
Creates new transaction
|
||||
Submits a signed transaction to the cluster for processing.
|
||||
|
||||
Before submitting, the following preflight checks are performed:
|
||||
1. The transaction signatures are verified
|
||||
2. The transaction is simulated against the latest max confirmed bank
|
||||
and on failure an error will be returned. Preflight checks may be disabled if
|
||||
desired.
|
||||
|
||||
#### Parameters:
|
||||
|
||||
* `<string>` - fully-signed Transaction, as base-58 encoded string
|
||||
* `<object>` - (optional) Configuration object containing the following field:
|
||||
* `skipPreflight: <bool>` - if true, skip the preflight transaction checks (default: false)
|
||||
|
||||
|
||||
#### Results:
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user