feat(gql): add query wallets
This commit is contained in:
parent
a09ef43775
commit
2f73e4bce0
12 changed files with 705 additions and 11 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -984,6 +984,7 @@ version = "0.1.0"
|
|||
dependencies = [
|
||||
"anyhow",
|
||||
"arrayvec 0.7.0",
|
||||
"bincode",
|
||||
"duniter-core",
|
||||
"duniter-gva-db",
|
||||
"flate2",
|
||||
|
|
|
@ -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 }
|
||||
|
|
83
dbs-reader/src/cursors.rs
Normal file
83
dbs-reader/src/cursors.rs
Normal file
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
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<Self, Self::Err> {
|
||||
if let Ok(pk) = PublicKey::from_base58(s) {
|
||||
Ok(PubKeyCursor(pk))
|
||||
} else {
|
||||
Err(WrongCursor)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PubKeyCursor> 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<std::cmp::Ordering> {
|
||||
self.0.as_ref().partial_cmp(other.0.as_ref())
|
||||
}
|
||||
}
|
|
@ -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<std::collections::BTreeSet<BlockNumber>>,
|
||||
amount_target_opt: Option<SourceAmount>,
|
||||
) -> KvResult<PagedData<uds_of_pubkey::UdsWithSum>>;
|
||||
fn wallets(
|
||||
&self,
|
||||
exclude_single_sig: bool,
|
||||
min_balance_opt: Option<SourceAmount>,
|
||||
page_info: PageInfo<wallets::WalletCursor>,
|
||||
) -> KvResult<PagedData<Vec<wallets::ScriptWithBalance>>>;
|
||||
fn wallets_single_sig(
|
||||
&self,
|
||||
min_balance_opt: Option<SourceAmount>,
|
||||
page_info: PageInfo<PubKeyCursor>,
|
||||
) -> KvResult<PagedData<Vec<wallets::PublicKeyWithBalance>>>;
|
||||
fn wallets_single_sig_with_idty_opt(
|
||||
&self,
|
||||
bc_db: &BcV2DbRo<FileBackend>,
|
||||
min_balance_opt: Option<SourceAmount>,
|
||||
page_info: PageInfo<PubKeyCursor>,
|
||||
) -> KvResult<PagedData<Vec<wallets::WalletSingleSigWithIdtyOpt>>>;
|
||||
fn wallets_with_idty_opt(
|
||||
&self,
|
||||
bc_db: &BcV2DbRo<FileBackend>,
|
||||
min_balance_opt: Option<SourceAmount>,
|
||||
page_info: PageInfo<wallets::WalletCursor>,
|
||||
) -> KvResult<PagedData<Vec<wallets::WalletWithIdtyOpt>>>;
|
||||
}
|
||||
|
||||
#[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<SourceAmount>,
|
||||
page_info: PageInfo<wallets::WalletCursor>,
|
||||
) -> KvResult<PagedData<Vec<wallets::ScriptWithBalance>>> {
|
||||
self.wallets_(exclude_single_sig, min_balance_opt, page_info)
|
||||
}
|
||||
|
||||
fn wallets_single_sig(
|
||||
&self,
|
||||
min_balance_opt: Option<SourceAmount>,
|
||||
page_info: PageInfo<PubKeyCursor>,
|
||||
) -> KvResult<PagedData<Vec<wallets::PublicKeyWithBalance>>> {
|
||||
self.wallets_single_sig_(min_balance_opt, page_info)
|
||||
}
|
||||
|
||||
fn wallets_single_sig_with_idty_opt(
|
||||
&self,
|
||||
bc_db: &BcV2DbRo<FileBackend>,
|
||||
min_balance_opt: Option<SourceAmount>,
|
||||
page_info: PageInfo<PubKeyCursor>,
|
||||
) -> KvResult<PagedData<Vec<wallets::WalletSingleSigWithIdtyOpt>>> {
|
||||
self.wallets_single_sig_with_idty_opt_(bc_db, min_balance_opt, page_info)
|
||||
}
|
||||
|
||||
fn wallets_with_idty_opt(
|
||||
&self,
|
||||
bc_db: &BcV2DbRo<FileBackend>,
|
||||
min_balance_opt: Option<SourceAmount>,
|
||||
page_info: PageInfo<wallets::WalletCursor>,
|
||||
) -> KvResult<PagedData<Vec<wallets::WalletWithIdtyOpt>>> {
|
||||
self.wallets_with_idty_opt_(bc_db, min_balance_opt, page_info)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
|
@ -30,6 +30,19 @@ impl<D: std::fmt::Debug + Default> PagedData<D> {
|
|||
}
|
||||
}
|
||||
}
|
||||
impl<D: std::fmt::Debug> PagedData<D> {
|
||||
pub fn map<F, T>(self, f: F) -> PagedData<T>
|
||||
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<T> {
|
||||
|
|
319
dbs-reader/src/wallets.rs
Normal file
319
dbs-reader/src/wallets.rs
Normal file
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
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<Self, Self::Err> {
|
||||
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<WalletCursor> for WalletConditionsV2 {
|
||||
fn from(val: WalletCursor) -> Self {
|
||||
WalletConditionsV2(val.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for WalletCursor {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<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.partial_cmp(&other_bin)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct PublicKeyWithBalance(pub PublicKey, pub SourceAmount);
|
||||
impl AsRef<PubKeyCursor> for PublicKeyWithBalance {
|
||||
fn as_ref(&self) -> &PubKeyCursor {
|
||||
PubKeyCursor::from_ref(&self.0)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ScriptWithBalance(pub WalletScriptV10, pub SourceAmount);
|
||||
impl AsRef<WalletCursor> for ScriptWithBalance {
|
||||
fn as_ref(&self) -> &WalletCursor {
|
||||
WalletCursor::from_ref(&self.0)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct WalletSingleSigWithIdtyOpt(pub PublicKeyWithBalance, pub Option<IdtyDbV2>);
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct WalletWithIdtyOpt(pub ScriptWithBalance, pub Option<IdtyDbV2>);
|
||||
|
||||
impl DbsReaderImpl {
|
||||
pub(super) fn wallets_(
|
||||
&self,
|
||||
exclude_single_sig: bool,
|
||||
min_balance_opt: Option<SourceAmount>,
|
||||
page_info: PageInfo<WalletCursor>,
|
||||
) -> KvResult<PagedData<Vec<ScriptWithBalance>>> {
|
||||
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<FileBackend>,
|
||||
min_balance_opt: Option<SourceAmount>,
|
||||
page_info: PageInfo<WalletCursor>,
|
||||
) -> KvResult<PagedData<Vec<WalletWithIdtyOpt>>> {
|
||||
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<SourceAmount>,
|
||||
page_info: PageInfo<PubKeyCursor>,
|
||||
) -> KvResult<PagedData<Vec<PublicKeyWithBalance>>> {
|
||||
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<FileBackend>,
|
||||
min_balance_opt: Option<SourceAmount>,
|
||||
page_info: PageInfo<PubKeyCursor>,
|
||||
) -> KvResult<PagedData<Vec<WalletSingleSigWithIdtyOpt>>> {
|
||||
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<C, E, F>(
|
||||
&self,
|
||||
filter_map: F,
|
||||
page_info: PageInfo<C>,
|
||||
) -> KvResult<PagedData<Vec<E>>>
|
||||
where
|
||||
C: Cursor + Into<WalletConditionsV2>,
|
||||
E: AsRef<C> + std::fmt::Debug + Send + Sync,
|
||||
F: Copy + Fn((WalletConditionsV2, SourceAmountValV2)) -> Option<E>,
|
||||
{
|
||||
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::<KvResult<Vec<_>>>()
|
||||
} else {
|
||||
it.filter_map_ok(filter_map).collect::<KvResult<Vec<_>>>()
|
||||
}
|
||||
})?
|
||||
} 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::<KvResult<Vec<_>>>()
|
||||
} else {
|
||||
it.filter_map_ok(filter_map).collect::<KvResult<Vec<_>>>()
|
||||
}
|
||||
})?
|
||||
};
|
||||
|
||||
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,
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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<SourceAmount> 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 {
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
#[derive(async_graphql::SimpleObject)]
|
||||
#[derive(Clone, Debug, async_graphql::SimpleObject)]
|
||||
pub(crate) struct Identity {
|
||||
pub is_member: bool,
|
||||
pub username: String,
|
||||
|
|
41
gql/src/entities/wallet_gva.rs
Normal file
41
gql/src/entities/wallet_gva.rs
Normal file
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
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<Identity>,
|
||||
}
|
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -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)]
|
||||
|
|
172
gql/src/queries/wallets.rs
Normal file
172
gql/src/queries/wallets.rs
Normal file
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
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<i64>,
|
||||
#[graphql(desc = "pagination", default)] pagination: Pagination,
|
||||
#[graphql(desc = "Wallet type filter", default)] wallet_type_filter: WalletTypeFilter,
|
||||
) -> async_graphql::Result<Connection<String, Wallet, EmptyFields, EmptyFields>> {
|
||||
let QueryContext { is_whitelisted } = ctx.data::<QueryContext>()?;
|
||||
|
||||
let data = ctx.data::<GvaSchemaData>()?;
|
||||
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<Vec<Wallet>> = 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)
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue