Skip to content

Commit

Permalink
100% code coverage + removed dedicated RegistrationTenantGate in favo…
Browse files Browse the repository at this point in the history
…r of simplicity
  • Loading branch information
thehenrytsai committed Jan 9, 2024
1 parent 0720f67 commit e525a5a
Show file tree
Hide file tree
Showing 9 changed files with 230 additions and 109 deletions.
2 changes: 1 addition & 1 deletion src/dwn-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ export class DwnServer {
if (!this.dwn) {
registrationManager = await RegistrationManager.create({ sqlDialect: tenantGateDB, termsOfService });

this.dwn = await Dwn.create(getDWNConfig(this.config, registrationManager.getTenantGate()));
this.dwn = await Dwn.create(getDWNConfig(this.config, registrationManager));
}

this.#httpApi = new HttpApi(this.config, this.dwn, registrationManager);
Expand Down
65 changes: 44 additions & 21 deletions src/registration/proof-of-work-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ export class ProofOfWorkManager {
private initialMaximumHashValueAsBigInt: bigint;
private desiredSolveCountPerMinute: number;

static readonly challengeRefreshFrequencyInMilliseconds = 10 * 60 * 1000; // 10 minutes
static readonly difficultyReevaluationFrequencyInMilliseconds = 10000;
public challengeRefreshFrequencyInSeconds: number;
public difficultyReevaluationFrequencyInSeconds: number;

public get currentMaximumAllowedHashValue(): bigint {
return this.currentMaximumHashValueAsBigInt;
Expand All @@ -20,19 +20,40 @@ export class ProofOfWorkManager {
return this.proofOfWorkOfLastMinute.size;
}

private constructor (desiredSolveCountPerMinute: number, initialMaximumHashValue: string) {
private constructor (input: {
desiredSolveCountPerMinute: number,
initialMaximumHashValue: string,
challengeRefreshFrequencyInSeconds: number,
difficultyReevaluationFrequencyInSeconds: number
}) {
const { desiredSolveCountPerMinute, initialMaximumHashValue } = input;

this.challengeNonces = { currentChallengeNonce: ProofOfWork.generateNonce() };
this.currentMaximumHashValueAsBigInt = BigInt(`0x${initialMaximumHashValue}`);
this.initialMaximumHashValueAsBigInt = BigInt(`0x${initialMaximumHashValue}`);
this.desiredSolveCountPerMinute = desiredSolveCountPerMinute;
this.challengeRefreshFrequencyInSeconds = input.challengeRefreshFrequencyInSeconds;
this.difficultyReevaluationFrequencyInSeconds = input.difficultyReevaluationFrequencyInSeconds;
}

public static async create(input: {
desiredSolveCountPerMinute: number,
initialMaximumHashValue: string,
autoStart: boolean,
challengeRefreshFrequencyInSeconds?: number,
difficultyReevaluationFrequencyInSeconds?: number
}): Promise<ProofOfWorkManager> {
const proofOfWorkManager = new ProofOfWorkManager(input.desiredSolveCountPerMinute, input.initialMaximumHashValue);
const { desiredSolveCountPerMinute, initialMaximumHashValue } = input;

const challengeRefreshFrequencyInSeconds = input.challengeRefreshFrequencyInSeconds ?? 10 * 60; // 10 minutes default
const difficultyReevaluationFrequencyInSeconds = input.difficultyReevaluationFrequencyInSeconds ?? 10; // 10 seconds default

const proofOfWorkManager = new ProofOfWorkManager({
desiredSolveCountPerMinute,
initialMaximumHashValue,
challengeRefreshFrequencyInSeconds,
difficultyReevaluationFrequencyInSeconds
});

if (input.autoStart) {
proofOfWorkManager.start();
Expand Down Expand Up @@ -104,12 +125,11 @@ export class ProofOfWorkManager {

private periodicallyRefreshChallengeNonce (): void {
try {
this.challengeNonces.previousChallengeNonce = this.challengeNonces.currentChallengeNonce;
this.challengeNonces.currentChallengeNonce = ProofOfWork.generateNonce();
this.refreshChallengeNonce();
} catch (error) {
console.error(`Encountered error while refreshing challenge nonce: ${error}`);
} finally {
setTimeout(async () => this.periodicallyRefreshChallengeNonce(), ProofOfWorkManager.challengeRefreshFrequencyInMilliseconds);
setTimeout(async () => this.periodicallyRefreshChallengeNonce(), this.challengeRefreshFrequencyInSeconds * 1000);
}
}

Expand All @@ -119,7 +139,7 @@ export class ProofOfWorkManager {
} catch (error) {
console.error(`Encountered error while updating proof of work difficulty: ${error}`);
} finally {
setTimeout(async () => this.periodicallyRefreshProofOfWorkDifficulty(), ProofOfWorkManager.difficultyReevaluationFrequencyInMilliseconds);
setTimeout(async () => this.periodicallyRefreshProofOfWorkDifficulty(), this.difficultyReevaluationFrequencyInSeconds * 1000);
}
}

Expand All @@ -132,6 +152,11 @@ export class ProofOfWorkManager {
}
}

private refreshChallengeNonce(): void {
this.challengeNonces.previousChallengeNonce = this.challengeNonces.currentChallengeNonce;
this.challengeNonces.currentChallengeNonce = ProofOfWork.generateNonce();
}

/**
* Refreshes the difficulty by changing the max hash value.
* The higher the number, the easier. Scale 1 (hardest) to 2^256 (easiest), represented in HEX.
Expand All @@ -149,7 +174,7 @@ export class ProofOfWorkManager {

// NOTE: bigint arithmetic does NOT work with decimals, so we work with "full numbers" by multiplying by a scale factor.
const scaleFactor = 1_000_000;
const difficultyEvaluationsPerMinute = 60000 / ProofOfWorkManager.difficultyReevaluationFrequencyInMilliseconds; // assumed to be >= 1;
const difficultyEvaluationsPerMinute = 60000 / (this.difficultyReevaluationFrequencyInSeconds * 1000); // assumed to be >= 1;

// NOTE: easier difficulty is represented by a larger max allowed hash value
// and harder difficulty is represented by a smaller max allowed hash value.
Expand All @@ -173,22 +198,14 @@ export class ProofOfWorkManager {
= (this.currentMaximumHashValueAsBigInt - newMaximumHashValueAsBigIntPriorToMultiplierAdjustment) *
(BigInt(Math.floor(increaseMultiplier * scaleFactor)) / BigInt(scaleFactor));

const hashValueDecreaseAmount
= hashValueDecreaseAmountPriorToEvaluationFrequencyAdjustment / BigInt(difficultyEvaluationsPerMinute);

let newMaximumHashValueAsBigInt = this.currentMaximumHashValueAsBigInt - hashValueDecreaseAmount;
const hashValueDecreaseAmount = hashValueDecreaseAmountPriorToEvaluationFrequencyAdjustment / BigInt(difficultyEvaluationsPerMinute);

if (newMaximumHashValueAsBigInt === BigInt(0)) {
// if newMaximumHashValueAsBigInt is 0, we use 1 instead because 0 cannot multiply another number
newMaximumHashValueAsBigInt = BigInt(1);
}

this.currentMaximumHashValueAsBigInt = newMaximumHashValueAsBigInt;
this.currentMaximumHashValueAsBigInt -= hashValueDecreaseAmount;
} else {
// if solve rate is lower than desired, make difficulty easier by making the max allowed hash value larger

if (this.currentMaximumHashValueAsBigInt === this.initialMaximumHashValueAsBigInt) {
// if current difficulty is already at initial difficulty, don't make it any easier
// if current difficulty is already at initial difficulty, nothing to do
return;
}

Expand All @@ -199,7 +216,13 @@ export class ProofOfWorkManager {
= differenceBetweenInitialAndCurrentDifficulty / BigInt(backToInitialDifficultyInMinutes * difficultyEvaluationsPerMinute);
}

this.currentMaximumHashValueAsBigInt += this.hashValueIncrementPerEvaluation;
// if newly calculated difficulty is lower than initial difficulty, just use the initial difficulty
const newMaximumAllowedHashValueAsBigInt = this.currentMaximumHashValueAsBigInt + this.hashValueIncrementPerEvaluation;
if (newMaximumAllowedHashValueAsBigInt >= this.initialMaximumHashValueAsBigInt) {
this.currentMaximumHashValueAsBigInt = this.initialMaximumHashValueAsBigInt;
} else {
this.currentMaximumHashValueAsBigInt = newMaximumAllowedHashValueAsBigInt;
}
}
}
}
11 changes: 1 addition & 10 deletions src/registration/proof-of-work.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,18 +68,9 @@ export class ProofOfWork {
qualifiedSolutionNonceFound = computedHashAsBigInt <= maximumAllowedHashValueAsBigInt;

iterations++;

// Log every 1M iterations.
if (iterations % 1_000_000 === 0) {
console.log(
`iterations: ${iterations}, time lapsed: ${
Date.now() - startTime
} ms`,
);
}
} while (!qualifiedSolutionNonceFound);

// Log final/successful attempt.
// Log final/successful iteration.
console.log(
`iterations: ${iterations}, time lapsed: ${Date.now() - startTime} ms`,
);
Expand Down
34 changes: 24 additions & 10 deletions src/registration/registration-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,14 @@ import type { RegistrationData, RegistrationRequest } from "./registration-types
import type { ProofOfWorkChallengeModel } from "./proof-of-work-types.js";
import { DwnServerError, DwnServerErrorCode } from "../dwn-error.js";
import type { TenantGate } from "@tbd54566975/dwn-sdk-js";
import { RegistrationTenantGate } from "./registration-tenant-gate.js";

export class RegistrationManager {
private tenantGate: TenantGate;
export class RegistrationManager implements TenantGate {
private proofOfWorkManager: ProofOfWorkManager;
private registrationStore: RegistrationStore;

private termsOfServiceHash?: string;
private termsOfService?: string;

public getTenantGate(): TenantGate {
return this.tenantGate;
}

public getTermsOfService(): string {
return this.termsOfService;
}
Expand All @@ -28,10 +22,17 @@ export class RegistrationManager {
return this.termsOfServiceHash;
}

/**
* Updates the terms-of-service. Exposed for testing purposes.
*/
public updateTermsOfService(termsOfService: string): void {
this.termsOfServiceHash = ProofOfWork.hashAsHexString([termsOfService]);
this.termsOfService = termsOfService;
}

private constructor (termsOfService?: string) {
if (termsOfService) {
this.termsOfServiceHash = ProofOfWork.hashAsHexString([termsOfService]);
this.termsOfService = termsOfService;
this.updateTermsOfService(termsOfService);
}
}

Expand All @@ -52,7 +53,6 @@ export class RegistrationManager {
// Initialize RegistrationStore.
const registrationStore = await RegistrationStore.create(sqlDialect);
registrationManager.registrationStore = registrationStore;
registrationManager.tenantGate = await RegistrationTenantGate.create(registrationStore, registrationManager.getTermsOfServiceHash());

return registrationManager;
}
Expand Down Expand Up @@ -90,4 +90,18 @@ export class RegistrationManager {
public async recordTenantRegistration(registrationData: RegistrationData): Promise<void> {
await this.registrationStore.insertOrUpdateTenantRegistration(registrationData);
}

public async isActiveTenant(tenant: string): Promise<boolean> {
const tenantRegistration = await this.registrationStore.getTenantRegistration(tenant);

if (tenantRegistration === undefined) {
return false
}

if (tenantRegistration.termsOfServiceHash !== this.termsOfServiceHash) {
return false;
}

return true;
}
}
25 changes: 0 additions & 25 deletions src/registration/registration-tenant-gate.ts

This file was deleted.

6 changes: 6 additions & 0 deletions tests/dwn-server.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ import { getTestDwn } from './test-dwn.js';
describe('DwnServer', function () {
let dwnServer: DwnServer;
before(async function () {

// Mute all server console logs during tests.
console.log = (): void => {};
console.error = (): void => {};
console.info = (): void => {};

const testDwn = await getTestDwn();
dwnServer = new DwnServer({ dwn: testDwn, config: config });
});
Expand Down
4 changes: 2 additions & 2 deletions tests/http-api.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,13 @@ describe('http api', function () {

config.registrationProofOfWorkEnabled = true;
config.termsOfServiceFilePath = './tests/fixtures/terms-of-service.txt';

// RegistrationManager creation
const sqlDialect = getDialectFromURI(new URL('sqlite://'));
const termsOfService = readFileSync(config.termsOfServiceFilePath).toString();
registrationManager = await RegistrationManager.create({ sqlDialect, termsOfService });

dwn = await getTestDwn(registrationManager.getTenantGate());
dwn = await getTestDwn(registrationManager);

httpApi = new HttpApi(config, dwn, registrationManager);

Expand Down
Loading

0 comments on commit e525a5a

Please sign in to comment.