diff --git a/Cargo.toml b/Cargo.toml index c8ba99f..a90218b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "wvm-archiver" -version = "0.1.1" +version = "0.1.2" edition = "2021" description = "EL data pipeline for WVM testnet v0" authors = ["charmful0x "] diff --git a/README.md b/README.md index 7aa1416..5e391d3 100644 --- a/README.md +++ b/README.md @@ -64,6 +64,26 @@ The WeaveVM Archiver node operates as follows: As mentioned, PlanetScale is used for cloud indexing, which allows a WeaveVM Archiver node to expose its WeaveVM data as a RESTful API. +### WeaveVM Archiver node instance info + +```bash +curl -X GET https://your_app.shuttleapp.rs/info +``` +**returns:** + +```rs +pub struct InfoServerResponse { + first_block: Option, + last_block: Option, + total_archived_blocks: u64, + archiver_balance: U256, + archiver_address: String, + network_name: String, + network_chain_id: u32, + network_rpc: String, +} +``` + ### Retrieve the WVM archive TXID for a given EVM block ID ```bash diff --git a/src/main.rs b/src/main.rs index 870386a..859ae8b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,10 +1,10 @@ use crate::utils::archive_block::archive; -use crate::utils::schema::Network; use crate::utils::planetscale::{ps_archive_block, ps_get_latest_block_id}; -use crate::utils::server_handlers::{handle_block, weave_gm}; +use crate::utils::schema::Network; +use crate::utils::server_handlers::{handle_block, handle_info, handle_weave_gm}; +use axum::{routing::get, Router}; use std::thread; use std::time::Duration; -use axum::{routing::get, Router}; use tokio::task; mod utils; @@ -19,8 +19,9 @@ async fn main() -> shuttle_axum::ShuttleAxum { println!("\n{:#?}\n\n", network); // server routes let router = Router::new() - .route("/", get(weave_gm)) - .route("/block/:id", get(handle_block)); + .route("/", get(handle_weave_gm)) + .route("/info", get(handle_info)) + .route("/block/:id", get(handle_block)); // poll blocks & archive in parallel task::spawn(async move { @@ -40,4 +41,3 @@ async fn main() -> shuttle_axum::ShuttleAxum { Ok(router.into()) } - diff --git a/src/utils/env_var.rs b/src/utils/env_var.rs index 086650f..e7c7de6 100644 --- a/src/utils/env_var.rs +++ b/src/utils/env_var.rs @@ -7,4 +7,4 @@ pub fn get_env_var(key: &str) -> Result { Ok(val) => Ok(val), Err(e) => Err(e), } -} \ No newline at end of file +} diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 6ccd214..21d9d22 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -1,7 +1,7 @@ pub mod archive_block; pub mod env_var; pub mod get_block; -pub mod schema; -pub mod transaction; pub mod planetscale; +pub mod schema; pub mod server_handlers; +pub mod transaction; diff --git a/src/utils/planetscale.rs b/src/utils/planetscale.rs index ba563f2..9db3b6f 100644 --- a/src/utils/planetscale.rs +++ b/src/utils/planetscale.rs @@ -1,34 +1,33 @@ use crate::utils::env_var::get_env_var; -use crate::utils::schema::{Network, PsGetBlockTxid}; -use planetscale_driver::{PSConnection, query}; +use crate::utils::schema::{Network, PsGetBlockTxid, PsGetExtremeBlock}; use anyhow::Error; +use planetscale_driver::{query, PSConnection}; use serde_json::Value; async fn ps_init() -> PSConnection { - let host = get_env_var("DATABASE_HOST").unwrap(); let username = get_env_var("DATABASE_USERNAME").unwrap(); let password = get_env_var("DATABASE_PASSWORD").unwrap(); - let conn: PSConnection = PSConnection::new( - &host, - &username, - &password, - ); + let conn: PSConnection = PSConnection::new(&host, &username, &password); - conn + conn } -pub async fn ps_archive_block(network_block_id: &u64, wvm_calldata_txid: &str) -> Result<(), Error> { +pub async fn ps_archive_block( + network_block_id: &u64, + wvm_calldata_txid: &str, +) -> Result<(), Error> { // format to the table VAR(66) limitation - let wvm_calldata_txid = wvm_calldata_txid.trim_matches('"'); + let wvm_calldata_txid = wvm_calldata_txid.trim_matches('"'); let conn = ps_init().await; - let res = query("INSERT INTO WeaveVMArchiver(NetworkBlockId, WeaveVMArchiveTxid) VALUES($0, \"$1\")") - .bind(network_block_id) - .bind(wvm_calldata_txid) - .execute(&conn) - .await; + let res = + query("INSERT INTO WeaveVMArchiver(NetworkBlockId, WeaveVMArchiveTxid) VALUES($0, \"$1\")") + .bind(network_block_id) + .bind(wvm_calldata_txid) + .execute(&conn) + .await; match res { Ok(result) => { @@ -46,7 +45,11 @@ pub async fn ps_get_latest_block_id() -> u64 { let network = Network::config(); let conn = ps_init().await; - let latest_archived: u64 = query("SELECT MAX(NetworkBlockId) AS LatestNetworkBlockId FROM WeaveVMArchiver;").fetch_scalar(&conn).await.unwrap_or(network.start_block); + let latest_archived: u64 = + query("SELECT MAX(NetworkBlockId) AS LatestNetworkBlockId FROM WeaveVMArchiver;") + .fetch_scalar(&conn) + .await + .unwrap_or(network.start_block); // return latest archived block in planetscale + 1 // so the process can start archiving from latest_archived + 1 latest_archived + 1 @@ -55,13 +58,32 @@ pub async fn ps_get_latest_block_id() -> u64 { pub async fn ps_get_archived_block_txid(id: u64) -> Value { let conn = ps_init().await; - let query_formatted = format!("SELECT WeaveVMArchiveTxid FROM WeaveVMArchiver WHERE NetworkBlockId = {}", id); - let txid: PsGetBlockTxid = - query(&query_formatted) - .fetch_one(&conn) - .await - .unwrap(); + let query_formatted = format!( + "SELECT WeaveVMArchiveTxid FROM WeaveVMArchiver WHERE NetworkBlockId = {}", + id + ); + let txid: PsGetBlockTxid = query(&query_formatted).fetch_one(&conn).await.unwrap(); let res = serde_json::json!(txid); res -} \ No newline at end of file +} + +pub async fn ps_get_blocks_extremes(extreme: &str) -> Value { + let conn = ps_init().await; + + let query_type = match extreme { + "first" => "ASC", + "last" => "DESC", + _ => panic!("invalid extreme value. Use 'first' or 'last'."), + }; + + let query_formatted = format!( + "SELECT NetworkBlockId FROM WeaveVMArchiver ORDER BY NetworkBlockId {} LIMIT 1;", + query_type + ); + + let query: PsGetExtremeBlock = query(&query_formatted).fetch_one(&conn).await.unwrap(); + + let res = serde_json::json!(query); + res +} diff --git a/src/utils/schema.rs b/src/utils/schema.rs index 7ae7d92..863c986 100644 --- a/src/utils/schema.rs +++ b/src/utils/schema.rs @@ -1,13 +1,15 @@ use crate::utils::env_var::get_env_var; +use crate::utils::transaction::get_archiver_balance; use borsh::to_vec; use borsh_derive::{BorshDeserialize, BorshSerialize}; +use ethers::types::U256; use ethers_providers::{Http, Provider}; +use planetscale_driver::Database; use serde::{Deserialize, Serialize}; use serde_json::Value; use std::convert::TryFrom; use std::fs::File; use std::io::{Read, Write}; -use planetscale_driver::Database; #[derive(Serialize, Deserialize, Debug)] pub struct Network { @@ -57,26 +59,26 @@ impl Network { pub struct Block { pub base_fee_per_gas: Option, // "baseFeePerGas" pub blob_gas_used: Option, // "blobGasUsed" - pub difficulty: Option, // "difficulty" + pub difficulty: Option, // "difficulty" pub excess_blob_gas: Option, // "excessBlobGas" - pub extra_data: Option, // "extraData" - pub gas_limit: Option, // "gasLimit" - pub gas_used: Option, // "gasUsed" - pub hash: Option, // "hash" - pub logs_bloom: Option, // "logsBloom" - pub miner: Option, // "miner" - pub mix_hash: Option, // "mixHash" - pub nonce: Option, // "nonce" - pub number: Option, // "number" + pub extra_data: Option, // "extraData" + pub gas_limit: Option, // "gasLimit" + pub gas_used: Option, // "gasUsed" + pub hash: Option, // "hash" + pub logs_bloom: Option, // "logsBloom" + pub miner: Option, // "miner" + pub mix_hash: Option, // "mixHash" + pub nonce: Option, // "nonce" + pub number: Option, // "number" pub parent_beacon_block_root: Option, // "parentBeaconBlockRoot" - pub parent_hash: Option, // "parentHash" - pub receipts_root: Option, // "receiptsRoot" + pub parent_hash: Option, // "parentHash" + pub receipts_root: Option, // "receiptsRoot" pub seal_fields: Vec, // "sealFields" as an array of strings - pub sha3_uncles: Option, // "sha3Uncles" - pub size: Option, // "size" - pub state_root: Option, // "stateRoot" - pub timestamp: Option, // "timestamp" - pub total_difficulty: Option, // "totalDifficulty" + pub sha3_uncles: Option, // "sha3Uncles" + pub size: Option, // "size" + pub state_root: Option, // "stateRoot" + pub timestamp: Option, // "timestamp" + pub total_difficulty: Option, // "totalDifficulty" pub transactions: Vec, // "transactions" as an array of strings } @@ -94,8 +96,45 @@ impl Block { } } - #[derive(Database, Debug, Serialize)] pub struct PsGetBlockTxid { - pub wvm_archive_txid : String -} \ No newline at end of file + pub wvm_archive_txid: String, +} + +#[derive(Database, Debug, Serialize)] +pub struct PsGetExtremeBlock { + pub block_id: u64, +} + +#[derive(Debug, Serialize)] +pub struct InfoServerResponse { + first_block: Option, + last_block: Option, + total_archived_blocks: u64, + archiver_balance: U256, + archiver_address: String, + network_name: String, + network_chain_id: u32, + network_rpc: String, +} + +impl InfoServerResponse { + pub async fn new(first_block: Option, last_block: Option) -> InfoServerResponse { + let network = Network::config(); + let total_archived_blocks = last_block.unwrap_or(0) - first_block.unwrap_or(0); + let archiver_balance = get_archiver_balance().await; + let archiver_balance = Some(archiver_balance).unwrap(); + + let instance: InfoServerResponse = InfoServerResponse { + archiver_balance, + first_block, + last_block, + total_archived_blocks, + archiver_address: network.archiver_address, + network_name: network.name, + network_chain_id: network.network_chain_id, + network_rpc: network.network_rpc, + }; + instance + } +} diff --git a/src/utils/server_handlers.rs b/src/utils/server_handlers.rs index 924e1e4..22c4fbd 100644 --- a/src/utils/server_handlers.rs +++ b/src/utils/server_handlers.rs @@ -1,11 +1,9 @@ -use crate::utils::planetscale::ps_get_archived_block_txid; +use crate::utils::planetscale::{ps_get_archived_block_txid, ps_get_blocks_extremes}; +use crate::utils::schema::InfoServerResponse; +use axum::{extract::Path, response::Json}; use serde_json::Value; -use axum::{ - extract::Path, - response::Json -}; -pub async fn weave_gm() -> &'static str { +pub async fn handle_weave_gm() -> &'static str { "WeaveGM!" } @@ -14,3 +12,14 @@ pub async fn handle_block(Path(id): Path) -> Json { Json(txid) } +pub async fn handle_info() -> Json { + let first = ps_get_blocks_extremes("first").await; + let last = ps_get_blocks_extremes("last").await; + + let first_block = first.get("block_id").unwrap().as_u64(); + let last_block = last.get("block_id").unwrap().as_u64(); + let stats_res = InfoServerResponse::new(first_block, last_block).await; + + let res = serde_json::to_value(&stats_res).unwrap(); + Json(res) +} diff --git a/src/utils/transaction.rs b/src/utils/transaction.rs index 1412018..ea72dd7 100644 --- a/src/utils/transaction.rs +++ b/src/utils/transaction.rs @@ -29,6 +29,14 @@ async fn assert_non_zero_balance(provider: &Provider, address: &Address) { assert!(balance > 0.into()); } +pub async fn get_archiver_balance() -> U256 { + let network = Network::config(); + let provider = Network::provider(&network, true).await; + let address = network.archiver_address.parse::
().unwrap(); + let balance = provider.get_balance(address, None).await.unwrap(); + balance +} + async fn send_transaction( client: &Client, address_from: &Address, @@ -51,4 +59,4 @@ async fn send_transaction( println!("\nWeaveVM Archiving TXID: {}", txid); Ok(txid) -} \ No newline at end of file +}