-
Notifications
You must be signed in to change notification settings - Fork 30
feat(backend): the utilities and services required for the pow feature #5784
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Simplifying optional param handling using toNullable() Co-authored-by: Antonio Ventilii <antonio.ventilii@dfinity.org>
Simplifying optional param handling using toNullable() Co-authored-by: Antonio Ventilii <antonio.ventilii@dfinity.org>
Simplifying optional param handling using toNullable() Co-authored-by: Antonio Ventilii <antonio.ventilii@dfinity.org>
…roducing-pow-worker-1
…w-worker-1' into feat/frontend/pow/introducing-pow-worker-1
…w-worker-1' into feat/frontend/pow/introducing-pow-worker-1
…roducing-pow-worker-1
…roducing-pow-worker-2
…rontend/pow/introducing-pow-worker-2
…w-worker-2' into feat/frontend/pow/introducing-pow-worker-2
…rontend/pow/introducing-pow-worker-2 # Conflicts: # src/frontend/src/tests/lib/services/loader.services.spec.ts
…roducing-pow-worker-2
*/ | ||
export const PostMessageBaseSchema = z.object({ | ||
msg: z.string(), // Message type | ||
requestId: z.string() // Unique identifier for tracking requests and responses |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we have a pre-existing base PostMessageSchema
, can we expand that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do not want to risk the to refactor the other messages since this structure is specifically designed for implementing the short-lived Request-Response Messaging pattern.
*/ | ||
export const PostMessageRequestBaseSchema = PostMessageBaseSchema.extend({ | ||
type: z.literal('request'), // Specifies this is a request | ||
data: z.unknown().optional() // Optional data payload |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why unknown
? don't we know what the data are?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i'm using a hierarchical inheritance structure for schema. The data is overridden by the actual type.
@@ -189,3 +189,92 @@ export const inferPostMessageSchema = <T extends z.ZodTypeAny>(dataSchema: T) => | |||
msg: z.union([PostMessageRequestSchema, PostMessageResponseSchema]), | |||
data: dataSchema.optional() | |||
}); | |||
|
|||
// ----------------------------------------------------------------------------------------------- | |||
// The generic data structures which are required to implement the Short Polling (request-response) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In general, we already have quite a lot PostMessage
types and schema, we use them for all the workers/schedulers. Is it not possible to use the same? I imagine you are using the same logic, no?
|
||
// ----------------------------------------------------------------------------------------------- | ||
// The post message types used for short polling between: | ||
// pow.worker.ts <---> worker.pow.services.ts | ||
// ----------------------------------------------------------------------------------------------- | ||
// Base Types | ||
export type PostMessageBase = z.infer<typeof PostMessageBaseSchema>; | ||
export type PostMessageRequestBase = z.infer<typeof PostMessageRequestBaseSchema>; | ||
export type PostMessageResponseBase = z.infer<typeof PostMessageResponseBaseSchema>; | ||
export type PostMessageCreatePowChallengeRequest = z.infer< | ||
typeof PostMessageCreatePowChallengeRequestSchema | ||
>; | ||
export type PostMessageCreatePowChallengeResponse = z.infer< | ||
typeof PostMessageCreatePowChallengeResponseSchema | ||
>; | ||
export type PostMessageAllowSigningRequest = z.infer<typeof PostMessageAllowSigningRequestSchema>; | ||
export type PostMessageAllowSigningResponse = z.infer<typeof PostMessageAllowSigningResponseSchema>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
which one of these types is used in this PR? If they are not needed here, let's remove them (and their schemas if not needed)
export async function sha256(input: string): Promise<ArrayBuffer> { | ||
return await crypto.subtle.digest('SHA-256', textEncoder.encode(input)); | ||
} | ||
|
||
/** | ||
* Converts an ArrayBuffer to its hexadecimal string representation. | ||
*/ | ||
export function bufferToHex(buffer: ArrayBuffer): string { | ||
return Array.from(new Uint8Array(buffer)) | ||
.map((byte) => byte.toString(16).padStart(2, '0')) | ||
.join(''); | ||
} | ||
|
||
/** | ||
* Combines the hashing and hex conversion of a string into a single function. | ||
*/ | ||
export async function hashToHex(input: string): Promise<string> { | ||
const hashBuffer = await sha256(input); | ||
return bufferToHex(hashBuffer); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
don't these functions already exists in some library that we have?
P.S. can you use arrow convention for them? for example: export const sha256 = ...
|
||
const responseHandlers = new Map<string, (data: unknown) => void>(); | ||
|
||
export function routeWorkerResponse(event: MessageEvent): boolean { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why the return is a boolean? WDYT of using ResultSuccess
?
import type { PostMessageRequestBase } from '$lib/types/post-message'; | ||
import type { z } from 'zod'; | ||
|
||
const responseHandlers = new Map<string, (data: unknown) => void>(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is this constant? why is it used? why is it not everything handled inside the worker?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is part of the short-lived Request-Response Messaging pattern. It is used to automatically route the request back to the sender. It is private and constant. If I move responseHandlers to pow.worker.ts I would also have to move routeWorkerResponse and sendMessageRequest to pow.worker.ts.
responseHandlers.delete(requestId); | ||
return true; | ||
} | ||
console.warn('No handler found for event', requestId); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
let's avoid warnings
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How do we usually troubleshoot? Is there I dedicated debug logger that can be activated if needed? Should I remove all warnings?
data: object; | ||
schema: z.ZodType<T>; | ||
}): Promise<T> { | ||
const requestId = crypto.randomUUID(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this entire function should be inside the worker. How the other workers handle this?
resolve(parsed.data); | ||
}); | ||
|
||
worker.postMessage(payload); // Send the typed payload |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i am not really sure this pertains here... usually the workers that we have handle the messaging in code. For example, have a look at class Scheduler
} | ||
}; | ||
|
||
export const _allowSigning = async ({ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why this hidden method?
return nonce; | ||
}; | ||
|
||
export const _createPowChallenge = async ({ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same
import { hashToHex } from '$lib/utils/crypto.utils'; | ||
|
||
function getTimestampNowMs(): bigint { | ||
return BigInt(Date.now()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
there is nowInBigIntNanoSeconds
maybe you can create nowInBigInt
there too
difficulty: number; | ||
}): Promise<number> => { | ||
if (difficulty <= 0) { | ||
throw new Error('Difficulty must be greater than zero'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i18n?
// is only used to measure the effective execution time | ||
const startTimestampMs = getTimestampNowMs(); | ||
|
||
while (true) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
let's make it functional
} | ||
|
||
let nonce = 0; | ||
const target = Math.floor(0xffffffff / difficulty); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe some comment here
return BigInt(Date.now()); | ||
} | ||
|
||
export const solvePowChallenge = async ({ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
tests for this one?
suggestion: put this in another PR
} | ||
|
||
const solveTimestampMs = getTimestampNowMs() - startTimestampMs; | ||
console.error(`Pow Challenge solved in ${Number(solveTimestampMs) / 1e3} seconds.`); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why printing an error?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That was used for debug purpose. I will remove it!
Pull request was closed
Motivation
This PR introduces utilities and tests to improve the worker communication framework, ensuring robust validation, seamless message handling, and secure hashing functionality. The changes enhance developer confidence in worker-related operations and establish a foundation for future scalability.
Changes
routeWorkerResponse
andsendMessageRequest
utilities inworker.utils.ts
for handling and sending worker messages with type-safe validation using Zod.worker.utils.spec.ts
test suite to thoroughly validate the behavior ofrouteWorkerResponse
andsendMessageRequest
.crypto.utils.ts
to provide utility functions for secure hashing (e.g.,sha256
,bufferToHex
,hashToHex
).post-message.ts
andpost-message.schema.ts
to centralize and type-check worker message schemas and definitions.Tests