-
Couldn't load subscription status.
- Fork 2
fix opRequestGas #673
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
base: main
Are you sure you want to change the base?
fix opRequestGas #673
Conversation
🦋 Changeset detectedLatest commit: a11ba48 The changes in this PR will be included in the next version bump. This PR includes changesets to release 5 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
a9ade41 to
8c0a6e6
Compare
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.
couple comments, I may be me misunderstanding some parts but based on what I understood had some questions
packages/client/src/opRequestGas.ts
Outdated
| (acc, { value }) => acc + value, | ||
| 0n | ||
| ); | ||
| // console.log("initial notes for gas asset value", initialNotesForGasAssetValue) |
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.
stale comment
packages/client/src/opRequestGas.ts
Outdated
| const additionalCompEstimate = | ||
| compEstimate - initialNotesForGasAssetValue; |
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.
Doesn't initialNotesForGasAssetValue include gas asset amount that is being unwrapped for the actual action (i.e. needs to be preserved for action itself)? Shouldn't we only be including gas token value that is not yet being unwrapped (i.e. would have been joinsplit output note but instead we'll unwrap for gas comp)?
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.
yes, that's why it's being subtracted.
packages/client/src/opRequestGas.ts
Outdated
| const extraNotes = await gatherNotes( | ||
| const additionalCompEstimate = | ||
| compEstimate - initialNotesForGasAssetValue; | ||
| if (currentGasNotesValue >= additionalCompEstimate) { |
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.
currentGasNotesValue is always guaranteed to be 0 first iteration (because its set to 0n on L222), this is expected?
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.
yes. if compEstimate - initialNotesForGasAssetValue is negative, then the unwrapped notes already have enough to cover gas and we don't have to do anything. I should probably make this more clear in the comment above and/or add additional comments explaining it inline.
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 good catch, this isn't quite right, it should be the difference between the total initial note value and total initial JS request value
packages/client/src/opRequestGas.ts
Outdated
| usedNotes.set(gasAsset, [ | ||
| ...(initialNotesForGasAsset ?? []), | ||
| ...newGasNotes, | ||
| ]); | ||
| const params = { | ||
| executionGasLimit, | ||
| numJoinSplits: computeNumJoinSplits(usedNotes), | ||
| numUniqueAssets: computeNumUniqueAssets(usedNotes, initialRefundAssets), | ||
| }; | ||
|
|
||
| currentGasEstimate = gasCompensationForParams(params); | ||
| if (initialNotesForGasAsset !== undefined) { | ||
| usedNotes.set(gasAsset, initialNotesForGasAsset); | ||
| } else { | ||
| usedNotes.delete(gasAsset); | ||
| } |
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.
not following the code in the below highlighted section. If there were initially used notes in the right gas asset, why do we need to re-set them in the usedNotes map on L285? Didn't we just set usedNotes gas asset to include any new gas notes on L273?
And for the else block where we delete gas asset from usedNotes, what if we needed some of them for the action itself (not for gas)? I understand that we'd delete if we're not using them at all given there's not enough for gas asset but not sure that's guaranteed
currentGasEstimate = gasCompensationForParams(params);
if (initialNotesForGasAsset !== undefined) {
usedNotes.set(gasAsset, initialNotesForGasAsset);
} else {
usedNotes.delete(gasAsset);
}
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 re-set them because we destructively modified usedNotes. We do this so that we can figure out exactly which notes are going to be used, compute the number of JSs and refunds required, compute a new estimate, and, during the next iteration of the loop, check if we have enough, trying again (from scratch, but with a larger comp estimate) if we don't. We have to re-set them because the next iteration could destructively reset them.
What matters for the output of this function is that the modified or additional joinsplit request amount has enough to cover the additional comp.
usedNotes is actually kind of a hack. It's there because opRequestGas takes in JS requests and outputs JS requests, but JS requests aren't necessarily 1-1 with notes. The proper way to "fix" this requires getting rid of this "stage" of the op preparation pipeline entirely and merging it into prepareOperation, but that's a more disruptive of a code change than I am comfortable making at the moment.
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.
Ok so basically the 2nd half of the loop (the half where both possible return statements have been passed) is meant to yield new params for num notes and gas est so the upper half (with the 2 return paths) are invoked next iteration
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.
correct. it's pretty fucked, I tried to refactor it but it kept breaking kek.
| await this.client.pruneOptimisticNullifiers(); | ||
| setTimeout(pruneOptimsiticNullifiers, opIntervalSeconds); | ||
| }; | ||
| void pruneOptimsiticNullifiers(); |
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.
Typo: Optimistic
| const pruneOptimsiticNullifiers = async () => { | ||
| await this.client.pruneOptimisticNullifiers(); | ||
| setTimeout(pruneOptimsiticNullifiers, opIntervalSeconds); | ||
| }; | ||
| void pruneOptimsiticNullifiers(); |
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.
Doesn't NocturneClient already do this?
https://github.com/nocturne-xyz/monorepo/blob/main/packages/client/src/NocturneClient.ts#L90-L94
835e158 to
23678e5
Compare

Motivation
opRequestGas returning wrong amount sometime when multiple joinsplits are needed
Solution
rewrite it with a different algorithm that iteratively checks the cost and adds joinsplits if more is needed.
Proof
https://www.loom.com/share/ec05313a3fd34e50a8040ee8f4836355?sid=ff0ab067-8503-4dbf-aa0d-2bc9d46018c8
PR Checklist