From ac5596afae0b8e30c6645eea2bb75865ff96306f Mon Sep 17 00:00:00 2001 From: OliverNChalk <11343499+OliverNChalk@users.noreply.github.com> Date: Sat, 30 Nov 2024 12:38:02 +0100 Subject: [PATCH 1/5] brain damage but we got healed midway --- Cargo.lock | 1 + Cargo.toml | 1 + crates/svm-test/Cargo.toml | 1 + crates/svm-test/src/harness.rs | 6 ++ crates/svm-test/src/ser.rs | 132 ++++++++++++++++++++++++++++++++ crates/svm-test/src/test_rpc.rs | 96 +++++++++++++++++++---- 6 files changed, 221 insertions(+), 16 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f11428f..eb846cc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4556,6 +4556,7 @@ dependencies = [ "solana-client", "solana-logger", "solana-sdk", + "solana-transaction-status", "spl-associated-token-account 5.0.1", "spl-token", "spl-token-2022 5.0.2", diff --git a/Cargo.toml b/Cargo.toml index 573aed3..a9054ab 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ solana-client = "2.0.13" solana-logger = "2.0.13" solana-program = "2.0.13" solana-sdk = "2.0.13" +solana-transaction-status = "2.0.13" spl-associated-token-account = "5.0.1" spl-token = { version = "6.0.0", features = ["no-entrypoint"] } spl-token-2022 = { version = "5.0.0", features = ["no-entrypoint"] } diff --git a/crates/svm-test/Cargo.toml b/crates/svm-test/Cargo.toml index 6a1c6c0..be90cce 100644 --- a/crates/svm-test/Cargo.toml +++ b/crates/svm-test/Cargo.toml @@ -23,6 +23,7 @@ solana-account-decoder = { workspace = true } solana-client = { workspace = true } solana-logger = { workspace = true } solana-sdk = { workspace = true } +solana-transaction-status.workspace = true spl-associated-token-account = { workspace = true, optional = true } spl-token = { workspace = true, optional = true } spl-token-2022 = { workspace = true, optional = true } diff --git a/crates/svm-test/src/harness.rs b/crates/svm-test/src/harness.rs index cb4e16f..94ec704 100644 --- a/crates/svm-test/src/harness.rs +++ b/crates/svm-test/src/harness.rs @@ -4,6 +4,8 @@ use std::sync::{Arc, OnceLock, Weak}; use dashmap::DashMap; use solana_sdk::account::Account; use solana_sdk::pubkey::Pubkey; +use solana_sdk::signature::Signature; +use solana_sdk::transaction::VersionedTransaction; use test_rpc::TestRpc; use super::*; @@ -87,6 +89,10 @@ impl Scenario { ) -> ScenarioWithOverrides { ScenarioWithOverrides { scenario: self.clone(), overrides } } + + pub fn load_tx(&self, sig: &Signature) -> VersionedTransaction { + self.rpc.load_tx_sync(self.runtime, sig) + } } #[async_trait::async_trait] diff --git a/crates/svm-test/src/ser.rs b/crates/svm-test/src/ser.rs index 0c5699c..c446d54 100644 --- a/crates/svm-test/src/ser.rs +++ b/crates/svm-test/src/ser.rs @@ -3,6 +3,8 @@ use serde_with::serde_as; use solana_sdk::account::Account; use solana_sdk::clock::Epoch; use solana_sdk::pubkey::Pubkey; +use solana_sdk::transaction::VersionedTransaction; +use solana_transaction_status::{EncodableWithMeta, EncodedTransaction, UiTransaction}; serde_with::serde_conv!( pub AccountAsJsonAccount, @@ -50,3 +52,133 @@ impl From for JsonAccount { } } } + +serde_with::serde_conv!( + pub TxAsJsonTx, + VersionedTransaction, + |tx: &VersionedTransaction| { + match tx.json_encode() { + EncodedTransaction::Json(tx) => tx, + _ => unreachable!(), + } + }, + |tx: UiTransaction| -> Result<_, std::convert::Infallible> { + Ok(EncodedTransaction::Json(tx).decode().unwrap()) + } +); + +/* +/// A more efficient JSON representation of a [`VersionedTransaction`]. +#[serde_as] +#[derive(Debug, Default, Clone, Serialize, Deserialize)] +pub struct JsonVersionedTransaction { + #[serde_as(as = "Vec")] + pub signatures: Vec, + pub message: JsonVersionedMessage, +} + +impl From for VersionedTransaction { + fn from(value: JsonVersionedTransaction) -> Self { + VersionedTransaction { + signatures: value.signatures, + message: value.message, + } + } +} + +impl From for JsonVersionedTransaction { + fn from(value: VersionedTransaction) -> Self { + JsonVersionedTransaction { + signatures: value.signatures, + message: value.message, + } + } +} + + +/// A more efficient JSON representation of a [`VersionedMessage`]. +#[serde_as] +#[derive(Debug, Default, Clone, Serialize, Deserialize)] +pub enum JsonVersionedMessage { + Legacy(JsonLegacyMessage), + V0(JsonMessageV0), +} + +impl From for VersionedMessage { + fn from(value: JsonVersionedMessage) -> Self { + match value { + JsonVersionedMessage::Legacy(message) => VersionedMessage::Legacy(message.into()), + JsonVersionedMessage::V0(message) => VersionedMessage::V0(message.into()), + } + } +} + +impl From for JsonVersionedMessage { + fn from(value: VersionedMessage) -> Self { + match value { + VersionedMessage::Legacy(message) => JsonVersionedMessage::Legacy(JsonLegacyMessage::from(message)), + VersionedMessage::V0(message) => JsonVersionedMessage::V0(JsonLegacyV0::from(message)), + } + } +} + + +/// A more efficient JSON representation of a [`LegacyMessage`]. +#[serde_as] +#[derive(Debug, Default, Clone, Serialize, Deserialize)] +pub struct JsonLegacyMessage { + message: (), + is_writable_account_cache: (), +} + +impl From for LegacyMessage<'static> { + fn from(value: JsonLegacyMessage) -> Self { + LegacyMessage { + message: std::borrow::Cow::Owned(value.message.into()), + is_writable_account_cache: value.is_writable_account_cache, + } + } +} + +impl<'a> From> for JsonLegacyMessage { + fn from(value: LegacyMessage<'a>) -> Self { + JsonLegacyMessage { + message: value.message, + is_writable_account_cache: value.is_writable_account_cache, + } + } +} + +/// A more efficient JSON representation of a [`legacy::Message`]. +#[serde_as] +#[derive(Debug, Default, Clone, Serialize, Deserialize)] +struct JsonMessage { + a: UiTransactionEncoding + header: MessageHeader, + account_keys: Vec, + recent_blockhash: Hash, + instructions: Vec, +} + +impl From for Message { + fn from(value: JsonMessage) -> Self { + legacy::Message { + header: value.header, + account_keys: value.account_keys, + recent_blockhash: value.recent_blockhash, + instructions: value.instructions, + } + } +} + +impl From for JsonMessage { + fn from(value: legacy::Message) -> Self { + JsonMessage { + header: value.header, + account_keys: value.account_keys, + recent_blockhash: value.recent_blockhash, + instructions: value.instructions, + } + } +} +*/ diff --git a/crates/svm-test/src/test_rpc.rs b/crates/svm-test/src/test_rpc.rs index bac5850..98cdbf5 100644 --- a/crates/svm-test/src/test_rpc.rs +++ b/crates/svm-test/src/test_rpc.rs @@ -9,9 +9,13 @@ use serde::{Deserialize, Serialize}; use serde_with::serde_as; use solana_account_decoder::UiAccountEncoding; use solana_client::nonblocking::rpc_client::RpcClient; -use solana_client::rpc_config::RpcAccountInfoConfig; +use solana_client::rpc_config::{RpcAccountInfoConfig, RpcTransactionConfig}; use solana_sdk::account::Account; +use solana_sdk::commitment_config::CommitmentConfig; use solana_sdk::pubkey::Pubkey; +use solana_sdk::signature::Signature; +use solana_sdk::transaction::VersionedTransaction; +use solana_transaction_status::UiTransactionEncoding; use tokio::runtime::Runtime; use crate::utils::{read_json, read_json_gz, WriteOnDrop}; @@ -40,17 +44,18 @@ pub fn test_static_data_path() -> PathBuf { test_data_path().join("static.json") } -static STATIC_CACHE: OnceLock = OnceLock::new(); +static STATIC_CACHE: OnceLock = OnceLock::new(); -pub fn get_static_cache() -> &'static RpcCache { +pub fn get_static_cache() -> &'static AccountCache { STATIC_CACHE.get_or_init(|| read_json(&test_static_data_path())) } #[derive(Derivative)] #[derivative(Debug)] pub struct TestRpc { - static_cache: &'static RpcCache, - cache: RwLock>, + static_cache: &'static AccountCache, + account_cache: RwLock>, + tx_cache: RwLock>, /// If the RPC is set the cache file will be ignored & overwritten. #[derivative(Debug = "ignore")] rpc: Option, @@ -59,19 +64,54 @@ pub struct TestRpc { impl TestRpc { fn unwrap_rpc(&self, key: &Pubkey) -> &RpcClient { self.rpc.as_ref().unwrap_or_else(|| { - panic!("Test tried to access an uncached account and TEST_RPC is not set; key={key:?}") + panic!("Test tried to access an uncached account and TEST_RPC is not set; key={key}") }) } + + pub(crate) fn load_tx_sync(&self, runtime: &Runtime, sig: &Signature) -> VersionedTransaction { + // Try load from cache + if let Some(tx) = self.tx_cache.read().unwrap().get(sig) { + return tx.clone(); + } + + // Load from RPC. + let rpc = self.rpc.as_ref().unwrap_or_else(|| { + panic!("Test tried to access an uncached account and TEST_RPC is not set; tx={sig}") + }); + let tx = runtime + .block_on(rpc.get_transaction_with_config( + sig, + RpcTransactionConfig { + commitment: Some(CommitmentConfig::processed()), + encoding: Some(UiTransactionEncoding::Base64), + max_supported_transaction_version: Some(1), + }, + )) + .unwrap() + .transaction + .transaction + .decode() + .unwrap(); + + // Update cache. + self.tx_cache.write().unwrap().insert(*sig, tx.clone()); + + tx + } } impl TestRpc { pub fn load_snapshot(slot: u64) -> Self { let static_cache = get_static_cache(); let cache_path = test_data_path().join(format!("snapshots/{slot}.json.gz")); - let cache = - RwLock::new(WriteOnDrop::new(read_json_gz::(&cache_path), Some(cache_path))); + let account_cache = RwLock::new(WriteOnDrop::new( + read_json_gz::(&cache_path), + Some(cache_path), + )); + let tx_cache = + RwLock::new(WriteOnDrop::new(read_json_gz::(&cache_path), Some(cache_path))); - TestRpc { static_cache, cache, rpc: None } + TestRpc { static_cache, account_cache, tx_cache, rpc: None } } pub fn load_scenario(name: &str) -> Self { @@ -86,13 +126,13 @@ impl TestRpc { let cache = RwLock::new(WriteOnDrop::new( match rpc.is_some() { - true => RpcCache(BTreeMap::default()), + true => AccountCache(BTreeMap::default()), false => read_json_gz(&cache_path), }, Some(cache_path), )); - TestRpc { static_cache: get_static_cache(), cache, rpc } + TestRpc { static_cache: get_static_cache(), account_cache: cache, rpc } } pub fn account_sync(&self, runtime: &'static Runtime, key: &Pubkey) -> Account { @@ -105,7 +145,7 @@ impl TestRpc { .static_cache .get(key) .cloned() - .or_else(|| self.cache.read().unwrap().get(key).cloned()) + .or_else(|| self.account_cache.read().unwrap().get(key).cloned()) { return cached; } @@ -126,7 +166,10 @@ impl TestRpc { .unwrap_or_default(); // Update cache. - self.cache.write().unwrap().insert(*key, account.clone()); + self.account_cache + .write() + .unwrap() + .insert(*key, account.clone()); account } @@ -134,12 +177,12 @@ impl TestRpc { #[serde_as] #[derive(Debug, Default, Serialize, Deserialize)] -pub struct RpcCache( +pub struct AccountCache( #[serde_as(as = "BTreeMap")] pub BTreeMap, ); -impl Deref for RpcCache { +impl Deref for AccountCache { type Target = BTreeMap; fn deref(&self) -> &Self::Target { @@ -147,7 +190,28 @@ impl Deref for RpcCache { } } -impl DerefMut for RpcCache { +impl DerefMut for AccountCache { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +#[serde_as] +#[derive(Debug, Default, Serialize, Deserialize)] +pub struct TxCache( + #[serde_as(as = "BTreeMap")] + pub BTreeMap, +); + +impl Deref for TxCache { + type Target = BTreeMap; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for TxCache { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 } From 7be6037f3268b668a74f456c0aa4089d25cff1db Mon Sep 17 00:00:00 2001 From: OliverNChalk <11343499+OliverNChalk@users.noreply.github.com> Date: Sat, 30 Nov 2024 13:00:37 +0100 Subject: [PATCH 2/5] clean up and test tx serialize --- crates/svm-test/src/harness.rs | 6 -- crates/svm-test/src/ser.rs | 184 ++++++++++++-------------------- crates/svm-test/src/test_rpc.rs | 34 +++--- 3 files changed, 81 insertions(+), 143 deletions(-) diff --git a/crates/svm-test/src/harness.rs b/crates/svm-test/src/harness.rs index 94ec704..f9c643f 100644 --- a/crates/svm-test/src/harness.rs +++ b/crates/svm-test/src/harness.rs @@ -63,12 +63,6 @@ impl Harness { scenario } - pub fn get_snapshot(&'static self, block: u64) -> Arc { - let rpc = TestRpc::load_snapshot(block); - - Arc::new(Scenario { runtime: &self.runtime, rpc }) - } - fn load_scenario(&'static self, name: &str) -> Arc { let rpc = TestRpc::load_scenario(name); diff --git a/crates/svm-test/src/ser.rs b/crates/svm-test/src/ser.rs index c446d54..b856f70 100644 --- a/crates/svm-test/src/ser.rs +++ b/crates/svm-test/src/ser.rs @@ -56,129 +56,79 @@ impl From for JsonAccount { serde_with::serde_conv!( pub TxAsJsonTx, VersionedTransaction, - |tx: &VersionedTransaction| { - match tx.json_encode() { - EncodedTransaction::Json(tx) => tx, - _ => unreachable!(), - } - }, - |tx: UiTransaction| -> Result<_, std::convert::Infallible> { - Ok(EncodedTransaction::Json(tx).decode().unwrap()) - } + to_json_tx, + from_json_tx ); -/* -/// A more efficient JSON representation of a [`VersionedTransaction`]. -#[serde_as] -#[derive(Debug, Default, Clone, Serialize, Deserialize)] -pub struct JsonVersionedTransaction { - #[serde_as(as = "Vec")] - pub signatures: Vec, - pub message: JsonVersionedMessage, -} - -impl From for VersionedTransaction { - fn from(value: JsonVersionedTransaction) -> Self { - VersionedTransaction { - signatures: value.signatures, - message: value.message, - } - } -} - -impl From for JsonVersionedTransaction { - fn from(value: VersionedTransaction) -> Self { - JsonVersionedTransaction { - signatures: value.signatures, - message: value.message, - } - } -} - - -/// A more efficient JSON representation of a [`VersionedMessage`]. -#[serde_as] -#[derive(Debug, Default, Clone, Serialize, Deserialize)] -pub enum JsonVersionedMessage { - Legacy(JsonLegacyMessage), - V0(JsonMessageV0), -} - -impl From for VersionedMessage { - fn from(value: JsonVersionedMessage) -> Self { - match value { - JsonVersionedMessage::Legacy(message) => VersionedMessage::Legacy(message.into()), - JsonVersionedMessage::V0(message) => VersionedMessage::V0(message.into()), - } - } -} - -impl From for JsonVersionedMessage { - fn from(value: VersionedMessage) -> Self { - match value { - VersionedMessage::Legacy(message) => JsonVersionedMessage::Legacy(JsonLegacyMessage::from(message)), - VersionedMessage::V0(message) => JsonVersionedMessage::V0(JsonLegacyV0::from(message)), - } - } -} - - -/// A more efficient JSON representation of a [`LegacyMessage`]. -#[serde_as] -#[derive(Debug, Default, Clone, Serialize, Deserialize)] -pub struct JsonLegacyMessage { - message: (), - is_writable_account_cache: (), -} - -impl From for LegacyMessage<'static> { - fn from(value: JsonLegacyMessage) -> Self { - LegacyMessage { - message: std::borrow::Cow::Owned(value.message.into()), - is_writable_account_cache: value.is_writable_account_cache, - } - } -} - -impl<'a> From> for JsonLegacyMessage { - fn from(value: LegacyMessage<'a>) -> Self { - JsonLegacyMessage { - message: value.message, - is_writable_account_cache: value.is_writable_account_cache, - } +fn to_json_tx(tx: &VersionedTransaction) -> UiTransaction { + match tx.json_encode() { + EncodedTransaction::Json(tx) => tx, + _ => unreachable!(), } } -/// A more efficient JSON representation of a [`legacy::Message`]. -#[serde_as] -#[derive(Debug, Default, Clone, Serialize, Deserialize)] -struct JsonMessage { - a: UiTransactionEncoding - header: MessageHeader, - account_keys: Vec, - recent_blockhash: Hash, - instructions: Vec, +fn from_json_tx(tx: UiTransaction) -> Result { + Ok(EncodedTransaction::Json(tx).decode().unwrap()) } -impl From for Message { - fn from(value: JsonMessage) -> Self { - legacy::Message { - header: value.header, - account_keys: value.account_keys, - recent_blockhash: value.recent_blockhash, - instructions: value.instructions, - } - } -} - -impl From for JsonMessage { - fn from(value: legacy::Message) -> Self { - JsonMessage { - header: value.header, - account_keys: value.account_keys, - recent_blockhash: value.recent_blockhash, - instructions: value.instructions, - } +#[cfg(test)] +mod tests { + use expect_test::expect; + use solana_sdk::hash::Hash; + use solana_sdk::signer::Signer; + use solana_sdk::system_instruction; + use solana_sdk::transaction::Transaction; + + use super::*; + use crate::utils::test_payer_keypair; + + const DUMMY_PUBKEY: Pubkey = Pubkey::new_from_array([1; 32]); + const DUMMY_HASH: Hash = Hash::new_from_array([2; 32]); + + #[test] + fn serialize_legacy_transaction() { + let tx = Transaction::new_signed_with_payer( + &[system_instruction::transfer(&test_payer_keypair().pubkey(), &DUMMY_PUBKEY, 500)], + Some(&test_payer_keypair().pubkey()), + &[test_payer_keypair()], + DUMMY_HASH, + ) + .into(); + + // Act - Serialize. + let serialized = serde_json::to_string_pretty(&to_json_tx(&tx)).unwrap(); + + // Assert. + expect![[r#" + { + "signatures": [ + "Y5KX5txmP8TwgsD2yx43AeUeHnLwPDz3nYhz3xudPMDq5AKmowKk3r3qjsGSp1VFSFhcc5T1dN9x3mqjpRpV1Xi" + ], + "message": { + "header": { + "numRequiredSignatures": 1, + "numReadonlySignedAccounts": 0, + "numReadonlyUnsignedAccounts": 1 + }, + "accountKeys": [ + "AKnL4NNf3DGWZJS6cPknBuEGnVsV4A4m5tgebLHaRSZ9", + "4vJ9JU1bJJE96FWSJKvHsmmFADCg4gpZQff4P3bkLKi", + "11111111111111111111111111111111" + ], + "recentBlockhash": "8qbHbw2BbbTHBW1sbeqakYXVKRQM8Ne7pLK7m6CVfeR", + "instructions": [ + { + "programIdIndex": 2, + "accounts": [ + 0, + 1 + ], + "data": "3Bxs4hfoaMPsQgGf", + "stackHeight": null + } + ] + } + }"#]] + .assert_eq(&serialized); } } -*/ diff --git a/crates/svm-test/src/test_rpc.rs b/crates/svm-test/src/test_rpc.rs index 98cdbf5..30e0438 100644 --- a/crates/svm-test/src/test_rpc.rs +++ b/crates/svm-test/src/test_rpc.rs @@ -101,38 +101,32 @@ impl TestRpc { } impl TestRpc { - pub fn load_snapshot(slot: u64) -> Self { - let static_cache = get_static_cache(); - let cache_path = test_data_path().join(format!("snapshots/{slot}.json.gz")); - let account_cache = RwLock::new(WriteOnDrop::new( - read_json_gz::(&cache_path), - Some(cache_path), - )); - let tx_cache = - RwLock::new(WriteOnDrop::new(read_json_gz::(&cache_path), Some(cache_path))); - - TestRpc { static_cache, account_cache, tx_cache, rpc: None } - } - pub fn load_scenario(name: &str) -> Self { - let cache_path = test_data_path().join(format!("{name}.json.gz")); let rpc = match std::env::var("TEST_RPC") { Ok(url) => Some(RpcClient::new(url)), Err(VarError::NotPresent) => None, Err(VarError::NotUnicode(raw)) => panic!("Non utf8 TEST_RPC; raw={raw:?}"), }; - assert!(rpc.is_some() || cache_path.exists(), "Need either `TEST_RPC` or test cache file"); - - let cache = RwLock::new(WriteOnDrop::new( + let account_cache_path = test_data_path().join(format!("{name}-accounts.json.gz")); + let account_cache = RwLock::new(WriteOnDrop::new( match rpc.is_some() { true => AccountCache(BTreeMap::default()), - false => read_json_gz(&cache_path), + false => read_json_gz(&account_cache_path), + }, + Some(account_cache_path), + )); + + let tx_cache_path = test_data_path().join(format!("{name}-tx.json.gz")); + let tx_cache = RwLock::new(WriteOnDrop::new( + match rpc.is_some() { + true => TxCache(BTreeMap::default()), + false => read_json_gz(&tx_cache_path), }, - Some(cache_path), + Some(tx_cache_path), )); - TestRpc { static_cache: get_static_cache(), account_cache: cache, rpc } + TestRpc { static_cache: get_static_cache(), account_cache, tx_cache, rpc } } pub fn account_sync(&self, runtime: &'static Runtime, key: &Pubkey) -> Account { From 91cebf521bbb1a447873181fa1f42a7cfed43428 Mon Sep 17 00:00:00 2001 From: OliverNChalk <11343499+OliverNChalk@users.noreply.github.com> Date: Sat, 30 Nov 2024 13:58:08 +0100 Subject: [PATCH 3/5] impl UiTransaction -> VersionedTransaction --- Cargo.lock | 17 ++++ crates/svm-test/Cargo.toml | 1 + crates/svm-test/src/ser.rs | 184 ++++++++++++++++++++++++++++++++++--- 3 files changed, 190 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index eb846cc..25b493f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1223,6 +1223,16 @@ dependencies = [ "once_cell", ] +[[package]] +name = "eyre" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" +dependencies = [ + "indenter", + "once_cell", +] + [[package]] name = "fastrand" version = "2.1.1" @@ -1671,6 +1681,12 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "indenter" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" + [[package]] name = "indexmap" version = "1.9.3" @@ -4544,6 +4560,7 @@ dependencies = [ "dashmap 6.1.0", "derivative", "expect-test", + "eyre", "faucet", "flate2", "futures", diff --git a/crates/svm-test/Cargo.toml b/crates/svm-test/Cargo.toml index be90cce..e7cdf86 100644 --- a/crates/svm-test/Cargo.toml +++ b/crates/svm-test/Cargo.toml @@ -12,6 +12,7 @@ async-trait = "0.1.81" auto_impl.workspace = true dashmap = "6.0.1" derivative = "2.2.0" +eyre = "0.6.12" flate2 = "1.0.32" futures = "0.3.30" itertools = "0.13.0" diff --git a/crates/svm-test/src/ser.rs b/crates/svm-test/src/ser.rs index b856f70..4f37232 100644 --- a/crates/svm-test/src/ser.rs +++ b/crates/svm-test/src/ser.rs @@ -2,9 +2,14 @@ use serde::{Deserialize, Serialize}; use serde_with::serde_as; use solana_sdk::account::Account; use solana_sdk::clock::Epoch; +use solana_sdk::instruction::CompiledInstruction; +use solana_sdk::message::v0::MessageAddressTableLookup; +use solana_sdk::message::{legacy, v0, VersionedMessage}; use solana_sdk::pubkey::Pubkey; use solana_sdk::transaction::VersionedTransaction; -use solana_transaction_status::{EncodableWithMeta, EncodedTransaction, UiTransaction}; +use solana_transaction_status::{ + EncodableWithMeta, EncodedTransaction, UiMessage, UiRawMessage, UiTransaction, +}; serde_with::serde_conv!( pub AccountAsJsonAccount, @@ -67,14 +72,92 @@ fn to_json_tx(tx: &VersionedTransaction) -> UiTransaction { } } -fn from_json_tx(tx: UiTransaction) -> Result { - Ok(EncodedTransaction::Json(tx).decode().unwrap()) +fn from_json_tx(tx: UiTransaction) -> Result { + Ok(VersionedTransaction { + signatures: tx + .signatures + .iter() + .try_fold(Vec::default(), |mut sigs, sig| { + sig.parse().map(|sig| { + sigs.push(sig); + + sigs + }) + })?, + message: match tx.message { + UiMessage::Parsed(_) => unimplemented!(), + UiMessage::Raw(UiRawMessage { + header, + account_keys, + recent_blockhash, + instructions, + address_table_lookups, + }) => { + let account_keys = + account_keys + .iter() + .try_fold(Vec::default(), |mut keys, key| { + key.parse().map(|key| { + keys.push(key); + + keys + }) + })?; + let recent_blockhash = recent_blockhash.parse()?; + let instructions = + instructions + .into_iter() + .try_fold(Vec::default(), |mut ixs, ix| { + solana_sdk::bs58::decode(&ix.data).into_vec().map(|data| { + ixs.push(CompiledInstruction { + program_id_index: ix.program_id_index, + accounts: ix.accounts, + data, + }); + + ixs + }) + })?; + + match address_table_lookups { + Some(address_table_lookups) => VersionedMessage::V0(v0::Message { + header, + account_keys, + recent_blockhash, + instructions, + address_table_lookups: address_table_lookups.into_iter().try_fold( + Vec::default(), + |mut alts, alt| { + alt.account_key.parse().map(|account_key| { + alts.push(MessageAddressTableLookup { + account_key, + writable_indexes: alt.writable_indexes, + readonly_indexes: alt.readonly_indexes, + }); + + alts + }) + }, + )?, + }), + None => VersionedMessage::Legacy(legacy::Message { + header, + account_keys, + recent_blockhash, + instructions, + }), + } + } + }, + }) } #[cfg(test)] mod tests { use expect_test::expect; + use solana_sdk::address_lookup_table::AddressLookupTableAccount; use solana_sdk::hash::Hash; + use solana_sdk::message::VersionedMessage; use solana_sdk::signer::Signer; use solana_sdk::system_instruction; use solana_sdk::transaction::Transaction; @@ -85,18 +168,24 @@ mod tests { const DUMMY_PUBKEY: Pubkey = Pubkey::new_from_array([1; 32]); const DUMMY_HASH: Hash = Hash::new_from_array([2; 32]); + #[serde_as] + #[derive(Debug, Serialize, Deserialize)] + struct Wrapper(#[serde_as(as = "TxAsJsonTx")] VersionedTransaction); + #[test] - fn serialize_legacy_transaction() { - let tx = Transaction::new_signed_with_payer( - &[system_instruction::transfer(&test_payer_keypair().pubkey(), &DUMMY_PUBKEY, 500)], - Some(&test_payer_keypair().pubkey()), - &[test_payer_keypair()], - DUMMY_HASH, - ) - .into(); + fn round_trip_legacy_tx() { + let tx = Wrapper( + Transaction::new_signed_with_payer( + &[system_instruction::transfer(&test_payer_keypair().pubkey(), &DUMMY_PUBKEY, 500)], + Some(&test_payer_keypair().pubkey()), + &[test_payer_keypair()], + DUMMY_HASH, + ) + .into(), + ); // Act - Serialize. - let serialized = serde_json::to_string_pretty(&to_json_tx(&tx)).unwrap(); + let serialized = serde_json::to_string_pretty(&tx).unwrap(); // Assert. expect![[r#" @@ -130,5 +219,76 @@ mod tests { } }"#]] .assert_eq(&serialized); + + // Act - Recover. + let recovered: Wrapper = serde_json::from_str(&serialized).unwrap(); + + // Assert. + assert_eq!(recovered.0, tx.0); + } + + #[test] + fn round_trip_v0_tx() { + let message = solana_sdk::message::v0::Message::try_compile( + &test_payer_keypair().pubkey(), + &[system_instruction::transfer(&test_payer_keypair().pubkey(), &DUMMY_PUBKEY, 500)], + &vec![AddressLookupTableAccount { key: DUMMY_PUBKEY, addresses: vec![DUMMY_PUBKEY] }], + DUMMY_HASH, + ) + .unwrap(); + let tx = Wrapper( + VersionedTransaction::try_new(VersionedMessage::V0(message), &[test_payer_keypair()]) + .unwrap(), + ); + + // Act - Serialize. + let serialized = serde_json::to_string_pretty(&tx).unwrap(); + + // Assert. + expect![[r#" + { + "signatures": [ + "44z9pb2J9mfT6VSj3hL2KmFUjU7BQub8xV1iyCTduaSVb83db1GL4gfbamAgrP2GB1yk7rkLHmHfsB1mgxouxDRH" + ], + "message": { + "header": { + "numRequiredSignatures": 1, + "numReadonlySignedAccounts": 0, + "numReadonlyUnsignedAccounts": 1 + }, + "accountKeys": [ + "AKnL4NNf3DGWZJS6cPknBuEGnVsV4A4m5tgebLHaRSZ9", + "11111111111111111111111111111111" + ], + "recentBlockhash": "8qbHbw2BbbTHBW1sbeqakYXVKRQM8Ne7pLK7m6CVfeR", + "instructions": [ + { + "programIdIndex": 1, + "accounts": [ + 0, + 2 + ], + "data": "3Bxs4hfoaMPsQgGf", + "stackHeight": null + } + ], + "addressTableLookups": [ + { + "accountKey": "4vJ9JU1bJJE96FWSJKvHsmmFADCg4gpZQff4P3bkLKi", + "writableIndexes": [ + 0 + ], + "readonlyIndexes": [] + } + ] + } + }"#]] + .assert_eq(&serialized); + + // Act - Recover. + let recovered: Wrapper = serde_json::from_str(&serialized).unwrap(); + + // Assert. + assert_eq!(recovered.0, tx.0); } } From a9e77bb78664d7cd32e78e6f7c7630d27308804b Mon Sep 17 00:00:00 2001 From: OliverNChalk <11343499+OliverNChalk@users.noreply.github.com> Date: Sat, 30 Nov 2024 14:33:05 +0100 Subject: [PATCH 4/5] derive Clone on Svm --- Cargo.lock | 22 ++++++++++++++++++---- crates/svm-test/src/svm.rs | 1 + 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 25b493f..ad5e7ec 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -63,6 +63,17 @@ dependencies = [ "zeroize", ] +[[package]] +name = "ahash" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" +dependencies = [ + "getrandom 0.2.15", + "once_cell", + "version_check", +] + [[package]] name = "ahash" version = "0.8.11" @@ -492,7 +503,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "115e54d64eb62cdebad391c19efc9dce4981c690c85a33a12199d99bb9546fee" dependencies = [ "borsh-derive 0.10.4", - "hashbrown 0.13.2", + "hashbrown 0.12.3", ] [[package]] @@ -1470,6 +1481,9 @@ name = "hashbrown" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash 0.7.8", +] [[package]] name = "hashbrown" @@ -1477,7 +1491,7 @@ version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" dependencies = [ - "ahash", + "ahash 0.8.11", ] [[package]] @@ -1902,7 +1916,7 @@ checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] name = "litesvm" version = "0.3.0" -source = "git+https://github.com/OliverNChalk/litesvm#0e840791846332b6b8feed109a34094e8be9e639" +source = "git+https://github.com/OliverNChalk/litesvm#fe938a2e40da5c01ee011f4502ff36e86aed36b4" dependencies = [ "bincode", "indexmap 2.6.0", @@ -3472,7 +3486,7 @@ version = "2.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00c4128122787a61d8f94fdaa04cb71b3dbb017d9939ac4d632264c55ec345de" dependencies = [ - "ahash", + "ahash 0.8.11", "bincode", "bv", "caps", diff --git a/crates/svm-test/src/svm.rs b/crates/svm-test/src/svm.rs index a894b94..16c16df 100644 --- a/crates/svm-test/src/svm.rs +++ b/crates/svm-test/src/svm.rs @@ -22,6 +22,7 @@ pub type DefaultLoader = HashMap { inner: litesvm::LiteSVM, pub loader: L, From 80c43a0d90266d0b5aa704e8d95729a5e533bf20 Mon Sep 17 00:00:00 2001 From: OliverNChalk <11343499+OliverNChalk@users.noreply.github.com> Date: Thu, 5 Dec 2024 14:51:02 +0100 Subject: [PATCH 5/5] bad --- crates/svm-test/src/svm.rs | 6 ++++-- crates/svm-test/src/test_rpc.rs | 29 +++++++++++++++++++---------- 2 files changed, 23 insertions(+), 12 deletions(-) diff --git a/crates/svm-test/src/svm.rs b/crates/svm-test/src/svm.rs index 16c16df..851c355 100644 --- a/crates/svm-test/src/svm.rs +++ b/crates/svm-test/src/svm.rs @@ -24,7 +24,7 @@ const PRE_LOADED: &[Pubkey] = #[derive(Clone)] pub struct Svm { - inner: litesvm::LiteSVM, + pub inner: litesvm::LiteSVM, pub loader: L, reserved_account_keys: ReservedAccountKeys, } @@ -56,7 +56,7 @@ where .with_lamports(1_000_000u64.wrapping_mul(10u64.pow(9))) .with_sysvars() .with_sigverify(true) - .with_blockhash_check(true) + // .with_blockhash_check(true) } pub fn new(loader: L) -> Self { @@ -181,6 +181,8 @@ where } // Programs are a bit special. + let key_s = key.to_string(); + println!("{key_s}"); let account = self.loader.load(key); match (account.executable, account.owner) { (true, bpf_loader::ID | native_loader::ID) => {} diff --git a/crates/svm-test/src/test_rpc.rs b/crates/svm-test/src/test_rpc.rs index 30e0438..a6070c7 100644 --- a/crates/svm-test/src/test_rpc.rs +++ b/crates/svm-test/src/test_rpc.rs @@ -55,7 +55,7 @@ pub fn get_static_cache() -> &'static AccountCache { pub struct TestRpc { static_cache: &'static AccountCache, account_cache: RwLock>, - tx_cache: RwLock>, + transaction_cache: RwLock>, /// If the RPC is set the cache file will be ignored & overwritten. #[derivative(Debug = "ignore")] rpc: Option, @@ -70,7 +70,7 @@ impl TestRpc { pub(crate) fn load_tx_sync(&self, runtime: &Runtime, sig: &Signature) -> VersionedTransaction { // Try load from cache - if let Some(tx) = self.tx_cache.read().unwrap().get(sig) { + if let Some(tx) = self.transaction_cache.read().unwrap().get(sig) { return tx.clone(); } @@ -82,7 +82,7 @@ impl TestRpc { .block_on(rpc.get_transaction_with_config( sig, RpcTransactionConfig { - commitment: Some(CommitmentConfig::processed()), + commitment: Some(CommitmentConfig::confirmed()), encoding: Some(UiTransactionEncoding::Base64), max_supported_transaction_version: Some(1), }, @@ -94,7 +94,10 @@ impl TestRpc { .unwrap(); // Update cache. - self.tx_cache.write().unwrap().insert(*sig, tx.clone()); + self.transaction_cache + .write() + .unwrap() + .insert(*sig, tx.clone()); tx } @@ -108,7 +111,12 @@ impl TestRpc { Err(VarError::NotUnicode(raw)) => panic!("Non utf8 TEST_RPC; raw={raw:?}"), }; - let account_cache_path = test_data_path().join(format!("{name}-accounts.json.gz")); + // Ensure the scenario directory exists. + let scenario = test_data_path().join(name); + std::fs::create_dir_all(&scenario).unwrap(); + + // Load account cache. + let account_cache_path = scenario.join("accounts.json.gz"); let account_cache = RwLock::new(WriteOnDrop::new( match rpc.is_some() { true => AccountCache(BTreeMap::default()), @@ -117,16 +125,17 @@ impl TestRpc { Some(account_cache_path), )); - let tx_cache_path = test_data_path().join(format!("{name}-tx.json.gz")); - let tx_cache = RwLock::new(WriteOnDrop::new( + // Load transaction cache. + let transaction_cache_path = scenario.join("transactions.json.gz"); + let transaction_cache = RwLock::new(WriteOnDrop::new( match rpc.is_some() { true => TxCache(BTreeMap::default()), - false => read_json_gz(&tx_cache_path), + false => read_json_gz(&transaction_cache_path), }, - Some(tx_cache_path), + Some(transaction_cache_path), )); - TestRpc { static_cache: get_static_cache(), account_cache, tx_cache, rpc } + TestRpc { static_cache: get_static_cache(), account_cache, transaction_cache, rpc } } pub fn account_sync(&self, runtime: &'static Runtime, key: &Pubkey) -> Account {