From 75c95070342f3088713f6532858f9497d8a8841c Mon Sep 17 00:00:00 2001 From: Mateusz Jasiuk Date: Thu, 23 May 2024 13:59:04 +0200 Subject: [PATCH 1/8] feat: balances and validators lists improvements --- chain/src/repository/pos.rs | 1 + .../2024-04-30-081808_init_validators/up.sql | 1 + orm/src/schema.rs | 1 + orm/src/validators.rs | 2 + pos/src/services/namada.rs | 1 + shared/src/block.rs | 2 + shared/src/validator.rs | 6 +- swagger.yml | 80 ++++++++++++++----- webserver/Cargo.toml | 4 +- webserver/src/app.rs | 4 + webserver/src/dto/pos.rs | 6 +- webserver/src/dto/utils.rs | 2 +- webserver/src/handler/pos.rs | 19 ++++- webserver/src/repository/pos.rs | 29 +++++++ webserver/src/response/balance.rs | 4 +- webserver/src/response/pos.rs | 2 + webserver/src/service/balance.rs | 16 +++- webserver/src/service/pos.rs | 20 +++++ 18 files changed, 168 insertions(+), 32 deletions(-) diff --git a/chain/src/repository/pos.rs b/chain/src/repository/pos.rs index 5d676b891..02994063d 100644 --- a/chain/src/repository/pos.rs +++ b/chain/src/repository/pos.rs @@ -110,6 +110,7 @@ pub fn update_validator_metadata( for metadata in metadata_change { let metadata_change_db = ValidatorUpdateMetadataDb { commission: metadata.commission, + name: metadata.name, email: metadata.email, website: metadata.website, description: metadata.description, diff --git a/orm/migrations/2024-04-30-081808_init_validators/up.sql b/orm/migrations/2024-04-30-081808_init_validators/up.sql index 7f9959971..84911bba1 100644 --- a/orm/migrations/2024-04-30-081808_init_validators/up.sql +++ b/orm/migrations/2024-04-30-081808_init_validators/up.sql @@ -6,6 +6,7 @@ CREATE TABLE validators ( voting_power INT NOT NULL, max_commission VARCHAR NOT NULL, commission VARCHAR NOT NULL, + name VARCHAR, email VARCHAR, website VARCHAR, description VARCHAR, diff --git a/orm/src/schema.rs b/orm/src/schema.rs index 15c38ccd2..de118ef9a 100644 --- a/orm/src/schema.rs +++ b/orm/src/schema.rs @@ -118,6 +118,7 @@ diesel::table! { voting_power -> Int4, max_commission -> Varchar, commission -> Varchar, + name -> Nullable, email -> Nullable, website -> Nullable, description -> Nullable, diff --git a/orm/src/validators.rs b/orm/src/validators.rs index e9ab9e363..c2115070c 100644 --- a/orm/src/validators.rs +++ b/orm/src/validators.rs @@ -15,6 +15,7 @@ pub struct ValidatorDb { pub voting_power: i32, pub max_commission: String, pub commission: String, + pub name: Option, pub email: Option, pub website: Option, pub description: Option, @@ -37,6 +38,7 @@ pub struct ValidatorInsertDb { #[diesel(check_for_backend(diesel::pg::Pg))] pub struct ValidatorUpdateMetadataDb { pub commission: Option, + pub name: Option, pub email: Option, pub website: Option, pub description: Option, diff --git a/pos/src/services/namada.rs b/pos/src/services/namada.rs index 46a2ca684..691fac893 100644 --- a/pos/src/services/namada.rs +++ b/pos/src/services/namada.rs @@ -63,6 +63,7 @@ pub async fn get_validator_set_at_epoch( voting_power: voting_power.to_string_native(), max_commission, commission, + name: None, email: None, description: None, website: None, diff --git a/shared/src/block.rs b/shared/src/block.rs index 1f08953f9..713e733b0 100644 --- a/shared/src/block.rs +++ b/shared/src/block.rs @@ -452,6 +452,7 @@ impl Block { commission: metadata_change_data .commission_rate .map(|c| c.to_string()), + name: metadata_change_data.name, email: metadata_change_data.email, description: metadata_change_data.description, website: metadata_change_data.website, @@ -473,6 +474,7 @@ impl Block { commission: Some( commission_change.new_rate.to_string(), ), + name: None, email: None, description: None, website: None, diff --git a/shared/src/validator.rs b/shared/src/validator.rs index a62ca9f3c..7b5a97e30 100644 --- a/shared/src/validator.rs +++ b/shared/src/validator.rs @@ -19,6 +19,7 @@ pub struct Validator { pub voting_power: VotingPower, pub max_commission: String, pub commission: String, + pub name: Option, pub email: Option, pub description: Option, pub website: Option, @@ -30,6 +31,7 @@ pub struct Validator { pub struct ValidatorMetadataChange { pub address: Id, pub commission: Option, + pub name: Option, pub email: Option, pub description: Option, pub website: Option, @@ -47,9 +49,10 @@ impl Validator { let commission = ((0..100).fake::() as f64 / 100_f64).to_string(); let email = Some(SafeEmail().fake()); let description: Option = CatchPhase().fake(); + let name = Some(CompanyName().fake::()); let website: Option = Some(format!( "{}.{}", - CompanyName().fake::(), + name.clone().unwrap(), DomainSuffix().fake::() )); let discord_handler: Option = Username().fake(); @@ -59,6 +62,7 @@ impl Validator { voting_power, max_commission, commission, + name, email, description, website, diff --git a/swagger.yml b/swagger.yml index 319711129..941e90795 100644 --- a/swagger.yml +++ b/swagger.yml @@ -16,6 +16,13 @@ paths: description: Health check /api/v1/pos/validator: get: + parameters: + - in: query + name: page + schema: + type: integer + minimum: 1 + description: Pagination parameter responses: '200': description: A list of validator. @@ -29,20 +36,36 @@ paths: items: $ref: '#/components/schemas/Validator' pagination: - type: object - properties: - page: - type: integer - minimum: 0 - per_page: - type: integer - minimum: 0 - total_pages: - type: integer - minimum: 0 - total_items: - type: integer - minimum: 0 + $ref: '#/components/schemas/Pagination' + /api/v1/pos/validator/{address}: + get: + parameters: + - in: path + name: address + schema: + type: string + required: true + description: Delegator address + - in: query + name: page + schema: + type: integer + minimum: 1 + description: Pagination parameter + responses: + '200': + description: A list of validator. + content: + application/json: + schema: + type: object + properties: + data: + type: array + items: + $ref: '#/components/schemas/Validator' + pagination: + $ref: '#/components/schemas/Pagination' /api/v1/pos/reward/{address}: get: summary: Get all the rewards for an address @@ -322,7 +345,9 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/Balance' + type: array + items: + $ref: '#/components/schemas/Balance' components: schemas: @@ -334,14 +359,14 @@ components: type: integer address: type: string - name: - type: string voting_power: type: string max_commission: type: string commission: type: string + name: + type: string email: type: string website: @@ -441,10 +466,25 @@ components: type: integer Balance: type: object + required: [token, balance] properties: - token: + tokenAddress: type: string balance: - type: number - format: float + type: string + Pagination: + type: object + properties: + page: + type: integer + minimum: 0 + per_page: + type: integer + minimum: 0 + total_pages: + type: integer + minimum: 0 + total_items: + type: integer minimum: 0 + diff --git a/webserver/Cargo.toml b/webserver/Cargo.toml index 6b1ccc1bb..92ccff880 100644 --- a/webserver/Cargo.toml +++ b/webserver/Cargo.toml @@ -40,8 +40,8 @@ orm.workspace = true diesel.workspace = true futures.workspace = true tokio-stream.workspace = true -# shared.workspace = true +namada_core.workspace = true deadpool-redis = "0.13.0" [build-dependencies] -vergen = { version = "8.0.0", features = ["build", "git", "gitcl"] } \ No newline at end of file +vergen = { version = "8.0.0", features = ["build", "git", "gitcl"] } diff --git a/webserver/src/app.rs b/webserver/src/app.rs index 17b2c9350..58753040f 100644 --- a/webserver/src/app.rs +++ b/webserver/src/app.rs @@ -43,6 +43,10 @@ impl ApplicationServer { Router::new() .route("/pos/validator", get(pos_handlers::get_validators)) + .route( + "/pos/validator/:address", + get(pos_handlers::get_validators_by_delegator), + ) .route("/pos/bond/:address", get(pos_handlers::get_bonds)) .route("/pos/unbond/:address", get(pos_handlers::get_unbonds)) .route( diff --git a/webserver/src/dto/pos.rs b/webserver/src/dto/pos.rs index 5195e9eae..2a54b1843 100644 --- a/webserver/src/dto/pos.rs +++ b/webserver/src/dto/pos.rs @@ -1,10 +1,8 @@ use serde::{Deserialize, Serialize}; use validator::Validate; -use super::utils::Pagination; - #[derive(Clone, Serialize, Deserialize, Validate)] pub struct PoSQueryParams { - #[serde(flatten)] - pub pagination: Option, + #[validate(range(min = 1, max = 10000))] + pub page: Option, } diff --git a/webserver/src/dto/utils.rs b/webserver/src/dto/utils.rs index bb9f802d3..aea26e990 100644 --- a/webserver/src/dto/utils.rs +++ b/webserver/src/dto/utils.rs @@ -1,7 +1,7 @@ use serde::{Deserialize, Serialize}; use validator::Validate; -#[derive(Clone, Serialize, Deserialize, Validate)] +#[derive(Debug, Clone, Serialize, Deserialize, Validate)] pub struct Pagination { #[validate(range(min = 1, max = 10000))] pub page: u64, diff --git a/webserver/src/handler/pos.rs b/webserver/src/handler/pos.rs index bc28d7d62..d71fb9b8a 100644 --- a/webserver/src/handler/pos.rs +++ b/webserver/src/handler/pos.rs @@ -19,13 +19,30 @@ pub async fn get_validators( Query(query): Query, State(state): State, ) -> Result>>, ApiError> { - let page = query.pagination.map(|p| p.page).unwrap_or(1); + let page = query.page.unwrap_or(1); let (validators, total_validators) = state.pos_service.get_all_validators(page).await?; let response = PaginatedResponse::new(validators, page, total_validators); Ok(Json(response)) } +#[debug_handler] +pub async fn get_validators_by_delegator( + _trace_id: TraceId, + _headers: HeaderMap, + Query(query): Query, + Path(address): Path, + State(state): State, +) -> Result>>, ApiError> { + let page = query.page.unwrap_or(1); + let (validators, total_validators) = state + .pos_service + .get_validators_by_delegator(address, page) + .await?; + + let response = PaginatedResponse::new(validators, page, total_validators); + Ok(Json(response)) +} #[debug_handler] pub async fn get_bonds( diff --git a/webserver/src/repository/pos.rs b/webserver/src/repository/pos.rs index f9cc47af0..56cc93d51 100644 --- a/webserver/src/repository/pos.rs +++ b/webserver/src/repository/pos.rs @@ -27,6 +27,12 @@ pub trait PosRepositoryTrait { page: i64, ) -> Result<(Vec, i64), String>; + async fn find_validators_by_delegator( + &self, + address: String, + page: i64, + ) -> Result<(Vec, i64), String>; + async fn find_validator_by_id( &self, id: i32, @@ -79,6 +85,29 @@ impl PosRepositoryTrait for PosRepository { .map_err(|e| e.to_string()) } + async fn find_validators_by_delegator( + &self, + address: String, + page: i64, + ) -> Result<(Vec, i64), String> { + let conn = self.app_state.get_db_connection().await; + + conn.interact(move |conn| { + let validator_ids = bonds::table + .filter(bonds::dsl::address.eq(address)) + .select(bonds::dsl::validator_id); + + validators::table + .select(ValidatorDb::as_select()) + .filter(validators::dsl::id.eq_any(validator_ids)) + .paginate(page) + .load_and_count_pages(conn) + }) + .await + .map_err(|e| e.to_string())? + .map_err(|e| e.to_string()) + } + async fn find_validator_by_id( &self, validator_id: i32, diff --git a/webserver/src/response/balance.rs b/webserver/src/response/balance.rs index 72fdc3ccd..e534be83f 100644 --- a/webserver/src/response/balance.rs +++ b/webserver/src/response/balance.rs @@ -5,14 +5,14 @@ use serde::{Deserialize, Serialize}; #[serde(rename_all = "camelCase")] pub struct AddressBalance { pub token_address: String, - pub balances: String, + pub balance: String, } impl From for AddressBalance { fn from(value: BalanceDb) -> Self { Self { token_address: value.token, - balances: value.raw_amount, + balance: value.raw_amount, } } } diff --git a/webserver/src/response/pos.rs b/webserver/src/response/pos.rs index cfca55f78..43e894aa4 100644 --- a/webserver/src/response/pos.rs +++ b/webserver/src/response/pos.rs @@ -11,6 +11,7 @@ pub struct Validator { pub voting_power: String, pub max_commission: String, pub commission: String, + pub name: Option, pub email: Option, pub website: Option, pub description: Option, @@ -61,6 +62,7 @@ impl From for Validator { voting_power: value.voting_power.to_string(), max_commission: value.max_commission, commission: value.commission, + name: value.name, email: value.email, website: value.website, description: value.description, diff --git a/webserver/src/service/balance.rs b/webserver/src/service/balance.rs index 8c61d48ab..6430b7cf6 100644 --- a/webserver/src/service/balance.rs +++ b/webserver/src/service/balance.rs @@ -1,3 +1,5 @@ +use namada_core::token::Amount; + use crate::appstate::AppState; use crate::error::balance::BalanceError; use crate::repository::balance::{BalanceRepo, BalanceRepoTrait}; @@ -25,6 +27,18 @@ impl BalanceService { .await .map_err(BalanceError::Database)?; - Ok(balances.into_iter().map(AddressBalance::from).collect()) + // TODO: temporary solution as we only store NAM balances + let denominated_balances: Vec = balances + .iter() + .cloned() + .map(|balance| AddressBalance { + token_address: balance.token, + balance: Amount::from_str(balance.raw_amount, 0) + .unwrap() + .to_string_native(), + }) + .collect(); + + Ok(denominated_balances) } } diff --git a/webserver/src/service/pos.rs b/webserver/src/service/pos.rs index 5142648b5..91cd6a7b1 100644 --- a/webserver/src/service/pos.rs +++ b/webserver/src/service/pos.rs @@ -34,6 +34,26 @@ impl PosService { )) } + pub async fn get_validators_by_delegator( + &self, + address: String, + page: u64, + ) -> Result<(Vec, u64), PoSError> { + let (db_validators, total_items) = self + .pos_repo + .find_validators_by_delegator(address, page as i64) + .await + .map_err(PoSError::Database)?; + + Ok(( + db_validators + .into_iter() + .map(ValidatorWithId::from) + .collect(), + total_items as u64, + )) + } + pub async fn get_bonds_by_address( &self, address: String, From 51e4f2000545a58b94b303d3936339d72016e744 Mon Sep 17 00:00:00 2001 From: Mateusz Jasiuk Date: Fri, 24 May 2024 15:45:11 +0200 Subject: [PATCH 2/8] feat: pos and gov changes --- orm/src/bond.rs | 17 ++++++++++++++-- orm/src/validators.rs | 4 ++-- swagger.yml | 27 ++++++++++++++++++++++++-- webserver/src/app.rs | 4 ++++ webserver/src/handler/governance.rs | 15 ++++++++++++++ webserver/src/repository/governance.rs | 22 +++++++++++++++++++++ webserver/src/response/governance.rs | 15 ++++++++++++-- webserver/src/service/governance.rs | 16 +++++++++++++++ 8 files changed, 112 insertions(+), 8 deletions(-) diff --git a/orm/src/bond.rs b/orm/src/bond.rs index 7d3f6f4d4..5c28a2180 100644 --- a/orm/src/bond.rs +++ b/orm/src/bond.rs @@ -1,6 +1,10 @@ -use diesel::{Insertable, Queryable, Selectable}; +use diesel::{ + associations::Associations, Identifiable, Insertable, Queryable, Selectable, +}; use shared::bond::Bond; +use crate::validators::ValidatorDb; + use crate::schema::bonds; #[derive(Insertable, Clone, Queryable, Selectable)] @@ -12,7 +16,16 @@ pub struct BondInsertDb { pub raw_amount: String, } -pub type BondDb = BondInsertDb; +#[derive(Identifiable, Clone, Queryable, Selectable, Associations)] +#[diesel(table_name = bonds)] +#[diesel(check_for_backend(diesel::pg::Pg))] +#[diesel(belongs_to(ValidatorDb, foreign_key = validator_id))] +pub struct BondDb { + pub id: i32, + pub address: String, + pub validator_id: i32, + pub raw_amount: String, +} impl BondInsertDb { pub fn from_bond(bond: Bond, validator_id: i32) -> Self { diff --git a/orm/src/validators.rs b/orm/src/validators.rs index c2115070c..00c3aba69 100644 --- a/orm/src/validators.rs +++ b/orm/src/validators.rs @@ -1,12 +1,12 @@ use std::str::FromStr; -use diesel::{AsChangeset, Insertable, Queryable, Selectable}; +use diesel::{AsChangeset, Identifiable, Insertable, Queryable, Selectable}; use serde::Serialize; use shared::validator::Validator; use crate::schema::validators; -#[derive(Serialize, Queryable, Selectable, Clone)] +#[derive(Identifiable, Serialize, Queryable, Selectable, Clone, Debug)] #[diesel(table_name = validators)] #[diesel(check_for_backend(diesel::pg::Pg))] pub struct ValidatorDb { diff --git a/swagger.yml b/swagger.yml index 941e90795..dea0db4fc 100644 --- a/swagger.yml +++ b/swagger.yml @@ -329,6 +329,25 @@ paths: type: array items: $ref: '#/components/schemas/Vote' + /api/v1/gov/voter/{address}/votes: + get: + summary: Get all the votes from a voter + parameters: + - in: path + name: address + schema: + type: string + required: true + description: The voter address + responses: + '200': + description: A list of votes. + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Vote' /api/v1/account/{address}: get: summary: Get the all the tokens balances of an address @@ -379,6 +398,7 @@ components: type: string Proposal: type: object + required: [id, content, type, author, startEpoch, endEpoch, activationEpoch, status, yayVotes, nayVotes, abstainVotes] properties: id: type: integer @@ -386,6 +406,7 @@ components: type: string type: type: string + enum: [default, default_with_wasm, pgf_steward, pgf_funding] data: type: string author: @@ -403,16 +424,17 @@ components: type: integer nayVotes: type: integer - abstrainVotes: + abstainVotes: type: integer Vote: type: object + required: [proposal_id, vote, voterAddress] properties: proposal_id: type: integer vote: type: string - enum: [yay, nay, abstrain] + enum: [yay, nay, abstain] voterAddress: type: string Reward: @@ -427,6 +449,7 @@ components: minimum: 0 Bond: type: object + required: [validator, amount] properties: validator: type: object diff --git a/webserver/src/app.rs b/webserver/src/app.rs index 58753040f..3a7dda08d 100644 --- a/webserver/src/app.rs +++ b/webserver/src/app.rs @@ -78,6 +78,10 @@ impl ApplicationServer { "/gov/proposal/:id/votes/:address", get(gov_handlers::get_governance_proposal_votes_by_address), ) + .route( + "/gov/voter/:address/votes", + get(gov_handlers::get_governance_proposal_votes_by_voter), + ) .route( "/account/:address", get(balance_handlers::get_address_balance), diff --git a/webserver/src/handler/governance.rs b/webserver/src/handler/governance.rs index cb5f544fc..d5147b9d0 100644 --- a/webserver/src/handler/governance.rs +++ b/webserver/src/handler/governance.rs @@ -102,3 +102,18 @@ pub async fn get_governance_proposal_votes_by_address( Ok(Json(proposal_votes)) } + +#[debug_handler] +pub async fn get_governance_proposal_votes_by_voter( + _trace_id: TraceId, + _headers: HeaderMap, + Path(address): Path, + State(state): State, +) -> Result>, ApiError> { + let proposal_votes = state + .gov_service + .find_governance_proposal_votes_by_voter(address) + .await?; + + Ok(Json(proposal_votes)) +} diff --git a/webserver/src/repository/governance.rs b/webserver/src/repository/governance.rs index 405cff904..ee8315cfa 100644 --- a/webserver/src/repository/governance.rs +++ b/webserver/src/repository/governance.rs @@ -49,6 +49,11 @@ pub trait GovernanceRepoTrait { proposal_id: i32, voter_address: String, ) -> Result, String>; + + async fn find_governance_proposal_votes_by_voter( + &self, + voter_address: String, + ) -> Result, String>; } #[async_trait] @@ -153,4 +158,21 @@ impl GovernanceRepoTrait for GovernanceRepo { .map_err(|e| e.to_string())? .map_err(|e| e.to_string()) } + + async fn find_governance_proposal_votes_by_voter( + &self, + voter_address: String, + ) -> Result, String> { + let conn = self.app_state.get_db_connection().await; + + conn.interact(move |conn| { + governance_votes::table + .filter(governance_votes::dsl::voter_address.eq(voter_address)) + .select(GovernanceProposalVoteDb::as_select()) + .get_results(conn) + }) + .await + .map_err(|e| e.to_string())? + .map_err(|e| e.to_string()) + } } diff --git a/webserver/src/response/governance.rs b/webserver/src/response/governance.rs index 2922aa9a8..a973c0c42 100644 --- a/webserver/src/response/governance.rs +++ b/webserver/src/response/governance.rs @@ -15,6 +15,17 @@ pub enum ProposalType { PgfFunding, } +impl Display for ProposalType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + ProposalType::Default => write!(f, "default"), + ProposalType::DefaultWithWasm => write!(f, "default_with_wasm"), + ProposalType::PgfSteward => write!(f, "pgf_steward"), + ProposalType::PgfFunding => write!(f, "pgf_funding"), + } + } +} + #[derive(Clone, Debug, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub enum VoteType { @@ -59,7 +70,7 @@ pub struct Proposal { pub status: ProposalStatus, pub yay_votes: u64, pub nay_votes: u64, - pub abstrain_votes: u64, + pub abstain_votes: u64, } #[derive(Clone, Debug, Deserialize, Serialize)] @@ -105,7 +116,7 @@ impl From for Proposal { }, yay_votes: value.yay_votes.parse::().unwrap_or_default(), nay_votes: value.nay_votes.parse::().unwrap_or_default(), - abstrain_votes: value + abstain_votes: value .abstain_votes .parse::() .unwrap_or_default(), diff --git a/webserver/src/service/governance.rs b/webserver/src/service/governance.rs index 0b06406af..46d388526 100644 --- a/webserver/src/service/governance.rs +++ b/webserver/src/service/governance.rs @@ -150,4 +150,20 @@ impl GovernanceService { .map(ProposalVote::from) .collect()) } + + pub async fn find_governance_proposal_votes_by_voter( + &self, + voter_address: String, + ) -> Result, GovernanceError> { + let db_proposal_votes = self + .governance_repo + .find_governance_proposal_votes_by_voter(voter_address) + .await + .map_err(GovernanceError::Database)?; + + Ok(db_proposal_votes + .into_iter() + .map(ProposalVote::from) + .collect()) + } } From 4722fc3d73ea4ae0e4b24b9c249b929836a89fd9 Mon Sep 17 00:00:00 2001 From: Mateusz Jasiuk Date: Mon, 27 May 2024 11:23:14 +0200 Subject: [PATCH 3/8] feat: make data and pagination required in swagger --- swagger.yml | 50 ++++++++------------------------------------------ 1 file changed, 8 insertions(+), 42 deletions(-) diff --git a/swagger.yml b/swagger.yml index dea0db4fc..4b483825e 100644 --- a/swagger.yml +++ b/swagger.yml @@ -30,6 +30,7 @@ paths: application/json: schema: type: object + required: [data, pagination] properties: data: type: array @@ -59,6 +60,7 @@ paths: application/json: schema: type: object + required: [data, pagination] properties: data: type: array @@ -183,26 +185,14 @@ paths: application/json: schema: type: object + required: [data, pagination] properties: data: type: array items: $ref: '#/components/schemas/Proposal' pagination: - type: object - properties: - page: - type: integer - minimum: 0 - per_page: - type: integer - minimum: 0 - total_pages: - type: integer - minimum: 0 - total_items: - type: integer - minimum: 0 + $ref: '#/components/schemas/Pagination' /api/v1/gov/search/{text}: get: summary: Get a list of governance proposals matching a text in the title @@ -227,26 +217,14 @@ paths: application/json: schema: type: object + required: [data, pagination] properties: data: type: array items: $ref: '#/components/schemas/Proposal' pagination: - type: object - properties: - page: - type: integer - minimum: 0 - per_page: - type: integer - minimum: 0 - total_pages: - type: integer - minimum: 0 - total_items: - type: integer - minimum: 0 + $ref: '#/components/schemas/Pagination' /api/v1/gov/proposal/{id}: get: summary: Get a governance proposal by proposal id @@ -283,26 +261,14 @@ paths: application/json: schema: type: object + required: [data, pagination] properties: data: type: array items: $ref: '#/components/schemas/Vote' pagination: - type: object - properties: - page: - type: integer - minimum: 0 - per_page: - type: integer - minimum: 0 - total_pages: - type: integer - minimum: 0 - total_items: - type: integer - minimum: 0 + $ref: '#/components/schemas/Pagination' /api/v1/gov/proposal/{id}/votes/{address}: get: summary: Get all the votes for a governance proposal from an address From 526756a744b493574e98dc53b23559c4c453c409 Mon Sep 17 00:00:00 2001 From: Mateusz Jasiuk Date: Tue, 28 May 2024 14:30:57 +0200 Subject: [PATCH 4/8] feat: return proposals time --- swagger.yml | 8 +++- webserver/src/repository/chain.rs | 15 +++++++ webserver/src/response/governance.rs | 31 +++++++++++++- webserver/src/service/governance.rs | 64 ++++++++++++++++++++++++++-- 4 files changed, 111 insertions(+), 7 deletions(-) diff --git a/swagger.yml b/swagger.yml index 4b483825e..372a51c9a 100644 --- a/swagger.yml +++ b/swagger.yml @@ -364,7 +364,7 @@ components: type: string Proposal: type: object - required: [id, content, type, author, startEpoch, endEpoch, activationEpoch, status, yayVotes, nayVotes, abstainVotes] + required: [id, content, type, author, startEpoch, endEpoch, activationEpoch, startTime, endTime, currentTime, status, yayVotes, nayVotes, abstainVotes] properties: id: type: integer @@ -383,6 +383,12 @@ components: type: integer activationEpoch: type: integer + startTime: + type: integer + endTime: + type: integer + currentTime: + type: integer status: type: string enum: [pending, voting, passed, rejected] diff --git a/webserver/src/repository/chain.rs b/webserver/src/repository/chain.rs index c2daedce6..6958af93d 100644 --- a/webserver/src/repository/chain.rs +++ b/webserver/src/repository/chain.rs @@ -15,6 +15,8 @@ pub trait ChainRepositoryTrait { fn new(app_state: AppState) -> Self; async fn find_latest_height(&self) -> Result, String>; + + async fn find_latest_epoch(&self) -> Result, String>; } #[async_trait] @@ -35,4 +37,17 @@ impl ChainRepositoryTrait for ChainRepository { .map_err(|e| e.to_string())? .map_err(|e| e.to_string()) } + + async fn find_latest_epoch(&self) -> Result, String> { + let conn = self.app_state.get_db_connection().await; + + conn.interact(move |conn| { + block_crawler_state::dsl::block_crawler_state + .select(max(block_crawler_state::dsl::epoch)) + .first::>(conn) + }) + .await + .map_err(|e| e.to_string())? + .map_err(|e| e.to_string()) + } } diff --git a/webserver/src/response/governance.rs b/webserver/src/response/governance.rs index a973c0c42..2ba24e4e8 100644 --- a/webserver/src/response/governance.rs +++ b/webserver/src/response/governance.rs @@ -1,5 +1,6 @@ use std::fmt::Display; +use namada_core::time::DateTimeUtc; use orm::governance_proposal::{ GovernanceProposalDb, GovernanceProposalKindDb, GovernanceProposalResultDb, }; @@ -67,6 +68,9 @@ pub struct Proposal { pub start_epoch: u64, pub end_epoch: u64, pub activation_epoch: u64, + pub start_time: i64, + pub end_time: i64, + pub current_time: i64, pub status: ProposalStatus, pub yay_votes: u64, pub nay_votes: u64, @@ -81,8 +85,26 @@ pub struct ProposalVote { pub voter_address: String, } -impl From for Proposal { - fn from(value: GovernanceProposalDb) -> Self { +// TODO: read it from storage later +const CONSENSUS_TIME_IN_SEC: i64 = 10; +const MIN_NUMBER_OF_BLOCKS: i64 = 4; + +impl Proposal { + pub fn from_proposal_db( + value: GovernanceProposalDb, + current_epoch: i32, + current_block: i32, + ) -> Self { + let seconds_since_beginning = + i64::from(current_block) * CONSENSUS_TIME_IN_SEC; + let seconds_until_end = i64::from(value.end_epoch - current_epoch) + * MIN_NUMBER_OF_BLOCKS + * CONSENSUS_TIME_IN_SEC; + + let time_now = DateTimeUtc::now().0.timestamp(); + let start_time = time_now - seconds_since_beginning; + let end_time = time_now + seconds_until_end; + Self { id: value.id as u64, content: value.content, @@ -103,6 +125,11 @@ impl From for Proposal { start_epoch: value.start_epoch as u64, end_epoch: value.end_epoch as u64, activation_epoch: value.activation_epoch as u64, + + start_time, + end_time, + current_time: time_now, + status: match value.result { GovernanceProposalResultDb::Passed => ProposalStatus::Passed, GovernanceProposalResultDb::Rejected => { diff --git a/webserver/src/service/governance.rs b/webserver/src/service/governance.rs index 46d388526..bc35b72d7 100644 --- a/webserver/src/service/governance.rs +++ b/webserver/src/service/governance.rs @@ -3,18 +3,21 @@ use orm::governance_proposal::GovernanceProposalResultDb; use crate::appstate::AppState; use crate::dto::governance::ProposalStatus; use crate::error::governance::GovernanceError; +use crate::repository::chain::{ChainRepository, ChainRepositoryTrait}; use crate::repository::governance::{GovernanceRepo, GovernanceRepoTrait}; use crate::response::governance::{Proposal, ProposalVote}; #[derive(Clone)] pub struct GovernanceService { governance_repo: GovernanceRepo, + chain_repo: ChainRepository, } impl GovernanceService { pub fn new(app_state: AppState) -> Self { Self { - governance_repo: GovernanceRepo::new(app_state), + governance_repo: GovernanceRepo::new(app_state.clone()), + chain_repo: ChainRepository::new(app_state), } } @@ -51,8 +54,27 @@ impl GovernanceService { .await .map_err(GovernanceError::Database)?; + let latest_epoch = self + .chain_repo + .find_latest_epoch() + .await + .map_err(GovernanceError::Database)? + .expect("latest epoch not found"); + + let latest_block = self + .chain_repo + .find_latest_height() + .await + .map_err(GovernanceError::Database)? + .expect("latest block not found"); + Ok(( - db_proposals.into_iter().map(Proposal::from).collect(), + db_proposals + .into_iter() + .map(|p| { + Proposal::from_proposal_db(p, latest_epoch, latest_block) + }) + .collect(), total_items as u64, )) } @@ -67,7 +89,22 @@ impl GovernanceService { .await .map_err(GovernanceError::Database)?; - Ok(db_proposal.map(Proposal::from)) + let latest_epoch = self + .chain_repo + .find_latest_epoch() + .await + .map_err(GovernanceError::Database)? + .expect("latest epoch not found"); + + let latest_block = self + .chain_repo + .find_latest_height() + .await + .map_err(GovernanceError::Database)? + .expect("latest block not found"); + + Ok(db_proposal + .map(|p| Proposal::from_proposal_db(p, latest_epoch, latest_block))) } pub async fn search_governance_proposals_by_pattern( @@ -85,8 +122,27 @@ impl GovernanceService { .await .map_err(GovernanceError::Database)?; + let latest_epoch = self + .chain_repo + .find_latest_epoch() + .await + .map_err(GovernanceError::Database)? + .expect("latest epoch not found"); + + let latest_block = self + .chain_repo + .find_latest_height() + .await + .map_err(GovernanceError::Database)? + .expect("latest block not found"); + Ok(( - db_proposals.into_iter().map(Proposal::from).collect(), + db_proposals + .into_iter() + .map(|p| { + Proposal::from_proposal_db(p, latest_epoch, latest_block) + }) + .collect(), total_items as u64, )) } From dbb90f803c91ed48f3a5fc8ac5ed8815ee06faca Mon Sep 17 00:00:00 2001 From: Mateusz Jasiuk Date: Wed, 29 May 2024 15:15:44 +0200 Subject: [PATCH 5/8] feat: tally type --- chain/src/main.rs | 31 +++++++- chain/src/repository/gov.rs | 8 +- chain/src/services/namada.rs | 64 +++++++++++++++- orm/migrations/.keep | 0 .../down.sql | 3 +- .../up.sql | 4 +- orm/src/governance_proposal.rs | 30 +++++++- orm/src/schema.rs | 13 ++++ shared/src/proposal.rs | 40 ++++++++++ swagger.yml | 75 ++++++------------- webserver/src/app.rs | 4 - webserver/src/handler/pos.rs | 17 ----- webserver/src/repository/pos.rs | 29 ------- webserver/src/response/governance.rs | 50 ++++++++++--- webserver/src/service/balance.rs | 8 +- webserver/src/service/mod.rs | 1 + webserver/src/service/pos.rs | 68 ++++++++++------- webserver/src/service/utils.rs | 7 ++ 18 files changed, 299 insertions(+), 153 deletions(-) delete mode 100644 orm/migrations/.keep create mode 100644 webserver/src/service/utils.rs diff --git a/chain/src/main.rs b/chain/src/main.rs index e4f183bdb..42737e46d 100644 --- a/chain/src/main.rs +++ b/chain/src/main.rs @@ -157,6 +157,11 @@ async fn crawling_fn( let proposals = block.governance_proposal(next_governance_proposal_id); tracing::info!("Creating {} governance proposals...", proposals.len()); + let proposals_with_tally = + namada_service::query_tallies(&client, proposals) + .await + .into_rpc_error()?; + let proposals_votes = block.governance_votes(); tracing::info!("Creating {} governance votes...", proposals_votes.len()); @@ -187,7 +192,10 @@ async fn crawling_fn( balances, )?; - repository::gov::insert_proposals(transaction_conn, proposals)?; + repository::gov::insert_proposals( + transaction_conn, + proposals_with_tally, + )?; repository::gov::insert_votes( transaction_conn, proposals_votes, @@ -261,6 +269,17 @@ async fn initial_query( tracing::info!("Querying proposals..."); let proposals = query_all_proposals(client).await.into_rpc_error()?; + let proposals_with_tally = + namada_service::query_tallies(&client, proposals.clone()) + .await + .into_rpc_error()?; + + let proposals_votes = namada_service::query_all_votes( + &client, + proposals.iter().map(|p| p.id).collect(), + ) + .await + .into_rpc_error()?; let crawler_state = CrawlerState::new(block_height, epoch); @@ -275,7 +294,15 @@ async fn initial_query( balances, )?; - repository::gov::insert_proposals(transaction_conn, proposals)?; + repository::gov::insert_proposals( + transaction_conn, + proposals_with_tally, + )?; + + repository::gov::insert_votes( + transaction_conn, + proposals_votes, + )?; repository::pos::insert_bonds(transaction_conn, bonds)?; repository::pos::insert_unbonds(transaction_conn, unbonds)?; diff --git a/chain/src/repository/gov.rs b/chain/src/repository/gov.rs index b28d613a2..e8678fbe3 100644 --- a/chain/src/repository/gov.rs +++ b/chain/src/repository/gov.rs @@ -2,20 +2,20 @@ use anyhow::Context; use diesel::{PgConnection, RunQueryDsl}; use orm::governance_proposal::GovernanceProposalInsertDb; use orm::governance_votes::GovernanceProposalVoteInsertDb; -use shared::proposal::GovernanceProposal; +use shared::proposal::{GovernanceProposal, TallyType}; use shared::vote::GovernanceVote; pub fn insert_proposals( transaction_conn: &mut PgConnection, - proposals: Vec, + proposals: Vec<(GovernanceProposal, TallyType)>, ) -> anyhow::Result<()> { diesel::insert_into(orm::schema::governance_proposals::table) .values::<&Vec>( &proposals .into_iter() - .map(|proposal| { + .map(|(proposal, tally_type)| { GovernanceProposalInsertDb::from_governance_proposal( - proposal, + proposal, tally_type, ) }) .collect::>(), diff --git a/chain/src/services/namada.rs b/chain/src/services/namada.rs index 3283a04ff..fbe179ed2 100644 --- a/chain/src/services/namada.rs +++ b/chain/src/services/namada.rs @@ -20,9 +20,10 @@ use shared::balance::{Amount, Balance, Balances}; use shared::block::{BlockHeight, Epoch}; use shared::bond::{Bond, BondAddresses, Bonds}; use shared::id::Id; -use shared::proposal::GovernanceProposal; +use shared::proposal::{GovernanceProposal, TallyType}; use shared::unbond::{Unbond, UnbondAddresses, Unbonds}; use shared::utils::BalanceChange; +use shared::vote::{GovernanceVote, ProposalVoteKind}; use subtle_encoding::hex; use tendermint_rpc::HttpClient; @@ -416,6 +417,67 @@ pub async fn query_tx_code_hash( } } +pub async fn is_steward( + client: &HttpClient, + address: &Id, +) -> anyhow::Result { + let address = NamadaSdkAddress::from_str(&address.to_string()) + .context("Failed to parse address")?; + + let is_steward = rpc::is_steward(client, &address).await; + + Ok(is_steward) +} + +pub async fn query_tallies( + client: &HttpClient, + proposals: Vec, +) -> anyhow::Result> { + let proposals = futures::stream::iter(proposals) + .filter_map(|proposal| async move { + let is_steward = + is_steward(&client, &proposal.author).await.ok()?; + + let tally_type = TallyType::from(&proposal.r#type, is_steward); + + Some((proposal, tally_type)) + }) + .map(futures::future::ready) + .buffer_unordered(20) + .collect::>() + .await; + + anyhow::Ok(proposals) +} + +pub async fn query_all_votes( + client: &HttpClient, + proposals_ids: Vec, +) -> anyhow::Result> { + let votes = futures::stream::iter(proposals_ids) + .filter_map(|proposal_id| async move { + let votes = + rpc::query_proposal_votes(client, proposal_id).await.ok()?; + + let votes = votes + .into_iter() + .map(|vote| GovernanceVote { + proposal_id, + vote: ProposalVoteKind::from(vote.data), + address: Id::from(vote.delegator), + }) + .collect::>(); + + Some(votes) + }) + .map(futures::future::ready) + .buffer_unordered(20) + .collect::>() + .await; + + anyhow::Ok(votes.iter().cloned().flatten().collect()) +} + fn to_block_height(block_height: u32) -> NamadaSdkBlockHeight { NamadaSdkBlockHeight::from(block_height as u64) } diff --git a/orm/migrations/.keep b/orm/migrations/.keep deleted file mode 100644 index e69de29bb..000000000 diff --git a/orm/migrations/2024-05-08-130928_governance_proposals/down.sql b/orm/migrations/2024-05-08-130928_governance_proposals/down.sql index 1f67d28d0..9365198de 100644 --- a/orm/migrations/2024-05-08-130928_governance_proposals/down.sql +++ b/orm/migrations/2024-05-08-130928_governance_proposals/down.sql @@ -2,4 +2,5 @@ DROP TABLE governance_proposals; DROP TYPE GOVERNANCE_KIND; -DROP TYPE GOVERNANCE_RESULT; \ No newline at end of file +DROP TYPE GOVERNANCE_RESULT; +DROP TYPE GOVERNANCE_TALLY_TYPE; diff --git a/orm/migrations/2024-05-08-130928_governance_proposals/up.sql b/orm/migrations/2024-05-08-130928_governance_proposals/up.sql index 1d02569c3..386ec8148 100644 --- a/orm/migrations/2024-05-08-130928_governance_proposals/up.sql +++ b/orm/migrations/2024-05-08-130928_governance_proposals/up.sql @@ -1,11 +1,13 @@ CREATE TYPE GOVERNANCE_KIND AS ENUM ('pgf_steward', 'pgf_funding', 'default', 'default_with_wasm'); CREATE TYPE GOVERNANCE_RESULT AS ENUM ('passed', 'rejected', 'pending', 'unknown', 'voting_period'); +CREATE TYPE GOVERNANCE_TALLY_TYPE AS ENUM ('two_thirds', 'one_half_over_one_third', 'less_one_half_over_one_third_nay'); CREATE TABLE governance_proposals ( id INT PRIMARY KEY, content VARCHAR NOT NULL, data VARCHAR, kind GOVERNANCE_KIND NOT NULL, + tally_type GOVERNANCE_TALLY_TYPE NOT NULL, author VARCHAR NOT NULL, start_epoch INT NOT NULL, end_epoch INT NOT NULL, @@ -17,4 +19,4 @@ CREATE TABLE governance_proposals ( ); CREATE INDEX index_governance_proposals_kind ON governance_proposals USING HASH (kind); -CREATE INDEX index_governance_proposals_result ON governance_proposals USING HASH (result); \ No newline at end of file +CREATE INDEX index_governance_proposals_result ON governance_proposals USING HASH (result); diff --git a/orm/src/governance_proposal.rs b/orm/src/governance_proposal.rs index fb0cc5208..fa36d5f74 100644 --- a/orm/src/governance_proposal.rs +++ b/orm/src/governance_proposal.rs @@ -3,7 +3,7 @@ use diesel::{Insertable, Queryable, Selectable}; use serde::{Deserialize, Serialize}; use shared::proposal::{ GovernanceProposal, GovernanceProposalKind, GovernanceProposalResult, - GovernanceProposalStatus, + GovernanceProposalStatus, TallyType, }; use crate::schema::governance_proposals; @@ -28,6 +28,26 @@ impl From for GovernanceProposalKindDb { } } +#[derive(Debug, Clone, Serialize, Deserialize, diesel_derive_enum::DbEnum)] +#[ExistingTypePath = "crate::schema::sql_types::GovernanceTallyType"] +pub enum GovernanceProposalTallyTypeDb { + TwoThirds, + OneHalfOverOneThird, + LessOneHalfOverOneThirdNay, +} + +impl From for GovernanceProposalTallyTypeDb { + fn from(value: TallyType) -> Self { + match value { + TallyType::TwoThirds => Self::TwoThirds, + TallyType::OneHalfOverOneThird => Self::OneHalfOverOneThird, + TallyType::LessOneHalfOverOneThirdNay => { + Self::LessOneHalfOverOneThirdNay + } + } + } +} + #[derive(Debug, Clone, Serialize, Deserialize, diesel_derive_enum::DbEnum)] #[ExistingTypePath = "crate::schema::sql_types::GovernanceResult"] pub enum GovernanceProposalResultDb { @@ -57,6 +77,7 @@ pub struct GovernanceProposalDb { pub content: String, pub data: Option, pub kind: GovernanceProposalKindDb, + pub tally_type: GovernanceProposalTallyTypeDb, pub author: String, pub start_epoch: i32, pub end_epoch: i32, @@ -75,6 +96,7 @@ pub struct GovernanceProposalInsertDb { pub content: String, pub data: Option, pub kind: GovernanceProposalKindDb, + pub tally_type: GovernanceProposalTallyTypeDb, pub author: String, pub start_epoch: i32, pub end_epoch: i32, @@ -82,12 +104,16 @@ pub struct GovernanceProposalInsertDb { } impl GovernanceProposalInsertDb { - pub fn from_governance_proposal(proposal: GovernanceProposal) -> Self { + pub fn from_governance_proposal( + proposal: GovernanceProposal, + tally_type: TallyType, + ) -> Self { Self { id: proposal.id as i32, content: proposal.content, data: proposal.data, kind: proposal.r#type.into(), + tally_type: tally_type.into(), author: proposal.author.to_string(), start_epoch: proposal.voting_start_epoch as i32, end_epoch: proposal.voting_end_epoch as i32, diff --git a/orm/src/schema.rs b/orm/src/schema.rs index de118ef9a..f7c1ecf96 100644 --- a/orm/src/schema.rs +++ b/orm/src/schema.rs @@ -17,6 +17,17 @@ pub mod sql_types { #[diesel(postgres_type(name = "governance_result"))] pub struct GovernanceResult; + #[derive( + diesel::query_builder::QueryId, + std::fmt::Debug, + diesel::sql_types::SqlType, + diesel::query_builder::QueryId, + std::fmt::Debug, + diesel::sql_types::SqlType, + )] + #[diesel(postgres_type(name = "governance_tally_type"))] + pub struct GovernanceTallyType; + #[derive( diesel::query_builder::QueryId, std::fmt::Debug, @@ -62,6 +73,7 @@ diesel::table! { diesel::table! { use diesel::sql_types::*; use super::sql_types::GovernanceKind; + use super::sql_types::GovernanceTallyType; use super::sql_types::GovernanceResult; governance_proposals (id) { @@ -69,6 +81,7 @@ diesel::table! { content -> Varchar, data -> Nullable, kind -> GovernanceKind, + tally_type -> GovernanceTallyType, author -> Varchar, start_epoch -> Int4, end_epoch -> Int4, diff --git a/shared/src/proposal.rs b/shared/src/proposal.rs index afecc7a54..edfb299ce 100644 --- a/shared/src/proposal.rs +++ b/shared/src/proposal.rs @@ -218,3 +218,43 @@ impl Distribution for Standard { } } } + +pub enum TallyType { + TwoThirds, + OneHalfOverOneThird, + LessOneHalfOverOneThirdNay, +} + +// TODO: copied from namada for time being +impl TallyType { + pub fn from( + proposal_type: &GovernanceProposalKind, + is_steward: bool, + ) -> Self { + match (proposal_type, is_steward) { + (GovernanceProposalKind::Default, _) => TallyType::TwoThirds, + (GovernanceProposalKind::DefaultWithWasm, _) => { + TallyType::TwoThirds + } + (GovernanceProposalKind::PgfSteward, _) => { + TallyType::OneHalfOverOneThird + } + (GovernanceProposalKind::PgfFunding, true) => { + TallyType::LessOneHalfOverOneThirdNay + } + (GovernanceProposalKind::PgfFunding, false) => { + TallyType::OneHalfOverOneThird + } + } + } +} + +impl Distribution for Standard { + fn sample(&self, rng: &mut R) -> TallyType { + match rng.gen_range(0..=2) { + 0 => TallyType::TwoThirds, + 1 => TallyType::OneHalfOverOneThird, + _ => TallyType::LessOneHalfOverOneThirdNay, + } + } +} diff --git a/swagger.yml b/swagger.yml index 372a51c9a..d4a2efb8a 100644 --- a/swagger.yml +++ b/swagger.yml @@ -38,36 +38,6 @@ paths: $ref: '#/components/schemas/Validator' pagination: $ref: '#/components/schemas/Pagination' - /api/v1/pos/validator/{address}: - get: - parameters: - - in: path - name: address - schema: - type: string - required: true - description: Delegator address - - in: query - name: page - schema: - type: integer - minimum: 1 - description: Pagination parameter - responses: - '200': - description: A list of validator. - content: - application/json: - schema: - type: object - required: [data, pagination] - properties: - data: - type: array - items: - $ref: '#/components/schemas/Validator' - pagination: - $ref: '#/components/schemas/Pagination' /api/v1/pos/reward/{address}: get: summary: Get all the rewards for an address @@ -159,9 +129,7 @@ paths: content: application/json: schema: - type: array - items: - $ref: '#/components/schemas/VotingPower' + $ref: '#/components/schemas/VotingPower' /api/v1/gov/proposal: get: summary: Get a list of governance proposals @@ -338,15 +306,15 @@ components: schemas: Validator: type: object - required: [id, address, name, voting_power, max_commission, commission] + required: [validatorId, address, name, votingPower, maxCommission, commission] properties: - id: + validatorId: type: integer address: type: string - voting_power: + votingPower: type: string - max_commission: + maxCommission: type: string commission: type: string @@ -358,13 +326,13 @@ components: type: string description: type: string - discord_handle: + discordHandle: type: string avatar: type: string Proposal: type: object - required: [id, content, type, author, startEpoch, endEpoch, activationEpoch, startTime, endTime, currentTime, status, yayVotes, nayVotes, abstainVotes] + required: [id, content, type, author, startEpoch, endEpoch, activationEpoch, startTime, endTime, currentTime, status, yayVotes, nayVotes, abstainVotes, tallyType] properties: id: type: integer @@ -372,7 +340,10 @@ components: type: string type: type: string - enum: [default, default_with_wasm, pgf_steward, pgf_funding] + enum: [default, defaultWithWasm, pgfSteward, pgfFunding] + tallyType: + type: string + enum: [twoThirds, oneHalfOverOneThird, lessOneHalfOverOneThirdNay] data: type: string author: @@ -393,11 +364,11 @@ components: type: string enum: [pending, voting, passed, rejected] yayVotes: - type: integer + type: string nayVotes: - type: integer + type: string abstainVotes: - type: integer + type: string Vote: type: object required: [proposal_id, vote, voterAddress] @@ -424,7 +395,6 @@ components: required: [validator, amount] properties: validator: - type: object $ref: '#/components/schemas/Validator' amount: type: number @@ -432,36 +402,37 @@ components: minimum: 0 Unbond: type: object + required: [validator, amount, withdrawEpoch] properties: validator: - type: object $ref: '#/components/schemas/Validator' amount: type: number format: float minimum: 0 - withdrawEpoch: - type: integer + withdrawEpoch: + type: number Withdraw: type: object + required: [amount, withdrawEpoch] properties: validator: - type: object $ref: '#/components/schemas/Validator' amount: type: number format: float minimum: 0 - withdrawEpoch: - type: integer + withdrawEpoch: + type: number VotingPower: type: object + required: [totalVotingPower] properties: - votingPower: + totalVotingPower: type: integer Balance: type: object - required: [token, balance] + required: [tokenAddress, balance] properties: tokenAddress: type: string diff --git a/webserver/src/app.rs b/webserver/src/app.rs index 3a7dda08d..435dedb34 100644 --- a/webserver/src/app.rs +++ b/webserver/src/app.rs @@ -43,10 +43,6 @@ impl ApplicationServer { Router::new() .route("/pos/validator", get(pos_handlers::get_validators)) - .route( - "/pos/validator/:address", - get(pos_handlers::get_validators_by_delegator), - ) .route("/pos/bond/:address", get(pos_handlers::get_bonds)) .route("/pos/unbond/:address", get(pos_handlers::get_unbonds)) .route( diff --git a/webserver/src/handler/pos.rs b/webserver/src/handler/pos.rs index d71fb9b8a..336a6aa3b 100644 --- a/webserver/src/handler/pos.rs +++ b/webserver/src/handler/pos.rs @@ -26,23 +26,6 @@ pub async fn get_validators( let response = PaginatedResponse::new(validators, page, total_validators); Ok(Json(response)) } -#[debug_handler] -pub async fn get_validators_by_delegator( - _trace_id: TraceId, - _headers: HeaderMap, - Query(query): Query, - Path(address): Path, - State(state): State, -) -> Result>>, ApiError> { - let page = query.page.unwrap_or(1); - let (validators, total_validators) = state - .pos_service - .get_validators_by_delegator(address, page) - .await?; - - let response = PaginatedResponse::new(validators, page, total_validators); - Ok(Json(response)) -} #[debug_handler] pub async fn get_bonds( diff --git a/webserver/src/repository/pos.rs b/webserver/src/repository/pos.rs index 56cc93d51..f9cc47af0 100644 --- a/webserver/src/repository/pos.rs +++ b/webserver/src/repository/pos.rs @@ -27,12 +27,6 @@ pub trait PosRepositoryTrait { page: i64, ) -> Result<(Vec, i64), String>; - async fn find_validators_by_delegator( - &self, - address: String, - page: i64, - ) -> Result<(Vec, i64), String>; - async fn find_validator_by_id( &self, id: i32, @@ -85,29 +79,6 @@ impl PosRepositoryTrait for PosRepository { .map_err(|e| e.to_string()) } - async fn find_validators_by_delegator( - &self, - address: String, - page: i64, - ) -> Result<(Vec, i64), String> { - let conn = self.app_state.get_db_connection().await; - - conn.interact(move |conn| { - let validator_ids = bonds::table - .filter(bonds::dsl::address.eq(address)) - .select(bonds::dsl::validator_id); - - validators::table - .select(ValidatorDb::as_select()) - .filter(validators::dsl::id.eq_any(validator_ids)) - .paginate(page) - .load_and_count_pages(conn) - }) - .await - .map_err(|e| e.to_string())? - .map_err(|e| e.to_string()) - } - async fn find_validator_by_id( &self, validator_id: i32, diff --git a/webserver/src/response/governance.rs b/webserver/src/response/governance.rs index 2ba24e4e8..95636dbca 100644 --- a/webserver/src/response/governance.rs +++ b/webserver/src/response/governance.rs @@ -3,6 +3,7 @@ use std::fmt::Display; use namada_core::time::DateTimeUtc; use orm::governance_proposal::{ GovernanceProposalDb, GovernanceProposalKindDb, GovernanceProposalResultDb, + GovernanceProposalTallyTypeDb, }; use orm::governance_votes::{GovernanceProposalVoteDb, GovernanceVoteKindDb}; use serde::{Deserialize, Serialize}; @@ -27,6 +28,28 @@ impl Display for ProposalType { } } +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub enum TallyType { + TwoThirds, + OneHalfOverOneThird, + LessOneHalfOverOneThirdNay, +} + +impl Display for TallyType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + TallyType::TwoThirds => write!(f, "two_thirds"), + TallyType::OneHalfOverOneThird => { + write!(f, "one_half_over_one_third") + } + TallyType::LessOneHalfOverOneThirdNay => { + write!(f, "less_one_half_over_one_third_nay") + } + } + } +} + #[derive(Clone, Debug, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub enum VoteType { @@ -63,6 +86,7 @@ pub struct Proposal { pub id: u64, pub content: String, pub r#type: ProposalType, + pub tally_type: TallyType, pub data: Option, pub author: String, pub start_epoch: u64, @@ -72,9 +96,9 @@ pub struct Proposal { pub end_time: i64, pub current_time: i64, pub status: ProposalStatus, - pub yay_votes: u64, - pub nay_votes: u64, - pub abstain_votes: u64, + pub yay_votes: String, + pub nay_votes: String, + pub abstain_votes: String, } #[derive(Clone, Debug, Deserialize, Serialize)] @@ -120,6 +144,17 @@ impl Proposal { ProposalType::DefaultWithWasm } }, + tally_type: match value.tally_type { + GovernanceProposalTallyTypeDb::TwoThirds => { + TallyType::TwoThirds + } + GovernanceProposalTallyTypeDb::OneHalfOverOneThird => { + TallyType::OneHalfOverOneThird + } + GovernanceProposalTallyTypeDb::LessOneHalfOverOneThirdNay => { + TallyType::LessOneHalfOverOneThirdNay + } + }, data: value.data, author: value.author, start_epoch: value.start_epoch as u64, @@ -141,12 +176,9 @@ impl Proposal { ProposalStatus::Voting } }, - yay_votes: value.yay_votes.parse::().unwrap_or_default(), - nay_votes: value.nay_votes.parse::().unwrap_or_default(), - abstain_votes: value - .abstain_votes - .parse::() - .unwrap_or_default(), + yay_votes: value.yay_votes, + nay_votes: value.nay_votes, + abstain_votes: value.abstain_votes, } } } diff --git a/webserver/src/service/balance.rs b/webserver/src/service/balance.rs index 6430b7cf6..7f2f519b6 100644 --- a/webserver/src/service/balance.rs +++ b/webserver/src/service/balance.rs @@ -1,10 +1,10 @@ -use namada_core::token::Amount; - use crate::appstate::AppState; use crate::error::balance::BalanceError; use crate::repository::balance::{BalanceRepo, BalanceRepoTrait}; use crate::response::balance::AddressBalance; +use super::utils::raw_amount_to_nam; + #[derive(Clone)] pub struct BalanceService { pub balance_repo: BalanceRepo, @@ -33,9 +33,7 @@ impl BalanceService { .cloned() .map(|balance| AddressBalance { token_address: balance.token, - balance: Amount::from_str(balance.raw_amount, 0) - .unwrap() - .to_string_native(), + balance: raw_amount_to_nam(balance.raw_amount), }) .collect(); diff --git a/webserver/src/service/mod.rs b/webserver/src/service/mod.rs index aa95e0d78..5310da139 100644 --- a/webserver/src/service/mod.rs +++ b/webserver/src/service/mod.rs @@ -2,3 +2,4 @@ pub mod balance; pub mod chain; pub mod governance; pub mod pos; +pub mod utils; diff --git a/webserver/src/service/pos.rs b/webserver/src/service/pos.rs index 91cd6a7b1..59af37ae0 100644 --- a/webserver/src/service/pos.rs +++ b/webserver/src/service/pos.rs @@ -3,6 +3,8 @@ use crate::error::pos::PoSError; use crate::repository::pos::{PosRepository, PosRepositoryTrait}; use crate::response::pos::{Bond, Reward, Unbond, ValidatorWithId, Withdraw}; +use super::utils::raw_amount_to_nam; + #[derive(Clone)] pub struct PosService { pos_repo: PosRepository, @@ -34,26 +36,6 @@ impl PosService { )) } - pub async fn get_validators_by_delegator( - &self, - address: String, - page: u64, - ) -> Result<(Vec, u64), PoSError> { - let (db_validators, total_items) = self - .pos_repo - .find_validators_by_delegator(address, page as i64) - .await - .map_err(PoSError::Database)?; - - Ok(( - db_validators - .into_iter() - .map(ValidatorWithId::from) - .collect(), - total_items as u64, - )) - } - pub async fn get_bonds_by_address( &self, address: String, @@ -79,7 +61,16 @@ impl PosService { ); } } - Ok(bonds) + + let denominated_bonds: Vec = bonds + .iter() + .cloned() + .map(|bond| Bond { + amount: raw_amount_to_nam(bond.amount), + ..bond + }) + .collect(); + Ok(denominated_bonds) } pub async fn get_unbonds_by_address( @@ -107,7 +98,15 @@ impl PosService { ); } } - Ok(unbonds) + let denominated_unbonds: Vec = unbonds + .iter() + .cloned() + .map(|unbond| Unbond { + amount: raw_amount_to_nam(unbond.amount), + ..unbond + }) + .collect(); + Ok(denominated_unbonds) } pub async fn get_withdraws_by_address( @@ -121,14 +120,14 @@ impl PosService { .await .map_err(PoSError::Database)?; - let mut unbonds = vec![]; + let mut withdraws = vec![]; for db_unbond in db_unbonds { let db_validator = self .pos_repo .find_validator_by_id(db_unbond.validator_id) .await; if let Ok(Some(db_validator)) = db_validator { - unbonds.push(Withdraw::from(db_unbond.clone(), db_validator)); + withdraws.push(Withdraw::from(db_unbond.clone(), db_validator)); } else { tracing::error!( "Couldn't find validator with id {} in bond query", @@ -136,7 +135,15 @@ impl PosService { ); } } - Ok(unbonds) + let denominated_withdraw: Vec = withdraws + .iter() + .cloned() + .map(|withdraw| Withdraw { + amount: raw_amount_to_nam(withdraw.amount), + ..withdraw + }) + .collect(); + Ok(denominated_withdraw) } pub async fn get_rewards_by_address( @@ -164,9 +171,18 @@ impl PosService { ); } } - Ok(rewards) + let denominated_rewards: Vec = rewards + .iter() + .cloned() + .map(|reward| Reward { + amount: raw_amount_to_nam(reward.amount), + ..reward + }) + .collect(); + Ok(denominated_rewards) } + // TODO: maybe remove object(struct) instead pub async fn get_total_voting_power(&self) -> Result { let total_voting_power_db = self .pos_repo diff --git a/webserver/src/service/utils.rs b/webserver/src/service/utils.rs new file mode 100644 index 000000000..dc1da5d30 --- /dev/null +++ b/webserver/src/service/utils.rs @@ -0,0 +1,7 @@ +use namada_core::token::Amount; + +pub fn raw_amount_to_nam(raw_amount: String) -> String { + Amount::from_str(raw_amount, 0) + .expect("raw_amount is not a valid string") + .to_string_native() +} From 0b24a503e5de7b71f81117d1d00fd6c7d64f312c Mon Sep 17 00:00:00 2001 From: Mateusz Jasiuk Date: Mon, 3 Jun 2024 16:38:08 +0200 Subject: [PATCH 6/8] feat: revealed pks --- Cargo.toml | 2 +- chain/src/main.rs | 21 ++++-- chain/src/repository/mod.rs | 1 + chain/src/repository/revealed_pk.rs | 22 ++++++ docker-compose.yml | 3 +- .../down.sql | 3 + .../2024-06-03-084149_init_revealed_pk/up.sql | 9 +++ orm/src/lib.rs | 1 + orm/src/revealed_pk.rs | 23 ++++++ orm/src/schema.rs | 36 ++++------ shared/src/block.rs | 20 ++++++ shared/src/lib.rs | 1 + shared/src/public_key.rs | 9 +++ shared/src/transaction.rs | 2 + swagger.yml | 24 ++++++- webserver/Cargo.toml | 1 + webserver/run.sh | 2 +- webserver/src/app.rs | 10 ++- webserver/src/config.rs | 3 + webserver/src/error/api.rs | 4 ++ webserver/src/error/mod.rs | 1 + webserver/src/error/revealed_pk.rs | 30 ++++++++ webserver/src/handler/mod.rs | 1 + webserver/src/handler/pk.rs | 24 +++++++ webserver/src/repository/mod.rs | 1 + webserver/src/repository/revealed_pk.rs | 69 ++++++++++++++++++ webserver/src/response/mod.rs | 1 + webserver/src/response/revealed_pk.rs | 16 +++++ webserver/src/service/mod.rs | 1 + webserver/src/service/revealed_pk.rs | 70 +++++++++++++++++++ webserver/src/state/common.rs | 9 ++- 31 files changed, 385 insertions(+), 35 deletions(-) create mode 100644 chain/src/repository/revealed_pk.rs create mode 100644 orm/migrations/2024-06-03-084149_init_revealed_pk/down.sql create mode 100644 orm/migrations/2024-06-03-084149_init_revealed_pk/up.sql create mode 100644 orm/src/revealed_pk.rs create mode 100644 shared/src/public_key.rs create mode 100644 webserver/src/error/revealed_pk.rs create mode 100644 webserver/src/handler/pk.rs create mode 100644 webserver/src/repository/revealed_pk.rs create mode 100644 webserver/src/response/revealed_pk.rs create mode 100644 webserver/src/service/revealed_pk.rs diff --git a/Cargo.toml b/Cargo.toml index 475e2a186..3e49ed961 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,7 +40,7 @@ chrono = { version = "0.4.30", features = ["serde"] } async-trait = "0.1.73" anyhow = "1.0.75" namada_core = { git = "https://github.com/anoma/namada", tag = "v0.38.1" } -namada_sdk = { git = "https://github.com/anoma/namada", tag = "v0.38.1" } +namada_sdk = { git = "https://github.com/anoma/namada", tag = "v0.38.1", default-features = false, features = ["tendermint-rpc", "std", "async-send", "download-params", "rand"] } namada_tx = { git = "https://github.com/anoma/namada", tag = "v0.38.1" } namada_governance = { git = "https://github.com/anoma/namada", tag = "v0.38.1" } namada_ibc = { git = "https://github.com/anoma/namada", tag = "v0.38.1" } diff --git a/chain/src/main.rs b/chain/src/main.rs index 42737e46d..78be5d1fd 100644 --- a/chain/src/main.rs +++ b/chain/src/main.rs @@ -177,6 +177,12 @@ async fn crawling_fn( .into_rpc_error()?; tracing::info!("Updating unbonds for {} addresses", unbonds.len()); + let revealed_pks = block.revealed_pks(); + tracing::info!( + "Updating revealed pks for {} addresses", + revealed_pks.len() + ); + let metadata_change = block.validator_metadata(); let reward_claimers = block.pos_rewards(); @@ -204,11 +210,6 @@ async fn crawling_fn( repository::pos::insert_bonds(transaction_conn, bonds)?; repository::pos::insert_unbonds(transaction_conn, unbonds)?; - repository::crawler::insert_crawler_state( - transaction_conn, - crawler_state, - )?; - repository::pos::delete_claimed_rewards( transaction_conn, reward_claimers, @@ -219,6 +220,16 @@ async fn crawling_fn( metadata_change, )?; + repository::revealed_pk::insert_revealed_pks( + transaction_conn, + revealed_pks, + )?; + + repository::crawler::insert_crawler_state( + transaction_conn, + crawler_state, + )?; + anyhow::Ok(()) }) }) diff --git a/chain/src/repository/mod.rs b/chain/src/repository/mod.rs index 0de5524b6..c811ef582 100644 --- a/chain/src/repository/mod.rs +++ b/chain/src/repository/mod.rs @@ -2,3 +2,4 @@ pub mod balance; pub mod crawler; pub mod gov; pub mod pos; +pub mod revealed_pk; diff --git a/chain/src/repository/revealed_pk.rs b/chain/src/repository/revealed_pk.rs new file mode 100644 index 000000000..69c433aa2 --- /dev/null +++ b/chain/src/repository/revealed_pk.rs @@ -0,0 +1,22 @@ +use anyhow::Context; +use diesel::{PgConnection, RunQueryDsl}; +use orm::{revealed_pk::RevealedPkInsertDb, schema::revealed_pk}; +use shared::{id::Id, public_key::PublicKey}; + +pub fn insert_revealed_pks( + transaction_conn: &mut PgConnection, + revealed_pks: Vec<(PublicKey, Id)>, +) -> anyhow::Result<()> { + diesel::insert_into(revealed_pk::table) + .values::<&Vec>( + &revealed_pks + .into_iter() + .map(|(pk, address)| RevealedPkInsertDb::from(pk, address)) + .collect::>(), + ) + .on_conflict_do_nothing() + .execute(transaction_conn) + .context("Failed to update balances in db")?; + + anyhow::Ok(()) +} diff --git a/docker-compose.yml b/docker-compose.yml index 0b7e1f0fe..b2cef3a10 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -65,10 +65,11 @@ services: environment: DATABASE_URL: postgres://postgres:password@postgres:5435/namada-indexer CACHE_URL: redis://dragonfly:6379 + TENDERMINT_URL: http://localhost:27657 healthcheck: test: curl --fail http://localhost:5000/health || exit 1 interval: 15s timeout: 10s retries: 3 start_period: 10s - \ No newline at end of file + diff --git a/orm/migrations/2024-06-03-084149_init_revealed_pk/down.sql b/orm/migrations/2024-06-03-084149_init_revealed_pk/down.sql new file mode 100644 index 000000000..c73c0034e --- /dev/null +++ b/orm/migrations/2024-06-03-084149_init_revealed_pk/down.sql @@ -0,0 +1,3 @@ +-- This file should undo anything in `up.sql` + +DROP TABLE IF EXISTS revealed_pk; diff --git a/orm/migrations/2024-06-03-084149_init_revealed_pk/up.sql b/orm/migrations/2024-06-03-084149_init_revealed_pk/up.sql new file mode 100644 index 000000000..28cffbfdd --- /dev/null +++ b/orm/migrations/2024-06-03-084149_init_revealed_pk/up.sql @@ -0,0 +1,9 @@ +-- Your SQL goes here + +CREATE TABLE revealed_pk ( + id SERIAL PRIMARY KEY, + address VARCHAR NOT NULL, + pk VARCHAR NOT NULL +); + +ALTER TABLE revealed_pk ADD UNIQUE (address, pk); diff --git a/orm/src/lib.rs b/orm/src/lib.rs index 078f8f44f..3fb61687a 100644 --- a/orm/src/lib.rs +++ b/orm/src/lib.rs @@ -6,6 +6,7 @@ pub mod governance_proposal; pub mod governance_votes; pub mod migrations; pub mod pos_rewards; +pub mod revealed_pk; pub mod schema; pub mod unbond; pub mod validators; diff --git a/orm/src/revealed_pk.rs b/orm/src/revealed_pk.rs new file mode 100644 index 000000000..618068a3b --- /dev/null +++ b/orm/src/revealed_pk.rs @@ -0,0 +1,23 @@ +use diesel::{Insertable, Queryable, Selectable}; +use shared::{id::Id, public_key::PublicKey}; + +use crate::schema::revealed_pk; + +#[derive(Insertable, Clone, Queryable, Selectable)] +#[diesel(table_name = revealed_pk)] +#[diesel(check_for_backend(diesel::pg::Pg))] +pub struct RevealedPkInsertDb { + pub pk: String, + pub address: String, +} + +pub type RevealedPkDb = RevealedPkInsertDb; + +impl RevealedPkInsertDb { + pub fn from(pk: PublicKey, address: Id) -> Self { + Self { + pk: pk.0, + address: address.to_string(), + } + } +} diff --git a/orm/src/schema.rs b/orm/src/schema.rs index f7c1ecf96..00a74717b 100644 --- a/orm/src/schema.rs +++ b/orm/src/schema.rs @@ -1,38 +1,19 @@ // @generated automatically by Diesel CLI. pub mod sql_types { - #[derive( - diesel::query_builder::QueryId, - std::fmt::Debug, - diesel::sql_types::SqlType, - )] + #[derive(diesel::query_builder::QueryId, std::fmt::Debug, diesel::sql_types::SqlType)] #[diesel(postgres_type(name = "governance_kind"))] pub struct GovernanceKind; - #[derive( - diesel::query_builder::QueryId, - std::fmt::Debug, - diesel::sql_types::SqlType, - )] + #[derive(diesel::query_builder::QueryId, std::fmt::Debug, diesel::sql_types::SqlType)] #[diesel(postgres_type(name = "governance_result"))] pub struct GovernanceResult; - #[derive( - diesel::query_builder::QueryId, - std::fmt::Debug, - diesel::sql_types::SqlType, - diesel::query_builder::QueryId, - std::fmt::Debug, - diesel::sql_types::SqlType, - )] + #[derive(diesel::query_builder::QueryId, std::fmt::Debug, diesel::sql_types::SqlType)] #[diesel(postgres_type(name = "governance_tally_type"))] pub struct GovernanceTallyType; - #[derive( - diesel::query_builder::QueryId, - std::fmt::Debug, - diesel::sql_types::SqlType, - )] + #[derive(diesel::query_builder::QueryId, std::fmt::Debug, diesel::sql_types::SqlType)] #[diesel(postgres_type(name = "vote_kind"))] pub struct VoteKind; } @@ -114,6 +95,14 @@ diesel::table! { } } +diesel::table! { + revealed_pk (id) { + id -> Int4, + address -> Varchar, + pk -> Varchar, + } +} + diesel::table! { unbonds (id) { id -> Int4, @@ -153,6 +142,7 @@ diesel::allow_tables_to_appear_in_same_query!( governance_proposals, governance_votes, pos_rewards, + revealed_pk, unbonds, validators, ); diff --git a/shared/src/block.rs b/shared/src/block.rs index 713e733b0..c4c7527b9 100644 --- a/shared/src/block.rs +++ b/shared/src/block.rs @@ -2,6 +2,7 @@ use std::collections::{BTreeMap, HashSet}; use std::str::FromStr; use namada_sdk::borsh::BorshDeserialize; +use namada_sdk::key::common::PublicKey as NamadaPublicKey; use namada_tx::data::pos; use subtle_encoding::hex; use tendermint_rpc::endpoint::block::Response as TendermintBlockResponse; @@ -12,6 +13,7 @@ use crate::checksums::Checksums; use crate::header::BlockHeader; use crate::id::Id; use crate::proposal::{GovernanceProposal, GovernanceProposalKind}; +use crate::public_key::PublicKey; use crate::transaction::{Transaction, TransactionKind}; use crate::unbond::UnbondAddresses; use crate::utils::BalanceChange; @@ -486,4 +488,22 @@ impl Block { }) .collect() } + + pub fn revealed_pks(&self) -> Vec<(PublicKey, Id)> { + self.transactions + .iter() + .filter_map(|tx| match &tx.kind { + TransactionKind::RevealPk(data) => { + let namada_public_key = + NamadaPublicKey::try_from_slice(data).unwrap(); + + Some(( + PublicKey::from(namada_public_key.clone()), + Id::from(namada_public_key), + )) + } + _ => None, + }) + .collect() + } } diff --git a/shared/src/lib.rs b/shared/src/lib.rs index 82a6cc8c3..d97ebeca4 100644 --- a/shared/src/lib.rs +++ b/shared/src/lib.rs @@ -9,6 +9,7 @@ pub mod error; pub mod header; pub mod id; pub mod proposal; +pub mod public_key; pub mod rewards; pub mod transaction; pub mod unbond; diff --git a/shared/src/public_key.rs b/shared/src/public_key.rs new file mode 100644 index 000000000..53b3fa223 --- /dev/null +++ b/shared/src/public_key.rs @@ -0,0 +1,9 @@ +use namada_sdk::key::common::PublicKey as NamadaPublicKey; + +pub struct PublicKey(pub String); + +impl From for PublicKey { + fn from(pk: NamadaPublicKey) -> Self { + PublicKey(pk.to_string()) + } +} diff --git a/shared/src/transaction.rs b/shared/src/transaction.rs index f350ec42c..65131eca7 100644 --- a/shared/src/transaction.rs +++ b/shared/src/transaction.rs @@ -24,6 +24,7 @@ pub enum TransactionKind { InitProposal(Vec), MetadataChange(Vec), CommissionChange(Vec), + RevealPk(Vec), Unknown, } @@ -60,6 +61,7 @@ impl TransactionKind { "tx_commission_change" => { TransactionKind::CommissionChange(data.to_vec()) } + "tx_reveal_pk" => TransactionKind::RevealPk(data.to_vec()), _ => TransactionKind::Unknown, } } diff --git a/swagger.yml b/swagger.yml index d4a2efb8a..725950955 100644 --- a/swagger.yml +++ b/swagger.yml @@ -302,6 +302,24 @@ paths: items: $ref: '#/components/schemas/Balance' + /api/v1/revealed_public_key/{address}: + get: + summary: Get revealed public key for an address if exists + parameters: + - in: path + name: address + schema: + type: string + required: true + description: The address account + responses: + '200': + description: Revealed public key. + content: + application/json: + schema: + $ref: '#/components/schemas/RevealedPk' + components: schemas: Validator: @@ -384,7 +402,6 @@ components: type: object properties: validator: - type: object $ref: '#/components/schemas/Validator' amount: type: number @@ -453,4 +470,9 @@ components: total_items: type: integer minimum: 0 + RevealedPk: + type: object + properties: + publicKey: + type: string diff --git a/webserver/Cargo.toml b/webserver/Cargo.toml index 92ccff880..3ed8855e2 100644 --- a/webserver/Cargo.toml +++ b/webserver/Cargo.toml @@ -25,6 +25,7 @@ tokio.workspace = true tower.workspace = true tower-http.workspace = true tracing.workspace = true +namada_sdk.workspace = true tracing-subscriber.workspace = true serde.workspace = true serde_json.workspace = true diff --git a/webserver/run.sh b/webserver/run.sh index 07d58ef40..a61a5bd39 100755 --- a/webserver/run.sh +++ b/webserver/run.sh @@ -1 +1 @@ -cargo run -- --database-url postgres://postgres:password@0.0.0.0:5435/namada-indexer --cache-url redis://redis@0.0.0.0:6379 +cargo run -- --database-url postgres://postgres:password@0.0.0.0:5435/namada-indexer --cache-url redis://redis@0.0.0.0:6379 --tendermint-url http://127.0.0.1:27657 diff --git a/webserver/src/app.rs b/webserver/src/app.rs index 435dedb34..dfaf48706 100644 --- a/webserver/src/app.rs +++ b/webserver/src/app.rs @@ -9,6 +9,7 @@ use axum::routing::get; use axum::{BoxError, Json, Router}; use axum_trace_id::SetTraceIdLayer; use lazy_static::lazy_static; +use namada_sdk::tendermint_rpc::HttpClient; use serde_json::json; use tower::buffer::BufferLayer; use tower::limit::RateLimitLayer; @@ -20,7 +21,7 @@ use crate::appstate::AppState; use crate::config::AppConfig; use crate::handler::{ balance as balance_handlers, chain as chain_handlers, - governance as gov_handlers, pos as pos_handlers, + governance as gov_handlers, pk as pk_handlers, pos as pos_handlers, }; use crate::state::common::CommonState; @@ -37,9 +38,10 @@ impl ApplicationServer { let cache_url = config.cache_url.clone(); let app_state = AppState::new(db_url, cache_url); + let client = HttpClient::new(config.tendermint_url.as_str()).unwrap(); let routes = { - let common_state = CommonState::new(app_state.clone()); + let common_state = CommonState::new(client, app_state.clone()); Router::new() .route("/pos/validator", get(pos_handlers::get_validators)) @@ -82,6 +84,10 @@ impl ApplicationServer { "/account/:address", get(balance_handlers::get_address_balance), ) + .route( + "/revealed_public_key/:address", + get(pk_handlers::get_revealed_pk), + ) .route("/chain/sync", get(chain_handlers::sync_height)) .with_state(common_state) }; diff --git a/webserver/src/config.rs b/webserver/src/config.rs index c9ffe4753..9c652c7eb 100644 --- a/webserver/src/config.rs +++ b/webserver/src/config.rs @@ -17,4 +17,7 @@ pub struct AppConfig { #[clap(long, env)] pub rps: Option, + + #[clap(long, env)] + pub tendermint_url: String, } diff --git a/webserver/src/error/api.rs b/webserver/src/error/api.rs index cde31f6d5..d546a58f3 100644 --- a/webserver/src/error/api.rs +++ b/webserver/src/error/api.rs @@ -5,6 +5,7 @@ use thiserror::Error; use super::balance::BalanceError; use super::governance::GovernanceError; use super::pos::PoSError; +use super::revealed_pk::RevealedPkError; use crate::response::api::ApiErrorResponse; #[derive(Error, Debug)] @@ -15,6 +16,8 @@ pub enum ApiError { BalanceError(#[from] BalanceError), #[error(transparent)] GovernanceError(#[from] GovernanceError), + #[error(transparent)] + RevealedPkError(#[from] RevealedPkError), #[error("No chain parameters stored")] NoChainParameters, #[error("Invalid request header")] @@ -33,6 +36,7 @@ impl IntoResponse for ApiError { ApiError::PoSError(error) => error.into_response(), ApiError::BalanceError(error) => error.into_response(), ApiError::GovernanceError(error) => error.into_response(), + ApiError::RevealedPkError(error) => error.into_response(), ApiError::InvalidHeader => ApiErrorResponse::send( StatusCode::BAD_REQUEST.as_u16(), Some("Invalid Header".to_string()), diff --git a/webserver/src/error/mod.rs b/webserver/src/error/mod.rs index 1e1daa86f..e5828d668 100644 --- a/webserver/src/error/mod.rs +++ b/webserver/src/error/mod.rs @@ -2,3 +2,4 @@ pub mod api; pub mod balance; pub mod governance; pub mod pos; +pub mod revealed_pk; diff --git a/webserver/src/error/revealed_pk.rs b/webserver/src/error/revealed_pk.rs new file mode 100644 index 000000000..8b6255b1a --- /dev/null +++ b/webserver/src/error/revealed_pk.rs @@ -0,0 +1,30 @@ +use axum::http::StatusCode; +use axum::response::{IntoResponse, Response}; +use thiserror::Error; + +use crate::response::api::ApiErrorResponse; + +#[derive(Error, Debug)] +pub enum RevealedPkError { + #[error("Revealed public key {0} not found")] + NotFound(u64), + #[error("Database error: {0}")] + Database(String), + #[error("Rpc error: {0}")] + Rpc(String), + #[error("Unknown error: {0}")] + Unknown(String), +} + +impl IntoResponse for RevealedPkError { + fn into_response(self) -> Response { + let status_code = match self { + RevealedPkError::NotFound(_) => StatusCode::NOT_FOUND, + RevealedPkError::Unknown(_) + | RevealedPkError::Database(_) + | RevealedPkError::Rpc(_) => StatusCode::INTERNAL_SERVER_ERROR, + }; + + ApiErrorResponse::send(status_code.as_u16(), Some(self.to_string())) + } +} diff --git a/webserver/src/handler/mod.rs b/webserver/src/handler/mod.rs index aa95e0d78..c75163b41 100644 --- a/webserver/src/handler/mod.rs +++ b/webserver/src/handler/mod.rs @@ -1,4 +1,5 @@ pub mod balance; pub mod chain; pub mod governance; +pub mod pk; pub mod pos; diff --git a/webserver/src/handler/pk.rs b/webserver/src/handler/pk.rs new file mode 100644 index 000000000..43f5056e9 --- /dev/null +++ b/webserver/src/handler/pk.rs @@ -0,0 +1,24 @@ +use axum::extract::{Path, State}; +use axum::http::HeaderMap; +use axum::Json; +use axum_macros::debug_handler; +use axum_trace_id::TraceId; + +use crate::error::api::ApiError; +use crate::response::revealed_pk::RevealedPk; +use crate::state::common::CommonState; + +#[debug_handler] +pub async fn get_revealed_pk( + _trace_id: TraceId, + _headers: HeaderMap, + Path(address): Path, + State(state): State, +) -> Result, ApiError> { + let revealed_pk = state + .revealed_pk_service + .get_revealed_pk_by_address(&state.client, address) + .await?; + + Ok(Json(revealed_pk)) +} diff --git a/webserver/src/repository/mod.rs b/webserver/src/repository/mod.rs index 5310da139..a5a54be62 100644 --- a/webserver/src/repository/mod.rs +++ b/webserver/src/repository/mod.rs @@ -2,4 +2,5 @@ pub mod balance; pub mod chain; pub mod governance; pub mod pos; +pub mod revealed_pk; pub mod utils; diff --git a/webserver/src/repository/revealed_pk.rs b/webserver/src/repository/revealed_pk.rs new file mode 100644 index 000000000..a03ed5751 --- /dev/null +++ b/webserver/src/repository/revealed_pk.rs @@ -0,0 +1,69 @@ +use axum::async_trait; +use diesel::{ExpressionMethods, QueryDsl, RunQueryDsl, SelectableHelper}; +use orm::revealed_pk::{RevealedPkDb, RevealedPkInsertDb}; +use orm::schema::revealed_pk; + +use crate::appstate::AppState; + +#[derive(Clone)] +pub struct RevealedPkRepo { + pub(crate) app_state: AppState, +} + +#[async_trait] +pub trait PkRepoTrait { + fn new(app_state: AppState) -> Self; + + async fn get_revealed_pk_by_address( + &self, + address: String, + ) -> Result, String>; + + async fn insert_revealed_pk( + &self, + address: RevealedPkInsertDb, + ) -> Result<(), String>; +} + +#[async_trait] +impl PkRepoTrait for RevealedPkRepo { + fn new(app_state: AppState) -> Self { + Self { app_state } + } + + async fn get_revealed_pk_by_address( + &self, + address: String, + ) -> Result, String> { + let conn = self.app_state.get_db_connection().await; + + conn.interact(move |conn| { + revealed_pk::table + .filter(revealed_pk::dsl::address.eq(address)) + .select(RevealedPkDb::as_select()) + .first(conn) + .ok() + }) + .await + .map_err(|e| e.to_string()) + } + + async fn insert_revealed_pk( + &self, + revealed_pk: RevealedPkInsertDb, + ) -> Result<(), String> { + let conn = self.app_state.get_db_connection().await; + + conn.interact(move |conn| -> Result<(), String> { + diesel::insert_into(revealed_pk::table) + .values::>(vec![revealed_pk]) + .on_conflict_do_nothing() + .execute(conn) + .map_err(|e| e.to_string())?; + + Ok(()) + }) + .await + .map_err(|e| e.to_string())? + } +} diff --git a/webserver/src/response/mod.rs b/webserver/src/response/mod.rs index 381e887e3..e756ce66e 100644 --- a/webserver/src/response/mod.rs +++ b/webserver/src/response/mod.rs @@ -2,4 +2,5 @@ pub mod api; pub mod balance; pub mod governance; pub mod pos; +pub mod revealed_pk; pub mod utils; diff --git a/webserver/src/response/revealed_pk.rs b/webserver/src/response/revealed_pk.rs new file mode 100644 index 000000000..0c40d0aa9 --- /dev/null +++ b/webserver/src/response/revealed_pk.rs @@ -0,0 +1,16 @@ +use orm::revealed_pk::RevealedPkDb; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct RevealedPk { + pub public_key: Option, +} + +impl From for RevealedPk { + fn from(value: RevealedPkDb) -> Self { + Self { + public_key: Some(value.pk), + } + } +} diff --git a/webserver/src/service/mod.rs b/webserver/src/service/mod.rs index 5310da139..a5a54be62 100644 --- a/webserver/src/service/mod.rs +++ b/webserver/src/service/mod.rs @@ -2,4 +2,5 @@ pub mod balance; pub mod chain; pub mod governance; pub mod pos; +pub mod revealed_pk; pub mod utils; diff --git a/webserver/src/service/revealed_pk.rs b/webserver/src/service/revealed_pk.rs new file mode 100644 index 000000000..67ef80176 --- /dev/null +++ b/webserver/src/service/revealed_pk.rs @@ -0,0 +1,70 @@ +use std::str::FromStr; + +use crate::appstate::AppState; +use crate::error::revealed_pk::RevealedPkError; +use crate::repository::revealed_pk::{PkRepoTrait, RevealedPkRepo}; +use crate::response::revealed_pk::RevealedPk; + +use namada_sdk::address::Address as NamadaAddress; +use namada_sdk::rpc; +use namada_sdk::tendermint_rpc::HttpClient; +use orm::revealed_pk::RevealedPkInsertDb; + +#[derive(Clone)] +pub struct RevealedPkService { + pub revealed_pk_repo: RevealedPkRepo, +} + +impl RevealedPkService { + pub fn new(app_state: AppState) -> Self { + Self { + revealed_pk_repo: RevealedPkRepo::new(app_state), + } + } + + pub async fn get_revealed_pk_by_address( + &self, + client: &HttpClient, + address: String, + ) -> Result { + // We look for a revealed public key in the database + let revealed_pk_db = self + .revealed_pk_repo + .get_revealed_pk_by_address(address.clone()) + .await + .map_err(RevealedPkError::Database)?; + + match revealed_pk_db { + // If we find a revealed public key in the database, we return it + Some(revealed_pk_db) => Ok(RevealedPk::from(revealed_pk_db)), + // If we don't find a revealed public key in the database, we look for it in the + // storage + None => { + let address = + NamadaAddress::from_str(&address).expect("Invalid address"); + + let public_key = rpc::get_public_key_at(client, &address, 0) + .await + .map_err(|e| RevealedPkError::Rpc(e.to_string()))?; + + // If we find a public key in the storage, we insert it in the database + if let Some(public_key) = public_key.clone() { + // TODO: maybe better to create it using strcuts from shared + let revealed_pk_db = RevealedPkInsertDb { + pk: public_key.to_string(), + address: address.to_string(), + }; + + self.revealed_pk_repo + .insert_revealed_pk(revealed_pk_db) + .await + .map_err(RevealedPkError::Database)?; + }; + + Ok(RevealedPk { + public_key: public_key.map(|pk| pk.to_string()), + }) + } + } + } +} diff --git a/webserver/src/state/common.rs b/webserver/src/state/common.rs index 27844d3f1..57561202d 100644 --- a/webserver/src/state/common.rs +++ b/webserver/src/state/common.rs @@ -1,8 +1,11 @@ +use namada_sdk::tendermint_rpc::HttpClient; + use crate::appstate::AppState; use crate::service::balance::BalanceService; use crate::service::chain::ChainService; use crate::service::governance::GovernanceService; use crate::service::pos::PosService; +use crate::service::revealed_pk::RevealedPkService; #[derive(Clone)] pub struct CommonState { @@ -10,15 +13,19 @@ pub struct CommonState { pub gov_service: GovernanceService, pub balance_service: BalanceService, pub chain_service: ChainService, + pub revealed_pk_service: RevealedPkService, + pub client: HttpClient, } impl CommonState { - pub fn new(data: AppState) -> Self { + pub fn new(client: HttpClient, data: AppState) -> Self { Self { pos_service: PosService::new(data.clone()), gov_service: GovernanceService::new(data.clone()), balance_service: BalanceService::new(data.clone()), chain_service: ChainService::new(data.clone()), + revealed_pk_service: RevealedPkService::new(data.clone()), + client, } } } From 1b7a0efc7bbf46e11f339d5a49b41c03e10b33e8 Mon Sep 17 00:00:00 2001 From: Mateusz Jasiuk Date: Tue, 4 Jun 2024 12:11:29 +0200 Subject: [PATCH 7/8] feat: gas table --- Cargo.toml | 1 + swagger.yml | 20 ++++++++++++++++++ webserver/Cargo.toml | 3 ++- webserver/src/app.rs | 4 +++- webserver/src/handler/gas.rs | 20 ++++++++++++++++++ webserver/src/handler/mod.rs | 1 + webserver/src/response/gas.rs | 8 +++++++ webserver/src/response/mod.rs | 1 + webserver/src/service/gas.rs | 39 +++++++++++++++++++++++++++++++++++ webserver/src/service/mod.rs | 1 + webserver/src/state/common.rs | 3 +++ 11 files changed, 99 insertions(+), 2 deletions(-) create mode 100644 webserver/src/handler/gas.rs create mode 100644 webserver/src/response/gas.rs create mode 100644 webserver/src/service/gas.rs diff --git a/Cargo.toml b/Cargo.toml index 3e49ed961..a4d7a199f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,6 +45,7 @@ namada_tx = { git = "https://github.com/anoma/namada", tag = "v0.38.1" } namada_governance = { git = "https://github.com/anoma/namada", tag = "v0.38.1" } namada_ibc = { git = "https://github.com/anoma/namada", tag = "v0.38.1" } namada_token = { git = "https://github.com/anoma/namada", tag = "v0.38.1" } +namada_parameters = { git = "https://github.com/anoma/namada", tag = "v0.38.1" } tendermint = "0.36.0" tendermint-config = "0.36.0" tendermint-rpc = { version = "0.36.0", features = ["http-client"] } diff --git a/swagger.yml b/swagger.yml index 725950955..e082b76de 100644 --- a/swagger.yml +++ b/swagger.yml @@ -320,6 +320,19 @@ paths: schema: $ref: '#/components/schemas/RevealedPk' + /api/v1/gas_table: + get: + summary: Get the gas table + responses: + '200': + description: Gas table + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Balance' + components: schemas: Validator: @@ -475,4 +488,11 @@ components: properties: publicKey: type: string + GasCost: + type: object + properties: + tokenAddress: + type: string + amount: + type: string diff --git a/webserver/Cargo.toml b/webserver/Cargo.toml index 3ed8855e2..f67880ecb 100644 --- a/webserver/Cargo.toml +++ b/webserver/Cargo.toml @@ -25,7 +25,6 @@ tokio.workspace = true tower.workspace = true tower-http.workspace = true tracing.workspace = true -namada_sdk.workspace = true tracing-subscriber.workspace = true serde.workspace = true serde_json.workspace = true @@ -42,6 +41,8 @@ diesel.workspace = true futures.workspace = true tokio-stream.workspace = true namada_core.workspace = true +namada_sdk.workspace = true +namada_parameters.workspace = true deadpool-redis = "0.13.0" [build-dependencies] diff --git a/webserver/src/app.rs b/webserver/src/app.rs index dfaf48706..fd4003489 100644 --- a/webserver/src/app.rs +++ b/webserver/src/app.rs @@ -20,7 +20,7 @@ use tower_http::trace::TraceLayer; use crate::appstate::AppState; use crate::config::AppConfig; use crate::handler::{ - balance as balance_handlers, chain as chain_handlers, + balance as balance_handlers, chain as chain_handlers, gas as gas_handlers, governance as gov_handlers, pk as pk_handlers, pos as pos_handlers, }; use crate::state::common::CommonState; @@ -88,6 +88,8 @@ impl ApplicationServer { "/revealed_public_key/:address", get(pk_handlers::get_revealed_pk), ) + // TODO: this is a temporary endpoint, we should get gas cost per tx type + .route("/gas_table", get(gas_handlers::get_gas_table)) .route("/chain/sync", get(chain_handlers::sync_height)) .with_state(common_state) }; diff --git a/webserver/src/handler/gas.rs b/webserver/src/handler/gas.rs new file mode 100644 index 000000000..0bbe9f683 --- /dev/null +++ b/webserver/src/handler/gas.rs @@ -0,0 +1,20 @@ +use axum::extract::State; +use axum::http::HeaderMap; +use axum::Json; +use axum_macros::debug_handler; +use axum_trace_id::TraceId; + +use crate::error::api::ApiError; +use crate::response::gas::GasCost; +use crate::state::common::CommonState; + +#[debug_handler] +pub async fn get_gas_table( + _trace_id: TraceId, + _headers: HeaderMap, + State(state): State, +) -> Result>, ApiError> { + let gas_table = state.gas_service.get_gas_table(&state.client).await; + + Ok(Json(gas_table)) +} diff --git a/webserver/src/handler/mod.rs b/webserver/src/handler/mod.rs index c75163b41..70fcc9317 100644 --- a/webserver/src/handler/mod.rs +++ b/webserver/src/handler/mod.rs @@ -1,5 +1,6 @@ pub mod balance; pub mod chain; +pub mod gas; pub mod governance; pub mod pk; pub mod pos; diff --git a/webserver/src/response/gas.rs b/webserver/src/response/gas.rs new file mode 100644 index 000000000..5bdd0b580 --- /dev/null +++ b/webserver/src/response/gas.rs @@ -0,0 +1,8 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct GasCost { + pub token_address: String, + pub amount: String, +} diff --git a/webserver/src/response/mod.rs b/webserver/src/response/mod.rs index e756ce66e..8174dff9f 100644 --- a/webserver/src/response/mod.rs +++ b/webserver/src/response/mod.rs @@ -1,5 +1,6 @@ pub mod api; pub mod balance; +pub mod gas; pub mod governance; pub mod pos; pub mod revealed_pk; diff --git a/webserver/src/service/gas.rs b/webserver/src/service/gas.rs new file mode 100644 index 000000000..b601d6ef6 --- /dev/null +++ b/webserver/src/service/gas.rs @@ -0,0 +1,39 @@ +use std::collections::BTreeMap; + +use namada_parameters::storage as parameter_storage; +use namada_sdk::{ + address::Address, rpc::query_storage_value, tendermint_rpc::HttpClient, + token, +}; + +use crate::{appstate::AppState, response::gas::GasCost}; + +#[derive(Clone)] +pub struct GasService {} + +impl GasService { + pub fn new(_app_state: AppState) -> Self { + Self {} + } + + pub async fn get_gas_table(&self, client: &HttpClient) -> Vec { + let key = parameter_storage::get_gas_cost_key(); + let gas_cost_table = query_storage_value::< + HttpClient, + BTreeMap, + >(client, &key) + .await + .expect("Parameter should be defined."); + + let mut gas_table: Vec = Vec::new(); + + for (token, gas_cost) in gas_cost_table { + gas_table.push(GasCost { + token_address: token.to_string(), + amount: gas_cost.to_string_native(), + }) + } + + gas_table + } +} diff --git a/webserver/src/service/mod.rs b/webserver/src/service/mod.rs index a5a54be62..36c092c5e 100644 --- a/webserver/src/service/mod.rs +++ b/webserver/src/service/mod.rs @@ -1,5 +1,6 @@ pub mod balance; pub mod chain; +pub mod gas; pub mod governance; pub mod pos; pub mod revealed_pk; diff --git a/webserver/src/state/common.rs b/webserver/src/state/common.rs index 57561202d..9f58cba21 100644 --- a/webserver/src/state/common.rs +++ b/webserver/src/state/common.rs @@ -3,6 +3,7 @@ use namada_sdk::tendermint_rpc::HttpClient; use crate::appstate::AppState; use crate::service::balance::BalanceService; use crate::service::chain::ChainService; +use crate::service::gas::GasService; use crate::service::governance::GovernanceService; use crate::service::pos::PosService; use crate::service::revealed_pk::RevealedPkService; @@ -14,6 +15,7 @@ pub struct CommonState { pub balance_service: BalanceService, pub chain_service: ChainService, pub revealed_pk_service: RevealedPkService, + pub gas_service: GasService, pub client: HttpClient, } @@ -25,6 +27,7 @@ impl CommonState { balance_service: BalanceService::new(data.clone()), chain_service: ChainService::new(data.clone()), revealed_pk_service: RevealedPkService::new(data.clone()), + gas_service: GasService::new(data.clone()), client, } } From b5733875bd53cf6999ad375bf0278ada8f3a2703 Mon Sep 17 00:00:00 2001 From: Mateusz Jasiuk Date: Tue, 4 Jun 2024 12:26:07 +0200 Subject: [PATCH 8/8] fix: clippy --- chain/src/main.rs | 4 ++-- chain/src/services/namada.rs | 5 ++--- seeder/src/main.rs | 19 +++++++++++++++---- shared/src/proposal.rs | 4 ++++ 4 files changed, 23 insertions(+), 9 deletions(-) diff --git a/chain/src/main.rs b/chain/src/main.rs index 78be5d1fd..d0c88c63a 100644 --- a/chain/src/main.rs +++ b/chain/src/main.rs @@ -281,12 +281,12 @@ async fn initial_query( tracing::info!("Querying proposals..."); let proposals = query_all_proposals(client).await.into_rpc_error()?; let proposals_with_tally = - namada_service::query_tallies(&client, proposals.clone()) + namada_service::query_tallies(client, proposals.clone()) .await .into_rpc_error()?; let proposals_votes = namada_service::query_all_votes( - &client, + client, proposals.iter().map(|p| p.id).collect(), ) .await diff --git a/chain/src/services/namada.rs b/chain/src/services/namada.rs index fbe179ed2..b7e2483c2 100644 --- a/chain/src/services/namada.rs +++ b/chain/src/services/namada.rs @@ -435,8 +435,7 @@ pub async fn query_tallies( ) -> anyhow::Result> { let proposals = futures::stream::iter(proposals) .filter_map(|proposal| async move { - let is_steward = - is_steward(&client, &proposal.author).await.ok()?; + let is_steward = is_steward(client, &proposal.author).await.ok()?; let tally_type = TallyType::from(&proposal.r#type, is_steward); @@ -475,7 +474,7 @@ pub async fn query_all_votes( .collect::>() .await; - anyhow::Ok(votes.iter().cloned().flatten().collect()) + anyhow::Ok(votes.iter().flatten().cloned().collect()) } fn to_block_height(block_height: u32) -> NamadaSdkBlockHeight { diff --git a/seeder/src/main.rs b/seeder/src/main.rs index 66e8a62c6..17a8865da 100644 --- a/seeder/src/main.rs +++ b/seeder/src/main.rs @@ -21,7 +21,9 @@ use seeder::state::AppState; use shared::balance::Balance; use shared::bond::Bond; use shared::error::{AsDbError, ContextDbInteractError, MainError}; -use shared::proposal::{GovernanceProposal, GovernanceProposalStatus}; +use shared::proposal::{ + GovernanceProposal, GovernanceProposalStatus, TallyType, +}; use shared::rewards::Reward; use shared::unbond::Unbond; use shared::validator::Validator; @@ -58,6 +60,15 @@ async fn main() -> anyhow::Result<(), MainError> { .map(GovernanceProposal::fake) .collect::>(); + let governance_proposals_with_tally = governance_proposals + .iter() + .cloned() + .map(|proposal| { + let tally_type = TallyType::fake(); + (proposal, tally_type) + }) + .collect::>(); + let governance_proposals_status = (0..config.total_proposals) .map(GovernanceProposalStatus::fake) .collect::>(); @@ -149,10 +160,10 @@ async fn main() -> anyhow::Result<(), MainError> { diesel::insert_into(governance_proposals::table) .values::<&Vec>( - &governance_proposals + &governance_proposals_with_tally .into_iter() - .map(|proposal| { - GovernanceProposalInsertDb::from_governance_proposal(proposal) + .map(|(proposal, tally_type)| { + GovernanceProposalInsertDb::from_governance_proposal(proposal, tally_type) }) .collect::>(), ) diff --git a/shared/src/proposal.rs b/shared/src/proposal.rs index edfb299ce..6cd55d204 100644 --- a/shared/src/proposal.rs +++ b/shared/src/proposal.rs @@ -227,6 +227,10 @@ pub enum TallyType { // TODO: copied from namada for time being impl TallyType { + pub fn fake() -> Self { + rand::random() + } + pub fn from( proposal_type: &GovernanceProposalKind, is_steward: bool,