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

Remove top-level await #1583

Merged
merged 20 commits into from
Apr 17, 2024
Merged

Remove top-level await #1583

merged 20 commits into from
Apr 17, 2024

Conversation

mitschabaude
Copy link
Member

@mitschabaude mitschabaude commented Apr 15, 2024

bindings: o1-labs/o1js-bindings#268

Details

Instead of awaiting bindings imports at the top-level, we hide them behind a new async function initializeBindings().

initializeBindings() does not have to be called by users of o1js, because this PR takes care to call it first wherever bindings are needed.

Almost all user-facing APIs which need to call initializeBindings() were already async -- making the remaining two async constitutes the two breaking changes of this PR:

  • LocalBlockchain()
  • Proof.fromJSON()

We also export initializeBindings() for explicit control, which can be useful to front-load the bindings import in web apps.

Loading time improvements

Because loading of the bindings is now deferred until it's needed, this comes with a great improvement for initial loading time of o1js. It will enable us to remove hacks to load o1js in a useEffect() or similar.

$ ./run-in-browser.js src/examples/benchmarks/import.web.ts

logs in the browser console:

import o1js: 29ms
initialize bindings: 318ms

@sam-goodwin
Copy link

Thanks for putting this together. I'm a little concerned that we haven't totally confirmed this as the root cause for next. I think there's also a bug in package.json and exports.

I wonder if we can add a test?

next build of a React server component will test the node path.

For client side (web) we'd have to test rendering an app. Is that what playwright is for?

@dfstio
Copy link

dfstio commented Apr 15, 2024

Removing top-level await is also very helpful for non-web environments. For example, it is a blocker for using Netlify functions or some typescript configurations.

@mitschabaude
Copy link
Member Author

mitschabaude commented Apr 16, 2024

Thanks for putting this together. I'm a little concerned that we haven't totally confirmed this as the root cause for next. I think there's also a bug in package.json and exports.

I agree that it might seem like the wrong order, but

  • we want to get rid of top level await anyway
  • we are currently very focused on delivering v1.0, the first stable version of o1js
  • which means we need to make all breaking changes now, asap
  • and also means I don't have time this week to debug the Next setup, which should be fixable without other breaking changes

I'll add the package.json tweak in this PR though!

@sam-goodwin
Copy link

No I think it's the right order. This is very important. My main point is that we should add a test for client and server side components in next. Full steam ahead on removing top level await! Thanks for getting that going.

@sam-goodwin
Copy link

But if you wanna follow that up in a later PR, that also makes sense.

Copy link
Member Author

@mitschabaude mitschabaude left a comment

Choose a reason for hiding this comment

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

most of the PR is fixing tests -- highlighted some of the actual changes

@@ -875,28 +875,11 @@ class AccountUpdate implements Types.AccountUpdate {
}

hash(): Field {
// these two ways of hashing are (and have to be) consistent / produce the same hash
Copy link
Member Author

Choose a reason for hiding this comment

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

finally had to remove this, because it would have meant account update hashing depends on the bindings

@@ -45,10 +45,11 @@ export { LocalBlockchain };
/**
* A mock Mina blockchain running locally and useful for testing.
*/
function LocalBlockchain({
async function LocalBlockchain({
Copy link
Member Author

Choose a reason for hiding this comment

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

LocalBlockchain -> async

Comment on lines +707 to 708
await initializeBindings();
let [, data, hash] = Pickles.dummyVerificationKey();
Copy link
Member Author

Choose a reason for hiding this comment

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

awaiting bindings in deploy(), to use Pickles

Copy link
Member Author

Choose a reason for hiding this comment

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

awaiting bindings in Circuit APIs

@@ -110,18 +110,21 @@ class Proof<Input, Output> {
proof: Pickles.proofToBase64([this.maxProofsVerified, this.proof]),
};
}
static fromJSON<S extends Subclass<typeof Proof>>(
static async fromJSON<S extends Subclass<typeof Proof>>(
Copy link
Member Author

Choose a reason for hiding this comment

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

Proof.fromJSON() becomes async because it needs Pickles and there's the chance people call this without calling compile() first

@@ -198,14 +198,13 @@ class Bool {
* This can only be called on non-witness values.
*/
toBoolean(): boolean {
Copy link
Member Author

Choose a reason for hiding this comment

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

I restructured this so that toBoolean() doesn't need Snarky to be defined when the input is constant.

When the input is a variable, it's fine to rely on Snarky because one of the circuit running methods must have been called first, and they are all guarded by initializeBindings()

Copy link
Member Author

Choose a reason for hiding this comment

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

we're awaiting bindings in every method that runs provable code, so that this code is able to use the Snarky import

@@ -33,6 +33,7 @@ type ProvableHashable<T> = Provable<T> & Hashable<T>;
class Sponge {
#sponge: unknown;

// TODO: implement constant version in TS. currently, you need to call `initializeBindings()` before successfully calling this
Copy link
Member Author

Choose a reason for hiding this comment

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

I left this as TODO. Poseidon.Sponge() cannot be used before initializing bindings first, which could lead to users experiencing errors. I think the function is rarely used though, and there's an easy workaround: call initializeBindings(). The eventual solution is to implement Sponge() in TS (should be similar to Poseidon.hash())

Copy link
Contributor

@ymekuria ymekuria Apr 16, 2024

Choose a reason for hiding this comment

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

Would it be easy/worthwhile to add an error message to call initializeBindings() until Sponge() is implemented in TS? It could be done in the future if so.

src/snarky.js Outdated
Copy link
Member Author

Choose a reason for hiding this comment

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

this is how the Node.js bindings work now

Copy link
Member Author

Choose a reason for hiding this comment

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

this is how the web bindings work now

Copy link
Contributor

@ymekuria ymekuria left a comment

Choose a reason for hiding this comment

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

I'm excited for theses changes! Nice work! I had some minor comments and questions.

CHANGELOG.md Outdated
@@ -28,19 +28,25 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
- Add assertion to the foreign EC addition gadget that prevents degenerate cases https://github.com/o1-labs/o1js/pull/1545
- Fixes soundness of ECDSA; slightly increases its constraints from ~28k to 29k
- Breaks circuits that used EC addition, like ECDSA
- `Mina.LocalBlockchain()` is made async https://github.com/o1-labs/o1js/pull/1583
Copy link
Contributor

@ymekuria ymekuria Apr 16, 2024

Choose a reason for hiding this comment

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

This will require updates in the docs as well as the CLI.

@@ -33,6 +33,7 @@ type ProvableHashable<T> = Provable<T> & Hashable<T>;
class Sponge {
#sponge: unknown;

// TODO: implement constant version in TS. currently, you need to call `initializeBindings()` before successfully calling this
Copy link
Contributor

@ymekuria ymekuria Apr 16, 2024

Choose a reason for hiding this comment

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

Would it be easy/worthwhile to add an error message to call initializeBindings() until Sponge() is implemented in TS? It could be done in the future if so.

@mitschabaude mitschabaude merged commit f74abb3 into main Apr 17, 2024
13 checks passed
@mitschabaude mitschabaude deleted the feature/no-top-level-await branch April 17, 2024 05:40
@sam-goodwin
Copy link

So glad these changes were merged. Is there a timeline for release to NPM?

@ymekuria
Copy link
Contributor

So glad these changes were merged. Is there a timeline for release to NPM?

@sam-goodwin There is a plan to release early next week. We will let you know if things change.

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.

Make every API that needs OCaml/Rust async instead of awaiting at the top level
5 participants