diff --git a/src/nodes/NodeManager.ts b/src/nodes/NodeManager.ts index c0b586e31..af84a9e59 100644 --- a/src/nodes/NodeManager.ts +++ b/src/nodes/NodeManager.ts @@ -5,11 +5,17 @@ import type KeyManager from '../keys/KeyManager'; import type { PublicKeyPem } from '../keys/types'; import type Sigchain from '../sigchain/Sigchain'; import type { ChainData, ChainDataEncoded } from '../sigchain/types'; -import type { NodeId, NodeAddress, NodeBucket, NodeBucketIndex } from '../nodes/types'; +import type { + NodeId, + NodeAddress, + NodeBucket, + NodeBucketIndex, +} from '../nodes/types'; import type { ClaimEncoded } from '../claims/types'; import type { Timer } from '../types'; import Logger from '@matrixai/logger'; import { StartStop, ready } from '@matrixai/async-init/dist/StartStop'; +import { IdInternal } from '@matrixai/id'; import * as nodesErrors from './errors'; import * as nodesUtils from './utils'; import * as networkUtils from '../network/utils'; @@ -19,7 +25,6 @@ import * as claimsErrors from '../claims/errors'; import * as sigchainUtils from '../sigchain/utils'; import * as claimsUtils from '../claims/utils'; import { timerStart } from '../utils/utils'; -import { IdInternal } from '@matrixai/id'; interface NodeManager extends StartStop {} @StartStop() @@ -488,7 +493,7 @@ class NodeManager { // return await this.nodeGraph.getBuckets(); // } - // FIXME + // FIXME potentially confusing name, should we rename this to renewBuckets? /** * To be called on key renewal. Re-orders all nodes in all buckets with respect * to the new node ID. @@ -584,27 +589,23 @@ class NodeManager { await this.setNodeQueueEmpty; } + /** + * Kademlia refresh bucket operation. + * It picks a random node within a bucket and does a search for that node. + * Connections during the search will will share node information with other + * nodes. + * @param bucketIndex + */ private async refreshBucket(bucketIndex: NodeBucketIndex) { // We need to generate a random nodeId for this bucket - const bucketRandomNodeId = this.generateRandomNodeIdForBucket(bucketIndex); + const nodeId = this.keyManager.getNodeId(); + const bucketRandomNodeId = nodesUtils.generateRandomNodeIdForBucket( + nodeId, + bucketIndex, + ); // We then need to start a findNode procedure await this.nodeConnectionManager.findNode(bucketRandomNodeId); } - - // FIXME: make a proper method here.. - // this needs to fit within the range of the wanted bucket - private generateRandomNodeIdForBucket(bucketIndex: NodeBucketIndex): NodeId { - const buffer = Buffer.alloc(32) //TODO: make this random - // calculate the most significant byte for bucket - const base = bucketIndex / 8 - const mSigByte = Math.floor(base) - const mSigBit = (base-mSigByte) * 8; - // TODO: - // 1. zero upper bytes down to mSigByte - // 2. mask mSigByte to zero out bits above bucket, force 1 in bucket bit and lower bits unchanged. - return IdInternal.fromBuffer(buffer) - } - } export default NodeManager; diff --git a/src/nodes/utils.ts b/src/nodes/utils.ts index 76bb4058a..c61a6cd58 100644 --- a/src/nodes/utils.ts +++ b/src/nodes/utils.ts @@ -7,6 +7,7 @@ import type { import { IdInternal } from '@matrixai/id'; import lexi from 'lexicographic-integer'; import { bytes2BigInt, bufferSplit } from '../utils'; +import * as keysUtils from '../keys/utils'; // FIXME: const prefixBuffer = Buffer.from([33]); @@ -283,6 +284,44 @@ function bucketSortByDistance( } } +function generateRandomDistanceForBucket(bucketIndex: NodeBucketIndex): NodeId { + const buffer = keysUtils.getRandomBytesSync(32); + // Calculate the most significant byte for bucket + const base = bucketIndex / 8; + const mSigByte = Math.floor(base); + const mSigBit = (base - mSigByte) * 8 + 1; + const mSigByteIndex = buffer.length - mSigByte - 1; + // Creating masks + // AND mask should look like 0b00011111 + // OR mask should look like 0b00010000 + const shift = 8 - mSigBit; + const andMask = 0b11111111 >>> shift; + const orMask = 0b10000000 >>> shift; + let byte = buffer[mSigByteIndex]; + byte = byte & andMask; // Forces 0 for bits above bucket bit + byte = byte | orMask; // Forces 1 in the desired bucket bit + buffer[mSigByteIndex] = byte; + // Zero out byte 'above' mSigByte + for (let byteIndex = 0; byteIndex < mSigByteIndex; byteIndex++) { + buffer[byteIndex] = 0; + } + return IdInternal.fromBuffer(buffer); +} + +function xOrNodeId(node1: NodeId, node2: NodeId): NodeId { + const xOrNodeArray = node1.map((byte, i) => byte ^ node2[i]); + const xOrNodeBuffer = Buffer.from(xOrNodeArray); + return IdInternal.fromBuffer(xOrNodeBuffer); +} + +function generateRandomNodeIdForBucket( + nodeId: NodeId, + bucket: NodeBucketIndex, +): NodeId { + const randomDistanceForBucket = generateRandomDistanceForBucket(bucket); + return xOrNodeId(nodeId, randomDistanceForBucket); +} + export { prefixBuffer, encodeNodeId, @@ -299,4 +338,7 @@ export { parseLastUpdatedBucketDbKey, nodeDistance, bucketSortByDistance, + generateRandomDistanceForBucket, + xOrNodeId, + generateRandomNodeIdForBucket, }; diff --git a/tests/nodes/utils.test.ts b/tests/nodes/utils.test.ts index 59d565812..c87a82f26 100644 --- a/tests/nodes/utils.test.ts +++ b/tests/nodes/utils.test.ts @@ -171,4 +171,22 @@ describe('nodes/utils', () => { i++; } }); + test('should generate random distance for a bucket', async () => { + // Const baseNodeId = testNodesUtils.generateRandomNodeId(); + const zeroNodeId = IdInternal.fromBuffer(Buffer.alloc(32, 0)); + for (let i = 0; i < 255; i++) { + const randomDistance = nodesUtils.generateRandomDistanceForBucket(i); + expect(nodesUtils.bucketIndex(zeroNodeId, randomDistance)).toEqual(i); + } + }); + test('should generate random NodeId for a bucket', async () => { + const baseNodeId = testNodesUtils.generateRandomNodeId(); + for (let i = 0; i < 255; i++) { + const randomDistance = nodesUtils.generateRandomNodeIdForBucket( + baseNodeId, + i, + ); + expect(nodesUtils.bucketIndex(baseNodeId, randomDistance)).toEqual(i); + } + }); });