Skip to content

Make zeko circuits compilable to js#394

Open
MartinOndejka wants to merge 6 commits into
compatiblefrom
zeko-to-js
Open

Make zeko circuits compilable to js#394
MartinOndejka wants to merge 6 commits into
compatiblefrom
zeko-to-js

Conversation

@MartinOndejka

Copy link
Copy Markdown
Collaborator

No description provided.

@gemini-code-assist

Copy link
Copy Markdown

Summary of Changes

Hello @MartinOndejka, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request undertakes a significant refactoring effort to make Zeko circuits compilable to JavaScript. The changes primarily involve migrating core library dependencies from Core and Async to their _kernel counterparts, abstracting key-value database interactions for JS compatibility, and systematically removing or replacing Unix-specific functionalities. These modifications are essential for enabling the Mina protocol's core components to function correctly within a web-based environment.

Highlights

  • Core and Async Library Migration: Migrated numerous modules from Core to Core_kernel and Async to Async_kernel to enhance JavaScript compatibility.
  • Key-Value Database Abstraction for JS: Introduced a new mina_ledger_kvdb library with a JavaScript-compatible in-memory key-value store implementation, replacing direct Rocksdb usage in indexed_merkle_tree and mina_ledger.
  • UUID Generation Update: Replaced Uuid_unix.create with Uuid.create_random Random.State.default across several files to remove Unix dependencies.
  • Tracing and Threading Refinements: Removed O1trace library dependencies and related tracing calls, and updated Async.Thread_safe.block_on_async_exn to Run_in_thread.block_on_async_exn for a more controlled threading model.
  • Trust System Simplification: Simplified the Syncable_ledger's interaction with the Trust_system by removing the trust_system parameter from key functions and replacing complex trust actions with simpler error handling or no-op deferred units.
  • Unix Dependency Reduction: Replaced Unix.Inet_addr with Caml_unix.inet_addr for IP address handling and introduced a local rmrf function to avoid File_system dependency, further reducing reliance on Unix specific features.
