Skip to content

Commit

Permalink
Merge pull request #346 from gBaGu/chore-replay-rpc-gas-calculation-test
Browse files Browse the repository at this point in the history
Chore(rpc-test): add test to check replayTransaction gas usage
  • Loading branch information
gBaGu authored Aug 26, 2022
2 parents 33f4110 + a2881ab commit 4e7f12a
Showing 1 changed file with 210 additions and 49 deletions.
259 changes: 210 additions & 49 deletions rpc-test/tests/rpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,12 @@ use solana_test_validator::TestValidatorGenesis;
use solana_rpc::rpc::JsonRpcConfig;
use solana_client::pubsub_client::PubsubClient;

use primitive_types::H256;
use primitive_types::{H256, U256};

use evm_rpc::Hex;
use evm_rpc::{Hex, RPCTransaction};
use evm_rpc::trace::TraceMeta;
use evm_state::TransactionInReceipt;
use solana_client::rpc_config::RpcSendTransactionConfig;
use solana_evm_loader_program::instructions::FeePayerType;

macro_rules! json_req {
Expand Down Expand Up @@ -81,18 +84,20 @@ fn get_blockhash(rpc_url: &str) -> Hash {
.unwrap()
}

fn wait_confirmation(rpc_url: &str, signatures: &[&Value]) -> bool {
fn wait_finalization(rpc_url: &str, signatures: &[&Value]) -> bool {
let request = json_req!("getSignatureStatuses", [signatures]);

for _ in 0..solana_sdk::clock::DEFAULT_TICKS_PER_SLOT {
let json = dbg!(post_rpc(request.clone(), &rpc_url));
let values = json["result"]["value"].as_array().unwrap();
let json = post_rpc(request.clone(), &rpc_url);
let values = dbg!(&json["result"])["value"].as_array().unwrap();
if values.iter().all(|v| !v.is_null()) {
for val in values {
assert_eq!(val["err"], Value::Null);
if values.iter().all(|v| {
assert_eq!(v["err"], Value::Null);
v["confirmationStatus"].as_str().unwrap() == "finalized"
}) {
info!("All signatures confirmed: {:?}", values);
return true;
}
info!("All signatures confirmed: {:?}", values);
return true;
}

sleep(Duration::from_secs(1));
Expand Down Expand Up @@ -194,9 +199,9 @@ fn test_rpc_replay_transaction() {
.enable_evm_state_archive()
.rpc_config(JsonRpcConfig {
enable_rpc_transaction_history: true,
..Default::default()
..JsonRpcConfig::default_for_test()
})
.start_with_mint_address(alice.pubkey(), SocketAddrSpace::new(/*allow_private_addr=*/ true))
.start_with_mint_address(alice.pubkey(), SocketAddrSpace::Unspecified)
.expect("validator start failed");
let rpc_url = test_validator.rpc_url();

Expand All @@ -211,7 +216,7 @@ fn test_rpc_replay_transaction() {

let req = json_req!("sendTransaction", json!([serialized_encoded_tx]));
let json: Value = post_rpc(req, &rpc_url);
wait_confirmation(&rpc_url, &[&json["result"]]);
wait_finalization(&rpc_url, &[&json["result"]]);

let evm_txs: Vec<_> = (0u64..3)
.map(|nonce| {
Expand All @@ -228,13 +233,6 @@ fn test_rpc_replay_transaction() {
.collect();
let tx_hashes: Vec<_> = evm_txs.iter().map(|tx| tx.tx_id_hash()).collect();

while get_blockhash(&rpc_url) == blockhash {
sleep(Duration::from_secs(1));
}
let blockhash = get_blockhash(&rpc_url);
while get_blockhash(&rpc_url) == blockhash {
sleep(Duration::from_secs(1));
}
let blockhash = get_blockhash(&rpc_url);
let ixs: Vec<_> = evm_txs
.into_iter()
Expand All @@ -244,7 +242,7 @@ fn test_rpc_replay_transaction() {
let serialized_encoded_tx = bs58::encode(serialize(&tx).unwrap()).into_string();
let req = json_req!("sendTransaction", json!([serialized_encoded_tx]));
let json = dbg!(post_rpc(req, &rpc_url));
wait_confirmation(&rpc_url, &[&json["result"]]);
wait_finalization(&rpc_url, &[&json["result"]]);

for tx_hash in tx_hashes {
let request = json_req!("trace_replayTransaction", json!([tx_hash, ["trace"]]));
Expand All @@ -270,9 +268,9 @@ fn test_rpc_block_transaction() {
.fee_rate_governor(FeeRateGovernor::new(0, 0))
.rpc_config(JsonRpcConfig {
enable_rpc_transaction_history: true,
..Default::default()
..JsonRpcConfig::default_for_test()
})
.start_with_mint_address(alice.pubkey(), SocketAddrSpace::new(/*allow_private_addr=*/ true))
.start_with_mint_address(alice.pubkey(), SocketAddrSpace::Unspecified)
.expect("validator start failed");
let rpc_url = test_validator.rpc_url();

Expand All @@ -287,7 +285,7 @@ fn test_rpc_block_transaction() {

let req = json_req!("sendTransaction", json!([serialized_encoded_tx]));
let json: Value = post_rpc(req, &rpc_url);
wait_confirmation(&rpc_url, &[&json["result"]]);
wait_finalization(&rpc_url, &[&json["result"]]);

let evm_txs: Vec<_> = (0u64..3)
.map(|nonce| {
Expand All @@ -304,13 +302,6 @@ fn test_rpc_block_transaction() {
.collect();
let _tx_hashes: Vec<_> = evm_txs.iter().map(|tx| tx.tx_id_hash()).collect();

while get_blockhash(&rpc_url) == blockhash {
sleep(Duration::from_secs(1));
}
let blockhash = get_blockhash(&rpc_url);
while get_blockhash(&rpc_url) == blockhash {
sleep(Duration::from_secs(1));
}
let blockhash = get_blockhash(&rpc_url);
let ixs: Vec<_> = evm_txs
.into_iter()
Expand All @@ -320,7 +311,7 @@ fn test_rpc_block_transaction() {
let serialized_encoded_tx = bs58::encode(serialize(&tx).unwrap()).into_string();
let req = json_req!("sendTransaction", json!([serialized_encoded_tx]));
let json = dbg!(post_rpc(req, &rpc_url));
wait_confirmation(&rpc_url, &[&json["result"]]);
wait_finalization(&rpc_url, &[&json["result"]]);

let request = json_req!("eth_getBlockTransactionCountByNumber", json!(["0x02"]));
let json = post_rpc(request.clone(), &rpc_url);
Expand Down Expand Up @@ -349,8 +340,8 @@ fn test_rpc_block_transaction() {
#[test]
fn test_rpc_replay_transaction_timestamp() {
use solana_evm_loader_program::{send_raw_tx, transfer_native_to_evm_ixs};
let filter = "warn,evm=debug,evm_state::context=info";
solana_logger::setup_with_default(filter);
// let filter = "warn,evm=debug,evm_state::context=info";
solana_logger::setup_with_default("warn");

let evm_secret_key = evm_state::SecretKey::from_slice(&[1; 32]).unwrap();
let evm_address = evm_state::addr_from_public_key(&evm_state::PublicKey::from_secret_key(
Expand All @@ -369,9 +360,9 @@ fn test_rpc_replay_transaction_timestamp() {
.enable_evm_state_archive()
.rpc_config(JsonRpcConfig {
enable_rpc_transaction_history: true,
..Default::default()
..JsonRpcConfig::default_for_test()
})
.start_with_mint_address(alice.pubkey(), SocketAddrSpace::new(/*allow_private_addr=*/ true))
.start_with_mint_address(alice.pubkey(), SocketAddrSpace::Unspecified)
.expect("validator start failed");
let rpc_url = test_validator.rpc_url();

Expand All @@ -386,7 +377,7 @@ fn test_rpc_replay_transaction_timestamp() {

let req = json_req!("sendTransaction", json!([serialized_encoded_tx]));
let json: Value = post_rpc(req, &rpc_url);
wait_confirmation(&rpc_url, &[&json["result"]]);
wait_finalization(&rpc_url, &[&json["result"]]);

// Contract with empty method that will revert after 60 seconds since creation
const TEST_CONTRACT: &str = "608060405234801561001057600080fd5b50426000819055506101ce806100276000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c8063e0c6190d14610030575b600080fd5b61003861003a565b005b603c60005461004991906100e0565b421061008a576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610081906100af565b60405180910390fd5b565b60006100996007836100cf565b91506100a48261016f565b602082019050919050565b600060208201905081810360008301526100c88161008c565b9050919050565b600082825260208201905092915050565b60006100eb82610136565b91506100f683610136565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0382111561012b5761012a610140565b5b828201905092915050565b6000819050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b7f455850495245440000000000000000000000000000000000000000000000000060008201525056fea2646970667358221220ab2757ebc2b2a29957de6784b28b802df45baf56c759e3bcfcd4b01365438e5864736f6c63430008070033";
Expand All @@ -411,32 +402,21 @@ fn test_rpc_replay_transaction_timestamp() {
.sign(&evm_secret_key, Some(chain_id));
let tx_call_hash = tx_call.tx_id_hash();

while get_blockhash(&rpc_url) == blockhash {
sleep(Duration::from_secs(1));
}
let blockhash = get_blockhash(&rpc_url);
while get_blockhash(&rpc_url) == blockhash {
sleep(Duration::from_secs(1));
}
let blockhash = dbg!(get_blockhash(&rpc_url));
let ixs = vec![send_raw_tx(alice.pubkey(), tx_create, None, FeePayerType::Evm)];
let tx = Transaction::new_signed_with_payer(&ixs, None, &[&alice], blockhash);
let serialized_encoded_tx = bs58::encode(serialize(&tx).unwrap()).into_string();
let req = json_req!("sendTransaction", json!([serialized_encoded_tx]));
let json = dbg!(post_rpc(req, &rpc_url));
wait_confirmation(&rpc_url, &[&json["result"]]);
wait_finalization(&rpc_url, &[&json["result"]]);

sleep(Duration::from_secs(30));
let recent_blockhash = get_blockhash(&rpc_url);

let ixs = vec![send_raw_tx(alice.pubkey(), tx_call, None, FeePayerType::Evm)];
let tx = Transaction::new_signed_with_payer(&ixs, None, &[&alice], recent_blockhash);
let serialized_encoded_tx = bs58::encode(serialize(&tx).unwrap()).into_string();
let req = json_req!("sendTransaction", json!([serialized_encoded_tx]));
let json = dbg!(post_rpc(req, &rpc_url));
wait_confirmation(&rpc_url, &[&json["result"]]);

sleep(Duration::from_secs(35));
wait_finalization(&rpc_url, &[&json["result"]]);

let request = json_req!("trace_replayTransaction", json!([tx_call_hash, ["trace"]]));
let json = post_rpc(request.clone(), &rpc_url);
Expand All @@ -447,6 +427,187 @@ fn test_rpc_replay_transaction_timestamp() {
}));
}

#[test]
fn test_rpc_replay_transaction_gas_used() {
use solana_evm_loader_program::{send_raw_tx, transfer_native_to_evm_ixs};
solana_logger::setup_with_default("warn");

let evm_secret_key = evm_state::SecretKey::from_slice(&[1; 32]).unwrap();
let evm_address = evm_state::addr_from_public_key(&evm_state::PublicKey::from_secret_key(
evm_state::SECP256K1,
&evm_secret_key,
));

let alice = Keypair::new();
let test_validator = TestValidatorGenesis::default()
.fee_rate_governor(FeeRateGovernor::new(0, 0))
.rent(Rent {
lamports_per_byte_year: 1,
exemption_threshold: 1.0,
..Rent::default()
})
.enable_evm_state_archive()
.rpc_config(JsonRpcConfig {
enable_rpc_transaction_history: true,
..JsonRpcConfig::default_for_test()
})
.start_with_mint_address(alice.pubkey(), SocketAddrSpace::Unspecified)
.expect("validator start failed");
let rpc_url = test_validator.rpc_url();

let req = json_req!("eth_chainId", json!([]));
let json = post_rpc(req, &rpc_url);
let chain_id = Hex::from_hex(json["result"].as_str().unwrap()).unwrap().0;
warn!("chain_id: {}", chain_id);

let blockhash = dbg!(get_blockhash(&rpc_url));
let ixs = transfer_native_to_evm_ixs(alice.pubkey(), 10000000, evm_address);
let tx = Transaction::new_signed_with_payer(&ixs, None, &[&alice], blockhash);
let serialized_encoded_tx = bs58::encode(serialize(&tx).unwrap()).into_string();

let req = json_req!("sendTransaction", json!([serialized_encoded_tx]));
let json: Value = post_rpc(req, &rpc_url);
wait_finalization(&rpc_url, &[&json["result"]]);

// Contract with empty method that will revert after 60 seconds since creation
const TEST_CONTRACT: &str = "608060405234801561001057600080fd5b506101d0806100206000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c8063c41b95d114610030575b600080fd5b61004a600480360381019061004591906100eb565b61004c565b005b81600052600060205260406000208181558260005260016020526040600020905081815582600052600260205260406000209050818155826000526003602052604060002090508181558260005260046020526040600020905081815582600052600560205260406000209050818155505050565b6000813590506100d08161016c565b92915050565b6000813590506100e581610183565b92915050565b6000806040838503121561010257610101610167565b5b6000610110858286016100c1565b9250506020610121858286016100d6565b9150509250929050565b60006101368261013d565b9050919050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000819050919050565b600080fd5b6101758161012b565b811461018057600080fd5b50565b61018c8161015d565b811461019757600080fd5b5056fea264697066735822122036bbd0224659aebd8570f0c915a7fb3e556fb74eb7fd5af4a2fde6020dfe6a2864736f6c63430008070033";
let tx_create_1 = evm_state::UnsignedTransaction {
nonce: 0.into(),
gas_price: 2000000000.into(),
gas_limit: 300000.into(),
action: evm_state::TransactionAction::Create,
value: 0.into(),
input: hex::decode(TEST_CONTRACT).unwrap(),
}
.sign(&evm_secret_key, Some(chain_id));
let tx_create_2 = evm_state::UnsignedTransaction {
nonce: 1.into(),
gas_price: 2000000000.into(),
gas_limit: 300000.into(),
action: evm_state::TransactionAction::Create,
value: 0.into(),
input: hex::decode(TEST_CONTRACT).unwrap(),
}
.sign(&evm_secret_key, Some(chain_id));
let contract_address_1 = tx_create_1.address().unwrap();
let contract_address_2 = tx_create_2.address().unwrap();

info!("Deploy contracts {}, {}", contract_address_1, contract_address_2);
// deploy first contract
let blockhash = dbg!(get_blockhash(&rpc_url));
let ixs = vec![send_raw_tx(alice.pubkey(), tx_create_1, None, FeePayerType::Evm)];
let tx = Transaction::new_signed_with_payer(&ixs, None, &[&alice], blockhash);
let serialized_encoded_tx = bs58::encode(serialize(&tx).unwrap()).into_string();
let req = json_req!("sendTransaction", json!([serialized_encoded_tx]));
let json = dbg!(post_rpc(req, &rpc_url));
wait_finalization(&rpc_url, &[&json["result"]]);

// deploy second contract
let blockhash = dbg!(get_blockhash(&rpc_url));
let ixs = vec![send_raw_tx(alice.pubkey(), tx_create_2, None, FeePayerType::Evm)];
let tx = Transaction::new_signed_with_payer(&ixs, None, &[&alice], blockhash);
let serialized_encoded_tx = bs58::encode(serialize(&tx).unwrap()).into_string();
let req = json_req!("sendTransaction", json!([serialized_encoded_tx]));
let json = dbg!(post_rpc(req, &rpc_url));
wait_finalization(&rpc_url, &[&json["result"]]);

// the first two txs are setting up contract state
// the last tx clears state of the first contract so we can get gas_used from receipt
let tx_calls = vec![
evm_state::UnsignedTransaction {
nonce: 2.into(),
gas_price: 2000000000.into(),
gas_limit: 300000.into(),
action: evm_state::TransactionAction::Call(contract_address_1),
value: 0.into(),
input: hex::decode("c41b95d1000000000000000000000000141a4802f84bb64c0320917672ef7d92658e964e0000000000000000000000000000000000000000000000000000000000000001").unwrap(),
}
.sign(&evm_secret_key, Some(chain_id)),
evm_state::UnsignedTransaction {
nonce: 3.into(),
gas_price: 2000000000.into(),
gas_limit: 300000.into(),
action: evm_state::TransactionAction::Call(contract_address_2),
value: 0.into(),
input: hex::decode("c41b95d1000000000000000000000000141a4802f84bb64c0320917672ef7d92658e964e0000000000000000000000000000000000000000000000000000000000000001").unwrap(),
}
.sign(&evm_secret_key, Some(chain_id)),
evm_state::UnsignedTransaction {
nonce: 4.into(),
gas_price: 2000000000.into(),
gas_limit: 300000.into(),
action: evm_state::TransactionAction::Call(contract_address_1),
value: 0.into(),
input: hex::decode("c41b95d1000000000000000000000000141a4802f84bb64c0320917672ef7d92658e964e0000000000000000000000000000000000000000000000000000000000000000").unwrap(),
}
.sign(&evm_secret_key, Some(chain_id)),
];
let tx_call_hashes: Vec<_> = tx_calls.iter().map(|tx| tx.tx_id_hash()).collect();
let rpc_txs: Vec<_> = tx_calls
.iter()
.map(|tx| RPCTransaction::from_transaction(TransactionInReceipt::Signed(tx.clone())).unwrap())
.collect();

let recent_blockhash = get_blockhash(&rpc_url);
let ixs: Vec<_> = tx_calls
.into_iter()
.map(|tx| send_raw_tx(alice.pubkey(), tx, None, FeePayerType::Evm))
.collect();
let tx = Transaction::new_signed_with_payer(&ixs, None, &[&alice], recent_blockhash);
let serialized_encoded_tx = bs58::encode(serialize(&tx).unwrap()).into_string();
let req = json_req!("sendTransaction", json!([serialized_encoded_tx]));
let json = dbg!(post_rpc(req, &rpc_url));
wait_finalization(&rpc_url, &[&json["result"]]);

let request = json_req!("eth_getTransactionReceipt", json!([tx_call_hashes[2]]));
let json = post_rpc(request.clone(), &rpc_url);
let target_gas_limit: U256 = Hex::from_hex(json["result"]["gasUsed"].as_str().unwrap()).unwrap().0;

// Create transaction to pass with estimate=false and fail otherwise
let tx_with_limit = evm_state::UnsignedTransaction {
nonce: 5.into(),
gas_price: 2000000000.into(),
gas_limit: target_gas_limit * 2,
action: evm_state::TransactionAction::Call(contract_address_2),
value: 0.into(),
input: hex::decode("c41b95d1000000000000000000000000141a4802f84bb64c0320917672ef7d92658e964e0000000000000000000000000000000000000000000000000000000000000000").unwrap(),
}
.sign(&evm_secret_key, Some(chain_id));
let tx_with_limit_hash = tx_with_limit.tx_id_hash();
let rpc_tx = RPCTransaction::from_transaction(TransactionInReceipt::Signed(tx_with_limit.clone())).unwrap();
let recent_blockhash = get_blockhash(&rpc_url);
let ixs = vec![send_raw_tx(alice.pubkey(), tx_with_limit, None, FeePayerType::Evm)];
let tx = Transaction::new_signed_with_payer(&ixs, None, &[&alice], recent_blockhash);
let serialized_encoded_tx = bs58::encode(serialize(&tx).unwrap()).into_string();
let req = json_req!(
"sendTransaction",
json!([
serialized_encoded_tx,
RpcSendTransactionConfig {
skip_preflight: true,
..RpcSendTransactionConfig::default()
}
])
);
let json = dbg!(post_rpc(req, &rpc_url));
wait_finalization(&rpc_url, &[&json["result"]]);

// check that replayTransaction works
let request = json_req!("trace_replayTransaction", json!([tx_with_limit_hash, ["trace"]]));
let json = post_rpc(request.clone(), &rpc_url);
warn!(">>>>> trace_replayTransaction: {}", dbg!(&json["result"]["trace"].as_array().unwrap()[0]["result"]));
assert!(!json["result"].is_null());
assert!(json["result"]["trace"].as_array().unwrap().iter().all(|v| {
!v.as_object().unwrap().contains_key("error")
}));

// check that call fails
let request = json_req!("trace_call", json!([rpc_tx, vec!["trace"], Some("0x04")]));
let json = post_rpc(request.clone(), &rpc_url);
warn!("trace_call: {}", json["result"]);
assert_eq!(json["result"]["trace"].as_array().unwrap()[0]["error"].as_str().unwrap(), "Out of gas");
}

#[test]
fn test_rpc_invalid_requests() {
solana_logger::setup();
Expand Down

0 comments on commit 4e7f12a

Please sign in to comment.