import type { DB, DBTransaction, LevelPath } from '@matrixai/db'; import type { ContextTimed, ContextTimedInput } from '@matrixai/contexts'; import type { NodeId, NodeAddress, NodeBucket, NodeContact, NodeContactAddress, NodeContactAddressData, NodeBucketMeta, NodeBucketIndex, NodeGraphSpace } from './types.js'; import type KeyRing from '../keys/KeyRing.js'; import Logger from '@matrixai/logger'; import { createDestroyStartStop } from '@matrixai/async-init'; /** * NodeGraph is an implementation of Kademlia for maintaining peer to peer * information about Polykey nodes. * * It is a database of fixed-size buckets, where each bucket * contains NodeId -> NodeData. The bucket index is a prefix key. * This means the data is ordered in terms of bucket index, and then node ID. * From lowest to highest. * * The NodeGraph is partitioned into 2 spaces. The reason to do this is allow * transactional resetting of the buckets if the own node ID changes. * * When the node ID changes, either due to key renewal or reset, we remap all * existing records to the other space, and then we swap the active space key. */ interface NodeGraph extends createDestroyStartStop.CreateDestroyStartStop { } declare class NodeGraph { static createNodeGraph({ db, keyRing, nodeIdBits, nodeBucketLimit, nodeContactAddressLimit, logger, fresh, }: { db: DB; keyRing: KeyRing; nodeIdBits?: number; nodeBucketLimit?: number; nodeContactAddressLimit?: number; logger?: Logger; fresh?: boolean; }): Promise; /** * Bit size of the node IDs. * This is also the total number of buckets. */ readonly nodeIdBits: number; /** * Max number of nodes in each bucket. */ readonly nodeBucketLimit: number; /** * Max number of nodeContactAddresses for each node. */ readonly nodeContactAddressLimit: number; protected logger: Logger; protected db: DB; protected keyRing: KeyRing; protected space: NodeGraphSpace; protected nodeGraphDbPath: LevelPath; /** * Meta stores the `keyof NodeBucketMeta` -> `NodeBucketMeta[keyof NodeBucketMeta]`. */ protected nodeGraphMetaDbPath: LevelPath; /** * Buckets stores `lexi(NodeBucketIndex)/NodeId/nodeContactAddress` -> `NodeContactAddressData`. * * nodeContactAddress are canoncialized to be consistent. */ protected nodeGraphBucketsDbPath: LevelPath; /** * Last updated stores * `lexi(NodeBucketIndex)/"time"/lexi(connectedTime)/nodeId` -> `nodeId`. * `lexi(NodeBucketIndex)/"nodeId"/nodeId` -> `lexi(connectedTime)`. */ protected nodeGraphConnectedDbPath: LevelPath; constructor({ db, keyRing, nodeIdBits, nodeBucketLimit, nodeContactAddressLimit, logger, }: { db: DB; keyRing: KeyRing; nodeIdBits: number; nodeBucketLimit: number; nodeContactAddressLimit: number; logger: Logger; }); start({ fresh, }?: { fresh?: boolean; }): Promise; stop(): Promise; destroy(): Promise; /** * Sets up the space key * The space string is suffixed to the `buckets` and `meta` sublevels * This is used to allow swapping of sublevels when remapping buckets * during `this.resetBuckets` */ protected setupSpace(tran: DBTransaction): Promise; /** * Derive the bucket index of the k-buckets from the new `NodeId` * The bucket key is the string encoded version of bucket index * that preserves lexicographic order */ bucketIndex(nodeId: NodeId): [NodeBucketIndex, string]; /** * Locks the bucket index for exclusive operations. * This allows you to sequence operations for any bucket. */ lockBucket(bucketIndex: number, tran: DBTransaction, ctx?: ContextTimed): Promise; /** * Get a single `NodeContact` */ getNodeContact(nodeId: NodeId, tran?: DBTransaction, ctx?: Partial): Promise; /** * Get all `NodeContact`. * * Results are sorted by `NodeBucketIndex` then `NodeId` then * `NodeContactAddress`. * The `order` parameter applies to both, for example: * NodeBucketIndex asc, NodeID asc, NodeContactAddress asc * NodeBucketIndex desc, NodeId desc, NodeContactAddress desc */ getNodeContacts(order: "asc" | "desc" | undefined, tran: DBTransaction | undefined, ctx: ContextTimed): AsyncGenerator<[NodeId, NodeContact]>; /** * Get a single `NodeContactAddressData`. */ getNodeContactAddressData(nodeId: NodeId, nodeAddress: NodeAddress | NodeContactAddress, tran?: DBTransaction): Promise; /** * Sets a single `NodeContact` for a `NodeId`. * This replaces the entire `NodeContact` for the `NodeId`. * This will increment the bucket count if it is a new `NodeID`. * * @throws {nodesErrors.ErrorNodeGraphBucketLimit} If the bucket is full. */ setNodeContact(nodeId: NodeId, nodeContact: NodeContact, tran?: DBTransaction): Promise; /** * Sets a single `NodeContactAddressData` for a `NodeId`. * This will increment the bucket count if it is a new `NodeID`. * * @throws {nodesErrors.ErrorNodeGraphBucketLimit} If the bucket is full. */ setNodeContactAddressData(nodeId: NodeId, nodeAddress: NodeAddress | NodeContactAddress, nodeContactAddressData: NodeContactAddressData, tran?: DBTransaction): Promise; /** * Unsets a `NodeId` record. * It will decrement the bucket count if it existed. */ unsetNodeContact(nodeId: NodeId, tran?: DBTransaction): Promise; unsetNodeContactAddress(nodeId: NodeId, nodeAddress: NodeAddress | NodeContactAddress, tran?: DBTransaction): Promise; /** * Sets the `connectedTime` for a NodeId, replaces the old value if it exists */ protected setConnectedTime(nodeId: NodeId, connectedTime: number, tran: DBTransaction, path?: LevelPath): Promise; /** * Deletes the `connectedTime` for a NodeId */ protected delConnectedTime(nodeId: NodeId, tran: DBTransaction): Promise; /** * Gets the `connectedTime` for a node */ getConnectedTime(nodeId: NodeId, tran?: DBTransaction): any; /** * Gets a bucket. * * The bucket's node IDs is sorted lexicographically by default * Alternatively you can acquire them sorted by connected timestamp * or by distance to the own NodeId. * * @param bucketIndex * @param sort * @param order * @param limit Limit the number of nodes returned, note that `-1` means * no limit, but `Infinity` means `0`. * @param tran * @param ctx */ getBucket(bucketIndex: NodeBucketIndex, sort?: 'nodeId' | 'distance' | 'connected', order?: 'asc' | 'desc', limit?: number, tran?: DBTransaction, ctx?: Partial): Promise; /** * Gets all buckets. * Buckets are always sorted by `NodeBucketIndex` first * Then secondly by the `sort` parameter * The `order` parameter applies to both, for example possible sorts: * NodeBucketIndex asc, NodeID asc * NodeBucketIndex desc, NodeId desc * NodeBucketIndex asc, distance asc * NodeBucketIndex desc, distance desc * NodeBucketIndex asc, connected asc * NodeBucketIndex desc, connected desc */ getBuckets(sort?: 'nodeId' | 'distance' | 'connected', order?: 'asc' | 'desc', tran?: DBTransaction): AsyncGenerator<[NodeBucketIndex, NodeBucket]>; /** * Resets the bucket according to the new node ID. * Run this after new node ID is generated via renewal or reset. */ resetBuckets(tran?: DBTransaction, ctx?: ContextTimed): Promise; /** * Get a bucket meta POJO. * This will provide default values for missing properties. */ getBucketMeta(bucketIndex: NodeBucketIndex, tran?: DBTransaction): Promise; /** * Get a single bucket meta property. * This will provide default values for missing properties. */ getBucketMetaProp(bucketIndex: NodeBucketIndex, key: Key, tran?: DBTransaction): Promise; /** * Gets the closest nodes (closest based on Kademlia XOR operator) to a * given node ID. The returned results will be sorted by distance in * ascending order. If the given node ID already exists in the node graph, * then it will be the first result. * * @param nodeId * @param limit - Defaults to the bucket limit. * @param tran * @param ctx * @returns The `NodeBucket` which could have less than `limit` nodes if the * node graph has less than the requested limit. */ getClosestNodes(nodeId: NodeId, limit?: number, tran?: DBTransaction, ctx?: Partial): Promise; /** * Sets a single bucket meta property. * Bucket meta properties cannot be mutated outside. */ protected setBucketMetaProp(bucketIndex: NodeBucketIndex, key: Key, value: NodeBucketMeta[Key], tran: DBTransaction): Promise; /** * Returns to total number of nodes in the `NodeGraph` */ nodesTotal(tran?: DBTransaction): Promise; /** * This utility will clean out the oldest addresses until a set amount remain */ protected purgeAddresses(nodeId: NodeId, num: number, tran?: DBTransaction): Promise; } export default NodeGraph;