Skip to content
Draft
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
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,7 @@ 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/
#.idea/
.DS_Store
#Private key files are now properly ignored
rollup_client/*.json
115 changes: 88 additions & 27 deletions rollup_client/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ use solana_sdk::{
transaction::Transaction,
};
use solana_transaction_status::UiTransactionEncoding::{self, Binary};
use std::{collections::HashMap, str::FromStr};
use core::hash;
use std::{collections::HashMap, ops::Div, str::FromStr};
// use serde_json;

#[derive(Serialize, Deserialize, Debug)]
Expand All @@ -28,12 +29,15 @@ pub struct GetTransaction {

#[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 path = "/home/izomana/adv-svm/Basic_Rollup_fork/rollup_client/mykey_1.json";
let path2 = "/home/izomana/adv-svm/Basic_Rollup_fork/rollup_client/testkey.json";
let path3 = "/home/izomana/adv-svm/Basic_Rollup_fork/rollup_client/owner.json";
let keypair = signer::keypair::read_keypair_file(path.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(), 1 * LAMPORTS_PER_SOL);
system_instruction::transfer(&keypair2.pubkey(), &keypair.pubkey(), 1 * (LAMPORTS_PER_SOL/2));
let tx = Transaction::new_signed_with_payer(
&[ix],
Some(&keypair2.pubkey()),
Expand Down Expand Up @@ -63,29 +67,86 @@ async fn main() -> Result<()> {

// let serialized_rollup_transaction = serde_json::to_string(&rtx)?;

let submit_transaction = client
.post("http://127.0.0.1:8080/submit_transaction")
.json(&rtx)
.send()
.await?;
// .json()
// .await?;

println!("{submit_transaction:#?}");
let mut hasher = Hasher::default();
hasher.hash(bincode::serialize(&rtx.sol_transaction).unwrap().as_slice());

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())]))
.send()
.await?
.json::<HashMap<String, String>>()
.await?;

println!("{tx_resp:#?}");
//UNCOMMENT
// let submit_transaction = client
// .post("http://127.0.0.1:8080/submit_transaction")
// .json(&rtx)
// .send()
// .await?;
// // .json()
// // .await?;

// println!("{submit_transaction:#?}");
// let mut hasher = Hasher::default();
// hasher.hash(bincode::serialize(&rtx.sol_transaction).unwrap().as_slice());

// println!("{:#?}", hasher.clone().result());

// let tx_resp = client
// .post("http://127.0.0.1:8080/get_transaction")
// .json(&GetTransaction{get_tx: rtx.sol_transaction.message.hash().to_string()})
// .send()
// .await?;
// // .json::<HashMap<String, String>>()
// // .await?;

// println!("{tx_resp:#?}");

// let amounts: Vec<i32> = vec![4, -2, 3, -5, 1, -4, 2, -1, 3, -1];
let amounts: Vec<(String, String, i32)> = vec![
(path.to_string(), path2.to_string(), 5),
(path3.to_string(), path.to_string(), -3),
(path2.to_string(), path3.to_string(), 8),
(path.to_string(), path3.to_string(), -7),
(path2.to_string(), path.to_string(), 4),
(path3.to_string(), path2.to_string(), -6),
(path.to_string(), path2.to_string(), 9),
(path2.to_string(), path3.to_string(), -2),
(path3.to_string(), path.to_string(), 1),
(path.to_string(), path3.to_string(), -4),
];
let mut txs: Vec<Transaction> = vec![];
for amt in amounts {
if amt.2 > 0 {
txs.push(gen_transfer_tx(amt.0, amt.1, amt.2 as u64).await);
} else {
txs.push(gen_transfer_tx(amt.1, amt.0, amt.2.abs() as u64).await);
}
}

for tx in txs {
let rtx = RollupTransaction {
sender: "Me".into(),
sol_transaction: tx
};

let submission = client
.post("http://127.0.0.1:8080/submit_transaction")
.json(&rtx)
.send()
.await?;

println!("Submission {submission:#?}");
}

println!("KP: {}", keypair.pubkey());
println!("KP2: {}", keypair2.pubkey());

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(),
)
}
5 changes: 4 additions & 1 deletion rollup_core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,11 @@ 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"
async-channel = "2.3.1"
bincode = "1.3.3"
112 changes: 112 additions & 0 deletions rollup_core/src/bundler.rs
Original file line number Diff line number Diff line change
@@ -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<CompiledInstruction>{
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::<SystemInstruction>(&cix.data),
Ok(SystemInstruction::Transfer { .. })
)
}

#[derive(PartialEq, Eq, Hash, Clone, Copy)]
struct TBundlerKey {
keys: [Pubkey; 2],
}

pub struct TransferBundler {
transfers: HashMap<TBundlerKey, i128>,
}

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<Instruction> {
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()
}
}
17 changes: 10 additions & 7 deletions rollup_core/src/frontend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ 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;
Expand Down Expand Up @@ -53,11 +53,12 @@ pub async fn submit_transaction(

pub async fn get_transaction(
body: web::Json<GetTransaction>,
sequencer_sender: web::Data<Sender<Transaction>>,
// sequencer_sender: web::Data<Sender<Transaction>>,
rollupdb_sender: web::Data<Sender<RollupDBMessage>>,
frontend_receiver: web::Data<Receiver<FrontendMessage>>,
) -> actix_web::Result<HttpResponse> {
// Validate transaction structure with serialization in function signature
println!("Getting tx...");
log::info!("Requested transaction");
log::info!("{body:?}");

Expand All @@ -67,16 +68,18 @@ pub async fn get_transaction(
add_processed_transaction: None,
frontend_get_tx: Some(Hash::new(body.get_tx.as_bytes())),
add_settle_proof: 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")])))
Expand Down
62 changes: 62 additions & 0 deletions rollup_core/src/loader.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
//! 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 {
solana_client::rpc_client::RpcClient,
solana_sdk::{
account::{AccountSharedData, ReadableAccount},
pubkey::Pubkey,
},
solana_svm::transaction_processing_callback::TransactionProcessingCallback,
std::{collections::HashMap, sync::RwLock},
};

/// 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<HashMap<Pubkey, AccountSharedData>>,
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, account: AccountSharedData) {
self.cache.write().unwrap().insert(pubkey, account);
}
}

/// 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<AccountSharedData> {
if let Some(account) = self.cache.read().unwrap().get(pubkey) {
return Some(account.clone());
}

let account: AccountSharedData = self.rpc_client.get_account(pubkey).ok()?.into();
self.cache.write().unwrap().insert(*pubkey, account.clone());

Some(account)
}

fn account_matches_owners(&self, account: &Pubkey, owners: &[Pubkey]) -> Option<usize> {
self.get_account_shared_data(account)
.and_then(|account| owners.iter().position(|key| account.owner().eq(key)))
}
}
Loading