Add a flag to simulateTransaction to use most recent blockhash (backport #17485) (#17497)

* Add a flag to simulateTransaction to use most recent blockhash

(cherry picked from commit 96cef5260c)

* rename flag

(cherry picked from commit e14f3eb529)

* sigVerify conflicts with replace, add tests

(cherry picked from commit 660d37aadf)

Co-authored-by: Justin Starry <justin@solana.com>
This commit is contained in:
mergify[bot]
2021-05-26 01:49:52 +00:00
committed by GitHub
parent 02c4170357
commit b06bfeec8d
3 changed files with 87 additions and 2 deletions

View File

@ -28,6 +28,8 @@ pub struct RpcSendTransactionConfig {
pub struct RpcSimulateTransactionConfig { pub struct RpcSimulateTransactionConfig {
#[serde(default)] #[serde(default)]
pub sig_verify: bool, pub sig_verify: bool,
#[serde(default)]
pub replace_recent_blockhash: bool,
#[serde(flatten)] #[serde(flatten)]
pub commitment: Option<CommitmentConfig>, pub commitment: Option<CommitmentConfig>,
pub encoding: Option<UiTransactionEncoding>, pub encoding: Option<UiTransactionEncoding>,

View File

@ -3141,15 +3141,24 @@ pub mod rpc_full {
debug!("simulate_transaction rpc request received"); debug!("simulate_transaction rpc request received");
let config = config.unwrap_or_default(); let config = config.unwrap_or_default();
let encoding = config.encoding.unwrap_or(UiTransactionEncoding::Base58); let encoding = config.encoding.unwrap_or(UiTransactionEncoding::Base58);
let (_, transaction) = deserialize_transaction(data, encoding)?; let (_, mut transaction) = deserialize_transaction(data, encoding)?;
if config.sig_verify { if config.sig_verify {
if config.replace_recent_blockhash {
return Err(Error::invalid_params(
"sigVerify may not be used with replaceRecentBlockhash",
));
}
if let Err(e) = verify_transaction(&transaction) { if let Err(e) = verify_transaction(&transaction) {
return Err(e); return Err(e);
} }
} }
let bank = &*meta.bank(config.commitment); let bank = &*meta.bank(config.commitment);
if config.replace_recent_blockhash {
transaction.message.recent_blockhash = bank.last_blockhash();
}
let (result, logs) = bank.simulate_transaction(transaction); let (result, logs) = bank.simulate_transaction(transaction);
Ok(new_response( Ok(new_response(
@ -4854,6 +4863,8 @@ pub mod tests {
let tx_serialized_encoded = bs58::encode(serialize(&tx).unwrap()).into_string(); let tx_serialized_encoded = bs58::encode(serialize(&tx).unwrap()).into_string();
tx.signatures[0] = Signature::default(); tx.signatures[0] = Signature::default();
let tx_badsig_serialized_encoded = bs58::encode(serialize(&tx).unwrap()).into_string(); let tx_badsig_serialized_encoded = bs58::encode(serialize(&tx).unwrap()).into_string();
tx.message.recent_blockhash = Hash::default();
let tx_invalid_recent_blockhash = bs58::encode(serialize(&tx).unwrap()).into_string();
bank.freeze(); // Ensure the root bank is frozen, `start_rpc_handler_with_tx()` doesn't do this bank.freeze(); // Ensure the root bank is frozen, `start_rpc_handler_with_tx()` doesn't do this
@ -4929,6 +4940,75 @@ pub mod tests {
r#"{{"jsonrpc":"2.0","id":1,"method":"simulateTransaction","params":["{}"]}}"#, r#"{{"jsonrpc":"2.0","id":1,"method":"simulateTransaction","params":["{}"]}}"#,
tx_serialized_encoded, tx_serialized_encoded,
); );
let res = io.handle_request_sync(&req, meta.clone());
let expected = json!({
"jsonrpc": "2.0",
"result": {
"context":{"slot":0},
"value":{"err":null, "logs":[
"Program 11111111111111111111111111111111 invoke [1]",
"Program 11111111111111111111111111111111 success"
]}
},
"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);
// Enabled both sigVerify=true and replaceRecentBlockhash=true
let req = format!(
r#"{{"jsonrpc":"2.0","id":1,"method":"simulateTransaction","params":["{}", {}]}}"#,
tx_serialized_encoded,
json!({
"sigVerify": true,
"replaceRecentBlockhash": true,
})
.to_string()
);
let res = io.handle_request_sync(&req, meta.clone());
let expected = json!({
"jsonrpc":"2.0",
"error": {
"code": ErrorCode::InvalidParams,
"message": "sigVerify may not be used with replaceRecentBlockhash"
},
"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 recent blockhash with replaceRecentBlockhash=false
let req = format!(
r#"{{"jsonrpc":"2.0","id":1,"method":"simulateTransaction","params":["{}", {{"replaceRecentBlockhash": false}}]}}"#,
tx_invalid_recent_blockhash,
);
let res = io.handle_request_sync(&req, meta.clone());
let expected = json!({
"jsonrpc":"2.0",
"result": {
"context":{"slot":0},
"value":{"err": "BlockhashNotFound", "logs":[]}
},
"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 recent blockhash with replaceRecentBlockhash=true
let req = format!(
r#"{{"jsonrpc":"2.0","id":1,"method":"simulateTransaction","params":["{}", {{"replaceRecentBlockhash": true}}]}}"#,
tx_invalid_recent_blockhash,
);
let res = io.handle_request_sync(&req, meta); let res = io.handle_request_sync(&req, meta);
let expected = json!({ let expected = json!({
"jsonrpc": "2.0", "jsonrpc": "2.0",
@ -4941,6 +5021,7 @@ pub mod tests {
}, },
"id": 1, "id": 1,
}); });
let expected: Response = let expected: Response =
serde_json::from_value(expected).expect("expected response deserialization"); serde_json::from_value(expected).expect("expected response deserialization");
let result: Response = serde_json::from_str(&res.expect("actual response")) let result: Response = serde_json::from_str(&res.expect("actual response"))

View File

@ -3231,9 +3231,11 @@ Simulate sending a transaction
- `<string>` - Transaction, as an encoded string. The transaction must have a valid blockhash, but is not required to be signed. - `<string>` - Transaction, as an encoded string. The transaction must have a valid blockhash, but is not required to be signed.
- `<object>` - (optional) Configuration object containing the following field: - `<object>` - (optional) Configuration object containing the following field:
- `sigVerify: <bool>` - if true the transaction signatures will be verified (default: false) - `sigVerify: <bool>` - if true the transaction signatures will be verified (default: false, conflicts with `replaceRecentBlockhash`)
- `commitment: <string>` - (optional) [Commitment](jsonrpc-api.md#configuring-state-commitment) level to simulate the transaction at (default: `"finalized"`). - `commitment: <string>` - (optional) [Commitment](jsonrpc-api.md#configuring-state-commitment) level to simulate the transaction at (default: `"finalized"`).
- `encoding: <string>` - (optional) Encoding used for the transaction data. Either `"base58"` (*slow*, **DEPRECATED**), or `"base64"`. (default: `"base58"`). - `encoding: <string>` - (optional) Encoding used for the transaction data. Either `"base58"` (*slow*, **DEPRECATED**), or `"base64"`. (default: `"base58"`).
- `replaceRecentBlockhash: <bool>` - (optional) if true the transaction recent blockhash will be replaced with the most recent blockhash.
(default: false, conflicts with `sigVerify`)
#### Results: #### Results: