Add pow metrics
This commit is contained in:
@@ -3,9 +3,11 @@ import { FileService } from './file.service';
|
|||||||
import { Post } from '@nestjs/common';
|
import { Post } from '@nestjs/common';
|
||||||
import { UploadedObjectInfo } from 'minio';
|
import { UploadedObjectInfo } from 'minio';
|
||||||
import { FileInterceptor } from '@nestjs/platform-express';
|
import { FileInterceptor } from '@nestjs/platform-express';
|
||||||
import { ApiConsumes } from '@nestjs/swagger';
|
import { ApiConsumes, ApiTags } from '@nestjs/swagger';
|
||||||
|
import { InjectMetric } from '@willsoto/nestjs-prometheus';
|
||||||
|
|
||||||
@Controller('file')
|
@Controller('file')
|
||||||
|
@ApiTags('file')
|
||||||
export class FileController {
|
export class FileController {
|
||||||
constructor(private readonly fileService: FileService) { }
|
constructor(private readonly fileService: FileService) { }
|
||||||
|
|
||||||
|
@@ -12,7 +12,7 @@ import {
|
|||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { KvService } from './kv.service';
|
import { KvService } from './kv.service';
|
||||||
import { Request } from 'express';
|
import { Request } from 'express';
|
||||||
import { ApiTags } from '@nestjs/swagger';
|
import { ApiQuery, ApiTags } from '@nestjs/swagger';
|
||||||
import { FileInterceptor } from '@nestjs/platform-express';
|
import { FileInterceptor } from '@nestjs/platform-express';
|
||||||
|
|
||||||
@Controller('kv')
|
@Controller('kv')
|
||||||
@@ -21,6 +21,8 @@ export class KvController {
|
|||||||
constructor(private readonly kvService: KvService) { }
|
constructor(private readonly kvService: KvService) { }
|
||||||
|
|
||||||
@Get(':namespace/:key/metadata')
|
@Get(':namespace/:key/metadata')
|
||||||
|
@ApiQuery({ name: 'namespace', required: true })
|
||||||
|
@ApiQuery({ name: 'key', required: true })
|
||||||
async getMetadata(
|
async getMetadata(
|
||||||
@Param('namespace') namespace: string,
|
@Param('namespace') namespace: string,
|
||||||
@Param('key') key: string,
|
@Param('key') key: string,
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { Body, Controller, Get, Post, Query } from '@nestjs/common';
|
import { Body, Controller, Get, Post, Query, Render } from '@nestjs/common';
|
||||||
import { PowService } from './pow.service';
|
import { PowService } from './pow.service';
|
||||||
import { ApiBody, ApiQuery, ApiTags } from '@nestjs/swagger';
|
import { ApiBody, ApiQuery, ApiTags } from '@nestjs/swagger';
|
||||||
|
|
||||||
@@ -9,6 +9,13 @@ export class PowController {
|
|||||||
private readonly powService: PowService,
|
private readonly powService: PowService,
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
|
@Get('')
|
||||||
|
@Render('pow/index')
|
||||||
|
async index() {
|
||||||
|
return {
|
||||||
|
difficulty: this.powService.getDifficulty(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
@Get('challenge')
|
@Get('challenge')
|
||||||
async generateChallenge() {
|
async generateChallenge() {
|
||||||
|
@@ -2,6 +2,7 @@ import { Module } from '@nestjs/common';
|
|||||||
import { PowService } from './pow.service';
|
import { PowService } from './pow.service';
|
||||||
import { CacheModule } from '@nestjs/cache-manager';
|
import { CacheModule } from '@nestjs/cache-manager';
|
||||||
import { PowController } from './pow.controller';
|
import { PowController } from './pow.controller';
|
||||||
|
import { PrometheusModule, makeGaugeProvider } from '@willsoto/nestjs-prometheus';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
@@ -9,8 +10,35 @@ import { PowController } from './pow.controller';
|
|||||||
ttl: 5,
|
ttl: 5,
|
||||||
max: 10,
|
max: 10,
|
||||||
}),
|
}),
|
||||||
|
PrometheusModule.register({
|
||||||
|
customMetricPrefix: 'pow',
|
||||||
|
defaultMetrics: {
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
providers: [PowService,
|
||||||
|
makeGaugeProvider({
|
||||||
|
name: 'challenges_generated',
|
||||||
|
help: 'The total number of POW challenges generated',
|
||||||
|
}),
|
||||||
|
makeGaugeProvider({
|
||||||
|
name: 'challenges_completed',
|
||||||
|
help: 'The total number of POW challenges completed',
|
||||||
|
}),
|
||||||
|
makeGaugeProvider({
|
||||||
|
name: 'successful_verifies',
|
||||||
|
help: 'The total number of successful POW challenge verifications',
|
||||||
|
}),
|
||||||
|
makeGaugeProvider({
|
||||||
|
name: 'failed_verifies',
|
||||||
|
help: 'The total number of failed POW challenge verifications',
|
||||||
|
}),
|
||||||
|
makeGaugeProvider({
|
||||||
|
name: 'difficulty',
|
||||||
|
help: 'The current POW difficulty',
|
||||||
|
}),
|
||||||
],
|
],
|
||||||
providers: [PowService],
|
|
||||||
controllers: [PowController]
|
controllers: [PowController]
|
||||||
})
|
})
|
||||||
export class PowModule { }
|
export class PowModule { }
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
import { CACHE_MANAGER } from '@nestjs/cache-manager';
|
import { CACHE_MANAGER } from '@nestjs/cache-manager';
|
||||||
import { Inject, Injectable, Logger } from '@nestjs/common';
|
import { Inject, Injectable, Logger } from '@nestjs/common';
|
||||||
|
import { InjectMetric } from '@willsoto/nestjs-prometheus';
|
||||||
import { Cache } from 'cache-manager';
|
import { Cache } from 'cache-manager';
|
||||||
import { randomBytes, createHash } from 'crypto';
|
import { randomBytes, createHash } from 'crypto';
|
||||||
|
|
||||||
@@ -11,7 +12,14 @@ export class PowService {
|
|||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(CACHE_MANAGER) private cacheManager: Cache,
|
@Inject(CACHE_MANAGER) private cacheManager: Cache,
|
||||||
) { }
|
@InjectMetric('challenges_generated') private challengesGenerated: any,
|
||||||
|
@InjectMetric('challenges_completed') private challengesCompleted: any,
|
||||||
|
@InjectMetric('successful_verifies') private successfulVerifies: any,
|
||||||
|
@InjectMetric('failed_verifies') private failedVerifies: any,
|
||||||
|
@InjectMetric('difficulty') private powDifficulty: any,
|
||||||
|
) {
|
||||||
|
this.powDifficulty.set(this.difficulty);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate a proof of work challenge, stored to redis for verification within
|
* Generate a proof of work challenge, stored to redis for verification within
|
||||||
@@ -19,7 +27,8 @@ export class PowService {
|
|||||||
*/
|
*/
|
||||||
async generateChallenge() {
|
async generateChallenge() {
|
||||||
const challenge = this.generateRandom256BitString();
|
const challenge = this.generateRandom256BitString();
|
||||||
console.log(challenge)
|
this.logger.verbose(`Generated challenge: ${challenge}`);
|
||||||
|
this.challengesGenerated.inc();
|
||||||
await this.cacheManager.set(challenge, true, 60 * 1000);
|
await this.cacheManager.set(challenge, true, 60 * 1000);
|
||||||
return challenge;
|
return challenge;
|
||||||
}
|
}
|
||||||
@@ -44,10 +53,17 @@ export class PowService {
|
|||||||
*/
|
*/
|
||||||
async verifyChallenge(challenge: string, proof: string): Promise<boolean> {
|
async verifyChallenge(challenge: string, proof: string): Promise<boolean> {
|
||||||
const expected = await this.cacheManager.get<boolean>(challenge);
|
const expected = await this.cacheManager.get<boolean>(challenge);
|
||||||
return expected ? this.hashAndCheck(proof + challenge) : false;
|
const success = expected ? this.hashAndCheck(proof + challenge) : false;
|
||||||
|
if (success) {
|
||||||
|
this.successfulVerifies.inc();
|
||||||
|
} else {
|
||||||
|
this.failedVerifies.inc();
|
||||||
|
}
|
||||||
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
async markChallengeAsComplete(challenge: string) {
|
async markChallengeAsComplete(challenge: string) {
|
||||||
|
this.challengesCompleted.inc();
|
||||||
await this.cacheManager.del(challenge);
|
await this.cacheManager.del(challenge);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,6 +92,7 @@ export class PowService {
|
|||||||
*/
|
*/
|
||||||
setDifficulty(difficulty: number) {
|
setDifficulty(difficulty: number) {
|
||||||
this.difficulty = difficulty;
|
this.difficulty = difficulty;
|
||||||
|
this.powDifficulty.set(this.difficulty);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
83
views/pow/index.hbs
Normal file
83
views/pow/index.hbs
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Proof Of Work API</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>PoW API</h1>
|
||||||
|
<p>
|
||||||
|
This is a simple API that allows you to generate a proof of work
|
||||||
|
challenges and verify the proof of work.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
The API has two endpoints:
|
||||||
|
<ul>
|
||||||
|
<li>GET
|
||||||
|
<a href='/pow/challenge'>/pow/challenge</a>
|
||||||
|
- This endpoint generates a new proof of work challenge.</li>
|
||||||
|
<li>POST
|
||||||
|
<a href='/pow/challenge'>/pow/challenge</a>
|
||||||
|
- This endpoint verifies a proof of work challenge.</li>
|
||||||
|
</ul>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
The proof of work challenge is to find a proof which, when concatenated
|
||||||
|
and hashed (via SHA 512) with a challenge string that starts with a
|
||||||
|
certain number of zeros. The number of zeros is determined by the
|
||||||
|
difficulty level. The difficulty level is a number between 1 and 10. The
|
||||||
|
higher the difficulty level, the more zeros the string must start with.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
The current difficulty is
|
||||||
|
{{difficulty}}.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
The example code for performing a proof of work challenge is:
|
||||||
|
<pre>
|
||||||
|
const crypto = require('crypto');
|
||||||
|
const axios = require('axios');
|
||||||
|
|
||||||
|
async function getChallenge() {
|
||||||
|
const response = await axios.get('https://api.us.dev/pow/challenge');
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function submitSolution(challenge, proof) {
|
||||||
|
const response = await axios.post('https://api.us.dev/pow/challenge', {
|
||||||
|
challenge,
|
||||||
|
proof
|
||||||
|
});
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
function hashProof(challenge, proof) {
|
||||||
|
return crypto.createHash('sha512').update(challenge + proof).digest('hex');
|
||||||
|
}
|
||||||
|
|
||||||
|
function verifyChallenge(hash: string, difficulty: number) {
|
||||||
|
return hash.startsWith('0'.repeat(difficulty));
|
||||||
|
}
|
||||||
|
|
||||||
|
async function proofOfWork() {
|
||||||
|
const challenge = await getChallenge();
|
||||||
|
let proof = 0;
|
||||||
|
let hash = '';
|
||||||
|
do {
|
||||||
|
proof++;
|
||||||
|
hash = hashProof(challenge, proof);
|
||||||
|
} while (!verifyChallenge(hash, 5));
|
||||||
|
return await submitSolution(challenge, proof);
|
||||||
|
}
|
||||||
|
|
||||||
|
proofOfWork().then(console.log).catch(console.error);
|
||||||
|
</pre>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
If you're a developer you should use the GET /pow/challenge endpoint to
|
||||||
|
get the challenge, then pass the challenge to your client, then have the
|
||||||
|
client find a solution to the challenge and then submit their proof as
|
||||||
|
part of their later action. Your service should then verify the solution
|
||||||
|
using the POST /pow/challenge endpoint (or your own local implementation)
|
||||||
|
to verify the solution.
|
||||||
|
</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
Reference in New Issue
Block a user