From 6ee381ff59deee8d7b1a698534ddb970e9fa941a Mon Sep 17 00:00:00 2001
From: librelois <c@elo.tf>
Date: Sat, 3 Apr 2021 13:07:04 +0200
Subject: [PATCH] [feat] gva: heads and peers

---
 Cargo.lock                                    |  49 ++++++++-
 rust-libs/modules/gva/dbs-reader/src/lib.rs   |  12 ++
 .../modules/gva/dbs-reader/src/network.rs     | 103 ++++++++++++++++++
 rust-libs/modules/gva/gql/Cargo.toml          |   1 +
 rust-libs/modules/gva/gql/src/entities.rs     |  26 +----
 .../modules/gva/gql/src/entities/network.rs   |  74 +++++++++++++
 rust-libs/modules/gva/gql/src/lib.rs          |   5 +-
 rust-libs/modules/gva/gql/src/queries.rs      |   2 +
 .../modules/gva/gql/src/queries/network.rs    |  97 +++++++++++++++++
 9 files changed, 341 insertions(+), 28 deletions(-)
 create mode 100644 rust-libs/modules/gva/dbs-reader/src/network.rs
 create mode 100644 rust-libs/modules/gva/gql/src/entities/network.rs
 create mode 100644 rust-libs/modules/gva/gql/src/queries/network.rs

diff --git a/Cargo.lock b/Cargo.lock
index ea8bc7d6c..7dd78769f 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 dcf72cc05..8e15fea48 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 000000000..05f3b7c33
--- /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 d8fbe3d66..08d49dd16 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 be3abb4a9..c6ac1569f 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 000000000..7f99918c0
--- /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 9858e81c7..019f8ce9a 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 710782baa..a23e8ad7c 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 000000000..39d104aab
--- /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(())
+    }
+}
-- 
GitLab