import type { DB, DBTransaction, LevelPath } from '@matrixai/db'; import type { ContextTimed, ContextTimedInput } from '@matrixai/contexts'; import type { PromiseCancellable } from '@matrixai/async-cancellable'; import type { ResourceAcquire } from '@matrixai/resources'; import type KeyRing from '../keys/KeyRing.js'; import type Sigchain from '../sigchain/Sigchain.js'; import type TaskManager from '../tasks/TaskManager.js'; import type GestaltGraph from '../gestalts/GestaltGraph.js'; import type { Task, TaskHandler, TaskHandlerId, TaskInfo } from '../tasks/types.js'; import type { SignedTokenEncoded } from '../tokens/types.js'; import type { Host, Port } from '../network/types.js'; import type { Claim, ClaimId, SignedClaim } from '../claims/types.js'; import type { ClaimNetworkAccess } from '../claims/payloads/index.js'; import type NodeConnection from '../nodes/NodeConnection.js'; import type { AgentClaimMessage, AgentRPCRequestParams, AgentRPCResponseResult } from './agent/types.js'; import type { NodeAddress, NodeBucket, NodeBucketIndex, NodeContactAddressData, NodeId, NodeIdEncoded } from './types.js'; import type NodeConnectionManager from './NodeConnectionManager.js'; import type NodeGraph from './NodeGraph.js'; import type { AgentClientManifestNodeManager } from './agent/callers/index.js'; import type { ClaimNetworkAuthority } from '../claims/payloads/claimNetworkAuthority.js'; import { MDNS } from '@matrixai/mdns'; import Logger from '@matrixai/logger'; import { startStop } from '@matrixai/async-init'; import { Lock, LockBox } from '@matrixai/async-locks'; import * as nodesEvents from './events.js'; import NodeConnectionQueue from './NodeConnectionQueue.js'; import Token from '../tokens/Token.js'; /** * NodeManager manages all operations involving nodes. * It encapsulates mutations to the NodeGraph. * It listens to the NodeConnectionManager events. */ interface NodeManager extends startStop.StartStop { } declare class NodeManager { /** * Time used to establish `NodeConnection` */ readonly connectionConnectTimeoutTime: number; readonly refreshBucketDelayTime: number; readonly refreshBucketDelayJitter: number; /** * Interval used to reestablish connections to maintain network health. * Will trigger a refreshBucket for bucket 255 if it is missing connections. * Will always trigger a findNode(this.keyRing.getNodeId()). */ readonly retryConnectionsDelayTime: number; /** * Timeout for finding a connection via MDNS */ readonly connectionFindMDNSTimeoutTime: number; readonly tasksPath = "NodeManager"; protected db: DB; protected logger: Logger; protected keyRing: KeyRing; protected sigchain: Sigchain; protected gestaltGraph: GestaltGraph; protected taskManager: TaskManager; protected nodeGraph: NodeGraph; protected nodeConnectionManager: NodeConnectionManager; protected mdnsOptions: { groups: Array; port: Port; } | undefined; protected mdns: MDNS | undefined; protected pendingNodes: Map>; protected concurrencyLimit: number; protected dnsServers: Array | undefined; /** * Used to track locks for authentication failure and acquiring connections */ protected connectionLockBox: LockBox; /** * If this node is acting as a network authority then the claim is stored here and used as needed. * If the node is not acting as an authority then the lack of claim here should indicate that. * If the claim is missing then any request that requires it should reject with an error. */ protected claimNetworkAuthority: Token | undefined; /** * If a node has joined a network then it's `ClaimNetworkAccess` is tracked here */ protected claimNetworkAccess: Token | undefined; /** * These are the level paths for mapping the ClaimNetworkAccess and ClaimNetworkAuthority claims for each network it has joined. * Used to look up and switch between networks as needed. */ protected nodeManagerDbPath: LevelPath; protected nodeManagerClaimNetworkAuthorityPath: LevelPath; protected nodeManagerClaimNetworkAccessPath: LevelPath; protected refreshBucketHandler: TaskHandler; readonly refreshBucketHandlerId: TaskHandlerId; protected gcBucketHandler: TaskHandler; readonly gcBucketHandlerId: TaskHandlerId; protected checkConnectionsHandler: TaskHandler; readonly checkConnectionsHandlerId: TaskHandlerId; protected syncNodeGraphHandler: (ctx: ContextTimed, _taskInfo: TaskInfo | undefined, initialNodes: Array<[NodeIdEncoded, NodeAddress]>, connectionConnectTimeoutTime: number | undefined) => Promise; readonly syncNodeGraphHandlerId: TaskHandlerId; protected handleEventNodeConnectionManagerConnectionAuthenticated: (e: nodesEvents.EventNodeConnectionManagerConnectionAuthenticated) => Promise; constructor({ db, keyRing, sigchain, gestaltGraph, taskManager, nodeGraph, nodeConnectionManager, mdnsOptions, connectionConnectTimeoutTime, refreshBucketDelayTime, refreshBucketDelayJitter, retryConnectionsDelayTime, // 45 seconds nodesConnectionFindLocalTimeoutTime, dnsServers, logger, }: { db: DB; keyRing: KeyRing; sigchain: Sigchain; gestaltGraph: GestaltGraph; taskManager: TaskManager; nodeGraph: NodeGraph; mdnsOptions?: { groups: Array; port: Port; }; nodeConnectionManager: NodeConnectionManager; connectionConnectTimeoutTime?: number; refreshBucketDelayTime?: number; refreshBucketDelayJitter?: number; retryConnectionsDelayTime?: number; nodesConnectionFindLocalTimeoutTime?: number; dnsServers?: Array; logger?: Logger; }); start(): Promise; stop(): Promise; /** * For usage with withF, to acquire a connection * This unique acquire function structure of returning the ResourceAcquire * itself is such that we can pass targetNodeId as a parameter (as opposed to * an acquire function with no parameters). * @param nodeId Id of target node to communicate with * @param ctx * @returns ResourceAcquire Resource API for use in with contexts */ acquireConnection(nodeId: NodeId, ctx: ContextTimed): ResourceAcquire>; /** * Perform some function on another node over the network with a connection. * Will either retrieve an existing connection, or create a new one if it * doesn't exist. * For use with normal arrow function * @param nodeId Id of target node to communicate with * @param f Function to handle communication * @param ctx */ withConnF(nodeId: NodeId, ctx: Partial | undefined, f: (conn: NodeConnection) => Promise): Promise; /** * Perform some function on another node over the network with a connection. * Will either retrieve an existing connection, or create a new one if it * doesn't exist. * for use with a generator function * @param nodeId Id of target node to communicate with * @param g Generator function to handle communication * @param ctx */ withConnG(nodeId: NodeId, ctx: Partial | undefined, g: (conn: NodeConnection) => AsyncGenerator): AsyncGenerator; /** * Will attempt to find a node within the network using a hybrid strategy of * attempting signalled connections, direct connections and checking MDNS. * * Will attempt to fix regardless of existing connection. * @param nodeId - NodeId of target to find. * @param connectionConnectTimeoutTime - timeout time for each individual connection. * @param connectionFindLocalTimeoutTime * @param concurrencyLimit - Limit the number of concurrent connections. * @param limit - Limit the number of total connections to be made before giving up. * @param ctx */ findNode({ nodeId, connectionConnectTimeoutTime, connectionFindMDNSTimeoutTime, concurrencyLimit, limit, }: { nodeId: NodeId; connectionConnectTimeoutTime?: number; connectionFindMDNSTimeoutTime?: number; concurrencyLimit?: number; limit?: number; }, ctx?: Partial): PromiseCancellable<[NodeAddress, NodeContactAddressData] | undefined>; /** * Will try to make a connection to the node using hole punched connections only * * @param nodeId - NodeId of the target to find. * @param nodeConnectionsQueue - shared nodeConnectionQueue helper class. * @param connectionConnectTimeoutTime - timeout time for each individual connection. * @param ctx */ findNodeBySignal(nodeId: NodeId, nodeConnectionsQueue: NodeConnectionQueue, connectionConnectTimeoutTime?: number, ctx?: Partial): PromiseCancellable<[[Host, Port], NodeContactAddressData]>; /** * Will try to make a connection to the node using direct connections only * * @param nodeId - NodeId of the target to find. * @param nodeConnectionsQueue - shared nodeConnectionQueue helper class. * @param connectionConnectTimeoutTime - timeout time for each individual connection. * @param ctx */ findNodeByDirect(nodeId: NodeId, nodeConnectionsQueue: NodeConnectionQueue, connectionConnectTimeoutTime?: number, ctx?: Partial): PromiseCancellable<[[Host, Port], NodeContactAddressData]>; /** * Will query via MDNS for running Polykey nodes with the matching NodeId * * @param nodeId * @param ctx */ queryMDNS(nodeId: NodeId, ctx?: Partial): PromiseCancellable>; /** * Will query MDNS for local nodes and attempt a connection. * @param nodeId - NodeId of the target to find. * @param ctx */ findNodeByMDNS(nodeId: NodeId, ctx?: Partial): PromiseCancellable<[[Host, Port], NodeContactAddressData]>; /** * Will ask the target node about the closest nodes to the `node` * and add them to the `nodeConnectionsQueue`. * * @param nodeId - node to find the closest nodes to * @param nodeIdTarget - node to make RPC requests to * @param nodeConnectionsQueue * @param ctx */ protected queueDataFromRequest(nodeId: NodeId, nodeIdTarget: NodeId, nodeConnectionsQueue: NodeConnectionQueue, ctx: ContextTimed): Promise; /** * Will attempt to establish connection using `findNode` or use existing connection. * Will return true if connection was established or already exists, false otherwise. */ pingNode(nodeId: NodeId, ctx?: Partial): PromiseCancellable<[NodeAddress, NodeContactAddressData] | undefined>; /** * Will attempt to make a direct connection without ICE. * This will only succeed due to these conditions * 1. connection already exists to target. * 2. Nat already allows port due to already being punched. * 3. Port is publicly accessible due to nat configuration . * Will return true if connection was established or already exists, false otherwise. */ pingNodeAddress(nodeId: NodeId, host: Host, port: Port, ctx?: Partial): PromiseCancellable; /** * Will attempt to make a direct connection without ICE. * This will only succeed due to these conditions * 1. connection already exists to target. * 2. Nat already allows port due to already being punched. * 3. Port is publicly accessible due to nat configuration . * Will return true if connection was established or already exists, false otherwise. */ pingNodeAddressMultiple(nodeId: NodeId, addresses: Array<[Host, Port]>, ctx?: Partial): PromiseCancellable; /** * Connects to the target node, and retrieves its sigchain data. * Verifies and returns the decoded chain as ChainData. Note: this will drop * any unverifiable claims. * For node1 -> node2 claims, the verification process also involves connecting * to node2 to verify the claim (to retrieve its signing public key). * @param targetNodeId Id of the node to connect request the chain data of. * @param claimId If set then we get the claims newer that this claim ID. * @param ctx */ requestChainData(targetNodeId: NodeId, claimId?: ClaimId, ctx?: Partial): PromiseCancellable>; /** * Call this function upon receiving a "claim node request" notification from * another node. */ claimNode(targetNodeId: NodeId, tran?: DBTransaction, ctx?: Partial): PromiseCancellable; handleClaimNode(requestingNodeId: NodeId, input: AsyncIterableIterator>, tran?: DBTransaction): AsyncGenerator>; /** * Creates a claim on the sigchain granting this node authority over a network to create `ClaimNetworkAccess` claims. * * @param networkNodeId - The public key NodeId for the root authority for the network * @param network - The network URL. * @param isPrivate - Indicates if the network is private or not. * @param signingHook - A callback used to sign the claim with the network's private key. * @param tran */ createClaimNetworkAuthority(networkNodeId: NodeId, network: string, isPrivate: boolean, signingHook: (token: Token) => Promise>, tran?: DBTransaction): Promise<[ClaimId, Token]>; createSelfSignedClaimNetworkAccess(claimNetworkAuthority: Token): Promise<[ClaimId, Token]>; /** * This takes a `ClaimNetworkAuthority` and tracks it in the database under the network name. */ protected setClaimNetworkAuthority(claimNetworkAuthority: Token, tran?: DBTransaction): Promise; /** * This returns the `ClaimNetworkAuthority` for the given network. */ protected getClaimNetworkAuthority(network: string, tran?: DBTransaction): Promise | undefined>; /** * This takes a `ClaimNetworkAccess` and tracks it in the database under the network name. */ protected setClaimNetworkAccess(claimNetworkAccess: Token, tran?: DBTransaction): Promise; /** * This returns the `ClaimNetworkAccess` for the given network. */ protected getClaimNetworkAccess(network: string, tran?: DBTransaction): Promise | undefined>; /** * This switches out the active `ClaimNetworkAuthority` and `ClaimNetworkAccess` for the desired network. * If no claims exist for the network or no network is provided, then it switches to using no network. * In doing so this also updates the `NodeConnectionManager`'s authentication callbacks to use the selected * network for authentication. * @param network - The Network URL for the desired network to switch to. * @param tran */ switchNetwork(network?: string, tran?: DBTransaction): Promise; /** * Quick hand utility for checking if the active `ClaimNetworkAuthority` is a private network */ isClaimNetworkAuthorityPrivate(): boolean | undefined; /** * This creates a cross-signed `ClaimNetworkAccess` on the sigchain. The resulting `ClaimNetworkAccess` is used to * authenticate connections between nodes within the network. * * @param targetNodeId - This is a node with an active `ClaimNetworkAuthority` for the network you wish to join. * This usually is a seed node for that network. * @param network - The URL of the network you wish to join. * @param tran * @param ctx */ claimNetwork(targetNodeId: NodeId, network: string, tran?: DBTransaction, ctx?: ContextTimedInput): Promise<[ClaimId, Token]>; /** * This provides the handler side of the ClaimNetwork logic. * @param requestingNodeId - The nodeId of the node making the request. This should be taken from the connections * certificate to confirm that `ClaimNetworkAccess` is being created for the node requesting it. * @param input - The input stream for the RPC handler. * @param tran */ handleClaimNetwork(requestingNodeId: NodeId, input: AsyncIterableIterator>, tran?: DBTransaction): AsyncGenerator>; /** * Gets the `ClaimNetworkAuthority` from the target node. It also verifies its valid and for the expected network. * @param network * @param targetNodeId */ remoteClaimNetworkAuthorityGet(network: string, targetNodeId: NodeId): Promise>; /** * The handler side logic for `remoteClaimNetworkAuthorityGet`. */ handleClaimNetworkAuthorityGet(): Promise; /** * Adds a node to the node graph. This assumes that you have already authenticated the node * Updates the node if the node already exists * This operation is blocking by default - set `block` to false to make it non-blocking * @param nodeId - ID of the node we wish to add * @param nodeAddress - Expected address of the node we want to add * @param nodeContactAddressData * @param block - When true it will wait for any garbage collection to finish before returning. * @param force - Flag for if we want to add the node without authenticating or if the bucket is full. * This will drop the oldest node in favor of the new. * @param connectionConnectTimeoutTime - Timeout for each ping operation during garbage collection. * @param ctx * @param tran */ setNode(nodeId: NodeId, nodeAddress: NodeAddress, nodeContactAddressData: NodeContactAddressData, block?: boolean, force?: boolean, connectionConnectTimeoutTime?: number, tran?: DBTransaction, ctx?: Partial): PromiseCancellable; /** * Removes a node from the NodeGraph */ unsetNode(nodeId: NodeId, tran: DBTransaction): Promise; /** * Gets the specified bucket from the NodeGraph */ getBucket(bucketIndex: number, tran?: DBTransaction): Promise; protected garbageCollectBucket(bucketIndex: number, connectionConnectTimeoutTime?: number, ctx?: Partial, tran?: DBTransaction): PromiseCancellable; protected addPendingNode(bucketIndex: number, nodeId: NodeId, nodeAddress: NodeAddress, nodeContactAddressData: NodeContactAddressData, block: boolean | undefined, connectionConnectTimeoutTime: number | undefined, ctx: ContextTimed, tran?: DBTransaction): Promise; protected setupGCTask(bucketIndex: number): Promise; /** * To be called on key renewal. Re-orders all nodes in all buckets with respect * to the new node ID. */ resetBuckets(): Promise; /** * Kademlia refresh bucket operation. * It generates a random node ID within the range of a bucket and does a * lookup for that node in the network. This will cause the network to update * its node graph information. * @param bucketIndex * @param connectionConnectTimeoutTime * @param ctx */ refreshBucket(bucketIndex: NodeBucketIndex, connectionConnectTimeoutTime?: number, ctx?: Partial): PromiseCancellable; protected setupRefreshBucketTasks(tran?: DBTransaction): any; /** * Cancels all NodeManager tasks. * These are ephemeral and will be recreated next time we start. */ protected stopTasks(): Promise; updateRefreshBucketDelay(bucketIndex: number, delay?: number, lazy?: boolean, tran?: DBTransaction, ctx?: Partial): Promise; /** * Perform an initial database synchronisation: get k of the closest nodes * from each seed node and add them to this database * Establish a connection to each node before adding it * By default this operation is blocking, set `block` to `false` to make it * * From the spec: * To join the network, a node u must have a contact to an already participating node w. u inserts w into the * appropriate k-bucket. u then performs a node lookup for its own node ID. Finally, u refreshes all k-buckets further * away than its closest neighbor. During the refreshes, u both populates its own k-buckets and inserts itself into * other nodes’ k-buckets as necessary. * * So this will do 3 steps * 1. Connect to the initial nodes * 2. do a find-node operation for itself * 3. reschedule refresh bucket operations for every bucket above the closest node we found * */ syncNodeGraph(initialNodes: Array<[NodeId, NodeAddress]>, connectionConnectTimeoutTime?: number, blocking?: boolean, ctx?: Partial): PromiseCancellable; } export default NodeManager;