diff --git a/.gitignore b/.gitignore index d01bd1a..8f16da7 100644 --- a/.gitignore +++ b/.gitignore @@ -18,4 +18,6 @@ Cargo.lock # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore # and can be added to the global gitignore or merged into this file. For a more nuclear # option (not recommended) you can uncomment the following to ignore the entire idea folder. -#.idea/ \ No newline at end of file +#.idea/ +.DS_Store +rollup_client/.json \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..a0a6e0a --- /dev/null +++ b/README.md @@ -0,0 +1,74 @@ +## SVM rollup +As part of the Turbin3 SVM cohort, our team built our own SVM rollup. +It fetches transactions, delegates funds via a Solana program, sends transactions to a sequencer, and processes them by locking accounts, loads and executes transactions, updating local state, and bundling similar transactions. Once the batch threshold is met (10 transactions), the rollup bundles them into one and settles the changes back on-chain. + +## Overview +A rollup is a Layer-2 scaling solution that processes and bundles transactions off-chain before settling them on the main chain. This reduces congestion and fees while keeping the security of the underlying blockchain. + +## Why would Solana need a rollup? +Rollups can enhance Solana by: +- **Increasing Throughput:** Offload transactions from the main chain. +- **Lower fees:** Batch transactions to lower costs. +- **Flexibility:** Allow for customized transaction processing without changing Solana’s core. + +## Currently building: +- **Support for SPL tokens** +- **Support for all types of transactions** + +## Flow +1. **Fetches Transactions** +2. **Delegates Funds:** Solana program +3. **Sends to the Sequencer** +4. **Locks accounts, Loads, and executes Transactions** +5. **Updates Local State** +6. **Bundles Similar Transactions:** Groups similar transactions into one. +7. **Batch(10) Bundling:** After 10 transactions, bundles them into a single transaction. +8. **Settles Changes to the Chain:** Commits batched changes back to Solana. + +## Module Overview + +**rollup_client.rs** +CLI that interacts with the rollup server. + - Creating and signing a Solana transfer transaction. + - Sends the transaction to the rollup via `/submit_transaction`. + - Hashes the transaction, via `/get_transaction` for status. + +**frontend.rs** + Actix Web + - A submission endpoint (`/submit_transaction`) that accepts and forwards transactions to the sequencer. + - A query endpoint (`/get_transaction`) that retrieves processed transactions from the rollup database. + - A test endpoint to verify server functionality. + +**loader.rs** + Implements the account loader for the rollup. This module: + - Fetches account data from Solana using RPC client. + - Caches account data locally. + - Implements the `TransactionProcessingCallback` required by SVM API + +**main.rs** + Entry point for the application. It: + - Sets up communication channels, using crossbeam and async channels. + - Creates threads for the sequencer and rollup database. + +**processor.rs** + Provides helper functions to configure and initialize the SVM API’s transaction batch processor. +It: + - Implements a fork graph (required by the processor). + - Sets up the processor’s program cache with built-in programs (system and BPF loader). + +**rollupdb.rs** + Implements an in-memory database that manages: + - Account states and locked accounts. + - Processed transactions. + - Communication with the frontend by retrieving transactions based on requests. + It handles locking and unlocking accounts as transactions are processed. + +**sequencer.rs** + Acts as the transaction sequencer and processor. It: + - Receives transactions via a crossbeam channel. + - Locks accounts for parallel execution. + - Uses Solana’s SVM API to process and validate transactions. + - Batches transactions (every 10 transactions) and settles when the threshold is reached. + +**settle.rs** + Contains the functionality to settle state changes on Solana. Creates and sends a proof transaction via Solana’s RPC, comitting updates to SVM. diff --git a/rollup_client/keys/receiver.json b/rollup_client/keys/receiver.json new file mode 100644 index 0000000..6a0be4c --- /dev/null +++ b/rollup_client/keys/receiver.json @@ -0,0 +1 @@ +˽SS=Gi?vsgXwOh37-UwK \ No newline at end of file diff --git a/rollup_client/keys/sender.json b/rollup_client/keys/sender.json new file mode 100644 index 0000000..2200dcd Binary files /dev/null and b/rollup_client/keys/sender.json differ diff --git a/rollup_client/mykey_1.json b/rollup_client/mykey_1.json new file mode 100644 index 0000000..3daab42 --- /dev/null +++ b/rollup_client/mykey_1.json @@ -0,0 +1 @@ +[85,229,25,2,195,236,148,200,234,152,150,129,92,99,246,193,19,117,254,211,33,38,1,243,126,75,180,191,140,17,12,140,29,55,69,35,33,158,92,165,33,100,170,203,216,15,80,53,180,40,104,87,171,226,158,188,1,197,92,158,205,28,79,20] \ No newline at end of file diff --git a/rollup_client/owner.json b/rollup_client/owner.json new file mode 100644 index 0000000..e883fb4 --- /dev/null +++ b/rollup_client/owner.json @@ -0,0 +1 @@ +[233,30,43,65,242,86,125,7,130,125,220,217,150,77,202,230,174,240,68,153,86,95,117,139,119,206,35,220,174,66,198,185,77,168,167,211,99,86,41,131,169,190,129,154,161,149,215,209,243,139,32,209,80,24,138,113,193,79,130,154,249,27,13,230] diff --git a/rollup_client/src/bin/generate_keys.rs b/rollup_client/src/bin/generate_keys.rs new file mode 100644 index 0000000..cc964f9 --- /dev/null +++ b/rollup_client/src/bin/generate_keys.rs @@ -0,0 +1,34 @@ +use solana_sdk::{ + signature::Keypair, + signer::Signer, +}; +use std::fs; +use std::path::Path; + +fn main() { + // Create keypairs + let sender = Keypair::new(); + let receiver = Keypair::new(); + + // Create keys directory if it doesn't exist + let keys_dir = Path::new("keys"); + fs::create_dir_all(keys_dir).unwrap(); + + // Save sender keypair + fs::write( + keys_dir.join("sender.json"), + sender.to_bytes().to_vec() + ).unwrap(); + + // Save receiver keypair + fs::write( + keys_dir.join("receiver.json"), + receiver.to_bytes().to_vec() + ).unwrap(); + + // Print public keys + println!("Generated keypairs:"); + println!("Sender pubkey: {} (saved to keys/sender.json)", sender.pubkey()); + println!("Receiver pubkey: {} (saved to keys/receiver.json)", receiver.pubkey()); + println!("\nPlease airdrop SOL to these addresses using the Solana CLI or devnet faucet"); +} \ No newline at end of file diff --git a/rollup_client/src/main.rs b/rollup_client/src/main.rs index cd819bc..3f43cbf 100644 --- a/rollup_client/src/main.rs +++ b/rollup_client/src/main.rs @@ -4,15 +4,15 @@ use serde::{Deserialize, Serialize}; use solana_client::nonblocking::rpc_client::{self, RpcClient}; use solana_sdk::{ instruction::Instruction, - keccak::{Hash, Hasher}, + hash::{Hash, Hasher}, native_token::LAMPORTS_PER_SOL, - signature::Signature, - signer::{self, Signer}, + signature::{Keypair, Signer}, system_instruction, system_program, transaction::Transaction, + pubkey::Pubkey, }; use solana_transaction_status::UiTransactionEncoding::{self, Binary}; -use std::{collections::HashMap, str::FromStr}; +use std::{collections::HashMap, str::FromStr, time::Duration, fs}; // use serde_json; #[derive(Serialize, Deserialize, Debug)] @@ -26,66 +26,120 @@ pub struct GetTransaction { pub get_tx: String, } -#[tokio::main] -async fn main() -> Result<()> { - let keypair = signer::keypair::read_keypair_file("/home/dev/.solana/testkey.json").unwrap(); - let keypair2 = signer::keypair::read_keypair_file("/home/dev/.solana/mykey_1.json").unwrap(); - let rpc_client = RpcClient::new("https://api.devnet.solana.com".into()); - - let ix = - system_instruction::transfer(&keypair2.pubkey(), &keypair.pubkey(), 1 * LAMPORTS_PER_SOL); - let tx = Transaction::new_signed_with_payer( - &[ix], - Some(&keypair2.pubkey()), - &[&keypair2], - rpc_client.get_latest_blockhash().await.unwrap(), - ); +#[derive(Serialize, Deserialize, Debug)] +#[serde(tag = "status", content = "data")] +pub enum TransactionResponse { + Success { message: String }, + Error { message: String }, +} - // let sig = Signature::from_str("3ENa2e9TG6stDNkUZkRcC2Gf5saNMUFhpptQiNg56nGJ9eRBgSJpZBi7WLP5ev7aggG1JAXQWzBk8Xfkjcx1YCM2").unwrap(); - // let tx = rpc_client.get_transaction(&sig, UiTransactionEncoding::Binary).await.unwrap(); +#[tokio::main] +async fn main() -> Result<(), Box> { + // Load existing keypairs from files + let sender = Keypair::from_bytes(&fs::read("keys/sender.json")?)?; + let receiver = Keypair::from_bytes(&fs::read("keys/receiver.json")?)?; + println!("rec: {:?}", receiver.pubkey()); + + // Connect to devnet + let rpc_client = RpcClient::new("https://api.devnet.solana.com".to_string()); + + // Print initial balances + let sender_balance = rpc_client.get_balance(&sender.pubkey()).await?; + let receiver_balance = rpc_client.get_balance(&receiver.pubkey()).await?; + println!("Initial Sender {} balance: {} SOL", sender.pubkey(), sender_balance as f64 / 1_000_000_000.0); + println!("Initial Receiver {} balance: {} SOL", receiver.pubkey(), receiver_balance as f64 / 1_000_000_000.0); + + // Initialize delegation service for sender + println!("\nInitializing delegation service for sender..."); let client = reqwest::Client::new(); - - // let tx_encoded: Transaction = tx.try_into().unwrap(); - - let test_response = client - .get("http://127.0.0.1:8080") - .send() - .await? - .json::>() - .await?; - - println!("{test_response:#?}"); - - let rtx = RollupTransaction { - sender: "Me".into(), - sol_transaction: tx, - }; - - // let serialized_rollup_transaction = serde_json::to_string(&rtx)?; - - let submit_transaction = client - .post("http://127.0.0.1:8080/submit_transaction") - .json(&rtx) + let response = client + .post("http://127.0.0.1:8080/init_delegation_service") + .body(sender.to_bytes().to_vec()) .send() .await?; - // .json() - // .await?; + println!("Sender delegation service init response: {:?}", response.text().await?); - println!("{submit_transaction:#?}"); - let mut hasher = Hasher::default(); - hasher.hash(bincode::serialize(&rtx.sol_transaction).unwrap().as_slice()); + // Wait for initialization + tokio::time::sleep(Duration::from_secs(2)).await; - println!("{:#?}", hasher.clone().result()); - - let tx_resp = client - .post("http://127.0.0.1:8080/get_transaction") - .json(&HashMap::from([("get_tx", hasher.result().to_string())])) + // Initialize delegation service for receiver + println!("\nInitializing delegation service for receiver..."); + let response = client + .post("http://127.0.0.1:8080/add_delegation_signer") + .body(receiver.to_bytes().to_vec()) .send() - .await? - .json::>() .await?; - - println!("{tx_resp:#?}"); - + println!("Receiver delegation service init response: {:?}", response.text().await?); + + // Wait for initialization + tokio::time::sleep(Duration::from_secs(2)).await; + + // Create test transactions + let amounts = vec![5, -3, 9, -10, 1, -10, 4, -3, 9, -6]; + let mut txs = Vec::new(); + + for amount in amounts { + let (from, to, lamports) = if amount > 0 { + (&sender, &receiver, amount as u64) + } else { + (&receiver, &sender, (-amount) as u64) + }; + + let ix = system_instruction::transfer( + &from.pubkey(), + &to.pubkey(), + lamports * (LAMPORTS_PER_SOL / 10) + ); + + let tx = Transaction::new_signed_with_payer( + &[ix], + Some(&from.pubkey()), + &[from], + rpc_client.get_latest_blockhash().await?, + ); + + txs.push(tx); + } + + // Submit transactions + println!("\nSubmitting transactions..."); + for (i, tx) in txs.into_iter().enumerate() { + let rtx = RollupTransaction { + sender: sender.pubkey().to_string(), + sol_transaction: tx, + }; + + let response = client + .post("http://127.0.0.1:8080/submit_transaction") + .json(&rtx) + .send() + .await?; + + println!("Transaction {} response: {:?}", i + 1, response.text().await?); + tokio::time::sleep(Duration::from_secs(1)).await; + } + + // Print final balances + let sender_balance = rpc_client.get_balance(&sender.pubkey()).await?; + let receiver_balance = rpc_client.get_balance(&receiver.pubkey()).await?; + println!("\nFinal Sender {} balance: {} SOL", sender.pubkey(), sender_balance as f64 / 1_000_000_000.0); + println!("Final Receiver {} balance: {} SOL", receiver.pubkey(), receiver_balance as f64 / 1_000_000_000.0); + Ok(()) } + +async fn gen_transfer_tx(path1: String, path2: String, amount: u64) -> Transaction { + println!("Amount: {amount}"); + let keypair = signer::keypair::read_keypair_file(path1.to_string()).unwrap(); + let keypair2 = signer::keypair::read_keypair_file(path2.to_string()).unwrap(); + let rpc_client = RpcClient::new("https://api.devnet.solana.com".into()); + + let ix = + system_instruction::transfer(&keypair2.pubkey(), &keypair.pubkey(), amount * (LAMPORTS_PER_SOL / 10)); + Transaction::new_signed_with_payer( + &[ix], + Some(&keypair2.pubkey()), + &[&keypair2], + rpc_client.get_latest_blockhash().await.unwrap(), + ) +} \ No newline at end of file diff --git a/rollup_client/testkey.json b/rollup_client/testkey.json new file mode 100644 index 0000000..3738a4f --- /dev/null +++ b/rollup_client/testkey.json @@ -0,0 +1 @@ +[104,117,96,190,131,134,101,169,18,12,134,152,145,31,0,55,246,209,135,166,162,8,183,14,233,140,170,45,220,131,115,20,87,120,117,245,178,151,16,46,66,46,115,100,12,254,35,169,37,124,73,61,124,55,178,158,70,21,128,162,115,99,69,220] \ No newline at end of file diff --git a/rollup_core/Cargo.toml b/rollup_core/Cargo.toml index 53a9440..2ca3362 100644 --- a/rollup_core/Cargo.toml +++ b/rollup_core/Cargo.toml @@ -15,8 +15,18 @@ solana-sdk = "2.0.7" solana-client = "2.0.7" solana-compute-budget = "2.0.7" solana-bpf-loader-program = "2.0.7" +solana-timings = "2.0.7" +solana-system-program = "2.0.7" env_logger = "0.11.5" log = "0.4.22" anyhow = "1.0.86" crossbeam = "0.8.4" -async-channel = "2.3.1" \ No newline at end of file +async-channel = "2.3.1" +bincode = "1.3.3" +<<<<<<< HEAD +borsh = "0.10" +sha1 = "=0.10.0" +digest = "=0.10.7" +sha2 = "0.10" +======= +>>>>>>> main diff --git a/rollup_core/src/bundler.rs b/rollup_core/src/bundler.rs new file mode 100644 index 0000000..e46239a --- /dev/null +++ b/rollup_core/src/bundler.rs @@ -0,0 +1,112 @@ +use std::collections::HashMap; + +use solana_sdk::{instruction::{CompiledInstruction, Instruction}, pubkey::Pubkey, system_instruction::{self, SystemInstruction}, system_program, transaction::Transaction}; +use bincode::deserialize; + +pub fn get_transaction_instructions(tx: &Transaction) -> Vec{ + tx.message.instructions.clone() +} + +pub fn is_transfer_ix(cix: &CompiledInstruction, account_keys: &[Pubkey]) -> bool { + if cix.program_id_index as usize >= account_keys.len(){ + return false; + } + let program_id = account_keys[cix.program_id_index as usize]; + if program_id != system_program::ID{ + return false + } + + matches!( + deserialize::(&cix.data), + Ok(SystemInstruction::Transfer { .. }) + ) +} + +#[derive(PartialEq, Eq, Hash, Clone, Copy)] +struct TBundlerKey { + keys: [Pubkey; 2], +} + +pub struct TransferBundler { + transfers: HashMap, +} + +impl TransferBundler { + pub fn new() -> Self { + Self { + transfers: HashMap::new() + } + } + + pub fn parse_compiled_instruction(ix: &CompiledInstruction, account_keys: &[Pubkey]) -> Option<(Pubkey, Pubkey, i128)>{ + //Ensure the instruction is from System Program (where transfer is from) + if ix.program_id_index as usize >= account_keys.len() || account_keys[ix.program_id_index as usize] != system_program::ID{ + return None; + } + //Ensure we have at least 2 accounts for transfer and enough data for SOL amount + if ix.accounts.len() < 2 || ix.data.len() < 8 { + return None; + } + + //Get accounts involved in transfer and amount to be transferred + let from = account_keys[ix.accounts[0] as usize]; + let to = account_keys[ix.accounts[1] as usize]; + + log::info!("FROM: {:?}", from.to_string()); + log::info!("TO: {:?}", to.to_string()); + log::info!("IX DATA: {:?}", ix.data); + + let amount = u64::from_le_bytes(ix.data[4..12].try_into().ok()?); + Some((from, to, amount as i128)) + } + + pub fn parse_instruction(ix: &Instruction) -> Option<(Pubkey, Pubkey, i128)>{ + //Enusre ix is owned by system program + if ix.program_id != system_program::ID{ + return None; + } + + //Ensure we have enough accounts + if ix.accounts.len() < 2{ + return None; + } + let from = ix.accounts[0].pubkey; + let to = ix.accounts[1].pubkey; + let amount = u64::from_le_bytes(ix.data[4..].try_into().ok()?); + + log::info!("FROM: {:?}", from.to_string()); + log::info!("TO: {:?}", to.to_string()); + log::info!("AMOUNT: {amount}"); + log::info!("IX DATA: {:?}", ix.data); + + + Some((from, to, amount as i128)) + } + + //Parses transactions and add transfer ixs to TransferBundler + pub fn bundle(&mut self, transaction: Transaction){ + let ixs = get_transaction_instructions(&transaction); + let account_keys: &[Pubkey] = &transaction.message.account_keys; + for ix in ixs { + if is_transfer_ix(&ix, account_keys){ + let (from, to, amount) = Self::parse_compiled_instruction(&ix, account_keys).unwrap(); + let mut keys = [from, to]; + keys.sort(); + + *self.transfers.entry(TBundlerKey {keys}).or_default() += if from == keys[0] {amount} else {-amount}; + } + } + } + + pub fn generate_final(self) -> Vec { + self.transfers.into_iter().filter_map(|(map_key, val)| { + if val < 0 { + Some(system_instruction::transfer(&map_key.keys[1], &map_key.keys[0], val.unsigned_abs() as u64)) + } else if val > 0 { + Some(system_instruction::transfer(&map_key.keys[0], &map_key.keys[1], val as u64)) + } else { + None + } + }).collect() + } +} \ No newline at end of file diff --git a/rollup_core/src/delegation.rs b/rollup_core/src/delegation.rs new file mode 100644 index 0000000..46771f6 --- /dev/null +++ b/rollup_core/src/delegation.rs @@ -0,0 +1,113 @@ +use sha2::{Sha256, Digest}; +use solana_sdk::{ + pubkey::Pubkey, + instruction::{AccountMeta, Instruction}, + system_program, +}; +use borsh::{BorshSerialize, BorshDeserialize}; + + +#[derive(BorshSerialize, BorshDeserialize)] +pub struct DelegatedAccount { + pub owner: Pubkey, + pub delegated_amount: u64, + pub last_deposit_time: i64, + pub bump: u8, +} + + +#[derive(BorshSerialize)] +pub struct InitializeDelegateArgs { + pub amount: u64, +} + +pub fn get_delegation_program_id() -> Pubkey { + "5MSF4TiUfD7dVm7P1ahPYJfEBLCUQn7hEPYXYHocVwzh" + .parse() + .unwrap() +} + +pub fn find_delegation_pda(owner: &Pubkey) -> (Pubkey, u8) { + Pubkey::find_program_address( + &[b"delegate", owner.as_ref()], + &get_delegation_program_id() + ) +} + +pub fn create_delegation_instruction(owner: &Pubkey, amount: u64) -> Instruction { + let (pda, _) = find_delegation_pda(owner); + + let discriminator = { + let mut hasher = Sha256::new(); + hasher.update(b"global:initialize_delegate"); + let result = hasher.finalize(); + let mut disc = [0u8; 8]; + disc.copy_from_slice(&result[..8]); + disc + }; + + let mut ix_data = discriminator.to_vec(); + ix_data.extend(InitializeDelegateArgs { amount }.try_to_vec().unwrap()); + + Instruction { + program_id: get_delegation_program_id(), + accounts: vec![ + AccountMeta::new(*owner, true), // Owner must be signer + AccountMeta::new(pda, false), // PDA account + AccountMeta::new_readonly(system_program::id(), false), // System program + ], + data: ix_data, + } +} + +pub fn create_topup_instruction(owner: &Pubkey, amount: u64) -> Instruction { + let (pda, _) = find_delegation_pda(owner); + + // Calculate Anchor discriminator for "top_up" + let discriminator = { + let mut hasher = Sha256::new(); + hasher.update(b"global:top_up"); + let result = hasher.finalize(); + let mut disc = [0u8; 8]; + disc.copy_from_slice(&result[..8]); + disc + }; + + let mut ix_data = discriminator.to_vec(); + ix_data.extend(amount.to_le_bytes()); + + Instruction { + program_id: get_delegation_program_id(), + accounts: vec![ + AccountMeta::new(*owner, true), // Owner must be signer + AccountMeta::new(pda, false), // PDA to be topped up + AccountMeta::new_readonly(system_program::id(), false), // System program + ], + data: ix_data, + } +} + +pub fn create_withdrawal_instruction(pda: &Pubkey, owner: &Pubkey, amount: u64) -> Instruction { + // Calculate Anchor discriminator for "withdraw" + let discriminator = { + let mut hasher = Sha256::new(); + hasher.update(b"global:withdraw"); + let result = hasher.finalize(); + let mut disc = [0u8; 8]; + disc.copy_from_slice(&result[..8]); + disc + }; + + let mut ix_data = discriminator.to_vec(); + ix_data.extend(amount.to_le_bytes()); + + Instruction { + program_id: get_delegation_program_id(), + accounts: vec![ + AccountMeta::new(*owner, true), // Owner must be a signer + AccountMeta::new(*pda, false), // PDA account + AccountMeta::new_readonly(system_program::id(), false), // System program + ], + data: ix_data, + } +} \ No newline at end of file diff --git a/rollup_core/src/delegation_service.rs b/rollup_core/src/delegation_service.rs new file mode 100644 index 0000000..6c05a91 --- /dev/null +++ b/rollup_core/src/delegation_service.rs @@ -0,0 +1,120 @@ +use { + crate::delegation::{create_delegation_instruction, create_withdrawal_instruction, find_delegation_pda, DelegatedAccount, create_topup_instruction}, anyhow::{anyhow, Result}, borsh::BorshDeserialize, log, solana_client::rpc_client::RpcClient, solana_sdk::{ + account::{AccountSharedData, ReadableAccount}, message::Message, pubkey::Pubkey, signature::Keypair, signer::Signer, transaction::Transaction + }, std::collections::HashMap +}; + +pub struct DelegationService { + rpc_client: RpcClient, + pda_cache: HashMap, + signers: HashMap, // Store multiple signers +} + +impl DelegationService { + pub fn new(rpc_url: &str, initial_signer: Keypair) -> Self { + let mut signers = HashMap::new(); + signers.insert(initial_signer.pubkey(), initial_signer); + + Self { + rpc_client: RpcClient::new(rpc_url.to_string()), + pda_cache: HashMap::new(), + signers, + } + } + + pub fn add_signer(&mut self, signer: Keypair) { + self.signers.insert(signer.pubkey(), signer); + } + + pub fn get_or_fetch_pda(&mut self, user: &Pubkey) -> Result> { + let (pda, _) = find_delegation_pda(user); + + // Always try fetching from chain first to be sure + match self.rpc_client.get_account(&pda) { + Ok(account) => { + log::info!( + "Found account for PDA: {}, data length: {}, owner: {}", + pda, + account.data().len(), + account.owner() + ); + // Skip the 8-byte discriminator when deserializing + if account.data().len() > 8 { + if let Ok(delegation) = DelegatedAccount::try_from_slice(&account.data()[8..]) { + self.pda_cache.insert(pda, account.into()); + Ok(Some((pda, delegation))) + } else { + log::warn!( + "Account exists but couldn't deserialize data for PDA: {}. Data: {:?}", + pda, + account.data() + ); + Ok(None) + } + } else { + log::warn!("Account data too short for PDA: {}", pda); + Ok(None) + } + } + Err(e) => { + log::info!("No account found for PDA: {} (Error: {})", pda, e); + self.pda_cache.remove(&pda); + Ok(None) + } + } + } + + pub fn create_delegation_transaction( + &mut self, + user: &Pubkey, + amount: u64, + ) -> Result { + // First check PDA existence + let has_existing = self.get_or_fetch_pda(user)?.is_some(); + log::info!("create::: signers are here: {:?}", self.signers); + // Then get signer after PDA check + let signer = self.signers.get(user) + .ok_or_else(|| anyhow!("No delegation signer found for {}", user))?; + + let instruction = if has_existing { + create_topup_instruction(user, amount) + } else { + create_delegation_instruction(user, amount) + }; + + let recent_blockhash = self.rpc_client.get_latest_blockhash()?; + let message = Message::new_with_blockhash( + &[instruction], + Some(&signer.pubkey()), + &recent_blockhash + ); + + let mut tx = Transaction::new_unsigned(message); + tx.try_sign(&[signer], recent_blockhash)?; + + Ok(tx) + } + + pub fn update_pda_state(&mut self, pda: Pubkey, account: AccountSharedData) { + self.pda_cache.insert(pda, account); + } + + pub fn create_withdrawal_transaction(&mut self, pda: &Pubkey, owner: &Pubkey, amount: u64) -> Result { + let instruction = create_withdrawal_instruction(pda, owner, amount); + log::info!("signers are here: {:?}", self.signers); + // Get the signer for the owner + let signer = self.signers.get(owner) + .ok_or_else(|| anyhow!("No delegation signer found for {}", owner))?; + + let recent_blockhash = self.rpc_client.get_latest_blockhash()?; + let message = Message::new(&[instruction], Some(&signer.pubkey())); + + let mut tx = Transaction::new_unsigned(message); + tx.sign(&[signer], recent_blockhash); + + Ok(tx) + } + pub fn get_keypair(&self, user: &Pubkey) -> Option<&Keypair> { + self.signers.get(user) + } +} diff --git a/rollup_core/src/errors.rs b/rollup_core/src/errors.rs new file mode 100644 index 0000000..56f9752 --- /dev/null +++ b/rollup_core/src/errors.rs @@ -0,0 +1,3 @@ +pub enum RollupErrors { + TransactionFailedOnlyFeesWereCollected, +} diff --git a/rollup_core/src/frontend.rs b/rollup_core/src/frontend.rs index c1d0d8d..cb3720d 100644 --- a/rollup_core/src/frontend.rs +++ b/rollup_core/src/frontend.rs @@ -1,15 +1,15 @@ use std::{ collections::HashMap, sync::{Arc, Mutex}, + cell::RefCell, }; use actix_web::{error, web, HttpResponse}; use async_channel::{Receiver, Send, Sender}; use crossbeam::channel::{Sender as CBSender, Receiver as CBReceiver}; use serde::{Deserialize, Serialize}; -use solana_sdk::keccak::Hash; +use solana_sdk::hash::Hash; // keccak::Hash use solana_sdk::transaction::Transaction; - use crate::rollupdb::RollupDBMessage; // message format to send found transaction from db to frontend @@ -28,55 +28,66 @@ pub struct GetTransaction { // message format used to receive transactions from clients #[derive(Serialize, Deserialize, Debug)] pub struct RollupTransaction { - sender: String, - sol_transaction: Transaction, + pub sender: String, + pub sol_transaction: Transaction, +} + +#[derive(Serialize, Deserialize, Debug)] +#[serde(tag = "status", content = "data")] +pub enum TransactionResponse { + Success { message: String }, + Error { message: String }, } + pub async fn submit_transaction( body: web::Json, sequencer_sender: web::Data>, - // rollupdb_sender: web::Data>, ) -> actix_web::Result { - // Validate transaction structure with serialization in function signature - log::info!("Submitted transaction"); - log::info!("{body:?}"); + // Validate transaction structure with serialization in function signature + log::info!("Submitted transaction"); + log::info!("{body:?}"); - // Send transaction to sequencer - sequencer_sender - .send(body.sol_transaction.clone()) - - .unwrap(); + // Send transaction to sequencer + sequencer_sender.send(body.sol_transaction.clone()) + .map_err(|e| actix_web::error::ErrorInternalServerError(e.to_string()))?; - // Return response - Ok(HttpResponse::Ok().json(HashMap::from([("Transaction status", "Submitted")]))) + Ok(HttpResponse::Ok().json(TransactionResponse::Success { + message: "Transaction submitted".to_string() + })) } pub async fn get_transaction( body: web::Json, - sequencer_sender: web::Data>, + // sequencer_sender: web::Data>, rollupdb_sender: web::Data>, frontend_receiver: web::Data>, ) -> actix_web::Result { // Validate transaction structure with serialization in function signature + println!("Getting tx..."); log::info!("Requested transaction"); log::info!("{body:?}"); rollupdb_sender .send(RollupDBMessage { lock_accounts: None, + add_new_data: None, add_processed_transaction: None, frontend_get_tx: Some(Hash::new(body.get_tx.as_bytes())), add_settle_proof: None, + get_account: None, + bundle_tx: false }) .await .unwrap(); if let Ok(frontend_message) = frontend_receiver.recv().await { - return Ok(HttpResponse::Ok().json(RollupTransaction { - sender: "Rollup RPC".into(), - sol_transaction: frontend_message.transaction.unwrap(), - })); - // Ok(HttpResponse::Ok().json(HashMap::from([("Transaction status", "requested")]))) + // return Ok(HttpResponse::Ok().json(RollupTransaction { + // sender: "Rollup RPC".into(), + // sol_transaction: frontend_message.transaction.unwrap(), + // })); + log::info!("Requested TX:\n {:?}", frontend_message.transaction.unwrap()); + return Ok(HttpResponse::Ok().json(HashMap::from([("Requested transaction status", "gotten successfully")]))); } Ok(HttpResponse::Ok().json(HashMap::from([("Transaction status", "requested")]))) diff --git a/rollup_core/src/loader.rs b/rollup_core/src/loader.rs new file mode 100644 index 0000000..19b6848 --- /dev/null +++ b/rollup_core/src/loader.rs @@ -0,0 +1,124 @@ +//! PayTube's "account loader" component, which provides the SVM API with the +//! ability to load accounts for PayTube channels. +//! +//! The account loader is a simple example of an RPC client that can first load +//! an account from the base chain, then cache it locally within the protocol +//! for the duration of the channel. + +use { + log::info, solana_client::rpc_client::RpcClient, solana_sdk::{ + account::{AccountSharedData, ReadableAccount}, + pubkey::Pubkey, + }, solana_svm::transaction_processing_callback::TransactionProcessingCallback, std::{collections::HashMap, sync::RwLock}, + std::ops::IndexMut, +}; + +// impl IndexMut for HashMap { +// fn index_mut(&mut self, index: Idx) -> &mut Self::Output { + +// } +// } +/// An account loading mechanism to hoist accounts from the base chain up to +/// an active PayTube channel. +/// +/// Employs a simple cache mechanism to ensure accounts are only loaded once. +pub struct RollupAccountLoader<'a> { + pub cache: RwLock>, + pub rpc_client: &'a RpcClient, +} + +impl<'a> RollupAccountLoader<'a> { + pub fn new(rpc_client: &'a RpcClient) -> Self { + Self { + cache: RwLock::new(HashMap::new()), + rpc_client, + } + } + + pub fn add_account(&mut self, pubkey: Pubkey, modified_or_new_account: AccountSharedData) { + let mut map = self.cache.write().unwrap(); + let res = map.contains_key(&pubkey); + if res == false { + map.insert(pubkey, modified_or_new_account); + log::info!("newone: {:?}", map); + } else { // PROBLEM HERE!!! SOMEHOW DON'T FIND THIS ELSE + + map.insert(pubkey, modified_or_new_account); + // map.entry(pubkey) + // .and_modify(|account| { + // log::info!("Updating existing account."); + // *account = modified_or_new_account.clone(); // Replace existing account + // }); + log::info!("oldone: {:?}", map); + + } + // let mut cache = self.cache.write().unwrap(); // Get a write lock once + + // if let Some(account) = cache.get_mut(&pubkey) { + // log::info!("Updating existing account"); + // *account = modified_or_new_account; // Overwrite existing entry + // } else { + // log::info!("Adding new account"); + // cache.insert(pubkey, modified_or_new_account); // Insert new entry + // } + + + // let mut cache = self.cache.write().unwrap(); + + + // cache.entry(pubkey) + // .and_modify(|account| { + // log::info!("Updating existing account."); + // *account = modified_or_new_account.clone(); // Replace existing account + // }) + // .or_insert_with(|| { + // log::info!("Inserting new account."); + // modified_or_new_account + // }); + // if let Some(account) = self.cache.read().unwrap().get(&pubkey) { + // log::info!("it is alright"); + // cache.entry(pubkey).and_modify( + // // |account| { + // // let data = account.data(); + // // account.set_data_from_slice(&data) + // // } + // |account| *account = modified_or_new_account.clone() + // ); + // } + // else { + // self.cache.write().unwrap().insert(pubkey, modified_or_new_account); + // log::info!("fucking problem"); + // } + // log::info!("cache: {:?}", self.cache.read().unwrap()); + // log::info!("entryyy: {:?}", self.cache.read().unwrap().get(&pubkey)); + + } +} + +/// Implementation of the SVM API's `TransactionProcessingCallback` interface. +/// +/// The SVM API requires this plugin be provided to provide the SVM with the +/// ability to load accounts. +/// +/// In the Agave validator, this implementation is Bank, powered by AccountsDB. +impl TransactionProcessingCallback for RollupAccountLoader<'_> { + fn get_account_shared_data(&self, pubkey: &Pubkey) -> Option { + if let Some(account) = self.cache.read().unwrap().get(pubkey) { + log::info!("the account was loaded from the database"); + return Some(account.clone()); + } + else { + None + } + + // let account: AccountSharedData = self.rpc_client.get_account(pubkey).ok()?.into(); + // self.cache.write().unwrap().insert(*pubkey, account.clone()); + // log::info!("the account was loaded from the rpcclient"); + // Some(account) + } + + fn account_matches_owners(&self, account: &Pubkey, owners: &[Pubkey]) -> Option { + self.get_account_shared_data(account) + .and_then(|account| owners.iter().position(|key| account.owner().eq(key))) + } +} diff --git a/rollup_core/src/main.rs b/rollup_core/src/main.rs index 8089972..50765d5 100644 --- a/rollup_core/src/main.rs +++ b/rollup_core/src/main.rs @@ -1,68 +1,90 @@ use std::thread; +use std::sync::{Arc, RwLock}; +use crate::delegation_service::DelegationService; -use actix_web::{web, App, HttpServer}; +use actix_web::{web, App, HttpResponse, HttpServer}; use async_channel; -use frontend::FrontendMessage; +use frontend::{FrontendMessage, RollupTransaction, TransactionResponse}; use rollupdb::{RollupDB, RollupDBMessage}; -use solana_sdk::transaction::Transaction; +use solana_sdk::pubkey::Pubkey; +use solana_sdk::signature::Keypair; +use solana_sdk::{account::AccountSharedData, transaction::Transaction}; use tokio::runtime::Builder; +use tokio::sync::oneshot; use crossbeam; mod frontend; mod rollupdb; mod sequencer; mod settle; +mod processor; +mod loader; +mod bundler; +mod errors; +mod delegation; +mod delegation_service; // #[actix_web::main] -fn main() { +// #[tokio::main] +fn main() { // async env_logger::init_from_env(env_logger::Env::new().default_filter_or("debug")); log::info!("starting HTTP server at http://localhost:8080"); - - let (sequencer_sender, sequencer_receiver) = crossbeam::channel::unbounded::(); + let (sequencer_sender, sequencer_receiver) = + crossbeam::channel::unbounded::(); let (rollupdb_sender, rollupdb_receiver) = crossbeam::channel::unbounded::(); + - // let (sequencer_sender, sequencer_receiver) = async_channel::bounded::(100); // Channel for communication between frontend and sequencer - // let (rollupdb_sender, rollupdb_receiver) = async_channel::unbounded::(); // Channel for communication between sequencer and accountsdb let (frontend_sender, frontend_receiver) = async_channel::unbounded::(); // Channel for communication between data availability layer and frontend - // std::thread::spawn(sequencer::run(sequencer_receiver, rollupdb_sender.clone())); - - // let rt = Builder::new() - // .threaded_scheduler() - // .enable_all() - // .build() - // .unwrap(); + pub type PubkeyAccountSharedData = Option>; + let (account_sender, account_receiver) = async_channel::unbounded::(); + let (sender_locked_account, receiver_locked_account) = async_channel::unbounded::(); + let db_sender2 = rollupdb_sender.clone(); let fe_2 = frontend_sender.clone(); + + + let signer = Keypair::new(); // Temporary keypair, will be replaced when client connects + + let (delegation_keypair_sender, delegation_keypair_receiver) = async_channel::unbounded::>(); + let delegation_service = Arc::new(RwLock::new( + DelegationService::new("https://api.devnet.solana.com", signer) + )); + + let delegation_service_clone = delegation_service.clone(); + let delegation_service_clone_1 = delegation_service.clone(); + let asdserver_thread = thread::spawn(|| { let rt = Builder::new_multi_thread() .worker_threads(4) + .enable_time() .build() .unwrap(); + rt.spawn(async { + sequencer::run( + sequencer_receiver, + db_sender2, + account_receiver, + receiver_locked_account, + delegation_service_clone, + ).await.unwrap() + }); - - rt.block_on(async {sequencer::run(sequencer_receiver, db_sender2).unwrap()}); - rt.spawn(RollupDB::run(rollupdb_receiver, fe_2)); + + rt.block_on(RollupDB::run(rollupdb_receiver, fe_2, account_sender, sender_locked_account, delegation_service_clone_1)); }); - // Create sequencer task - // tokio::spawn(sequencer::run(sequencer_receiver, rollupdb_sender.clone())); - // tokio::task::spawn_blocking(|| sequencer::run(sequencer_receiver, rollupdb_sender.clone()) ).await.unwrap(); - // tokio::task::block_in_place(|| sequencer::run(sequencer_receiver, rollupdb_sender.clone()) ).await.unwrap(); - - // Create rollup db task (accounts + transactions) - // tokio::spawn(RollupDB::run(rollupdb_receiver, frontend_sender.clone())); - - // let frontend_receiver_mutex = Arc::new(Mutex::new(frontend_receiver)); + // Spawn the Actix Web server in a separate thread - let server_thread = thread::spawn(|| { + let server_thread = thread::spawn( || { // Create a separate Tokio runtime for Actix Web let rt2 = Builder::new_multi_thread() .worker_threads(4) .enable_io() + .enable_time() .build() .unwrap(); @@ -82,10 +104,33 @@ fn main() { "/submit_transaction", web::post().to(frontend::submit_transaction), ) - // .service( - // web::resource("/submit_transaction") - // .route(web::post().to(frontend::submit_transaction)), - // ) + .route( + "/init_delegation_service", + { + let delegation_service = delegation_service.clone(); + web::post().to(move |body: web::Bytes| { + let keypair = Keypair::from_bytes(&body).unwrap(); + *delegation_service.write().unwrap() = + DelegationService::new("https://api.devnet.solana.com", keypair); + log::info!("Delegation service initialized___"); + // log::info!("{:?}", ) + HttpResponse::Ok() + }) + }, + ) + .route( + "/add_delegation_signer", + { + let delegation_service = delegation_service.clone(); + web::post().to(move |body: web::Bytes| { + let keypair = Keypair::from_bytes(&body).unwrap(); + delegation_service.write().unwrap().add_signer(keypair); + log::info!("Added signer to delegation service"); + HttpResponse::Ok() + }) + }, + ) + }) .worker_max_blocking_threads(2) .bind("127.0.0.1:8080") @@ -93,13 +138,8 @@ fn main() { .run() .await .unwrap(); - // tokio::time::sleep(std::time::Duration::from_secs(20)).await; }); }); server_thread.join().unwrap(); - // rt.shutdown_timeout(std::time::Duration::from_secs(20)); - - - // Ok(()) } diff --git a/rollup_core/src/processor.rs b/rollup_core/src/processor.rs new file mode 100644 index 0000000..e5beeb7 --- /dev/null +++ b/rollup_core/src/processor.rs @@ -0,0 +1,84 @@ +//! A helper to initialize Solana SVM API's `TransactionBatchProcessor`. + +use { + solana_bpf_loader_program::syscalls::create_program_runtime_environment_v1, + solana_compute_budget::compute_budget::ComputeBudget, + solana_program_runtime::loaded_programs::{BlockRelation, ForkGraph, ProgramCacheEntry}, + solana_sdk::{clock::Slot, feature_set::FeatureSet, pubkey::Pubkey, transaction}, + solana_svm::{ + account_loader::CheckedTransactionDetails, + transaction_processing_callback::TransactionProcessingCallback, + transaction_processor::TransactionBatchProcessor, + }, + solana_system_program::system_processor, + std::sync::{Arc, RwLock}, + std::collections::HashSet, +}; + +/// In order to use the `TransactionBatchProcessor`, another trait - Solana +/// Program Runtime's `ForkGraph` - must be implemented, to tell the batch +/// processor how to work across forks. +/// +/// Since PayTube doesn't use slots or forks, this implementation is mocked. +pub(crate) struct RollupForkGraph {} + +impl ForkGraph for RollupForkGraph { + fn relationship(&self, _a: Slot, _b: Slot) -> BlockRelation { + BlockRelation::Unknown + } +} + +/// This function encapsulates some initial setup required to tweak the +/// `TransactionBatchProcessor` for use within PayTube. +/// +/// We're simply configuring the mocked fork graph on the SVM API's program +/// cache, then adding the System program to the processor's builtins. +pub(crate) fn create_transaction_batch_processor( + callbacks: &CB, + feature_set: &FeatureSet, + compute_budget: &ComputeBudget, + fork_graph: Arc>, +) -> TransactionBatchProcessor { + let processor = TransactionBatchProcessor::::new( + /* slot */ 1, + /* epoch */ 1, + Arc::downgrade(&fork_graph), + Some(Arc::new( + create_program_runtime_environment_v1(feature_set, compute_budget, false, false) + .unwrap(), + )), + None, + ); + + processor.program_cache.write().unwrap().set_fork_graph(Arc::downgrade(&fork_graph)); + + processor.prepare_program_cache_for_upcoming_feature_set( + callbacks, feature_set, compute_budget, 1, 50 + ); + + // Add system program + processor.add_builtin( + callbacks, + solana_system_program::id(), + "system_program", + ProgramCacheEntry::new_builtin(0, b"system_program".len(), system_processor::Entrypoint::vm), + ); + + processor +} + +/// This function is also a mock. In the Agave validator, the bank pre-checks +/// transactions before providing them to the SVM API. We mock this step in +/// PayTube, since we don't need to perform such pre-checks. +pub(crate) fn get_transaction_check_results( + len: usize, + lamports_per_signature: u64, +) -> Vec> { + vec![ + transaction::Result::Ok(CheckedTransactionDetails::new( + None, + lamports_per_signature + )); + len + ] +} diff --git a/rollup_core/src/rollupdb.rs b/rollup_core/src/rollupdb.rs index b5995dc..bfb3c1d 100644 --- a/rollup_core/src/rollupdb.rs +++ b/rollup_core/src/rollupdb.rs @@ -1,16 +1,18 @@ use async_channel::{Receiver, Sender}; +use log::log; use serde::{Deserialize, Serialize}; use solana_sdk::{ - account::AccountSharedData, keccak::Hash, pubkey::Pubkey, transaction::Transaction, + account::AccountSharedData, hash::Hash, pubkey::Pubkey, transaction::Transaction, // keccak::Hash -> hash::Hash }; use crossbeam::channel::{Receiver as CBReceiver, Sender as CBSender}; use std::{ collections::{HashMap, HashSet}, - default, + default, sync::{Arc, RwLock}, }; - -use crate::frontend::FrontendMessage; +use tokio::sync::oneshot; +use crate::{delegation_service::DelegationService, frontend::FrontendMessage, settle::settle_state}; +use crate::bundler::*; #[derive(Serialize, Deserialize)] pub struct RollupDBMessage { @@ -18,6 +20,11 @@ pub struct RollupDBMessage { pub add_processed_transaction: Option, pub frontend_get_tx: Option, pub add_settle_proof: Option, + pub get_account: Option, + pub add_new_data: Option>, + // pub response: Option, + //Testing purposes + pub bundle_tx: bool } #[derive(Serialize, Debug, Default)] @@ -25,27 +32,67 @@ pub struct RollupDB { accounts_db: HashMap, locked_accounts: HashMap, transactions: HashMap, + pda_mappings: HashMap, // user -> pda mapping + // async_ver_recv: Receiver> } impl RollupDB { pub async fn run( rollup_db_receiver: CBReceiver, frontend_sender: Sender, + account_sender: Sender>>, + sender_locked_accounts: Sender, + delegation_service: Arc>, ) { let mut db = RollupDB { accounts_db: HashMap::new(), locked_accounts: HashMap::new(), transactions: HashMap::new(), + pda_mappings: HashMap::new(), }; while let Ok(message) = rollup_db_receiver.recv() { + log::info!("Received RollupDBMessage"); + log::info!("Received RollupDBMessage"); if let Some(accounts_to_lock) = message.lock_accounts { + let mut information_to_send: Vec<(Pubkey, AccountSharedData)> = Vec::new(); + log::info!("locking: {:?}", db.accounts_db); // Lock accounts, by removing them from the accounts_db hashmap, and adding them to locked accounts - let _ = accounts_to_lock.iter().map(|pubkey| { - db.locked_accounts - .insert(pubkey.clone(), db.accounts_db.remove(pubkey).unwrap()) - }); + for pubkey in accounts_to_lock.iter() { + if let Some(account) = db.accounts_db.get(pubkey) { + db.locked_accounts + .insert(pubkey.clone(), db.accounts_db.remove(pubkey).unwrap()); + log::info!("account was found"); + } + else { + let rpc_client_temp = RpcClient::new("https://api.devnet.solana.com".to_string()); + let account = rpc_client_temp.get_account(pubkey).unwrap(); + let data: AccountSharedData = account.into(); + db.locked_accounts + .insert(pubkey.clone(), data); + log::info!("account was not found"); + } + + if let Some(account) = db.locked_accounts.get(&pubkey) { + // account_sender.send(Some(account.clone())).await.unwrap(); + information_to_send.push((*pubkey, account.clone())); + } + else { + // account_sender.send(None).await.unwrap(); + panic!() + } + + } + log::info!("locking done: {:?}", db.accounts_db); + log::info!("locked accounts done: {:?}", db.locked_accounts); + + + log::info!("information to send -> {:?}", information_to_send); + account_sender.send(Some(information_to_send)).await.unwrap(); + // log::info!("2: {:#?}", db.locked_accounts); } else if let Some(get_this_hash_tx) = message.frontend_get_tx { + log::info!("Getting tx for frontend"); + log::info!("Getting tx for frontend"); let req_tx = db.transactions.get(&get_this_hash_tx).unwrap(); frontend_sender @@ -56,7 +103,130 @@ impl RollupDB { .await .unwrap(); } else if let Some(tx) = message.add_processed_transaction { + + let processed_data = message.add_new_data.unwrap(); + + // unlocking accounts + let locked_keys = tx.message.account_keys.clone(); // get the keys + log::info!("it is starting accounts_db{:#?}", db.accounts_db); + log::info!("it is starting locked_db{:#?}", db.locked_accounts); + for (pubkey, data) in processed_data.iter() { + db.locked_accounts.remove(pubkey).unwrap(); + db.accounts_db.insert(*pubkey, data.clone()); + log::info!("it is final accounts_db{:#?}", db.accounts_db); + log::info!("it is final locked_db{:#?}", db.locked_accounts); + + + } + // send transaction to the db.transactions + + db.transactions.insert(tx.message.hash(), tx.clone()); + log::info!("locked: {:#?}", db.locked_accounts); + log::info!("43210: {:#?}", db.accounts_db); + + // communication channel with database + // communcation with the frontend + } + else if message.bundle_tx { + log::info!("BUNDLING TX"); + let mut tx_bundler = TransferBundler::new(); + for (_, tx) in db.transactions.clone() { + tx_bundler.bundle(tx); + } + let final_ixs = tx_bundler.generate_final(); + + let pubkey_user = &final_ixs[0].accounts[0].pubkey; + let del_service = delegation_service.read().unwrap(); + let user_keypair = del_service.get_keypair(pubkey_user).unwrap(); + + settle_state(&final_ixs , user_keypair).await.unwrap(); + + log::info!("\nFinal Transfer Ixs:"); + for ix in final_ixs{ + if let Some((from, to, amount)) = TransferBundler::parse_instruction(&ix){ + } + } + log::info!("BUNDLING DONE"); + db.transactions.clear(); + + } + else if let Some(pubkey) = message.get_account { + if db.locked_accounts.contains_key(&pubkey) { + sender_locked_accounts.send(true).await.unwrap(); + } else { + sender_locked_accounts.send(false).await.unwrap(); + } + // if + // log::info!("4321: {:#?}", db.locked_accounts); + // if let Some(account) = db.locked_accounts.get(&pubkey) { + // account_sender.send(Some(account.clone())).await.unwrap(); + // } + // else { + // account_sender.send(None).await.unwrap(); + // } + } + } } + + pub fn register_pda(&mut self, user: Pubkey, pda: Pubkey) { + self.pda_mappings.insert(user, pda); + } + + pub fn get_pda_for_user(&self, user: &Pubkey) -> Option<&Pubkey> { + self.pda_mappings.get(user) + } + + pub fn register_pda(&mut self, user: Pubkey, pda: Pubkey) { + self.pda_mappings.insert(user, pda); + } + + pub fn get_pda_for_user(&self, user: &Pubkey) -> Option<&Pubkey> { + self.pda_mappings.get(user) + } } + + + + // if let Some(account) = db.locked_accounts.remove(&pubkey) { + // let data_for_the_account = data.get(pubkey).unwrap() + // db.accounts_db.insert(pubkey, data.get(pubkey).unwrap()); // Unlock and restore + // } +// accounts_to_lock + // .iter() + // .map(|pubkey| { + // // match db.locked_accounts.get(pubkey) { + // // Some(account) => { + // // db.locked_accounts + // // .insert(pubkey.clone(), db.accounts_db.remove(pubkey).unwrap()); + // // } + // // None => { + // // let rpc_client_temp = RpcClient::new("https://api.devnet.solana.com".to_string()); + // // let account = rpc_client_temp.get_account(pubkey).unwrap(); + // // let data: AccountSharedData = account.into(); + // // db.locked_accounts + // // .insert(pubkey.clone(), data); + // // } + // // } + // log::info!("99999999999999999999999999999999"); + // if let Some(account) = db.accounts_db.get(pubkey) { + // db.locked_accounts + // .insert(pubkey.clone(), db.accounts_db.remove(pubkey).unwrap()); + // log::info!("111111111111111111111112"); + // } + // else { + // let rpc_client_temp = RpcClient::new("https://api.devnet.solana.com".to_string()); + // let account = rpc_client_temp.get_account(pubkey).unwrap(); + // let data: AccountSharedData = account.into(); + // db.locked_accounts + // .insert(pubkey.clone(), data); + // log::info!("2222222222222222222222222"); + // } + // }); + + + // let _ = accounts_to_lock.iter().map(|pubkey| { + // db.locked_accounts + // .insert(pubkey.clone(), db.accounts_db.remove(pubkey).unwrap()) + // }); diff --git a/rollup_core/src/sequencer.rs b/rollup_core/src/sequencer.rs index e4f0a1f..f87e5bd 100644 --- a/rollup_core/src/sequencer.rs +++ b/rollup_core/src/sequencer.rs @@ -1,37 +1,129 @@ +use core::panic; use std::{ - collections::{HashMap, HashSet}, - sync::{Arc, RwLock}, + collections::{HashMap, HashSet}, sync::{Arc, RwLock}, time, vec }; use anyhow::{anyhow, Result}; -use async_channel::Sender; +use async_channel::{Sender, Receiver}; use crossbeam::channel::{Sender as CBSender, Receiver as CBReceiver}; use solana_client::{nonblocking::rpc_client as nonblocking_rpc_client, rpc_client::RpcClient}; use solana_compute_budget::compute_budget::ComputeBudget; use solana_program_runtime::{ invoke_context::{self, EnvironmentConfig, InvokeContext}, - loaded_programs::{BlockRelation, ForkGraph, LoadProgramMetrics, ProgramCacheEntry, ProgramCacheForTxBatch, ProgramRuntimeEnvironments}, sysvar_cache, timings::ExecuteTimings, + loaded_programs::{BlockRelation, ForkGraph, LoadProgramMetrics, ProgramCacheEntry, ProgramCacheForTxBatch, ProgramRuntimeEnvironments}, sysvar_cache, }; use solana_bpf_loader_program::syscalls::create_program_runtime_environment_v1; use solana_sdk::{ - account::{AccountSharedData, ReadableAccount}, clock::{Epoch, Slot}, feature_set::FeatureSet, fee::FeeStructure, hash::Hash, pubkey::Pubkey, rent::Rent, rent_collector::RentCollector, transaction::{SanitizedTransaction, Transaction}, transaction_context::TransactionContext + account::{AccountSharedData, ReadableAccount}, clock::{Epoch, Slot}, feature_set::FeatureSet, fee::FeeStructure, hash::Hash, instruction, pubkey::Pubkey, rent::Rent, rent_collector::RentCollector, sysvar::instructions, transaction::{SanitizedTransaction, Transaction}, transaction_context::{IndexOfAccount, TransactionContext}, }; +use solana_timings::ExecuteTimings; use solana_svm::{ - message_processor::MessageProcessor, - transaction_processing_callback::TransactionProcessingCallback, - transaction_processor::{TransactionBatchProcessor, TransactionProcessingEnvironment}, + transaction_processing_callback::TransactionProcessingCallback, transaction_processing_result::ProcessedTransaction, transaction_processor::{TransactionBatchProcessor, TransactionProcessingConfig, TransactionProcessingEnvironment} }; - -use crate::{rollupdb::RollupDBMessage, settle::settle_state}; - -pub fn run( - sequencer_receiver_channel: CBReceiver, - rollupdb_sender: CBSender, +use tokio::time::{sleep, Duration}; +use crate::{delegation::find_delegation_pda, delegation_service::DelegationService, rollupdb::RollupDBMessage, settle::settle_state}; +use crate::loader::RollupAccountLoader; +use crate::processor::*; +use crate::bundler::*; +use crate::errors::RollupErrors; + + +pub async fn run( // async + sequencer_receiver_channel: CBReceiver, // CBReceiver + rollupdb_sender: CBSender, // CBSender + account_reciever: Receiver>>, + receiver_locked_accounts: Receiver, + delegation_service: Arc>, ) -> Result<()> { + let mut tx_counter = 0u32; - while let transaction = sequencer_receiver_channel.recv().unwrap() { + + let rpc_client_temp = RpcClient::new("https://api.devnet.solana.com".to_string()); + + let mut rollup_account_loader = RollupAccountLoader::new( + &rpc_client_temp, + ); + while let Ok(transaction) = sequencer_receiver_channel.recv() { + let sender = transaction.message.account_keys[0]; + let amount = 1_000_000_000; + + // Check delegation status first + let needs_delegation = { + let mut delegation_service = delegation_service.write().unwrap(); + match delegation_service.get_or_fetch_pda(&sender)? { + Some((_, delegation)) => { + // Log current and required amounts + log::info!( + "Checking delegation: current amount={}, required amount={}", + delegation.delegated_amount, + amount + ); + delegation.delegated_amount < amount + } + None => { + log::info!("No existing delegation found, creating new one with amount={}", amount); + true + } + } + }; + + if needs_delegation { + // Create delegation transaction outside the lock + let delegation_tx = { + let mut delegation_service = delegation_service.write().unwrap(); + match delegation_service.create_delegation_transaction(&sender, amount) { + Ok(tx) => tx, + Err(e) => { + log::error!("Failed to create delegation transaction: {}", e); + continue; + } + } + }; + + // Submit and confirm delegation + match rpc_client_temp.send_and_confirm_transaction(&delegation_tx) { + Ok(sig) => { + log::info!("Created delegation with signature: {}", sig); + // Wait for confirmation + tokio::time::sleep(Duration::from_secs(2)).await; + + // Update cache after successful delegation + let (pda, _) = find_delegation_pda(&sender); + if let Ok(account) = rpc_client_temp.get_account(&pda) { + delegation_service.write().unwrap() + .update_pda_state(pda, account.into()); + } + } + Err(e) => { + log::error!("Failed to create delegation: {}", e); + continue; // Skip processing this transaction + } + } + } + let accounts_to_lock = transaction.message.account_keys.clone(); + for pubkey in accounts_to_lock.iter() { + loop { + rollupdb_sender + .send(RollupDBMessage { + lock_accounts: None, + frontend_get_tx: None, + add_settle_proof: None, + add_new_data: None, + add_processed_transaction: None, + get_account: Some(*pubkey), + bundle_tx: false + bundle_tx: false + }) + + .map_err(|_| anyhow!("failed to send message to rollupdb"))?; + if receiver_locked_accounts.recv().await.unwrap() == false { + break; + } + sleep(Duration::from_millis(500)).await; + } + } tx_counter += 1; // lock accounts in rollupdb to keep paralell execution possible, just like on solana rollupdb_sender @@ -39,20 +131,217 @@ pub fn run( lock_accounts: Some(accounts_to_lock), frontend_get_tx: None, add_settle_proof: None, + add_new_data: None, add_processed_transaction: None, + get_account: None, + // response: Some(true), + bundle_tx: false + bundle_tx: false }) .map_err(|_| anyhow!("failed to send message to rollupdb"))?; - // Verify ransaction signatures, integrity - - // Process transaction + if let Some(vec_of_accounts_data) = account_reciever.recv().await.unwrap() { + log::info!("received::: {:?}", vec_of_accounts_data); + for (pubkey, account) in vec_of_accounts_data.iter() { + rollup_account_loader.add_account(*pubkey, account.clone()); + log::info!("sucess:") + } + } + for pubkey in transaction.message.account_keys.iter(){ + let data = rollup_account_loader.get_account_shared_data(pubkey); + log::info!("data from an account: {:?}", data); + } let compute_budget = ComputeBudget::default(); let feature_set = FeatureSet::all_enabled(); - let fee_structure = FeeStructure::default(); + let mut fee_structure = FeeStructure::default(); + fee_structure.lamports_per_signature = 0; let lamports_per_signature = fee_structure.lamports_per_signature; - // let rent_collector = RentCollector::default(); + let rent_collector = RentCollector::default(); + let mut timings = ExecuteTimings::default(); + let fork_graph = Arc::new(RwLock::new(RollupForkGraph {})); + + let mut used_cu = 0u64; + let sanitized = SanitizedTransaction::try_from_legacy_transaction( // to check here for the problem + Transaction::from(transaction.clone()), + &HashSet::new(), + ); + + log::info!("{:?}", sanitized.clone()); + + // let needed_programs: Vec<(Pubkey, AccountSharedData)> = + // accounts_data + // .iter() + // .filter(|(pubkey, account)| account.executable()) + // .map(|(pubkey, account)| (pubkey.clone(), account.clone())) + // .collect(); + + + let processor = create_transaction_batch_processor( + &rollup_account_loader, + &feature_set, + &compute_budget, + Arc::clone(&fork_graph), + ); + + let checks = get_transaction_check_results(1, fee_structure.lamports_per_signature); + let sanitized_transaction = &[sanitized.unwrap()]; + + let processing_environment = TransactionProcessingEnvironment { + blockhash: Hash::default(), + epoch_total_stake: 0u64, + //epoch_vote_accounts: None, + feature_set: Arc::new(feature_set), + //fee_structure: Some(&fee_structure), + //lamports_per_signature: fee_structure.lamports_per_signature, + blockhash_lamports_per_signature: fee_structure.lamports_per_signature, + fee_lamports_per_signature: fee_structure.lamports_per_signature, + rent_collector: Some(&rent_collector), + }; + + let processing_config = TransactionProcessingConfig { + compute_budget: Some(compute_budget), + ..Default::default() + }; + + let status = processor.load_and_execute_sanitized_transactions( + &rollup_account_loader, + sanitized_transaction, + checks, + &processing_environment, + &processing_config + ); + log::info!("{:#?}", status.processing_results); + log::info!("error_metrics: {:#?}", status.error_metrics); + + let data_new = + status + .processing_results + .iter() + .map(|res| { + println!("Executed transaction:"); + log::info!("Executed transaction"); + let enum_one = res.as_ref().unwrap(); + + match enum_one { + ProcessedTransaction::Executed(tx) => { + println!("Executed transaction: {:?}", tx.loaded_transaction.accounts); + Some(tx.loaded_transaction.accounts.clone()) + } + ProcessedTransaction::FeesOnly(tx) => { + println!("Fees-only transaction: {:?}", tx); + None + } + } + }).collect::>>>(); + + let first_index_data = data_new[0].as_ref().unwrap().clone(); + log::info!("swq {:?}", first_index_data); + // Send processed transaction to db for storage and availability + rollupdb_sender + .send(RollupDBMessage { + lock_accounts: None, + add_processed_transaction: Some(transaction.clone()), + add_new_data: Some(first_index_data), + frontend_get_tx: None, + add_settle_proof: None, + get_account: None, + bundle_tx: false + }) + + .unwrap(); + + //View sent processed tx details + let ixs = get_transaction_instructions(&transaction); + let acc_keys: &[Pubkey] = &transaction.message.account_keys; + if let Some((from, to, amount)) = TransferBundler::parse_compiled_instruction(&ixs[0], acc_keys) { + log::info!(" + Transaction Info\n + From: {from:?}\n + To: {to:?}\n + Amount: {amount} + + ") + } + + + + // Call settle if transaction amount since last settle hits 10 + if tx_counter >= 10 { + rollupdb_sender.send(RollupDBMessage { + lock_accounts: None, + add_processed_transaction: None, + add_settle_proof: None, + get_account: None, + add_new_data: None, + frontend_get_tx: None, + bundle_tx: true + }).unwrap(); + + + + log::info!("Start bundling!"); + + // Get the current user's delegation and withdraw funds + let (pda, delegation) = { + let mut delegation_service = delegation_service.write().unwrap(); + if let Some(delegation_info) = delegation_service.get_or_fetch_pda(&sender)? { + delegation_info + } else { + log::error!("No delegation found for {} during withdrawal", sender); + return Ok(()); + } + }; + + // Create and send withdrawal transaction + let withdrawal_tx = { + let mut delegation_service = delegation_service.write().unwrap(); + delegation_service.create_withdrawal_transaction(&pda, &sender, delegation.delegated_amount)? + }; + + // Submit withdrawal transaction synchronously + match rpc_client_temp.send_and_confirm_transaction(&withdrawal_tx) { + Ok(sig) => { + log::info!("Withdrew {} lamports from delegation {}, signature: {}", + delegation.delegated_amount, pda, sig); + }, + Err(e) => { + log::error!("Failed to withdraw from delegation {}: {}", pda, e); + } + } + + // Reset counter after processing + tx_counter = 0; + + } + } + Ok(()) +} + + // Lock db to avoid state changes during settlement + // Lock db to avoid state changes during settlement + + // Prepare root hash, or your own proof to send to chain + + // Send proof to chain + + // let _settle_tx_hash = settle_state("proof".into()).await?; + // .map(|pubkey| { + // ( + // pubkey.clone(), + // rpc_client_temp.get_account(pubkey).unwrap().into(), + // ) + // }) + + +// log::info!("accounts_data: {needed_programs:?}"); + + // for (pubkey, account) in needed_programs.iter() { + // rollup_account_loader.add_account(*pubkey, account.clone()); + // } + +// let rent_collector = RentCollector::default(); // Solana runtime. // let fork_graph = Arc::new(RwLock::new(SequencerForkGraph {})); @@ -68,113 +357,185 @@ pub fn run( // let rent = Rent::default(); - let rpc_client_temp = RpcClient::new("https://api.devnet.solana.com".to_string()); - - let accounts_data = transaction - .message - .account_keys - .iter() - .map(|pubkey| { - ( - pubkey.clone(), - rpc_client_temp.get_account(pubkey).unwrap().into(), - ) - }) - .collect::>(); - let mut transaction_context = TransactionContext::new(accounts_data, Rent::default(), 0, 0); + + // //****************************************************************************************************/ + // // let instructions = &transaction.message.instructions; + // // // let index_array_of_program_pubkeys = Vec::with_capacity(instructions.len()); + // // let program_ids = &transaction.message.account_keys; + + // // let needed_programs: Vec<&Pubkey> = instructions + // // .iter() + // // .map( + // // |instruction| + // // instruction.program_id(program_ids)).collect(); + // //****************************************************************************************************/ + + // let mut transaction_context = TransactionContext::new( + // accounts_data, + // Rent::default(), + // compute_budget.max_instruction_stack_depth, + // compute_budget.max_instruction_trace_length, + // ); + // // transaction_context.get_current_instruction_context().unwrap().get_index_of_program_account_in_transaction(2).unwrap(); + // // transaction_context.push(); + + + // // here we have to load them somehow + + // let runtime_env = Arc::new( + // create_program_runtime_environment_v1(&feature_set, &compute_budget, false, false) + // .unwrap(), + // ); + + // let mut prog_cache = ProgramCacheForTxBatch::new( + // Slot::default(), + // ProgramRuntimeEnvironments { + // program_runtime_v1: runtime_env.clone(), + // program_runtime_v2: runtime_env, + // }, + // None, + // Epoch::default(), + // ); + + // // prog_cache.replenish(accounts_data., entry) + + // let sysvar_c = sysvar_cache::SysvarCache::default(); + // let env = EnvironmentConfig::new( + // Hash::default(), + // None, + // None, + // Arc::new(feature_set), + // lamports_per_signature, + // &sysvar_c, + // ); + // // let default_env = EnvironmentConfig::new(blockhash, epoch_total_stake, epoch_vote_accounts, feature_set, lamports_per_signature, sysvar_cache) + + // // let processing_environment = TransactionProcessingEnvironment { + // // blockhash: Hash::default(), + // // epoch_total_stake: None, + // // epoch_vote_accounts: None, + // // feature_set: Arc::new(feature_set), + // // fee_structure: Some(&fee_structure), + // // lamports_per_signature, + // // rent_collector: Some(&rent_collector), + // // }; - let runtime_env = Arc::new( - create_program_runtime_environment_v1(&feature_set, &compute_budget, false, false) - .unwrap(), - ); + - let mut prog_cache = ProgramCacheForTxBatch::new( - Slot::default(), - ProgramRuntimeEnvironments { - program_runtime_v1: runtime_env.clone(), - program_runtime_v2: runtime_env, - }, - None, - Epoch::default(), - ); + // // for (pubkey, account) in rollup_account_loader.cache.read().unwrap().iter() { + // // let _p = rollup_account_loader.get_account_shared_data(pubkey); + // // log::info!("account: {_p:?}"); + // // } + // // let cache = &rollup_account_loader.cache.read().unwrap(); + // // let pew = cache.keys().next().cloned().unwrap(); + // // let owner = cache.get(&pew).unwrap().owner(); + // // log::debug!("pubkey: {owner:?}"); + - let sysvar_c = sysvar_cache::SysvarCache::default(); - let env = EnvironmentConfig::new( - Hash::default(), - None, - None, - Arc::new(feature_set), - lamports_per_signature, - &sysvar_c, - ); - // let default_env = EnvironmentConfig::new(blockhash, epoch_total_stake, epoch_vote_accounts, feature_set, lamports_per_signature, sysvar_cache) - - // let processing_environment = TransactionProcessingEnvironment { - // blockhash: Hash::default(), - // epoch_total_stake: None, - // epoch_vote_accounts: None, - // feature_set: Arc::new(feature_set), - // fee_structure: Some(&fee_structure), - // lamports_per_signature, - // rent_collector: Some(&rent_collector), - // }; + // let program_cache_entry = load_program_with_pubkey( + // &rollup_account_loader, + // &prog_cache.environments, + // &rollup_account_loader.cache.read().unwrap().keys().next().cloned().unwrap(),//&needed_programs[0].0, + // 0, + // &mut ExecuteTimings::default(), + // false + // ); + + // log::info!("program_cache_entry: {program_cache_entry:?}"); + + // prog_cache.replenish( + // needed_programs[0].0, + // program_cache_entry.unwrap(), + // ); + // // { + // // let instruction_ctx = transaction_context.get_current_instruction_context(); + // // log::debug!("instruction_ctx: {instruction_ctx:?}"); + // // } + // // let instruction_ctx_height = transaction_context.get_instruction_context_stack_height(); + + // // log::debug!("instruction_ctx_height: {instruction_ctx_height}"); + + // // let instruction_ctx_next = transaction_context.get_next_instruction_context(); + // // // let instruction_ctx = transaction_context.get_next_instruction_context(); - let mut invoke_context = InvokeContext::new( - &mut transaction_context, - &mut prog_cache, - env, - None, - compute_budget.to_owned() - ); + // // log::debug!("instruction_ctx: {instruction_ctx_next:?}"); - let mut used_cu = 0u64; - let sanitized = SanitizedTransaction::try_from_legacy_transaction( - Transaction::from(transaction.clone()), - &HashSet::new(), - ) - ; - log::info!("{:?}", sanitized.clone()); + + + // let mut invoke_context = InvokeContext::new( + // &mut transaction_context, + // &mut prog_cache, + // env, + // None, + // compute_budget.to_owned() + // ); + + + // // let instruction_ctx_2 = invoke_context.transaction_context.get_current_instruction_context(); + // // log::debug!("instruction_ctx_2: {instruction_ctx_2:?}"); + // // let instruction_ctx_height = invoke_context.transaction_context.get_instruction_context_stack_height(); + // // log::debug!("instruction_ctx_height: {instruction_ctx_height}"); + // // let instruction_ctx_height = invoke_context.transaction_context.get_instruction_context_at_index_in_trace(0); + // // log::debug!("instruction_ctx_height: {instruction_ctx_height:?}"); + + + + + // // HAS TO BE AN ADDRESS OF THE PROGRAM + + // // invoke_context.program_cache_for_tx_batch.replenish(key, program_cache_entry.unwrap()); - let mut timings = ExecuteTimings::default(); - let result_msg = MessageProcessor::process_message( - &sanitized.unwrap().message(), - &vec![], - &mut invoke_context, - &mut timings, - &mut used_cu, - ); - // Send processed transaction to db for storage and availability - rollupdb_sender - .send(RollupDBMessage { - lock_accounts: None, - add_processed_transaction: Some(transaction), - frontend_get_tx: None, - add_settle_proof: None, - }) - - .unwrap(); - // Call settle if transaction amount since last settle hits 10 - if tx_counter >= 10 { - // Lock db to avoid state changes during settlement - // Prepare root hash, or your own proof to send to chain + // // let account_index = invoke_context + // // .transaction_context + // // .find_index_of_account(&instructions::id()); + + // // if account_index.is_none() { + // // panic!("Could not find instructions account"); + // // } + + // let program_indices: Vec = vec![0]; + // let result_msg = MessageProcessor::process_message( + // &sanitized.unwrap().message().to_owned(), // ERROR WITH SOLANA_SVM VERSION + // // ?should be fixed with help of chagning versions of solana-svm ? + // // &sanitized.unwrap().message().to_owned(), + // &[program_indices], // TODO: automotize this process + // &mut invoke_context, + // &mut timings, + // &mut used_cu, + // ); + + // log::info!("{:?}", &result_msg); + // log::info!("The message was done sucessfully"); + + + + + + +// TWO WAYS -> TRANSACTIONBATCHPROCCESOR OR MESSAGEPROCESSOR + +// PAYTUBE in SVM FOLDER + +// The question of how often to pull/push the state out of mainnet state + +// PDA as a *treasury , to solve problem with sol that could disapear from account + +// to create kind of a program that will lock funds on mainnet + +// MagicBlock relyaing on their infrustructure + +// To make a buffer between sending two transactions - // Send proof to chain - // let _settle_tx_hash = settle_state("proof".into()).await?; - tx_counter = 0u32; - } - } - Ok(()) -} // / In order to use the `TransactionBatchProcessor`, another trait - Solana // / Program Runtime's `ForkGraph` - must be implemented, to tell the batch @@ -224,4 +585,4 @@ pub fn run( // self.get_account_shared_data(account) // .and_then(|account| owners.iter().position(|key| account.owner().eq(key))) // } -// } +// } \ No newline at end of file diff --git a/rollup_core/src/settle.rs b/rollup_core/src/settle.rs index bf5dab3..556e1a1 100644 --- a/rollup_core/src/settle.rs +++ b/rollup_core/src/settle.rs @@ -1,17 +1,44 @@ use anyhow::Result; -use solana_client::nonblocking::rpc_client::RpcClient; -use solana_sdk::{blake3::Hash, transaction::Transaction}; +use async_channel::Receiver; +use solana_client::rpc_client::RpcClient; +use solana_sdk::{hash::Hash, instruction::Instruction, message::Message, signature::Keypair, transaction::Transaction}; // Settle the state on solana, called by sequencer -pub async fn settle_state(proof: Hash) -> Result { - let rpc_client = RpcClient::new("https://api.devnet.solana.com".into()); +pub async fn settle_state(vec_of_instructions: &Vec, signer: &Keypair) -> Result { + + println!("Settling state"); + + let rpc_client = RpcClient::new("https://api.devnet.solana.com".to_string()); + + let ix = vec_of_instructions[0].clone(); + let payer = &ix.accounts[0].pubkey; + + let ixs = vec_of_instructions.as_slice(); + let recent_blockhash = rpc_client.get_latest_blockhash().unwrap(); + log::info!("Almost settled stateee: {:?}", recent_blockhash); + + + let tx = Transaction::new_signed_with_payer( + ixs, + Some(payer), + &[signer], + recent_blockhash, + ); + + log::info!("Almost settled state: {:?}", tx); + + // let tx = Transaction::new(from_keypairs, message, recent_blockhash) + + // let message = Message::new_with_compiled_instructions(num_required_signatures, num_readonly_signed_accounts, num_readonly_unsigned_accounts, account_keys, recent_blockhash, instructions) // Create proof transaction, calling the right function in the contract - let transaction = Transaction::default(); // Send transaction to contract on chain let settle_tx_hash = rpc_client - .send_and_confirm_transaction(&transaction) - .await?; + .send_and_confirm_transaction(&tx) + // .await?; + .unwrap(); + + log::info!("Settled state: {}", settle_tx_hash.to_string()); Ok(settle_tx_hash.to_string()) }