Skip to content

Commit

Permalink
Exposed DWN_REGISTRATION_PROOF_OF_WORK_INITIAL_MAX_HASH setting
Browse files Browse the repository at this point in the history
  • Loading branch information
thehenrytsai committed Jan 11, 2024
1 parent b6dfc11 commit 33b12b5
Show file tree
Hide file tree
Showing 9 changed files with 72 additions and 58 deletions.
25 changes: 13 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -276,18 +276,19 @@ cloudflared tunnel --url http://localhost:3000

Configuration can be set using environment variables

| Env Var | Description | Default |
| ---------------------------------------- | --------------------------------------------------------------------------------------- | ---------------------- |
| `DS_PORT` | Port that the server listens on | `3000` |
| `DS_MAX_RECORD_DATA_SIZE` | Maximum size for `RecordsWrite` data. use `b`, `kb`, `mb`, `gb` for value | `1gb` |
| `DS_WEBSOCKET_SERVER` | Whether to enable listening over `ws:`. values: `on`,`off` | `on` |
| `DWN_REGISTRATION_STORE_URL` | URL to use for storage of registered DIDs | `sqlite://data/dwn.db` |
| `DWN_REGISTRATION_PROOF_OF_WORK_ENABLED` | Require new users to complete a proof-of-work challenge | `false` |
| `DWN_TERMS_OF_SERVICE_FILE_PATH` | Required terms of service agreement if set. Value is path to the terms of service file. | unset |
| `DWN_STORAGE` | URL to use for storage by default. See [Storage Options](#storage-options) for details | `level://data` |
| `DWN_STORAGE_MESSAGES` | URL to use for storage of messages. | value of `DWN_STORAGE` |
| `DWN_STORAGE_DATA` | URL to use for data storage | value of `DWN_STORAGE` |
| `DWN_STORAGE_EVENTS` | URL to use for event storage | value of `DWN_STORAGE` |
| Env Var | Description | Default |
| ------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- | ---------------------- |
| `DS_PORT` | Port that the server listens on | `3000` |
| `DS_MAX_RECORD_DATA_SIZE` | Maximum size for `RecordsWrite` data. use `b`, `kb`, `mb`, `gb` for value | `1gb` |
| `DS_WEBSOCKET_SERVER` | Whether to enable listening over `ws:`. values: `on`,`off` | `on` |
| `DWN_REGISTRATION_STORE_URL` | URL to use for storage of registered DIDs | `sqlite://data/dwn.db` |
| `DWN_REGISTRATION_PROOF_OF_WORK_ENABLED` | Require new users to complete a proof-of-work challenge | `false` |
| `DWN_REGISTRATION_PROOF_OF_WORK_INITIAL_MAX_HASH` | Initial maximum allowed hash in 64 char HEX string. The more leading zeros (smaller number) the higher the difficulty. | `false` |
| `DWN_TERMS_OF_SERVICE_FILE_PATH` | Required terms of service agreement if set. Value is path to the terms of service file. | unset |
| `DWN_STORAGE` | URL to use for storage by default. See [Storage Options](#storage-options) for details | `level://data` |
| `DWN_STORAGE_MESSAGES` | URL to use for storage of messages. | value of `DWN_STORAGE` |
| `DWN_STORAGE_DATA` | URL to use for data storage | value of `DWN_STORAGE` |
| `DWN_STORAGE_EVENTS` | URL to use for event storage | value of `DWN_STORAGE` |

### Storage Options

Expand Down
1 change: 1 addition & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export const config = {
// tenant registration feature configuration
registrationStoreUrl: process.env.DWN_REGISTRATION_STORE_URL || process.env.DWN_STORAGE || 'sqlite://data/dwn.db',
registrationProofOfWorkEnabled: process.env.DWN_REGISTRATION_PROOF_OF_WORK_ENABLED === 'true',
registrationProofOfWorkInitialMaxHash: process.env.DWN_REGISTRATION_PROOF_OF_WORK_INITIAL_MAX_HASH,
termsOfServiceFilePath: process.env.DWN_TERMS_OF_SERVICE_FILE_PATH,

// log level - trace/debug/info/warn/error
Expand Down
1 change: 1 addition & 0 deletions src/dwn-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ export class DwnServer {
registrationManager = await RegistrationManager.create({
registrationStoreUrl: this.config.registrationStoreUrl,
termsOfServiceFilePath: this.config.termsOfServiceFilePath,
initialMaximumAllowedHashValue: this.config.registrationProofOfWorkInitialMaxHash,
});

this.dwn = await Dwn.create(getDWNConfig(this.config, registrationManager));
Expand Down
45 changes: 25 additions & 20 deletions src/registration/proof-of-work-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,18 @@ import { ProofOfWork } from "./proof-of-work.js";
* Can have multiple instances each having their own desired solve rate and difficulty.
*/
export class ProofOfWorkManager {
// Takes from seconds to ~1 minute to solve on an M1 MacBook.
private static readonly defaultMaximumAllowedHashValue = '000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF';

// Challenge nonces that can be used for proof-of-work.
private challengeNonces: { currentChallengeNonce: string, previousChallengeNonce?: string };

// There is opportunity to improve implementation here.
private proofOfWorkOfLastMinute: Map<string, number> = new Map(); // proofOfWorkId -> timestamp of proof-of-work

private difficultyIncreaseMultiplier: number;
private currentMaximumHashValueAsBigInt: bigint;
private initialMaximumHashValueAsBigInt: bigint;
private currentMaximumAllowedHashValueAsBigInt: bigint;
private initialMaximumAllowedHashValueAsBigInt: bigint;
private desiredSolveCountPerMinute: number;

/**
Expand All @@ -32,7 +35,7 @@ export class ProofOfWorkManager {
* The current maximum allowed hash value.
*/
public get currentMaximumAllowedHashValue(): bigint {
return this.currentMaximumHashValueAsBigInt;
return this.currentMaximumAllowedHashValueAsBigInt;
}

/**
Expand All @@ -44,16 +47,16 @@ export class ProofOfWorkManager {

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

this.challengeNonces = { currentChallengeNonce: ProofOfWork.generateNonce() };
this.currentMaximumHashValueAsBigInt = BigInt(`0x${initialMaximumHashValue}`);
this.initialMaximumHashValueAsBigInt = BigInt(`0x${initialMaximumHashValue}`);
this.currentMaximumAllowedHashValueAsBigInt = BigInt(`0x${initialMaximumAllowedHashValue}`);
this.initialMaximumAllowedHashValueAsBigInt = BigInt(`0x${initialMaximumAllowedHashValue}`);
this.desiredSolveCountPerMinute = desiredSolveCountPerMinute;
this.difficultyIncreaseMultiplier = input.difficultyIncreaseMultiplier;
this.challengeRefreshFrequencyInSeconds = input.challengeRefreshFrequencyInSeconds;
Expand All @@ -70,21 +73,22 @@ export class ProofOfWorkManager {
*/
public static async create(input: {
desiredSolveCountPerMinute: number,
initialMaximumHashValue: string,
autoStart: boolean,
initialMaximumAllowedHashValue?: string,
difficultyIncreaseMultiplier?: number,
challengeRefreshFrequencyInSeconds?: number,
difficultyReevaluationFrequencyInSeconds?: number
}): Promise<ProofOfWorkManager> {
const { desiredSolveCountPerMinute, initialMaximumHashValue } = input;
const { desiredSolveCountPerMinute } = input;

const initialMaximumAllowedHashValue = input.initialMaximumAllowedHashValue ?? ProofOfWorkManager.defaultMaximumAllowedHashValue;
const difficultyIncreaseMultiplier = input.difficultyIncreaseMultiplier ?? 1; // 1x default
const challengeRefreshFrequencyInSeconds = input.challengeRefreshFrequencyInSeconds ?? 10 * 60; // 10 minutes default
const difficultyReevaluationFrequencyInSeconds = input.difficultyReevaluationFrequencyInSeconds ?? 10; // 10 seconds default

const proofOfWorkManager = new ProofOfWorkManager({
desiredSolveCountPerMinute,
initialMaximumHashValue,
initialMaximumAllowedHashValue,
difficultyIncreaseMultiplier,
challengeRefreshFrequencyInSeconds,
difficultyReevaluationFrequencyInSeconds
Expand Down Expand Up @@ -218,42 +222,43 @@ export class ProofOfWorkManager {
// if solve rate is higher than desired, make difficulty harder by making the max allowed hash value smaller

const currentSolveRateInFractionOfDesiredSolveRate = latestSolveCountPerMinute / this.desiredSolveCountPerMinute;
const newMaximumHashValueAsBigIntPriorToMultiplierAdjustment
= (this.currentMaximumHashValueAsBigInt * BigInt(scaleFactor)) /
const newMaximumAllowedHashValueAsBigIntPriorToMultiplierAdjustment
= (this.currentMaximumAllowedHashValueAsBigInt * BigInt(scaleFactor)) /
(BigInt(Math.floor(currentSolveRateInFractionOfDesiredSolveRate * this.difficultyIncreaseMultiplier * scaleFactor)));

const hashValueDecreaseAmountPriorToEvaluationFrequencyAdjustment
= (this.currentMaximumHashValueAsBigInt - newMaximumHashValueAsBigIntPriorToMultiplierAdjustment) *
= (this.currentMaximumAllowedHashValueAsBigInt - newMaximumAllowedHashValueAsBigIntPriorToMultiplierAdjustment) *
(BigInt(Math.floor(this.difficultyIncreaseMultiplier * scaleFactor)) / BigInt(scaleFactor));

// Adjustment based on the reevaluation frequency to provide more-or-less consistent behavior regardless of the reevaluation frequency.
const hashValueDecreaseAmount = hashValueDecreaseAmountPriorToEvaluationFrequencyAdjustment / BigInt(difficultyEvaluationsPerMinute);

this.currentMaximumHashValueAsBigInt -= hashValueDecreaseAmount;
this.currentMaximumAllowedHashValueAsBigInt -= hashValueDecreaseAmount;

// Resetting to allow hash increment to be recalculated when difficulty needs to be reduced (in `else` block below)
this.hashValueIncrementPerEvaluation = undefined;
} 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 (this.currentMaximumAllowedHashValueAsBigInt === this.initialMaximumAllowedHashValueAsBigInt) {
// if current difficulty is already at initial difficulty, nothing to do
return;
}

if (this.hashValueIncrementPerEvaluation === undefined) {
const backToInitialDifficultyInMinutes = 10;
const differenceBetweenInitialAndCurrentDifficulty = this.initialMaximumHashValueAsBigInt - this.currentMaximumHashValueAsBigInt;
const differenceBetweenInitialAndCurrentDifficulty
= this.initialMaximumAllowedHashValueAsBigInt - this.currentMaximumAllowedHashValueAsBigInt;
this.hashValueIncrementPerEvaluation
= differenceBetweenInitialAndCurrentDifficulty / BigInt(backToInitialDifficultyInMinutes * difficultyEvaluationsPerMinute);
}

// 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;
const newMaximumAllowedHashValueAsBigInt = this.currentMaximumAllowedHashValueAsBigInt + this.hashValueIncrementPerEvaluation;
if (newMaximumAllowedHashValueAsBigInt >= this.initialMaximumAllowedHashValueAsBigInt) {
this.currentMaximumAllowedHashValueAsBigInt = this.initialMaximumAllowedHashValueAsBigInt;
} else {
this.currentMaximumHashValueAsBigInt = newMaximumAllowedHashValueAsBigInt;
this.currentMaximumAllowedHashValueAsBigInt = newMaximumAllowedHashValueAsBigInt;
}
}
}
Expand Down
5 changes: 3 additions & 2 deletions src/registration/registration-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,16 +54,17 @@ export class RegistrationManager implements TenantGate {
public static async create(input: {
registrationStoreUrl: string,
termsOfServiceFilePath?: string
initialMaximumAllowedHashValue?: string,
}): Promise<RegistrationManager> {
const { termsOfServiceFilePath, registrationStoreUrl } = input;
const { termsOfServiceFilePath, registrationStoreUrl, initialMaximumAllowedHashValue } = input;

const registrationManager = new RegistrationManager(termsOfServiceFilePath);

// Initialize and start ProofOfWorkManager.
registrationManager.proofOfWorkManager = await ProofOfWorkManager.create({
autoStart: true,
desiredSolveCountPerMinute: 10,
initialMaximumHashValue: '00FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF'
initialMaximumAllowedHashValue,
});

// Initialize RegistrationStore.
Expand Down
7 changes: 4 additions & 3 deletions tests/dwn-server.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,17 @@ import { expect } from 'chai';

import { config } from '../src/config.js';
import { DwnServer } from '../src/dwn-server.js';
import { getTestDwn } from './test-dwn.js';

describe('DwnServer', function () {
let dwnServer: DwnServer;
before(async function () {
const testDwn = await getTestDwn();
dwnServer = new DwnServer({ dwn: testDwn, config: config });
dwnServer = new DwnServer({ config: config });
});

after(async function () {
dwnServer.stop(() => console.log('server stop'));
});

it('should create an instance of DwnServer', function () {
expect(dwnServer).to.be.an.instanceOf(DwnServer);
});
Expand All @@ -24,6 +24,7 @@ describe('DwnServer', function () {
});
expect(response.status).to.equal(200);
});

it('should stop the server', async function () {
dwnServer.stop(() => console.log('server Stop'));
// Add an assertion to check that the server has been stopped
Expand Down
4 changes: 3 additions & 1 deletion tests/http-api.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,13 @@ describe('http api', function () {
config.registrationStoreUrl = 'sqlite://';
config.registrationProofOfWorkEnabled = true;
config.termsOfServiceFilePath = './tests/fixtures/terms-of-service.txt';
config.registrationProofOfWorkInitialMaxHash = '0FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF'; // 1 in 16 chance of solving

// RegistrationManager creation
const registrationStoreUrl = config.registrationStoreUrl;
const termsOfServiceFilePath = config.termsOfServiceFilePath;
registrationManager = await RegistrationManager.create({ registrationStoreUrl, termsOfServiceFilePath });
const initialMaximumAllowedHashValue = config.registrationProofOfWorkInitialMaxHash;
registrationManager = await RegistrationManager.create({ registrationStoreUrl, termsOfServiceFilePath, initialMaximumAllowedHashValue });

dwn = await getTestDwn(registrationManager);

Expand Down
Loading

0 comments on commit 33b12b5

Please sign in to comment.