Changelog
  • src/app/zeko/indexed_merkle_tree/indexed_merkle_tree.ml
    • Updated Core to Core_kernel.
    • Replaced Rocksdb.Database with Mina_ledger_kvdb.Database.
  • src/lib/concurrency/pipe_lib/dune
    • Removed core and o1trace library dependencies.
  • src/lib/concurrency/pipe_lib/swappable_strict_pipe.ml
    • Removed O1trace.background_thread call and replaced it with don't_wait_for.
  • src/lib/consensus/constants.ml
    • Replaced Core.Time.Span with Time.Span.
  • src/lib/consensus/dune
    • Removed async_unix, core.uuid, async, core, perf_histograms, file_system, internal_tracing, and o1trace library dependencies.
    • Added run_in_thread library dependency.
  • src/lib/consensus/epoch.ml
    • Updated Core to Core_kernel.
  • src/lib/consensus/global_slot.ml
    • Updated Core to Core_kernel.
  • src/lib/consensus/intf.ml
    • Updated Async to Async_kernel.
  • src/lib/consensus/proof_of_stake.ml
    • Added local gen_int_incl and rmrf functions.
    • Replaced O1trace.sync_thread calls with direct function calls.
    • Replaced File_system.rmrf with local rmrf.
    • Replaced Uuid_unix.create with Uuid.create_random Random.State.default.
    • Replaced Core.Int.gen_incl with local gen_int_incl.
    • Replaced Async.Thread_safe.block_on_async_exn with Run_in_thread.block_on_async_exn.
    • Removed O1trace.thread call.
  • src/lib/consensus/proof_of_stake_fuzzer.ml
    • Replaced Core.Printf.printf with Printf.printf.
  • src/lib/consensus/slot.ml
    • Added local gen_int_incl function.
    • Replaced Core.Int.gen_incl with local gen_int_incl.
  • src/lib/consensus/stake_proof.ml
    • Updated Core to Core_kernel.
  • src/lib/genesis_ledger/dune
    • Updated core to core_kernel library dependency.
  • src/lib/merkle_ledger/database.ml
    • Added Async = Async_kernel module alias.
    • Removed open Core and Unix dependencies.
    • Replaced Uuid_unix.create with Uuid.create_random Random.State.default.
    • Replaced Cache_dir.autogen_path ^/ Uuid.to_string uuid with Filename.concat Cache_dir.autogen_path (Uuid.to_string uuid).
  • src/lib/merkle_ledger/dune
    • Removed async, async_unix, core, core.uuid, and rocks library dependencies.
  • src/lib/merkle_ledger/graphviz.ml
    • Updated Async to Async_kernel.
    • Replaced Writer.save with Stdio.Out_channel.write_all and Deferred.unit.
  • src/lib/merkle_ledger/intf.ml
    • Added Async = Async_kernel module alias.
  • src/lib/merkle_ledger/null_ledger.ml
    • Added Async = Async_kernel module alias.
    • Replaced Uuid_unix.create with Uuid.create_random Random.State.default.
  • src/lib/merkle_mask/dune
    • Removed async, core, and core.uuid library dependencies.
  • src/lib/merkle_mask/maskable_merkle_tree.ml
    • Updated Core to Core_kernel.
  • src/lib/merkle_mask/masking_merkle_tree.ml
    • Updated Core to Core_kernel.
    • Added Async = Async_kernel module alias.
    • Replaced Uuid_unix.create with Uuid.create_random Random.State.default.
  • src/lib/mina_ledger/dune
    • Removed rocks, core, async, zkapp_command_builder, o1trace, and rocksdb library dependencies.
    • Added mina_ledger.kvdb library dependency.
  • src/lib/mina_ledger/ledger.ml
    • Updated Core to Core_kernel.
    • Added Async = Async_kernel module alias.
    • Replaced Rocksdb.Database with Mina_ledger_kvdb.Database.
    • Replaced the tokens test unit test with a no-op.
  • src/lib/mina_ledger/ledger.mli
    • Updated Core to Core_kernel.
  • src/lib/mina_ledger_kvdb/dune
    • Added new dune file for the mina_ledger_kvdb library.
  • src/lib/mina_ledger_kvdb/js/dune
    • Added new dune file for the JavaScript implementation of mina_ledger_kvdb.
  • src/lib/mina_ledger_kvdb/js/mina_ledger_kvdb.ml
    • Added new OCaml file implementing an in-memory key-value database for JavaScript compatibility.
  • src/lib/mina_ledger_kvdb/mina_ledger_kvdb.mli
    • Added new OCaml interface file for mina_ledger_kvdb.
  • src/lib/mina_ledger_kvdb/native/dune
    • Added new dune file for the native implementation of mina_ledger_kvdb.
  • src/lib/mina_ledger_kvdb/native/mina_ledger_kvdb.ml
    • Added new OCaml file implementing mina_ledger_kvdb using Rocksdb for native environments.
  • src/lib/mina_state/dune
    • Updated core to core_kernel library dependency.
  • src/lib/mina_state/registers.ml
    • Updated Core to Core_kernel.
  • src/lib/mina_state/snarked_ledger_state.ml
    • Updated Core to Core_kernel.
  • src/lib/mina_state/snarked_ledger_state_intf.ml
    • Updated Core to Core_kernel.
  • src/lib/network_peer/dune
    • Removed core, async, async.async_rpc, async_rpc_kernel, and async_kernel library dependencies.
    • Added core_kernel.caml_unix library dependency.
  • src/lib/network_peer/envelope.ml
    • Updated Core to Core_kernel.
    • Replaced Unix.Inet_addr.of_string with Caml_unix.inet_addr_of_string.
    • Manually implemented sexp_of_t and t_of_sexp for Incoming to handle Time.t.
  • src/lib/network_peer/envelope.mli
    • Updated Core to Core_kernel.
    • Added explicit sexp_of_t and t_of_sexp declarations for Incoming.
  • src/lib/network_peer/peer.ml
    • Updated Core to Core_kernel.
    • Replaced Unix.Inet_addr.t with Caml_unix.inet_addr and updated related functions.
    • Manually implemented sexp_of_t, t_of_sexp, compare, and hash for Inet_addr.Stable.V1.t.
  • src/lib/o1js_stub/dune
    • Added compile_simple_real and zeko_circuits library dependencies.
    • Added mina_ledger.kvdb.js library dependency.
  • src/lib/staged_ledger_diff/diff.ml
    • Added Async = Async_kernel module alias.
  • src/lib/staged_ledger_diff/diff.mli
    • Added Async = Async_kernel module alias.
  • src/lib/staged_ledger_diff/dune
    • Removed async and async_unix library dependencies.
  • src/lib/syncable_ledger/dune
    • Removed core, async, and trust_system library dependencies.
  • src/lib/syncable_ledger/syncable_ledger.ml
    • Changed trust_system parameter type from Trust_system.t to a polymorphic type 'trust and ignored it in create.
    • Removed trust_system field from t record.
    • Simplified error handling in answer_query by returning direct error strings instead of Trust_system.Actions.
    • Replaced record_envelope_sender calls with Deferred.unit.
  • src/lib/transaction_protocol_state/dune
    • Removed core library dependency.
  • src/lib/transaction_snark/dune
    • Removed async, async_unix, core, coda_genesis_ledger, consensus, merkle_ledger, and o1trace library dependencies.
  • src/lib/transaction_snark/transaction_snark.ml
    • Updated Core to Core_kernel.
    • Added Async = Async_kernel module alias.
    • Replaced Async.Thread_safe.block_on_async_exn with Run_in_thread.block_on_async_exn.
  • src/lib/transaction_snark/transaction_snark_intf.ml
    • Added Async = Async_kernel module alias.
    • Updated Core to Core_kernel.
  • src/lib/transaction_snark/transaction_validator.ml
    • Removed O1trace.sync_thread calls.
    • Updated Core to Core_kernel.
  • src/lib/transaction_snark_work/dune
    • Removed core library dependency.
  • src/lib/transaction_witness/dune
    • Removed core library dependency.
  • src/lib/trust_system/dune
    • Removed core, async, async_unix, and rocksdb library dependencies.
  • src/lib/trust_system/peer_trust.ml
    • Updated Core to Core_kernel.
    • Updated Peer_id.ip type from Unix.Inet_addr.Blocking_sexp.t to Network_peer.Peer.Inet_addr.t.
    • Simplified Time.of_string_gen to Time.of_string.
    • Updated Unix.Inet_addr.equal to Network_peer.Peer.Inet_addr.compare.
    • Replaced Async_unix__Thread_safe.block_on_async_exn with Run_in_thread.block_on_async_exn.
    • Replaced Unix.Inet_addr.of_string with Caml_unix.inet_addr_of_string.
    • Updated [%test_eq: Time.t] to [%test_eq: string] for time comparison.
    • Replaced Rocksdb.Serializable.Make with Key_value_database.Make_mock and a custom Db module.
    • Replaced File_system.remove_dir with Deferred.unit.
  • src/lib/trust_system/peer_trust.mli
    • Updated Core to Core_kernel.
    • Updated Unix.Inet_addr.Blocking_sexp.t to Network_peer.Peer.Inet_addr.t.
  • src/lib/trust_system/record.ml
    • Updated Core to Core_kernel.
    • Updated trust_last_updated and banned_until_opt types to use Time.t[@version_asserted].
  • src/lib/trust_system/record.mli
    • Added open Core_kernel.
    • Updated Now.now function signature to return Time.t.
  • src/lib/trust_system/trust_system.ml
    • Updated Core to Core_kernel.
  • src/lib/vrf_lib/dune
    • Removed core library dependency.
  • src/lib/vrf_lib/standalone.ml
    • Updated Core to Core_kernel.
Activity
  • The pull request description is empty, indicating that the intent and context are to be derived from the code changes and title.
  • The author, MartinOndejka, has made extensive changes across many files, primarily focusing on library migrations and dependency reductions for JavaScript compatibility.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request refactors the codebase to improve JavaScript compatibility by replacing Core with Core_kernel, Async with Async_kernel, and Uuid_unix with Uuid.create_random. It also replaces Rocksdb.Database with Mina_ledger_kvdb.Database for key-value storage. The rmrf function, which uses Caml.Sys.command, is flagged as problematic for JS compilation and should be abstracted. The of_yojson implementations for Inet_addr and Peer modules are vulnerable to DoS attacks due to uncaught exceptions when parsing malformed IP addresses. The t_of_sexp function in Incoming module is also vulnerable to DoS attacks due to uncaught exceptions when parsing malformed time formats. The zeko_prev_key function's performance is a concern due to sorting the entire database table on every invocation. The logic for measuring ledger sync duration has been removed, resulting in a loss of observability. Finally, the tokens test case has been removed, decreasing test coverage.

Comment on lines +51 to +55
let rmrf path =
ignore
(Caml.Sys.command
(sprintf "rm -rf %s" (Filename.quote path))
: int )

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

critical

The rmrf function uses Caml.Sys.command, which is not available in JavaScript and will cause a runtime error. Given that the goal of this PR is to make circuits JS-compilable, this is a critical issue. This functionality should be abstracted away, perhaps into a new virtual module similar to Mina_ledger_kvdb, with a no-op implementation for JavaScript and the current implementation for native builds.

