• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

knowledgepixels / nanopub-registry / 22896903711

10 Mar 2026 09:56AM UTC coverage: 26.992% (-0.2%) from 27.147%
22896903711

push

github

tkuhn
fix: improve resilience of peer sync

- Handle malformed nanopub timestamps gracefully instead of rejecting
  the nanopub; treat as no timestamp
- Skip individual nanopubs that fail during full/recent fetch instead of
  aborting the entire stream
- Catch all exceptions (not just IOException) in loadAllNanopubs so
  updatePeerState is always called
- Checkpoint fullFetchPosition every 1000 nanopubs so progress survives
  process restarts

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

170 of 690 branches covered (24.64%)

Branch coverage included in aggregate %.

555 of 1996 relevant lines covered (27.81%)

4.9 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

62.55
src/main/java/com/knowledgepixels/registry/RegistryDB.java
1
package com.knowledgepixels.registry;
2

3
import com.knowledgepixels.registry.db.IndexInitializer;
4
import com.mongodb.MongoClient;
5
import com.mongodb.MongoNamespace;
6
import com.mongodb.ErrorCategory;
7
import com.mongodb.MongoWriteException;
8
import com.mongodb.ServerAddress;
9
import com.mongodb.client.ClientSession;
10
import com.mongodb.client.MongoCollection;
11
import com.mongodb.client.MongoCursor;
12
import com.mongodb.client.MongoDatabase;
13
import com.mongodb.client.model.CountOptions;
14
import com.mongodb.client.model.UpdateOptions;
15
import net.trustyuri.TrustyUriUtils;
16
import org.bson.Document;
17
import org.bson.conversions.Bson;
18
import org.bson.types.Binary;
19
import org.eclipse.rdf4j.common.exception.RDF4JException;
20
import org.eclipse.rdf4j.model.IRI;
21
import org.eclipse.rdf4j.rio.RDFFormat;
22
import org.nanopub.MalformedNanopubException;
23
import org.nanopub.Nanopub;
24

25
import java.util.Calendar;
26
import org.nanopub.NanopubUtils;
27
import org.nanopub.extra.security.MalformedCryptoElementException;
28
import org.nanopub.extra.security.NanopubSignatureElement;
29
import org.nanopub.extra.security.SignatureUtils;
30
import org.nanopub.jelly.JellyUtils;
31
import org.slf4j.Logger;
32
import org.slf4j.LoggerFactory;
33

34
import java.io.IOException;
35
import java.security.GeneralSecurityException;
36
import java.util.ArrayList;
37

38
import static com.mongodb.client.model.Indexes.ascending;
39

40
public class RegistryDB {
41

42
    private RegistryDB() {
43
    }
44

45
    private static final String REGISTRY_DB_NAME = Utils.getEnv("REGISTRY_DB_NAME", "nanopubRegistry");
12✔
46

47
    private static final Logger logger = LoggerFactory.getLogger(RegistryDB.class);
9✔
48

49
    private static MongoClient mongoClient;
50
    private static MongoDatabase mongoDB;
51

52
    /**
53
     * Returns the MongoDB database instance.
54
     *
55
     * @return the MongoDatabase instance
56
     */
57
    public static MongoDatabase getDB() {
58
        return mongoDB;
6✔
59
    }
60

61
    /**
62
     * Returns the MongoDB client instance.
63
     *
64
     * @return the MongoClient instance
65
     */
66
    public static MongoClient getClient() {
67
        return mongoClient;
6✔
68
    }
69

70
    /**
71
     * Returns the specified collection from the MongoDB database.
72
     *
73
     * @param name the name of the collection
74
     * @return the MongoCollection instance
75
     */
76
    public static MongoCollection<Document> collection(String name) {
77
        return mongoDB.getCollection(name);
12✔
78
    }
79

80
    /**
81
     * Initializes the MongoDB connection and sets up collections and indexes if not already initialized.
82
     */
83
    public static void init() {
84
        if (mongoClient != null) {
6✔
85
            logger.info("RegistryDB already initialized");
9✔
86
            return;
3✔
87
        }
88
        final String REGISTRY_DB_HOST = Utils.getEnv("REGISTRY_DB_HOST", "mongodb");
12✔
89
        final int REGISTRY_DB_PORT = Integer.parseInt(Utils.getEnv("REGISTRY_DB_PORT", String.valueOf(ServerAddress.defaultPort())));
18✔
90
        logger.info("Initializing RegistryDB connection to database '{}' at {}:{}", REGISTRY_DB_NAME, REGISTRY_DB_HOST, REGISTRY_DB_PORT);
54✔
91
        mongoClient = new MongoClient(REGISTRY_DB_HOST, REGISTRY_DB_PORT);
18✔
92
        mongoDB = mongoClient.getDatabase(REGISTRY_DB_NAME);
12✔
93

94
        try (ClientSession mongoSession = mongoClient.startSession()) {
9✔
95
            if (isInitialized(mongoSession)) {
9!
96
                return;
×
97
            }
98
            IndexInitializer.initCollections(mongoSession);
6✔
99
        }
×
100
    }
3✔
101

102
    /**
103
     * Checks if the database has been initialized.
104
     *
105
     * @param mongoSession the MongoDB client session
106
     * @return true if initialized, false otherwise
107
     */
108
    public static boolean isInitialized(ClientSession mongoSession) {
109
        return getValue(mongoSession, Collection.SERVER_INFO.toString(), "setupId") != null;
24!
110
    }
111

112
    /**
113
     * Renames a collection in the database. If the new collection name already exists, it will be dropped first.
114
     *
115
     * @param oldCollectionName the current name of the collection
116
     * @param newCollectionName the new name for the collection
117
     */
118
    public static void rename(String oldCollectionName, String newCollectionName) {
119
        // Designed as idempotent operation: calling multiple times has same effect as calling once
120
        if (hasCollection(oldCollectionName)) {
9✔
121
            if (hasCollection(newCollectionName)) {
9✔
122
                collection(newCollectionName).drop();
9✔
123
            }
124
            collection(oldCollectionName).renameCollection(new MongoNamespace(REGISTRY_DB_NAME, newCollectionName));
24✔
125
        }
126
    }
3✔
127

128
    /**
129
     * Checks if a collection with the given name exists in the database.
130
     *
131
     * @param collectionName the name of the collection to check
132
     * @return true if the collection exists, false otherwise
133
     */
134
    public static boolean hasCollection(String collectionName) {
135
        return mongoDB.listCollectionNames().into(new ArrayList<>()).contains(collectionName);
30✔
136
    }
137

138
    /**
139
     * Increases the trust state counter in the server info collection.
140
     *
141
     * @param mongoSession the MongoDB client session
142
     */
143
    public static void increaseStateCounter(ClientSession mongoSession) {
144
        MongoCursor<Document> cursor = collection(Collection.SERVER_INFO.toString()).find(mongoSession, new Document("_id", "trustStateCounter")).cursor();
36✔
145
        if (cursor.hasNext()) {
9✔
146
            long counter = cursor.next().getLong("value");
21✔
147
            collection(Collection.SERVER_INFO.toString()).updateOne(mongoSession, new Document("_id", "trustStateCounter"), new Document("$set", new Document("value", counter + 1)));
69✔
148
        } else {
3✔
149
            collection(Collection.SERVER_INFO.toString()).insertOne(mongoSession, new Document("_id", "trustStateCounter").append("value", 0L));
42✔
150
        }
151
    }
3✔
152

153
    /**
154
     * Checks if an element with the given name exists in the specified collection.
155
     *
156
     * @param mongoSession the MongoDB client session
157
     * @param collection   the name of the collection
158
     * @param elementName  the name of the element used as the _id field
159
     * @return true if the element exists, false otherwise
160
     */
161
    public static boolean has(ClientSession mongoSession, String collection, String elementName) {
162
        return has(mongoSession, collection, new Document("_id", elementName));
27✔
163
    }
164

165
    private static final CountOptions hasCountOptions = new CountOptions().limit(1);
21✔
166

167
    /**
168
     * Checks if any document matching the given filter exists in the specified collection.
169
     *
170
     * @param mongoSession the MongoDB client session
171
     * @param collection   the name of the collection
172
     * @param find         the filter to match documents
173
     * @return true if at least one matching document exists, false otherwise
174
     */
175
    public static boolean has(ClientSession mongoSession, String collection, Bson find) {
176
        return collection(collection).countDocuments(mongoSession, find, hasCountOptions) > 0;
39✔
177
    }
178

179
    /**
180
     * Retrieves a cursor for documents matching the given filter in the specified collection.
181
     *
182
     * @param mongoSession the MongoDB client session
183
     * @param collection   the name of the collection
184
     * @param find         the filter to match documents
185
     * @return a MongoCursor for the matching documents
186
     */
187
    public static MongoCursor<Document> get(ClientSession mongoSession, String collection, Bson find) {
188
        return collection(collection).find(mongoSession, find).cursor();
21✔
189
    }
190

191
    /**
192
     * Retrieves the value of an element with the given name from the specified collection.
193
     *
194
     * @param mongoSession the MongoDB client session
195
     * @param collection   the name of the collection
196
     * @param elementName  the name of the element used as the _id field
197
     * @return the value of the element, or null if not found
198
     */
199
    public static Object getValue(ClientSession mongoSession, String collection, String elementName) {
200
        logger.info("Getting value of element '{}' from collection '{}'", elementName, collection);
15✔
201
        Document d = collection(collection).find(mongoSession, new Document("_id", elementName)).first();
36✔
202
        if (d == null) {
6✔
203
            return null;
6✔
204
        }
205
        return d.get("value");
12✔
206
    }
207

208
    /**
209
     * Retrieves the boolean value of an element with the given name from the specified collection.
210
     *
211
     * @param mongoSession the MongoDB client session
212
     * @param collection   the name of the collection
213
     * @param elementName  the name of the element used as the _id field
214
     * @return the value of the element, or null if not found
215
     */
216
    public static boolean isSet(ClientSession mongoSession, String collection, String elementName) {
217
        Document d = collection(collection).find(mongoSession, new Document("_id", elementName)).first();
36✔
218
        if (d == null) {
6✔
219
            return false;
6✔
220
        }
221
        return d.getBoolean("value");
15✔
222
    }
223

224
    /**
225
     * Retrieves a single document matching the given filter from the specified collection.
226
     *
227
     * @param mongoSession the MongoDB client session
228
     * @param collection   the name of the collection
229
     * @param find         the filter to match the document
230
     * @return the matching document, or null if not found
231
     */
232
    public static Document getOne(ClientSession mongoSession, String collection, Bson find) {
233
        return collection(collection).find(mongoSession, find).first();
24✔
234
    }
235

236
    /**
237
     * Retrieves the maximum value of a specified field from the documents in the given collection.
238
     *
239
     * @param mongoSession the MongoDB client session
240
     * @param collection   the name of the collection
241
     * @param fieldName    the field for which to find the maximum value
242
     * @return the maximum value of the specified field, or null if no documents exist
243
     */
244
    public static Object getMaxValue(ClientSession mongoSession, String collection, String fieldName) {
245
        Document doc = collection(collection).find(mongoSession).projection(new Document(fieldName, 1)).sort(new Document(fieldName, -1)).first();
63✔
246
        if (doc == null) {
6✔
247
            return null;
6✔
248
        }
249
        return doc.get(fieldName);
12✔
250
    }
251

252
    /**
253
     * Retrieves the document with the maximum value of a specified field from the documents matching the given filter in the specified collection.
254
     *
255
     * @param mongoSession the MongoDB client session
256
     * @param collection   the name of the collection
257
     * @param find         the filter to match documents
258
     * @param fieldName    the field for which to find the maximum value
259
     * @return the document with the maximum value of the specified field, or null if no matching documents exist
260
     */
261
    public static Document getMaxValueDocument(ClientSession mongoSession, String collection, Bson find, String fieldName) {
262
        return collection(collection).find(mongoSession, find).sort(new Document(fieldName, -1)).first();
45✔
263
    }
264

265
    /**
266
     * Sets or updates a document in the specified collection.
267
     *
268
     * @param mongoSession the MongoDB client session
269
     * @param collection   the name of the collection
270
     * @param update       the document to set or update (must contain an _id field)
271
     */
272
    public static void set(ClientSession mongoSession, String collection, Document update) {
273
        Bson find = new Document("_id", update.get("_id"));
24✔
274
        MongoCursor<Document> cursor = collection(collection).find(mongoSession, find).cursor();
21✔
275
        if (cursor.hasNext()) {
9✔
276
            collection(collection).updateOne(mongoSession, find, new Document("$set", update));
33✔
277
        }
278
    }
3✔
279

280
    /**
281
     * Inserts a document into the specified collection.
282
     *
283
     * @param mongoSession the MongoDB client session
284
     * @param collection   the name of the collection
285
     * @param doc          the document to insert
286
     */
287
    public static void insert(ClientSession mongoSession, String collection, Document doc) {
288
        collection(collection).insertOne(mongoSession, doc);
15✔
289
    }
3✔
290

291
    /**
292
     * Sets the value of an element with the given name in the specified collection.
293
     * If the element does not exist, it will be created.
294
     *
295
     * @param mongoSession the MongoDB client session
296
     * @param collection   the name of the collection
297
     * @param elementId    the name of the element used as the _id field
298
     * @param value        the value to set
299
     */
300
    public static void setValue(ClientSession mongoSession, String collection, String elementId, Object value) {
301
        collection(collection).updateOne(mongoSession, new Document("_id", elementId), new Document("$set", new Document("value", value)), new UpdateOptions().upsert(true));
72✔
302
    }
3✔
303

304
    /**
305
     * Records the hash of a given value in the "hashes" collection.
306
     * If the hash already exists, it will be ignored.
307
     *
308
     * @param mongoSession the MongoDB client session
309
     * @param value        the value to hash and record
310
     */
311
    public static void recordHash(ClientSession mongoSession, String value) {
312
        try {
313
            insert(mongoSession, "hashes", new Document("value", value).append("hash", Utils.getHash(value)));
36✔
314
        } catch (MongoWriteException e) {
×
315
            // Duplicate key error -- ignore it
316
            if (e.getError().getCategory() != ErrorCategory.DUPLICATE_KEY) throw e;
×
317
        }
3✔
318
    }
3✔
319

320
    /**
321
     * Retrieves the original value corresponding to a given hash from the "hashes" collection.
322
     *
323
     * @param hash the hash to look up
324
     * @return the original value, or null if not found
325
     */
326
    public static String unhash(String hash) {
327
        try (var c = collection("hashes").find(new Document("hash", hash)).cursor()) {
30✔
328
            if (c.hasNext()) {
9✔
329
                return c.next().getString("value");
24✔
330
            }
331
            return null;
12✔
332
        }
12!
333
    }
334

335
    /**
336
     * Loads a nanopublication into the database.
337
     *
338
     * @param mongoSession the MongoDB client session
339
     * @param nanopub      the nanopublication to load
340
     */
341
    public static boolean loadNanopub(ClientSession mongoSession, Nanopub nanopub) {
342
        return loadNanopub(mongoSession, nanopub, null);
21✔
343
    }
344

345
    /**
346
     * Loads a nanopublication into the database, optionally filtering by public key hash and types.
347
     *
348
     * @param mongoSession the MongoDB client session
349
     * @param nanopub      the nanopublication to load
350
     * @param pubkeyHash   the public key hash to filter by (can be null)
351
     * @param types        the types to filter by (can be empty)
352
     * @return true if the nanopublication was loaded, false otherwise
353
     */
354
    public static boolean loadNanopub(ClientSession mongoSession, Nanopub nanopub, String pubkeyHash, String... types) {
355
        if (nanopub.getTripleCount() > 1200) {
12!
356
            logger.info("Nanopub has too many triples ({}): {}", nanopub.getTripleCount(), nanopub.getUri());
×
357
            return false;
×
358
        }
359
        if (nanopub.getByteCount() > 1000000) {
15!
360
            logger.info("Nanopub is too large ({}): {}", nanopub.getByteCount(), nanopub.getUri());
×
361
            return false;
×
362
        }
363
        Calendar creationTime;
364
        try {
365
            creationTime = nanopub.getCreationTime();
9✔
366
        } catch (Exception ex) {
×
367
            logger.info("Nanopub has malformed timestamp, treating as no timestamp: {}", nanopub.getUri());
×
368
            creationTime = null;
×
369
        }
3✔
370
        if (creationTime != null && creationTime.getTimeInMillis() > System.currentTimeMillis() + 60000) {
27✔
371
            logger.info("Nanopub has a future timestamp: {}", nanopub.getUri());
15✔
372
            return false;
6✔
373
        }
374
        String nanopubUriStr = nanopub.getUri().stringValue();
12✔
375
        for (IRI graphUri : nanopub.getGraphUris()) {
33✔
376
            if (!graphUri.stringValue().startsWith(nanopubUriStr)) {
15✔
377
                logger.info("Nanopub has graph URI not matching base URI: {}", nanopub.getUri());
15✔
378
                return false;
6✔
379
            }
380
        }
3✔
381
        String pubkey = getPubkey(nanopub);
9✔
382
        if (pubkey == null) {
6✔
383
            logger.info("Ignoring invalid nanopub: {}", nanopub.getUri());
15✔
384
            return false;
6✔
385
        }
386
        String ph = Utils.getHash(pubkey);
9✔
387
        if (pubkeyHash != null && !pubkeyHash.equals(ph)) {
6!
388
            logger.info("Ignoring nanopub with non-matching pubkey: {}", nanopub.getUri());
×
389
            return false;
×
390
        }
391
        recordHash(mongoSession, pubkey);
9✔
392

393
        String ac = TrustyUriUtils.getArtifactCode(nanopub.getUri().stringValue());
15✔
394
        if (ac == null) {
6!
395
            // I don't think this ever happens, but checking here to be sure
396
            logger.info("ERROR. Unexpected Trusty URI: {}", nanopub.getUri());
×
397
            return false;
×
398
        }
399
        if (has(mongoSession, Collection.NANOPUBS.toString(), ac)) {
18!
400
            logger.debug("Already loaded: {}", nanopub.getUri());
×
401
        } else {
402
            Long counter = (Long) getMaxValue(mongoSession, Collection.NANOPUBS.toString(), "counter");
21✔
403
            if (counter == null) counter = 0l;
15!
404
            String nanopubString;
405
            byte[] jellyContent;
406
            try {
407
                nanopubString = NanopubUtils.writeToString(nanopub, RDFFormat.TRIG);
12✔
408
                // Save the same thing in the Jelly format for faster loading
409
                jellyContent = JellyUtils.writeNanopubForDB(nanopub);
9✔
410
            } catch (IOException ex) {
×
411
                throw new RuntimeException(ex);
×
412
            }
3✔
413
            collection(Collection.NANOPUBS.toString()).insertOne(mongoSession, new Document("_id", ac).append("fullId", nanopub.getUri().stringValue()).append("counter", counter + 1).append("pubkey", ph).append("content", nanopubString).append("jelly", new Binary(jellyContent)));
102✔
414

415
            for (IRI invalidatedId : Utils.getInvalidatedNanopubIds(nanopub)) {
33✔
416
                String invalidatedAc = TrustyUriUtils.getArtifactCode(invalidatedId.stringValue());
12✔
417
                if (invalidatedAc == null) continue;  // This should never happen; checking here just to be sure
6!
418

419
                // Add this nanopub also to all lists of invalidated nanopubs:
420
                collection("invalidations").insertOne(mongoSession, new Document("invalidatingNp", ac).append("invalidatingPubkey", ph).append("invalidatedNp", invalidatedAc));
45✔
421
                MongoCursor<Document> invalidatedEntries = collection("listEntries").find(mongoSession, new Document("np", invalidatedAc).append("pubkey", ph)).cursor();
42✔
422
                while (invalidatedEntries.hasNext()) {
9!
423
                    Document invalidatedEntry = invalidatedEntries.next();
×
424
                    addToList(mongoSession, nanopub, ph, invalidatedEntry.getString("type"));
×
425
                }
×
426

427
                collection("listEntries").updateMany(mongoSession, new Document("np", invalidatedAc).append("pubkey", ph), new Document("$set", new Document("invalidated", true)));
69✔
428
                collection("trustEdges").updateMany(mongoSession, new Document("source", invalidatedAc), new Document("$set", new Document("invalidated", true)));
60✔
429
            }
3✔
430
        }
431

432
        if (pubkeyHash != null) {
6!
433
            for (String type : types) {
×
434
                // TODO Check if nanopub really has the type?
435
                addToList(mongoSession, nanopub, pubkeyHash, Utils.getTypeHash(mongoSession, type));
×
436
                if (type.equals("$")) {
×
437
                    for (IRI t : NanopubUtils.getTypes(nanopub)) {
×
438
                        addToList(mongoSession, nanopub, pubkeyHash, Utils.getTypeHash(mongoSession, t));
×
439
                    }
×
440
                }
441
            }
442
        }
443

444
        // Add the invalidating nanopubs also to the lists of this nanopub:
445
        try (MongoCursor<Document> invalidations = collection("invalidations").find(mongoSession, new Document("invalidatedNp", ac).append("invalidatingPubkey", ph)).cursor()) {
42✔
446
            if (invalidations.hasNext()) {
9!
447
                collection("listEntries").updateMany(mongoSession, new Document("np", ac).append("pubkey", ph), new Document("$set", new Document("invalidated", true)));
×
448
                collection("trustEdges").updateMany(mongoSession, new Document("source", ac), new Document("$set", new Document("invalidated", true)));
×
449
            }
450
            while (invalidations.hasNext()) {
9!
451
                String iac = invalidations.next().getString("invalidatingNp");
×
452
                try {
453
                    Document npDoc = collection(Collection.NANOPUBS.toString()).find(mongoSession, new Document("_id", iac)).projection(new Document("jelly", 1)).first();
×
454
                    Nanopub inp = JellyUtils.readFromDB(npDoc.get("jelly", Binary.class).getData());
×
455
                    for (IRI type : NanopubUtils.getTypes(inp)) {
×
456
                        addToList(mongoSession, inp, ph, Utils.getTypeHash(mongoSession, type));
×
457
                    }
×
458
                } catch (RDF4JException | MalformedNanopubException ex) {
×
459
                    ex.printStackTrace();
×
460
                }
×
461
            }
×
462

463
        }
464

465
        return true;
6✔
466
    }
467

468
    private static void addToList(ClientSession mongoSession, Nanopub nanopub, String pubkeyHash, String typeHash) {
469
        String ac = TrustyUriUtils.getArtifactCode(nanopub.getUri().stringValue());
×
470
        try {
471
            insert(mongoSession, "lists", new Document("pubkey", pubkeyHash).append("type", typeHash));
×
472
        } catch (MongoWriteException e) {
×
473
            // Duplicate key error -- ignore it
474
            if (e.getError().getCategory() != ErrorCategory.DUPLICATE_KEY) throw e;
×
475
        }
×
476

477
        if (has(mongoSession, "listEntries", new Document("pubkey", pubkeyHash).append("type", typeHash).append("np", ac))) {
×
478
            logger.debug("Already listed: {}", nanopub.getUri());
×
479
        } else {
480

481
            Document doc = getMaxValueDocument(mongoSession, "listEntries", new Document("pubkey", pubkeyHash).append("type", typeHash), "position");
×
482
            long position;
483
            String checksum;
484
            if (doc == null) {
×
485
                position = 0l;
×
486
                checksum = NanopubUtils.updateXorChecksum(nanopub.getUri(), NanopubUtils.INIT_CHECKSUM);
×
487
            } else {
488
                position = doc.getLong("position") + 1;
×
489
                checksum = NanopubUtils.updateXorChecksum(nanopub.getUri(), doc.getString("checksum"));
×
490
            }
491
            collection("listEntries").insertOne(mongoSession, new Document("pubkey", pubkeyHash).append("type", typeHash).append("position", position).append("np", ac).append("checksum", checksum).append("invalidated", false));
×
492
        }
493
    }
×
494

495
    /**
496
     * Returns the public key string of the Nanopub's signature, or null if not available or invalid.
497
     *
498
     * @param nanopub the nanopub to extract the public key from
499
     * @return The public key string, or null if not available or invalid.
500
     */
501
    public static String getPubkey(Nanopub nanopub) {
502
        // TODO shouldn't this be moved to a utility class in nanopub-java? there is a similar method in NanopubElement class of nanodash
503
        NanopubSignatureElement el;
504
        try {
505
            el = SignatureUtils.getSignatureElement(nanopub);
9✔
506
            if (el != null && SignatureUtils.hasValidSignature(el) && el.getPublicKeyString() != null) {
24!
507
                return el.getPublicKeyString();
9✔
508
            }
509
        } catch (MalformedCryptoElementException | GeneralSecurityException ex) {
×
510
            logger.error("Error in checking the signature of the nanopub {}", nanopub.getUri());
×
511
        }
3✔
512
        return null;
6✔
513
    }
514

515
    /**
516
     * Calculates a hash representing the current state of the trust paths in the loading collection.
517
     *
518
     * @param mongoSession the MongoDB client session
519
     * @return the calculated trust state hash
520
     */
521
    public static String calculateTrustStateHash(ClientSession mongoSession) {
522
        MongoCursor<Document> tp = collection("trustPaths_loading").find(mongoSession).sort(ascending("_id")).cursor();
×
523
        // TODO Improve this so we don't create the full string just for calculating its hash:
524
        String s = "";
×
525
        while (tp.hasNext()) {
×
526
            Document d = tp.next();
×
527
            s += d.getString("_id") + " (" + d.getString("type") + ")\n";
×
528
        }
×
529
        return Utils.getHash(s);
×
530
    }
531

532
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc