-
Notifications
You must be signed in to change notification settings - Fork 325
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
fix: on nullifier collision during insertion of revertibles from private, app logic should fail gracefully and proceed to teardown #12422
base: master
Are you sure you want to change the base?
Conversation
…ate, app logic should fail gracefully and proceed to tairdown
await expect( | ||
votingContract.methods.cast_vote(candidate).send({ skipPublicSimulation: true }).wait(), | ||
).rejects.toThrow('Reason: Tx dropped by P2P node.'); | ||
).rejects.toThrow(TxStatus.APP_LOGIC_REVERTED); | ||
}); |
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.
TxReceipt doesn't have an applogic-revert error message and it doesn't seem trivial to add it unfortunately.
const revertStart = process.hrtime.bigint(); | ||
// FIXME(#12375): TX shouldn't die if revertible insertions fail. Should just revert to snapshot. | ||
await this.insertRevertiblesFromPrivate(context); | ||
// add new contracts to the contracts db so that their functions may be found and called | ||
await this.worldStateDB.addNewRevertibleContracts(tx); | ||
const revertEnd = process.hrtime.bigint(); | ||
this.metrics.recordPrivateEffectsInsertion(Number(revertEnd - revertStart) / 1_000, 'revertible'); | ||
|
||
if (context.hasPhase(TxExecutionPhase.APP_LOGIC)) { | ||
const appLogicResult: ProcessedPhase = await this.simulateAppLogicPhase(context); | ||
processedPhases.push(appLogicResult); | ||
const success = await this.insertRevertiblesFromPrivate(context); | ||
if (success) { | ||
// add new contracts to the contracts db so that their functions may be found and called | ||
await this.worldStateDB.addNewRevertibleContracts(tx); | ||
const revertEnd = process.hrtime.bigint(); | ||
this.metrics.recordPrivateEffectsInsertion(Number(revertEnd - revertStart) / 1_000, 'revertible'); | ||
|
||
// Only proceed with app logic if there was no revert during revertible insertion | ||
if (context.hasPhase(TxExecutionPhase.APP_LOGIC)) { | ||
const appLogicResult: ProcessedPhase = await this.simulateAppLogicPhase(context); | ||
processedPhases.push(appLogicResult); | ||
} | ||
} else { | ||
this.log.debug(`Revertible insertions failed. Skipping app logic.`); | ||
} |
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.
Only proceed to app logic if inserting revertibles succeeds.
public async insertRevertiblesFromPrivate(context: PublicTxContext) { | ||
public async insertRevertiblesFromPrivate(context: PublicTxContext): /*success=*/ Promise<boolean> { | ||
// Fork the state manager so we can rollback to end of setup if app logic reverts. | ||
await context.state.fork(); | ||
const stateManager = context.state.getActiveStateManager(); | ||
try { | ||
await stateManager.writeSiloedNullifiersFromPrivate(context.revertibleAccumulatedDataFromPrivate.nullifiers); | ||
} catch (e) { | ||
if (e instanceof NullifierCollisionError) { | ||
// FIXME(#12375): simulator should be able to recover from this and just revert to snapshot. | ||
throw new NullifierCollisionError( | ||
`Nullifier collision encountered when inserting revertible nullifiers from private. Details:\n${e.message}\n.Stack:${e.stack}`, | ||
// Instead of throwing, revert the app_logic phase | ||
context.revert( | ||
TxExecutionPhase.APP_LOGIC, | ||
new SimulationError( | ||
`Nullifier collision encountered when inserting revertible nullifiers from private\nDetails: ${e.message}\nError stack: ${e.stack}`, | ||
[], | ||
), | ||
/*culprit=*/ 'insertRevertiblesFromPrivate', | ||
); | ||
return /*success=*/ false; | ||
} else { | ||
throw 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.
Only rethrow if the error is NOT the known nullifier collision. Otherwise just revert app logic with the error and return success=false.
}); | ||
|
||
// Mock the writeSiloedNullifiersFromPrivate method to throw a NullifierCollisionError | ||
(AvmPersistableStateManager.prototype as any).writeSiloedNullifiersFromPrivate = jest |
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.
Ugh, can't we mock it in a... saner way? 🙃
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 maybe it shouldn't be mocked. I could craft a real-ish test of this instead.
Issue with mocking this function here is that it isn't an arg to PublicTxSimulator but is crafted inside. (this is probably something that could be solved by better dependency injection?)
expect(txResult.processedPhases.length).toBe(2); | ||
expect(txResult.processedPhases[0].phase).toBe(TxExecutionPhase.SETUP); | ||
expect(txResult.processedPhases[1].phase).toBe(TxExecutionPhase.TEARDOWN); | ||
|
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 not expect(txResult.?processedPhases).toBe([TxExecutionPhase.SETUP, TxExecutionPhase.TEARDOWN])
? (or toequals)
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 I copied other tests, but I agree this is nicer!
No description provided.