diff --git a/Cargo.toml b/Cargo.toml index 29171eb21..57d92f5b6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -54,4 +54,5 @@ overflow-checks = false [patch.crates-io] ed25519-dalek = { git = "https://github.com/FindoraNetwork/ed25519-dalek", rev = "ad461f" } +curve25519-dalek = { git = "https://github.com/FindoraNetwork/curve25519-dalek", rev = "a2df65" } x25519-dalek = { git = "https://github.com/FindoraNetwork/x25519-dalek", rev = "53bb1a" } diff --git a/src/components/abciapp/src/api/query_server/query_api/ledger_api.rs b/src/components/abciapp/src/api/query_server/query_api/ledger_api.rs index 2a85db911..f40706e47 100644 --- a/src/components/abciapp/src/api/query_server/query_api/ledger_api.rs +++ b/src/components/abciapp/src/api/query_server/query_api/ledger_api.rs @@ -6,20 +6,26 @@ use { super::server::QueryServer, actix_web::{error, web}, config::abci::global_cfg::CFG, - finutils::api::{DelegationInfo, NetworkRoute, ValidatorDetail}, + finutils::api::{ + DelegationInfo, DelegatorInfo, DelegatorList, NetworkRoute, Validator, + ValidatorDetail, ValidatorList, + }, globutils::HashOf, ledger::{ data_model::{ AssetType, AssetTypeCode, AuthenticatedUtxo, StateCommitmentData, TxnSID, TxoSID, UnAuthenticatedUtxo, Utxo, }, - staking::{DelegationState, TendermintAddr}, + staking::{ + DelegationRwdDetail, DelegationState, Staking, TendermintAddr, + TendermintAddrRef, + }, }, parking_lot::RwLock, ruc::*, - serde::Deserialize, + serde::{Deserialize, Serialize}, std::{collections::BTreeMap, mem, sync::Arc}, - zei::xfr::structs::OwnerMemo, + zei::xfr::{sig::XfrPublicKey, structs::OwnerMemo}, }; /// Ping route to check for liveness of API @@ -212,6 +218,230 @@ pub async fn query_global_state_version( web::Json(hash) } +/// Query current validator list, +/// validtors who have not completed self-deletagion will be filtered out. +#[allow(unused)] +pub async fn query_validators( + data: web::Data>>, +) -> actix_web::Result> { + let qs = data.read(); + let ledger = &qs.ledger_cloned; + let staking = ledger.get_staking(); + + if let Some(validator_data) = staking.validator_get_current() { + let validators = validator_data.get_validator_addr_map(); + let validators_list = validators + .iter() + .flat_map(|(tendermint_addr, pk)| { + validator_data + .get_validator_by_id(pk) + .filter(|v| v.td_power != 0) + .map(|v| { + let mut power_list = validator_data + .body + .values() + .map(|v| v.td_power) + .collect::>(); + power_list.sort_unstable(); + let rank = power_list.len() + - power_list.binary_search(&v.td_power).unwrap(); + Validator::new( + tendermint_addr.clone(), + rank as u64, + staking.delegation_has_addr(&pk), + &v, + ) + }) + }) + .collect(); + return Ok(web::Json(ValidatorList::new( + staking.cur_height(), + validators_list, + ))); + }; + + Ok(web::Json(ValidatorList::new(0, vec![]))) +} + +#[allow(missing_docs)] +#[derive(Deserialize, Debug)] +pub struct DelegationRwdQueryParams { + address: String, + height: Option, +} + +/// get delegation reward according to `DelegationRwdQueryParams` +pub async fn get_delegation_reward( + data: web::Data>>, + web::Query(info): web::Query, +) -> actix_web::Result>> { + // Convert from base64 representation + let key: XfrPublicKey = globutils::wallet::public_key_from_base64(&info.address) + .c(d!()) + .map_err(|e| error::ErrorBadRequest(e.to_string()))?; + + let qs = data.read(); + + let hdr = qs + .ledger_cloned + .api_cache + .as_ref() + .unwrap() + .staking_delegation_rwd_hist + .get(&key) + .c(d!()) + .map_err(|e| error::ErrorNotFound(e.to_string()))?; + + let h = qs.ledger_cloned.get_tendermint_height(); + + let mut req_h = info.height.unwrap_or(h); + alt!(req_h > h, req_h = h); + + Ok(web::Json( + hdr.get_closest_smaller(&req_h) + .map(|(_, r)| vec![r]) + .unwrap_or_default(), + )) +} + +#[allow(missing_docs)] +#[derive(Deserialize, Debug)] +pub struct ValidatorDelegationQueryParams { + address: TendermintAddr, + epoch_size: Option, + epoch_cnt: Option, +} + +#[allow(missing_docs)] +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +pub struct ValidatorDelegation { + return_rate: [u128; 2], + self_delegation: u64, + delegated: u64, +} + +/// get history according to `ValidatorDelegationQueryParams` +pub async fn get_validator_delegation_history( + data: web::Data>>, + web::Query(info): web::Query, +) -> actix_web::Result>> { + let qs = data.read(); + let ledger = &qs.ledger_cloned; + let staking = ledger.get_staking(); + + let v_id = staking + .validator_td_addr_to_app_pk(&info.address) + .c(d!()) + .map_err(error::ErrorBadRequest)?; + + let h = staking.cur_height(); + + let start_height = staking + .delegation_get(&v_id) + .ok_or_else(|| error::ErrorBadRequest("not exists"))? + .start_height; + + let staking_global_rate_hist = &qs + .ledger_cloned + .api_cache + .as_ref() + .unwrap() + .staking_global_rate_hist; + + let delegation_amount_hist = ledger + .api_cache + .as_ref() + .unwrap() + .staking_delegation_amount_hist + .get(&v_id); + + let self_delegation_amount_hist = ledger + .api_cache + .as_ref() + .unwrap() + .staking_self_delegation_hist + .get(&v_id); + + let mut esiz = info.epoch_size.unwrap_or(10); + alt!(esiz > h, esiz = h); + alt!(0 == esiz, esiz = 1); + + let ecnt_max = h.saturating_sub(start_height) / esiz; + let mut ecnt = info + .epoch_cnt + .map(|n| min!(n, ecnt_max)) + .unwrap_or(min!(16, ecnt_max)); + + alt!(ecnt > h, ecnt = h); + alt!(ecnt > 1024, ecnt = 1024); + alt!(0 == ecnt, ecnt = 1); + + let mut c1 = map! { B 1 + h => None}; + let mut c2 = map! { B 1 + h => None}; + let mut c3 = map! { B 1 + h => None}; + let res = (0..ecnt) + .map(|i| h - i * esiz) + .filter_map(|hi| { + if c1.range(..=hi).next().is_none() { + c1.clear(); + if let Some((h, v)) = staking_global_rate_hist.get_closest_smaller(&hi) { + c1.insert(h, Some(v)); + } else { + c1.insert(0, None); + } + } + + c1.values().copied().next().flatten().map(|return_rate| { + if c2.range(..=hi).next().is_none() { + c2.clear(); + if let Some((h, v)) = delegation_amount_hist + .as_ref() + .and_then(|dah| dah.get_closest_smaller(&hi)) + { + c2.insert(h, Some(v)); + } else { + c2.insert(0, None); + } + } + + if c3.range(..=hi).next().is_none() { + c3.clear(); + if let Some((h, v)) = self_delegation_amount_hist + .as_ref() + .and_then(|sdah| sdah.get_closest_smaller(&hi)) + { + c3.insert(h, Some(v)); + } else { + c3.insert(0, None); + } + } + + ValidatorDelegation { + return_rate, + delegated: c2.values().copied().next().flatten().unwrap_or_default(), + self_delegation: c3 + .values() + .copied() + .next() + .flatten() + .unwrap_or_default(), + } + }) + }) + .collect(); + + Ok(web::Json(res)) +} + +#[allow(missing_docs)] +#[derive(Deserialize, Debug)] +pub struct DelegatorQueryParams { + address: String, + page: usize, + per_page: usize, + order: OrderOption, +} + #[derive(Deserialize, Debug, PartialEq)] #[serde(rename_all = "snake_case")] enum OrderOption { @@ -219,6 +449,65 @@ enum OrderOption { Asc, } +/// paging Query delegators according to `DelegatorQueryParams` +pub async fn get_delegators_with_params( + data: web::Data>>, + web::Query(info): web::Query, +) -> actix_web::Result> { + let qs = data.read(); + let ledger = &qs.ledger_cloned; + let staking = ledger.get_staking(); + + if info.page == 0 || info.order == OrderOption::Asc { + return Ok(web::Json(DelegatorList::new(vec![]))); + } + + let start = (info.page - 1) + .checked_mul(info.per_page) + .c(d!()) + .map_err(error::ErrorBadRequest)?; + let end = start + .checked_add(info.per_page) + .c(d!()) + .map_err(error::ErrorBadRequest)?; + + let list = validator_get_delegator_list(staking, info.address.as_ref(), start, end) + .c(d!()) + .map_err(error::ErrorNotFound)?; + + let list: Vec = list + .iter() + .map(|(key, am)| { + DelegatorInfo::new(globutils::wallet::public_key_to_base64(key), **am) + }) + .collect(); + + Ok(web::Json(DelegatorList::new(list))) +} + +/// query delegator list according to `TendermintAddr` +pub async fn query_delegator_list( + data: web::Data>>, + addr: web::Path, +) -> actix_web::Result> { + let qs = data.read(); + let ledger = &qs.ledger_cloned; + let staking = ledger.get_staking(); + + let list = validator_get_delegator_list(staking, addr.as_ref(), 0, usize::MAX) + .c(d!()) + .map_err(error::ErrorNotFound)?; + + let list: Vec = list + .iter() + .map(|(key, am)| { + DelegatorInfo::new(globutils::wallet::public_key_to_base64(key), **am) + }) + .collect(); + + Ok(web::Json(DelegatorList::new(list))) +} + /// query validator detail according to `TendermintAddr` pub async fn query_validator_detail( data: web::Data>>, @@ -441,3 +730,28 @@ impl NetworkRoute for ApiRoutes { "/".to_owned() + endpoint } } + +#[allow(missing_docs)] +pub fn validator_get_delegator_list<'a>( + s: &'a Staking, + validator: TendermintAddrRef, + start: usize, + mut end: usize, +) -> Result> { + let validator = s.validator_td_addr_to_app_pk(validator).c(d!())?; + + if let Some(v) = s.validator_get_current_one_by_id(&validator) { + if start >= v.delegators.len() || start > end { + return Err(eg!("Index out of range")); + } + if end > v.delegators.len() { + end = v.delegators.len(); + } + + Ok((start..end) + .filter_map(|i| v.delegators.get_index(i)) + .collect()) + } else { + Err(eg!("Not a validator or non-existing node address")) + } +} diff --git a/src/components/abciapp/src/api/query_server/query_api/mod.rs b/src/components/abciapp/src/api/query_server/query_api/mod.rs index 8786dda3a..e90d15ae2 100644 --- a/src/components/abciapp/src/api/query_server/query_api/mod.rs +++ b/src/components/abciapp/src/api/query_server/query_api/mod.rs @@ -16,17 +16,22 @@ use { globutils::wallet, ledger::{ data_model::{ - b64dec, AssetTypeCode, DefineAsset, IssuerPublicKey, TxOutput, TxnIDHash, - TxnSID, TxoSID, XfrAddress, + b64dec, AssetTypeCode, DefineAsset, IssuerPublicKey, Transaction, TxOutput, + TxnIDHash, TxnSID, TxoSID, XfrAddress, BLACK_HOLE_PUBKEY, + }, + staking::{ + ops::mint_fra::MintEntry, FF_PK_EXTRA_120_0000, FRA, FRA_TOTAL_AMOUNT, }, - staking::ops::mint_fra::MintEntry, }, ledger_api::*, parking_lot::RwLock, ruc::*, serde::{Deserialize, Serialize}, server::QueryServer, - std::sync::Arc, + std::{ + collections::{BTreeMap, HashSet}, + sync::Arc, + }, tracing::info, zei::{ serialization::ZeiFromToBytes, @@ -44,6 +49,32 @@ pub async fn version() -> actix_web::Result { )) } +/// Queries the status of a transaction by its handle. Returns either a not committed message or a +/// serialized TxnStatus. +pub async fn get_address( + data: web::Data>>, + info: web::Path, +) -> actix_web::Result { + let server = data.read(); + let address_res = server.get_address_of_sid(TxoSID(*info)); + let res = if let Some(address) = address_res { + serde_json::to_string(&address)? + } else { + format!("No utxo {} found. Please retry with a new utxo.", &info) + }; + Ok(res) +} + +/// Returns the owner memo required to decrypt the asset record stored at given index, if it exists. +#[allow(clippy::unnecessary_wraps)] +pub async fn get_owner_memo( + data: web::Data>>, + info: web::Path, +) -> actix_web::Result>, actix_web::error::Error> { + let server = data.read(); + Ok(web::Json(server.get_owner_memo(TxoSID(*info)))) +} + /// Separate a string of `TxoSID` by ',' and query the corresponding memo #[allow(clippy::unnecessary_wraps)] pub async fn get_owner_memo_batch( @@ -63,6 +94,27 @@ pub async fn get_owner_memo_batch( Ok(web::Json(resp)) } +/// Returns an array of the utxo sids currently spendable by a given address +pub async fn get_owned_utxos( + data: web::Data>>, + owner: web::Path, +) -> actix_web::Result>> { + let qs = data.read(); + let ledger = &qs.ledger_cloned; + + let pk = wallet::public_key_from_base64(owner.as_str()) + .map_err(actix_web::error::ErrorServiceUnavailable)?; + + let utxos = ledger + .get_owned_utxos(&pk) + .map_err(actix_web::error::ErrorServiceUnavailable)? + .keys() + .copied() + .collect(); + + Ok(web::Json(utxos)) +} + /// Define interface type #[allow(missing_docs)] pub enum QueryServerRoutes { @@ -292,6 +344,144 @@ pub async fn get_coinbase_oper_list( })) } +/// Returns the list of claim transations of a given ledger address +pub async fn get_claim_txns( + data: web::Data>>, + web::Query(info): web::Query, +) -> actix_web::Result>>> { + // Convert from base64 representation + let key: XfrPublicKey = wallet::public_key_from_base64(&info.address) + .c(d!()) + .map_err(|e| error::ErrorBadRequest(e.to_string()))?; + + let server = data.read(); + + if info.page == 0 { + return Ok(web::Json(vec![])); + } + + let start = (info.page - 1) + .checked_mul(info.per_page) + .c(d!()) + .map_err(error::ErrorBadRequest)?; + let end = start + .checked_add(info.per_page) + .c(d!()) + .map_err(error::ErrorBadRequest)?; + + let records = server + .get_claim_transactions( + &XfrAddress { key }, + start, + end, + info.order == OrderOption::Desc, + ) + .c(d!()) + .map_err(error::ErrorBadRequest)?; + + Ok(web::Json(records)) +} + +/// Returns the list of transations associated with a given ledger address +pub async fn get_related_txns( + data: web::Data>>, + info: web::Path, +) -> actix_web::Result>> { + // Convert from base64 representation + let key: XfrPublicKey = XfrPublicKey::zei_from_bytes( + &b64dec(&*info) + .c(d!()) + .map_err(|e| error::ErrorBadRequest(e.to_string()))?, + ) + .c(d!()) + .map_err(|e| error::ErrorBadRequest(e.to_string()))?; + let server = data.read(); + let records = server.get_related_transactions(&XfrAddress { key }); + Ok(web::Json(records.unwrap_or_default())) +} + +/// Returns the list of transfer transations associated with a given asset +pub async fn get_related_xfrs( + data: web::Data>>, + info: web::Path, +) -> actix_web::Result>> { + let server = data.read(); + if let Ok(token_code) = AssetTypeCode::new_from_base64(&info) { + if let Some(records) = server.get_related_transfers(&token_code) { + Ok(web::Json(records)) + } else { + Err(actix_web::error::ErrorNotFound( + "Specified asset definition does not currently exist.", + )) + } + } else { + Err(actix_web::error::ErrorBadRequest( + "Invalid asset definition encoding.", + )) + } +} + +#[allow(missing_docs)] +#[allow(clippy::unnecessary_wraps)] + +pub async fn get_circulating_supply( + data: web::Data>>, +) -> actix_web::Result>, actix_web::error::Error> { + let l = data.read(); + let fra = FRA as f64; + + let cs = l.ledger_cloned.staking_get_global_unlocked_amount() as f64 / fra; + let gd = l.ledger_cloned.get_staking().get_global_delegation_amount() as f64 / fra; + let rr = l.ledger_cloned.staking_get_block_rewards_rate(); + let rr = rr[0] as f64 / rr[1] as f64; + + let res = map! { B + "global_return_rate" => rr, + "global_circulating_supply" => cs, + "global_delegation_amount" => gd + }; + + Ok(web::Json(res)) +} + +/// return +/// global_circulating_supply +/// global_adjusted_circulating_supply +/// global_total_supply +pub async fn get_total_supply( + data: web::Data>>, +) -> actix_web::Result>, actix_web::error::Error> { + let l = data.read(); + let burn_pubkey = *BLACK_HOLE_PUBKEY; + let extra_pubkey = *FF_PK_EXTRA_120_0000; + + let burn_balance = l + .ledger_cloned + .get_nonconfidential_balance(&burn_pubkey) + .unwrap_or(0); + let extra_balance = l + .ledger_cloned + .get_nonconfidential_balance(&extra_pubkey) + .unwrap_or(0); + + let fra = FRA as f64; + + let big_9 = l.ledger_cloned.staking_get_global_unlocked_amount(); + let big_8 = big_9 + extra_balance; + + let cs = big_8 as f64 / fra; + let acs = big_9 as f64 / fra; + let ts = (FRA_TOTAL_AMOUNT - burn_balance) as f64 / fra; + + let res = map! { B + "global_circulating_supply" => cs, + "global_adjusted_circulating_supply" => acs, + "global_total_supply" => ts + }; + + Ok(web::Json(res)) +} + #[inline(always)] #[allow(missing_docs)] pub async fn get_checkpoint( @@ -316,15 +506,46 @@ impl QueryApi { .data(Arc::clone(&server)) .route("/ping", web::get().to(ping)) .route("/version", web::get().to(version)) + .service( + web::resource("get_total_supply") + .route(web::get().to(get_total_supply)), + ) + .service( + web::resource("circulating_supply") + .route(web::get().to(get_circulating_supply)), + ) + .route( + &QueryServerRoutes::GetAddress.with_arg_template("txo_sid"), + web::get().to(get_address), + ) + .route( + &QueryServerRoutes::GetOwnedUtxos.with_arg_template("address"), + web::get().to(get_owned_utxos), + ) + .route( + &QueryServerRoutes::GetOwnerMemo.with_arg_template("txo_sid"), + web::get().to(get_owner_memo), + ) .route( &QueryServerRoutes::GetOwnerMemoBatch .with_arg_template("txo_sid_list"), web::get().to(get_owner_memo_batch), ) + .route( + &QueryServerRoutes::GetRelatedTxns.with_arg_template("address"), + web::get().to(get_related_txns), + ) + .service( + web::resource("claim_history").route(web::get().to(get_claim_txns)), + ) .service( web::resource("coinbase_history") .route(web::get().to(get_coinbase_oper_list)), ) + .route( + &QueryServerRoutes::GetRelatedXfrs.with_arg_template("asset_token"), + web::get().to(get_related_xfrs), + ) .route( &QueryServerRoutes::GetCreatedAssets.with_arg_template("address"), web::get().to(get_created_assets), @@ -395,10 +616,30 @@ impl QueryApi { &ApiRoutes::OwnedUtxos.with_arg_template("owner"), web::get().to(query_owned_utxos), ) + .route( + &ApiRoutes::ValidatorList.route(), + web::get().to(query_validators), + ) .route( &ApiRoutes::DelegationInfo.with_arg_template("XfrPublicKey"), web::get().to(query_delegation_info), ) + .route( + &ApiRoutes::DelegatorList.with_arg_template("NodeAddress"), + web::get().to(query_delegator_list), + ) + .service( + web::resource("/delegator_list") + .route(web::get().to(get_delegators_with_params)), + ) + .service( + web::resource("/delegation_rewards") + .route(web::get().to(get_delegation_reward)), + ) + .service( + web::resource("/validator_delegation") + .route(web::get().to(get_validator_delegation_history)), + ) .route( &ApiRoutes::ValidatorDetail.with_arg_template("NodeAddress"), web::get().to(query_validator_detail), diff --git a/src/components/abciapp/src/api/query_server/query_api/server.rs b/src/components/abciapp/src/api/query_server/query_api/server.rs index 2350f7e2b..f05e5ebf2 100644 --- a/src/components/abciapp/src/api/query_server/query_api/server.rs +++ b/src/components/abciapp/src/api/query_server/query_api/server.rs @@ -6,15 +6,15 @@ use { lazy_static::lazy_static, ledger::{ data_model::{ - AssetTypeCode, DefineAsset, IssuerPublicKey, TxOutput, TxnIDHash, TxnSID, - TxoSID, XfrAddress, + AssetTypeCode, DefineAsset, IssuerPublicKey, Transaction, TxOutput, + TxnIDHash, TxnSID, TxoSID, XfrAddress, }, - staking::ops::mint_fra::MintEntry, + staking::{ops::mint_fra::MintEntry, BlockHeight}, store::LedgerState, }, parking_lot::{Condvar, Mutex, RwLock}, ruc::*, - std::sync::Arc, + std::{collections::HashSet, sync::Arc}, zei::xfr::structs::OwnerMemo, }; @@ -138,6 +138,107 @@ impl QueryServer { Ok((0, vec![])) } + /// Returns a list of claim transactions of a given ledger address + pub fn get_claim_transactions( + &self, + address: &XfrAddress, + start: usize, + end: usize, + order_desc: bool, + ) -> Result>> { + if let Some(hist) = self + .ledger_cloned + .api_cache + .as_ref() + .unwrap() + .claim_hist_txns + .get(address) + { + let len = hist.len(); + if len > start { + let slice = match order_desc { + false => { + let mut new_end = len; + if len > end { + new_end = end; + } + hist.iter() + .skip(start.saturating_sub(1)) + .take((new_end + 1) - start) + .map(|(k, _)| k) + .collect() + } + true => { + let mut new_start = 0; + if len > end { + new_start = len - end; + } + let mut tmp = hist + .iter() + .skip(new_start.saturating_sub(1)) + .take((len - start + 1) - new_start) + .map(|(k, _)| k) + .collect::>(); + tmp.reverse(); + tmp + } + }; + + return Ok(slice + .iter() + .map(|h| { + if let Ok(tx) = + ruc::info!(self.ledger_cloned.get_transaction_light(*h)) + { + Some(tx.txn) + } else { + None + } + }) + .collect()); + } + } + + Err(eg!("Record not found")) + } + + /// Returns the set of transactions that are in some way related to a given ledger address. + /// An xfr address is related to a transaction if it is one of the following: + /// 1. Owner of a transfer output + /// 2. Transfer signer (owner of input or co-signer) + /// 3. Signer of a an issuance txn + /// 4. Signer of a kv_update txn + /// 5. Signer of a memo_update txn + #[inline(always)] + pub fn get_related_transactions( + &self, + address: &XfrAddress, + ) -> Option> { + self.ledger_cloned + .api_cache + .as_ref() + .unwrap() + .related_transactions + .get(&address) + .map(|d| d.iter().map(|(k, _)| k).collect()) + } + + /// Returns the set of transfer transactions that are associated with a given asset. + /// The asset type must be nonconfidential. + #[inline(always)] + pub fn get_related_transfers( + &self, + code: &AssetTypeCode, + ) -> Option> { + self.ledger_cloned + .api_cache + .as_ref() + .unwrap() + .related_transfers + .get(&code) + .map(|d| d.iter().map(|(k, _)| k).collect()) + } + /// Returns the owner of a given txo_sid. #[inline(always)] pub fn get_address_of_sid(&self, txo_sid: TxoSID) -> Option { @@ -199,6 +300,17 @@ impl QueryServer { .get(&txo_sid) } + /// retrieve block reward rate at specified block height + #[inline(always)] + pub fn query_block_rewards_rate(&self, height: &BlockHeight) -> Option<[u128; 2]> { + self.ledger_cloned + .api_cache + .as_ref() + .unwrap() + .staking_global_rate_hist + .get(height) + } + /// update after a new block is created #[inline(always)] pub fn update(&mut self) { diff --git a/src/components/finutils/Cargo.toml b/src/components/finutils/Cargo.toml index 8d3a84dcc..d76cef1cd 100644 --- a/src/components/finutils/Cargo.toml +++ b/src/components/finutils/Cargo.toml @@ -20,7 +20,6 @@ rand_chacha = "0.2" curve25519-dalek = { version = "3.0", features = ["serde"] } wasm-bindgen = { version = "=0.2.84", features = ["serde-serialize"] } sha2 = "0.10" -sha3 = "0.8" zei = { git = "https://github.com/FindoraNetwork/zei", branch = "stable-main" } ruc = "1.0" diff --git a/src/components/finutils/src/common/mod.rs b/src/components/finutils/src/common/mod.rs index 229200837..ab00ab060 100644 --- a/src/components/finutils/src/common/mod.rs +++ b/src/components/finutils/src/common/mod.rs @@ -18,7 +18,6 @@ pub mod utils; use { self::utils::{get_evm_staking_address, get_validator_memo_and_rate}, crate::api::DelegationInfo, - crate::common::utils::mapping_address, globutils::wallet, lazy_static::lazy_static, ledger::{ @@ -292,11 +291,11 @@ pub fn claim(td_addr: &str, am: Option<&str>, sk_str: Option<&str>) -> Result<() pub fn show(basic: bool) -> Result<()> { let kp = get_keypair().c(d!())?; - ruc::info!(get_serv_addr()).map(|i| { + let serv_addr = ruc::info!(get_serv_addr()).map(|i| { println!("\x1b[31;01mServer URL:\x1b[00m\n{i}\n"); - })?; + }); - ruc::info!(get_keypair()).map(|i| { + let xfr_account = ruc::info!(get_keypair()).map(|i| { println!( "\x1b[31;01mFindora Address:\x1b[00m\n{}\n", wallet::public_key_to_bech32(&i.get_pk()) @@ -305,34 +304,66 @@ pub fn show(basic: bool) -> Result<()> { "\x1b[31;01mFindora Public Key:\x1b[00m\n{}\n", wallet::public_key_to_base64(&i.get_pk()) ); - })?; + }); - ruc::info!(utils::get_balance(&kp)).map(|i| { + let self_balance = ruc::info!(utils::get_balance(&kp)).map(|i| { println!("\x1b[31;01mNode Balance:\x1b[00m\n{i} FRA units\n"); - })?; + }); if basic { return Ok(()); } - ruc::info!(get_td_pubkey()).map(|i| { + let td_info = ruc::info!(get_td_pubkey()).map(|i| { let addr = td_pubkey_to_td_addr(&i); println!("\x1b[31;01mValidator Node Addr:\x1b[00m\n{addr}\n"); (i, addr) - })?; + }); - let evm_staking_address = get_evm_staking_address()?; - let url = format!("{}:8545", get_serv_addr()?); - let address = utils::get_trigger_on_contract_address(&url, evm_staking_address)?; - let (bound_amount, unbound_amount) = - utils::get_evm_delegation_info(&url, address, mapping_address(kp.get_pk_ref()))?; + let di = utils::get_delegation_info(kp.get_pk_ref()); + let bond_entries = match di.as_ref() { + Ok(di) => Some(di.bond_entries.clone()), + Err(_) => None, + }; - println!( - "\x1b[31;01mYour Delegation:\x1b[00m\nbound_amount:{}\nunbound_amount:{}\n", - bound_amount, unbound_amount - ); + let delegation_info = di.and_then(|di| { + serde_json::to_string_pretty(&di).c(d!("server returned invalid data")) + }); + let delegation_info = ruc::info!(delegation_info).map(|i| { + println!("\x1b[31;01mYour Delegation:\x1b[00m\n{i}\n"); + }); + + if let Ok((tpk, addr)) = td_info.as_ref() { + let self_delegation = + bond_entries.map_or(false, |bes| bes.iter().any(|i| &i.0 == addr)); + if self_delegation { + let res = utils::get_validator_detail(&td_pubkey_to_td_addr(tpk)) + .c(d!("Validator not found")) + .and_then(|di| { + serde_json::to_string_pretty(&di) + .c(d!("server returned invalid data")) + }) + .map(|i| { + println!("\x1b[31;01mYour Staking:\x1b[00m\n{i}\n"); + }); + ruc::info_omit!(res); + } + } - Ok(()) + if [ + serv_addr, + xfr_account, + td_info.map(|_| ()), + self_balance, + delegation_info, + ] + .iter() + .any(|i| i.is_err()) + { + Err(eg!("unable to obtain complete information")) + } else { + Ok(()) + } } /// Setup for a cli tool diff --git a/src/components/finutils/src/common/utils.rs b/src/components/finutils/src/common/utils.rs index 24b16dafa..9b0dfc99a 100644 --- a/src/components/finutils/src/common/utils.rs +++ b/src/components/finutils/src/common/utils.rs @@ -23,7 +23,6 @@ use { serde::{self, Deserialize, Serialize}, serde_json::Value, sha2::{Digest, Sha256}, - sha3::Keccak256, std::{ collections::{BTreeMap, HashMap}, str::FromStr, @@ -33,7 +32,7 @@ use { web3::{ ethabi::{Function, Param, ParamType, StateMutability, Token}, transports::Http, - types::{BlockId, BlockNumber, Bytes, CallRequest, H160, U256}, + types::{BlockId, BlockNumber, Bytes, CallRequest, H160}, Web3, }, zei::xfr::{ @@ -746,122 +745,3 @@ pub fn get_validator_memo_and_rate( }; Ok((memo, rate)) } - -#[allow(missing_docs)] -pub fn get_evm_delegation_info( - url: &str, - staking_address: H160, - address: H160, -) -> Result<(U256, U256)> { - let transport = Http::new(url).c(d!())?; - let web3 = Web3::new(transport); - - #[allow(deprecated)] - let function = Function { - name: "delegators".to_owned(), - inputs: vec![ - Param { - name: String::new(), - kind: ParamType::Address, - internal_type: Some(String::from("address")), - }, - Param { - name: String::new(), - kind: ParamType::Address, - internal_type: Some(String::from("address")), - }, - ], - outputs: vec![ - Param { - name: String::from("boundAmount"), - kind: ParamType::Uint(256), - internal_type: Some(String::from("uint256")), - }, - Param { - name: String::from("unboundAmount"), - kind: ParamType::Uint(256), - internal_type: Some(String::from("uint256")), - }, - ], - constant: None, - state_mutability: StateMutability::View, - }; - let data = function - .encode_input(&[Token::Address(staking_address), Token::Address(address)]) - .map_err(|e| eg!("{:?}", e))?; - - let ret_data = Runtime::new() - .c(d!())? - .block_on(web3.eth().call( - CallRequest { - to: Some(staking_address), - data: Some(Bytes(data)), - ..Default::default() - }, - Some(BlockId::Number(BlockNumber::Latest)), - )) - .c(d!())?; - - let ret = function.decode_output(&ret_data.0).c(d!())?; - let bound_amount = if let Some(Token::Uint(bound_amount)) = ret.get(0) { - bound_amount - } else { - return Err(eg!("bound_amount not found")); - }; - let unbound_amount = if let Some(Token::Uint(unbound_amount)) = ret.get(1) { - unbound_amount - } else { - return Err(eg!("unbound_amount not found")); - }; - Ok((*bound_amount, *unbound_amount)) -} - -#[allow(missing_docs)] -pub fn get_trigger_on_contract_address( - url: &str, - staking_address: H160, -) -> Result { - let transport = Http::new(url).c(d!())?; - let web3 = Web3::new(transport); - - #[allow(deprecated)] - let function = Function { - name: "getTriggerOnContractAddress".to_owned(), - inputs: vec![], - outputs: vec![Param { - name: String::new(), - kind: ParamType::Address, - internal_type: Some(String::from("address")), - }], - constant: None, - state_mutability: StateMutability::View, - }; - let data = function.encode_input(&[]).map_err(|e| eg!("{:?}", e))?; - - let ret_data = Runtime::new() - .c(d!())? - .block_on(web3.eth().call( - CallRequest { - to: Some(staking_address), - data: Some(Bytes(data)), - ..Default::default() - }, - Some(BlockId::Number(BlockNumber::Latest)), - )) - .c(d!())?; - - let ret = function.decode_output(&ret_data.0).c(d!())?; - let address = if let Some(Token::Address(address)) = ret.get(0) { - address - } else { - return Err(eg!("bound_amount not found")); - }; - - Ok(*address) -} - -#[allow(missing_docs)] -pub fn mapping_address(pk: &XfrPublicKey) -> H160 { - let result = ::digest(pk.as_bytes()); - H160::from_slice(&result.as_slice()[..20]) -} diff --git a/src/ledger/Cargo.toml b/src/ledger/Cargo.toml index 61e9f8c4d..9ea5afd6c 100644 --- a/src/ledger/Cargo.toml +++ b/src/ledger/Cargo.toml @@ -56,7 +56,7 @@ abci_mock = [] lazy_static = "1.4.0" [dependencies.fixed] -version = "=1.19.0" +version = "1.19.0" features = ["f16", "serde"] [target.'cfg(not(target_arch = "wasm32"))'.dependencies] diff --git a/src/ledger/src/data_model/mod.rs b/src/ledger/src/data_model/mod.rs index 0cc08b1c3..d3b2a54e7 100644 --- a/src/ledger/src/data_model/mod.rs +++ b/src/ledger/src/data_model/mod.rs @@ -414,6 +414,17 @@ pub struct XfrAddress { pub key: XfrPublicKey, } +impl XfrAddress { + #[cfg(not(target_arch = "wasm32"))] + pub(crate) fn to_base64(self) -> String { + b64enc(&self.key.as_bytes()) + } + + // pub(crate) fn to_bytes(self) -> Vec { + // self.key.as_bytes().to_vec() + // } +} + impl Hash for XfrAddress { #[inline(always)] fn hash(&self, state: &mut H) { diff --git a/src/ledger/src/store/api_cache.rs b/src/ledger/src/store/api_cache.rs index caa041726..465613efd 100644 --- a/src/ledger/src/store/api_cache.rs +++ b/src/ledger/src/store/api_cache.rs @@ -8,15 +8,19 @@ use { Operation, Transaction, TxOutput, TxnIDHash, TxnSID, TxoSID, XfrAddress, ASSET_TYPE_FRA, }, - staking::{ops::mint_fra::MintEntry, BlockHeight, KEEP_HIST}, + staking::{ + ops::mint_fra::MintEntry, Amount, BlockHeight, DelegationRwdDetail, + CHAN_D_AMOUNT_HIST, CHAN_GLOB_RATE_HIST, CHAN_V_SELF_D_HIST, KEEP_HIST, + }, store::LedgerState, }, config::abci::global_cfg::CFG, fbnc::{new_mapx, new_mapxnk, Mapx, Mapxnk}, + globutils::wallet, ruc::*, serde::{Deserialize, Serialize}, std::collections::HashSet, - zei::xfr::structs::OwnerMemo, + zei::xfr::{sig::XfrPublicKey, structs::OwnerMemo}, }; type Issuances = Vec<(TxOutput, Option)>; @@ -25,6 +29,12 @@ type Issuances = Vec<(TxOutput, Option)>; #[derive(Clone, Deserialize, Serialize)] pub struct ApiCache { pub(crate) prefix: String, + /// Set of transactions related to a ledger address + pub related_transactions: Mapx>, + /// Set of transfer transactions related to an asset code + pub related_transfers: Mapx>, + /// List of claim transactions related to a ledger address + pub claim_hist_txns: Mapx>, /// Payments from coinbase pub coinbase_oper_hist: Mapx>, /// Created assets @@ -43,6 +53,18 @@ pub struct ApiCache { pub txn_sid_to_hash: Mapxnk, /// txn hash to txn sid pub txn_hash_to_sid: Mapx, + /// global rate history + pub staking_global_rate_hist: Mapxnk, + /// - self-delegation amount history + /// - `NonConfidential` FRAs amount + /// - only valid for validators + pub staking_self_delegation_hist: Mapx>, + /// - delegation amount per block height + /// - only valid for a validator + pub staking_delegation_amount_hist: Mapx>, + /// rewards history, used on some pulic nodes, such as fullnode + pub staking_delegation_rwd_hist: + Mapx>, /// there are no transactions lost before last_sid pub last_sid: Mapx, } @@ -51,6 +73,13 @@ impl ApiCache { pub(crate) fn new(prefix: &str) -> Self { ApiCache { prefix: prefix.to_owned(), + related_transactions: new_mapx!(format!( + "api_cache/{prefix}related_transactions", + )), + related_transfers: new_mapx!( + format!("api_cache/{prefix}related_transfers",) + ), + claim_hist_txns: new_mapx!(format!("api_cache/{prefix}claim_hist_txns",)), coinbase_oper_hist: new_mapx!(format!( "api_cache/{prefix}coinbase_oper_hist", )), @@ -66,6 +95,18 @@ impl ApiCache { txo_to_txnid: new_mapxnk!(format!("api_cache/{prefix}txo_to_txnid",)), txn_sid_to_hash: new_mapxnk!(format!("api_cache/{prefix}txn_sid_to_hash",)), txn_hash_to_sid: new_mapx!(format!("api_cache/{prefix}txn_hash_to_sid",)), + staking_global_rate_hist: new_mapxnk!(format!( + "api_cache/{prefix}staking_global_rate_hist", + )), + staking_self_delegation_hist: new_mapx!(format!( + "api_cache/{prefix}staking_self_delegation_hist", + )), + staking_delegation_amount_hist: new_mapx!(format!( + "api_cache/{prefix}staking_delegation_amount_hist", + )), + staking_delegation_rwd_hist: new_mapx!(format!( + "api_cache/{prefix}staking_delegation_rwd_hist", + )), last_sid: new_mapx!(format!("api_cache/{prefix}last_sid",)), } } @@ -120,6 +161,70 @@ impl ApiCache { let token_code = issuance.body.code; save_issuance!(token_issuances, token_code); } + + /// Cache history style data + /// + /// Note: This function's data will migrate to findora scanner. + pub fn cache_hist_data(&mut self) { + CHAN_GLOB_RATE_HIST.1.lock().try_iter().for_each(|(h, r)| { + self.staking_global_rate_hist.insert(h, r); + }); + + CHAN_V_SELF_D_HIST + .1 + .lock() + .try_iter() + .for_each(|(pk, h, r)| { + self.staking_self_delegation_hist + .entry(pk) + .or_insert(new_mapxnk!(format!( + "staking_self_delegation_hist_subdata/{}", + wallet::public_key_to_base64(&pk) + ))) + .insert(h, r); + }); + + CHAN_D_AMOUNT_HIST + .1 + .lock() + .try_iter() + .for_each(|(pk, h, r)| { + self.staking_delegation_amount_hist + .entry(pk) + .or_insert(new_mapxnk!(format!( + "staking_delegation_amount_hist_subdata/{}", + wallet::public_key_to_base64(&pk) + ))) + .insert(h, r); + }); + + // CHAN_D_RWD_HIST.1.lock().try_iter().for_each(|(pk, h, r)| { + // #[allow(unused_mut)] + // let mut dd = + // self.staking_delegation_rwd_hist + // .entry(pk) + // .or_insert(new_mapxnk!(format!( + // "staking_delegation_rwd_hist_subdata/{}", + // wallet::public_key_to_base64(&pk) + // ))); + // let mut dd = dd.entry(h).or_insert_with(DelegationRwdDetail::default); + // + // dd.block_height = r.block_height; + // dd.amount += r.amount; + // dd.penalty_amount += r.penalty_amount; + // + // alt!(0 < r.bond, dd.bond = r.bond); + // alt!(r.return_rate.is_some(), dd.return_rate = r.return_rate); + // alt!( + // r.commission_rate.is_some(), + // dd.commission_rate = r.commission_rate + // ); + // alt!( + // r.global_delegation_percent.is_some(), + // dd.global_delegation_percent = r.global_delegation_percent + // ); + // }); + } } /// An xfr address is related to a transaction if it is one of the following: @@ -366,12 +471,16 @@ pub fn update_api_cache(ledger: &mut LedgerState) -> Result<()> { check_lost_data(ledger)?; + ledger.api_cache.as_mut().unwrap().cache_hist_data(); + let block = if let Some(b) = ledger.blocks.last() { b } else { return Ok(()); }; + let prefix = ledger.api_cache.as_mut().unwrap().prefix.clone(); + // Update ownership status for (txn_sid, txo_sids) in block.txns.iter().map(|v| (v.tx_id, v.txo_ids.as_slice())) { @@ -397,6 +506,90 @@ pub fn update_api_cache(ledger: &mut LedgerState) -> Result<()> { (addresses, owner_memos) }; + let classify_op = |op: &Operation| { + match op { + Operation::Claim(i) => { + let key = XfrAddress { + key: i.get_claim_publickey(), + }; + ledger + .api_cache + .as_mut() + .unwrap() + .claim_hist_txns + .entry(key) + .or_insert_with(|| { + new_mapxnk!(format!( + "api_cache/{}claim_hist_txns/{}", + prefix, + key.to_base64() + )) + }) + .set_value(txn_sid, Default::default()); + } + Operation::MintFra(i) => i.entries.iter().for_each(|me| { + let key = XfrAddress { + key: me.utxo.record.public_key, + }; + #[allow(unused_mut)] + let mut hist = ledger + .api_cache + .as_mut() + .unwrap() + .coinbase_oper_hist + .entry(key) + .or_insert_with(|| { + new_mapxnk!(format!( + "api_cache/{}coinbase_oper_hist/{}", + prefix, + key.to_base64() + )) + }); + hist.insert(i.height, me.clone()); + }), + _ => { /* filter more operations before this line */ } + }; + }; + + // Update related addresses + // Apply classify_op for each operation in curr_txn + let related_addresses = get_related_addresses(&curr_txn, classify_op); + for address in &related_addresses { + ledger + .api_cache + .as_mut() + .unwrap() + .related_transactions + .entry(*address) + .or_insert_with(|| { + new_mapxnk!(format!( + "api_cache/{}related_transactions/{}", + prefix, + address.to_base64() + )) + }) + .insert(txn_sid, Default::default()); + } + + // Update transferred nonconfidential assets + let transferred_assets = get_transferred_nonconfidential_assets(&curr_txn); + for asset in &transferred_assets { + ledger + .api_cache + .as_mut() + .unwrap() + .related_transfers + .entry(*asset) + .or_insert_with(|| { + new_mapxnk!(format!( + "api_cache/{}related_transfers/{}", + &prefix, + asset.to_base64() + )) + }) + .insert(txn_sid, Default::default()); + } + // Add created asset for op in &curr_txn.body.operations { match op {