diff --git a/goCode/goApp/main.go b/goCode/goApp/main.go index 68a6414..5fee1b7 100644 --- a/goCode/goApp/main.go +++ b/goCode/goApp/main.go @@ -6,10 +6,14 @@ package main // fetch_transactions je Rust funkcija koja vraca podatke o transakcijama iz poslednjeg bloka char* fetch_transactions(const char* rpc_url); + +// fetch_last_5_blocks je Rust funkcija koja vraca podatke o transakciji maksimalnog potrosenog gasa u poslednjih 5 blokova +char* fetch_last_5_blocks(const char* rpc_url); */ import "C" import ( + "encoding/json" "fmt" "unsafe" @@ -21,28 +25,49 @@ import ( func main() { - rpcURL, recipientAddress, privateKeyHex := config.LoadEnv() + rpcTestnetURL, rpcMainnetURL, recipientAddress, privateKeyHex := config.LoadEnv() - blockNumber := rpc.GetLatestBlock(rpcURL) + blockNumber := rpc.GetLatestBlock(rpcTestnetURL) fmt.Println(blockNumber) fmt.Println() fakeBalance := utilis.EthToHex(100) // 100 ETH - transaction.SetFakeBalance(rpcURL, recipientAddress, fakeBalance) + transaction.SetFakeBalance(rpcTestnetURL, recipientAddress, fakeBalance) sendEth := 1 - transaction.SendTransaction(rpcURL, recipientAddress, int64(sendEth), privateKeyHex) + transaction.SendTransaction(rpcTestnetURL, recipientAddress, int64(sendEth), privateKeyHex) - //--------------------------- + // === Deo koji zove Rust FFI za transakcije === fmt.Println() - cRpcURL := C.CString(rpcURL) - defer C.free(unsafe.Pointer(cRpcURL)) + cRpcTestnetURL := C.CString(rpcTestnetURL) + defer C.free(unsafe.Pointer(cRpcTestnetURL)) - result := C.fetch_transactions(cRpcURL) + result := C.fetch_transactions(cRpcTestnetURL) defer C.free(unsafe.Pointer(result)) goResult := C.GoString(result) - fmt.Println("Podaci o transakcijama iz poslednjeg bloka:\n", goResult) + fmt.Println("Rust FFI: Podaci o transakcijama iz poslednjeg bloka:\n", goResult) + + // === Deo koji zove Rust FFI za 5 blokova === + fmt.Println() + + cRpcMainnetURL := C.CString(rpcMainnetURL) + defer C.free(unsafe.Pointer(cRpcMainnetURL)) + + resultArray := C.fetch_last_5_blocks(cRpcMainnetURL) + defer C.free(unsafe.Pointer(resultArray)) + goArray := C.GoString(resultArray) + + var summaries []rpc.TxSummary + err := json.Unmarshal([]byte(goArray), &summaries) + if err != nil { + fmt.Println("Greska pri parsiranju JSON-a iz Rust-a:", err) + return + } + for _, s := range summaries { + fmt.Printf("Blok %s | TX %s | Gas %d | %.3f%% u bloku\n", + s.BlockNumber, s.TxHash, s.GasUsed, s.PercentInBlock) + } } diff --git a/goCode/internal/config/config.go b/goCode/internal/config/config.go index fa95c44..898f749 100644 --- a/goCode/internal/config/config.go +++ b/goCode/internal/config/config.go @@ -7,15 +7,20 @@ import ( "github.com/joho/godotenv" ) -func LoadEnv() (string, string, string) { +func LoadEnv() (string, string, string, string) { err := godotenv.Load("../.env") if err != nil { log.Fatal("Greska pri ucitavanju .env fajla") } - rpcURL := os.Getenv("RPC_URL") - if rpcURL == "" { - log.Fatal("Nedostaje RPC_URL u .env fajlu") + rpcTestnetURL := os.Getenv("RPC_TESTNET_URL") + if rpcTestnetURL == "" { + log.Fatal("Nedostaje RPC_TESTNET_URL u .env fajlu") + } + + rpcMainnetURL := os.Getenv("RPC_MAINNET_URL") + if rpcTestnetURL == "" { + log.Fatal("Nedostaje RPC_TESTNET_URL u .env fajlu") } recipient := os.Getenv("RECIPIENT_ADDRESS") @@ -28,5 +33,5 @@ func LoadEnv() (string, string, string) { log.Fatal("Nedostaje PRIVATE_KEY u .env fajlu") } - return rpcURL, recipient, privateKeyHex + return rpcTestnetURL, rpcMainnetURL, recipient, privateKeyHex } diff --git a/goCode/internal/rpc/rpc.go b/goCode/internal/rpc/rpc.go index 5ced86b..db89d3d 100644 --- a/goCode/internal/rpc/rpc.go +++ b/goCode/internal/rpc/rpc.go @@ -41,7 +41,8 @@ func GetLatestBlock(rpcURL string) string { log.Fatalf("Greska pri parsiranju JSON odgovora: %v", err) } - fmt.Println("BLOCK Odgovor:", string(rpcResp.Result)) + fmt.Println("sadrzaj BLOCK-a:\n", string(rpcResp.Result)) + fmt.Println() // Parsiramo blok var block Block diff --git a/goCode/internal/rpc/types.go b/goCode/internal/rpc/types.go index 42ca5f8..5493eb4 100644 --- a/goCode/internal/rpc/types.go +++ b/goCode/internal/rpc/types.go @@ -30,3 +30,10 @@ type Transaction struct { Gas string `json:"gas"` GasPrice string `json:"gasPrice"` } + +type TxSummary struct { + BlockNumber uint64 `json:"block_number"` + TxHash string `json:"tx_hash"` + GasUsed uint64 `json:"gas_used"` + PercentInBlock float64 `json:"percent_in_block"` +} diff --git a/rustCode/Cargo.toml b/rustCode/Cargo.toml index 2f3ac1d..01be590 100644 --- a/rustCode/Cargo.toml +++ b/rustCode/Cargo.toml @@ -14,3 +14,4 @@ serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" reqwest = { version = "0.11", features = ["json"] } dotenvy = "0.15" +futures = "0.3.31" diff --git a/rustCode/src/config/mod.rs b/rustCode/src/config/mod.rs index 3aa9669..284c19d 100644 --- a/rustCode/src/config/mod.rs +++ b/rustCode/src/config/mod.rs @@ -1,11 +1,15 @@ use dotenvy::from_path; use std::{env, path::Path}; -pub fn load_env() { +pub fn load_testnet_env() { let env_path = Path::new("../goCode/.env"); from_path(env_path).expect("Nije moguce ucitati .env fajl sa date putanje"); } -pub fn get_rpc_url() -> String { - env::var("RPC_URL").expect("RPC_URL nije definisan") +pub fn get_testnet_rpc_url() -> String { + env::var("RPC_TESTNET_URL").expect("RPC_URL nije definisan") } + +pub fn get_mainnet_rpc_url() -> String { + env::var("RPC_MAINNET_URL").expect("RPC_URL nije definisan") +} \ No newline at end of file diff --git a/rustCode/src/lib.rs b/rustCode/src/lib.rs index 12bfc2a..9ec0f3a 100644 --- a/rustCode/src/lib.rs +++ b/rustCode/src/lib.rs @@ -1,9 +1,11 @@ pub mod models; pub mod rpc; +pub mod utils; -use crate::rpc::fetch::fetch_latest_block; +use crate::rpc::fetch::{fetch_latest_block, fetch_last_5_blocks_and_receipts}; use std::ffi::CString; use std::os::raw::c_char; +use crate::utils::hex_to_u64; #[no_mangle] pub extern "C" fn fetch_transactions(rpc_url: *const c_char) -> *mut c_char { @@ -13,7 +15,7 @@ pub extern "C" fn fetch_transactions(rpc_url: *const c_char) -> *mut c_char { // Pozivanje fetch_latest_block funkcije za dobijanje najnovijeg bloka let future = tokio::runtime::Runtime::new() .unwrap() - .block_on(fetch_latest_block(rpc_url_str)); + .block_on(fetch_latest_block(rpc_url_str, true)); // Prikupljanje transakcija iz rezultata let mut transaction_data = String::new(); @@ -30,6 +32,22 @@ pub extern "C" fn fetch_transactions(rpc_url: *const c_char) -> *mut c_char { let c_str_result = CString::new(transaction_data).unwrap(); - // Vracanje C stringa koji Go moye koristiti + // Vracanje C stringa koji Go moze koristiti c_str_result.into_raw() } + +#[no_mangle] +pub extern "C" fn fetch_last_5_blocks(rpc_url: *const c_char) -> *mut c_char { + let c_rpc = unsafe { std::ffi::CStr::from_ptr(rpc_url) }; + let rpc_url_str = c_rpc.to_str().unwrap(); + + let runtime = tokio::runtime::Runtime::new().unwrap(); + let latest_block = runtime.block_on(fetch_latest_block(rpc_url_str, true)); + let latest_block_number = hex_to_u64(&latest_block.number); + + let summaries = runtime.block_on(fetch_last_5_blocks_and_receipts(rpc_url_str.to_string(), latest_block_number)); + + let json_str = serde_json::to_string(&summaries).unwrap(); + let c_str_result = CString::new(json_str).unwrap(); + c_str_result.into_raw() +} \ No newline at end of file diff --git a/rustCode/src/main.rs b/rustCode/src/main.rs index 70cfe9c..2fe3ccb 100644 --- a/rustCode/src/main.rs +++ b/rustCode/src/main.rs @@ -1,36 +1,51 @@ mod config; mod models; mod rpc; +mod utils; -use config::{get_rpc_url, load_env}; -use models::SimpleBlock; -use rpc::fetch::fetch_latest_block; +use config::{get_testnet_rpc_url, get_mainnet_rpc_url, load_testnet_env}; +use rpc::fetch::{fetch_latest_block, fetch_block_by_number, fetch_last_5_blocks_and_receipts}; +use utils::{print_block_info, hex_to_u64, analyze_max_gas_transaction}; use tokio; -fn print_block_info(block: &SimpleBlock) { - println!("Blok broj: {}", block.number); - println!("Hash bloka: {}", block.hash); - println!("Timestamp: {}", block.timestamp); - println!("Gas used: {}", block.gas_used); - println!("Broj transakcija: {}", block.transactions.len()); - - for tx in &block.transactions { - println!("---"); - println!("Tx hash: {}", tx.hash); - println!("From: {}", tx.from); - println!("To: {}", tx.to.clone().unwrap_or_else(|| "N/A".into())); - println!("Value: {}", tx.value); - println!("Gas: {}", tx.gas); - println!("Gas price: {}", tx.gas_price); - } -} - #[tokio::main] async fn main() { - load_env(); + load_testnet_env(); + + let testnet_rpc_url = get_testnet_rpc_url(); + let mainnet_rpc_url = get_mainnet_rpc_url(); + + + let (block_testnet, block_mainnet) = tokio::join!( + fetch_latest_block(&testnet_rpc_url, true), + fetch_block_by_number(&mainnet_rpc_url, "latest", true) + ); - let rpc_url = get_rpc_url(); + println!("Testnet blok:"); + print_block_info(&block_testnet); + println!("----------------------"); + println!("Mainnet blok:"); + print_block_info(&block_mainnet); + + let transactions = block_mainnet.transactions; + //print_transactions(&transactions); + + analyze_max_gas_transaction(&mainnet_rpc_url, &transactions, &block_mainnet.gas_used).await; + + println!(); + println!("======================="); + println!("Fetchujem 5 poslednjih blokova i njihove max gas transakcije..."); + println!("=======================\n"); + + let latest_block_number = hex_to_u64(&block_mainnet.number); + let summaries = fetch_last_5_blocks_and_receipts(mainnet_rpc_url, latest_block_number).await; + + println!("\n===== REZIME 5 BLOKOVA ====="); + for s in summaries { + println!( + "Blok {} | TX {} | Gas {} | {:.3}%", + s.block_number, s.tx_hash, s.gas_used, s.percent_in_block + ); + } - let block = fetch_latest_block(&rpc_url).await; - print_block_info(&block); -} +} \ No newline at end of file diff --git a/rustCode/src/models/mod.rs b/rustCode/src/models/mod.rs index f957c88..5294d2a 100644 --- a/rustCode/src/models/mod.rs +++ b/rustCode/src/models/mod.rs @@ -1,7 +1,7 @@ use serde::Deserialize; #[derive(Debug, Deserialize)] -pub struct RPCResponse { +pub struct RPCResponseBlock { pub result: SimpleBlock, } @@ -15,6 +15,7 @@ pub struct SimpleBlock { pub transactions: Vec, } +#[allow(dead_code)] #[derive(Debug, Deserialize)] pub struct SimpleTransaction { pub hash: String, @@ -26,3 +27,27 @@ pub struct SimpleTransaction { #[serde(rename = "gasPrice")] pub gas_price: String, } + +#[derive(Debug, Deserialize)] +pub struct RPCResponseReceipt { + pub result: TransactionReceipt, +} + +#[allow(dead_code)] +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct TransactionReceipt { + pub block_hash: String, + pub block_number: String, + pub transaction_hash: String, + pub gas_used: String, + pub cumulative_gas_used: String, +} + +#[derive(serde::Serialize)] +pub struct TxSummary { + pub block_number: String, + pub tx_hash: String, + pub gas_used: u64, + pub percent_in_block: f64, +} \ No newline at end of file diff --git a/rustCode/src/rpc/fetch.rs b/rustCode/src/rpc/fetch.rs index 1298f31..69b5f16 100644 --- a/rustCode/src/rpc/fetch.rs +++ b/rustCode/src/rpc/fetch.rs @@ -1,12 +1,14 @@ -use crate::models::{RPCResponse, SimpleBlock}; +use crate::models::{RPCResponseBlock, RPCResponseReceipt, SimpleBlock, TransactionReceipt, TxSummary}; use reqwest::Client; use serde_json::json; +use std::sync::Arc; +use crate::utils::{find_max_gas_transaction, hex_to_u64, calculate_gas_percentage}; -pub async fn fetch_latest_block(rpc_url: &str) -> SimpleBlock { +pub async fn fetch_latest_block(rpc_url: &str, transaction_bool: bool) -> SimpleBlock { let req_body = json!( { "jsonrpc": "2.0", "method": "eth_getBlockByNumber", - "params": ["latest", true], + "params": ["latest", transaction_bool], "id": 1 }); @@ -18,7 +20,100 @@ pub async fn fetch_latest_block(rpc_url: &str) -> SimpleBlock { .await .expect("Greska pri slanju zahteva"); - let resp_json: RPCResponse = resp.json().await.expect("Greska pri parsiranju odgovora"); + let resp_json: RPCResponseBlock = resp.json().await.expect("Greska pri parsiranju odgovora"); resp_json.result +} + +pub async fn fetch_block_by_number(rpc_url: &str, block_number: &str, transaction_bool: bool +) -> SimpleBlock { + let req_body = json!( { + "jsonrpc": "2.0", + "method": "eth_getBlockByNumber", + "params": [block_number, transaction_bool], + "id": 1 + }); + + let client = Client::new(); + let resp = client + .post(rpc_url) + .json(&req_body) + .send() + .await + .expect("Greska pri slanju zahteva"); + + let resp_json: RPCResponseBlock = resp.json().await.expect("Greska pri parsiranju odgovora"); + + resp_json.result +} + +pub async fn fetch_transaction_receipt(rpc_url: &str, tx_hash: &str) -> TransactionReceipt { + let req_body = json!({ + "jsonrpc": "2.0", + "method": "eth_getTransactionReceipt", + "params": [tx_hash], + "id": 1 + }); + + let client = Client::new(); + + let resp = client + .post(rpc_url) + .json(&req_body) + .send() + .await + .expect("Greska pri slanju zahteva"); + + let receipt_resp: RPCResponseReceipt = resp.json().await.expect("Greska pri parsiranju odgovora"); + + receipt_resp.result +} + +pub async fn fetch_last_5_blocks_and_receipts(rpc_url: String, latest_block_number: u64) +-> Vec { + let rpc_arc = Arc::new(rpc_url); + + let mut tasks = Vec::new(); + + for i in 0..5 { + let rpc = Arc::clone(&rpc_arc); + let block_number = latest_block_number.saturating_sub(i); // da ne padne ispod 0 + + let task = tokio::spawn(async move { + let block_number_hex = format!("0x{:x}", block_number); + let block = fetch_block_by_number(&rpc, &block_number_hex, true).await; + + if let Some(max_tx) = find_max_gas_transaction(&block.transactions) { + let receipt = fetch_transaction_receipt(&rpc, &max_tx.hash).await; + let tx_gas_used = hex_to_u64(&receipt.gas_used); + let block_gas_used = hex_to_u64(&block.gas_used); + let percent = calculate_gas_percentage(tx_gas_used, block_gas_used); + + println!( + "Blok {} | Max TX: {} | Gas: {} | %. u bloku: {:.3}%", + block.number, max_tx.hash, tx_gas_used, percent + ); + + Some(TxSummary { + block_number: block.number.clone(), + tx_hash: max_tx.hash.clone(), + gas_used: tx_gas_used, + percent_in_block: percent, + }) + } else { + println!("Blok {} nema transakcije.", block.number); + None + } + }); + + tasks.push(task); + } + + let results = futures::future::join_all(tasks).await; + + // filtriram samo uspesne + results + .into_iter() + .filter_map(|res| res.ok().flatten()) + .collect() } \ No newline at end of file diff --git a/rustCode/src/utils/functions.rs b/rustCode/src/utils/functions.rs new file mode 100644 index 0000000..f1e7b3c --- /dev/null +++ b/rustCode/src/utils/functions.rs @@ -0,0 +1,63 @@ +use crate::models::{SimpleTransaction, TransactionReceipt}; +use crate::rpc::fetch::fetch_transaction_receipt; + +pub fn find_max_gas_transaction(transactions: &[SimpleTransaction]) -> Option<&SimpleTransaction> { + transactions + .iter() + .max_by_key(|tx| u64::from_str_radix(tx.gas.trim_start_matches("0x"), 16).unwrap_or(0)) +} + +pub fn hex_to_u64(hex: &str) -> u64 { + u64::from_str_radix(hex.trim_start_matches("0x"), 16).unwrap_or(0) +} + +pub fn calculate_gas_percentage(tx_gas_used: u64, block_gas_used: u64) -> f64 { + if block_gas_used == 0 { + return 0.0; + } + (tx_gas_used as f64 / block_gas_used as f64) * 100.0 +} + +pub fn consume_and_calculate_gas(receipt: TransactionReceipt, block_gas_used_hex: String) -> f64 { + let tx_gas_used = hex_to_u64(&receipt.gas_used); + let block_gas_used = hex_to_u64(&block_gas_used_hex); + calculate_gas_percentage(tx_gas_used, block_gas_used) +} + +pub async fn analyze_max_gas_transaction( + mainnet_rpc_url: &str, + transactions: &[SimpleTransaction], + block_gas_used_hex: &str, +) { + println!("----------------------"); + + if let Some(max_tx) = find_max_gas_transaction(transactions) { + println!("Transakcija sa najvecim gas limitom: "); + println!("Hash {}", max_tx.hash); + println!("Gas limit: {}", hex_to_u64(&max_tx.gas)); + println!(); + + let receipt = fetch_transaction_receipt(mainnet_rpc_url, &max_tx.hash).await; + //println!("{:?}", receipt); // OVO MOZE + let tx_gas_used = hex_to_u64(&receipt.gas_used); + let block_gas_used = hex_to_u64(block_gas_used_hex); + let percent_of_block = calculate_gas_percentage(tx_gas_used, block_gas_used); + + println!("Gas potrosen od ove transakcije: {}", tx_gas_used); + println!("Gas potrosen u bloku: {}", block_gas_used); + println!("Procenat potrosnje u bloku: {:.6}%", percent_of_block); + println!(); + + // Ownership primer + println!("Ponovljeno izracunavanje preko dodele ownership-a"); + let percent_of_block1 = consume_and_calculate_gas(receipt, block_gas_used_hex.to_string()); + println!("Procenat potrosnje u bloku: {:.6}%", percent_of_block1); + //println!("{:?}", receipt); //OVO NE MOZE jer je promenjen owner + //println!("{}",block_mainnet.gas_used); + //let tx_gas_used1 = hex_to_u64(&receipt.gas_used); + //let block_gas_used1 = hex_to_u64(&block_mainnet.gas_used); + + } else { + println!("Nema transakcija u ovom bloku"); + } +} \ No newline at end of file diff --git a/rustCode/src/utils/mod.rs b/rustCode/src/utils/mod.rs new file mode 100644 index 0000000..1494b89 --- /dev/null +++ b/rustCode/src/utils/mod.rs @@ -0,0 +1,5 @@ +pub mod prints; +pub mod functions; + +pub use prints::*; +pub use functions::*; \ No newline at end of file diff --git a/rustCode/src/utils/prints.rs b/rustCode/src/utils/prints.rs new file mode 100644 index 0000000..d7ccdb5 --- /dev/null +++ b/rustCode/src/utils/prints.rs @@ -0,0 +1,25 @@ +use crate::models::{SimpleBlock, SimpleTransaction}; + +pub fn print_block_info(block: &SimpleBlock) { + println!("Blok broj: {}", block.number); + println!("Hash bloka: {}", block.hash); + println!("Timestamp: {}", block.timestamp); + println!("Gas used: {}", block.gas_used); + println!("Broj transakcija: {}", block.transactions.len()); +} + +#[allow(dead_code)] +pub fn print_transactions(transactions: &[SimpleTransaction]) { + for tx in transactions { + println!("---"); + println!("Tx hash: {}", tx.hash); + println!("From: {}", tx.from); + println!("To: {}", tx.to.clone().unwrap_or_else(|| "N/A".into())); + println!("Value: {}", tx.value); + println!("Gas: {}", tx.gas); + println!("Gas price: {}", tx.gas_price); + } + + println!("----------------------"); + println!("Ispisano je {} transakcija", transactions.len()); +} \ No newline at end of file