-
Notifications
You must be signed in to change notification settings - Fork 1
Feat/scaffold coordinator service api calls #11
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
Feat/scaffold coordinator service api calls #11
Conversation
.eslintrc.cjs
Outdated
@@ -18,6 +18,7 @@ module.exports = { | |||
"@typescript-eslint/no-explicit-any": "off", | |||
"@typescript-eslint/no-unused-vars": ["warn", { ignoreRestSiblings: true }], | |||
"@typescript-eslint/prefer-nullish-coalescing": "warn", | |||
"@typescript-eslint/no-duplicate-enum-values": "off", |
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 appeared to be getting a false positive here where this warning would throw during pre-commit hooks for the new enums, even though they don't seem to have duplicate values. Adding this to each file also did not work so I was a bit confused why it was getting thrown
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.
So disabling the rule here worked whereas disabling it in the file did not
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'd not disable this eslint rule. Better to fix the issues related to it. It can't be false positive, something can be related to mixed default and defined values.
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.
Didn't give this too much thought as wasn't sure about a lot of the values. Please let me know if you see anything that is definitely wrong
Thank you John! It is looking good, I will add some comments 😊 |
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.
Thank you @JohnGuilding I left some comments, try reading them from the "Conversation" tab so they are in order (my idea was to leave the main file /coordinator/coordinator.ts
to the end :)
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.
@JohnGuilding thanks, left some comments
.eslintrc.cjs
Outdated
@@ -18,6 +18,7 @@ module.exports = { | |||
"@typescript-eslint/no-explicit-any": "off", | |||
"@typescript-eslint/no-unused-vars": ["warn", { ignoreRestSiblings: true }], | |||
"@typescript-eslint/prefer-nullish-coalescing": "warn", | |||
"@typescript-eslint/no-duplicate-enum-values": "off", |
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'd not disable this eslint rule. Better to fix the issues related to it. It can't be false positive, something can be related to mixed default and defined values.
|
||
type GenerateResponse = z.infer<typeof GenerateResponseSchema>; | ||
type SubmitResponse = z.infer<typeof SubmitResponseSchema>; | ||
type CoordinatorServiceResult<T, E = Error> = { success: true; data: T } | { success: false; error: E }; |
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.
It should be an interface, not type
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.
z.infer
returns type not an interface. Might remove zod anyway as you can argue it is over-engineering
Do you guys have a style guide or anything that you refer to? Just curious to understand the patterns/preferences you follow so I know for the future
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 don't think we have a specific guideline but we should build one 😊 . Most of it is guided by:
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.
Instead of types we prefer using interfaces, it's just an agreement (not written somewhere yet).
So you can use it like this:
interface ICoordinatorServiceResult<T, E = Error> {
success: boolean;
data?: T;
error?: E;
}
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.
Sure, switched to using an interface.
Curious to get your thoughts on the following:
export type CoordinatorServiceResult<T, E = Error> = { success: true; data: T } | { success: false; error: E };
const usingDiscriminatedUnion: CoordinatorServiceResult<boolean> = {
success: true,
error: new Error("I'm an error"), // This does not compile
};
export interface ICoordinatorServiceResult<T, E = Error> {
success: boolean;
data?: T;
error?: E;
}
const usingInterface: ICoordinatorServiceResult<boolean> = {
success: true,
error: new Error("I'm an error"), // This compiles
};
The discriminated union type here provides a bit more flexibility and type safety in this example. For return types, I think this could be useful. Do you have a preference for this or would you rather stick with interfaces?
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.
Actually, for this case you can keep the type but move inline types to interfaces:
interface ISuccessResult<T> {
success: true;
data: T;
}
interface IErrorResult<E = Error> {
success: false;
error: E;
}
Or you can get rid of success
property and check if there is data
or error
let response: Response; | ||
try { | ||
response = await fetch(`${BASE_URL}/proof/merge`, { |
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.
Do not use try/catch like this. You can use it like fetch(...).catch((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.
Sure, done. Out of interest, why is there a preference for this way of doing things over try catch?
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.
It's just a more functional approach instead of procedural. With catch
method you don't need to have a mutation for response
and keep in const
, and it makes code cleaner because you can't return
the value from catch
method so you need to explicitly handle it outside which makes it more obvious when you read the code (not a good practice to have some logic handling in catch).
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.
you don't need to have a mutation for response and keep in const
I like this benefit
makes code cleaner because you can't return the value from catch method so you need to explicitly handle it outside which makes it more obvious when you read the code (not a good practice to have some logic handling in catch).
Ok yeah I see how this can be cleaner
Thanks for the explanation
export const merge = async ( | ||
maciContractAddress: Address, | ||
pollId: number, | ||
approval: string, | ||
sessionKeyAddress: Address | ||
): Promise<CoordinatorServiceResult<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.
Use one object instead of four separate params
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.
These args have changed now but have done this where multiple args still exist
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.
So this is the idea that if I want to add new argument, I just add a new property to the object, plus I don't need to worry about the order of the arguments. For instance first option looks better than second one:
merge({ maciContractAddress: "0x...", pollId: 0n, approval: "321...", sessionKeyAddress: "123..." });
merge("0x...", 0n, "321...", "123...");
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.
Got it. Yeah I agree named arguments are cleaner - thanks for pointing this out!
startBlock: Number(blockNumber) - 100, | ||
endBlock: Number(blockNumber) + 100, |
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 are these values hardcoded?
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 copied and pasted code from the coordinator service. Assume these are wrong, I guess I should consume them as arguments into the function?
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.
These values are used to generate a MACI state that would later be used to generate a proof. In our context startBlock
and endBlock
would be variables. startBlock
would be the proposal snapshot block and the end block will be the getFutureBlockNumberAtTimestamp(pollEndDate)
I suggest we send these values as arguments into the function generateProofs
😊
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.
Added as arguments!
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.
But why is 100
hardcoded?
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 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 think it should be at least moved as a const or to config (.env) and I don't know why this +/- 100 blocks are on tests.
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.
Would something like this work? Or would you want to move the 100
values themselves to config? Something else?
test("should generate proofs correctly", async () => {
const blockNumber = await publicClient.getBlockNumber();
const startBlock = Number(blockNumber) - 100;
const endBlock = Number(blockNumber) + 100;
const encryptedCoordinatorPrivateKey = await encryptWithCoordinatorRSAPublicKey(
coordinatorMACIKeypair.privateKey.serialize(),
);
const response = await fetch(`${TEST_URL}/proof/generate`, {
method: "POST",
headers: {
Authorization: encryptedHeader,
"Content-Type": "application/json",
},
body: JSON.stringify({
poll: Number(pollId),
maciContractAddress: maciAddress,
mode: testPollDeploymentConfig.mode,
encryptedCoordinatorPrivateKey,
startBlock,
endBlock,
blocksPerBatch: 20,
chain: CHAIN,
} as IGenerateArgs),
});
let response: Response; | ||
try { | ||
response = await fetch(`${BASE_URL}/proof/generate`, { |
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 here for try/catch
export const submit = async ( | ||
pollId: number, | ||
maciContractAddress: Address, | ||
approval: string, | ||
sessionKeyAddress: Address | ||
): Promise<CoordinatorServiceResult<SubmitResponse>> => { |
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 here for one object param
approval: string, | ||
sessionKeyAddress: Address | ||
): Promise<CoordinatorServiceResult<SubmitResponse>> => { | ||
const args: ISubmitProofsArgs = { |
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.
No need to specify type here
let response: Response; | ||
try { | ||
response = await fetch(`${BASE_URL}/proof/submit`, { |
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 here for try/catch
yeah good point, since I deleted the code based on Nico's comments I could remove this easily |
@NicoSerranoP @0xmad addressed your comments or left a follow up question. let me know if I missed anything! |
|
||
// TODO: move to env | ||
const BASE_URL = "http://localhost:3000/v1"; | ||
const MACI_CONTRACT_ADDRESS = "0x0000000000000000000000000000000000000000"; |
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.
Instead of MACI_CONTRACT_ADDRESS
lets use 🚀
import { PUB_MACI_ADDRESS } from "@/constants";
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.
Cheers done
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.
Please, use PUBLIC_MACI_ADDRESS
name instead
Add schemas.ts Add errors.ts Add final TODOs Note copied code
The component with the lint error did not even use props, so removed the interface
ac3194c
to
0d438d7
Compare
@@ -17,6 +17,9 @@ export const PUB_MACI_ADDRESS = (process.env.NEXT_PUBLIC_MACI_ADDRESS ?? "") as | |||
export const PUB_MACI_DEPLOYMENT_BLOCK = Number(process.env.NEXT_PUBLIC_MACI_DEPLOYMENT_BLOCK ?? 0); | |||
export const NEXT_PUBLIC_SECONDS_PER_BLOCK = Number(process.env.NEXT_PUBLIC_SECONDS_PER_BLOCK ?? 1); // ETH Mainnet block takes ~12s | |||
|
|||
// MACI Coordinator service | |||
export const PUB_COORDINATOR_SERVICE_URL = process.env.NEXT_PUBLIC_COORDINATOR_SERVICE_URL ?? ""; |
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.
export const PUB_COORDINATOR_SERVICE_URL = process.env.NEXT_PUBLIC_COORDINATOR_SERVICE_URL ?? ""; | |
export const PUBLIC_COORDINATOR_SERVICE_URL = process.env.NEXT_PUBLIC_COORDINATOR_SERVICE_URL ?? ""; |
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 do it for all the constants here
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.
sure, done
}).catch((error) => { | ||
throw error instanceof Error ? error : new Error(String(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.
Is there a case where you can have string error instead of Error
?
If no, you can just remove this catch (and other catches too).
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 not entirely sure but I agree that this could be redundant, removed catch blocks
const publicClient = usePublicClient(); | ||
const { signMessageAsync } = useSignMessage(); | ||
|
||
const getPublicKey = async (): Promise<KeyLike> => { |
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 use useCallback
here and add it to the deps for other useCallback
s
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.
Done
}: { | ||
pollId: number; | ||
encryptedCoordinatorPrivateKey: string; | ||
startBlock: number; | ||
endBlock: number; | ||
}): Promise<CoordinatorServiceResult<GenerateResponse>> => { |
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 move it to the interface
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.
Done
}: { | ||
pollId: number; | ||
encryptedCoordinatorPrivateKey: string; | ||
startBlock: number; | ||
endBlock: number; | ||
}) => Promise<CoordinatorServiceResult<GenerateResponse>>; |
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 move it to the interface and reuse
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.
Done
export type SubmitResponse = z.infer<typeof SubmitResponseSchema>; | ||
export type CoordinatorServiceResult<T, E = Error> = { success: true; data: T } | { success: false; error: E }; | ||
|
||
export interface CoordinatorContextType { |
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.
export interface CoordinatorContextType { | |
export interface ICoordinatorContextType { |
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.
Done
|
||
export interface IVoteArgs { | ||
voteOptionIndex: bigint; | ||
newVoteWeight: bigint; | ||
} | ||
|
||
export type GenerateResponse = z.infer<typeof GenerateResponseSchema>; |
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.
export type GenerateResponse = z.infer<typeof GenerateResponseSchema>; | |
export type TGenerateResponse = z.infer<typeof GenerateResponseSchema>; |
|
||
export interface IVoteArgs { | ||
voteOptionIndex: bigint; | ||
newVoteWeight: bigint; | ||
} | ||
|
||
export type GenerateResponse = z.infer<typeof GenerateResponseSchema>; | ||
export type SubmitResponse = z.infer<typeof SubmitResponseSchema>; |
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.
export type SubmitResponse = z.infer<typeof SubmitResponseSchema>; | |
export type TSubmitResponse = z.infer<typeof SubmitResponseSchema>; |
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.
Done
Replaced with #19 |
What does this PR do:
merge
generateProofs
submit
getSigner
,getAuthorizationHeader
Open questions
Other notes
I wanted to keep this PR as small as possible, so didn't make it an e2e PR that could be tested. My thinking was that I could at least solve authentication and add any remaining utilities in a follow up PR. That said, please feel free to suggest any improvements here or other suggestions for next steps.
There is a fair amount of copied code in this PR, the new logic is mainly in
coordinator.ts
. Spoke with Nico and we discussed that we would look to potentially export a lot of the logic that the coordinator uses into an sdk to that devs can just import those instead of copying them over which is what I'm doing now. We agreed that seemed out of scope for now, but let me know if you wanted me to do that first - only issue is that is would be time consuming given we want to get the demo out soon.