MinaFoundation / Core-Grants

21 stars 11 forks source link

RFC-0003: Asynchronous Circuits in `o1js` #4

Closed teddyjfpender closed 4 months ago

teddyjfpender commented 7 months ago

šŸš€ Introduction to the Innovation

Here is RFC-0003! It's about bringing asynchronous circuits to o1js. This enhancement is set to significantly improve zkApp design patterns and eliminate work-arounds for performing asynchronous operations with circuits.

šŸ”‘ Why This Change Is Crucial

Currently, o1js circuits are constrained by sequential execution and are unable to perform asynchronous operations, which limits their ability to interact with dynamic data sources (such as other circuits!). By adding asynchronous circuit capabilities, we can overcome these limitations, offering a boost to zkApp design flexibility.

šŸ” Exploring Practical Use Cases

There are a few detailed scenarios like dynamic data integration in smart contract circuits and real-time player attribute updates in games that touch on a small part of the large impact this feature can have across a variety of applications.

šŸ’­ Seeking Your Input

Your expertise, implementation designs, feedback, and creative use-case scenarios are invaluable in shaping a robust implementation.

šŸ“– For More Details

Please refer to the full RFC document for an in-depth understanding of the scope, potential impact, and the challenges addressed.

šŸ¤ Join the Discussion

This is an open invitation to all developers, users, and stakeholders within the Mina ecosystem to be a part of this exciting development. Let's work together to take the Mina ecosystem to new heights of innovation and functionality!

mitschabaude commented 5 months ago

Some notes on implementation:

An o1js circuit is given as a callback to Pickles, which gives it as a callback to Snarky at various steps of compiling/proving. In both Pickles and Snarky, the circuit is expected to finish synchronously. Therefore, the main lift is to adapt both of those libraries to allow async circuits.

We already had to allow dealing with JS Promises in other places of Pickles (like when it waits for a Kimchi proof to finish), therefore there is an OCaml library called Promise (code here) which implements Promises for both native OCaml and JS. The goal would be to make a circuit coming from o1js return a Promise.t, and thread that through Pickles.

A good starting point to enable async circuits is probably the checked_runner.ml module of snarky, particularly the type run which describes how a function 't is run to produce a value 'a, and in the process update snarky's internal state of type run_state:

https://github.com/o1-labs/snarky/blob/94b2df82129658d505b612806a5804bc192f13f0/src/base/checked_runner.ml#L433

(* checked_runner.ml *)
type ('a, 't) run = 't -> run_state -> run_state * 'a

In the new model, we instead want that type to return (run_state * 'a) Promise.t. An interesting exercise, also to get to know snarky, is to just change the type and then start fixing everything that breaks:

(* checked_runner.ml *)
- type ('a, 't) run = 't -> run_state -> run_state * 'a
+ type ('a, 't) run = 't -> run_state -> (run_state * 'a) Promise.t

To make this work, you need to add the promise library to snarky's dune file

 (libraries bitstring_lib core_kernel h_list interval_union snarky_intf
-  bignum.bigint)
+ promise bignum.bigint)

For dealing with promises, you could use monadic let syntax, like this:

(* example code using a `run` of type `run` *)
- let state, result =  run circuit state in
+ let%map.Promise state, result =  run circuit state in

which magically will make the function this is done in return a Promise.t.

However, instead of literally changing the run type and fixing everything that breaks, a smoother approach would be to introduce a sibling type, run_async, and progressively use that in more and more places:

(* checked_runner.ml *)
type ('a, 't) run = 't -> run_state -> run_state * 'a
+ type ('a, 't) run_async = 't -> run_state -> (run_state * 'a) Promise.t

This would be my preferred approach to start personally!

Matthew also suggested functoring over the run type entirely, so different consumers of snarky could make it be either async or sync. This would have the immense benefit that not all code in snarky using run has to be repeated in a run_async version.

mitschabaude commented 4 months ago

Implementation is under way! https://github.com/o1-labs/o1js/pull/1450