diff --git a/Cargo.lock b/Cargo.lock index ea8bc7d6cabe4573a0c6d2aa59ca06f9c18e32d8..7dd78769f506fa415b323587a1c8aa8462e20533 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -80,6 +80,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "ansi_term" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +dependencies = [ + "winapi", +] + [[package]] name = "anyhow" version = "1.0.34" @@ -688,7 +697,7 @@ version = "2.33.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002" dependencies = [ - "ansi_term", + "ansi_term 0.11.0", "atty", "bitflags 1.2.1", "strsim 0.8.0", @@ -934,6 +943,16 @@ dependencies = [ "memchr", ] +[[package]] +name = "ctor" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fbaabec2c953050352311293be5c6aba8e141ba19d6811862b232d6fd020484" +dependencies = [ + "quote", + "syn", +] + [[package]] name = "ctrlc" version = "3.1.7" @@ -1001,6 +1020,12 @@ dependencies = [ "syn", ] +[[package]] +name = "diff" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e25ea47919b1560c4e3b7fe0aaab9becf5b84a10325ddf7db0f0ba5e1026499" + [[package]] name = "difference" version = "2.0.0" @@ -1379,6 +1404,7 @@ dependencies = [ "futures", "log", "mockall", + "pretty_assertions", "resiter", "serde", "serde_json", @@ -2653,6 +2679,15 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +[[package]] +name = "output_vt100" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53cdc5b785b7a58c5aad8216b3dfa114df64b0b06ae6e1501cef91df2fbdf8f9" +dependencies = [ + "winapi", +] + [[package]] name = "parking" version = "2.0.0" @@ -2867,6 +2902,18 @@ dependencies = [ "treeline", ] +[[package]] +name = "pretty_assertions" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f297542c27a7df8d45de2b0e620308ab883ad232d06c14b76ac3e144bda50184" +dependencies = [ + "ansi_term 0.12.1", + "ctor", + "diff", + "output_vt100", +] + [[package]] name = "proc-macro-crate" version = "0.1.5" diff --git a/rust-libs/modules/gva/dbs-reader/src/lib.rs b/rust-libs/modules/gva/dbs-reader/src/lib.rs index dcf72cc05d546237fbdd4aeee4357069c19ca429..8e15fea48877b8f56d234506c7cadc15e251c332 100644 --- a/rust-libs/modules/gva/dbs-reader/src/lib.rs +++ b/rust-libs/modules/gva/dbs-reader/src/lib.rs @@ -27,6 +27,7 @@ pub mod current_frame; pub mod endpoints; pub mod find_inputs; pub mod idty; +pub mod network; pub mod pagination; pub mod txs_history; pub mod uds_of_pubkey; @@ -153,6 +154,10 @@ pub trait DbsReader { bc_db: &BcV2DbRo<FileBackend>, pubkey: PublicKey, ) -> KvResult<Option<duniter_dbs::IdtyDbV2>>; + fn peers_and_heads<DB: 'static + DunpV1DbReadable>( + &self, + dunp_db: &DB, + ) -> KvResult<Vec<(duniter_dbs::PeerCardDbV1, Vec<duniter_dbs::DunpHeadDbV1>)>>; fn unspent_uds_of_pubkey( &self, bc_db: &BcV2DbRo<FileBackend>, @@ -301,6 +306,13 @@ impl DbsReader for DbsReaderImpl { self.idty_(bc_db, pubkey) } + fn peers_and_heads<DB: 'static + DunpV1DbReadable>( + &self, + dunp_db: &DB, + ) -> KvResult<Vec<(duniter_dbs::PeerCardDbV1, Vec<duniter_dbs::DunpHeadDbV1>)>> { + self.peers_and_heads_(dunp_db) + } + fn unspent_uds_of_pubkey( &self, bc_db: &BcV2DbRo<FileBackend>, diff --git a/rust-libs/modules/gva/dbs-reader/src/network.rs b/rust-libs/modules/gva/dbs-reader/src/network.rs new file mode 100644 index 0000000000000000000000000000000000000000..05f3b7c333c79e617e304b490f48bc311f3d5e85 --- /dev/null +++ b/rust-libs/modules/gva/dbs-reader/src/network.rs @@ -0,0 +1,103 @@ +// 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 dubp::crypto::keys::PublicKey as _; +use duniter_dbs::databases::dunp_v1::DunpV1DbReadable; +use duniter_dbs::{DunpHeadDbV1, PeerCardDbV1}; + +#[allow(clippy::unnecessary_wraps)] +impl DbsReaderImpl { + pub(super) fn peers_and_heads_<DB: DunpV1DbReadable>( + &self, + dunp_db: &DB, + ) -> KvResult<Vec<(PeerCardDbV1, Vec<DunpHeadDbV1>)>> { + Ok(dunp_db.peers_old().iter(.., |it| { + it.values() + .filter_map(|peer_res| { + if let Ok(peer) = peer_res { + if let Ok(pubkey) = PublicKey::from_base58(&peer.pubkey) { + let k_min = duniter_dbs::DunpNodeIdV1Db::new(0, pubkey); + let k_max = duniter_dbs::DunpNodeIdV1Db::new(u32::MAX, pubkey); + Some(( + peer, + dunp_db.heads_old().iter(k_min..k_max, |it| { + it.values().filter_map(|head| head.ok()).collect() + }), + )) + } else { + None + } + } else { + None + } + }) + .collect() + })) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use duniter_dbs::databases::dunp_v1::DunpV1DbWritable; + + #[test] + fn test_peers_and_heads() -> KvResult<()> { + let dunp_db = duniter_dbs::databases::dunp_v1::DunpV1Db::<Mem>::open(MemConf::default())?; + let db_reader = DbsReaderImpl::mem(); + let pk = PublicKey::default(); + + dunp_db.peers_old_write().upsert( + PubKeyKeyV2(pk), + PeerCardDbV1 { + pubkey: pk.to_string(), + ..Default::default() + }, + )?; + dunp_db.heads_old_write().upsert( + duniter_dbs::DunpNodeIdV1Db::new(42, pk), + DunpHeadDbV1::default(), + )?; + dunp_db.heads_old_write().upsert( + duniter_dbs::DunpNodeIdV1Db::new(43, pk), + DunpHeadDbV1 { + pubkey: PublicKey::from_base58("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") + .expect("invalid pubkey"), + ..Default::default() + }, + )?; + + assert_eq!( + db_reader.peers_and_heads(&dunp_db)?, + vec![( + PeerCardDbV1 { + pubkey: pk.to_string(), + ..Default::default() + }, + vec![ + DunpHeadDbV1::default(), + DunpHeadDbV1 { + pubkey: PublicKey::from_base58("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") + .expect("invalid pubkey"), + ..Default::default() + } + ] + )] + ); + + Ok(()) + } +} diff --git a/rust-libs/modules/gva/gql/Cargo.toml b/rust-libs/modules/gva/gql/Cargo.toml index d8fbe3d66ec017876172a433197ec696e28e674b..08d49dd161288ab1fa39e067694713b78ce23b58 100644 --- a/rust-libs/modules/gva/gql/Cargo.toml +++ b/rust-libs/modules/gva/gql/Cargo.toml @@ -31,6 +31,7 @@ duniter-dbs = { path = "../../../duniter-dbs", features = ["mem"] } duniter-gva-dbs-reader = { path = "../dbs-reader", features = ["mock"] } duniter-global = { path = "../../../duniter-global", features = ["mock"] } mockall = "0.9.1" +pretty_assertions = "0.7" serde_json = "1.0.53" tokio = { version = "1.2", features = ["macros", "rt-multi-thread", "time"] } unwrap = "1.2.1" diff --git a/rust-libs/modules/gva/gql/src/entities.rs b/rust-libs/modules/gva/gql/src/entities.rs index be3abb4a9b76e055dcf795731d86d5c81edd0fca..c6ac1569f842a1cf73575aaf264dc65ca57e7a51 100644 --- a/rust-libs/modules/gva/gql/src/entities.rs +++ b/rust-libs/modules/gva/gql/src/entities.rs @@ -15,6 +15,7 @@ pub mod block_gva; pub mod idty_gva; +pub mod network; pub mod tx_gva; pub mod ud_gva; pub mod utxos_gva; @@ -37,31 +38,6 @@ pub(crate) struct EdgeTx { pub(crate) direction: TxDirection, } -#[derive(Default, async_graphql::SimpleObject)] -#[graphql(name = "Peer")] -pub struct PeerCardGva { - pub version: u32, - pub currency: String, - pub pubkey: String, - pub blockstamp: String, - pub endpoints: Vec<String>, - pub status: String, - pub signature: String, -} -impl From<duniter_dbs::PeerCardDbV1> for PeerCardGva { - fn from(peer: duniter_dbs::PeerCardDbV1) -> Self { - Self { - version: peer.version, - currency: peer.currency, - pubkey: peer.pubkey, - blockstamp: peer.blockstamp, - endpoints: peer.endpoints, - status: peer.status, - signature: peer.signature, - } - } -} - pub(crate) enum RawTxOrChanges { FinalTx(String), Changes(Vec<String>), diff --git a/rust-libs/modules/gva/gql/src/entities/network.rs b/rust-libs/modules/gva/gql/src/entities/network.rs new file mode 100644 index 0000000000000000000000000000000000000000..7f99918c045e4cc345cf2ae9e58326574a66c3f0 --- /dev/null +++ b/rust-libs/modules/gva/gql/src/entities/network.rs @@ -0,0 +1,74 @@ +// 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/>. + +#[derive(Default, async_graphql::SimpleObject)] +#[graphql(name = "Peer")] +pub struct PeerCardGva { + pub version: u32, + pub currency: String, + pub pubkey: String, + pub blockstamp: String, + pub endpoints: Vec<String>, + pub status: String, + pub signature: String, +} +impl From<duniter_dbs::PeerCardDbV1> for PeerCardGva { + fn from(peer: duniter_dbs::PeerCardDbV1) -> Self { + Self { + version: peer.version, + currency: peer.currency, + pubkey: peer.pubkey, + blockstamp: peer.blockstamp, + endpoints: peer.endpoints, + status: peer.status, + signature: peer.signature, + } + } +} + +#[derive(Default, async_graphql::SimpleObject)] +#[graphql(name = "Head")] +pub struct HeadGva { + pub api: String, + pub pubkey: String, + pub blockstamp: String, + pub software: String, + pub software_version: String, + pub pow_prefix: u32, + pub free_member_room: u32, + pub free_mirror_room: u32, + pub signature: String, +} +impl From<duniter_dbs::DunpHeadDbV1> for HeadGva { + fn from(head: duniter_dbs::DunpHeadDbV1) -> Self { + Self { + api: head.api, + pubkey: head.pubkey.to_string(), + blockstamp: head.blockstamp.to_string(), + software: head.software, + software_version: head.software_version, + pow_prefix: head.pow_prefix, + free_member_room: head.free_member_room, + free_mirror_room: head.free_member_room, + signature: head.signature.to_string(), + } + } +} + +#[derive(async_graphql::SimpleObject)] +pub(crate) struct PeerWithHeads { + pub peer: PeerCardGva, + pub heads: Vec<HeadGva>, +} diff --git a/rust-libs/modules/gva/gql/src/lib.rs b/rust-libs/modules/gva/gql/src/lib.rs index 9858e81c777a032cdb3968a6fa8baa2481df3ea4..019f8ce9a239443ad0d83e248f7edcf02c4fdf39 100644 --- a/rust-libs/modules/gva/gql/src/lib.rs +++ b/rust-libs/modules/gva/gql/src/lib.rs @@ -37,11 +37,12 @@ pub use schema::{build_schema_with_data, get_schema_definition, GvaSchema, GvaSc use crate::entities::{ block_gva::{Block, BlockMeta}, idty_gva::Identity, + network::{HeadGva, PeerCardGva, PeerWithHeads}, tx_gva::TxGva, ud_gva::{CurrentUdGva, RevalUdGva, UdGva}, utxos_gva::UtxosGva, - AggregateSum, AmountWithBase, EdgeTx, PeerCardGva, RawTxOrChanges, Sum, TxDirection, - TxsHistoryMempool, UtxoGva, UtxoTimedGva, + AggregateSum, AmountWithBase, EdgeTx, RawTxOrChanges, Sum, TxDirection, TxsHistoryMempool, + UtxoGva, UtxoTimedGva, }; use crate::inputs::{TxIssuer, TxRecipient, UdsFilter}; use crate::inputs_validators::TxCommentValidator; diff --git a/rust-libs/modules/gva/gql/src/queries.rs b/rust-libs/modules/gva/gql/src/queries.rs index 710782baacf13d6a493c9073823941da90826690..a23e8ad7c3b11770cc5f99a47b47cad9c0849b6e 100644 --- a/rust-libs/modules/gva/gql/src/queries.rs +++ b/rust-libs/modules/gva/gql/src/queries.rs @@ -21,6 +21,7 @@ pub mod endpoints; pub mod first_utxos_of_scripts; pub mod gen_tx; pub mod idty; +pub mod network; pub mod txs_history; pub mod uds; pub mod utxos_of_script; @@ -38,6 +39,7 @@ pub struct QueryRoot( queries::first_utxos_of_scripts::FirstUtxosQuery, queries::gen_tx::GenTxsQuery, queries::idty::IdtyQuery, + queries::network::NetworkQuery, queries::txs_history::TxsHistoryBlockchainQuery, queries::txs_history::TxsHistoryMempoolQuery, queries::uds::UdsQuery, diff --git a/rust-libs/modules/gva/gql/src/queries/network.rs b/rust-libs/modules/gva/gql/src/queries/network.rs new file mode 100644 index 0000000000000000000000000000000000000000..39d104aab48244944b440c24e1384b8a414b3d41 --- /dev/null +++ b/rust-libs/modules/gva/gql/src/queries/network.rs @@ -0,0 +1,97 @@ +// 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(Default, async_graphql::SimpleObject)] +pub(crate) struct NetworkQuery { + network: NetworkQueryInner, +} + +#[derive(Default)] +pub(crate) struct NetworkQueryInner; + +#[async_graphql::Object] +impl NetworkQueryInner { + /// Get peers and heads + async fn nodes( + &self, + ctx: &async_graphql::Context<'_>, + ) -> async_graphql::Result<Vec<PeerWithHeads>> { + let data = ctx.data::<GvaSchemaData>()?; + + let db_reader = data.dbs_reader(); + + Ok(data + .dbs_pool + .execute(move |dbs| db_reader.peers_and_heads(&dbs.dunp_db)) + .await?? + .into_iter() + .map(|(peer, heads)| PeerWithHeads { + peer: PeerCardGva::from(peer), + heads: heads.into_iter().map(HeadGva::from).collect(), + }) + .collect()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::tests::*; + use duniter_dbs::databases::dunp_v1::DunpV1Db; + use pretty_assertions::assert_eq; + + #[tokio::test] + async fn test_peers_and_heads() -> anyhow::Result<()> { + let mut dbs_reader = MockDbsReader::new(); + dbs_reader + .expect_peers_and_heads::<DunpV1Db<FileBackend>>() + .times(1) + .returning(|_| { + Ok(vec![( + duniter_dbs::PeerCardDbV1::default(), + vec![duniter_dbs::DunpHeadDbV1::default()], + )]) + }); + let schema = create_schema(MockAsyncAccessor::new(), dbs_reader)?; + assert_eq!( + exec_graphql_request( + &schema, + r#"{ network { nodes { peer { blockstamp }, heads { blockstamp } } } }"# + ) + .await?, + serde_json::json!({ + "data": { + "network": { + "nodes": [ + { + "heads": [ + { + "blockstamp": "0-0000000000000000000000000000000000000000000000000000000000000000" + } + ], + "peer": { + "blockstamp": "" + } + } + ], + } + } + }) + ); + Ok(()) + } +}