Comment on lines 47 to 51
let of_yojson = function
| `String s ->
Ok (Unix.Inet_addr.of_string s)
Ok (Caml_unix.inet_addr_of_string s)
| _ ->
Error "expected string"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

security-high high

The of_yojson implementation for Inet_addr calls Caml_unix.inet_addr_of_string directly on the input string. This function raises a Failure exception if the string is not a valid IP address. Since this exception is not caught, providing a malformed IP address in a JSON payload (e.g., from a network peer or a configuration file) will cause the entire application to crash, leading to a Denial of Service (DoS).

      let of_yojson = function
        | `String s ->
            (try Ok (Caml_unix.inet_addr_of_string s)
             with _ -> Error "invalid IP address")
        | _ ->
            Error "expected string"

List.Assoc.find ls "libp2p_port" ~equal:String.equal >>= lift_int
in
let host = Unix.Inet_addr.of_string host_str in
let host = Caml_unix.inet_addr_of_string host_str in

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

security-high high

Similar to the Inet_addr module, the of_yojson implementation for the Peer module calls Caml_unix.inet_addr_of_string on host_str without handling potential exceptions. A malformed 'host' field in the JSON will crash the application.

             let host = try Caml_unix.inet_addr_of_string host_str with _ -> failwith "invalid host" in

Comment on lines +85 to +97
let t_of_sexp a_of_sexp = function
| Sexp.List [ data; sender; received_at ] ->
{ data = a_of_sexp data
; sender = Sender.t_of_sexp sender
; received_at =
( match received_at with
| Sexp.Atom s ->
Time.of_string s
| _ ->
failwith "Incoming.t_of_sexp: received_at must be atom" )
}
| _ ->
failwith "Incoming.t_of_sexp: expected 3-tuple list"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

security-medium medium

The t_of_sexp function calls Time.of_string on an atom from the Sexp input. Time.of_string raises an exception if the string is not a valid time format. If this function is used to deserialize data from an untrusted source, it can be used to trigger a crash (DoS).

Comment on lines +74 to +92
let zeko_prev_key t ~key =
let rec loop prev = function
| [] -> (
match prev with
| Some k ->
k
| None ->
failwith "zeko_prev_key: no previous key found" )
| (k, _) :: rest ->
if Bigstring.compare k key >= 0 then (
match prev with
| Some k ->
k
| None ->
failwith "zeko_prev_key: no previous key found" )
else loop (Some k) rest
in
loop None (to_alist t)
end

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

security-medium medium

The zeko_prev_key function calls to_alist, which performs a full sort of the entire database table (List.sort) on every invocation. In the context of an indexed Merkle tree, this function is called frequently during insertions and lookups. As the number of entries (N) in the ledger grows, the $O(N \log N)$ complexity of sorting will lead to severe performance degradation and potential Denial of Service, especially in a JavaScript environment.

