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,
|
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")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct RpcSimulateTransactionConfig {
|
pub struct RpcSimulateTransactionConfig {
|
||||||
pub sig_verify: bool,
|
pub sig_verify: bool,
|
||||||
|
|
|
@ -713,6 +713,19 @@ fn verify_signature(input: &str) -> Result<Signature> {
|
||||||
.map_err(|e| Error::invalid_params(format!("{:?}", e)))
|
.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)]
|
#[derive(Clone)]
|
||||||
pub struct Meta {
|
pub struct Meta {
|
||||||
pub request_processor: Arc<RwLock<JsonRpcRequestProcessor>>,
|
pub request_processor: Arc<RwLock<JsonRpcRequestProcessor>>,
|
||||||
|
@ -904,7 +917,12 @@ pub trait RpcSol {
|
||||||
) -> Result<String>;
|
) -> Result<String>;
|
||||||
|
|
||||||
#[rpc(meta, name = "sendTransaction")]
|
#[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")]
|
#[rpc(meta, name = "simulateTransaction")]
|
||||||
fn simulate_transaction(
|
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 (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 transactions_socket = UdpSocket::bind("0.0.0.0:0").unwrap();
|
||||||
let tpu_addr = get_tpu_addr(&meta.cluster_info)?;
|
let tpu_addr = get_tpu_addr(&meta.cluster_info)?;
|
||||||
transactions_socket
|
transactions_socket
|
||||||
|
@ -1416,7 +1462,6 @@ impl RpcSol for RpcSolImpl {
|
||||||
info!("send_transaction: send_to error: {:?}", err);
|
info!("send_transaction: send_to error: {:?}", err);
|
||||||
Error::internal_error()
|
Error::internal_error()
|
||||||
})?;
|
})?;
|
||||||
let signature = transaction.signatures[0].to_string();
|
|
||||||
trace!(
|
trace!(
|
||||||
"send_transaction: sent {} bytes, signature={}",
|
"send_transaction: sent {} bytes, signature={}",
|
||||||
wire_transaction.len(),
|
wire_transaction.len(),
|
||||||
|
@ -1432,10 +1477,7 @@ impl RpcSol for RpcSolImpl {
|
||||||
config: Option<RpcSimulateTransactionConfig>,
|
config: Option<RpcSimulateTransactionConfig>,
|
||||||
) -> RpcResponse<TransactionStatus> {
|
) -> RpcResponse<TransactionStatus> {
|
||||||
let (_, transaction) = deserialize_bs58_transaction(data)?;
|
let (_, transaction) = deserialize_bs58_transaction(data)?;
|
||||||
let config = config.unwrap_or(RpcSimulateTransactionConfig { sig_verify: false });
|
let config = config.unwrap_or_default();
|
||||||
|
|
||||||
let bank = &*meta.request_processor.read().unwrap().bank(None)?;
|
|
||||||
assert!(bank.is_frozen());
|
|
||||||
|
|
||||||
let mut result = if config.sig_verify {
|
let mut result = if config.sig_verify {
|
||||||
transaction.verify()
|
transaction.verify()
|
||||||
|
@ -1443,17 +1485,10 @@ impl RpcSol for RpcSolImpl {
|
||||||
Ok(())
|
Ok(())
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let bank = &*meta.request_processor.read().unwrap().bank(None)?;
|
||||||
|
|
||||||
if result.is_ok() {
|
if result.is_ok() {
|
||||||
let transactions = [transaction];
|
result = run_transaction_simulation(&bank, &[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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
new_response(
|
new_response(
|
||||||
|
|
|
@ -3,6 +3,7 @@ use solana_sdk::clock::Slot;
|
||||||
|
|
||||||
const JSON_RPC_SERVER_ERROR_0: i64 = -32000;
|
const JSON_RPC_SERVER_ERROR_0: i64 = -32000;
|
||||||
const JSON_RPC_SERVER_ERROR_1: i64 = -32001;
|
const JSON_RPC_SERVER_ERROR_1: i64 = -32001;
|
||||||
|
const JSON_RPC_SERVER_ERROR_2: i64 = -32002;
|
||||||
|
|
||||||
pub enum RpcCustomError {
|
pub enum RpcCustomError {
|
||||||
NonexistentClusterRoot {
|
NonexistentClusterRoot {
|
||||||
|
@ -13,6 +14,9 @@ pub enum RpcCustomError {
|
||||||
slot: Slot,
|
slot: Slot,
|
||||||
first_available_block: Slot,
|
first_available_block: Slot,
|
||||||
},
|
},
|
||||||
|
SendTransactionPreflightFailure {
|
||||||
|
message: String,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<RpcCustomError> for Error {
|
impl From<RpcCustomError> for Error {
|
||||||
|
@ -40,6 +44,11 @@ impl From<RpcCustomError> for Error {
|
||||||
),
|
),
|
||||||
data: None,
|
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
|
### 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:
|
#### Parameters:
|
||||||
|
|
||||||
* `<string>` - fully-signed Transaction, as base-58 encoded string
|
* `<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:
|
#### Results:
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue