diff --git a/doc/api.md b/doc/api.md index f6db880..05ad34d 100644 --- a/doc/api.md +++ b/doc/api.md @@ -14,8 +14,8 @@ * [3.1 统计delegate,undelegate,claim总量](#3.1)

1.1 获取Validator集合

-* `GET /api/valiators` -* 参数 +* `GET /api/valiators` +* 参数 | 参数 | 类型 | 必传 | 说明 | |-----------|--------|----|----------| @@ -24,8 +24,8 @@ | page | number | N | 页码,默认1 | | page_size | number | N | 页大小,默认10 | -* Request: `http://localhost/api/validators?online=true&page=1&page_size=5` -* Response: 返回结果按power(质押总量)降序排列 +* Request: `http://localhost/api/validators?online=true&page=1&page_size=5` +* Response: 返回结果按power(质押总量)降序排列 ```json { "total": 31, @@ -146,15 +146,15 @@ ```

1.2 获取validator最近20笔质押变化

-* `GET /api/diff/latest` -* 参数 +* `GET /api/diff/latest` +* 参数 | 参数 | 类型 | 必传 | 说明 | |-----------|--------|----|-------------| | validator | string | Y | validator地址 | -* Request: `http://localhost/api/diff/latest?validator=0xc8d2d4ff0b882243f82c1fb20574c81e4c866e72` -* Response: +* Request: `http://localhost/api/diff/latest?validator=0xc8d2d4ff0b882243f82c1fb20574c81e4c866e72` +* Response: * 按高度降序排列 * 返回值中`amount`,非零正数表示delegate的数量,非零负数表示undelegate的数量 * 如果`amount`是0,则用`op`区分,`op`为零表示delegate,非零表示undelegate @@ -247,8 +247,8 @@ ] ```

1.3 获取validator的delegate记录

-* `GET /api/records/delegate` -* 参数 +* `GET /api/records/delegate` +* 参数 | 参数 | 类型 | 必传 | 说明 | |-----------|--------|----|--------------------------| @@ -256,8 +256,8 @@ | page | number | N | 页码,默认1 | | page_size | number | N | 页大小,默认10 | -* Request: `http://localhost/api/records/delegate?validator=0xc8d2d4ff0b882243f82c1fb20574c81e4c866e72&page=1&page_size=5` -* Response: +* Request: `http://localhost/api/records/delegate?validator=0xc8d2d4ff0b882243f82c1fb20574c81e4c866e72&page=1&page_size=5` +* Response: * 按`timestamp`降序排列 ```json { @@ -304,8 +304,8 @@ ```

1.4 获取validator的undelegate记录

-* `GET /api/records/undelegate` -* 参数 +* `GET /api/records/undelegate` +* 参数 | 参数 | 类型 | 必传 | 说明 | |-----------|--------|----|---------------------------| @@ -313,8 +313,8 @@ | page | number | N | 页码,默认1 | | page_size | number | N | 页大小,默认10 | -* Request: `http://localhost/api/records/undelegate?validator=0x6e20c920f1bdb817f0e19cd05dae01c6affa5228&page=1&page_size=10` -* Response: +* Request: `http://localhost/api/records/undelegate?validator=0x6e20c920f1bdb817f0e19cd05dae01c6affa5228&page=1&page_size=10` +* Response: * 按`timestamp`降序排列 ```json { @@ -342,16 +342,16 @@

2.1 获取bound数量

-* `GET /api/bound` -* 参数 +* `GET /api/bound` +* 参数 | 参数 | 类型 | 必传 | 说明 | |-----------|--------|----|-------------| | validator | string | Y | validator地址 | | delegator | string | Y | delegator地址 | -* Request: `http://localhost/api/bound?validator=0x09ef1db6b67d1cbf7eba6bd9b204611848993df7&delegator=0x2d15d52cc138ffb322b732239cd3630735abac88` -* Response: +* Request: `http://localhost/api/bound?validator=0x09ef1db6b67d1cbf7eba6bd9b204611848993df7&delegator=0x2d15d52cc138ffb322b732239cd3630735abac88` +* Response: ```json { "bound_amount": "110000001800000063120", @@ -360,17 +360,16 @@ ``` -

2.2 获取reward数量

-* `GET /api/reward` -* 参数 +* `GET /api/reward` +* 参数 | 参数 | 类型 | 必传 | 说明 | |---------|--------|----|-------------| | address | string | Y | delegator地址 | -* Request: `http://localhost/api/reward?address=0x2d15d52cc138ffb322b732239cd3630735abac88` -* Response: +* Request: `http://localhost/api/reward?address=0x2d15d52cc138ffb322b732239cd3630735abac88` +* Response: ```json { "reward": "16742332457649244907" @@ -378,16 +377,16 @@ ```

2.3 获取debt数量

