Skip to content

Commit

Permalink
feat: implemented NodeManager.refreshBucket()
Browse files Browse the repository at this point in the history
This method preforms the kademlia `refreshBucket` operation. It selects a random node within the bucket and preforms a search for that node. The process exchanges node information with any nodes it connects to.

#345
  • Loading branch information
tegefaulkes authored and emmacasolin committed Jun 14, 2022
1 parent 9ed6e2b commit b4fbc0b
Show file tree
Hide file tree
Showing 3 changed files with 86 additions and 2 deletions.
28 changes: 26 additions & 2 deletions src/nodes/NodeManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 } 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';
Expand Down Expand Up @@ -514,7 +520,7 @@ class NodeManager {
// return await this.nodeGraph.getAllBuckets(tran);
// }

// 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.
Expand Down Expand Up @@ -609,6 +615,24 @@ class NodeManager {
public async queueDrained(): Promise<void> {
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 nodeId = this.keyManager.getNodeId();
const bucketRandomNodeId = nodesUtils.generateRandomNodeIdForBucket(
nodeId,
bucketIndex,
);
// We then need to start a findNode procedure
await this.nodeConnectionManager.findNode(bucketRandomNodeId);
}
}

export default NodeManager;
42 changes: 42 additions & 0 deletions src/nodes/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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]);
Expand Down Expand Up @@ -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<NodeId>(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<NodeId>(xOrNodeBuffer);
}

function generateRandomNodeIdForBucket(
nodeId: NodeId,
bucket: NodeBucketIndex,
): NodeId {
const randomDistanceForBucket = generateRandomDistanceForBucket(bucket);
return xOrNodeId(nodeId, randomDistanceForBucket);
}

export {
prefixBuffer,
encodeNodeId,
Expand All @@ -299,4 +338,7 @@ export {
parseLastUpdatedBucketDbKey,
nodeDistance,
bucketSortByDistance,
generateRandomDistanceForBucket,
xOrNodeId,
generateRandomNodeIdForBucket,
};
18 changes: 18 additions & 0 deletions tests/nodes/utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<NodeId>(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);
}
});
});

0 comments on commit b4fbc0b

Please sign in to comment.