Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ juniper_codegen = { version = "0.16.0", default-features = false }
juniper_graphql_ws = { version = "0.4.0", default-features = false }
lazy_static = "1.5.0"
libloading = "0.7.4"
litesvm = { version = "0.11.0", features = ["nodejs-internal"] }
litesvm = { version = "0.11.0", features = ["nodejs-internal", "precompiles"] }
litesvm-token = "0.11.0"
log = "0.4.27"
mime_guess = { version = "2.0.4", default-features = false }
Expand Down Expand Up @@ -112,6 +112,7 @@ solana-commitment-config = { version = "3.1", default-features = false }
solana-compute-budget-interface = { version = "3.0", default-features = false }
solana-epoch-info = { version = "3.1", default-features = false }
solana-epoch-schedule = { version = "3.0", default-features = false }
solana-ed25519-program = { version = "3.0", default-features = false }
solana-feature-gate-interface = { version = "3.1", default-features = false }
solana-genesis-config = { version = "3.0", default-features = false }
# solana-geyser-plugin-manager = { version = "=3.1.6", default-features = false } # Disabled: version conflicts with litesvm 0.9.1 (requires solana-instruction =3.0.0 vs ~3.1)
Expand Down
2 changes: 2 additions & 0 deletions crates/core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,10 @@ txtx-addon-network-svm = { workspace = true }


[dev-dependencies]
ed25519-dalek = "1.0.1"
test-case = { workspace = true }
env_logger = "0.11"
solana-ed25519-program = { workspace = true }
tempfile = { workspace = true }
spl-token-metadata-interface = { workspace = true }

Expand Down
61 changes: 61 additions & 0 deletions crates/core/src/surfnet/surfnet_lite_svm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ impl SurfnetLiteSvm {
.with_lamports(1_000_000u64.wrapping_mul(LAMPORTS_PER_SOL))
.with_sysvars()
.with_default_programs()
.with_precompiles()
.with_blockhash_check(false)
.with_sigverify(false)
}
Expand Down Expand Up @@ -342,3 +343,63 @@ fn create_native_mint(svm: &mut SurfnetLiteSvm) {
svm.set_account(spl_token_interface::native_mint::ID, account)
.expect("Failed to create native mint account in SVM");
}
#[cfg(test)]
mod tests {
use ed25519_dalek::Signer as DalekSigner;
use solana_compute_budget_interface::ComputeBudgetInstruction;
use solana_ed25519_program::new_ed25519_instruction_with_signature;
use solana_keypair::Keypair;
use solana_message::{Message, VersionedMessage};
use solana_signer::Signer;
use solana_transaction::versioned::VersionedTransaction;

use super::*;

fn build_ed25519_transaction(
payer: &Keypair,
blockhash: solana_hash::Hash,
) -> VersionedTransaction {
let payer_dalek = ed25519_dalek::Keypair::from_bytes(&payer.to_bytes())
.expect("failed to create dalek keypair");
let message = b"surfpool ed25519 precompile regression";
let signature = payer_dalek.sign(message);
let ed25519_ix = new_ed25519_instruction_with_signature(
message,
&signature.to_bytes(),
payer.pubkey().as_array(),
);
let compute_budget_ixs = [
ComputeBudgetInstruction::set_compute_unit_limit(100_000),
ComputeBudgetInstruction::set_compute_unit_price(1),
];

let tx_message = Message::new_with_blockhash(
&[
compute_budget_ixs[0].clone(),
compute_budget_ixs[1].clone(),
ed25519_ix,
],
Some(&payer.pubkey()),
&blockhash,
);

VersionedTransaction::try_new(VersionedMessage::Legacy(tx_message), &[&payer])
.expect("failed to create ed25519 transaction")
}

#[test]
fn test_base_litesvm_settings_registers_ed25519_precompile() {
let mut svm = SurfnetLiteSvm::base_litesvm_settings();
let payer = Keypair::new();
svm.airdrop(&payer.pubkey(), LAMPORTS_PER_SOL)
.expect("failed to fund test payer");
let tx = build_ed25519_transaction(&payer, svm.latest_blockhash());

let result = svm.send_transaction(tx);

assert!(
result.is_ok(),
"ed25519 precompile should be available in base_litesvm_settings"
);
}
}
79 changes: 79 additions & 0 deletions crates/core/src/tests/integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use std::{str::FromStr, sync::Arc, time::Duration};

use base64::Engine;
use crossbeam_channel::{unbounded, unbounded as crossbeam_unbounded};
use ed25519_dalek::Signer as DalekSigner;
use jsonrpc_core::{
Error, Result as JsonRpcResult,
futures::future::{self, join_all},
Expand All @@ -18,6 +19,7 @@ use solana_client::{
use solana_clock::{Clock, Slot};
use solana_commitment_config::{CommitmentConfig, CommitmentLevel};
use solana_compute_budget_interface::ComputeBudgetInstruction;
use solana_ed25519_program::new_ed25519_instruction_with_signature;
use solana_epoch_info::EpochInfo;
use solana_hash::Hash;
use solana_keypair::Keypair;
Expand Down Expand Up @@ -752,6 +754,83 @@ async fn test_simulate_transaction_no_signers(test_type: TestType) {
);
}

#[test_case(TestType::sqlite(); "with on-disk sqlite db")]
#[test_case(TestType::in_memory(); "with in-memory sqlite db")]
#[test_case(TestType::no_db(); "with no db")]
#[cfg_attr(feature = "postgres", test_case(TestType::postgres(); "with postgres db"))]
#[tokio::test(flavor = "multi_thread")]
async fn test_transaction_with_ed25519_instruction(test_type: TestType) {
let payer = Keypair::new();
let (mut svm_instance, _simnet_events_rx, _geyser_events_rx) = test_type.initialize_svm();
svm_instance
.airdrop(&payer.pubkey(), LAMPORTS_PER_SOL)
.unwrap()
.unwrap();
let recent_blockhash = svm_instance.latest_blockhash();

// Issue #587 previously failed here with:
// "Transaction simulation failed: Error processing Instruction 2: Unsupported program id".
let tx = {
let payer_dalek =
ed25519_dalek::Keypair::from_bytes(&payer.to_bytes()).expect("invalid dalek keypair");
let message = b"surfpool ed25519 precompile integration test";
let signature = payer_dalek.sign(message);
let ed25519_ix = new_ed25519_instruction_with_signature(
message,
&signature.to_bytes(),
payer.pubkey().as_array(),
);
let compute_budget_ixs = [
ComputeBudgetInstruction::set_compute_unit_limit(100_000),
ComputeBudgetInstruction::set_compute_unit_price(1),
];
let tx_message = Message::new_with_blockhash(
&[
compute_budget_ixs[0].clone(),
compute_budget_ixs[1].clone(),
ed25519_ix,
],
Some(&payer.pubkey()),
&recent_blockhash,
);

VersionedTransaction::try_new(VersionedMessage::Legacy(tx_message), &[payer])
.expect("Failed to create ed25519 transaction")
};
let svm_locker = SurfnetSvmLocker::new(svm_instance);
let simulation_res = svm_locker.simulate_transaction(tx.clone(), true);

assert!(
simulation_res.is_ok(),
"Expected ed25519 transaction simulation to succeed"
);

let (status_tx, status_rx) = unbounded();
let _ = svm_locker
.process_transaction(&None, tx, status_tx, true, true)
.await
.unwrap();

// Wait for transaction processing
match status_rx.recv() {
Ok(TransactionStatusEvent::Success(_)) => {
println!("Transaction processed successfully");
}
Ok(TransactionStatusEvent::SimulationFailure((error, _))) => {
panic!("Transaction simulation failed: {:?}", error);
}
Ok(TransactionStatusEvent::ExecutionFailure((error, _))) => {
panic!("Transaction execution failed: {:?}", error);
}
Ok(TransactionStatusEvent::VerificationFailure(error)) => {
panic!("Transaction verification failed: {}", error);
}
Err(e) => {
panic!("Failed to receive transaction status: {:?}", e);
}
}
}

#[cfg_attr(feature = "ignore_tests_ci", ignore = "flaky CI tests")]
#[test_case(TestType::sqlite(); "with on-disk sqlite db")]
#[test_case(TestType::in_memory(); "with in-memory sqlite db")]
Expand Down
Loading