Skip to content
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/consensus call #70

Open
wants to merge 25 commits into
base: development
Choose a base branch
from

Conversation

Keyrxng
Copy link
Contributor

@Keyrxng Keyrxng commented Feb 25, 2025

Resolves #60

  • New Method:
  public async consensusCall<TMethodReturnData = unknown>(requestPayload: RequestPayload, quorumThreshold: `0.${number}`): Promise<TMethodReturnData> {
  • added test case
  • manually qa'd

QA:

image

@Keyrxng
Copy link
Contributor Author

Keyrxng commented Feb 25, 2025

@rndquu @EresDev Let me know what you guys think of this whenever you can cheers.

Through my manual QA, it ofc does not reach a consensus 100% of the time (which is good?). But at first glance in-sync:12, validated:9 looks like a better approach than just #62 alone.

Although if we consider the failure rate + the fact this is a read-op-only method I'm not sure about implementing it as a core way of validating providers and it's probably better to stay a separate method in it's own right.

I've only read about 100 pages so far Eres, give me a week to read it a couple times and I'll go heavy on the refactoring in a dedicated PR. Great recommendation, ty.

Copy link
Contributor

Unlisted binaries (1)

Filename binaries
package.json x

@Keyrxng
Copy link
Contributor Author

Keyrxng commented Feb 26, 2025

Knip CI failing out of my control and I can't re-run it

image

@0x4007
Copy link
Member

0x4007 commented Feb 26, 2025

Knip CI failing out of my control and I can't re-run it

image

I suppose we can try using bun instead of yarn. https://github.com/0x4007/travel-stipend/blob/f5bb2faa1b873bb1ca40ba04b97ba468b0d2cc97/.github/workflows/knip.yml

Copy link
Member

@rndquu rndquu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Keyrxng Pls update readme with the new method

@rndquu rndquu requested a review from EresDev February 28, 2025 11:12
@rndquu rndquu self-requested a review February 28, 2025 16:08
Copy link
Member

@rndquu rndquu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Pls add a unit test for the consensusCall() method when it fails to reach the quorum
  2. Pls update script-test-consensus.ts and add an example of using it with eth_getTransactionReceipt

if (val instanceof Error) {
val = val.message;
} else {
val = val.hash;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some of the RPC methods (like eth_getTransactionReceipt) don't have the hash property in the response so I don't fully understand the logic

Copy link
Contributor Author

@Keyrxng Keyrxng Feb 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I debated this myself a little.

I went with JSON.Stringify at first and then return returning JSON.parse but some response data is enormous so I went with hash instead thinking it was more universal than what you've pointed it out to be.

Other than JSON.Stringify I'm not sure how we can match responses with certainty across all methods/request results.

Copy link
Contributor Author

@Keyrxng Keyrxng Feb 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what about .hash ?? .transactionHash I feel that might be better but still a lot of methods would be excluded.

Perhaps consensusCall takes a a 3rd param which is the prop of the return data that we need to compare and then allow the user to tell us?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've implemented .hash ?? .transactionHash for now as that covers eth_getTransactionReceipt at least.

A 3rd param sounds scalable across all eth_ methods, failing that, stringifying the results might be the best bet.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

JSON.Stringify sounds good, we need a generalized solution that covers most of RPC methods, not just 2

Comment on lines +162 to +174
public async getFirstAvailableRpcProvider() {
const rpcList = [...networkRpcs[this._networkId].rpcs].filter((rpc) => rpc.url.includes("https"));
shuffleArray(rpcList);
const rpcPromises: Record<string, Promise<PromiseResult>[]> = {};

for (const rpc of rpcList) {
this._rpcService.createBlockRequestAndByteCodeRacePromises(this._runtimeRpcs, rpcPromises, this._rpcTimeout);
const results = await Promise.allSettled(rpcPromises[rpc.url] ?? []);
const hasPassedAllChecks = results.every((res) => res && res.status === "fulfilled" && res.value.success);
if (hasPassedAllChecks) {
return new JsonRpcProvider({ url: rpc.url, skipFetchSetup: true }, Number(this._networkId));
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

when this function runs for me in the unit tests, this._runtimeRpcs is always empty. This is major dependency of createBlockRequestAndByteCodeRacePromises. Because it gets empty array where it is supposed to work on, it just doesn't do anything here. Is this._runtimeRpcs also always empty for you? then remove redundant code here.

@@ -244,4 +227,47 @@ export class RPCService {
return false;
}
}

createBlockRequestAndByteCodeRacePromises(runtimeRpcs: string[], rpcPromises: Record<string, Promise<PromiseResult>[]>, rpcTimeout: number) {
Copy link
Contributor

@EresDev EresDev Feb 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We discussed this in the previous PR. Do not change objects passed in arguments. rpcPromises is being passed here, just to get filled up inside this function.

Results produced by a function should be returned, and not fill up in one of its arguments.

Also, break this down into two functions. If you will do so, name of this function will improve, and you will not have to add such checks.

 if (result.value.rpcMethod === "eth_getBlockByNumber") {
} else if (result.value.rpcMethod === "eth_getCode"

return {
rpcUrl,
duration: performance.now() - startTime,
success: true,
success: "result" in (res?.data ?? {}) ? true : false,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe change to

success: !!res?.data?.result

But there is one difference, see if it works. But this line should be simplified even if my suggestion doesn't work.

const getBlockNumberPayload = JSON.stringify({
// this is similar to `ValidBlockData`, I didn't want to change it incase it's in other projects
type JsonRpcResponse = { jsonrpc: string; id: number; result: string | { number: string; timestamp: string; hash?: string; transactionHash?: string } };
export type PromiseResult<T extends JsonRpcResponse = JsonRpcResponse> = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PromiseResult Naming needs improvement. You can use AI to suggest you better variable, types, and function names. See

const rpcList = [...networkRpcs[this._networkId].rpcs];
shuffleArray(rpcList);
for (const rpc of rpcList) {
public async consensusCall<TMethodReturnData = unknown>(requestPayload: RequestPayload, quorumThreshold: `0.${number}`): Promise<TMethodReturnData> {
Copy link
Contributor

@EresDev EresDev Feb 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I say let's add new class Security{} and add this method consensusCall to it. Later, we will keep adding more security related stuff to this class.

RpcHandler is already a god object for me. @rndquu what do you think?

Comment on lines +15 to +30
export const testConfig: HandlerConstructorConfig = {
networkId: "100",
autoStorage: false,
cacheRefreshCycles: 3,
networkName: null,
rpcTimeout: 10000,
runtimeRpcs: rpcUrls,
networkRpcs: rpcUrls.map((url) => ({ url })),
proxySettings: {
retryCount: 3,
retryDelay: 10,
logTier: "verbose",
logger: new PrettyLogs(),
strictLogs: true,
},
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this exact object is present in 3 files. Reduce duplication. Extract it to somewhere like tests/shared.ts and reuse it from here.

@@ -67,6 +69,7 @@
"jest-md-dashboard": "0.8.0",
"knip": "5.27.0",
"lint-staged": "^15.2.2",
"nock": "13.5.6",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nock for http mocking here? We are already using msw in text-conversation-rewards plugin and pay.ubq.fi.

Comment on lines +52 to +74
nock(rpcUrls[0])
.post("/")
.reply(200, {
jsonrpc: "2.0",
result: { number: "0x1b4", hash: "0x1b4" },
id: 1,
});

nock(rpcUrls[1])
.post("/")
.reply(200, {
jsonrpc: "2.0",
result: { number: "0x1b4", hash: "0x1b4" },
id: 1,
});

nock(rpcUrls[2])
.post("/")
.reply(200, {
jsonrpc: "2.0",
result: { number: "0x1b4", hash: "0x1b4" },
id: 1,
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

loop to reduce duplication


/**
* A test script to ensure that the module can be imported and used correctly
* This script is not meant to be run in the test suite
Copy link
Contributor

@EresDev EresDev Feb 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

actually this is test and all your script-***.ts files. These are e2e tests. These are very useful. Move them to tests/e2e folder. At the end of each test, instead of console.log, use expect().

},
};

const blockConsensusResponse = await handler.consensusCall(blockByNumberPayload, "0.5");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

block number consensus test will not be deterministic. So, instead of block number in e2e test, maybe you can read token symbol of UBQ.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Improve trust ratio of rpcs served
4 participants