diff --git a/Cargo.lock b/Cargo.lock index c9d829c..fe38fd1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -984,6 +984,7 @@ version = "0.1.0" dependencies = [ "anyhow", "arrayvec 0.7.0", + "bincode", "duniter-core", "duniter-gva-db", "flate2", diff --git a/dbs-reader/Cargo.toml b/dbs-reader/Cargo.toml index 3063442..b25a488 100644 --- a/dbs-reader/Cargo.toml +++ b/dbs-reader/Cargo.toml @@ -17,6 +17,7 @@ mock = ["mockall"] [dependencies] anyhow = "1.0.34" arrayvec = { version = "0.7", features = ["serde"] } +bincode = "1.3" duniter-core = { git = "https://git.duniter.org/nodes/rust/duniter-core" } duniter-gva-db = { path = "../db" } flate2 = { version = "1.0", features = ["zlib-ng-compat"], default-features = false } diff --git a/dbs-reader/src/cursors.rs b/dbs-reader/src/cursors.rs new file mode 100644 index 0000000..b23b938 --- /dev/null +++ b/dbs-reader/src/cursors.rs @@ -0,0 +1,83 @@ +// Copyright (C) 2020 Éloïs SANCHEZ. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +use crate::*; +use duniter_core::crypto::keys::ed25519::PublicKey; +use duniter_core::crypto::keys::PublicKey as _; +use duniter_core::dbs::WalletConditionsV2; + +#[derive(Clone, Copy, Debug)] +pub struct WrongCursor; +impl std::fmt::Display for WrongCursor { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "wrong cursor") + } +} +impl std::error::Error for WrongCursor {} + +pub trait Cursor: + 'static + Clone + std::fmt::Debug + std::fmt::Display + Default + FromStr + Ord +{ +} + +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] +pub struct PubKeyCursor(pub PublicKey); + +impl PubKeyCursor { + pub fn from_ref(pk: &PublicKey) -> &Self { + #[allow(trivial_casts)] + unsafe { + &*(pk as *const PublicKey as *const PubKeyCursor) + } + } +} + +impl Cursor for PubKeyCursor {} + +impl std::fmt::Display for PubKeyCursor { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0.to_string()) + } +} + +impl FromStr for PubKeyCursor { + type Err = WrongCursor; + + fn from_str(s: &str) -> Result { + if let Ok(pk) = PublicKey::from_base58(s) { + Ok(PubKeyCursor(pk)) + } else { + Err(WrongCursor) + } + } +} + +impl From for WalletConditionsV2 { + fn from(val: PubKeyCursor) -> Self { + WalletConditionsV2(WalletScriptV10::single_sig(val.0)) + } +} + +impl Ord for PubKeyCursor { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.0.as_ref().cmp(other.0.as_ref()) + } +} + +impl PartialOrd for PubKeyCursor { + fn partial_cmp(&self, other: &Self) -> Option { + self.0.as_ref().partial_cmp(other.0.as_ref()) + } +} diff --git a/dbs-reader/src/lib.rs b/dbs-reader/src/lib.rs index 98fba11..0d45be7 100644 --- a/dbs-reader/src/lib.rs +++ b/dbs-reader/src/lib.rs @@ -25,6 +25,7 @@ pub mod block; pub mod blocks_chunks; pub mod current_frame; +pub mod cursors; pub mod find_inputs; pub mod idty; pub mod network; @@ -32,7 +33,9 @@ pub mod pagination; pub mod txs_history; pub mod uds_of_pubkey; pub mod utxos; +pub mod wallets; +pub use crate::cursors::{Cursor, PubKeyCursor, WrongCursor}; pub use crate::pagination::{PageInfo, PagedData}; pub use duniter_core::bda_types::MAX_FIRST_UTXOS; @@ -69,15 +72,6 @@ use std::{ str::FromStr, }; -#[derive(Clone, Copy, Debug)] -pub struct WrongCursor; -impl std::fmt::Display for WrongCursor { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "wrong cursor") - } -} -impl std::error::Error for WrongCursor {} - #[cfg_attr(feature = "mock", mockall::automock)] pub trait DbsReader { fn all_uds_of_pubkey( @@ -181,6 +175,29 @@ pub trait DbsReader { bn_to_exclude_opt: Option>, amount_target_opt: Option, ) -> KvResult>; + fn wallets( + &self, + exclude_single_sig: bool, + min_balance_opt: Option, + page_info: PageInfo, + ) -> KvResult>>; + fn wallets_single_sig( + &self, + min_balance_opt: Option, + page_info: PageInfo, + ) -> KvResult>>; + fn wallets_single_sig_with_idty_opt( + &self, + bc_db: &BcV2DbRo, + min_balance_opt: Option, + page_info: PageInfo, + ) -> KvResult>>; + fn wallets_with_idty_opt( + &self, + bc_db: &BcV2DbRo, + min_balance_opt: Option, + page_info: PageInfo, + ) -> KvResult>>; } #[derive(Clone, Copy, Debug)] @@ -359,6 +376,41 @@ impl DbsReader for DbsReaderImpl { amount_target_opt, ) } + + fn wallets( + &self, + exclude_single_sig: bool, + min_balance_opt: Option, + page_info: PageInfo, + ) -> KvResult>> { + self.wallets_(exclude_single_sig, min_balance_opt, page_info) + } + + fn wallets_single_sig( + &self, + min_balance_opt: Option, + page_info: PageInfo, + ) -> KvResult>> { + self.wallets_single_sig_(min_balance_opt, page_info) + } + + fn wallets_single_sig_with_idty_opt( + &self, + bc_db: &BcV2DbRo, + min_balance_opt: Option, + page_info: PageInfo, + ) -> KvResult>> { + self.wallets_single_sig_with_idty_opt_(bc_db, min_balance_opt, page_info) + } + + fn wallets_with_idty_opt( + &self, + bc_db: &BcV2DbRo, + min_balance_opt: Option, + page_info: PageInfo, + ) -> KvResult>> { + self.wallets_with_idty_opt_(bc_db, min_balance_opt, page_info) + } } #[cfg(test)] diff --git a/dbs-reader/src/pagination.rs b/dbs-reader/src/pagination.rs index 48d80bb..8cd2931 100644 --- a/dbs-reader/src/pagination.rs +++ b/dbs-reader/src/pagination.rs @@ -30,6 +30,19 @@ impl PagedData { } } } +impl PagedData { + pub fn map(self, f: F) -> PagedData + where + T: std::fmt::Debug, + F: FnOnce(D) -> T, + { + PagedData { + data: f(self.data), + has_previous_page: self.has_previous_page, + has_next_page: self.has_next_page, + } + } +} #[derive(Debug)] pub struct PageInfo { diff --git a/dbs-reader/src/wallets.rs b/dbs-reader/src/wallets.rs new file mode 100644 index 0000000..a6dca72 --- /dev/null +++ b/dbs-reader/src/wallets.rs @@ -0,0 +1,319 @@ +// Copyright (C) 2020 Éloïs SANCHEZ. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +use crate::*; +use duniter_core::crypto::keys::ed25519::PublicKey; +use duniter_core::crypto::keys::PublicKey as _; +use duniter_core::dbs::{bincode_db, IdtyDbV2, WalletConditionsV2}; + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct WalletCursor(WalletScriptV10); +impl WalletCursor { + pub fn from_ref(script: &WalletScriptV10) -> &Self { + #[allow(trivial_casts)] + unsafe { + &*(script as *const WalletScriptV10 as *const WalletCursor) + } + } +} + +impl Cursor for WalletCursor {} + +impl Default for WalletCursor { + fn default() -> Self { + WalletCursor(WalletScriptV10::single_sig(PublicKey::default())) + } +} + +impl std::fmt::Display for WalletCursor { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0.to_string()) + } +} + +impl FromStr for WalletCursor { + type Err = WrongCursor; + + fn from_str(s: &str) -> Result { + if let Ok(pubkey) = PublicKey::from_base58(s) { + Ok(WalletCursor(WalletScriptV10::single_sig(pubkey))) + } else if let Ok(wallet_script) = duniter_core::documents_parser::wallet_script_from_str(s) + { + Ok(WalletCursor(wallet_script)) + } else { + Err(WrongCursor) + } + } +} + +impl Ord for WalletCursor { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + use bincode::config::Options as _; + let self_bin = bincode_db() + .serialize(&self.0) + .unwrap_or_else(|_| unreachable!()); + let other_bin = bincode_db() + .serialize(&other.0) + .unwrap_or_else(|_| unreachable!()); + self_bin.cmp(&other_bin) + } +} + +impl From for WalletConditionsV2 { + fn from(val: WalletCursor) -> Self { + WalletConditionsV2(val.0) + } +} + +impl PartialOrd for WalletCursor { + fn partial_cmp(&self, other: &Self) -> Option { + use bincode::config::Options as _; + let self_bin = bincode_db() + .serialize(&self.0) + .unwrap_or_else(|_| unreachable!()); + let other_bin = bincode_db() + .serialize(&other.0) + .unwrap_or_else(|_| unreachable!()); + self_bin.partial_cmp(&other_bin) + } +} + +#[derive(Clone, Copy, Debug)] +pub struct PublicKeyWithBalance(pub PublicKey, pub SourceAmount); +impl AsRef for PublicKeyWithBalance { + fn as_ref(&self) -> &PubKeyCursor { + PubKeyCursor::from_ref(&self.0) + } +} + +#[derive(Debug)] +pub struct ScriptWithBalance(pub WalletScriptV10, pub SourceAmount); +impl AsRef for ScriptWithBalance { + fn as_ref(&self) -> &WalletCursor { + WalletCursor::from_ref(&self.0) + } +} + +#[derive(Debug)] +pub struct WalletSingleSigWithIdtyOpt(pub PublicKeyWithBalance, pub Option); + +#[derive(Debug)] +pub struct WalletWithIdtyOpt(pub ScriptWithBalance, pub Option); + +impl DbsReaderImpl { + pub(super) fn wallets_( + &self, + exclude_single_sig: bool, + min_balance_opt: Option, + page_info: PageInfo, + ) -> KvResult>> { + if let Some(min_balance) = min_balance_opt { + if exclude_single_sig { + self.wallets_inner( + |(k, v)| { + if !k.0.is_single_sig() && v.0 >= min_balance { + Some(ScriptWithBalance(k.0, v.0)) + } else { + None + } + }, + page_info, + ) + } else { + self.wallets_inner( + |(k, v)| { + if v.0 >= min_balance { + Some(ScriptWithBalance(k.0, v.0)) + } else { + None + } + }, + page_info, + ) + } + } else if exclude_single_sig { + self.wallets_inner( + |(k, v)| { + if !k.0.is_single_sig() { + Some(ScriptWithBalance(k.0, v.0)) + } else { + None + } + }, + page_info, + ) + } else { + self.wallets_inner(|(k, v)| Some(ScriptWithBalance(k.0, v.0)), page_info) + } + } + + pub(super) fn wallets_with_idty_opt_( + &self, + bc_db: &BcV2DbRo, + min_balance_opt: Option, + page_info: PageInfo, + ) -> KvResult>> { + let paged_data = self.wallets_(false, min_balance_opt, page_info)?; + + let mut data = Vec::with_capacity(paged_data.data.len()); + for script_with_balance in paged_data.data { + let idty_opt = if let Some(pubkey) = script_with_balance.0.as_single_sig() { + bc_db.identities().get(&PubKeyKeyV2(pubkey))? + } else { + None + }; + + data.push(WalletWithIdtyOpt(script_with_balance, idty_opt)); + } + + Ok(PagedData { + data, + has_next_page: paged_data.has_next_page, + has_previous_page: paged_data.has_previous_page, + }) + } + + pub(super) fn wallets_single_sig_( + &self, + min_balance_opt: Option, + page_info: PageInfo, + ) -> KvResult>> { + if let Some(min_balance) = min_balance_opt { + self.wallets_inner( + |(k, v)| { + if v.0 >= min_balance { + k.0.as_single_sig().map(|pk| PublicKeyWithBalance(pk, v.0)) + } else { + None + } + }, + page_info, + ) + } else { + self.wallets_inner( + |(k, v)| k.0.as_single_sig().map(|pk| PublicKeyWithBalance(pk, v.0)), + page_info, + ) + } + } + + pub(super) fn wallets_single_sig_with_idty_opt_( + &self, + bc_db: &BcV2DbRo, + min_balance_opt: Option, + page_info: PageInfo, + ) -> KvResult>> { + let paged_data = self.wallets_single_sig_(min_balance_opt, page_info)?; + + let mut data = Vec::with_capacity(paged_data.data.len()); + for pk_with_balance in paged_data.data { + let idty_opt = bc_db.identities().get(&PubKeyKeyV2(pk_with_balance.0))?; + data.push(WalletSingleSigWithIdtyOpt(pk_with_balance, idty_opt)); + } + + Ok(PagedData { + data, + has_next_page: paged_data.has_next_page, + has_previous_page: paged_data.has_previous_page, + }) + } + + fn wallets_inner( + &self, + filter_map: F, + page_info: PageInfo, + ) -> KvResult>> + where + C: Cursor + Into, + E: AsRef + std::fmt::Debug + Send + Sync, + F: Copy + Fn((WalletConditionsV2, SourceAmountValV2)) -> Option, + { + let first_cursor_opt = if page_info.not_all() { + self.0 + .balances() + .iter(.., |it| it.filter_map_ok(filter_map).next_res())? + .map(|element| element.as_ref().to_owned()) + } else { + None + }; + let last_cursor_opt = if page_info.not_all() { + self.0 + .balances() + .iter_rev(.., |it| it.filter_map_ok(filter_map).next_res())? + .map(|element| element.as_ref().to_owned()) + } else { + None + }; + + let cursor_opt = page_info.pos.clone(); + let data = if page_info.order { + let first_key = cursor_opt + .unwrap_or_else(|| first_cursor_opt.clone().unwrap_or_default()) + .into(); + self.0.balances().iter(first_key.., |it| { + if let Some(limit) = page_info.limit_opt { + it.filter_map_ok(filter_map) + .take(limit.get()) + .collect::>>() + } else { + it.filter_map_ok(filter_map).collect::>>() + } + })? + } else { + let last_key = cursor_opt + .unwrap_or_else(|| last_cursor_opt.clone().unwrap_or_default()) + .into(); + self.0.balances().iter_rev(..=last_key, |it| { + if let Some(limit) = page_info.limit_opt { + it.filter_map_ok(filter_map) + .take(limit.get()) + .collect::>>() + } else { + it.filter_map_ok(filter_map).collect::>>() + } + })? + }; + + let page_not_reversed = page_info.order; + + Ok(PagedData { + has_next_page: if page_info.order { + has_next_page( + data.iter() + .map(|element| OwnedOrRef::Borrow(element.as_ref())), + last_cursor_opt, + page_info.clone(), + page_not_reversed, + ) + } else { + // Server can't efficiently determine hasNextPage in DESC order + false + }, + has_previous_page: if page_info.order { + // Server can't efficiently determine hasPreviousPage in ASC order + false + } else { + has_previous_page( + data.iter() + .map(|element| OwnedOrRef::Borrow(element.as_ref())), + first_cursor_opt, + page_info, + page_not_reversed, + ) + }, + data, + }) + } +} diff --git a/gql/src/entities.rs b/gql/src/entities.rs index a7f5102..b2c93db 100644 --- a/gql/src/entities.rs +++ b/gql/src/entities.rs @@ -19,6 +19,7 @@ pub mod network; pub mod tx_gva; pub mod ud_gva; pub mod utxos_gva; +pub mod wallet_gva; use crate::*; @@ -27,11 +28,19 @@ pub(crate) struct AggregateSum { pub(crate) aggregate: Sum, } -#[derive(Default, async_graphql::SimpleObject)] +#[derive(Clone, Copy, Debug, Default, async_graphql::SimpleObject)] pub(crate) struct AmountWithBase { pub(crate) amount: i32, pub(crate) base: i32, } +impl From for AmountWithBase { + fn from(sa: SourceAmount) -> Self { + Self { + amount: sa.amount() as i32, + base: sa.base() as i32, + } + } +} #[derive(async_graphql::SimpleObject)] pub(crate) struct EdgeTx { diff --git a/gql/src/entities/idty_gva.rs b/gql/src/entities/idty_gva.rs index ee77b0e..8729895 100644 --- a/gql/src/entities/idty_gva.rs +++ b/gql/src/entities/idty_gva.rs @@ -13,7 +13,7 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -#[derive(async_graphql::SimpleObject)] +#[derive(Clone, Debug, async_graphql::SimpleObject)] pub(crate) struct Identity { pub is_member: bool, pub username: String, diff --git a/gql/src/entities/wallet_gva.rs b/gql/src/entities/wallet_gva.rs new file mode 100644 index 0000000..dfab890 --- /dev/null +++ b/gql/src/entities/wallet_gva.rs @@ -0,0 +1,41 @@ +// Copyright (C) 2020 Éloïs SANCHEZ. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +use crate::*; + +#[derive(Clone, Copy, async_graphql::Enum, Eq, PartialEq)] +pub(crate) enum WalletTypeFilter { + /// All wallets + All, + /// Exclude wallets scripts with single SIG condition + OnlyComplex, + /// Only wallets scripts with single SIG condition + OnlySimple, +} +impl Default for WalletTypeFilter { + fn default() -> WalletTypeFilter { + WalletTypeFilter::OnlySimple + } +} + +#[derive(Clone, Debug, async_graphql::SimpleObject)] +pub(crate) struct Wallet { + /// Wallet script or public key + pub(crate) script: String, + /// Wallet balance + pub(crate) balance: AmountWithBase, + /// Optional identity attached to this wallet + pub(crate) idty: Option, +} diff --git a/gql/src/lib.rs b/gql/src/lib.rs index e801b81..1d91438 100644 --- a/gql/src/lib.rs +++ b/gql/src/lib.rs @@ -41,6 +41,7 @@ use crate::entities::{ tx_gva::{PendingTxGva, WrittenTxGva}, ud_gva::{CurrentUdGva, RevalUdGva, UdGva}, utxos_gva::UtxosGva, + wallet_gva::{Wallet, WalletTypeFilter}, AggregateSum, AmountWithBase, EdgeTx, RawTxOrChanges, Sum, TxDirection, TxsHistoryMempool, UtxoGva, UtxoTimedGva, }; diff --git a/gql/src/queries.rs b/gql/src/queries.rs index ad733e3..1026447 100644 --- a/gql/src/queries.rs +++ b/gql/src/queries.rs @@ -24,6 +24,7 @@ pub mod network; pub mod txs_history; pub mod uds; pub mod utxos_of_script; +pub mod wallets; use crate::*; @@ -42,6 +43,7 @@ pub struct QueryRoot( queries::txs_history::TxsHistoryMempoolQuery, queries::uds::UdsQuery, queries::utxos_of_script::UtxosQuery, + queries::wallets::WalletsQuery, ); #[derive(Default, async_graphql::SimpleObject)] diff --git a/gql/src/queries/wallets.rs b/gql/src/queries/wallets.rs new file mode 100644 index 0000000..699a085 --- /dev/null +++ b/gql/src/queries/wallets.rs @@ -0,0 +1,172 @@ +// Copyright (C) 2020 Éloïs SANCHEZ. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +use crate::*; +use async_graphql::connection::*; +use duniter_gva_dbs_reader::{ + wallets::{WalletSingleSigWithIdtyOpt, WalletWithIdtyOpt}, + PagedData, +}; + +#[derive(Default)] +pub(crate) struct WalletsQuery; +#[async_graphql::Object] +impl WalletsQuery { + /// Universal dividends issued by a public key + #[allow(clippy::clippy::too_many_arguments)] + async fn wallets( + &self, + ctx: &async_graphql::Context<'_>, + #[graphql(desc = "minimal balance")] min_balance: Option, + #[graphql(desc = "pagination", default)] pagination: Pagination, + #[graphql(desc = "Wallet type filter", default)] wallet_type_filter: WalletTypeFilter, + ) -> async_graphql::Result> { + let QueryContext { is_whitelisted } = ctx.data::()?; + + let data = ctx.data::()?; + let dbs_reader = data.dbs_reader(); + + let current_base = + if let Some(current_ud) = data.cm_accessor.get_current_meta(|cm| cm.current_ud).await { + current_ud.base() + } else { + 0 + }; + let min_balance_opt = min_balance.map(|amount| SourceAmount::new(amount, current_base)); + + let PagedData { + data, + has_next_page, + has_previous_page, + }: PagedData> = match wallet_type_filter { + WalletTypeFilter::OnlyComplex => { + let pagination = Pagination::convert_to_page_info(pagination, *is_whitelisted)?; + data.dbs_pool + .execute(move |_| dbs_reader.wallets(true, min_balance_opt, pagination)) + .await?? + .map(|data| { + data.into_iter() + .map(|script_with_sa| Wallet { + script: script_with_sa.0.to_string(), + balance: AmountWithBase::from(script_with_sa.1), + idty: None, + }) + .collect() + }) + } + WalletTypeFilter::OnlySimple => { + let pagination = Pagination::convert_to_page_info(pagination, *is_whitelisted)?; + if ctx + .look_ahead() + .field("edges") + .field("node") + .field("idty") + .exists() + { + data.dbs_pool + .execute(move |shared_dbs| { + dbs_reader.wallets_single_sig_with_idty_opt( + &shared_dbs.bc_db_ro, + min_balance_opt, + pagination, + ) + }) + .await?? + .map(|data| { + data.into_iter() + .map(|WalletSingleSigWithIdtyOpt(pk_with_sa, idty_opt)| Wallet { + script: pk_with_sa.0.to_string(), + balance: AmountWithBase::from(pk_with_sa.1), + idty: idty_opt.map(|idty_db| Identity { + is_member: idty_db.is_member, + username: idty_db.username, + }), + }) + .collect() + }) + } else { + data.dbs_pool + .execute(move |_| { + dbs_reader.wallets_single_sig(min_balance_opt, pagination) + }) + .await?? + .map(|data| { + data.into_iter() + .map(|pk_with_sa| Wallet { + script: pk_with_sa.0.to_string(), + balance: AmountWithBase::from(pk_with_sa.1), + idty: None, + }) + .collect() + }) + } + } + WalletTypeFilter::All => { + let pagination = Pagination::convert_to_page_info(pagination, *is_whitelisted)?; + if ctx + .look_ahead() + .field("edges") + .field("node") + .field("idty") + .exists() + { + data.dbs_pool + .execute(move |shared_dbs| { + dbs_reader.wallets_with_idty_opt( + &shared_dbs.bc_db_ro, + min_balance_opt, + pagination, + ) + }) + .await?? + .map(|data| { + data.into_iter() + .map(|WalletWithIdtyOpt(script_with_sa, idty_opt)| Wallet { + script: script_with_sa.0.to_string(), + balance: AmountWithBase::from(script_with_sa.1), + idty: idty_opt.map(|idty_db| Identity { + is_member: idty_db.is_member, + username: idty_db.username, + }), + }) + .collect() + }) + } else { + data.dbs_pool + .execute(move |_| dbs_reader.wallets(false, min_balance_opt, pagination)) + .await?? + .map(|data| { + data.into_iter() + .map(|script_with_sa| Wallet { + script: script_with_sa.0.to_string(), + balance: AmountWithBase::from(script_with_sa.1), + idty: None, + }) + .collect() + }) + } + } + }; + + let mut conn = Connection::new(has_previous_page, has_next_page); + + conn.append( + data.into_iter() + .map(|wallet| Edge::new(wallet.script.clone(), wallet)), + ); + + Ok(conn) + } +}