Comment on lines 2730 to +2758
match requested_syncs with
| One required_sync ->
let open Async.Deferred.Let_syntax in
let start = Core.Time.now () in
let open Deferred.Let_syntax in
let%map result = sync required_sync in
let { snapshot_id; _ } = required_sync in
( match snapshot_id with
| Staking_epoch_snapshot ->
Mina_metrics.(
Counter.inc Bootstrap.staking_epoch_ledger_sync_ms
Core.Time.(diff (now ()) start |> Span.to_ms))
Counter.inc Bootstrap.staking_epoch_ledger_sync_ms 0.0)
| Next_epoch_snapshot ->
Mina_metrics.(
Counter.inc Bootstrap.next_epoch_ledger_sync_ms
Core.Time.(diff (now ()) start |> Span.to_ms)) ) ;
Counter.inc Bootstrap.next_epoch_ledger_sync_ms 0.0) ) ;
result
| Both { staking; next } ->
(*Sync staking ledger before syncing the next ledger*)
let open Deferred.Or_error.Let_syntax in
let start = Core.Time.now () in
let%bind () =
sync
{ snapshot_id = Staking_epoch_snapshot
; expected_root = staking
}
in
Mina_metrics.(
Counter.inc Bootstrap.staking_epoch_ledger_sync_ms
Core.Time.(diff (now ()) start |> Span.to_ms)) ;
let start = Core.Time.now () in
Counter.inc Bootstrap.staking_epoch_ledger_sync_ms 0.0) ;
let%map () =
sync { snapshot_id = Next_epoch_snapshot; expected_root = next }
in
Mina_metrics.(
Counter.inc Bootstrap.next_epoch_ledger_sync_ms
Core.Time.(diff (now ()) start |> Span.to_ms)) )
Counter.inc Bootstrap.next_epoch_ledger_sync_ms 0.0) )

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The logic for measuring ledger sync duration has been removed, and the corresponding metrics are now always reported as 0.0. This results in a loss of observability. Since Core_kernel.Time is available, the timing functionality can be restored in a JS-compatible way.

          match requested_syncs with
          | One required_sync ->
              let open Deferred.Let_syntax in
              let start = Time.now () in
              let%map result = sync required_sync in
              let { snapshot_id; _ } = required_sync in
              ( match snapshot_id with
              | Staking_epoch_snapshot ->
                  Mina_metrics.(
                    Counter.inc Bootstrap.staking_epoch_ledger_sync_ms
                      Time.(diff (now ()) start |> Span.to_ms))
              | Next_epoch_snapshot ->
                  Mina_metrics.(
                    Counter.inc Bootstrap.next_epoch_ledger_sync_ms
                      Time.(diff (now ()) start |> Span.to_ms)) ) ;
              result
          | Both { staking; next } ->
              (*Sync staking ledger before syncing the next ledger*)
              let open Deferred.Or_error.Let_syntax in
              let start = Time.now () in
              let%bind () =
                sync
                  { snapshot_id = Staking_epoch_snapshot
                  ; expected_root = staking
                  }
              in
              Mina_metrics.(
                Counter.inc Bootstrap.staking_epoch_ledger_sync_ms
                  Time.(diff (now ()) start |> Span.to_ms)) ;
              let start = Time.now () in
              let%map () =
                sync { snapshot_id = Next_epoch_snapshot; expected_root = next }
              in
              Mina_metrics.(
                Counter.inc Bootstrap.next_epoch_ledger_sync_ms
                  Time.(diff (now ()) start |> Span.to_ms)) )

[| keypair_and_amounts.(0); keypair_and_amounts.(1) |]
ledger ;
main ledger )
let%test_unit "tokens test" = ()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The test case "tokens test" has been removed, which results in a loss of test coverage for token functionality with zkApps. This was likely done to remove the Zkapp_command_builder dependency for JS compatibility. Please consider refactoring the test to work without this dependency or add a TODO comment to restore it in the future.

Comment on lines +478 to +479
{ staking = Uuid.create_random Random.State.default
; next = Uuid.create_random Random.State.default

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Bug: The switch to Uuid.create_random Random.State.default for UUID generation is not thread-safe and can cause collisions in concurrent environments, leading to data corruption.
Severity: CRITICAL

Suggested Fix

Revert the use of Uuid.create_random Random.State.default in multi-threaded contexts. If Uuid_unix cannot be used due to JS-compatibility, a thread-safe random number generator, such as one based on Splittable_random.State, should be used to ensure uniqueness. Alternatively, implement synchronization mechanisms like mutexes around the UUID generation calls.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent.
Verify if this is a real issue. If it is, propose a fix; if not, explain why it's not
valid.

Location: src/lib/consensus/proof_of_stake.ml#L478-L479

Potential issue: The systematic replacement of `Uuid_unix.create()` with
`Uuid.create_random Random.State.default` introduces a race condition. The new function
uses a shared, process-local pseudo-random number generator that is not thread-safe. In
a concurrent environment, such as the Mina node's consensus module, simultaneous calls
from different threads can produce identical UUIDs. Because these UUIDs are used to
construct filesystem paths for epoch ledgers, a collision will cause data from different
ledgers to overwrite each other, leading to silent data corruption.

Did we get this right? 👍 / 👎 to inform future reviews.

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.

1 participant