parent
5e43304eca
commit
427c78d891
@ -119,6 +119,19 @@ impl RpcClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn simulate_transaction(
|
||||||
|
&self,
|
||||||
|
transaction: &Transaction,
|
||||||
|
sig_verify: bool,
|
||||||
|
) -> RpcResult<TransactionStatus> {
|
||||||
|
let serialized_encoded = bs58::encode(serialize(transaction).unwrap()).into_string();
|
||||||
|
self.send(
|
||||||
|
RpcRequest::SimulateTransaction,
|
||||||
|
json!([serialized_encoded, { "sigVerify": sig_verify }]),
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_signature_status(
|
pub fn get_signature_status(
|
||||||
&self,
|
&self,
|
||||||
signature: &Signature,
|
signature: &Signature,
|
||||||
|
@ -6,6 +6,12 @@ pub struct RpcSignatureStatusConfig {
|
|||||||
pub search_transaction_history: bool,
|
pub search_transaction_history: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct RpcSimulateTransactionConfig {
|
||||||
|
pub sig_verify: bool,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub enum RpcLargestAccountsFilter {
|
pub enum RpcLargestAccountsFilter {
|
||||||
|
@ -40,6 +40,7 @@ pub enum RpcRequest {
|
|||||||
RegisterNode,
|
RegisterNode,
|
||||||
RequestAirdrop,
|
RequestAirdrop,
|
||||||
SendTransaction,
|
SendTransaction,
|
||||||
|
SimulateTransaction,
|
||||||
SignVote,
|
SignVote,
|
||||||
GetMinimumBalanceForRentExemption,
|
GetMinimumBalanceForRentExemption,
|
||||||
MinimumLedgerSlot,
|
MinimumLedgerSlot,
|
||||||
@ -84,6 +85,7 @@ impl fmt::Display for RpcRequest {
|
|||||||
RpcRequest::RegisterNode => "registerNode",
|
RpcRequest::RegisterNode => "registerNode",
|
||||||
RpcRequest::RequestAirdrop => "requestAirdrop",
|
RpcRequest::RequestAirdrop => "requestAirdrop",
|
||||||
RpcRequest::SendTransaction => "sendTransaction",
|
RpcRequest::SendTransaction => "sendTransaction",
|
||||||
|
RpcRequest::SimulateTransaction => "simulateTransaction",
|
||||||
RpcRequest::SignVote => "signVote",
|
RpcRequest::SignVote => "signVote",
|
||||||
RpcRequest::GetMinimumBalanceForRentExemption => "getMinimumBalanceForRentExemption",
|
RpcRequest::GetMinimumBalanceForRentExemption => "getMinimumBalanceForRentExemption",
|
||||||
RpcRequest::MinimumLedgerSlot => "minimumLedgerSlot",
|
RpcRequest::MinimumLedgerSlot => "minimumLedgerSlot",
|
||||||
|
221
core/src/rpc.rs
221
core/src/rpc.rs
@ -847,6 +847,14 @@ pub trait RpcSol {
|
|||||||
#[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) -> Result<String>;
|
||||||
|
|
||||||
|
#[rpc(meta, name = "simulateTransaction")]
|
||||||
|
fn simulate_transaction(
|
||||||
|
&self,
|
||||||
|
meta: Self::Metadata,
|
||||||
|
data: String,
|
||||||
|
config: Option<RpcSimulateTransactionConfig>,
|
||||||
|
) -> RpcResponse<TransactionStatus>;
|
||||||
|
|
||||||
#[rpc(meta, name = "getSlotLeader")]
|
#[rpc(meta, name = "getSlotLeader")]
|
||||||
fn get_slot_leader(
|
fn get_slot_leader(
|
||||||
&self,
|
&self,
|
||||||
@ -1327,41 +1335,67 @@ impl RpcSol for RpcSolImpl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn send_transaction(&self, meta: Self::Metadata, data: String) -> Result<String> {
|
fn send_transaction(&self, meta: Self::Metadata, data: String) -> Result<String> {
|
||||||
let data = bs58::decode(data).into_vec().unwrap();
|
let (wire_transaction, transaction) = deserialize_bs58_transaction(data)?;
|
||||||
if data.len() >= PACKET_DATA_SIZE {
|
|
||||||
info!(
|
|
||||||
"send_transaction: transaction too large: {} bytes (max: {} bytes)",
|
|
||||||
data.len(),
|
|
||||||
PACKET_DATA_SIZE
|
|
||||||
);
|
|
||||||
return Err(Error::invalid_request());
|
|
||||||
}
|
|
||||||
let tx: Transaction = bincode::config()
|
|
||||||
.limit(PACKET_DATA_SIZE as u64)
|
|
||||||
.deserialize(&data)
|
|
||||||
.map_err(|err| {
|
|
||||||
info!("send_transaction: deserialize error: {:?}", err);
|
|
||||||
Error::invalid_request()
|
|
||||||
})?;
|
|
||||||
|
|
||||||
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)?;
|
||||||
trace!("send_transaction: leader is {:?}", &tpu_addr);
|
trace!("send_transaction: leader is {:?}", &tpu_addr);
|
||||||
transactions_socket
|
transactions_socket
|
||||||
.send_to(&data, tpu_addr)
|
.send_to(&wire_transaction, tpu_addr)
|
||||||
.map_err(|err| {
|
.map_err(|err| {
|
||||||
info!("send_transaction: send_to error: {:?}", err);
|
info!("send_transaction: send_to error: {:?}", err);
|
||||||
Error::internal_error()
|
Error::internal_error()
|
||||||
})?;
|
})?;
|
||||||
let signature = tx.signatures[0].to_string();
|
let signature = transaction.signatures[0].to_string();
|
||||||
trace!(
|
trace!(
|
||||||
"send_transaction: sent {} bytes, signature={}",
|
"send_transaction: sent {} bytes, signature={}",
|
||||||
data.len(),
|
wire_transaction.len(),
|
||||||
signature
|
signature
|
||||||
);
|
);
|
||||||
Ok(signature)
|
Ok(signature)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn simulate_transaction(
|
||||||
|
&self,
|
||||||
|
meta: Self::Metadata,
|
||||||
|
data: String,
|
||||||
|
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 mut result = if config.sig_verify {
|
||||||
|
transaction.verify()
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
};
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
new_response(
|
||||||
|
&bank,
|
||||||
|
TransactionStatus {
|
||||||
|
slot: bank.slot(),
|
||||||
|
confirmations: Some(0),
|
||||||
|
status: result.clone(),
|
||||||
|
err: result.err(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
fn get_slot_leader(
|
fn get_slot_leader(
|
||||||
&self,
|
&self,
|
||||||
meta: Self::Metadata,
|
meta: Self::Metadata,
|
||||||
@ -1498,6 +1532,26 @@ impl RpcSol for RpcSolImpl {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn deserialize_bs58_transaction(bs58_transaction: String) -> Result<(Vec<u8>, Transaction)> {
|
||||||
|
let wire_transaction = bs58::decode(bs58_transaction).into_vec().unwrap();
|
||||||
|
if wire_transaction.len() >= PACKET_DATA_SIZE {
|
||||||
|
info!(
|
||||||
|
"transaction too large: {} bytes (max: {} bytes)",
|
||||||
|
wire_transaction.len(),
|
||||||
|
PACKET_DATA_SIZE
|
||||||
|
);
|
||||||
|
return Err(Error::invalid_request());
|
||||||
|
}
|
||||||
|
bincode::config()
|
||||||
|
.limit(PACKET_DATA_SIZE as u64)
|
||||||
|
.deserialize(&wire_transaction)
|
||||||
|
.map_err(|err| {
|
||||||
|
info!("transaction deserialize error: {:?}", err);
|
||||||
|
Error::invalid_request()
|
||||||
|
})
|
||||||
|
.map(|transaction| (wire_transaction, transaction))
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub mod tests {
|
pub mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
@ -2146,6 +2200,133 @@ pub mod tests {
|
|||||||
assert_eq!(expected, result);
|
assert_eq!(expected, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_rpc_simulate_transaction() {
|
||||||
|
let bob_pubkey = Pubkey::new_rand();
|
||||||
|
let RpcHandler {
|
||||||
|
io,
|
||||||
|
meta,
|
||||||
|
blockhash,
|
||||||
|
alice,
|
||||||
|
bank,
|
||||||
|
..
|
||||||
|
} = start_rpc_handler_with_tx(&bob_pubkey);
|
||||||
|
|
||||||
|
let mut tx = system_transaction::transfer(&alice, &bob_pubkey, 1234, blockhash);
|
||||||
|
let tx_serialized_encoded = bs58::encode(serialize(&tx).unwrap()).into_string();
|
||||||
|
tx.signatures[0] = Signature::default();
|
||||||
|
let tx_badsig_serialized_encoded = bs58::encode(serialize(&tx).unwrap()).into_string();
|
||||||
|
|
||||||
|
bank.freeze(); // Ensure the root bank is frozen, `start_rpc_handler_with_tx()` doesn't do this
|
||||||
|
|
||||||
|
// Good signature with sigVerify=true
|
||||||
|
let req = format!(
|
||||||
|
r#"{{"jsonrpc":"2.0","id":1,"method":"simulateTransaction","params":["{}", {{"sigVerify": true}}]}}"#,
|
||||||
|
tx_serialized_encoded,
|
||||||
|
);
|
||||||
|
let res = io.handle_request_sync(&req, meta.clone());
|
||||||
|
let expected = json!({
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"result": {
|
||||||
|
"context":{"slot":0},
|
||||||
|
"value":{"confirmations":0,"slot": 0,"status":{"Ok":null},"err":null}
|
||||||
|
},
|
||||||
|
"id": 1,
|
||||||
|
});
|
||||||
|
let expected: Response =
|
||||||
|
serde_json::from_value(expected).expect("expected response deserialization");
|
||||||
|
let result: Response = serde_json::from_str(&res.expect("actual response"))
|
||||||
|
.expect("actual response deserialization");
|
||||||
|
assert_eq!(expected, result);
|
||||||
|
|
||||||
|
// Bad signature with sigVerify=true
|
||||||
|
let req = format!(
|
||||||
|
r#"{{"jsonrpc":"2.0","id":1,"method":"simulateTransaction","params":["{}", {{"sigVerify": true}}]}}"#,
|
||||||
|
tx_badsig_serialized_encoded,
|
||||||
|
);
|
||||||
|
let res = io.handle_request_sync(&req, meta.clone());
|
||||||
|
let expected = json!({
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"result": {
|
||||||
|
"context":{"slot":0},
|
||||||
|
"value":{"confirmations":0,"slot":0,"status":{"Err":"SignatureFailure"},"err":"SignatureFailure"}
|
||||||
|
},
|
||||||
|
"id": 1,
|
||||||
|
});
|
||||||
|
let expected: Response =
|
||||||
|
serde_json::from_value(expected).expect("expected response deserialization");
|
||||||
|
let result: Response = serde_json::from_str(&res.expect("actual response"))
|
||||||
|
.expect("actual response deserialization");
|
||||||
|
assert_eq!(expected, result);
|
||||||
|
|
||||||
|
// Bad signature with sigVerify=false
|
||||||
|
let req = format!(
|
||||||
|
r#"{{"jsonrpc":"2.0","id":1,"method":"simulateTransaction","params":["{}", {{"sigVerify": false}}]}}"#,
|
||||||
|
tx_serialized_encoded,
|
||||||
|
);
|
||||||
|
let res = io.handle_request_sync(&req, meta.clone());
|
||||||
|
let expected = json!({
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"result": {
|
||||||
|
"context":{"slot":0},
|
||||||
|
"value":{"confirmations":0,"slot": 0,"status":{"Ok":null},"err":null}
|
||||||
|
},
|
||||||
|
"id": 1,
|
||||||
|
});
|
||||||
|
let expected: Response =
|
||||||
|
serde_json::from_value(expected).expect("expected response deserialization");
|
||||||
|
let result: Response = serde_json::from_str(&res.expect("actual response"))
|
||||||
|
.expect("actual response deserialization");
|
||||||
|
assert_eq!(expected, result);
|
||||||
|
|
||||||
|
// Bad signature with default sigVerify setting (false)
|
||||||
|
let req = format!(
|
||||||
|
r#"{{"jsonrpc":"2.0","id":1,"method":"simulateTransaction","params":["{}"]}}"#,
|
||||||
|
tx_serialized_encoded,
|
||||||
|
);
|
||||||
|
let res = io.handle_request_sync(&req, meta.clone());
|
||||||
|
let expected = json!({
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"result": {
|
||||||
|
"context":{"slot":0},
|
||||||
|
"value":{"confirmations":0,"slot": 0,"status":{"Ok":null},"err":null}
|
||||||
|
},
|
||||||
|
"id": 1,
|
||||||
|
});
|
||||||
|
let expected: Response =
|
||||||
|
serde_json::from_value(expected).expect("expected response deserialization");
|
||||||
|
let result: Response = serde_json::from_str(&res.expect("actual response"))
|
||||||
|
.expect("actual response deserialization");
|
||||||
|
assert_eq!(expected, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[should_panic]
|
||||||
|
fn test_rpc_simulate_transaction_panic_on_unfrozen_bank() {
|
||||||
|
let bob_pubkey = Pubkey::new_rand();
|
||||||
|
let RpcHandler {
|
||||||
|
io,
|
||||||
|
meta,
|
||||||
|
blockhash,
|
||||||
|
alice,
|
||||||
|
bank,
|
||||||
|
..
|
||||||
|
} = start_rpc_handler_with_tx(&bob_pubkey);
|
||||||
|
|
||||||
|
let tx = system_transaction::transfer(&alice, &bob_pubkey, 1234, blockhash);
|
||||||
|
let tx_serialized_encoded = bs58::encode(serialize(&tx).unwrap()).into_string();
|
||||||
|
|
||||||
|
assert!(!bank.is_frozen());
|
||||||
|
|
||||||
|
let req = format!(
|
||||||
|
r#"{{"jsonrpc":"2.0","id":1,"method":"simulateTransaction","params":["{}", {{"sigVerify": true}}]}}"#,
|
||||||
|
tx_serialized_encoded,
|
||||||
|
);
|
||||||
|
|
||||||
|
// should panic because `bank` is not frozen
|
||||||
|
let _ = io.handle_request_sync(&req, meta.clone());
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_rpc_confirm_tx() {
|
fn test_rpc_confirm_tx() {
|
||||||
let bob_pubkey = Pubkey::new_rand();
|
let bob_pubkey = Pubkey::new_rand();
|
||||||
|
@ -50,6 +50,7 @@ To interact with a Solana node inside a JavaScript application, use the [solana-
|
|||||||
* [minimumLedgerSlot](jsonrpc-api.md#minimumledgerslot)
|
* [minimumLedgerSlot](jsonrpc-api.md#minimumledgerslot)
|
||||||
* [requestAirdrop](jsonrpc-api.md#requestairdrop)
|
* [requestAirdrop](jsonrpc-api.md#requestairdrop)
|
||||||
* [sendTransaction](jsonrpc-api.md#sendtransaction)
|
* [sendTransaction](jsonrpc-api.md#sendtransaction)
|
||||||
|
* [simulateTransaction](jsonrpc-api.md#simulatetransaction)
|
||||||
* [setLogFilter](jsonrpc-api.md#setlogfilter)
|
* [setLogFilter](jsonrpc-api.md#setlogfilter)
|
||||||
* [validatorExit](jsonrpc-api.md#validatorexit)
|
* [validatorExit](jsonrpc-api.md#validatorexit)
|
||||||
* [Subscription Websocket](jsonrpc-api.md#subscription-websocket)
|
* [Subscription Websocket](jsonrpc-api.md#subscription-websocket)
|
||||||
@ -1112,10 +1113,34 @@ Creates new transaction
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
// Request
|
// Request
|
||||||
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"sendTransaction", "params":["3gKEMTuxvm3DKEJc4UyiyoNz1sxwdVRW2pyDDXqaCvUjGApnsazGh2y4W92zuaSSdJhBbWLYAkZokBt4N5oW27R7zCVaLLpLxvATL2GgheEh9DmmDR1P9r1ZqirVXM2fF3z5cafmc4EtwWc1UErFdCWj1qYvy4bDGMLXRYLURxaKytEEqrxz6JXj8rUHhDpjTZeFxmC6iAW3hZr6cmaAzewQCQfiEv2HfydriwHDtN95u3Y1EF6SuXxcRqox2aTjGye2Ln9zFj4XbnAtjCmkZhR"]}' http://localhost:8899
|
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"sendTransaction", "params":["4hXTCkRzt9WyecNzV1XPgCDfGAZzQKNxLXgynz5QDuWWPSAZBZSHptvWRL3BjCvzUXRdKvHL2b7yGrRQcWyaqsaBCncVG7BFggS8w9snUts67BSh3EqKpXLUm5UMHfD7ZBe9GhARjbNQMLJ1QD3Spr6oMTBU6EhdB4RD8CP2xUxr2u3d6fos36PD98XS6oX8TQjLpsMwncs5DAMiD4nNnR8NBfyghGCWvCVifVwvA8B8TJxE1aiyiv2L429BCWfyzAme5sZW8rDb14NeCQHhZbtNqfXhcp2tAnaAT"]}' http://localhost:8899
|
||||||
|
|
||||||
// Result
|
// Result
|
||||||
{"jsonrpc":"2.0","result":"2EBVM6cB8vAAD93Ktr6Vd8p67XPbQzCJX47MpReuiCXJAtcjaxpvWpcg9Ege1Nr5Tk3a2GFrByT7WPBjdsTycY9b","id":1}
|
{"jsonrpc":"2.0","result":"2id3YC2jK9G5Wo2phDx4gJVAew8DcY5NAojnVuao8rkxwPYPe8cSwE5GzhEgJA2y8fVjDEo6iR6ykBvDxrTQrtpb","id":1}
|
||||||
|
```
|
||||||
|
|
||||||
|
### simulateTransaction
|
||||||
|
|
||||||
|
Simulate sending a transaction
|
||||||
|
|
||||||
|
#### Parameters:
|
||||||
|
|
||||||
|
* `<string>` - Transaction, as base-58 encoded string. The transaction must have a valid blockhash, but is not required to be signed.
|
||||||
|
* `<object>` - (optional) Configuration object containing the following field:
|
||||||
|
* `sigVerify: <bool>` - if true the transaction signatures will be verified (default: false)
|
||||||
|
|
||||||
|
#### Results:
|
||||||
|
|
||||||
|
An RpcResponse containing a TransactionStatus object
|
||||||
|
|
||||||
|
#### Example:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
// Request
|
||||||
|
curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1, "method":"simulateTransaction", "params":["4hXTCkRzt9WyecNzV1XPgCDfGAZzQKNxLXgynz5QDuWWPSAZBZSHptvWRL3BjCvzUXRdKvHL2b7yGrRQcWyaqsaBCncVG7BFggS8w9snUts67BSh3EqKpXLUm5UMHfD7ZBe9GhARjbNQMLJ1QD3Spr6oMTBU6EhdB4RD8CP2xUxr2u3d6fos36PD98XS6oX8TQjLpsMwncs5DAMiD4nNnR8NBfyghGCWvCVifVwvA8B8TJxE1aiyiv2L429BCWfyzAme5sZW8rDb14NeCQHhZbtNqfXhcp2tAnaAT"]}' http://localhost:8899
|
||||||
|
|
||||||
|
// Result
|
||||||
|
{"jsonrpc":"2.0","result":{"context":{"slot":218},"value":{"confirmations":0,"err":null,"slot":218,"status":{"Ok":null}}},"id":1}
|
||||||
```
|
```
|
||||||
|
|
||||||
### setLogFilter
|
### setLogFilter
|
||||||
|
Loading…
x
Reference in New Issue
Block a user