p2p-db / test / ird / p2p / db / replication_test.cljs
769 words
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
(ns ird.p2p.db.replication-test
  "Two real Helia/OrbitDB nodes over loopback tcp, always on: the empirical
  proof that the pinned helia + @orbitdb/core + @libp2p/gossipsub set
  replicates sealed records between peers through native OrbitDB sync —
  history caught up on join (the heads exchange), live records over
  gossipsub, both directions — and that only ciphertext is ever stored or
  sent. If your environment cannot bind loopback tcp, set
  IRD_P2P_DB_NO_NODE=1 to skip."
  (:require [clojure.string :as str]
            [cljs.test :refer [deftest is testing]]
            [cljs.core.async :as async :refer [<!]]
            [ird.p2p.db.core :as db]
            [ird.p2p.db.errors :as err]
            [ird.p2p.db.test-util :as tu :refer [deftest-async]]
            [ird.p2p.impl.bytes :as bytes]))

(defn- latin1 [b64] (.toString (bytes/b64->buffer b64) "latin1"))

(deftest-async two-nodes-replicate-sealed-records
  {:timeout-ms 60000}
  (if (tu/skip-nodes?)
    (is true "IRD_P2P_DB_NO_NODE set — skipping the real two-node test")
    (let [secret (db/random-secret)
          a (<! (db/open {:secret secret :app-salt "test/v1" :name "chat"}))]
      (is (not (tu/error? a)) (some-> a ex-message))
      (testing "the node is a private swarm"
        (is (= ["libp2p-router"] (:routers (:node a))) "no delegated HTTP routing")
        (is (= #{"identify" "pubsub"} (set (:services (:node a)))) "no dht, no relay server, no autonat, no upnp")
        (is (some #{"bitswap"} (:block-brokers (:node a)))))
      ;; A appends before B exists: B must catch up through the heads exchange
      (let [r1 (<! (db/append a {:text "unu" :n 1}))
            r2 (<! (db/append a {:text "doi" :n 2}))
            _ (is (and (:hash r1) (:hash r2)))
            _ (is (= {:text "unu" :n 1} (:payload r1)))
            _ (is (= 1 (get-in r1 [:clock :time])))
            _ (is (= 2 (get-in r2 [:clock :time])))
            b (<! (db/open {:secret secret :app-salt "test/v1" :name "chat" :dial (<! (db/multiaddrs a))}))
            _ (is (not (tu/error? b)) (some-> b ex-message))
            ca (tu/collector (db/entries-stream a))
            cb (tu/collector (db/entries-stream b))
            pa (tu/collector (db/peers-stream a))
            pb (tu/collector (db/peers-stream b))
            wire (tu/collector ((:sniff! (:node b))))]
        (is (= (db/address a) (db/address b)) "both peers derive the same address from name + identity")
        (is (str/starts-with? (db/address a) "/orbitdb/"))
        (is (= 43 (count (:manifest-name a))))
        (is (= (:manifest-name a) (:name (:db a))) "OrbitDB's manifest carries the opaque derived name")
        (is (not= "chat" (:name (:db a))) "…never the human one")
        (is (= (:identity a) (:identity b)))
        (is (not= (db/peer-id a) (db/peer-id b)))
        (is (some? (<! (tu/wait-for pb #(= :peer-joined (:type %)) 20000))) "B exchanged heads with A")
        (is (some? (<! (tu/wait-for pa #(and (= :peer-joined (:type %)) (= (db/peer-id b) (:peer %))) 20000))))
        (is (some? (<! (tu/wait-for cb #(= 2 (get-in % [:payload :n])) 20000))) "B caught up with A's history")
        (is (= [{:text "unu" :n 1} {:text "doi" :n 2}] (mapv :payload @cb)) "in canonical order")
        (is (= [(:hash r1) (:hash r2)] (mapv :hash @cb)))
        (is (= [1 2] (mapv #(get-in % [:clock :time]) @cb)))
        (is (every? #(= (:by r1) (:by %)) @cb) "one shared writer identity")
        (is (= [1 2] (mapv #(get-in % [:payload :n]) @ca)) "A's own appends are on its stream too")
        (is (some #{(db/peer-id b)} (db/peers a)))
        ;; live: A appends after the join settled — B receives it over gossipsub
        (let [marker "plaintext-marker-3"
              r3 (<! (db/append a {:text "trei" :n 3 :marker marker}))]
          (is (some? (<! (tu/wait-for cb #(= 3 (get-in % [:payload :n])) 20000))) "B received the live record")
          (is (= (:hash r3) (:hash (last @cb))))
          (is (= {:text "trei" :n 3 :marker marker} (:payload (last @cb))))
          ;; and the other direction
          (let [r4 (<! (db/append b {:text "patru" :n 4}))]
            (is (not (tu/error? r4)) (some-> r4 ex-message))
            (is (some? (<! (tu/wait-for ca #(= 4 (get-in % [:payload :n])) 20000))) "A received B's record"))
          (testing "snapshots agree, in the same canonical order"
            (let [ea (<! (db/entries a)) eb (<! (db/entries b))]
              (is (= 4 (count ea) (count eb)))
              (is (= (mapv :hash ea) (mapv :hash eb)))
              (is (= [1 2 3 4] (mapv #(get-in % [:payload :n]) eb)))
              (is (= ea eb))))
          (testing "ciphertext only: the bytes OrbitDB stores, hashes, signs and sends"
            (let [sealed (:payload (<! ((:get (:db a)) (:hash r3))))
                  stored (<! ((:entry-bytes (:db b)) (:hash r3)))]
              (is (bytes/valid-b64? sealed) "the stored value is the sealed record")
              (is (= 2 (bytes/byte-at sealed 0)))
              (is (some? stored) "B holds the entry bytes")
              (let [s (latin1 stored)]
                (is (str/includes? s sealed) "the entry bytes carry the sealed record")
                (is (not (str/includes? s marker)) "…and no plaintext")
                (is (not (str/includes? s "trei")))
                (is (not (str/includes? s "\"text\""))))
              (is (some? (<! (tu/wait-for wire #(str/includes? (latin1 (:data %)) sealed) 10000)))
                  "the live record crossed gossipsub as the sealed bytes")
              (is (every? #(= (db/address a) (:topic %)) @wire) "the pubsub topic is the database address")
              (is (not-any? #(let [s (latin1 (:data %))] (or (str/includes? s marker) (str/includes? s "\"text\""))) @wire)
                  "no plaintext ever crossed the wire"))))
        (<! (db/close a))
        (<! (db/close b))
        (is (= :closed @(:state a)))
        (is (= :closed @(:state b)))))))

static mirror of HEAD · about · clone: git clone https://ird.cod-sursa.ro/p2p-db.git