-* `GET /api/debt` -* 参数 +* `GET /api/debt` +* 参数 | 参数 | 类型 | 必传 | 说明 | |-----------|--------|----|-------------| | validator | string | Y | validator地址 | | delegator | string | Y | delegator地址 | -* Request: `http://localhost/api/debt?validator=0xd518c4f95a3f39ed853a2614566897c4ad5a008f&delegator=0x2d15d52cc138ffb322b732239cd3630735abac88` -* Response: +* Request: `http://localhost/api/debt?validator=0xd518c4f95a3f39ed853a2614566897c4ad5a008f&delegator=0x2d15d52cc138ffb322b732239cd3630735abac88` +* Response: ```json { "debt": "96558069283467635" @@ -395,15 +394,15 @@ ```

3.1 获取debt数量

-* `GET /api/sum` -* 参数 +* `GET /api/sum` +* 参数 | 参数 | 类型 | 必传 | 说明 | |---------|--------|----|----| | address | string | Y | 地址 | -* Request: `http://localhost/api/sum?address=0xeb2b96369e83e1466bb56f2bf9d97cbda130e741` -* Response: +* Request: `http://localhost/api/sum?address=0xeb2b96369e83e1466bb56f2bf9d97cbda130e741` +* Response: ```json { "delegate": "32254951206000000000000", diff --git a/updater/Cargo.toml b/updater/Cargo.toml new file mode 100644 index 0000000..d1dbcb0 --- /dev/null +++ b/updater/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "updater" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +base64 = "0.22.0" +clap = { version = "4.5.1", features = ["derive"] } +crossbeam = "0.8.4" +env_logger = "0.11.2" +ethers = { version = "2.0.13", features = ["abigen","legacy"] } +log = "0.4.20" +num_cpus = "1.16.0" +reqwest = { version = "0.12.0", features = ["json"] } +rustc-hex = "2.1.0" +serde = "1.0.197" +serde_json = "1.0.114" +sha2 = "0.10.8" +sqlx = { version = "0.7.3", features = ["bigdecimal", "runtime-tokio", "postgres", "chrono", "json"]} +tokio = { version = "1.36.0", features = ["full"]} +toml = "0.8.12" +url = "2.5.0" diff --git a/updater/src/main.rs b/updater/src/main.rs new file mode 100644 index 0000000..ae95894 --- /dev/null +++ b/updater/src/main.rs @@ -0,0 +1,91 @@ +mod db; +mod error; +mod updater; + +use crate::db::Storage; +use crate::error::Result; +use crate::updater::Updater; +use clap::Parser; +use ethers::contract::abigen; +use ethers::prelude::{Http, Provider}; +use ethers::types::Address; +use log::info; +use serde::{Deserialize, Serialize}; +use sqlx::pool::PoolOptions; +use sqlx::{Pool, Postgres}; +use std::fs::File; +use std::io::Read; +use std::sync::Arc; +use std::time::Duration; + +const DEFAULT_RPC_RETRIES: usize = 3; +const DEFAULT_INTERVAL: u64 = 15; // 15s + +abigen!(RewardContract, "../abi/Reward.json"); +abigen!(StakingContract, "../abi/Staking.json"); + +#[derive(Serialize, Deserialize)] +struct UpdaterConfig { + pub evm_rpc: String, + pub staking: String, + pub reward: String, + pub db_url: String, +} + +impl UpdaterConfig { + pub fn new(file_path: &str) -> Result { + let mut f = File::open(file_path)?; + let mut s = String::new(); + f.read_to_string(&mut s)?; + let c: UpdaterConfig = toml::from_str(&s)?; + Ok(c) + } +} + +#[derive(Parser, Debug)] +struct Args { + /// Node RPC + #[arg(long)] + pub node: String, + /// Block height to start scanning + #[arg(long)] + pub start: Option, + /// Interval of scanning in seconds + #[arg(long)] + pub interval: Option, +} + +#[tokio::main] +async fn main() -> Result<()> { + env_logger::init(); + + let config = UpdaterConfig::new("./config.toml")?; + info!("EVM RPC: {}", config.evm_rpc); + info!("Staking contract: {}", config.staking); + info!("Reward contract: {}", config.reward); + + let pool: Pool = PoolOptions::new() + .connect(&config.db_url) + .await + .expect("can't connect to database"); + info!("Connecting db...ok"); + + let storage = Storage::new(pool); + let args = Args::parse(); + let interval = if let Some(interval) = args.interval { + Duration::from_secs(interval) + } else { + Duration::from_secs(DEFAULT_INTERVAL) + }; + + let provider = Provider::::try_from(config.evm_rpc)?; + let staking_addr: Address = config.staking.parse()?; + let staking = StakingContract::new(staking_addr, Arc::new(provider.clone())); + let reward_addr: Address = config.reward.parse()?; + let reward = RewardContract::new(reward_addr, Arc::new(provider.clone())); + info!("Updating interval: {}s", interval.as_secs()); + let updater = Updater::new(DEFAULT_RPC_RETRIES, provider, staking, reward, storage); + let _ = updater.run().await?; + + Ok(()) +}