diff --git a/Cargo.lock b/Cargo.lock index 8503ee7a2..8d07ab582 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "addr2line" @@ -125,7 +125,7 @@ name = "alloc-test" version = "0.1.1" source = "git+https://github.com/openmina/alloc-test.git#0d3951eaddc58a6ea2d2134966ed5954234889d3" dependencies = [ - "clap 4.5.2", + "clap 4.5.20", "derive_builder", "derive_more", "num", @@ -164,7 +164,7 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" dependencies = [ - "winapi", + "winapi 0.3.9", ] [[package]] @@ -183,9 +183,9 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.6" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" +checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" [[package]] name = "anstyle-parse" @@ -245,8 +245,8 @@ version = "0.3.0" source = "git+https://github.com/openmina/algebra?rev=33a1de2#33a1de22d36af53bd9169475195d9a6ea809bd5b" dependencies = [ "ark-ff", - "ark-serialize", - "ark-std", + "ark-serialize 0.3.0", + "ark-std 0.3.0", "derivative", "num-traits", "rayon", @@ -260,8 +260,8 @@ source = "git+https://github.com/openmina/algebra?rev=33a1de2#33a1de22d36af53bd9 dependencies = [ "ark-ff-asm", "ark-ff-macros", - "ark-serialize", - "ark-std", + "ark-serialize 0.3.0", + "ark-std 0.3.0", "derivative", "num-bigint", "num-traits", @@ -297,8 +297,8 @@ version = "0.3.0" source = "git+https://github.com/openmina/algebra?rev=33a1de2#33a1de22d36af53bd9169475195d9a6ea809bd5b" dependencies = [ "ark-ff", - "ark-serialize", - "ark-std", + "ark-serialize 0.3.0", + "ark-std 0.3.0", "derivative", "hashbrown 0.11.2", "rayon", @@ -310,10 +310,21 @@ version = "0.3.0" source = "git+https://github.com/openmina/algebra?rev=33a1de2#33a1de22d36af53bd9169475195d9a6ea809bd5b" dependencies = [ "ark-serialize-derive", - "ark-std", + "ark-std 0.3.0", "digest 0.9.0", ] +[[package]] +name = "ark-serialize" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5" +dependencies = [ + "ark-std 0.4.0", + "digest 0.10.7", + "num-bigint", +] + [[package]] name = "ark-serialize-derive" version = "0.3.0" @@ -335,6 +346,16 @@ dependencies = [ "rayon", ] +[[package]] +name = "ark-std" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94893f1e0c6eeab764ade8dc4c0db24caf4fe7cbbaafc0eba0a9030f447b5185" +dependencies = [ + "num-traits", + "rand", +] + [[package]] name = "arrayref" version = "0.3.7" @@ -527,7 +548,7 @@ checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" dependencies = [ "hermit-abi 0.1.19", "libc", - "winapi", + "winapi 0.3.9", ] [[package]] @@ -809,6 +830,15 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "btreemultimap" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6786e40464bbcf1d72084fb26dd5f96c10c41c23a6d2f118d8c82870d4aa5447" +dependencies = [ + "serde", +] + [[package]] name = "bumpalo" version = "3.14.0" @@ -966,9 +996,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.2" +version = "4.5.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b230ab84b0ffdf890d5a10abdbc8b83ae1c4918275daea1ab8801f71536b2651" +checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8" dependencies = [ "clap_builder", "clap_derive", @@ -976,9 +1006,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.2" +version = "4.5.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4" +checksum = "19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54" dependencies = [ "anstream", "anstyle", @@ -988,11 +1018,11 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.0" +version = "4.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "307bc0538d5f0f83b8248db3087aa92fe504e4691294d0c96c0eabc33f47ba47" +checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" dependencies = [ - "heck 0.4.1", + "heck 0.5.0", "proc-macro2", "quote", "syn 2.0.58", @@ -1010,7 +1040,7 @@ version = "0.10.3" dependencies = [ "anyhow", "bytes", - "clap 4.5.2", + "clap 4.5.20", "console", "dialoguer", "hex", @@ -1986,7 +2016,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213" dependencies = [ "libc", - "winapi", + "winapi 0.3.9", ] [[package]] @@ -2562,7 +2592,7 @@ checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867" dependencies = [ "libc", "match_cfg", - "winapi", + "winapi 0.3.9", ] [[package]] @@ -2694,7 +2724,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cbc0fa01ffc752e9dbc72818cdb072cd028b86be5e09dd04c5a643704fe101a9" dependencies = [ "libc", - "winapi", + "winapi 0.3.9", ] [[package]] @@ -2838,6 +2868,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +dependencies = [ + "either", +] + [[package]] name = "itertools" version = "0.12.0" @@ -2939,6 +2978,16 @@ dependencies = [ "cpufeatures", ] +[[package]] +name = "kernel32-sys" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" +dependencies = [ + "winapi 0.2.8", + "winapi-build", +] + [[package]] name = "kimchi" version = "0.1.0" @@ -2947,7 +2996,7 @@ dependencies = [ "ark-ec", "ark-ff", "ark-poly", - "ark-serialize", + "ark-serialize 0.3.0", "blake2", "disjoint-set", "groupmap", @@ -2984,6 +3033,12 @@ dependencies = [ "spin 0.5.2", ] +[[package]] +name = "leb128" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" + [[package]] name = "ledger-tool" version = "0.10.3" @@ -3631,7 +3686,7 @@ dependencies = [ "binprot_derive", "blake2", "bs58 0.4.0", - "clap 4.5.2", + "clap 4.5.20", "derive_more", "fuzzcheck", "hex", @@ -3714,7 +3769,7 @@ dependencies = [ "ark-ec", "ark-ff", "ark-poly", - "ark-serialize", + "ark-serialize 0.3.0", "backtrace", "base64 0.13.1", "bitvec", @@ -4107,7 +4162,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e62e2187cbceeafee9fb7b5e5e182623e0628ebf430a479df4487beb8f92fd7a" dependencies = [ "overload", - "winapi", + "winapi 0.3.9", ] [[package]] @@ -4117,7 +4172,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" dependencies = [ "overload", - "winapi", + "winapi 0.3.9", ] [[package]] @@ -4302,7 +4357,7 @@ dependencies = [ "ark-ec", "ark-ff", "ark-poly", - "ark-serialize", + "ark-serialize 0.3.0", "bcs", "hex", "num-bigint", @@ -4335,6 +4390,17 @@ dependencies = [ "memchr", ] +[[package]] +name = "object" +version = "0.36.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" +dependencies = [ + "flate2", + "memchr", + "ruzstd", +] + [[package]] name = "ocaml-boxroot-sys" version = "0.2.0" @@ -4594,7 +4660,7 @@ version = "0.10.3" dependencies = [ "anyhow", "axum", - "clap 4.5.2", + "clap 4.5.20", "console", "ctrlc", "derive_more", @@ -4663,7 +4729,7 @@ name = "openmina-producer-dashboard" version = "0.10.3" dependencies = [ "bincode", - "clap 4.5.2", + "clap 4.5.20", "dotenvy", "graphql_client", "mina-p2p-messages", @@ -4764,7 +4830,7 @@ dependencies = [ "bytes", "cfg-if", "chacha20poly1305 0.10.1", - "clap 4.5.2", + "clap 4.5.20", "crypto-bigint", "curve25519-dalek", "derive_more", @@ -4889,7 +4955,7 @@ dependencies = [ "libc", "redox_syscall 0.2.16", "smallvec", - "winapi", + "winapi 0.3.9", ] [[package]] @@ -5011,6 +5077,48 @@ dependencies = [ "indexmap 2.0.2", ] +[[package]] +name = "phf" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" +dependencies = [ + "phf_macros", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0" +dependencies = [ + "phf_shared", + "rand", +] + +[[package]] +name = "phf_macros" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3444646e286606587e49f3bcf1679b8cef1dc2c5ecc29ddacaffc305180d464b" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2", + "quote", + "syn 2.0.58", +] + +[[package]] +name = "phf_shared" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" +dependencies = [ + "siphasher", +] + [[package]] name = "pin-project" version = "1.1.5" @@ -5100,7 +5208,7 @@ dependencies = [ "ark-ec", "ark-ff", "ark-poly", - "ark-serialize", + "ark-serialize 0.3.0", "blake2", "groupmap", "itertools 0.10.5", @@ -5712,7 +5820,7 @@ dependencies = [ "spin 0.5.2", "untrusted 0.7.1", "web-sys", - "winapi", + "winapi 0.3.9", ] [[package]] @@ -5730,6 +5838,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "ring_buffer" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "944cd13fb0222712688b9e6cf8002c31eca42e9eb963bd0a1e4d36da2a27bdbf" + [[package]] name = "rmp" version = "0.8.12" @@ -5789,6 +5903,19 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "rsprocmaps" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8cbd0b569820357ce882cf52267c9babd85d7f4f5260820af41ea4fce701163" +dependencies = [ + "libc", + "pest", + "pest_derive", + "phf", + "serde", +] + [[package]] name = "rtcp" version = "0.11.0" @@ -5970,6 +6097,15 @@ version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" +[[package]] +name = "ruzstd" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99c3938e133aac070997ddc684d4b393777d293ba170f2988c8fd5ea2ad4ce21" +dependencies = [ + "twox-hash", +] + [[package]] name = "rw-stream-sink" version = "0.4.0" @@ -6336,6 +6472,12 @@ dependencies = [ "rand_core", ] +[[package]] +name = "siphasher" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" + [[package]] name = "slab" version = "0.4.9" @@ -6444,7 +6586,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" dependencies = [ "libc", - "winapi", + "winapi 0.3.9", ] [[package]] @@ -6920,6 +7062,26 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "term" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2077e54d38055cf1ca0fd7933a2e00cd3ec8f6fed352b2a377f06dcdaaf3281" +dependencies = [ + "kernel32-sys", + "winapi 0.2.8", +] + +[[package]] +name = "text-diff" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "309238dd66f8bf11a20d015b727b926f294a13fcb8d56770bb984e7a22c43897" +dependencies = [ + "getopts", + "term", +] + [[package]] name = "textwrap" version = "0.11.0" @@ -7254,6 +7416,40 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "transaction_fuzzer" +version = "0.1.0" +dependencies = [ + "ark-ec", + "ark-ff", + "ark-serialize 0.4.2", + "binprot", + "binprot_derive", + "bitvec", + "btreemultimap", + "clap 4.5.20", + "flate2", + "itertools 0.11.0", + "leb128", + "md5", + "mina-curves", + "mina-hasher", + "mina-p2p-messages", + "mina-signer", + "mina-tree", + "num-bigint", + "object 0.36.5", + "once_cell", + "openmina-core", + "rand", + "ring_buffer", + "rsprocmaps", + "serde", + "serde_json", + "text-diff", + "tuple-map", +] + [[package]] name = "trust-dns-proto" version = "0.23.2" @@ -7362,6 +7558,16 @@ dependencies = [ "o1-utils", ] +[[package]] +name = "twox-hash" +version = "1.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675" +dependencies = [ + "cfg-if", + "static_assertions", +] + [[package]] name = "typenum" version = "1.17.0" @@ -7584,7 +7790,7 @@ dependencies = [ "anyhow", "ark-ec", "ark-ff", - "ark-serialize", + "ark-serialize 0.3.0", "bs58 0.4.0", "hex", "itertools 0.12.0", @@ -7995,7 +8201,7 @@ dependencies = [ "rand", "thiserror", "tokio", - "winapi", + "winapi 0.3.9", ] [[package]] @@ -8014,6 +8220,12 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "653f141f39ec16bba3c5abe400a0c60da7468261cc2cbf36805022876bc721a8" +[[package]] +name = "winapi" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" + [[package]] name = "winapi" version = "0.3.9" @@ -8024,6 +8236,12 @@ dependencies = [ "winapi-x86_64-pc-windows-gnu", ] +[[package]] +name = "winapi-build" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" + [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" diff --git a/Cargo.toml b/Cargo.toml index db1f98995..f2e1251e2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,6 +27,7 @@ members = [ "tools/hash-tool", "tools/ledger-tool", "tools/salsa-simple", + "tools/fuzzing", "producer-dashboard", "fuzzer", diff --git a/ledger/Cargo.toml b/ledger/Cargo.toml index 05d398007..87da5a99d 100644 --- a/ledger/Cargo.toml +++ b/ledger/Cargo.toml @@ -102,7 +102,7 @@ compression = ["zstd"] # Add this feature to run tests in both nodejs and browser: # https://github.com/rustwasm/wasm-bindgen/issues/2571 in_nodejs = [] - +fuzzing = [] [profile.release] debug = true diff --git a/ledger/src/dummy/mod.rs b/ledger/src/dummy/mod.rs index c243b3fce..7ecb6d3a6 100644 --- a/ledger/src/dummy/mod.rs +++ b/ledger/src/dummy/mod.rs @@ -5,10 +5,10 @@ use mina_p2p_messages::v2::PicklesProofProofsVerifiedMaxStableV2; pub use openmina_core::dummy::*; -#[cfg(test)] +#[cfg(any(test, feature = "fuzzing"))] use crate::VerificationKey; -#[cfg(test)] +#[cfg(any(test, feature = "fuzzing"))] pub mod for_tests; /// Value of `vk` when we run `dune runtest src/lib/staged_ledger -f` @@ -24,7 +24,7 @@ pub mod for_tests; /// /// Core.Printf.eprintf !"vk=%{sexp: (Side_loaded_verification_key.t, Frozen_ledger_hash.t) With_hash.t}\n%!" vk; /// Core.Printf.eprintf !"vk_binprot=[%s]\n%!" s; -#[cfg(test)] // Used for tests only +#[cfg(any(test, feature = "fuzzing"))] // Used for tests/fuzzing only pub fn trivial_verification_key() -> VerificationKey { use mina_p2p_messages::v2::MinaBaseVerificationKeyWireStableV1; diff --git a/ledger/src/ffi/mod.rs b/ledger/src/ffi/mod.rs index 2fdf9abd3..5e813202a 100644 --- a/ledger/src/ffi/mod.rs +++ b/ledger/src/ffi/mod.rs @@ -3,6 +3,6 @@ mod database; mod mask; mod ondisk; // mod transaction_fuzzer; -mod util; +//mod util; use database::*; diff --git a/ledger/src/ffi/transaction_fuzzer.rs b/ledger/src/ffi/transaction_fuzzer.rs deleted file mode 100644 index 8be5adf8b..000000000 --- a/ledger/src/ffi/transaction_fuzzer.rs +++ /dev/null @@ -1,1190 +0,0 @@ -use ark_serialize::EmptyFlags; -use mina_curves::pasta::Fq; -use mina_hasher::Fp; -use mina_p2p_messages::number::Number; -use mina_p2p_messages::pseq::PaddedSeq; -use mina_p2p_messages::v2::{ - CompositionTypesBranchDataDomainLog2StableV1, CompositionTypesBranchDataStableV1, - CompositionTypesDigestConstantStableV1, LedgerHash, LimbVectorConstantHex64StableV1, - PicklesBaseProofsVerifiedStableV1, - PicklesProofProofsVerified2ReprStableV2MessagesForNextStepProof, - PicklesProofProofsVerified2ReprStableV2MessagesForNextWrapProof, - PicklesProofProofsVerified2ReprStableV2PrevEvals, - PicklesProofProofsVerified2ReprStableV2PrevEvalsEvals, - PicklesProofProofsVerified2ReprStableV2PrevEvalsEvalsEvals, - PicklesProofProofsVerified2ReprStableV2PrevEvalsEvalsEvalsLookupA, - PicklesProofProofsVerified2ReprStableV2Proof, - PicklesProofProofsVerified2ReprStableV2ProofMessages, - PicklesProofProofsVerified2ReprStableV2ProofMessagesLookupA, - PicklesProofProofsVerified2ReprStableV2ProofOpenings, - PicklesProofProofsVerified2ReprStableV2ProofOpeningsProof, - PicklesProofProofsVerified2ReprStableV2Statement, - PicklesProofProofsVerified2ReprStableV2StatementFp, - PicklesProofProofsVerified2ReprStableV2StatementPlonk, - PicklesProofProofsVerified2ReprStableV2StatementProofState, - PicklesProofProofsVerified2ReprStableV2StatementProofStateDeferredValues, - PicklesProofProofsVerifiedMaxStableV2, - PicklesReducedMessagesForNextProofOverSameFieldWrapChallengesVectorStableV2, - PicklesReducedMessagesForNextProofOverSameFieldWrapChallengesVectorStableV2A, - PicklesReducedMessagesForNextProofOverSameFieldWrapChallengesVectorStableV2AChallenge, -}; -use mina_signer::{ - CompressedPubKey, CurvePoint, Keypair, NetworkId, ScalarField, SecKey, Signature, Signer, -}; -use ocaml_interop::{ocaml_export, OCaml, OCamlBytes, OCamlRef, ToOCaml}; -use rand::distributions::{Alphanumeric, DistString}; -use rand::seq::SliceRandom; -use std::rc::Rc; -use std::str::FromStr; -use std::{array, iter}; -use tuple_map::*; - -use crate::ffi::util::{deserialize, serialize}; -use crate::scan_state::currency::{ - Amount, Balance, BlockTime, Fee, Length, Magnitude, MinMax, Nonce, Sgn, Signed, Slot, -}; -use crate::scan_state::scan_state::ConstraintConstants; -use crate::scan_state::transaction_logic::protocol_state::{ - EpochData, EpochLedger, ProtocolStateView, -}; -use crate::scan_state::transaction_logic::signed_command::{ - PaymentPayload, SignedCommand, SignedCommandPayload, -}; -use crate::scan_state::transaction_logic::transaction_applied::{ - signed_command_applied, CommandApplied, TransactionApplied, Varying, -}; -use crate::scan_state::transaction_logic::transaction_union_payload::TransactionUnionPayload; -use crate::scan_state::transaction_logic::zkapp_command::{ - self, AccountPreconditions, AccountUpdate, ClosedInterval, FeePayer, FeePayerBody, Numeric, - OrIgnore, SetOrKeep, Update, WithHash, ZkAppCommand, -}; -use crate::scan_state::transaction_logic::{ - apply_transaction, signed_command, Memo, Transaction, UserCommand, -}; -use crate::staged_ledger::sparse_ledger::LedgerIntf; -use crate::{ - Account, AccountId, AuthRequired, CurveAffine, Mask, Permissions, PlonkVerificationKeyEvals, - ProofVerified, Timing, TokenId, TokenSymbol, VerificationKey, VotingFor, ZkAppUri, -}; - -use ark_ec::{AffineCurve, ProjectiveCurve}; -use ark_ff::UniformRand; -use ark_ff::{Field, SquareRootField, Zero}; -use rand::rngs::SmallRng; -use rand::{self, Rng, SeedableRng}; - -fn sign_payload(keypair: &Keypair, payload: &SignedCommandPayload) -> Signature { - let tx = TransactionUnionPayload::of_user_command_payload(payload); - let mut signer = mina_signer::create_legacy(NetworkId::TESTNET); - signer.sign(keypair, &tx) -} - -fn new_signed_command( - keypair: &Keypair, - fee: Fee, - fee_payer_pk: CompressedPubKey, - nonce: Nonce, - valid_until: Option, - memo: Memo, - body: signed_command::Body, -) -> SignedCommand { - let payload = SignedCommandPayload::create(fee, fee_payer_pk, nonce, valid_until, memo, body); - let signature = sign_payload(keypair, &payload); - - SignedCommand { - payload, - signer: keypair.public.into_compressed(), - signature, - } -} - -fn new_payment( - source_pk: CompressedPubKey, - receiver_pk: CompressedPubKey, - amount: Amount, -) -> signed_command::Body { - let payload = PaymentPayload { - source_pk, - receiver_pk, - amount, - }; - signed_command::Body::Payment(payload) -} - -fn new_payment_tx( - keypair: &Keypair, - fee: Fee, - fee_payer_pk: CompressedPubKey, - nonce: Nonce, - valid_until: Option, - memo: Memo, - receiver_pk: CompressedPubKey, - amount: Amount, -) -> SignedCommand { - let body = new_payment(keypair.public.into_compressed(), receiver_pk, amount); - new_signed_command(keypair, fee, fee_payer_pk, nonce, valid_until, memo, body) -} - -/* - Reimplement random key generation w/o the restriction on CryptoRgn trait. - Since we are only using this for fuzzing we want a faster (unsafe) Rng like SmallRng. -*/ -fn gen_sk(rng: &mut SmallRng) -> SecKey { - let secret: ScalarField = ScalarField::rand(rng); - SecKey::new(secret) -} - -fn gen_keypair(rng: &mut SmallRng) -> Keypair { - let sec_key = gen_sk(rng); - let scalar = sec_key.into_scalar(); - let public = CurvePoint::prime_subgroup_generator() - .mul(scalar) - .into_affine(); - - Keypair::from_parts_unsafe(scalar, public) -} - -// Taken from ocaml_tests -/// Same values when we run `dune runtest src/lib/staged_ledger -f` -fn dummy_state_view(global_slot_since_genesis: Option) -> ProtocolStateView { - // TODO: Use OCaml implementation, not hardcoded value - let f = |s: &str| Fp::from_str(s).unwrap(); - - ProtocolStateView { - snarked_ledger_hash: f( - "19095410909873291354237217869735884756874834695933531743203428046904386166496", - ), - timestamp: BlockTime::from_u64(1600251300000), - blockchain_length: Length::from_u32(1), - min_window_density: Length::from_u32(77), - last_vrf_output: (), - total_currency: Amount::from_u64(10016100000000000), - global_slot_since_hard_fork: Slot::from_u32(0), - global_slot_since_genesis: global_slot_since_genesis.unwrap_or_else(Slot::zero), - staking_epoch_data: EpochData { - ledger: EpochLedger { - hash: f( - "19095410909873291354237217869735884756874834695933531743203428046904386166496", - ), - total_currency: Amount::from_u64(10016100000000000), - }, - seed: Fp::zero(), - start_checkpoint: Fp::zero(), - lock_checkpoint: Fp::zero(), - epoch_length: Length::from_u32(1), - }, - next_epoch_data: EpochData { - ledger: EpochLedger { - hash: f( - "19095410909873291354237217869735884756874834695933531743203428046904386166496", - ), - total_currency: Amount::from_u64(10016100000000000), - }, - seed: f( - "18512313064034685696641580142878809378857342939026666126913761777372978255172", - ), - start_checkpoint: Fp::zero(), - lock_checkpoint: f( - "9196091926153144288494889289330016873963015481670968646275122329689722912273", - ), - epoch_length: Length::from_u32(2), - }, - } -} - -/// Same values when we run `dune runtest src/lib/staged_ledger -f` -const CONSTRAINT_CONSTANTS: ConstraintConstants = ConstraintConstants { - sub_windows_per_window: 11, - ledger_depth: 35, - work_delay: 2, - block_window_duration_ms: 180000, - transaction_capacity_log_2: 7, - pending_coinbase_depth: 5, - coinbase_amount: Amount::from_u64(720000000000), - supercharged_coinbase_factor: 2, - account_creation_fee: Fee::from_u64(1000000000), - fork: None, -}; - -struct FuzzerCtx { - constraint_constants: ConstraintConstants, - txn_state_view: ProtocolStateView, - ledger: Mask, - rng: SmallRng, - potential_senders: Vec, - potential_new_accounts: Vec, -} - -impl FuzzerCtx { - fn new(seed: u64, constraint_constants: ConstraintConstants) -> Self { - let depth = constraint_constants.ledger_depth as usize; - Self { - constraint_constants, - txn_state_view: dummy_state_view(None), - ledger: { - let root = Mask::new_root(crate::Database::create(depth.try_into().unwrap())); - root.make_child() - }, - rng: SmallRng::seed_from_u64(seed), - potential_senders: Vec::new(), - potential_new_accounts: Vec::new(), - } - } - - fn create_inital_accounts(&mut self, n: usize) { - for _ in 0..n { - loop { - let keypair = gen_keypair(&mut self.rng); - - if !self - .potential_senders - .iter() - .any(|x| x.public == keypair.public) - { - let pk_compressed = keypair.public.into_compressed(); - let account_id = AccountId::new(pk_compressed, TokenId::default()); - let mut account = Account::initialize(&account_id); - - account.balance = - Balance::from_u64(self.rng.gen_range(1_000_000_000..u64::MAX)); - account.nonce = Nonce::from_u32(self.rng.gen_range(0..1000)); - account.timing = Timing::Untimed; - - self.potential_senders.push(keypair); - self.ledger.create_new_account(account_id, account).unwrap(); - break; - } - } - } - } - - fn rnd_base_field(&mut self) -> Fp { - let mut bf = None; - - // TODO: optimize by masking out MSBs from bytes and remove loop - while bf.is_none() { - let bytes = self.rng.gen::<[u8; 32]>(); - bf = Fp::from_random_bytes_with_flags::(&bytes); - } - - bf.unwrap().0 - } - - fn rnd_option(&mut self, mut f: F) -> Option - where - F: FnMut(&mut Self) -> T, - { - if self.rng.gen_bool(0.9) { - None - } else { - Some(f(self)) - } - } - - fn rnd_or_ignore(&mut self, mut f: F) -> OrIgnore - where - F: FnMut(&mut Self) -> T, - { - if self.rng.gen_bool(0.9) { - OrIgnore::Ignore - } else { - OrIgnore::Check(f(self)) - } - } - - fn rnd_set_or_keep(&mut self, mut f: F) -> SetOrKeep - where - F: FnMut(&mut Self) -> T, - { - if self.rng.gen_bool(0.9) { - SetOrKeep::Keep - } else { - SetOrKeep::Set(f(self)) - } - } - - fn rnd_closed_interval(&mut self, mut f: F) -> ClosedInterval - where - F: FnMut(&mut Self) -> T, - { - ClosedInterval { - lower: f(self), - upper: f(self), - } - } - - fn rnd_numeric(&mut self, mut f: F) -> Numeric - where - F: FnMut(&mut Self) -> T, - { - self.rnd_or_ignore(|x| x.rnd_closed_interval(|x| f(x))) - } - - fn rnd_signed(&mut self, mut f: F) -> Signed - where - F: FnMut(&mut Self) -> T, - { - let sgn = if self.rng.gen_bool(0.5) { - Sgn::Pos - } else { - Sgn::Neg - }; - - Signed::create(f(self), sgn) - } - - fn rnd_slot(&mut self) -> Slot { - Slot::from_u32(self.rng.gen_range( - self.txn_state_view.global_slot_since_genesis.as_u32()..Slot::max().as_u32(), - )) - } - - fn rnd_pubkey(&mut self) -> CompressedPubKey { - let keypair = self - .potential_senders - .choose(&mut self.rng) - .unwrap() - .clone(); - keypair.public.into_compressed() - } - - fn find_keypair(&mut self, pkey: &CompressedPubKey) -> Option<&Keypair> { - self.potential_senders - .iter() - .find(|x| x.public.into_compressed() == *pkey) - } - - fn rnd_pubkey_new(&mut self) -> CompressedPubKey { - let keypair = gen_keypair(&mut self.rng); - let pk = keypair.public.into_compressed(); - - if !self - .potential_senders - .iter() - .any(|x| x.public == keypair.public) - { - self.potential_new_accounts.push(keypair) - } - - pk - } - - fn rnd_memo(&mut self) -> Memo { - Memo::with_number(self.rng.gen()) - } - - fn account_from_pubkey(&mut self, pkey: &CompressedPubKey) -> Account { - let account_location = self - .ledger - .location_of_account(&AccountId::new(pkey.clone(), TokenId::default())); - - let location = account_location.unwrap(); - self.ledger.get(&location).unwrap() - } - - fn rnd_balance_u64(&mut self, account: &Account) -> u64 { - let balance = account.balance.as_u64(); - - if balance > 1 { - self.rng.gen_range(0..balance) - } else { - 0 - } - } - - fn rnd_fee(&mut self, account: &Account) -> Fee { - Fee::from_u64(self.rnd_balance_u64(account)) - } - - fn rnd_balance(&mut self, account: &Account) -> Balance { - Balance::from_u64(self.rnd_balance_u64(account)) - } - - fn rnd_amount(&mut self, account: &Account, fee: Fee) -> Amount { - let balance = self.rnd_balance_u64(account); - Amount::from_u64(balance.saturating_sub(fee.as_u64())) - } - - fn rnd_fee_payer(&mut self) -> FeePayer { - let public_key = self.rnd_pubkey(); - let account = self.account_from_pubkey(&public_key); - FeePayer { - body: FeePayerBody { - public_key, - fee: self.rnd_fee(&account), - valid_until: self.rnd_option(Self::rnd_slot), - nonce: account.nonce, - }, - // filled later when tx is complete - authorization: Signature::dummy(), - } - } - - fn rnd_fp(&mut self) -> Fp { - Fp::rand(&mut self.rng) - } - - fn rnd_curve_point>(&mut self) -> (F, F) { - /* - WARNING: we need to generate valid curve points to avoid binprot deserializarion - exceptions in the OCaml side. However this is an expensive task. - - TODO: a more efficient way of doing this? - */ - let mut x = F::rand(&mut self.rng); - - loop { - let y_squared = x.square().mul(x).add(Into::::into(5)); - - if let Some(y) = y_squared.sqrt() { - return (x, y); - } - - x.add_assign(F::one()); - } - } - - fn rnd_curve_affine(&mut self) -> CurveAffine { - let (x, y) = self.rnd_curve_point(); - CurveAffine::(x, y) - } - - fn rnd_plonk_verification_key_evals(&mut self) -> PlonkVerificationKeyEvals { - PlonkVerificationKeyEvals { - sigma: array::from_fn(|_| self.rnd_curve_affine()), - coefficients: array::from_fn(|_| self.rnd_curve_affine()), - generic: self.rnd_curve_affine(), - psm: self.rnd_curve_affine(), - complete_add: self.rnd_curve_affine(), - mul: self.rnd_curve_affine(), - emul: self.rnd_curve_affine(), - endomul_scalar: self.rnd_curve_affine(), - } - } - - fn rnd_verification_key(&mut self) -> WithHash { - let data = VerificationKey { - max_proofs_verified: vec![ProofVerified::N0, ProofVerified::N1, ProofVerified::N2] - .choose(&mut self.rng) - .unwrap() - .clone(), - wrap_index: self.rnd_plonk_verification_key_evals(), - wrap_vk: None, // TODO - }; - let hash = data.digest(); - WithHash { data, hash } - } - - fn rnd_auth_required(&mut self) -> AuthRequired { - *vec![ - AuthRequired::None, - AuthRequired::Either, - AuthRequired::Proof, - AuthRequired::Signature, - AuthRequired::Impossible, - //AuthRequired::Both, - ] - .choose(&mut self.rng) - .unwrap() - } - - fn rnd_permissions(&mut self) -> Permissions { - Permissions:: { - edit_state: self.rnd_auth_required(), - send: self.rnd_auth_required(), - receive: self.rnd_auth_required(), - set_delegate: self.rnd_auth_required(), - set_permissions: self.rnd_auth_required(), - set_verification_key: self.rnd_auth_required(), - set_zkapp_uri: self.rnd_auth_required(), - edit_sequence_state: self.rnd_auth_required(), - set_token_symbol: self.rnd_auth_required(), - increment_nonce: self.rnd_auth_required(), - set_voting_for: self.rnd_auth_required(), - } - } - - fn rnd_zkapp_uri(&mut self) -> ZkAppUri { - /* - TODO: this needs to be fixed (assign a boundary) in the protocol since it is - possible to set a zkApp URI of arbitrary size. - - Since the field is opaque to the Mina protocol logic, randomly generating - URIs makes little sense and will consume a significant amount of ledger space. - */ - ZkAppUri::new() - } - - fn rnd_token_symbol(&mut self) -> TokenSymbol { - /* - TokenSymbol must be <= 6 **bytes**. This boundary doesn't exist at type-level, - instead it is check by binprot after deserializing the *string* object: - https://github.com/MinaProtocol/mina/blob/develop/src/lib/mina_base/account.ml#L124 - - We will let this function generate strings larger than 6 bytes with low probability, - just to cover the error handling code, but must of the time we want to avoid failing - this check. - */ - if self.rng.gen_bool(0.9) { - TokenSymbol::default() - } else { - let rnd_len = self.rng.gen_range(1..=6); - // TODO: fix n random chars for n random bytes - TokenSymbol(Alphanumeric.sample_string(&mut self.rng, rnd_len)) - } - } - - fn rnd_timing(&mut self, account: &Account) -> zkapp_command::Timing { - let fee = self.rnd_fee(account); - let fee2 = self.rnd_fee(account); - - zkapp_command::Timing { - initial_minimum_balance: self.rnd_balance(account), - cliff_time: self.rnd_slot(), - cliff_amount: self.rnd_amount(account, fee), - vesting_period: self.rnd_slot(), - vesting_increment: self.rnd_amount(account, fee2), - } - } - - fn rnd_voting_for(&mut self) -> VotingFor { - VotingFor(self.rnd_fp()) - } - - fn rnd_update(&mut self, account: &Account) -> Update { - Update { - app_state: array::from_fn(|_| self.rnd_set_or_keep(Self::rnd_fp)), - delegate: self.rnd_set_or_keep(Self::rnd_pubkey), - verification_key: self.rnd_set_or_keep(Self::rnd_verification_key), - permissions: self.rnd_set_or_keep(Self::rnd_permissions), - zkapp_uri: self.rnd_set_or_keep(Self::rnd_zkapp_uri), - token_symbol: self.rnd_set_or_keep(Self::rnd_token_symbol), - timing: self.rnd_set_or_keep(|x| Self::rnd_timing(x, account)), - voting_for: self.rnd_set_or_keep(Self::rnd_voting_for), - } - } - - fn rnd_events(&mut self) -> zkapp_command::Events { - /* - An Event is a list of arrays of Fp, there doesn't seem to be any limit - neither in the size of the list or the array's size. The total size should - be bounded by the transport protocol (currently libp2p, ~32MB). - - Since this field is ignored by nodes (except maybe for archive nodes), we - we will generate empty events (at least for the moment). - */ - zkapp_command::Events(Vec::new()) - } - - fn rnd_sequence_events(&mut self) -> zkapp_command::Actions { - // See comment above in rnd_events - zkapp_command::Actions(Vec::new()) - } - - fn rnd_block_time(&mut self) -> BlockTime { - self.rng.gen() - } - - fn rnd_length(&mut self) -> Length { - self.rng.gen() - } - - fn rnd_nonce(&mut self, account: &Account) -> Nonce { - if self.rng.gen_bool(0.9) { - account.nonce - } else { - self.rng.gen() - } - } - - fn rnd_epoch_data(&mut self) -> zkapp_command::EpochData { - zkapp_command::EpochData { - ledger: zkapp_command::EpochLedger { - hash: self.rnd_or_ignore(Self::rnd_fp), - total_currency: self.rnd_numeric(|x| x.rng.gen()), - }, - seed: self.rnd_or_ignore(Self::rnd_fp), - start_checkpoint: self.rnd_or_ignore(Self::rnd_fp), - lock_checkpoint: self.rnd_or_ignore(Self::rnd_fp), - epoch_length: self.rnd_numeric(Self::rnd_length), - } - } - - fn rnd_zkapp_preconditions(&mut self, account: &Account) -> zkapp_command::ZkAppPreconditions { - zkapp_command::ZkAppPreconditions { - snarked_ledger_hash: self.rnd_or_ignore(Self::rnd_fp), - timestamp: self.rnd_numeric(Self::rnd_block_time), - blockchain_length: self.rnd_numeric(Self::rnd_length), - min_window_density: self.rnd_numeric(Self::rnd_length), - last_vrf_output: (), - total_currency: self.rnd_numeric(|x| Self::rnd_amount(x, account, Fee::from_u64(0))), - global_slot_since_hard_fork: self.rnd_numeric(Self::rnd_slot), - global_slot_since_genesis: self.rnd_numeric(Self::rnd_slot), - staking_epoch_data: self.rnd_epoch_data(), - next_epoch_data: self.rnd_epoch_data(), - } - } - - fn rnd_account(&mut self, account: &Account) -> zkapp_command::Account { - zkapp_command::Account { - balance: self.rnd_numeric(|x| Self::rnd_balance(x, account)), - nonce: self.rnd_numeric(|x| Self::rnd_nonce(x, account)), - receipt_chain_hash: self.rnd_or_ignore(Self::rnd_fp), - delegate: self.rnd_or_ignore(Self::rnd_pubkey), - state: array::from_fn(|_| self.rnd_or_ignore(Self::rnd_fp)), - sequence_state: self.rnd_or_ignore(Self::rnd_fp), - proved_state: self.rnd_or_ignore(|x| x.rng.gen_bool(0.1)), - is_new: self.rnd_or_ignore(|x| x.rng.gen_bool(0.1)), - } - } - - fn rnd_account_preconditions(&mut self, account: &Account) -> AccountPreconditions { - match vec![0, 1, 2].choose(&mut self.rng).unwrap() { - 0 => AccountPreconditions::Accept, - 1 => AccountPreconditions::Nonce(self.rnd_nonce(account)), - _ => AccountPreconditions::Full(Box::new(self.rnd_account(account))), - } - } - - fn rnd_preconditions(&mut self, account: &Account) -> zkapp_command::Preconditions { - zkapp_command::Preconditions { - network: self.rnd_zkapp_preconditions(account), - account: self.rnd_account_preconditions(account), - } - } - - fn rnd_authorization(&mut self) -> zkapp_command::AuthorizationKind { - vec![ - zkapp_command::AuthorizationKind::NoneGiven, - zkapp_command::AuthorizationKind::Signature, - zkapp_command::AuthorizationKind::Proof, - ] - .choose(&mut self.rng) - .unwrap() - .clone() - } - - fn rnd_wrap_challenges_vector( - &mut self, - ) -> PicklesReducedMessagesForNextProofOverSameFieldWrapChallengesVectorStableV2AChallenge { - PicklesReducedMessagesForNextProofOverSameFieldWrapChallengesVectorStableV2AChallenge { - inner: PaddedSeq(array::from_fn(|_| { - LimbVectorConstantHex64StableV1(Number(self.rng.gen())) - })), - } - } - - fn rnd_proof_state(&mut self) -> PicklesProofProofsVerified2ReprStableV2StatementProofState { - PicklesProofProofsVerified2ReprStableV2StatementProofState { - deferred_values: - PicklesProofProofsVerified2ReprStableV2StatementProofStateDeferredValues { - plonk: PicklesProofProofsVerified2ReprStableV2StatementPlonk { - alpha: self.rnd_wrap_challenges_vector(), - beta: PaddedSeq(array::from_fn(|_| { - LimbVectorConstantHex64StableV1(Number(self.rng.gen())) - })), - gamma: PaddedSeq(array::from_fn(|_| { - LimbVectorConstantHex64StableV1(Number(self.rng.gen())) - })), - zeta: self.rnd_wrap_challenges_vector(), - joint_combiner: self.rnd_option(|x| Self::rnd_wrap_challenges_vector(x)), - }, - combined_inner_product: - PicklesProofProofsVerified2ReprStableV2StatementFp::ShiftedValue( - mina_p2p_messages::bigint::BigInt::from(self.rnd_fp()), - ), - b: PicklesProofProofsVerified2ReprStableV2StatementFp::ShiftedValue( - mina_p2p_messages::bigint::BigInt::from(self.rnd_fp()), - ), - xi: self.rnd_wrap_challenges_vector(), - bulletproof_challenges: PaddedSeq(array::from_fn(|_| { - PicklesReducedMessagesForNextProofOverSameFieldWrapChallengesVectorStableV2A { - prechallenge: self.rnd_wrap_challenges_vector() - } - })), - branch_data: CompositionTypesBranchDataStableV1 { - proofs_verified: (vec![ - PicklesBaseProofsVerifiedStableV1::N0, - PicklesBaseProofsVerifiedStableV1::N1, - PicklesBaseProofsVerifiedStableV1::N2, - ] - .choose(&mut self.rng) - .unwrap() - .clone(),), - domain_log2: CompositionTypesBranchDataDomainLog2StableV1( - mina_p2p_messages::char::Char(self.rng.gen()), - ), - }, - }, - sponge_digest_before_evaluations: CompositionTypesDigestConstantStableV1(PaddedSeq( - array::from_fn(|_| LimbVectorConstantHex64StableV1(Number(self.rng.gen()))), - )), - messages_for_next_wrap_proof: - PicklesProofProofsVerified2ReprStableV2MessagesForNextWrapProof { - challenge_polynomial_commitment: self - .rnd_curve_point::() - .map(mina_p2p_messages::bigint::BigInt::from), - old_bulletproof_challenges: PaddedSeq(array::from_fn(|_| { - PicklesReducedMessagesForNextProofOverSameFieldWrapChallengesVectorStableV2( - PaddedSeq(array::from_fn(|_| { - PicklesReducedMessagesForNextProofOverSameFieldWrapChallengesVectorStableV2A { - prechallenge: self.rnd_wrap_challenges_vector() - } - })), - ) - })), - }, - } - } - - fn rnd_vec(&mut self, mut f: F, size: usize) -> Vec - where - F: FnMut(&mut Self) -> T, - { - //let size = self.rng.gen_range(0..=max_size); - iter::repeat_with(|| f(self)).take(size).collect() - } - - fn rnd_proof(&mut self) -> zkapp_command::SideLoadedProof { - let proof = PicklesProofProofsVerifiedMaxStableV2 { - statement: PicklesProofProofsVerified2ReprStableV2Statement { - proof_state: self.rnd_proof_state(), - messages_for_next_step_proof: PicklesProofProofsVerified2ReprStableV2MessagesForNextStepProof { - app_state: (), - challenge_polynomial_commitments: self.rnd_vec( - |x| { - x.rnd_curve_point::().map(mina_p2p_messages::bigint::BigInt::from) - }, - 1 - ), - old_bulletproof_challenges: self.rnd_vec( - |x| { - PaddedSeq(array::from_fn(|_| { - PicklesReducedMessagesForNextProofOverSameFieldWrapChallengesVectorStableV2A { - prechallenge: x.rnd_wrap_challenges_vector() - } - })) - }, - 1 - ) - } - }, - prev_evals: PicklesProofProofsVerified2ReprStableV2PrevEvals { - evals: PicklesProofProofsVerified2ReprStableV2PrevEvalsEvals { - public_input: self.rnd_curve_point::().map(mina_p2p_messages::bigint::BigInt::from), - evals: PicklesProofProofsVerified2ReprStableV2PrevEvalsEvalsEvals { - w: PaddedSeq( - array::from_fn(|_| self.rnd_vec( - |x| x.rnd_curve_point::().map(mina_p2p_messages::bigint::BigInt::from), - 1 - ).into_iter().unzip() - )), - z: self.rnd_vec( - |x| x.rnd_curve_point::().map(mina_p2p_messages::bigint::BigInt::from), - 1 - ).into_iter().unzip(), - s: PaddedSeq( - array::from_fn(|_| self.rnd_vec( - |x| x.rnd_curve_point::().map(mina_p2p_messages::bigint::BigInt::from), - 1 - ).into_iter().unzip() - )), - generic_selector: self.rnd_vec( - |x| x.rnd_curve_point::().map(mina_p2p_messages::bigint::BigInt::from), - 1 - ).into_iter().unzip(), - poseidon_selector: self.rnd_vec( - |x| x.rnd_curve_point::().map(mina_p2p_messages::bigint::BigInt::from), - 1 - ).into_iter().unzip(), - lookup: self.rnd_option(|x| { - PicklesProofProofsVerified2ReprStableV2PrevEvalsEvalsEvalsLookupA { - sorted: x.rnd_vec(|x| x.rnd_vec( - |x| x.rnd_curve_point::().map(mina_p2p_messages::bigint::BigInt::from), - 1 - ).into_iter().unzip(), - 1 - ), - aggreg: x.rnd_vec( - |x| x.rnd_curve_point::().map(mina_p2p_messages::bigint::BigInt::from), - 1 - ).into_iter().unzip(), - table: x.rnd_vec( - |x| x.rnd_curve_point::().map(mina_p2p_messages::bigint::BigInt::from), - 1 - ).into_iter().unzip(), - runtime: x.rnd_option(|x| x.rnd_vec( - |x| x.rnd_curve_point::().map(mina_p2p_messages::bigint::BigInt::from), - 1 - ).into_iter().unzip()) - } - }) - } - }, - ft_eval1: mina_p2p_messages::bigint::BigInt::from(self.rnd_fp()) - }, - proof: PicklesProofProofsVerified2ReprStableV2Proof { - messages: PicklesProofProofsVerified2ReprStableV2ProofMessages { - w_comm: PaddedSeq( - array::from_fn(|_| { - self.rnd_vec( - |x| x.rnd_curve_point::().map(mina_p2p_messages::bigint::BigInt::from), - 1 - ) - } - ) - ), - z_comm: self.rnd_vec( - |x| x.rnd_curve_point::().map(mina_p2p_messages::bigint::BigInt::from), - 1 - ), - t_comm: self.rnd_vec( - |x| x.rnd_curve_point::().map(mina_p2p_messages::bigint::BigInt::from), - 1 - ), - lookup: self.rnd_option(|x| { - PicklesProofProofsVerified2ReprStableV2ProofMessagesLookupA { - sorted: x.rnd_vec( - |x| x.rnd_vec( - |x| x.rnd_curve_point::().map(mina_p2p_messages::bigint::BigInt::from), - 1 - ), - 1 - ), - aggreg: x.rnd_vec( - |x|x.rnd_curve_point::().map(mina_p2p_messages::bigint::BigInt::from), - 1 - ), - runtime: x.rnd_option( - |x| x.rnd_vec( - |x|x.rnd_curve_point::().map(mina_p2p_messages::bigint::BigInt::from), - 1 - ) - ) - } - }) - }, - openings: PicklesProofProofsVerified2ReprStableV2ProofOpenings { - proof: PicklesProofProofsVerified2ReprStableV2ProofOpeningsProof { - lr: self.rnd_vec( - |x| { - ( - x.rnd_curve_point::().map(mina_p2p_messages::bigint::BigInt::from), - x.rnd_curve_point::().map(mina_p2p_messages::bigint::BigInt::from) - ) - }, - 1 - ), - z_1: mina_p2p_messages::bigint::BigInt::from(self.rnd_fp()), - z_2: mina_p2p_messages::bigint::BigInt::from(self.rnd_fp()), - delta: self.rnd_curve_point::().map(mina_p2p_messages::bigint::BigInt::from), - challenge_polynomial_commitment: self.rnd_curve_point::().map(mina_p2p_messages::bigint::BigInt::from) - }, - evals: PicklesProofProofsVerified2ReprStableV2PrevEvalsEvalsEvals { - w: PaddedSeq( - array::from_fn(|_| self.rnd_vec( - |x| x.rnd_curve_point::().map(mina_p2p_messages::bigint::BigInt::from), - 1 - ).into_iter().unzip() - )), - z: self.rnd_vec( - |x| x.rnd_curve_point::().map(mina_p2p_messages::bigint::BigInt::from), - 1 - ).into_iter().unzip(), - s: PaddedSeq( - array::from_fn(|_| self.rnd_vec( - |x| x.rnd_curve_point::().map(mina_p2p_messages::bigint::BigInt::from), - 1 - ).into_iter().unzip() - )), - generic_selector: self.rnd_vec( - |x| x.rnd_curve_point::().map(mina_p2p_messages::bigint::BigInt::from), - 1 - ).into_iter().unzip(), - poseidon_selector: self.rnd_vec( - |x| x.rnd_curve_point::().map(mina_p2p_messages::bigint::BigInt::from), - 1 - ).into_iter().unzip(), - lookup: self.rnd_option(|x| { - PicklesProofProofsVerified2ReprStableV2PrevEvalsEvalsEvalsLookupA { - sorted: x.rnd_vec( - |x| x.rnd_vec( - |x| x.rnd_curve_point::().map(mina_p2p_messages::bigint::BigInt::from), - 1 - ).into_iter().unzip(), - 1 - ), - aggreg: x.rnd_vec( - |x| x.rnd_curve_point::().map(mina_p2p_messages::bigint::BigInt::from), - 1 - ).into_iter().unzip(), - table: x.rnd_vec( - |x| x.rnd_curve_point::().map(mina_p2p_messages::bigint::BigInt::from), - 1 - ).into_iter().unzip(), - runtime: x.rnd_option( - |x| x.rnd_vec( - |x| x.rnd_curve_point::().map(mina_p2p_messages::bigint::BigInt::from), - 1 - ).into_iter().unzip() - ) - } - }) - }, - ft_eval1: mina_p2p_messages::bigint::BigInt::from(self.rnd_fp()) - } - }, - }; - Rc::new(proof) - } - - fn rnd_control(&mut self) -> zkapp_command::Control { - match vec![0, 1, 2].choose(&mut self.rng).unwrap() { - 0 => zkapp_command::Control::NoneGiven, - // TODO: calculate signature after building the transaction - 1 => zkapp_command::Control::Signature(Signature::dummy()), - _ => zkapp_command::Control::Proof(self.rnd_proof()), - } - } - - fn rnd_account_update(&mut self) -> AccountUpdate { - let public_key = self.rnd_pubkey(); - let account = self.account_from_pubkey(&public_key); - - AccountUpdate { - body: zkapp_command::Body { - public_key, - token_id: TokenId::default(), // TODO: randomize - update: self.rnd_update(&account), - balance_change: self - .rnd_signed(|x| Self::rnd_amount(x, &account, Fee::from_u64(0))), - increment_nonce: self.rng.gen_bool(0.9), - events: self.rnd_events(), - sequence_events: self.rnd_sequence_events(), - call_data: self.rnd_fp(), - preconditions: self.rnd_preconditions(&account), - use_full_commitment: self.rng.gen_bool(0.5), - caller: TokenId::default(), // TODO: randomize (MinaBaseAccountUpdateCallTypeStableV1) - authorization_kind: self.rnd_authorization(), - }, - authorization: self.rnd_control(), - } - } - - fn rnd_forest(&mut self) -> zkapp_command::CallForest { - let mut forest = zkapp_command::CallForest::::new(); - //let count = self.rng.gen_range(0..10); - let count = 1; - - for _ in 0..count { - let calls = /*if self.rng.gen_bool(0.8*) {*/ - None - /*} else { - Some(self.rnd_forest()) - }*/; - - forest = forest.cons(calls, self.rnd_account_update()); - } - - //println!("rnd_forest len {}", forest.0.len()); - forest - } - - fn rnd_zkapp_command(&mut self) -> ZkAppCommand { - let fee_payer = self.rnd_fee_payer(); - let account_updates = self.rnd_forest(); - let memo = self.rnd_memo(); - - ZkAppCommand { - fee_payer, - account_updates, - memo, - } - } - - fn rnd_payment_tx(&mut self) -> SignedCommand { - let fee_payer_pk = self.rnd_pubkey(); - let account = self.account_from_pubkey(&fee_payer_pk); - let fee = self.rnd_fee(&account); - let valid_until = self.rnd_option(Self::rnd_slot); - let memo = self.rnd_memo(); - let receiver_pk = if self.rng.gen_bool(0.8) { - // use existing account - self.rnd_pubkey() - } else { - // create new account - self.rnd_pubkey_new() - }; - let amount = self.rnd_amount(&account, fee); - - new_payment_tx( - self.find_keypair(&fee_payer_pk).unwrap(), - fee, - fee_payer_pk, - account.nonce, - valid_until, - memo, - receiver_pk, - amount, - ) - } - - fn apply_transaction(&mut self, tx: &Transaction) -> Result { - apply_transaction( - &self.constraint_constants, - &self.txn_state_view, - &mut self.ledger, - &tx, - ) - } - - fn rnd_transaction(&mut self) -> Transaction { - let zkapp_command = self.rnd_zkapp_command(); - Transaction::Command(UserCommand::ZkAppCommand(Box::new(zkapp_command))) - - //let signed_command = self.rnd_payment_tx(); - //Transaction::Command(UserCommand::SignedCommand(Box::new(signed_command))) - } - - fn get_ledger_root(&mut self) -> Fp { - self.ledger.merkle_root() - } - - fn get_ledger_accounts(&self) -> Vec { - let locations = self.ledger.account_locations(); - locations - .iter() - .map(|x| self.ledger.get(x).unwrap()) - .collect() - } -} - -ocaml_export! { - fn rust_transaction_fuzzer( - rt, - set_initial_accounts_method: OCamlRef OCamlBytes>, - apply_transaction_method: OCamlRef OCamlBytes>, - ) { - println!("Rust called"); - let mut ctx = FuzzerCtx::new(0, CONSTRAINT_CONSTANTS); - - println!("New context"); - ctx.create_inital_accounts(10); - - println!("Initial accounts"); - let initial_accounts = serialize(&ctx.get_ledger_accounts()); - let ocaml_method = set_initial_accounts_method.to_boxroot(rt); - let rust_ledger_root_hash = ctx.get_ledger_root(); - - println!("calling set_initial_accounts (OCaml)"); - // Duplicate initial accounts in the OCaml side - let ocaml_ledger_root_hash: OCaml = ocaml_method.try_call(rt, &initial_accounts).unwrap(); - let x: Vec = ocaml_ledger_root_hash.to_rust(); - let mut ocaml_ledger_root_hash = Fp::from(deserialize::(x.as_slice()).0.clone()); - - println!("Initial ledger hash =>\n Rust: {:?}\n OCaml: {:?}", rust_ledger_root_hash, ocaml_ledger_root_hash); - assert!(ocaml_ledger_root_hash == rust_ledger_root_hash); - let mut iter_num = 0; - - loop { - println!("Iteration {}", iter_num); - iter_num += 1; - let tx = ctx.rnd_transaction(); - - /* - if let Transaction::Command(UserCommand::ZkAppCommand(x)) = &tx { - - for tmp in x.account_updates.0.iter() { - let tmp2 = tmp.elt.account_update.clone(); - println!("acc update: {:?}", tmp2.protocol_state_precondition()); - - for call in tmp.elt.calls.0.iter() { - let tmp2 = call.elt.account_update.clone(); - println!("call acc update: {:?}", tmp2.protocol_state_precondition()); - } - - } - } - */ - - //println!("tx {:?}", tx); - - /* - We don't have generated types for Transaction, but we have one - for UserCommand (MinaBaseUserCommandStableV2). Extract and - serialize the inner UserCommand and let a OCaml wrapper build - the transaction. - */ - let user_command = match &tx { - Transaction::Command(user_command) => serialize(user_command), - _ => unimplemented!() - }; - - let ocaml_method = apply_transaction_method.to_boxroot(rt); - match ocaml_method.try_call(rt, &user_command) { - Ok(ledger_root_hash) => { - let x: Vec = ledger_root_hash.to_rust(); - let root_hash_deserialized = deserialize::(x.as_slice()).0.clone(); - ocaml_ledger_root_hash = Fp::from(root_hash_deserialized); - - } - Err(e) => { - println!("Error: {:?}", e); - panic!() - } - } - - - let applied = ctx.apply_transaction(&tx); - println!("tx: {:?} applied: {:?}", tx, applied); - - // Add new accounts created by the transaction to the potential senders list - - if applied.is_ok() { - let new_accounts = match applied.unwrap().varying { - Varying::Command(command) => { - match command { - CommandApplied::SignedCommand(cmd) => { - match cmd.body { - signed_command_applied::Body::Payments { new_accounts } => Some(new_accounts), - _ => None - } - }, - CommandApplied::ZkappCommand(cmd) => { - Some(cmd.new_accounts) - } - } - }, - _ => unimplemented!() - }; - - if let Some(new_accounts) = new_accounts { - let new_accounts = ctx.potential_new_accounts - .iter() - .filter(|kp| new_accounts.iter().any(|acc| acc.public_key == kp.public.into_compressed())); - - for acc in new_accounts { - if !ctx - .potential_senders - .iter() - .any(|x| x.public == acc.public) - { - ctx.potential_senders.push(acc.clone()) - } - } - - ctx.potential_new_accounts.clear(); - } - } - - let rust_ledger_root_hash = ctx.get_ledger_root(); - - println!("ledger hash =>\n Rust: {:?}\n OCaml: {:?}", rust_ledger_root_hash, ocaml_ledger_root_hash); - assert!(ocaml_ledger_root_hash == rust_ledger_root_hash); - - } - - OCaml::unit() - } - -} diff --git a/ledger/src/lib.rs b/ledger/src/lib.rs index 49f94a86e..7fefb43e9 100644 --- a/ledger/src/lib.rs +++ b/ledger/src/lib.rs @@ -40,7 +40,7 @@ mod cache; #[cfg(all(not(target_family = "wasm"), feature = "ocaml-interop"))] mod ffi; -#[cfg(test)] +#[cfg(any(test, feature = "fuzzing"))] pub mod generators; mod account; diff --git a/ledger/src/mask/mask.rs b/ledger/src/mask/mask.rs index c97009d8e..f407bfde2 100644 --- a/ledger/src/mask/mask.rs +++ b/ledger/src/mask/mask.rs @@ -289,6 +289,21 @@ impl Mask { fn test_matrix(&self) -> HashesMatrix { self.with(|this| this.test_matrix()) } + + /// Use for fuzzing only + #[cfg(feature = "fuzzing")] + pub fn fuzzing_to_root(&self) -> Mask { + let accounts = self.to_list(); + let mut new_root = Self::create(self.depth() as usize); + + for account in accounts { + new_root + .get_or_create_account(account.id(), account) + .unwrap(); + } + + new_root + } } impl BaseLedger for Mask { diff --git a/ledger/src/scan_state/conv.rs b/ledger/src/scan_state/conv.rs index 0becbaad9..0455f6a92 100644 --- a/ledger/src/scan_state/conv.rs +++ b/ledger/src/scan_state/conv.rs @@ -1830,112 +1830,143 @@ impl From<&zkapp_command::ZkAppCommand> for MinaBaseZkappCommandTStableV1WireSta } } -impl TryFrom<&TransactionSnarkScanStateTransactionWithWitnessStableV2> for TransactionWithWitness { +impl TryFrom<&MinaTransactionLogicTransactionAppliedVaryingStableV2> + for transaction_applied::Varying +{ type Error = InvalidBigInt; fn try_from( - value: &TransactionSnarkScanStateTransactionWithWitnessStableV2, + value: &MinaTransactionLogicTransactionAppliedVaryingStableV2, ) -> Result { use mina_p2p_messages::v2::MinaTransactionLogicTransactionAppliedVaryingStableV2::*; use mina_p2p_messages::v2::MinaTransactionLogicTransactionAppliedCommandAppliedStableV2::*; use mina_p2p_messages::v2::MinaTransactionLogicTransactionAppliedSignedCommandAppliedBodyStableV2::*; - // use mina_p2p_messages::v2::TransactionSnarkPendingCoinbaseStackStateInitStackStableV1::{Base, Merge}; - use mina_p2p_messages::v2::MinaStateSnarkedLedgerStatePendingCoinbaseStackStateInitStackStableV1::{Base, Merge}; - use crate::scan_state::scan_state::transaction_snark::InitStack; use transaction_applied::signed_command_applied; - Ok(Self { - transaction_with_info: TransactionApplied { - previous_hash: value.transaction_with_info.previous_hash.to_field()?, - varying: match &value.transaction_with_info.varying { - Command(cmd) => match cmd { - SignedCommand(cmd) => transaction_applied::Varying::Command( - transaction_applied::CommandApplied::SignedCommand(Box::new( - transaction_applied::SignedCommandApplied { - common: transaction_applied::signed_command_applied::Common { - user_command: WithStatus { - data: (&cmd.common.user_command.data).try_into()?, - status: (&cmd.common.user_command.status).into(), - }, - }, - body: match &cmd.body { - Payment { new_accounts } => { - signed_command_applied::Body::Payments { - new_accounts: new_accounts - .iter() - .cloned() - .map(TryInto::try_into) - .collect::>()?, - } - } - StakeDelegation { previous_delegate } => { - signed_command_applied::Body::StakeDelegation { - previous_delegate: match previous_delegate.as_ref() { - Some(prev) => Some(prev.try_into()?), - None => None, - } - } - } - Failed => signed_command_applied::Body::Failed, - }, + let result = match value { + Command(cmd) => match cmd { + SignedCommand(cmd) => transaction_applied::Varying::Command( + transaction_applied::CommandApplied::SignedCommand(Box::new( + transaction_applied::SignedCommandApplied { + common: transaction_applied::signed_command_applied::Common { + user_command: WithStatus { + data: (&cmd.common.user_command.data).try_into()?, + status: (&cmd.common.user_command.status).into(), }, - )), - ), - ZkappCommand(cmd) => transaction_applied::Varying::Command( - transaction_applied::CommandApplied::ZkappCommand(Box::new( - transaction_applied::ZkappCommandApplied { - accounts: cmd - .accounts - .iter() - .map(|(id, account_opt)| { - let id: AccountId = id.try_into()?; - let account: Option = match account_opt.as_ref() { - Some(account) => Some(account.try_into()?), - None => None, - }; - let account = account.map(Box::new); - - Ok((id, account)) - }) - .collect::>()?, - command: WithStatus { - data: (&cmd.command.data).try_into()?, - status: (&cmd.command.status).into(), - }, - new_accounts: cmd.new_accounts.iter().map(TryInto::try_into).collect::>()?, - }, - )), - ), - }, - FeeTransfer(ft) => transaction_applied::Varying::FeeTransfer( - transaction_applied::FeeTransferApplied { - fee_transfer: WithStatus { - data: (&ft.fee_transfer.data).try_into()?, - status: (&ft.fee_transfer.status).into(), }, - new_accounts: ft.new_accounts.iter().map(TryInto::try_into).collect::>()?, - burned_tokens: ft.burned_tokens.clone().into(), + body: match &cmd.body { + Payment { new_accounts } => { + signed_command_applied::Body::Payments { + new_accounts: new_accounts + .iter() + .cloned() + .map(TryInto::try_into) + .collect::>()?, + } + } + StakeDelegation { previous_delegate } => { + signed_command_applied::Body::StakeDelegation { + previous_delegate: match previous_delegate.as_ref() { + Some(prev) => Some(prev.try_into()?), + None => None, + }, + } + } + Failed => signed_command_applied::Body::Failed, + }, }, - ), - Coinbase(cb) => transaction_applied::Varying::Coinbase(transaction_applied::CoinbaseApplied { - coinbase: WithStatus { - data: crate::scan_state::transaction_logic::Coinbase { - receiver: (&cb.coinbase.data.receiver).try_into()?, - amount: cb.coinbase.data.amount.clone().into(), - fee_transfer: match cb.coinbase.data.fee_transfer.as_ref() { - Some(ft) => Some(crate::scan_state::transaction_logic::CoinbaseFeeTransfer { + )), + ), + ZkappCommand(cmd) => transaction_applied::Varying::Command( + transaction_applied::CommandApplied::ZkappCommand(Box::new( + transaction_applied::ZkappCommandApplied { + accounts: cmd + .accounts + .iter() + .map(|(id, account_opt)| { + let id: AccountId = id.try_into()?; + let account: Option = match account_opt.as_ref() { + Some(account) => Some(account.try_into()?), + None => None, + }; + let account = account.map(Box::new); + + Ok((id, account)) + }) + .collect::>()?, + command: WithStatus { + data: (&cmd.command.data).try_into()?, + status: (&cmd.command.status).into(), + }, + new_accounts: cmd + .new_accounts + .iter() + .map(TryInto::try_into) + .collect::>()?, + }, + )), + ), + }, + FeeTransfer(ft) => { + transaction_applied::Varying::FeeTransfer(transaction_applied::FeeTransferApplied { + fee_transfer: WithStatus { + data: (&ft.fee_transfer.data).try_into()?, + status: (&ft.fee_transfer.status).into(), + }, + new_accounts: ft + .new_accounts + .iter() + .map(TryInto::try_into) + .collect::>()?, + burned_tokens: ft.burned_tokens.clone().into(), + }) + } + Coinbase(cb) => { + transaction_applied::Varying::Coinbase(transaction_applied::CoinbaseApplied { + coinbase: WithStatus { + data: crate::scan_state::transaction_logic::Coinbase { + receiver: (&cb.coinbase.data.receiver).try_into()?, + amount: cb.coinbase.data.amount.clone().into(), + fee_transfer: match cb.coinbase.data.fee_transfer.as_ref() { + Some(ft) => Some( + crate::scan_state::transaction_logic::CoinbaseFeeTransfer { receiver_pk: (&ft.receiver_pk).try_into()?, fee: Fee::from_u64(ft.fee.as_u64()), - }), - None => None, - } + }, + ), + None => None, }, - status: (&cb.coinbase.status).into(), }, - new_accounts: cb.new_accounts.iter().map(TryInto::try_into).collect::>()?, - burned_tokens: cb.burned_tokens.clone().into(), - }), - }, + status: (&cb.coinbase.status).into(), + }, + new_accounts: cb + .new_accounts + .iter() + .map(TryInto::try_into) + .collect::>()?, + burned_tokens: cb.burned_tokens.clone().into(), + }) + } + }; + + Ok(result) + } +} + +impl TryFrom<&TransactionSnarkScanStateTransactionWithWitnessStableV2> for TransactionWithWitness { + type Error = InvalidBigInt; + + fn try_from( + value: &TransactionSnarkScanStateTransactionWithWitnessStableV2, + ) -> Result { + // use mina_p2p_messages::v2::TransactionSnarkPendingCoinbaseStackStateInitStackStableV1::{Base, Merge}; + use mina_p2p_messages::v2::MinaStateSnarkedLedgerStatePendingCoinbaseStackStateInitStackStableV1::{Base, Merge}; + use crate::scan_state::scan_state::transaction_snark::InitStack; + + Ok(Self { + transaction_with_info: TransactionApplied { + previous_hash: value.transaction_with_info.previous_hash.to_field()?, + varying: (&value.transaction_with_info.varying).try_into()?, }, state_hash: { let (state, body) = &value.state_hash; diff --git a/ledger/src/scan_state/transaction_logic.rs b/ledger/src/scan_state/transaction_logic.rs index a12c708cb..0369c2ada 100644 --- a/ledger/src/scan_state/transaction_logic.rs +++ b/ledger/src/scan_state/transaction_logic.rs @@ -1754,6 +1754,29 @@ pub mod zkapp_command { pub epoch_length: Numeric, } + #[cfg(feature = "fuzzing")] + impl EpochData { + pub fn new( + ledger: EpochLedger, + seed: Hash, + start_checkpoint: Hash, + lock_checkpoint: Hash, + epoch_length: Numeric, + ) -> Self { + EpochData { + ledger, + seed, + start_checkpoint, + lock_checkpoint, + epoch_length, + } + } + + pub fn ledger_mut(&mut self) -> &mut EpochLedger { + &mut self.ledger + } + } + impl ToInputs for EpochData { /// https://github.com/MinaProtocol/mina/blob/3fe924c80a4d01f418b69f27398f5f93eb652514/src/lib/mina_base/zkapp_precondition.ml#L875 fn to_inputs(&self, inputs: &mut Inputs) { @@ -2297,6 +2320,25 @@ pub mod zkapp_command { pub valid_while: Numeric, } + #[cfg(feature = "fuzzing")] + impl Preconditions { + pub fn new( + network: ZkAppPreconditions, + account: AccountPreconditions, + valid_while: Numeric, + ) -> Self { + Self { + network, + account, + valid_while, + } + } + + pub fn network_mut(&mut self) -> &mut ZkAppPreconditions { + &mut self.network + } + } + impl ToFieldElements for Preconditions { fn to_field_elements(&self, fields: &mut Vec) { let Self { @@ -7847,7 +7889,7 @@ where l } -#[cfg(test)] +#[cfg(any(test, feature = "fuzzing"))] pub mod for_tests { use mina_signer::Keypair; use rand::Rng; diff --git a/ledger/src/staged_ledger/staged_ledger.rs b/ledger/src/staged_ledger/staged_ledger.rs index 9c18088ef..06af06cdf 100644 --- a/ledger/src/staged_ledger/staged_ledger.rs +++ b/ledger/src/staged_ledger/staged_ledger.rs @@ -116,6 +116,16 @@ pub struct StagedLedger { } impl StagedLedger { + #[cfg(feature = "fuzzing")] + pub fn ledger_ref(&self) -> &Mask { + &self.ledger + } + + #[cfg(feature = "fuzzing")] + pub fn ledger_mut(&mut self) -> &mut Mask { + &mut self.ledger + } + pub fn proof_txns_with_state_hashes( &self, ) -> Option, Fp, Slot)>>> { diff --git a/tools/fuzzing/Cargo.toml b/tools/fuzzing/Cargo.toml new file mode 100644 index 000000000..17aa8b602 --- /dev/null +++ b/tools/fuzzing/Cargo.toml @@ -0,0 +1,53 @@ +[package] +name = "transaction_fuzzer" +version = "0.1.0" +edition = "2021" + + +[dependencies] +ark-serialize = "0.4.2" +mina-hasher = { workspace = true } +mina-signer = { workspace = true } +mina-curves = { workspace = true } +mina-p2p-messages = { workspace = true } +openmina-core = { path = "../../core" } +ledger = { path = "../../ledger", package = "mina-tree", features = ["fuzzing"] } +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +flate2 = "1.0.24" +md5 = "0.7.0" +btreemultimap = "0.1.1" +bitvec = "1.0.1" +object = "0.36.4" +rsprocmaps = "0.3.2" +leb128 = "0.2.1" +rand = { version = "0.8.5", features = ["small_rng"] } +ring_buffer = "2.0.2" +ark-ff = { version = "0.3.0", features = [ "parallel", "asm", "std" ] } +ark-ec = { version = "0.3.0", features = [ "std" ] } +#ark-ff = { git = "https://github.com/openmina/algebra", branch = "openmina", features = [ "parallel", "asm", "std" ] } +#ark-ec = { git = "https://github.com/openmina/algebra", branch = "openmina", features = [ "std" ] } +once_cell = "1.18.0" +text-diff = "0.4.0" +num-bigint = "0.4.0" +tuple-map = "0.4.0" +itertools = "0.11.0" +#binprot = "0.1.7" +binprot = { git = "https://github.com/openmina/binprot-rs", rev = "2b5a909" } +binprot_derive = { git = "https://github.com/openmina/binprot-rs", rev = "2b5a909" } +clap = "4.5.20" + + +[profile.release] +opt-level = 3 +debug = 2 +debug-assertions = true +overflow-checks = true +lto = true +panic = "abort" + +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(unstable)'] } + +[features] +nightly = [] \ No newline at end of file diff --git a/tools/fuzzing/Dockerfile b/tools/fuzzing/Dockerfile new file mode 100644 index 000000000..301471ac4 --- /dev/null +++ b/tools/fuzzing/Dockerfile @@ -0,0 +1,78 @@ +# FIXME: not working :( +FROM debian:bullseye + +RUN apt -y update && \ + apt -y upgrade && \ + apt -y install \ + apt-transport-https \ + ca-certificates \ + pkg-config \ + build-essential \ + curl \ + git \ + dnsutils \ + dumb-init \ + gettext \ + gnupg2 \ + unzip \ + bubblewrap \ + jq \ + libgmp10 \ + libgomp1 \ + libssl1.1 \ + libpq-dev \ + libffi-dev \ + libgmp-dev \ + libssl-dev \ + libbz2-dev \ + zlib1g-dev \ + m4 \ + libsodium-dev \ + libjemalloc-dev \ + procps \ + python3 \ + tzdata \ + liblmdb-dev \ + rsync + +SHELL ["/bin/bash", "-c"] + +RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y +RUN yes "" | sh <(curl -fsSL https://raw.githubusercontent.com/ocaml/opam/master/shell/install.sh) +RUN ARCH=$(uname -m) && \ + if [ "$ARCH" = "aarch64" ]; then \ + ARCH="arm64"; \ + elif [ "$ARCH" = "x86_64" ]; then \ + ARCH="amd64"; \ + else \ + echo "Unsupported architecture: $ARCH" && exit 1; \ + fi && \ + rm -rf /usr/local/go && \ + curl -sSL https://go.dev/dl/go1.19.5.linux-$ARCH.tar.gz | tar -C /usr/local -xzf - +RUN export PATH=$PATH:/usr/local/go/bin +RUN curl -sSL https://capnproto.org/capnproto-c++-0.10.2.tar.gz | tar -zxf - \ + && cd capnproto-c++-0.10.2 \ + && ./configure \ + && make -j6 check \ + && make install \ + && cd .. \ + && rm -rf capnproto-c++-0.10.2 + +RUN git clone https://github.com/openmina/mina.git +WORKDIR /mina +RUN git submodule update --init --recursive && \ + git config --local --add submodule.recurse true && \ + git checkout openmina/fuzzer +RUN opam init --disable-sandboxing && \ + opam switch create .&& \ + eval $(opam config env) && \ + opam switch import -y opam.export && \ + ./scripts/pin-external-packages.sh +RUN source ~/.cargo/env && \ + eval $(opam config env) && \ + export PATH=$PATH:/usr/local/go/bin && \ + export DUNE_PROFILE=devnet && \ + make libp2p_helper && \ + dune build src/app/transaction_fuzzer/transaction_fuzzer.exe --profile=devnet +CMD ["/mina/_build/default/src/app/transaction_fuzzer/transaction_fuzzer.exe", "execute"] + diff --git a/tools/fuzzing/build.rs b/tools/fuzzing/build.rs new file mode 100644 index 000000000..ad294606a --- /dev/null +++ b/tools/fuzzing/build.rs @@ -0,0 +1,16 @@ +use std::process::Command; + +fn main() { + let output = Command::new("rustc") + .args(["-vV"]) + .output() + .expect("Failed to execute rustc"); + + let stdout = String::from_utf8(output.stdout).unwrap(); + + if stdout.contains("nightly") { + println!("cargo:rustc-cfg=feature=\"nightly\""); + } + + println!("cargo:rerun-if-changed=build.rs"); +} diff --git a/tools/fuzzing/rust-toolchain.toml b/tools/fuzzing/rust-toolchain.toml new file mode 100644 index 000000000..a25e7e060 --- /dev/null +++ b/tools/fuzzing/rust-toolchain.toml @@ -0,0 +1,3 @@ +[toolchain] +channel = "nightly" + diff --git a/tools/fuzzing/src/context.rs b/tools/fuzzing/src/context.rs new file mode 100644 index 000000000..96710d111 --- /dev/null +++ b/tools/fuzzing/src/context.rs @@ -0,0 +1,1354 @@ +use crate::generator::{Generator, GeneratorRange32, GeneratorRange64}; +use crate::mutator::Mutator; +use crate::{deserialize, serialize}; +use ark_ff::Zero; +//use binprot::macros::BinProtWrite; +use ledger::scan_state::currency::{Amount, Fee, Length, Magnitude, Nonce, Signed, Slot}; +use ledger::scan_state::transaction_logic::protocol_state::{ + protocol_state_view, EpochData, EpochLedger, ProtocolStateView, +}; +use ledger::scan_state::transaction_logic::transaction_applied::{ + signed_command_applied, CommandApplied, TransactionApplied, Varying, +}; +use ledger::scan_state::transaction_logic::{ + apply_transactions, Transaction, TransactionStatus, UserCommand, +}; +use ledger::sparse_ledger::LedgerIntf; +use ledger::staged_ledger::staged_ledger::StagedLedger; +use ledger::{dummy, Account, AccountId, Database, Mask, Timing, TokenId}; +use mina_hasher::Fp; +use mina_p2p_messages::binprot::SmallString1k; +use mina_p2p_messages::{ + bigint, binprot, + v2::{ + MinaTransactionLogicTransactionAppliedStableV2, + TransactionSnarkScanStateLedgerProofWithSokMessageStableV2, + }, +}; +use mina_signer::{CompressedPubKey, Keypair}; +use openmina_core::constants::ConstraintConstants; +use rand::{rngs::SmallRng, seq::SliceRandom, Rng, SeedableRng}; +use ring_buffer::RingBuffer; +use std::collections::HashMap; +use std::fmt::Debug; +use std::{fs, str::FromStr}; + +/// Same values when we run `dune runtest src/lib/staged_ledger -f` +pub const CONSTRAINT_CONSTANTS: ConstraintConstants = ConstraintConstants { + sub_windows_per_window: 11, + ledger_depth: 35, + work_delay: 2, + block_window_duration_ms: 180000, + transaction_capacity_log_2: 7, + pending_coinbase_depth: 5, + coinbase_amount: 720000000000, + supercharged_coinbase_factor: 2, + account_creation_fee: 1000000000, + fork: None, +}; + +// Taken from ocaml_tests +/// Same values when we run `dune runtest src/lib/staged_ledger -f` +#[coverage(off)] +fn dummy_state_view(global_slot_since_genesis: Option) -> ProtocolStateView { + // TODO: Use OCaml implementation, not hardcoded value + let f = #[coverage(off)] + |s: &str| Fp::from_str(s).unwrap(); + + ProtocolStateView { + snarked_ledger_hash: f( + "19095410909873291354237217869735884756874834695933531743203428046904386166496", + ), + blockchain_length: Length::from_u32(1), + min_window_density: Length::from_u32(77), + total_currency: Amount::from_u64(10016100000000000), + global_slot_since_genesis: global_slot_since_genesis.unwrap_or_else(Slot::zero), + staking_epoch_data: EpochData { + ledger: EpochLedger { + hash: f( + "19095410909873291354237217869735884756874834695933531743203428046904386166496", + ), + total_currency: Amount::from_u64(10016100000000000), + }, + seed: Fp::zero(), + start_checkpoint: Fp::zero(), + lock_checkpoint: Fp::zero(), + epoch_length: Length::from_u32(1), + }, + next_epoch_data: EpochData { + ledger: EpochLedger { + hash: f( + "19095410909873291354237217869735884756874834695933531743203428046904386166496", + ), + total_currency: Amount::from_u64(10016100000000000), + }, + seed: f( + "18512313064034685696641580142878809378857342939026666126913761777372978255172", + ), + start_checkpoint: Fp::zero(), + lock_checkpoint: f( + "9196091926153144288494889289330016873963015481670968646275122329689722912273", + ), + epoch_length: Length::from_u32(2), + }, + } +} + +#[coverage(off)] +pub fn dummy_state_and_view( + global_slot: Option, +) -> ( + mina_p2p_messages::v2::MinaStateProtocolStateValueStableV2, + ProtocolStateView, +) { + let mut state = dummy::for_tests::dummy_protocol_state(); + + if let Some(global_slot) = global_slot { + let new_global_slot = global_slot; + + let global_slot_since_genesis = { + let since_genesis = &state.body.consensus_state.global_slot_since_genesis; + let curr = &state + .body + .consensus_state + .curr_global_slot_since_hard_fork + .slot_number; + + let since_genesis = Slot::from_u32(since_genesis.as_u32()); + let curr = Slot::from_u32(curr.as_u32()); + + (since_genesis.checked_sub(&curr).unwrap()) + .checked_add(&new_global_slot) + .unwrap() + }; + + let cs = &mut state.body.consensus_state; + cs.curr_global_slot_since_hard_fork.slot_number = (&new_global_slot).into(); + cs.global_slot_since_genesis = (&global_slot_since_genesis).into(); + }; + + let view = protocol_state_view(&state); + + (state, view) +} + +pub enum PermissionModel { + Any, // Allow any (random) combination of permissions + Empty, // Permissions are always set to None + Initial, // Permissions are always set to "user_default" set (signature only). + Default, // "default" permissions as set by SnarkyJS when deploying a zkApp. + TokenOwner, // permission set usually set in Token owner zkApps +} + +impl Clone for PermissionModel { + #[coverage(off)] + fn clone(&self) -> Self { + match self { + PermissionModel::Any => PermissionModel::Any, + PermissionModel::Empty => PermissionModel::Empty, + PermissionModel::Initial => PermissionModel::Initial, + PermissionModel::Default => PermissionModel::Default, + PermissionModel::TokenOwner => PermissionModel::TokenOwner, + } + } +} + +// #[derive(BinProtWrite, Debug)] +// pub struct TxProofCreateInputs { +// pub sok_message: MinaBaseSokMessageStableV1, +// pub snarked_ledger_state: MinaStateSnarkedLedgerStateStableV2, +// pub witness: TxWitness, +// } + +// #[derive(BinProtWrite, Debug)] +// pub struct TxWitness { +// pub transaction: Tx, +// pub first_pass_ledger: MinaBaseSparseLedgerBaseStableV2, +// pub second_pass_ledger: MinaBaseSparseLedgerBaseStableV2, +// pub protocol_state_body: MinaStateProtocolStateBodyValueStableV2, +// pub init_stack: MinaBasePendingCoinbaseStackVersionedStableV1, +// pub status: MinaBaseTransactionStatusStableV2, +// pub block_global_slot: UnsignedExtendedUInt32StableV1, +// } + +#[derive(Debug)] +pub struct ApplyTxResult { + root_hash: Fp, + apply_result: Vec, + error: String, +} + +impl binprot::BinProtRead for ApplyTxResult { + #[coverage(off)] + fn binprot_read(r: &mut R) -> Result + where + Self: Sized, + { + let root_hash: Fp = bigint::BigInt::binprot_read(r)?.into(); + // Start of Selection + let apply_result = Vec::::binprot_read(r)? + .into_iter() + .map( + #[coverage(off)] + |MinaTransactionLogicTransactionAppliedStableV2 { + previous_hash, + varying, + }| TransactionApplied { + previous_hash: (&previous_hash.0).into(), + varying: (&varying).into(), + }, + ) + .collect(); + let error: String = SmallString1k::binprot_read(r)?.0; + + Ok(ApplyTxResult { + root_hash, + apply_result, + error, + }) + } +} + +// // TODO: remove this type once `Transaction` implements `BinProtWrite`. +// #[derive(BinProtWrite, Debug, Clone)] +// pub enum Tx { +// UserCommand(UserCommand), +// } + +// impl From for Transaction { +// fn from(value: Tx) -> Self { +// match value { +// Tx::UserCommand(v) => Self::Command(v), +// } +// } +// } + +// impl From for Tx { +// fn from(value: Transaction) -> Self { +// match value { +// Transaction::Command(v) => Tx::UserCommand(v), +// _ => unimplemented!(), +// } +// } +// } + +pub enum LedgerKind { + Mask(Mask), + Staged(StagedLedger, Mask), +} + +impl Clone for LedgerKind { + #[coverage(off)] + fn clone(&self) -> Self { + match self { + Self::Mask(ledger) => Self::Mask(ledger.copy()), + Self::Staged(ledger, snarked_ledger) => { + Self::Staged(ledger.clone(), snarked_ledger.clone()) + } + } + } +} + +pub struct FuzzerState { + pub ledger: LedgerKind, + pub potential_senders: Vec<(Keypair, PermissionModel)>, + pub potential_new_accounts: Vec<(Keypair, PermissionModel)>, + pub cache_pool: RingBuffer, + pub cache_apply: RingBuffer, +} + +impl Clone for FuzzerState { + #[coverage(off)] + fn clone(&self) -> Self { + Self { + ledger: self.ledger.clone(), + potential_senders: self.potential_senders.clone(), + potential_new_accounts: self.potential_new_accounts.clone(), + cache_pool: self.cache_pool.clone(), + cache_apply: self.cache_apply.clone(), + } + } +} + +pub struct GeneratorCtx { + pub rng: SmallRng, + pub max_account_balance: u64, + pub minimum_fee: u64, + pub excess_fee: Signed, + pub token_id: TokenId, + pub tx_proof: Option, + pub nonces: HashMap, // TODO: implement hash trait for CompressedPubKey + /// Attempt to produce a valid zkapp + pub attempt_valid_zkapp: bool, +} + +pub struct FuzzerCtx { + pub constraint_constants: ConstraintConstants, + pub txn_state_view: ProtocolStateView, + pub fuzzcases_path: String, + pub gen: GeneratorCtx, + pub state: FuzzerState, + pub snapshots: RingBuffer, +} + +impl FuzzerCtx { + #[coverage(off)] + fn get_ledger_inner(&self) -> &Mask { + match &self.state.ledger { + LedgerKind::Mask(ledger) => ledger, + LedgerKind::Staged(ledger, _) => ledger.ledger_ref(), + } + } + + #[coverage(off)] + fn get_ledger_inner_mut(&mut self) -> &mut Mask { + match &mut self.state.ledger { + LedgerKind::Mask(ledger) => ledger, + LedgerKind::Staged(ledger, _) => ledger.ledger_mut(), + } + } + + #[coverage(off)] + fn get_snarked_ledger_inner_mut(&mut self) -> Option<&mut Mask> { + match &mut self.state.ledger { + LedgerKind::Mask(_) => None, + LedgerKind::Staged(_, snarked_ledger) => Some(snarked_ledger), + } + } + + // #[coverage(off)] + // fn set_snarked_ledger(&mut self, snarked_ledger: Mask) { + // match &mut self.state.ledger { + // LedgerKind::Mask(_) => panic!(), + // LedgerKind::Staged(_, old_snarked_ledger) => *old_snarked_ledger = snarked_ledger, + // } + // } + + // #[coverage(off)] + // fn get_staged_ledger(&mut self) -> &mut StagedLedger { + // match &mut self.state.ledger { + // LedgerKind::Staged(ledger, _) => ledger, + // _ => panic!(), + // } + // } + + #[coverage(off)] + pub fn get_snarked_ledger(&mut self) -> &mut Mask { + match &mut self.state.ledger { + LedgerKind::Staged(_, ledger) => ledger, + _ => panic!(), + } + } + + #[coverage(off)] + pub fn create_inital_accounts(&mut self, n: usize) { + for _ in 0..n { + loop { + let keypair: Keypair = self.gen(); + + if !self.state.potential_senders.iter().any( + #[coverage(off)] + |(kp, _)| kp.public == keypair.public, + ) { + let pk_compressed = keypair.public.into_compressed(); + let account_id = AccountId::new(pk_compressed, TokenId::default()); + let mut account = Account::initialize(&account_id); + + account.balance = GeneratorRange64::gen_range( + self, + 1_000_000_000_000..=self.gen.max_account_balance, + ); + account.nonce = GeneratorRange32::gen_range(self, 0..=u32::MAX); + account.timing = Timing::Untimed; + + let permission_model = self.gen(); + self.state + .potential_senders + .push((keypair, permission_model)); + + if let Some(snarked_ledger) = self.get_snarked_ledger_inner_mut() { + snarked_ledger + .create_new_account(account_id.clone(), account.clone()) + .unwrap(); + }; + + self.get_ledger_inner_mut() + .create_new_account(account_id, account) + .unwrap(); + + break; + } + } + } + } + + #[coverage(off)] + pub fn get_account(&mut self, pkey: &CompressedPubKey) -> Option { + let account_location = LedgerIntf::location_of_account( + self.get_ledger_inner(), + &AccountId::new(pkey.clone(), TokenId::default()), + ); + + account_location.map( + #[coverage(off)] + |location| *(LedgerIntf::get(self.get_ledger_inner(), &location).unwrap()).clone(), + ) + } + + #[coverage(off)] + pub fn find_sender(&mut self, pkey: &CompressedPubKey) -> Option<&(Keypair, PermissionModel)> { + self.state.potential_senders.iter().find( + #[coverage(off)] + |(kp, _)| kp.public.into_compressed() == *pkey, + ) + } + + #[coverage(off)] + pub fn find_permissions(&mut self, pkey: &CompressedPubKey) -> Option<&PermissionModel> { + self.find_sender(pkey).map( + #[coverage(off)] + |(_, pm)| pm, + ) + } + + #[coverage(off)] + pub fn find_keypair(&mut self, pkey: &CompressedPubKey) -> Option<&Keypair> { + self.find_sender(pkey).map( + #[coverage(off)] + |(kp, _)| kp, + ) + } + + #[coverage(off)] + pub fn random_keypair(&mut self) -> Keypair { + self.state + .potential_senders + .choose(&mut self.gen.rng) + .unwrap() + .0 + .clone() + } + + #[coverage(off)] + pub fn random_ntransactions(&mut self) -> usize { + self.gen.rng.gen_range(0..400) + } + + #[coverage(off)] + pub fn random_snark_worker_fee(&mut self) -> Fee { + let fee = self.gen.rng.gen_range(0..10_000_000); + Fee::from_u64(fee) + } + + #[coverage(off)] + pub fn random_user_command(&mut self) -> UserCommand { + if self.gen.rng.gen_bool(0.9) { + if !self.state.cache_apply.is_empty() { + // Pick transaction from the applied tx cache and mutate it + let index = self.gen.rng.gen_range(0..self.state.cache_apply.len()); + + if let Some(mut transaction) = self.state.cache_apply.get_relative(index).cloned() { + self.mutate(&mut transaction); + return transaction; + } + } + + // If we can't find a tx in the applied cache, try one from the pool cache + if self.gen.rng.gen_bool(0.5) && !self.state.cache_pool.is_empty() { + let index = self.gen.rng.gen_range(0..self.state.cache_pool.len()); + + if let Some(mut transaction) = self.state.cache_pool.get_relative(index).cloned() { + self.mutate(&mut transaction); + return transaction; + } + } + } + + // Generate random transaction + self.gen() + } + + #[coverage(off)] + pub fn random_tx_proof( + &mut self, + ) -> TransactionSnarkScanStateLedgerProofWithSokMessageStableV2 { + let mut proof = self + .gen + .tx_proof + .clone() + .expect("valid tx proof not set for FuzzerCtx"); + self.mutate(&mut proof); + proof + } + + // #[coverage(off)] + // pub fn random_create_tx_proof_inputs( + // &mut self, + // protocol_state_body: MinaStateProtocolStateBodyValueStableV2, + // mut verify_tx: F, + // ) -> Option<(bool, TxProofCreateInputs)> + // where + // F: FnMut(&Transaction) -> bool, + // { + // let state_body_hash = MinaHash::hash(&protocol_state_body); + // let block_global_slot = protocol_state_body + // .consensus_state + // .global_slot_since_genesis + // .clone(); + // let init_stack = protocol_state_body + // .blockchain_state + // .ledger_proof_statement + // .source + // .pending_coinbase_stack + // .clone(); + + // let tx = self.random_transaction(); + // let is_valid = verify_tx(&tx); + // let transaction = WithStatus::applied(tx.clone()); + // let tx = Tx::from(tx); + + // let ledger = self.get_ledger_inner().make_child(); + // let staged_ledger = + // StagedLedger::create_exn(self.constraint_constants.clone(), ledger).unwrap(); + // let apply_res = StagedLedger::update_ledger_and_get_statements( + // &self.constraint_constants, + // // TODO(binier): construct from passed protocol_state_body. + // self.txn_state_view.global_slot_since_genesis, + // staged_ledger.ledger(), + // &(&init_stack).into(), + // (vec![transaction.clone()], None), + // // TODO(binier): construct from passed protocol_state_body. + // &self.txn_state_view, + // ( + // // TODO(binier): use state hash instead. Not used anyways though. + // state_body_hash, + // state_body_hash, + // ), + // ); + // let tx_with_witness = match apply_res { + // Ok((txs_with_witness, ..)) => txs_with_witness.into_iter().next().unwrap(), + // Err(_) => return None, + // }; + + // Some(( + // is_valid, + // TxProofCreateInputs { + // sok_message: serde_json::from_value(serde_json::json!({ + // "fee":"25000000", + // "prover":"B62qn7G9oFofQDGiAoP8TmYce7185PjWJ39unqjr2v7EgsRDoFCFc1k" + // })) + // .unwrap(), + // snarked_ledger_state: (&tx_with_witness.statement).into(), + // witness: TxWitness { + // transaction: tx, + // first_pass_ledger: (&tx_with_witness.first_pass_ledger_witness).into(), + // second_pass_ledger: (&tx_with_witness.second_pass_ledger_witness).into(), + // protocol_state_body, + // // TODO(binier): should we somehow use value from `tx_with_witness`? + // init_stack, + // status: MinaBaseTransactionStatusStableV2::Applied, + // block_global_slot, + // }, + // }, + // )) + // } + + #[coverage(off)] + pub fn take_snapshot(&mut self) { + println!("Taking snapshot..."); + self.snapshots.push_back(self.state.clone()); + } + + #[coverage(off)] + pub fn restore_snapshot(&mut self) { + if !self.snapshots.is_empty() { + // Pick random snapshot + let index = self.gen.rng.gen_range(0..self.snapshots.len()); + + if let Some(state) = self.snapshots.get_relative(index).cloned() { + println!("Restoring snapshot {}...", index); + self.state = state; + } + } + } + + // #[coverage(off)] + // pub fn serialize_transaction(tx: &Transaction) -> Vec { + // /* + // We don't have generated types for Transaction, but we have one + // for UserCommand (MinaBaseUserCommandStableV2). Extract and + // serialize the inner UserCommand and let a OCaml wrapper build + // the transaction. + // */ + // match &tx { + // Transaction::Command(user_command) => serialize(user_command), + // _ => unimplemented!(), + // } + // } + + // #[coverage(off)] + // pub fn serialize_ledger(&self) -> Vec { + // serialize(&self.get_ledger_accounts()) + // } + + #[coverage(off)] + fn save_fuzzcase(&self, tx: &Transaction, filename: &String) { + let filename = self.fuzzcases_path.clone() + &filename + ".fuzzcase"; + + println!("Saving fuzzcase: {}", filename); + + let user_command = match tx { + Transaction::Command(user_command) => user_command.clone(), + _ => unimplemented!(), + }; + + let mut file = fs::OpenOptions::new() + .write(true) + .truncate(true) + .create(true) + .open(filename) + .unwrap(); + + serialize(&(self.get_ledger_accounts(), user_command), &mut file); + } + + #[coverage(off)] + pub fn load_fuzzcase(&mut self, file_path: &String) -> UserCommand { + println!("Loading fuzzcase: {}", file_path); + let bytes = fs::read(file_path).unwrap(); + let (accounts, user_command): (Vec, UserCommand) = + deserialize(&mut bytes.as_slice()); + + let depth = self.constraint_constants.ledger_depth as usize; + let root = Mask::new_root(Database::create(depth.try_into().unwrap())); + + *self.get_ledger_inner_mut() = root.make_child(); + + for account in accounts { + self.get_ledger_inner_mut() + .create_new_account(account.id(), account) + .unwrap(); + } + + user_command + } + + // #[coverage(off)] + // pub fn apply_staged_ledger_diff( + // &mut self, + // diff: Diff, + // global_slot: Slot, + // coinbase_receiver: CompressedPubKey, + // current_view: ProtocolStateView, + // state_hashes: (Fp, Fp), + // state_tbl: &HashMap, + // iteration: usize, + // ) -> Result, ()> { + // if iteration == 1271 { + // #[derive(Clone, Debug, PartialEq, BinProtRead, BinProtWrite)] + // struct State { + // scan_state: mina_p2p_messages::v2::TransactionSnarkScanStateStableV2, + // pending_coinbase_collection: mina_p2p_messages::v2::MinaBasePendingCoinbaseStableV2, + // states: Vec<( + // mina_p2p_messages::bigint::BigInt, + // mina_p2p_messages::v2::MinaStateProtocolStateValueStableV2, + // )>, + // snarked_ledger: Vec, + // expected_staged_ledger_merkle_root: mina_p2p_messages::bigint::BigInt, + // } + + // let sl = self.get_staged_ledger(); + // let sc = sl.scan_state.clone(); + // let pcc = sl.pending_coinbase_collection.clone(); + // let expected_staged_ledger_merkle_root = sl.ledger().clone().merkle_root(); + // let snarked_ledger = self.get_snarked_ledger(); + + // let state = State { + // scan_state: (&sc).into(), + // pending_coinbase_collection: (&pcc).into(), + // states: state_tbl + // .iter() + // .map(|(h, v)| (h.into(), v.clone())) + // .collect(), + // snarked_ledger: { + // snarked_ledger + // .to_list() + // .into_iter() + // .map(Into::into) + // .collect() + // // todo!() + // }, + // expected_staged_ledger_merkle_root: expected_staged_ledger_merkle_root.into(), + // }; + + // let mut file = std::fs::File::create("/tmp/state.bin").unwrap(); + // BinProtWrite::binprot_write(&state, &mut file).unwrap(); + // file.sync_all().unwrap(); + + // eprintln!("data saved"); + // } + + // let constraint_constants = self.constraint_constants.clone(); + + // let DiffResult { + // hash_after_applying, + // ledger_proof, + // pending_coinbase_update: _, + // } = self + // .get_staged_ledger() + // .apply( + // None, + // &constraint_constants, + // global_slot, + // diff, + // (), + // &Verifier, + // ¤t_view, + // state_hashes, + // coinbase_receiver, + // false, + // ) + // .map_err(|_| ())?; + // // .unwrap(); + + // if let Some((proof, _transactions)) = ledger_proof { + // self.update_snarked_ledger(state_tbl, proof) + // }; + + // self.get_staged_ledger().commit_and_reparent_to_root(); + + // Ok(hash_after_applying) + // } + + // #[coverage(off)] + // fn update_snarked_ledger( + // &mut self, + // state_tbl: &HashMap, + // proof: LedgerProof, + // ) { + // let target_snarked_ledger = { + // let stmt = proof.statement_ref(); + // stmt.target.first_pass_ledger + // }; + + // let apply_first_pass = |global_slot: Slot, + // txn_state_view: &ProtocolStateView, + // ledger: &mut Mask, + // transaction: &Transaction| { + // apply_transaction_first_pass( + // &CONSTRAINT_CONSTANTS, + // global_slot, + // txn_state_view, + // ledger, + // transaction, + // ) + // }; + + // let apply_second_pass = |ledger: &mut Mask, tx: TransactionPartiallyApplied| { + // apply_transaction_second_pass(&CONSTRAINT_CONSTANTS, ledger, tx) + // }; + + // let apply_first_pass_sparse_ledger = + // |global_slot: Slot, + // txn_state_view: &ProtocolStateView, + // sparse_ledger: &mut SparseLedger, + // transaction: &Transaction| { + // apply_transaction_first_pass( + // &CONSTRAINT_CONSTANTS, + // global_slot, + // txn_state_view, + // sparse_ledger, + // transaction, + // ) + // }; + + // let mut ledger = self.get_snarked_ledger().fuzzing_to_root(); + + // let get_state = |hash: Fp| Ok(state_tbl.get(&hash).cloned().unwrap()); + + // assert!(self + // .get_staged_ledger() + // .scan_state() + // .latest_ledger_proof() + // .is_some()); + + // self.get_staged_ledger() + // .scan_state() + // .get_snarked_ledger_sync( + // &mut ledger, + // get_state, + // apply_first_pass, + // apply_second_pass, + // apply_first_pass_sparse_ledger, + // ) + // .unwrap(); + + // eprintln!("#############################################################"); + // eprintln!(" NEW SNARKED LEDGER: {:?}", target_snarked_ledger); + // eprintln!("#############################################################"); + + // assert_eq!(ledger.merkle_root(), target_snarked_ledger); + // self.set_snarked_ledger(ledger); + // assert_eq!( + // self.get_snarked_ledger().merkle_root(), + // target_snarked_ledger + // ); + // } + + // #[coverage(off)] + // pub fn create_staged_ledger_diff( + // &mut self, + // txns: Vec, + // global_slot: Slot, + // prover: CompressedPubKey, + // coinbase_receiver: CompressedPubKey, + // current_view: ProtocolStateView, + // ocaml_result: &Result< + // ( + // StagedLedgerDiffDiffStableV2, + // Vec<(transaction_logic::valid::UserCommand, String)>, + // ), + // String, + // >, + // iteration: usize, + // snark_worker_fees: &mut Vec, + // ) -> Result, ()> { + // eprintln!(); + // eprintln!("###################################################"); + // eprintln!(" CREATE_STAGED_LEDGER_DIFF "); + // eprintln!("###################################################"); + + // eprintln!( + // "get_staged_ledger num_account={:?}", + // self.get_staged_ledger().ledger.account_locations().len() + // ); + // // get_staged_ledger + // self.gen.nonces.clear(); + + // let stmt_to_work_random_prover = |stmt: &Statement| -> Option { + // let fee = snark_worker_fees.pop().unwrap(); + // Some(Checked { + // fee, + // proofs: stmt.map(|statement| { + // LedgerProof::create( + // statement.clone(), + // SokDigest::default(), + // dummy::dummy_transaction_proof(), + // ) + // }), + // prover: prover.clone(), + // }) + // }; + + // let txns = txns + // .into_iter() + // .map(|tx| { + // let Transaction::Command(cmd) = tx else { + // unreachable!() + // }; + // cmd.to_valid() + // }) + // .collect(); + + // let constraint_constants = self.constraint_constants.clone(); + + // dbg!(global_slot); + + // let result = self.get_staged_ledger().create_diff( + // &constraint_constants, + // global_slot, + // None, + // coinbase_receiver, + // (), + // ¤t_view, + // txns, + // stmt_to_work_random_prover, + // false, // Always false on berkeleynet now + // ); + + // // FIXME: ignoring error messages + // if result.is_err() && ocaml_result.is_err() { + // return Ok(None); + // } + + // if !(result.is_ok() && ocaml_result.is_ok()) { + // println!( + // "!!! create_staged_ledger_diff mismatch between OCaml and Rust (result is_ok)\n{:?}\n{:?}\n", + // result, ocaml_result + // ); + + // //let bigint: num_bigint::BigUint = ledger.merkle_root().into(); + // //self.save_fuzzcase(tx, &bigint.to_string()); + // return Err(()); + // } + + // let (diff, invalid_cmds) = result.unwrap(); + // let (ocaml_diff, ocaml_invalid_cmds) = ocaml_result.as_ref().unwrap(); + + // if iteration == 1271 { + // let mut file = std::fs::File::create("/tmp/diff.bin").unwrap(); + // BinProtWrite::binprot_write(ocaml_diff, &mut file).unwrap(); + // file.sync_all().unwrap(); + // eprintln!("SAVED DIFF"); + // } + + // let diff = diff.forget(); + + // // FIXME: ignore error messages as work around for differences in string formatting between Rust and OCaml + // let rust_invalid_cmds: Vec<_> = invalid_cmds.iter().map(|x| x.0.clone()).collect(); + + // let ocaml_invalid_cmds2: Vec<_> = ocaml_invalid_cmds.iter().map(|x| x.0.clone()).collect(); + + // // Make sure we got same result + // if !(rust_invalid_cmds == ocaml_invalid_cmds2) { + // println!( + // "!!! create_staged_ledger_diff mismatch between OCaml and Rust (invalids)\n{}\n", + // self.diagnostic(&rust_invalid_cmds, &ocaml_invalid_cmds2) + // ); + + // eprintln!("last_string={:?}", ocaml_invalid_cmds.last().unwrap().1); + + // //let bigint: num_bigint::BigUint = ledger.merkle_root().into(); + // //self.save_fuzzcase(tx, &bigint.to_string()); + // return Err(()); + // } + + // let ocaml_diff: Diff = ocaml_diff.into(); + + // if !(diff == ocaml_diff) { + // println!( + // "!!! create_staged_ledger_diff mismatch between OCaml and Rust (diff)\n{}\n", + // self.diagnostic(&diff, &ocaml_diff) + // ); + // println!("!!! OCAML=\n{:?}\n", &ocaml_diff,); + + // //let bigint: num_bigint::BigUint = ledger.merkle_root().into(); + // //self.save_fuzzcase(tx, &bigint.to_string()); + // return Err(()); + // } + + // Ok(Some(diff)) + // } + + // #[coverage(off)] + // pub fn of_scan_state_pending_coinbases_and_snarked_ledger( + // &mut self, + // current_state: &MinaStateProtocolStateValueStableV2, + // state_tbl: &HashMap, + // iteration: usize, + // ) { + // eprintln!("#######################################################"); + // eprintln!("of_scan_state_pending_coinbases_and_snarked_ledger"); + // eprintln!("#######################################################"); + + // let get_state = |hash: Fp| state_tbl.get(&hash).cloned().unwrap(); + + // let mut snarked_ledger = self.get_snarked_ledger().fuzzing_to_root(); + // let sl = self.get_staged_ledger(); + // let expected_hash: StagedLedgerHash = sl.hash(); + // let expected_staged_ledger_merkle_root = sl.ledger.clone().merkle_root(); + + // dbg!(snarked_ledger.merkle_root()); + + // let new_staged_ledger = StagedLedger::of_scan_state_pending_coinbases_and_snarked_ledger( + // (), + // &CONSTRAINT_CONSTANTS, + // Verifier, + // sl.scan_state.clone(), + // snarked_ledger.copy(), + // { + // let registers: transaction_snark::Registers = (¤t_state + // .body + // .blockchain_state + // .ledger_proof_statement + // .target) + // .into(); + // registers.local_state + // }, + // expected_staged_ledger_merkle_root, + // sl.pending_coinbase_collection.clone(), + // get_state, + // ); + + // // if new_staged_ledger.is_err() || iteration == 370 { + + // // #[derive(Clone, Debug, PartialEq, binprot_derive::BinProtRead, BinProtWrite)] + // // struct State { + // // scan_state: mina_p2p_messages::v2::TransactionSnarkScanStateStableV2, + // // pending_coinbase_collection: mina_p2p_messages::v2::MinaBasePendingCoinbaseStableV2, + // // states: Vec<(mina_p2p_messages::bigint::BigInt, MinaStateProtocolStateValueStableV2)>, + // // snarked_ledger: Vec, + // // expected_staged_ledger_merkle_root: mina_p2p_messages::bigint::BigInt, + // // } + + // // let sc = sl.scan_state.clone(); + // // let pcc = sl.pending_coinbase_collection.clone(); + + // // let state = State { + // // scan_state: (&sc).into(), + // // pending_coinbase_collection: (&pcc).into(), + // // states: state_tbl.iter().map(|(h, v)| (h.into(), v.clone())).collect(), + // // snarked_ledger: { + // // use crate::BaseLedger; + // // snarked_ledger.to_list().into_iter().map(Into::into).collect() + // // // todo!() + // // }, + // // expected_staged_ledger_merkle_root: expected_staged_ledger_merkle_root.into(), + // // }; + + // // let mut file = std::fs::File::create("/tmp/state.bin").unwrap(); + // // BinProtWrite::binprot_write(&state, &mut file).unwrap(); + // // file.sync_all().unwrap(); + + // // eprintln!("data saved"); + // // } + + // let mut new_staged_ledger = new_staged_ledger.unwrap(); + + // assert_eq!(expected_hash, sl.hash()); + // assert_eq!(expected_hash, new_staged_ledger.hash()); + // eprintln!("#######################################################"); + // eprintln!("of_scan_state_pending_coinbases_and_snarked_ledger OK"); + // eprintln!("#######################################################"); + // } + + #[coverage(off)] + fn diagnostic(&self, applied: &impl Debug, applied_ocaml: &impl Debug) -> String { + use text_diff::{diff, Difference}; + + let orig = format!("{:#?}", applied); + let edit = format!("{:#?}", applied_ocaml); + let split = " "; + let (_, changeset) = diff(orig.as_str(), edit.as_str(), split); + + let mut ret = String::new(); + + for seq in changeset { + match seq { + Difference::Same(ref x) => { + ret.push_str(x); + ret.push_str(split); + } + Difference::Add(ref x) => { + ret.push_str("\x1B[92m"); + ret.push_str(x); + ret.push_str("\x1B[0m"); + ret.push_str(split); + } + Difference::Rem(ref x) => { + ret.push_str("\x1B[91m"); + ret.push_str(x); + ret.push_str("\x1B[0m"); + ret.push_str(split); + } + } + } + + ret + } + + #[coverage(off)] + pub fn apply_transaction( + &mut self, + user_command: &UserCommand, + expected_apply_result: &ApplyTxResult, + ) -> Result<(), ()> { + self.gen.nonces.clear(); + + let mut ledger = self.get_ledger_inner().make_child(); + + // If we called apply_transaction it means we passed the tx pool check, so add tx to the cache + if let UserCommand::ZkAppCommand(command) = user_command { + if !command.account_updates.is_empty() { + //println!("Storing in pool cache {:?}", tx); + self.state.cache_pool.push_back(user_command.clone()); + } + } + + //println!("tx: {:?}\n", tx); + let tx = Transaction::Command(user_command.clone()); + + let applied = apply_transactions( + &self.constraint_constants, + self.txn_state_view.global_slot_since_genesis, + &self.txn_state_view, + &mut ledger, + &[tx.clone()], + ); + + println!( + "tx: {:?}\n applied: {:?}\n expected: {:?}", + tx, applied, expected_apply_result + ); + + match applied { + Ok(applied) => { + // For now we work with one transaction at a time + let applied = &applied[0]; + + if expected_apply_result.apply_result.len() != 1 { + println!( + "!!! Apply failed in OCaml (error: {}) but it didn't in Rust: {:?}", + expected_apply_result.error, applied + ); + let bigint: num_bigint::BigUint = LedgerIntf::merkle_root(&mut ledger).into(); + self.save_fuzzcase(&tx, &bigint.to_string()); + return Err(()); + } else { + if applied != &expected_apply_result.apply_result[0] { + println!( + "!!! Apply result mismatch between OCaml and Rust\n{}\n", + self.diagnostic(applied, &expected_apply_result.apply_result[0]) + ); + + let bigint: num_bigint::BigUint = + LedgerIntf::merkle_root(&mut ledger).into(); + self.save_fuzzcase(&tx, &bigint.to_string()); + return Err(()); + } + } + + // Save applied transactions in the cache for later use (mutation) + if *applied.transaction_status() == TransactionStatus::Applied { + if let UserCommand::ZkAppCommand(command) = user_command { + if !command.account_updates.is_empty() { + //println!("Storing in apply cache {:?}", tx); + self.state.cache_apply.push_back(user_command.clone()); + } + } + } else { + //println!("{:?}", applied.transaction_status()); + } + + // Add new accounts created by the transaction to the potential senders list + let new_accounts = match &applied.varying { + Varying::Command(command) => match command { + CommandApplied::SignedCommand(cmd) => match &cmd.body { + signed_command_applied::Body::Payments { new_accounts } => { + Some(new_accounts) + } + _ => None, + }, + CommandApplied::ZkappCommand(cmd) => Some(&cmd.new_accounts), + }, + _ => unimplemented!(), + }; + + if let Some(new_accounts) = new_accounts { + let new_accounts = self.state.potential_new_accounts.iter().filter( + #[coverage(off)] + |(kp, _)| { + new_accounts.iter().any( + #[coverage(off)] + |acc| acc.public_key == kp.public.into_compressed(), + ) + }, + ); + + for acc in new_accounts { + if !self.state.potential_senders.iter().any( + #[coverage(off)] + |(kp, _)| kp.public == acc.0.public, + ) { + self.state.potential_senders.push(acc.clone()) + } + } + + self.state.potential_new_accounts.clear(); + } + } + Err(error_string) => { + // Currently disabled until invariants are fixed + if error_string.starts_with("Invariant violation") { + let bigint: num_bigint::BigUint = LedgerIntf::merkle_root(&mut ledger).into(); + self.save_fuzzcase(&tx, &bigint.to_string()); + return Err(()); + } + + if expected_apply_result.apply_result.len() == 1 { + println!( + "!!! Apply failed in Rust (error: {}) but it didn't in OCaml: {:?}", + error_string, &expected_apply_result.apply_result[0] + ); + let bigint: num_bigint::BigUint = LedgerIntf::merkle_root(&mut ledger).into(); + self.save_fuzzcase(&tx, &bigint.to_string()); + return Err(()); + } + } + } + + let rust_ledger_root_hash = LedgerIntf::merkle_root(&mut ledger); + + if &expected_apply_result.root_hash != &rust_ledger_root_hash { + println!( + "Ledger hash mismatch: {:?} != {:?} (expected)", + rust_ledger_root_hash, expected_apply_result.root_hash + ); + let bigint: num_bigint::BigUint = rust_ledger_root_hash.into(); + self.save_fuzzcase(&tx, &bigint.to_string()); + Err(()) + } else { + ledger.commit(); + Ok(()) + } + } + + #[coverage(off)] + pub fn get_ledger_root(&mut self) -> Fp { + LedgerIntf::merkle_root(self.get_ledger_inner_mut()) + } + + #[coverage(off)] + pub fn get_ledger_accounts(&self) -> Vec { + let locations = self.get_ledger_inner().account_locations(); + locations + .iter() + .map( + #[coverage(off)] + |x| *(LedgerIntf::get(self.get_ledger_inner(), x).unwrap()), + ) + .collect() + } +} + +pub struct FuzzerCtxBuilder { + constraint_constants: Option, + txn_state_view: Option, + fuzzcases_path: Option, + seed: u64, + minimum_fee: u64, + max_account_balance: u64, + initial_accounts: usize, + cache_size: usize, + snapshots_size: usize, + is_staged_ledger: bool, +} + +impl FuzzerCtxBuilder { + #[coverage(off)] + pub fn new() -> Self { + Self { + constraint_constants: None, + txn_state_view: None, + fuzzcases_path: None, + seed: 0, + minimum_fee: 1_000_000, // Sane default in case we don't obtain it from OCaml + max_account_balance: 1_000_000_000_000_000, + initial_accounts: 10, + cache_size: 4096, + snapshots_size: 128, + is_staged_ledger: false, + } + } + + #[coverage(off)] + pub fn constants(&mut self, constraint_constants: ConstraintConstants) -> &mut Self { + self.constraint_constants = Some(constraint_constants); + self + } + + #[coverage(off)] + pub fn state_view(&mut self, txn_state_view: ProtocolStateView) -> &mut Self { + self.txn_state_view = Some(txn_state_view); + self + } + + #[coverage(off)] + pub fn fuzzcases_path(&mut self, fuzzcases_path: String) -> &mut Self { + self.fuzzcases_path = Some(fuzzcases_path); + self + } + + #[coverage(off)] + pub fn seed(&mut self, seed: u64) -> &mut Self { + self.seed = seed; + self + } + + #[coverage(off)] + pub fn minimum_fee(&mut self, minimum_fee: u64) -> &mut Self { + self.minimum_fee = minimum_fee; + self + } + + #[coverage(off)] + pub fn initial_accounts(&mut self, initial_accounts: usize) -> &mut Self { + self.initial_accounts = initial_accounts; + self + } + + #[coverage(off)] + pub fn cache_size(&mut self, cache_size: usize) -> &mut Self { + assert!(cache_size != 0 && cache_size.is_power_of_two()); + self.cache_size = cache_size; + self + } + + #[coverage(off)] + pub fn snapshots_size(&mut self, snapshots_size: usize) -> &mut Self { + assert!(snapshots_size != 0 && snapshots_size.is_power_of_two()); + self.snapshots_size = snapshots_size; + self + } + + #[coverage(off)] + pub fn is_staged_ledger(&mut self, is_staged_ledger: bool) -> &mut Self { + self.is_staged_ledger = is_staged_ledger; + self + } + + #[coverage(off)] + pub fn build(&mut self) -> FuzzerCtx { + let constraint_constants = self + .constraint_constants + .clone() + .unwrap_or(CONSTRAINT_CONSTANTS); + let depth = constraint_constants.ledger_depth as usize; + let root = Mask::new_root(Database::create(depth.try_into().unwrap())); + let txn_state_view = self + .txn_state_view + .clone() + .unwrap_or(dummy_state_view(None)); + let fuzzcases_path = self.fuzzcases_path.clone().unwrap_or("./".to_string()); + + let ledger = match self.is_staged_ledger { + true => { + let snarked_ledger_mask = root.make_child().fuzzing_to_root(); + // let snarked_ledger_mask = root.make_child(); + LedgerKind::Staged( + StagedLedger::create_exn(constraint_constants.clone(), root.make_child()) + .unwrap(), + snarked_ledger_mask, + ) + } + false => LedgerKind::Mask(root.make_child()), + }; + + let mut ctx = FuzzerCtx { + constraint_constants, + txn_state_view, + fuzzcases_path, + gen: GeneratorCtx { + rng: SmallRng::seed_from_u64(self.seed), + minimum_fee: self.minimum_fee, + excess_fee: Signed::::zero(), + token_id: TokenId::default(), + tx_proof: None, + max_account_balance: self.max_account_balance, + nonces: HashMap::new(), + attempt_valid_zkapp: true, + }, + state: FuzzerState { + ledger, + potential_senders: Vec::new(), + potential_new_accounts: Vec::new(), + cache_pool: RingBuffer::with_capacity(self.cache_size), + cache_apply: RingBuffer::with_capacity(self.cache_size), + }, + snapshots: RingBuffer::with_capacity(self.snapshots_size), + }; + + ctx.create_inital_accounts(self.initial_accounts); + ctx + } +} diff --git a/tools/fuzzing/src/coverage/covfun.rs b/tools/fuzzing/src/coverage/covfun.rs new file mode 100644 index 000000000..499e34a93 --- /dev/null +++ b/tools/fuzzing/src/coverage/covfun.rs @@ -0,0 +1,248 @@ +use super::util::{cursor_align, read_int, Leb128}; +use std::io::Cursor; + +//#[derive(Debug)] +pub struct Record { + pub name_hash: u64, + pub data_len: u32, + pub func_hash: u64, + pub translation_unit_hash: u64, +} + +impl Record { + #[coverage(off)] + pub fn read(cursor: &mut Cursor<&Vec>) -> Self { + Self { + name_hash: read_int(cursor), + data_len: read_int(cursor), + func_hash: read_int(cursor), + translation_unit_hash: read_int(cursor), + } + } +} + +//#[derive(Debug)] +pub enum PseudoCounter { + ExpansionRegion(u64), + CodeRegion, + SkippedRegion, + BranchRegion, +} + +impl PseudoCounter { + #[coverage(off)] + pub fn new(value: u64) -> Self { + let data = value >> 1; + + match value & 1 { + 0 => match data { + 0 => Self::CodeRegion, + 2 => Self::SkippedRegion, + 4 => Self::BranchRegion, + _ => panic!(), + }, + _ => Self::ExpansionRegion(data), + } + } +} + +//#[derive(Debug)] +pub enum Counter { + Zero, + Reference(usize), + Substraction(usize), + Addition(usize), +} + +impl Counter { + #[coverage(off)] + pub fn new(value: u64) -> Self { + let idx = (value >> 2) as usize; + + match value & 0b11 { + 0 => Self::Zero, + 1 => Self::Reference(idx), + 2 => Self::Substraction(idx), + 3 => Self::Addition(idx), + _ => unreachable!(), + } + } + + #[coverage(off)] + pub fn read(cursor: &mut Cursor<&Vec>) -> Self { + Counter::new(u64::read_leb128(cursor)) + } +} + +//#[derive(Debug)] +pub enum Header { + PseudoCounter(PseudoCounter), + Counter(Counter), +} + +impl Header { + #[coverage(off)] + pub fn read(cursor: &mut Cursor<&Vec>) -> Self { + let header = u64::read_leb128(cursor); + let data = header >> 2; + + match header & 0b11 { + 0 => Self::PseudoCounter(PseudoCounter::new(data)), + _ => Self::Counter(Counter::new(header)), + } + } +} + +//#[derive(Debug)] +pub struct CounterExpression { + pub lhs: Counter, + pub rhs: Counter, +} + +impl CounterExpression { + #[coverage(off)] + pub fn read(cursor: &mut Cursor<&Vec>) -> Self { + Self { + lhs: Counter::read(cursor), + rhs: Counter::read(cursor), + } + } + + #[coverage(off)] + fn resolve_op(op: &Counter, counters: &'static [i64], expressions: &Vec) -> i64 { + match op { + Counter::Zero => 0, + Counter::Reference(idx) => counters[*idx], + Counter::Substraction(idx) => expressions[*idx].resolve_sub(counters, expressions), + Counter::Addition(idx) => expressions[*idx].resolve_add(counters, expressions), + } + } + + #[coverage(off)] + pub fn resolve_sub(&self, counters: &'static [i64], expressions: &Vec) -> i64 { + let lhs = Self::resolve_op(&self.lhs, counters, expressions); + let rhs = Self::resolve_op(&self.rhs, counters, expressions); + //println!("sub {} {}", lhs, rhs); + lhs.wrapping_sub(rhs) + } + + #[coverage(off)] + pub fn resolve_add(&self, counters: &'static [i64], expressions: &Vec) -> i64 { + let lhs = Self::resolve_op(&self.lhs, counters, expressions); + let rhs = Self::resolve_op(&self.rhs, counters, expressions); + //println!("add {} {}", lhs, rhs); + lhs.wrapping_add(rhs) + } +} + +//#[derive(Debug, Clone)] +pub struct SourceRange { + pub delta_line_start: usize, + pub column_start: usize, + pub num_lines: usize, + pub column_end: usize, +} + +impl Clone for SourceRange { + #[coverage(off)] + fn clone(&self) -> Self { + Self { + delta_line_start: self.delta_line_start, + column_start: self.column_start, + num_lines: self.num_lines, + column_end: self.column_end, + } + } +} + +impl SourceRange { + #[coverage(off)] + pub fn read(cursor: &mut Cursor<&Vec>) -> Self { + Self { + delta_line_start: usize::read_leb128(cursor), + column_start: usize::read_leb128(cursor), + num_lines: usize::read_leb128(cursor), + column_end: usize::read_leb128(cursor), + } + } +} + +//#[derive(Debug)] +pub struct Region { + pub header: Header, + pub source_range: SourceRange, +} + +impl Region { + #[coverage(off)] + pub fn read(cursor: &mut Cursor<&Vec>) -> Self { + Self { + header: Header::read(cursor), + source_range: SourceRange::read(cursor), + } + } +} + +//#[derive(Debug)] +pub struct FunCov { + pub function_record: Record, + pub expressions: Vec, + pub mapping_regions: Vec<(u64, Vec)>, +} + +impl FunCov { + #[coverage(off)] + pub fn read(cursor: &mut Cursor<&Vec>) -> Self { + let function_record = Record::read(cursor); + //let data_pos = cursor.position(); + + assert!(function_record.data_len > 0); + // numIndices : LEB128, filenameIndex0 : LEB128, filenameIndex1 : LEB128... + let num_indices = u64::read_leb128(cursor); + let file_id_mapping: Vec = (0..num_indices) + .map( + #[coverage(off)] + |_| u64::read_leb128(cursor), + ) + .collect(); + + // numExpressions : LEB128, expr0LHS : LEB128, expr0RHS : LEB128, expr1LHS : LEB128, expr1RHS : LEB128... + let num_expressions = u64::read_leb128(cursor); + let expressions: Vec = (0..num_expressions) + .map( + #[coverage(off)] + |_| CounterExpression::read(cursor), + ) + .collect(); + + // [numRegionArrays : LEB128, regionsForFile0, regionsForFile1, ...] + // Not actually included? + // let num_region_arrays = leb128::read::unsigned(&mut covfun_cursor).unwrap(); + + let mapping_regions: Vec<(u64, Vec)> = file_id_mapping + .iter() + .map( + #[coverage(off)] + |index| { + // [numRegions : LEB128, region0, region1, ...] + let num_regions = u64::read_leb128(cursor); + let regions: Vec = (0..num_regions) + .map( + #[coverage(off)] + |_| Region::read(cursor), + ) + .collect(); + (*index, regions) + }, + ) + .collect(); + + cursor_align::(cursor); + + Self { + function_record, + expressions, + mapping_regions, + } + } +} diff --git a/tools/fuzzing/src/coverage/covmap.rs b/tools/fuzzing/src/coverage/covmap.rs new file mode 100644 index 000000000..6e43b4262 --- /dev/null +++ b/tools/fuzzing/src/coverage/covmap.rs @@ -0,0 +1,99 @@ +use super::util::{cursor_align, read_int, Leb128}; +use std::io::{Cursor, Read, Seek, SeekFrom}; + +//#[derive(Debug)] +#[allow(dead_code)] +pub struct Header { + zero: u32, + encoded_filenames_len: u32, + zero2: u32, + version: u32, +} + +impl Header { + #[coverage(off)] + pub fn read(cursor: &mut Cursor<&Vec>) -> Self { + let header = Self { + zero: read_int(cursor), + encoded_filenames_len: read_int(cursor), + zero2: read_int(cursor), + version: read_int(cursor), + }; + + assert!(header.zero == 0 && header.zero2 == 0); + header + } +} + +//#[derive(Debug)] +pub struct Filenames(pub Vec); + +impl Filenames { + #[coverage(off)] + pub fn read(cursor: &mut Cursor<&Vec>) -> Self { + let num_filenames = u64::read_leb128(cursor); + let uncompressed_len = usize::read_leb128(cursor); + let compressed_len = usize::read_leb128(cursor); + let pos = cursor.position() as usize; + + let mut filenames = Vec::new(); + let mut decompressed_buf = vec![0; uncompressed_len]; + let buf = if compressed_len != 0 { + flate2::Decompress::new(true) + .decompress( + &cursor.get_ref()[pos..pos + compressed_len], + &mut decompressed_buf[..], + flate2::FlushDecompress::Finish, + ) + .unwrap(); + decompressed_buf + } else { + cursor.get_ref()[pos..pos + uncompressed_len].to_vec() + }; + + let mut cursor = Cursor::new(&buf); + + while (cursor.position() as usize) < uncompressed_len { + filenames.push(Self::read_filename(&mut cursor)); + } + + assert!(filenames.len() == num_filenames as usize); + Self(filenames) + } + + #[coverage(off)] + fn read_filename(cursor: &mut Cursor<&Vec>) -> String { + let string_len = usize::read_leb128(cursor); + let mut output = vec![0; string_len]; + cursor.read_exact(output.as_mut_slice()).unwrap(); + String::from_utf8(output).unwrap() + } +} + +//#[derive(Debug)] +#[allow(dead_code)] +pub struct CovMap { + header: Header, + pub encoded_data_hash: u64, + pub filenames: Filenames, +} + +impl CovMap { + #[coverage(off)] + pub fn read(cursor: &mut Cursor<&Vec>) -> Self { + let header = Header::read(cursor); + let pos = cursor.position() as usize; + let end = pos + header.encoded_filenames_len as usize; + let digest = md5::compute(&cursor.get_ref()[pos..end]).0; + let encoded_data_hash: u64 = read_int(&mut Cursor::new(digest)); + let filenames = Filenames::read(cursor); + cursor.seek(SeekFrom::Start(end as u64)).unwrap(); + cursor_align::(cursor); + + Self { + header, + encoded_data_hash, + filenames, + } + } +} diff --git a/tools/fuzzing/src/coverage/mod.rs b/tools/fuzzing/src/coverage/mod.rs new file mode 100644 index 000000000..334455e12 --- /dev/null +++ b/tools/fuzzing/src/coverage/mod.rs @@ -0,0 +1,8 @@ +pub mod cov; +mod covfun; +mod covmap; +mod names; +mod profile_data; +pub mod reports; +pub mod stats; +mod util; diff --git a/tools/fuzzing/src/coverage/names.rs b/tools/fuzzing/src/coverage/names.rs new file mode 100644 index 000000000..814e980ae --- /dev/null +++ b/tools/fuzzing/src/coverage/names.rs @@ -0,0 +1,74 @@ +use std::io::{BufRead, Cursor, Seek, SeekFrom}; + +use super::util::{get_names, Leb128}; + +//#[derive(Debug)] +#[allow(dead_code)] +pub struct Names(Vec); + +impl Names { + #[coverage(off)] + pub fn new() -> Self { + let names_buf = unsafe { get_names() }.to_vec(); + let mut cursor = Cursor::new(&names_buf); + let mut output = Vec::new(); + let mut pos = cursor.position(); + let names_buf_len = names_buf.len() as u64; + + while pos < names_buf_len { + let uncompressed_len = u64::read_leb128(&mut cursor); + let compressed_len = u64::read_leb128(&mut cursor); + + pos = cursor.position(); + let start = pos as usize; + + if compressed_len == 0 { + let end = start + uncompressed_len as usize; + Self::read_names(&names_buf[start..end], &mut output); + pos += uncompressed_len; + } else { + let end = start + compressed_len as usize; + let decompressed_buf = + Self::decompress(&names_buf[start..end], uncompressed_len as usize); + Self::read_names(decompressed_buf.as_slice(), &mut output); + pos += compressed_len; + } + + cursor.seek(SeekFrom::Start(pos)).unwrap(); + } + + Self(output) + } + + #[coverage(off)] + fn read_names(names: &[u8], output: &mut Vec) { + let mut names = Cursor::new(names); + + loop { + let mut name = Vec::new(); + + if names.read_until(0x01, &mut name).unwrap() == 0 { + return; + } + + if *name.last().unwrap() == 0x01 { + name.pop(); + } + + output.push(String::from_utf8(name).unwrap()); + } + } + + #[coverage(off)] + fn decompress(compressed_buf: &[u8], uncompressed_len: usize) -> Vec { + let mut decompressed_buf = vec![0; uncompressed_len]; + flate2::Decompress::new(true) + .decompress( + compressed_buf, + &mut decompressed_buf, + flate2::FlushDecompress::Finish, + ) + .unwrap(); + decompressed_buf + } +} diff --git a/tools/fuzzing/src/coverage/profile_data.rs b/tools/fuzzing/src/coverage/profile_data.rs new file mode 100644 index 000000000..e139621ca --- /dev/null +++ b/tools/fuzzing/src/coverage/profile_data.rs @@ -0,0 +1,58 @@ +use super::util::{cursor_align, get_data, read_int}; +use std::{io::Cursor, mem}; + +// From LLVM's compiler-rt/include/profile/InstrProfData.inc +#[repr(packed)] +#[derive(Debug)] +pub struct FunControl { + pub name_hash: u64, + pub func_hash: u64, + pub relative_counter_ptr: u64, + pub relative_bitmap_ptr: u64, + pub function_ptr: u64, + pub values_ptr: u64, + pub num_counters: u32, + pub num_value_sites: u16, + pub num_bitmap_bytes: u32, +} + +impl FunControl { + #[coverage(off)] + pub fn read(cursor: &mut Cursor<&Vec>) -> Self { + Self { + name_hash: read_int(cursor), + func_hash: read_int(cursor), + relative_counter_ptr: read_int(cursor), + relative_bitmap_ptr: read_int(cursor), + function_ptr: read_int(cursor), + values_ptr: read_int(cursor), + num_counters: read_int(cursor), + num_value_sites: read_int(cursor), + num_bitmap_bytes: read_int(cursor), + } + } + + #[coverage(off)] + pub fn size() -> usize { + mem::size_of::() + } +} + +#[derive(Debug)] +pub struct ProfileData(pub Vec); + +impl ProfileData { + #[coverage(off)] + pub fn new() -> Self { + let data_buf = unsafe { get_data() }.to_vec(); + let mut cursor = Cursor::new(&data_buf); + let mut output = Vec::new(); + + while (cursor.position() as usize + FunControl::size()) < data_buf.len() { + output.push(FunControl::read(&mut cursor)); + cursor_align::(&mut cursor); + } + + Self(output) + } +} diff --git a/tools/fuzzing/src/coverage/reports.rs b/tools/fuzzing/src/coverage/reports.rs new file mode 100644 index 000000000..3364c1013 --- /dev/null +++ b/tools/fuzzing/src/coverage/reports.rs @@ -0,0 +1,642 @@ +use super::cov::FileDump; +use itertools::Itertools; +use std::fs::File; +use std::io::{BufRead, BufReader, Write}; +use std::{env, fmt, fs}; + +//#[derive(Debug, Clone, Serialize)] +pub struct LineCounter { + pub col_start: usize, + pub col_end: usize, + pub count: i64, +} + +impl Clone for LineCounter { + #[coverage(off)] + fn clone(&self) -> Self { + Self { + col_start: self.col_start, + col_end: self.col_end, + count: self.count, + } + } +} + +impl serde::Serialize for LineCounter { + #[coverage(off)] + fn serialize<__S>(&self, __serializer: __S) -> serde::__private::Result<__S::Ok, __S::Error> + where + __S: serde::Serializer, + { + let mut __serde_state = match serde::Serializer::serialize_struct( + __serializer, + "LineCounter", + false as usize + 1 + 1 + 1, + ) { + serde::__private::Ok(__val) => __val, + serde::__private::Err(__err) => { + return serde::__private::Err(__err); + } + }; + match serde::ser::SerializeStruct::serialize_field( + &mut __serde_state, + "col_start", + &self.col_start, + ) { + serde::__private::Ok(__val) => __val, + serde::__private::Err(__err) => { + return serde::__private::Err(__err); + } + }; + match serde::ser::SerializeStruct::serialize_field( + &mut __serde_state, + "col_end", + &self.col_end, + ) { + serde::__private::Ok(__val) => __val, + serde::__private::Err(__err) => { + return serde::__private::Err(__err); + } + }; + match serde::ser::SerializeStruct::serialize_field(&mut __serde_state, "count", &self.count) + { + serde::__private::Ok(__val) => __val, + serde::__private::Err(__err) => { + return serde::__private::Err(__err); + } + }; + serde::ser::SerializeStruct::end(__serde_state) + } +} + +impl LineCounter { + #[coverage(off)] + fn overlap(&self, rhs: &Self) -> bool { + self.col_start < rhs.col_end && rhs.col_start < self.col_end + } + + #[coverage(off)] + fn contains(&self, rhs: &Self) -> bool { + self.col_start < rhs.col_start && self.col_end > rhs.col_end + } + + #[coverage(off)] + fn merge(&self, rhs: &Self, count: i64) -> Self { + Self { + col_start: self.col_start.min(rhs.col_start), + col_end: self.col_end.max(rhs.col_end), + count, + } + } + + #[coverage(off)] + fn split3(&self, rhs: &Self) -> Vec { + vec![ + Self { + col_start: self.col_start, + col_end: rhs.col_start, + count: self.count, + }, + Self { + col_start: rhs.col_start, + col_end: rhs.col_end, + count: rhs.count, + }, + Self { + col_start: rhs.col_end, + col_end: self.col_end, + count: self.count, + }, + ] + } + + #[coverage(off)] + fn split2(&self, rhs: &Self) -> Vec { + vec![ + Self { + col_start: self.col_start, + col_end: rhs.col_start, + count: self.count, + }, + Self { + col_start: rhs.col_start, + col_end: rhs.col_end, + count: rhs.count, + }, + ] + } + + #[coverage(off)] + fn split(&self, rhs: &Self) -> Vec { + if self.contains(&rhs) { + self.split3(&rhs) + } else if rhs.contains(self) { + rhs.split3(self) + } else { + if self.col_start < rhs.col_start { + self.split2(rhs) + } else { + rhs.split2(self) + } + } + } + + #[coverage(off)] + pub fn join(&self, rhs: &Self) -> Option> { + if self.overlap(rhs) { + if self.count == rhs.count { + Some(vec![self.merge(rhs, self.count)]) + } else { + if self.col_start == rhs.col_start && self.col_end == rhs.col_end { + Some(vec![self.merge(rhs, self.count.max(rhs.count))]) + } else { + Some(self.split(rhs)) + } + } + } else { + None + } + } +} + +#[coverage(off)] +fn color_line_counters(line: &str, counters: &Vec) -> String { + let mut result = String::new(); + + if counters.is_empty() { + return line.to_string(); + } + + let mut line_color = 43; // light yellow + + for counter in counters.iter() { + if counter.count > 0 { + if line_color == 41 { + line_color = 43; + break; + } + + line_color = 42; // light green + } else { + if line_color == 42 { + line_color = 43; + break; + } + + line_color = 41; // light red + } + } + + result.push_str(&format!("\x1b[1;{}m\x1b[1;37m", line_color)); + + for (column, c) in line.chars().enumerate() { + let counter = counters.iter().find( + #[coverage(off)] + |LineCounter { + col_start, col_end, .. + }| *col_start <= column && *col_end >= column, + ); + + if let Some(counter) = counter { + if column == counter.col_start { + let color_code: u8 = if counter.count == 0 { 101 } else { 102 }; + result.push_str(&format!("\x1b[1;{}m\x1b[1;{}m", line_color, color_code)); + } + } + + result.push(c); + + if let Some(counter) = counter { + // avoid reset colors if there is another counter + if column == counter.col_end + && (counter.col_start == counter.col_end + || counters + .iter() + .find( + #[coverage(off)] + |LineCounter { col_start, .. }| *col_start == column, + ) + .is_none()) + { + result.push_str(&format!("\x1b[1;{}m\x1b[1;37m", line_color)); + } + } + } + + result.push_str("\x1b[0m"); + result +} + +//#[derive(Debug, Serialize)] +pub struct LineCoverage { + pub line: String, + pub counters: Vec, +} + +impl serde::Serialize for LineCoverage { + #[coverage(off)] + fn serialize<__S>(&self, __serializer: __S) -> serde::__private::Result<__S::Ok, __S::Error> + where + __S: serde::Serializer, + { + let mut __serde_state = match serde::Serializer::serialize_struct( + __serializer, + "LineCoverage", + false as usize + 1 + 1, + ) { + serde::__private::Ok(__val) => __val, + serde::__private::Err(__err) => { + return serde::__private::Err(__err); + } + }; + match serde::ser::SerializeStruct::serialize_field(&mut __serde_state, "line", &self.line) { + serde::__private::Ok(__val) => __val, + serde::__private::Err(__err) => { + return serde::__private::Err(__err); + } + }; + match serde::ser::SerializeStruct::serialize_field( + &mut __serde_state, + "counters", + &self.counters, + ) { + serde::__private::Ok(__val) => __val, + serde::__private::Err(__err) => { + return serde::__private::Err(__err); + } + }; + serde::ser::SerializeStruct::end(__serde_state) + } +} + +impl fmt::Display for LineCoverage { + #[coverage(off)] + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", color_line_counters(&self.line, &self.counters)) + } +} + +//#[derive(Debug, Serialize)] +pub struct FileCoverage { + pub filename: String, + pub lines: Vec, +} + +impl serde::Serialize for FileCoverage { + #[coverage(off)] + fn serialize<__S>(&self, __serializer: __S) -> serde::__private::Result<__S::Ok, __S::Error> + where + __S: serde::Serializer, + { + let mut __serde_state = match serde::Serializer::serialize_struct( + __serializer, + "FileCoverage", + false as usize + 1 + 1, + ) { + serde::__private::Ok(__val) => __val, + serde::__private::Err(__err) => { + return serde::__private::Err(__err); + } + }; + match serde::ser::SerializeStruct::serialize_field( + &mut __serde_state, + "filename", + &self.filename, + ) { + serde::__private::Ok(__val) => __val, + serde::__private::Err(__err) => { + return serde::__private::Err(__err); + } + }; + match serde::ser::SerializeStruct::serialize_field(&mut __serde_state, "lines", &self.lines) + { + serde::__private::Ok(__val) => __val, + serde::__private::Err(__err) => { + return serde::__private::Err(__err); + } + }; + serde::ser::SerializeStruct::end(__serde_state) + } +} + +impl FileCoverage { + #[coverage(off)] + pub fn covered_percent(&self) -> usize { + let mut total = 0; + let mut covered = 0; + + for line in self.lines.iter() { + total += line.counters.len(); + covered += line.counters.iter().fold( + 0, + #[coverage(off)] + |acc, lc| if lc.count != 0 { acc + 1 } else { acc }, + ); + } + + if total != 0 { + (covered * 100) / total + } else { + 0 + } + } +} + +impl fmt::Display for FileCoverage { + #[coverage(off)] + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}:\n", self.filename)?; + + for (i, line) in self.lines.iter().enumerate() { + write!(f, "{:>6}: {}\n", i, line)?; + } + + Ok(()) + } +} + +//#[derive(Debug)] +pub struct CoverageReport(Vec); + +impl fmt::Display for CoverageReport { + #[coverage(off)] + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + for file in self.0.iter() { + write!(f, "{}\n", file)?; + } + + Ok(()) + } +} + +impl CoverageReport { + #[coverage(off)] + pub fn write_files(&self, report_prefix: String) { + let sources_path = env::var("REPORTS_PATH").unwrap_or("/tmp/".to_string()); + let report_file_vec: Vec<(String, &FileCoverage)> = self + .0 + .iter() + .enumerate() + .map( + #[coverage(off)] + |(n, filecov)| (report_prefix.clone() + &n.to_string() + ".json", filecov), + ) + .collect(); + + let report_index: Vec<(String, usize, String)> = report_file_vec + .iter() + .map( + #[coverage(off)] + |(report_name, filecov)| { + ( + report_name.clone(), + filecov.covered_percent(), + filecov.filename.clone(), + ) + }, + ) + .collect(); + + let mut file = fs::OpenOptions::new() + .write(true) + .truncate(true) + .create(true) + .open(sources_path.clone() + &report_prefix + "index.json") + .unwrap(); + + file.write_all(serde_json::to_string(&report_index).unwrap().as_bytes()) + .unwrap(); + + for (report_name, filecov) in report_file_vec.iter() { + let mut file = fs::OpenOptions::new() + .write(true) + .truncate(true) + .create(true) + .open(sources_path.clone() + report_name) + .unwrap(); + + file.write_all(serde_json::to_string(filecov).unwrap().as_bytes()) + .unwrap(); + } + } + + #[coverage(off)] + fn merge_counters(counter: &LineCounter, counters: &mut Vec) { + for idx in 0.. { + if idx == counters.len() { + counters.push(counter.clone()); + break; + } + + if let Some(new_counters) = counters[idx].join(&counter) { + counters.remove(idx); + + for counter in new_counters.iter() { + Self::merge_counters(counter, counters); + } + + break; + } else { + if counters[idx].col_end > counter.col_end { + counters.insert(idx, counter.clone()); + break; + } + } + } + } + + #[coverage(off)] + pub fn from_llvm_dump(llvm_dump: &Vec) -> Self { + let sources_path = env::var("RUST_BUILD_PATH").unwrap_or_else(|_| { + env::current_dir() + .unwrap() + .parent() + .unwrap() + .parent() + .unwrap() + .to_str() + .unwrap() + .to_string() + }); + let mut file_coverage_vec = Vec::new(); + + for FileDump { + filename, + source_counters_vec, + } in llvm_dump.iter() + { + // Ignore external crates. Might want them back at some point? + if filename.starts_with("/rustc/") + || filename.contains("/.cargo/") + || filename.contains("/.rustup/") + { + continue; + } + + let path = if filename.starts_with("/") { + filename.clone() + } else { + sources_path.clone() + "/" + filename + }; + + let file = File::open(path).unwrap(); + let mut lines: Vec = BufReader::new(file) + .lines() + .map( + #[coverage(off)] + |line| LineCoverage { + line: line.unwrap(), + counters: Vec::new(), + }, + ) + .collect(); + + for source_counters in source_counters_vec.iter() { + let mut previous_line = 0; + + for (count, source_range) in source_counters.iter() { + let count = *count; + let start = previous_line + source_range.delta_line_start - 1; + let end = start + source_range.num_lines; + //println!("{:?}", source_range); + + for line in start..=end { + if line == lines.len() || lines[line].line.chars().count() == 0 { + continue; + } + + let col_start = if line == start { + source_range.column_start - 1 + } else { + 0 + }; + let mut col_end = if line == end { + source_range.column_end - 1 + } else { + lines[line].line.chars().count() + }; + + // Why can this happen? + if col_end < col_start { + col_end = col_start; + } + + let line_counter = LineCounter { + col_start, + col_end, + count, + }; + + Self::merge_counters(&line_counter, &mut lines[line].counters); + } + + previous_line += source_range.delta_line_start; + } + } + + let file_cov = FileCoverage { + filename: filename.clone(), + lines, + }; + + file_coverage_vec.push(file_cov); + } + + Self(file_coverage_vec) + } + + #[coverage(off)] + pub fn from_bisect_dump(bisect_dump: &Vec<(String, Vec, Vec)>) -> Self { + let sources_path = env::var("OCAML_BUILD_PATH").unwrap(); + let mut file_coverage_vec = Vec::new(); + + for (filename, points, counts) in bisect_dump.iter() { + let file_contents = fs::read(sources_path.clone() + filename).unwrap(); + let line_offset_vec: Vec = file_contents + .iter() + .enumerate() + .filter_map( + #[coverage(off)] + |(i, &x)| if x == b'\n' { Some(i) } else { None }, + ) + .collect(); + let mut lines: Vec = line_offset_vec + .iter() + .enumerate() + .map( + #[coverage(off)] + |(i, &end_pos)| { + let start_pos = if i == 0 { + 0 + } else { + line_offset_vec[i - 1] + 1 + }; + LineCoverage { + line: String::from_utf8((&file_contents[start_pos..end_pos]).to_vec()) + .unwrap(), + counters: Vec::new(), + } + }, + ) + .collect(); + + for (point, count) in points + .iter() + .zip(counts.iter()) + .filter_map( + #[coverage(off)] + |(point, count)| { + // FIXME: figure out why bisect-ppx is reporting point values such as -1 + if *point <= 0 { + None + } else { + Some(((*point - 1) as usize, *count)) + } + }, + ) + .sorted_by_key( + #[coverage(off)] + |(point, _count)| *point, + ) + { + let (line_num, _) = line_offset_vec + .iter() + .enumerate() + .find( + #[coverage(off)] + |(i, &offset)| point < offset || i + 1 == line_offset_vec.len(), + ) + .unwrap(); + + let col = if line_num == 0 { + point as usize + } else { + point as usize - line_offset_vec[line_num - 1] + }; + + // TODO: find a better way to convert bytes position to char position + let col_start = + String::from_utf8((&lines[line_num].line.as_bytes()[..col]).to_vec()) + .unwrap() + .chars() + .count(); + + // It seems there isn't an "end column" in bisect-ppx + let col_end = col_start; + + lines[line_num].counters.push(LineCounter { + col_start, + col_end, + count, + }) + } + + file_coverage_vec.push(FileCoverage { + filename: filename.clone(), + lines, + }) + } + + Self(file_coverage_vec) + } +} diff --git a/tools/fuzzing/src/coverage/stats.rs b/tools/fuzzing/src/coverage/stats.rs new file mode 100644 index 000000000..3bf473c9b --- /dev/null +++ b/tools/fuzzing/src/coverage/stats.rs @@ -0,0 +1,146 @@ +use super::cov::FileCounters; +use btreemultimap::BTreeMultiMap; +use std::fmt; + +//#[derive(Debug)] +pub struct Stats(BTreeMultiMap); + +impl Stats { + // If we are just interested in coverage statistics for some subset of source files + #[coverage(off)] + pub fn filter_filenames(&self, filenames: &Vec<&str>) -> Self { + let mut filtered_map = BTreeMultiMap::new(); + + for (k, v) in self.0.iter().filter( + #[coverage(off)] + |(_, (filename, _, _))| { + filenames.iter().any( + #[coverage(off)] + |name| filename.ends_with(name), + ) + }, + ) { + filtered_map.insert(*k, v.clone()); + } + + Self(filtered_map) + } + + #[coverage(off)] + pub fn filter_path(&self, path: &str) -> Self { + let mut filtered_map = BTreeMultiMap::new(); + + for (k, v) in self.0.iter().filter( + #[coverage(off)] + |(_, (filename, _, _))| !filename.contains(path), + ) { + filtered_map.insert(*k, v.clone()); + } + + Self(filtered_map) + } + + // If we are interested just in top `n` files with more coverage + #[coverage(off)] + pub fn filter_top(&self, n: usize) -> Self { + let mut filtered_map = BTreeMultiMap::new(); + + for (k, v) in self.0.clone().iter().rev().take(n) { + filtered_map.insert(*k, v.clone()); + } + + Self(filtered_map) + } + + // returns: (coverage percent, total counters, covered counters) + #[coverage(off)] + pub fn get_total(&self) -> (usize, usize, usize) { + let (total, covered) = self.0.iter().fold( + (0, 0), + #[coverage(off)] + |(acc_total, acc_covered), (_, (_, file_total, file_covered))| { + (acc_total + file_total, acc_covered + file_covered) + }, + ); + let cov_percent = if total > 0 { + (covered * 100) / total + } else { + 0 + }; + (cov_percent, total, covered) + } + + #[coverage(off)] + pub fn has_coverage_increased(&self, rhs: &Self) -> bool { + self.get_total().2 > rhs.get_total().2 + } + + #[coverage(off)] + pub fn from_file_counters(filecounters: &Vec) -> Self { + let mut result = BTreeMultiMap::new(); + + for FileCounters { + filename, counters, .. + } in filecounters.iter() + { + let (total, covered) = counters.iter().fold( + (0, 0), + #[coverage(off)] + |(total, covered), counters| { + ( + total + counters.len(), + covered + + counters.iter().fold( + 0, + #[coverage(off)] + |acc, counter| if *counter != 0 { acc + 1 } else { acc }, + ), + ) + }, + ); + + if total != 0 { + let cov_percent = (covered * 100) / total; + result.insert(cov_percent, (filename.clone(), total, covered)); + } + } + + Self(result) + } + + #[coverage(off)] + pub fn from_bisect_dump(bisect_dump: &Vec<(String, Vec, Vec)>) -> Self { + let mut result = BTreeMultiMap::new(); + + for (filename, _points, counts) in bisect_dump.iter() { + let covered = counts.iter().fold( + 0, + #[coverage(off)] + |acc, counter| if *counter != 0 { acc + 1 } else { acc }, + ); + let total = counts.len(); + + if total != 0 { + let cov_percent = (covered * 100) / total; + result.insert(cov_percent, (filename.clone(), total, covered)); + } + } + + Self(result) + } +} + +impl fmt::Display for Stats { + #[coverage(off)] + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + for (percent, (filename, total, covered)) in self.0.iter() { + write!( + f, + "{:>3}% {:>4}/{:>4}: {}\n", + percent, covered, total, filename + )?; + } + let (percent, total, covered) = self.get_total(); + write!(f, "{:>3}% {:>4}/{:>4}: Total\n", percent, covered, total) + } +} diff --git a/tools/fuzzing/src/coverage/util.rs b/tools/fuzzing/src/coverage/util.rs new file mode 100644 index 000000000..c8c323a28 --- /dev/null +++ b/tools/fuzzing/src/coverage/util.rs @@ -0,0 +1,136 @@ +use core::slice; +use std::{ + collections::HashMap, + io::Read, + io::{Cursor, Seek, SeekFrom}, + path::Path, +}; + +use bitvec::macros::internal::funty; +use object::{Object, ObjectSection}; + +extern "C" { + fn __llvm_profile_begin_counters() -> *mut i64; + fn __llvm_profile_end_counters() -> *mut i64; + fn __llvm_profile_get_num_counters(begin: *mut i64, end: *mut i64) -> i64; + fn __llvm_profile_begin_data() -> *const u8; + fn __llvm_profile_end_data() -> *const u8; + fn __llvm_profile_begin_names() -> *const u8; + fn __llvm_profile_end_names() -> *const u8; +} + +#[coverage(off)] +pub unsafe fn get_counters() -> &'static mut [i64] { + let begin = __llvm_profile_begin_counters(); + let end = __llvm_profile_end_counters(); + let num_counters = __llvm_profile_get_num_counters(begin, end); + slice::from_raw_parts_mut(begin, num_counters as usize) +} + +#[coverage(off)] +pub unsafe fn get_data() -> &'static [u8] { + let begin = __llvm_profile_begin_data(); + let end = __llvm_profile_end_data(); + slice::from_raw_parts(begin, end.offset_from(begin) as usize) +} + +#[coverage(off)] +pub unsafe fn get_names() -> &'static [u8] { + let begin = __llvm_profile_begin_names(); + let end = __llvm_profile_end_names(); + slice::from_raw_parts(begin, end.offset_from(begin) as usize) +} + +#[coverage(off)] +pub fn get_module_path() -> String { + let maps = rsprocmaps::from_path("/proc/self/maps").unwrap(); + + for map in maps { + let map = map.unwrap(); + + let rsprocmaps::AddressRange { begin, end } = map.address_range; + + if (begin..end).contains(&(get_module_path as u64)) { + if let rsprocmaps::Pathname::Path(path) = map.pathname { + return path; + } else { + panic!() + } + } + } + + panic!() +} + +#[coverage(off)] +pub fn get_elf_sections(module: String, section_names: &Vec<&str>) -> HashMap> { + let mut result = HashMap::new(); + let path = Path::new(&module); + + let bin_data = std::fs::read(path).unwrap(); + let obj_file = object::File::parse(&*bin_data).unwrap(); + + for section_name in section_names.iter() { + let section = obj_file.section_by_name(section_name).unwrap(); + result.insert(section_name.to_string(), section.data().unwrap().to_vec()); + } + + result +} + +#[coverage(off)] +pub fn read_int(cursor: &mut Cursor) -> T +where + T: funty::Numeric, + A: AsRef<[u8]>, +{ + let mut buf = [0u8; N]; + cursor.read_exact(buf.as_mut_slice()).unwrap(); + T::from_le_bytes(buf) +} + +pub trait Leb128 { + type T; + + fn read_leb128(cursor: &mut Cursor<&Vec>) -> Self::T; +} + +impl Leb128 for i64 { + type T = i64; + + #[coverage(off)] + fn read_leb128(cursor: &mut Cursor<&Vec>) -> Self::T { + leb128::read::signed(cursor).unwrap() + } +} + +impl Leb128 for u64 { + type T = u64; + + #[coverage(off)] + fn read_leb128(cursor: &mut Cursor<&Vec>) -> Self::T { + leb128::read::unsigned(cursor).unwrap() + } +} + +impl Leb128 for usize { + type T = usize; + + #[coverage(off)] + fn read_leb128(cursor: &mut Cursor<&Vec>) -> Self::T { + leb128::read::unsigned(cursor).unwrap() as usize + } +} + +#[coverage(off)] +fn align(pos: u64) -> u64 { + let alignment = std::mem::size_of::() as u64; + (pos + (alignment - 1)) & !(alignment - 1) +} + +#[coverage(off)] +pub fn cursor_align(cursor: &mut Cursor<&Vec>) { + cursor + .seek(SeekFrom::Start(align::(cursor.position()))) + .unwrap(); +} diff --git a/tools/fuzzing/src/generator.rs b/tools/fuzzing/src/generator.rs new file mode 100644 index 000000000..b12b0498d --- /dev/null +++ b/tools/fuzzing/src/generator.rs @@ -0,0 +1,2299 @@ +use ark_ec::AffineCurve; +use ark_ec::ProjectiveCurve; +use ark_ff::SquareRootField; +use ark_ff::{Field, UniformRand}; +use ledger::generators::zkapp_command_builder::get_transaction_commitments; +use ledger::proofs::field::FieldWitness; +use ledger::proofs::transaction::InnerCurve; +use ledger::scan_state::currency::{Magnitude, SlotSpan, TxnVersion}; +use ledger::{ + proofs::transaction::PlonkVerificationKeyEvals, + scan_state::{ + currency::{Amount, Balance, BlockTime, Fee, Length, MinMax, Nonce, Sgn, Signed, Slot}, + transaction_logic::{ + signed_command::{ + self, PaymentPayload, SignedCommand, SignedCommandPayload, StakeDelegationPayload, + }, + transaction_union_payload::TransactionUnionPayload, + zkapp_command::{ + self, AccountPreconditions, AccountUpdate, ClosedInterval, FeePayer, FeePayerBody, + MayUseToken, Numeric, OrIgnore, SetOrKeep, Update, ZkAppCommand, + }, + zkapp_statement::TransactionCommitment, + Memo, Transaction, UserCommand, + }, + }, + Account, AuthRequired, Permissions, ProofVerified, TokenId, TokenSymbol, VerificationKey, + VotingFor, ZkAppUri, +}; +use ledger::{SetVerificationKey, TXN_VERSION_CURRENT}; +use mina_curves::pasta::Fq; +use mina_hasher::Fp; +use mina_p2p_messages::array::ArrayN; +use mina_p2p_messages::list::List; +use mina_p2p_messages::v2::{ + PicklesProofProofsVerified2ReprStableV2StatementProofStateDeferredValuesPlonk, + PicklesWrapWireProofCommitmentsStableV1, PicklesWrapWireProofEvaluationsStableV1, + PicklesWrapWireProofStableV1, PicklesWrapWireProofStableV1Bulletproof, +}; +use mina_p2p_messages::{ + number::Number, + pseq::PaddedSeq, + v2::{ + CompositionTypesBranchDataDomainLog2StableV1, CompositionTypesBranchDataStableV1, + CompositionTypesDigestConstantStableV1, CurrencyAmountStableV1, CurrencyFeeStableV1, + LedgerHash, LimbVectorConstantHex64StableV1, MinaBaseAccountIdDigestStableV1, + MinaBaseCallStackDigestStableV1, MinaBaseFeeExcessStableV1, MinaBaseLedgerHash0StableV1, + MinaBasePendingCoinbaseCoinbaseStackStableV1, MinaBasePendingCoinbaseStackHashStableV1, + MinaBasePendingCoinbaseStackVersionedStableV1, MinaBasePendingCoinbaseStateStackStableV1, + MinaBaseStackFrameStableV1, MinaBaseTransactionStatusFailureCollectionStableV1, + MinaStateBlockchainStateValueStableV2LedgerProofStatementSource, + MinaTransactionLogicZkappCommandLogicLocalStateValueStableV1, + PicklesBaseProofsVerifiedStableV1, + PicklesProofProofsVerified2ReprStableV2MessagesForNextStepProof, + PicklesProofProofsVerified2ReprStableV2MessagesForNextWrapProof, + PicklesProofProofsVerified2ReprStableV2PrevEvals, + PicklesProofProofsVerified2ReprStableV2PrevEvalsEvals, + PicklesProofProofsVerified2ReprStableV2PrevEvalsEvalsEvals, + PicklesProofProofsVerified2ReprStableV2Statement, + PicklesProofProofsVerified2ReprStableV2StatementFp, + PicklesProofProofsVerified2ReprStableV2StatementProofState, + PicklesProofProofsVerified2ReprStableV2StatementProofStateDeferredValues, + PicklesProofProofsVerified2ReprStableV2StatementProofStateDeferredValuesPlonkFeatureFlags, + PicklesProofProofsVerifiedMaxStableV2, + PicklesReducedMessagesForNextProofOverSameFieldWrapChallengesVectorStableV2, + PicklesReducedMessagesForNextProofOverSameFieldWrapChallengesVectorStableV2A, + PicklesReducedMessagesForNextProofOverSameFieldWrapChallengesVectorStableV2AChallenge, + SgnStableV1, SignedAmount, TokenFeeExcess, TokenIdKeyHash, UnsignedExtendedUInt32StableV1, + UnsignedExtendedUInt64Int64ForVersionTagsStableV1, + }, +}; +use mina_signer::{ + CompressedPubKey, CurvePoint, Keypair, NetworkId, ScalarField, SecKey, Signature, Signer, +}; +use rand::distributions::DistString; +use rand::Rng; +use rand::{distributions::Alphanumeric, seq::SliceRandom}; +use std::{array, iter, ops::RangeInclusive, sync::Arc}; +use tuple_map::TupleMap2; + +use super::context::{FuzzerCtx, PermissionModel}; + +macro_rules! impl_default_generator_for_wrapper_type { + ($fuzz_ctx: ty, $wrapper: tt) => { + impl Generator<$wrapper> for $fuzz_ctx { + #[coverage(off)] + fn gen(&mut self) -> $wrapper { + $wrapper(self.gen()) + } + } + }; + ($fuzz_ctx: ty, $wrapper: tt, $inner: ty) => { + impl Generator<$wrapper> for $fuzz_ctx { + #[coverage(off)] + fn gen(&mut self) -> $wrapper { + let inner: $inner = self.gen(); + inner.into() + } + } + }; +} + +pub trait Generator { + fn gen(&mut self) -> T; +} + +impl Generator for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> bool { + self.gen.rng.gen_bool(0.5) + } +} + +/* +impl Generator for FuzzerCtx { + // rnd_base_field + fn gen(&mut self) -> Fp { + let mut bf = None; + + // TODO: optimize by masking out MSBs from bytes and remove loop + while bf.is_none() { + let bytes = self.gen.rng.gen::<[u8; 32]>(); + bf = Fp::from_random_bytes_with_flags::(&bytes); + } + + bf.unwrap().0 + } +} +*/ + +impl Generator for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> Fp { + Fp::rand(&mut self.gen.rng) + } +} + +impl Generator for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> Slot { + if self.gen.rng.gen_bool(0.9) { + self.txn_state_view.global_slot_since_genesis + } else { + Slot::from_u32(self.gen.rng.gen_range(0..Slot::max().as_u32())) + } + } +} + +impl Generator for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> SlotSpan { + if self.gen.rng.gen_bool(0.9) { + SlotSpan::from_u32(self.txn_state_view.global_slot_since_genesis.as_u32()) + } else { + SlotSpan::from_u32(self.gen.rng.gen_range(0..SlotSpan::max().as_u32())) + } + } +} + +impl Generator for FuzzerCtx { + /* + Reimplement random key generation w/o the restriction on CryptoRgn trait. + Since we are only using this for fuzzing we want a faster (unsafe) Rng like SmallRng. + */ + #[coverage(off)] + fn gen(&mut self) -> SecKey { + let secret: ScalarField = ScalarField::rand(&mut self.gen.rng); + SecKey::new(secret) + } +} + +impl Generator for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> Keypair { + let sec_key: SecKey = self.gen(); + let scalar = sec_key.into_scalar(); + let public = CurvePoint::prime_subgroup_generator() + .mul(scalar) + .into_affine(); + + let keypair = Keypair::from_parts_unsafe(scalar, public); + + if !self.state.potential_senders.iter().any( + #[coverage(off)] + |(kp, _)| kp.public == keypair.public, + ) { + let permission_model = self.gen(); + self.state + .potential_new_accounts + .push((keypair.clone(), permission_model)) + } + + keypair + } +} + +impl Generator for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> CompressedPubKey { + let keypair = if self.gen.rng.gen_bool(0.9) { + // use existing account + self.random_keypair() + } else { + // create new account + self.gen() + }; + + keypair.public.into_compressed() + } +} + +impl Generator for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> Memo { + Memo::with_number(self.gen.rng.gen()) + } +} + +impl + SquareRootField> Generator<(F, F)> for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> (F, F) { + /* + WARNING: we need to generate valid curve points to avoid binprot deserializarion + exceptions in the OCaml side. However this is an expensive task. + + TODO: a more efficient way of doing this? + */ + let mut x = F::rand(&mut self.gen.rng); + + loop { + let y_squared = x.square().mul(x).add(Into::::into(5)); + + if let Some(y) = y_squared.sqrt() { + return (x, y); + } + + x.add_assign(F::one()); + } + } +} + +impl Generator> for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> InnerCurve { + let (x, y) = self.gen(); + InnerCurve::::from((x, y)) + } +} + +impl Generator> for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> PlonkVerificationKeyEvals { + PlonkVerificationKeyEvals { + sigma: array::from_fn( + #[coverage(off)] + |_| self.gen(), + ), + coefficients: array::from_fn( + #[coverage(off)] + |_| self.gen(), + ), + generic: self.gen(), + psm: self.gen(), + complete_add: self.gen(), + mul: self.gen(), + emul: self.gen(), + endomul_scalar: self.gen(), + } + } +} + +impl Generator for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> VerificationKey { + VerificationKey { + max_proofs_verified: vec![ProofVerified::N0, ProofVerified::N1, ProofVerified::N2] + .choose(&mut self.gen.rng) + .unwrap() + .clone(), + actual_wrap_domain_size: vec![ProofVerified::N0, ProofVerified::N1, ProofVerified::N2] + .choose(&mut self.gen.rng) + .unwrap() + .clone(), + wrap_index: Box::new(self.gen()), + wrap_vk: None, // TODO + } + } +} + +/* +impl + Hashable> Generator> for FuzzerCtx +where + FuzzerCtx: Generator, +{ + fn gen(&mut self) -> zkapp_command::WithHash { + let data: T = self.gen(); + let hash = data.digest(); + zkapp_command::WithHash { data, hash } + } +} +*/ + +impl Generator> for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> zkapp_command::WithHash { + let data: VerificationKey = self.gen(); + let hash = data.digest(); + zkapp_command::WithHash { data, hash } + } +} + +impl Generator for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> AuthRequired { + *vec![ + AuthRequired::None, + AuthRequired::Either, + AuthRequired::Proof, + AuthRequired::Signature, + AuthRequired::Impossible, + //AuthRequired::Both, + ] + .choose(&mut self.gen.rng) + .unwrap() + } +} + +impl Generator for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> PermissionModel { + vec![ + PermissionModel::Any, + PermissionModel::Empty, + PermissionModel::Initial, + PermissionModel::Default, + PermissionModel::TokenOwner, + ] + .choose(&mut self.gen.rng) + .unwrap() + .clone() + } +} + +impl Generator for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> ZkAppUri { + /* + TODO: this needs to be fixed (assign a boundary) in the protocol since it is + possible to set a zkApp URI of arbitrary size. + + Since the field is opaque to the Mina protocol logic, randomly generating + URIs makes little sense and will consume a significant amount of ledger space. + */ + ZkAppUri::new() + } +} + +impl Generator for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> TokenSymbol { + /* + TokenSymbol must be <= 6 **bytes**. This boundary doesn't exist at type-level, + instead it is check by binprot after deserializing the *string* object: + https://github.com/MinaProtocol/mina/blob/develop/src/lib/mina_base/account.ml#L124 + + We will let this function generate strings larger than 6 bytes with low probability, + just to cover the error handling code, but must of the time we want to avoid failing + this check. + */ + if self.gen.rng.gen_bool(0.9) { + TokenSymbol::default() + } else { + let rnd_len = self.gen.rng.gen_range(1..=6); + // TODO: fix n random chars for n random bytes + TokenSymbol(Alphanumeric.sample_string(&mut self.gen.rng, rnd_len)) + } + } +} + +impl Generator for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> VotingFor { + VotingFor(self.gen()) + } +} + +impl Generator for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> zkapp_command::Events { + /* + An Event is a list of arrays of Fp, there doesn't seem to be any limit + neither in the size of the list or the array's size. The total size should + be bounded by the transport protocol (currently libp2p, ~32MB). + */ + + if self.gen.rng.gen_bool(0.9) { + zkapp_command::Events(Vec::new()) + } else { + // Generate random Events in the same fashion as Mina's generator (up to 5x3 elements). + let n = self.gen.rng.gen_range(0..=5); + + zkapp_command::Events( + (0..=n) + .map( + #[coverage(off)] + |_| { + let n = self.gen.rng.gen_range(0..=3); + zkapp_command::Event( + (0..=n) + .map( + #[coverage(off)] + |_| self.gen(), + ) + .collect(), + ) + }, + ) + .collect(), + ) + } + } +} + +impl Generator for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> zkapp_command::Actions { + // See comment in generator above + + if self.gen.rng.gen_bool(0.9) { + zkapp_command::Actions(Vec::new()) + } else { + let n = self.gen.rng.gen_range(0..=5); + + zkapp_command::Actions( + (0..=n) + .map( + #[coverage(off)] + |_| { + let n = self.gen.rng.gen_range(0..=3); + zkapp_command::Event( + (0..=n) + .map( + #[coverage(off)] + |_| self.gen(), + ) + .collect(), + ) + }, + ) + .collect(), + ) + } + } +} + +impl Generator for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> BlockTime { + self.gen.rng.gen() + } +} + +impl Generator for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> Length { + self.gen.rng.gen() + } +} + +pub trait GeneratorRange32 { + fn gen_range(&mut self, range: RangeInclusive) -> T; +} + +pub trait GeneratorRange64 { + fn gen_range(&mut self, range: RangeInclusive) -> T; +} + +impl GeneratorRange64 for FuzzerCtx { + #[coverage(off)] + fn gen_range(&mut self, range: RangeInclusive) -> Balance { + Balance::from_u64(self.gen.rng.gen_range(range)) + } +} + +impl GeneratorRange64 for FuzzerCtx { + #[coverage(off)] + fn gen_range(&mut self, range: RangeInclusive) -> Fee { + Fee::from_u64(self.gen.rng.gen_range(range)) + } +} + +impl GeneratorRange64 for FuzzerCtx { + #[coverage(off)] + fn gen_range(&mut self, range: RangeInclusive) -> Amount { + Amount::from_u64(self.gen.rng.gen_range(range)) + } +} + +impl GeneratorRange32 for FuzzerCtx { + #[coverage(off)] + fn gen_range(&mut self, range: RangeInclusive) -> Nonce { + Nonce::from_u32(self.gen.rng.gen_range(range)) + } +} + +impl GeneratorRange32 for FuzzerCtx { + #[coverage(off)] + fn gen_range(&mut self, range: RangeInclusive) -> Length { + Length::from_u32(self.gen.rng.gen_range(range)) + } +} + +pub trait GeneratorWrapper T> { + fn gen_wrap(&mut self, f: F) -> W; +} + +impl T> GeneratorWrapper, T, F> for FuzzerCtx { + #[coverage(off)] + fn gen_wrap(&mut self, mut f: F) -> Option { + if self.gen.rng.gen_bool(0.9) { + None + } else { + Some(f(self)) + } + } +} + +impl T> GeneratorWrapper, T, F> for FuzzerCtx { + #[coverage(off)] + fn gen_wrap(&mut self, mut f: F) -> OrIgnore { + if self.gen.rng.gen_bool(0.5) { + OrIgnore::Ignore + } else { + OrIgnore::Check(f(self)) + } + } +} + +impl T> GeneratorWrapper, T, F> for FuzzerCtx { + #[coverage(off)] + fn gen_wrap(&mut self, mut f: F) -> SetOrKeep { + if self.gen.rng.gen_bool(0.5) { + SetOrKeep::Keep + } else { + SetOrKeep::Set(f(self)) + } + } +} + +impl T> GeneratorWrapper, T, F> + for FuzzerCtx +{ + #[coverage(off)] + fn gen_wrap(&mut self, mut f: F) -> ClosedInterval { + if self.gen.rng.gen_bool(0.5) { + // constant case + let val = f(self); + + ClosedInterval { + lower: val.clone(), + upper: val, + } + } else { + ClosedInterval { + lower: f(self), + upper: f(self), + } + } + } +} + +impl Generator for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> Sgn { + if self.gen.rng.gen_bool(0.5) { + Sgn::Pos + } else { + Sgn::Neg + } + } +} + +pub trait GeneratorWrapperMany T> { + fn gen_wrap_many(&mut self, f: F) -> W; +} + +impl T, const N: u64> GeneratorWrapperMany, T, F> + for FuzzerCtx +{ + #[coverage(off)] + fn gen_wrap_many(&mut self, mut f: F) -> ArrayN { + iter::repeat_with( + #[coverage(off)] + || f(self), + ) + .take(N as usize) + .collect() + } +} + +impl Generator> for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> Numeric { + self.gen_wrap( + #[coverage(off)] + |x| { + x.gen_wrap( + #[coverage(off)] + |x| -> Amount { GeneratorRange64::gen_range(x, 0..=u64::MAX) }, + ) + }, + ) + } +} + +impl Generator> for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> Numeric { + self.gen_wrap( + #[coverage(off)] + |x| { + x.gen_wrap( + #[coverage(off)] + |x| -> Length { GeneratorRange32::gen_range(x, 0..=u32::MAX) }, + ) + }, + ) + } +} + +impl Generator for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> FeePayer { + let public_key = if self.gen.attempt_valid_zkapp || self.gen.rng.gen_bool(0.9) { + self.random_keypair().public.into_compressed() + } else { + self.gen() + }; + + let account = self.get_account(&public_key); + // FIXME: boundary at i64 MAX because OCaml side is encoding it as int + let max_fee = match account.as_ref() { + Some(account) if self.gen.attempt_valid_zkapp || self.gen.rng.gen_bool(0.9) => { + self.gen.minimum_fee.max(account.balance.as_u64()) + } + _ => self + .gen + .rng + .gen_range(self.gen.minimum_fee + 1..=10_000_000), + }; + + let fee: Fee = GeneratorRange64::gen_range(self, self.gen.minimum_fee..=max_fee); + + let nonce = match self.gen.nonces.get(&public_key.into_address()) { + Some(nonce) => *nonce, + None => account + .as_ref() + .map(|account| account.nonce) + .unwrap_or_else(|| { + if self.gen.rng.gen_bool(0.9) { + Nonce::from_u32(0) + } else { + GeneratorRange32::gen_range(self, 0..=u32::MAX) + } + }), + }; + + self.gen + .nonces + .insert(public_key.into_address(), nonce.incr()); + + FeePayer { + body: FeePayerBody { + public_key, + fee, + valid_until: self.gen_wrap( + #[coverage(off)] + |x| -> Slot { x.gen() }, + ), + nonce, + }, + // filled later when tx is complete + authorization: Signature::dummy(), + } + } +} + +impl Generator for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> zkapp_command::EpochData { + zkapp_command::EpochData::new( + zkapp_command::EpochLedger { + hash: self.gen_wrap( + #[coverage(off)] + |x| x.gen(), + ), + total_currency: self.gen(), + }, + self.gen_wrap( + #[coverage(off)] + |x| x.gen(), + ), + self.gen_wrap( + #[coverage(off)] + |x| x.gen(), + ), + self.gen_wrap( + #[coverage(off)] + |x| x.gen(), + ), + self.gen(), + ) + } +} + +impl Generator for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> LimbVectorConstantHex64StableV1 { + LimbVectorConstantHex64StableV1(Number(self.gen.rng.gen())) + } +} + +impl + Generator< + PicklesProofProofsVerified2ReprStableV2StatementProofStateDeferredValuesPlonkFeatureFlags, + > for FuzzerCtx +{ + #[coverage(off)] + fn gen( + &mut self, + ) -> PicklesProofProofsVerified2ReprStableV2StatementProofStateDeferredValuesPlonkFeatureFlags + { + PicklesProofProofsVerified2ReprStableV2StatementProofStateDeferredValuesPlonkFeatureFlags { + range_check0: self.gen.rng.gen_bool(0.5), + range_check1: self.gen.rng.gen_bool(0.5), + foreign_field_add: self.gen.rng.gen_bool(0.5), + foreign_field_mul: self.gen.rng.gen_bool(0.5), + xor: self.gen.rng.gen_bool(0.5), + rot: self.gen.rng.gen_bool(0.5), + lookup: self.gen.rng.gen_bool(0.5), + runtime_tables: self.gen.rng.gen_bool(0.5), + } + } +} + +impl Generator + for FuzzerCtx +{ + #[coverage(off)] + fn gen( + &mut self, + ) -> PicklesProofProofsVerified2ReprStableV2StatementProofStateDeferredValuesPlonk { + PicklesProofProofsVerified2ReprStableV2StatementProofStateDeferredValuesPlonk { + alpha: self.gen(), + beta: self.gen(), + gamma: self.gen(), + zeta: self.gen(), + joint_combiner: self.gen_wrap( + #[coverage(off)] + |x| x.gen(), + ), + feature_flags: self.gen(), + } + } +} + +impl Generator for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> mina_p2p_messages::bigint::BigInt { + mina_p2p_messages::bigint::BigInt::from(Generator::::gen(self)) + } +} + +impl Generator for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> PicklesProofProofsVerified2ReprStableV2StatementFp { + PicklesProofProofsVerified2ReprStableV2StatementFp::ShiftedValue(self.gen()) + } +} + +impl Generator + for FuzzerCtx +{ + #[coverage(off)] + fn gen( + &mut self, + ) -> PicklesReducedMessagesForNextProofOverSameFieldWrapChallengesVectorStableV2A { + PicklesReducedMessagesForNextProofOverSameFieldWrapChallengesVectorStableV2A { + prechallenge: self.gen(), + } + } +} + +impl Generator> for FuzzerCtx +where + FuzzerCtx: Generator, +{ + #[coverage(off)] + fn gen(&mut self) -> PaddedSeq { + PaddedSeq::(array::from_fn( + #[coverage(off)] + |_| self.gen(), + )) + } +} + +impl T, const N: usize> GeneratorWrapper, T, F> + for FuzzerCtx +{ + #[coverage(off)] + fn gen_wrap(&mut self, mut f: F) -> PaddedSeq { + PaddedSeq::(array::from_fn( + #[coverage(off)] + |_| f(self), + )) + } +} + +impl + Generator + for FuzzerCtx +{ + #[coverage(off)] + fn gen( + &mut self, + ) -> PicklesReducedMessagesForNextProofOverSameFieldWrapChallengesVectorStableV2AChallenge { + PicklesReducedMessagesForNextProofOverSameFieldWrapChallengesVectorStableV2AChallenge { + inner: self.gen(), + } + } +} + +impl Generator for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> CompositionTypesBranchDataDomainLog2StableV1 { + CompositionTypesBranchDataDomainLog2StableV1(mina_p2p_messages::char::Char( + self.gen.rng.gen(), + )) + } +} + +impl Generator for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> CompositionTypesBranchDataStableV1 { + CompositionTypesBranchDataStableV1 { + proofs_verified: vec![ + PicklesBaseProofsVerifiedStableV1::N0, + PicklesBaseProofsVerifiedStableV1::N1, + PicklesBaseProofsVerifiedStableV1::N2, + ] + .choose(&mut self.gen.rng) + .unwrap() + .clone(), + domain_log2: self.gen(), + } + } +} + +impl Generator + for FuzzerCtx +{ + #[coverage(off)] + fn gen(&mut self) -> PicklesProofProofsVerified2ReprStableV2StatementProofStateDeferredValues { + PicklesProofProofsVerified2ReprStableV2StatementProofStateDeferredValues { + plonk: self.gen(), + bulletproof_challenges: self.gen(), + branch_data: self.gen(), + } + } +} + +impl Generator for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> CompositionTypesDigestConstantStableV1 { + CompositionTypesDigestConstantStableV1(self.gen()) + } +} + +impl Generator + for FuzzerCtx +{ + #[coverage(off)] + fn gen( + &mut self, + ) -> PicklesReducedMessagesForNextProofOverSameFieldWrapChallengesVectorStableV2 { + PicklesReducedMessagesForNextProofOverSameFieldWrapChallengesVectorStableV2(self.gen()) + } +} + +#[coverage(off)] +pub fn gen_curve_point>( + ctx: &mut impl Generator<(T, T)>, +) -> ( + mina_p2p_messages::bigint::BigInt, + mina_p2p_messages::bigint::BigInt, +) +where + mina_p2p_messages::bigint::BigInt: From, +{ + Generator::<(T, T)>::gen(ctx).map(mina_p2p_messages::bigint::BigInt::from) +} + +#[coverage(off)] +pub fn gen_curve_point_many, const N: u64>( + ctx: &mut FuzzerCtx, +) -> ArrayN< + ( + mina_p2p_messages::bigint::BigInt, + mina_p2p_messages::bigint::BigInt, + ), + { N }, +> +where + mina_p2p_messages::bigint::BigInt: From, +{ + ctx.gen_wrap_many( + #[coverage(off)] + |x| gen_curve_point::(x), + ) +} + +#[coverage(off)] +pub fn gen_curve_point_many_unzip, const N: u64>( + ctx: &mut FuzzerCtx, +) -> ( + ArrayN, + ArrayN, +) +where + mina_p2p_messages::bigint::BigInt: From + Default, +{ + let (a, b): (Vec<_>, Vec<_>) = gen_curve_point_many::(ctx).into_iter().unzip(); + + ( + ArrayN::from_iter(a.into_iter()), + ArrayN::from_iter(b.into_iter()), + ) +} + +impl Generator for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> PicklesProofProofsVerified2ReprStableV2MessagesForNextWrapProof { + PicklesProofProofsVerified2ReprStableV2MessagesForNextWrapProof { + challenge_polynomial_commitment: gen_curve_point::(self), + old_bulletproof_challenges: self.gen(), + } + } +} + +impl Generator for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> PicklesProofProofsVerified2ReprStableV2StatementProofState { + PicklesProofProofsVerified2ReprStableV2StatementProofState { + deferred_values: self.gen(), + sponge_digest_before_evaluations: self.gen(), + messages_for_next_wrap_proof: self.gen(), + } + } +} + +impl Generator for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> PicklesProofProofsVerified2ReprStableV2MessagesForNextStepProof { + PicklesProofProofsVerified2ReprStableV2MessagesForNextStepProof { + app_state: (), + challenge_polynomial_commitments: List::one(gen_curve_point::(self)), // TODO: variable num of elements + old_bulletproof_challenges: List::one(self.gen_wrap( + // TODO: variable num of elements + #[coverage(off)] + |x| x.gen(), + )), + } + } +} + +impl Generator for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> PicklesProofProofsVerified2ReprStableV2Statement { + PicklesProofProofsVerified2ReprStableV2Statement { + proof_state: self.gen(), + messages_for_next_step_proof: self.gen(), + } + } +} + +// impl Generator for FuzzerCtx { +// #[coverage(off)] +// fn gen(&mut self) -> PicklesProofProofsVerified2ReprStableV2PrevEvalsEvalsEvalsLookupA { +// PicklesProofProofsVerified2ReprStableV2PrevEvalsEvalsEvalsLookupA { +// sorted: self.gen_wrap_many( +// #[coverage(off)] +// |x| gen_curve_point_many_unzip::(x, 1), +// 1, +// ), +// aggreg: gen_curve_point_many_unzip::(self, 1), +// table: gen_curve_point_many_unzip::(self, 1), +// runtime: self.gen_wrap( +// #[coverage(off)] +// |x| gen_curve_point_many_unzip::(x, 1), +// ), +// } +// } +// } + +impl Generator for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> PicklesProofProofsVerified2ReprStableV2PrevEvalsEvalsEvals { + PicklesProofProofsVerified2ReprStableV2PrevEvalsEvalsEvals { + w: self.gen_wrap( + #[coverage(off)] + |x| gen_curve_point_many_unzip::(x), + ), + complete_add_selector: gen_curve_point_many_unzip::(self), + mul_selector: gen_curve_point_many_unzip::(self), + emul_selector: gen_curve_point_many_unzip::(self), + endomul_scalar_selector: gen_curve_point_many_unzip::(self), + range_check0_selector: self.gen_wrap( + #[coverage(off)] + |x| gen_curve_point_many_unzip::(x), + ), + range_check1_selector: self.gen_wrap( + #[coverage(off)] + |x| gen_curve_point_many_unzip::(x), + ), + foreign_field_add_selector: self.gen_wrap( + #[coverage(off)] + |x| gen_curve_point_many_unzip::(x), + ), + foreign_field_mul_selector: self.gen_wrap( + #[coverage(off)] + |x| gen_curve_point_many_unzip::(x), + ), + xor_selector: self.gen_wrap( + #[coverage(off)] + |x| gen_curve_point_many_unzip::(x), + ), + rot_selector: self.gen_wrap( + #[coverage(off)] + |x| gen_curve_point_many_unzip::(x), + ), + lookup_aggregation: self.gen_wrap( + #[coverage(off)] + |x| gen_curve_point_many_unzip::(x), + ), + lookup_table: self.gen_wrap( + #[coverage(off)] + |x| gen_curve_point_many_unzip::(x), + ), + lookup_sorted: self.gen_wrap( + #[coverage(off)] + |x| { + x.gen_wrap( + #[coverage(off)] + |x| gen_curve_point_many_unzip::(x), + ) + }, + ), + runtime_lookup_table: self.gen_wrap( + #[coverage(off)] + |x| gen_curve_point_many_unzip::(x), + ), + runtime_lookup_table_selector: self.gen_wrap( + #[coverage(off)] + |x| gen_curve_point_many_unzip::(x), + ), + xor_lookup_selector: self.gen_wrap( + #[coverage(off)] + |x| gen_curve_point_many_unzip::(x), + ), + lookup_gate_lookup_selector: self.gen_wrap( + #[coverage(off)] + |x| gen_curve_point_many_unzip::(x), + ), + range_check_lookup_selector: self.gen_wrap( + #[coverage(off)] + |x| gen_curve_point_many_unzip::(x), + ), + foreign_field_mul_lookup_selector: self.gen_wrap( + #[coverage(off)] + |x| gen_curve_point_many_unzip::(x), + ), + coefficients: self.gen_wrap( + #[coverage(off)] + |x| gen_curve_point_many_unzip::(x), + ), + z: gen_curve_point_many_unzip::(self), + s: self.gen_wrap( + #[coverage(off)] + |x| gen_curve_point_many_unzip::(x), + ), + generic_selector: gen_curve_point_many_unzip::(self), + poseidon_selector: gen_curve_point_many_unzip::(self), + } + } +} + +impl Generator for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> PicklesProofProofsVerified2ReprStableV2PrevEvalsEvals { + PicklesProofProofsVerified2ReprStableV2PrevEvalsEvals { + public_input: gen_curve_point::(self), + evals: self.gen(), + } + } +} + +impl Generator for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> PicklesProofProofsVerified2ReprStableV2PrevEvals { + PicklesProofProofsVerified2ReprStableV2PrevEvals { + evals: self.gen(), + ft_eval1: mina_p2p_messages::bigint::BigInt::from(Generator::::gen(self)), + } + } +} + +// impl Generator for FuzzerCtx { +// #[coverage(off)] +// fn gen(&mut self) -> PicklesProofProofsVerified2ReprStableV2ProofMessagesLookupA { +// PicklesProofProofsVerified2ReprStableV2ProofMessagesLookupA { +// sorted: self.gen_wrap_many( +// #[coverage(off)] +// |x| gen_curve_point_many::(x, 1), +// 1, +// ), +// aggreg: gen_curve_point_many::(self, 1), +// runtime: self.gen_wrap( +// #[coverage(off)] +// |x| gen_curve_point_many::(x, 1), +// ), +// } +// } +// } + +// impl Generator for FuzzerCtx { +// #[coverage(off)] +// fn gen(&mut self) -> PicklesProofProofsVerified2ReprStableV2ProofMessages { +// PicklesProofProofsVerified2ReprStableV2ProofMessages { +// w_comm: self.gen_wrap( +// #[coverage(off)] +// |x| gen_curve_point_many::(x, 1), +// ), +// z_comm: gen_curve_point_many::(self, 1), +// t_comm: gen_curve_point_many::(self, 1), +// lookup: self.gen_wrap( +// #[coverage(off)] +// |x| x.gen(), +// ), +// } +// } +// } + +// impl Generator for FuzzerCtx { +// #[coverage(off)] +// fn gen(&mut self) -> PicklesProofProofsVerified2ReprStableV2ProofOpeningsProof { +// PicklesProofProofsVerified2ReprStableV2ProofOpeningsProof { +// lr: self.gen_wrap_many( +// #[coverage(off)] +// |x| (gen_curve_point::(x), gen_curve_point::(x)), +// 1, +// ), +// z_1: self.gen(), +// z_2: self.gen(), +// delta: gen_curve_point::(self), +// challenge_polynomial_commitment: gen_curve_point::(self), +// } +// } +// } + +// impl Generator for FuzzerCtx { +// #[coverage(off)] +// fn gen(&mut self) -> PicklesProofProofsVerified2ReprStableV2ProofOpenings { +// PicklesProofProofsVerified2ReprStableV2ProofOpenings { +// proof: self.gen(), +// evals: self.gen(), +// ft_eval1: self.gen(), +// } +// } +// } + +// impl Generator for FuzzerCtx { +// #[coverage(off)] +// fn gen(&mut self) -> PicklesProofProofsVerified2ReprStableV2Proof { +// PicklesProofProofsVerified2ReprStableV2Proof { +// messages: self.gen(), +// openings: self.gen(), +// } +// } +// } + +impl Generator for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> PicklesWrapWireProofCommitmentsStableV1 { + PicklesWrapWireProofCommitmentsStableV1 { + w_comm: self.gen_wrap( + #[coverage(off)] + |x| gen_curve_point::(x), + ), + z_comm: gen_curve_point::(self), + t_comm: self.gen_wrap( + #[coverage(off)] + |x| gen_curve_point::(x), + ), + } + } +} + +impl Generator for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> PicklesWrapWireProofStableV1Bulletproof { + PicklesWrapWireProofStableV1Bulletproof { + lr: self.gen_wrap_many( + #[coverage(off)] + |x| (gen_curve_point::(x), gen_curve_point::(x)), + ), + z_1: Generator::::gen(self).into(), + z_2: Generator::::gen(self).into(), + delta: gen_curve_point::(self), + challenge_polynomial_commitment: gen_curve_point::(self), + } + } +} + +impl Generator for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> PicklesWrapWireProofEvaluationsStableV1 { + PicklesWrapWireProofEvaluationsStableV1 { + w: self.gen_wrap( + #[coverage(off)] + |x| gen_curve_point::(x), + ), + coefficients: self.gen_wrap( + #[coverage(off)] + |x| gen_curve_point::(x), + ), + z: gen_curve_point::(self), + s: self.gen_wrap( + #[coverage(off)] + |x| gen_curve_point::(x), + ), + generic_selector: gen_curve_point::(self), + poseidon_selector: gen_curve_point::(self), + complete_add_selector: gen_curve_point::(self), + mul_selector: gen_curve_point::(self), + emul_selector: gen_curve_point::(self), + endomul_scalar_selector: gen_curve_point::(self), + } + } +} + +impl Generator for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> PicklesWrapWireProofStableV1 { + PicklesWrapWireProofStableV1 { + bulletproof: self.gen(), + evaluations: self.gen(), + ft_eval1: self.gen(), + commitments: self.gen(), + } + } +} + +impl Generator for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> PicklesProofProofsVerifiedMaxStableV2 { + PicklesProofProofsVerifiedMaxStableV2 { + statement: self.gen(), + prev_evals: self.gen(), + proof: self.gen(), + } + } +} + +impl Generator for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> zkapp_command::SideLoadedProof { + Arc::new(self.gen()) + } +} + +impl Generator> for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> SetVerificationKey { + SetVerificationKey { + auth: self.gen(), + txn_version: if self.gen.rng.gen_bool(0.9) { + TXN_VERSION_CURRENT + } else { + TxnVersion::from(self.gen.rng.gen()) + }, + } + } +} + +impl Generator for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> Balance { + GeneratorRange64::::gen_range(self, 0..=Balance::max().as_u64()) + } +} + +impl Generator for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> Amount { + GeneratorRange64::::gen_range(self, 0..=Amount::max().as_u64()) + } +} + +pub trait GeneratorFromAccount { + fn gen_from_account(&mut self, account: &Account) -> T; +} + +impl GeneratorFromAccount for FuzzerCtx { + #[coverage(off)] + fn gen_from_account(&mut self, account: &Account) -> zkapp_command::AuthorizationKind { + let vk_hash = if self.gen.rng.gen_bool(0.9) + && account.zkapp.is_some() + && account.zkapp.as_ref().unwrap().verification_key.is_some() + { + account + .zkapp + .as_ref() + .unwrap() + .verification_key + .as_ref() + .unwrap() + .hash() + } else { + self.gen() + }; + + let options = vec![ + zkapp_command::AuthorizationKind::NoneGiven, + zkapp_command::AuthorizationKind::Signature, + zkapp_command::AuthorizationKind::Proof(vk_hash), + ]; + + options.choose(&mut self.gen.rng).unwrap().clone() + } +} + +impl GeneratorFromAccount> for FuzzerCtx { + #[coverage(off)] + fn gen_from_account(&mut self, account: &Account) -> Permissions { + let permission_model = match self.find_permissions(&account.public_key) { + Some(model) => model, + None => [ + PermissionModel::Any, + PermissionModel::Empty, + PermissionModel::Initial, + PermissionModel::TokenOwner, + ] + .choose(&mut self.gen.rng) + .unwrap(), + }; + + match permission_model { + PermissionModel::Any => Permissions:: { + edit_state: self.gen(), + access: self.gen(), + send: self.gen(), + receive: self.gen(), + set_delegate: self.gen(), + set_permissions: self.gen(), + set_verification_key: self.gen(), + set_zkapp_uri: self.gen(), + edit_action_state: self.gen(), + set_token_symbol: self.gen(), + increment_nonce: self.gen(), + set_voting_for: self.gen(), + set_timing: self.gen(), + }, + PermissionModel::Empty => Permissions::::empty(), + PermissionModel::Initial => Permissions::::user_default(), + PermissionModel::Default => Permissions:: { + edit_state: AuthRequired::Proof, + access: AuthRequired::None, + send: AuthRequired::Signature, + receive: AuthRequired::None, + set_delegate: AuthRequired::Signature, + set_permissions: AuthRequired::Signature, + set_verification_key: SetVerificationKey:: { + auth: AuthRequired::Signature, + txn_version: TXN_VERSION_CURRENT, + }, + set_zkapp_uri: AuthRequired::Signature, + edit_action_state: AuthRequired::Proof, + set_token_symbol: AuthRequired::Signature, + increment_nonce: AuthRequired::Signature, + set_voting_for: AuthRequired::Signature, + set_timing: AuthRequired::Proof, + }, + PermissionModel::TokenOwner => Permissions:: { + edit_state: AuthRequired::Either, // Proof | Signature + access: AuthRequired::Either, // Proof | Signature + send: AuthRequired::Signature, + receive: AuthRequired::Proof, + set_delegate: AuthRequired::Signature, + set_permissions: AuthRequired::Signature, + set_verification_key: SetVerificationKey:: { + auth: AuthRequired::Signature, + txn_version: TXN_VERSION_CURRENT, + }, + set_zkapp_uri: AuthRequired::Signature, + edit_action_state: AuthRequired::Proof, + set_token_symbol: AuthRequired::Signature, + increment_nonce: AuthRequired::Signature, + set_voting_for: AuthRequired::Signature, + set_timing: AuthRequired::Proof, + }, + } + } +} + +impl GeneratorFromAccount> for FuzzerCtx +where + FuzzerCtx: GeneratorFromAccount, +{ + #[coverage(off)] + fn gen_from_account(&mut self, account: &Account) -> ClosedInterval { + ClosedInterval { + lower: self.gen_from_account(account), + upper: self.gen_from_account(account), + } + } +} + +impl GeneratorFromAccount for FuzzerCtx { + #[coverage(off)] + fn gen_from_account(&mut self, account: &Account) -> Fee { + GeneratorRange64::::gen_range(self, 0..=account.balance.as_u64() / 100) + } +} + +impl GeneratorFromAccount for FuzzerCtx { + #[coverage(off)] + fn gen_from_account(&mut self, account: &Account) -> Balance { + GeneratorRange64::::gen_range(self, 0..=(account.balance.as_u64() / 100)) + } +} + +impl GeneratorFromAccount> for FuzzerCtx { + #[coverage(off)] + fn gen_from_account(&mut self, account: &Account) -> Signed { + if self.gen.rng.gen_bool(0.9) { + Signed::::zero() + } else { + if self.gen.token_id == TokenId::default() { + if self.gen.excess_fee.is_zero() { + let magnitude = GeneratorRange64::::gen_range( + self, + 0..=(account.balance.as_u64().wrapping_div(10).saturating_mul(7)), + ); + self.gen.excess_fee = Signed::::create(magnitude, self.gen()); + self.gen.excess_fee + } else { + let ret = self.gen.excess_fee.negate(); + self.gen.excess_fee = Signed::::zero(); + ret + } + } else { + // Custom Tokens + let magnitude = GeneratorRange64::::gen_range(self, 0..=u64::MAX); + Signed::::create(magnitude, self.gen()) + } + } + } +} + +impl GeneratorFromAccount for FuzzerCtx { + #[coverage(off)] + fn gen_from_account(&mut self, account: &Account) -> Amount { + if self.gen.token_id == TokenId::default() && self.gen.rng.gen_bool(0.9) { + GeneratorRange64::::gen_range(self, 0..=account.balance.as_u64()) + } else { + // Custom Tokens + GeneratorRange64::::gen_range(self, 0..=u64::MAX) + } + } +} + +impl GeneratorFromAccount for FuzzerCtx { + #[coverage(off)] + fn gen_from_account(&mut self, account: &Account) -> zkapp_command::Timing { + if self.gen.rng.gen_bool(0.5) { + zkapp_command::Timing { + initial_minimum_balance: Balance::zero(), + cliff_time: Slot::zero(), + cliff_amount: Amount::zero(), + vesting_period: SlotSpan::from_u32(1), + vesting_increment: Amount::zero(), + } + } else { + zkapp_command::Timing { + initial_minimum_balance: self.gen_from_account(account), + cliff_time: self.gen(), + cliff_amount: self.gen_from_account(account), + vesting_period: self.gen(), + vesting_increment: self.gen_from_account(account), + } + } + } +} + +impl GeneratorFromAccount for FuzzerCtx { + #[coverage(off)] + fn gen_from_account(&mut self, account: &Account) -> Nonce { + let nonce = match self.gen.nonces.get(&account.public_key.into_address()) { + Some(nonce) => *nonce, + None => account.nonce, + }; + // We assume successful aplication + self.gen + .nonces + .insert(account.public_key.into_address(), nonce.incr()); + nonce + } +} + +impl GeneratorFromAccount for FuzzerCtx { + #[coverage(off)] + fn gen_from_account(&mut self, account: &Account) -> Update { + Update { + app_state: if self.gen.rng.gen_bool(0.9) { + array::from_fn( + #[coverage(off)] + |_| { + self.gen_wrap( + #[coverage(off)] + |x| x.gen(), + ) + }, + ) + } else { + // cover changing_entire_app_state + array::from_fn( + #[coverage(off)] + |_| SetOrKeep::Set(self.gen()), + ) + }, + delegate: self.gen_wrap( + #[coverage(off)] + |x| x.gen(), + ), + verification_key: self.gen_wrap( + #[coverage(off)] + |x| x.gen(), + ), + permissions: self.gen_wrap( + #[coverage(off)] + |x| x.gen_from_account(account), + ), + zkapp_uri: self.gen_wrap( + #[coverage(off)] + |x| x.gen(), + ), + token_symbol: self.gen_wrap( + #[coverage(off)] + |x| x.gen(), + ), + timing: self.gen_wrap( + #[coverage(off)] + |x| x.gen_from_account(account), + ), + voting_for: self.gen_wrap( + #[coverage(off)] + |x| x.gen(), + ), + } + } +} + +impl GeneratorFromAccount for FuzzerCtx { + #[coverage(off)] + fn gen_from_account(&mut self, account: &Account) -> zkapp_command::ZkAppPreconditions { + if self.gen.rng.gen_bool(0.9) { + zkapp_command::ZkAppPreconditions::accept() + } else { + zkapp_command::ZkAppPreconditions { + snarked_ledger_hash: self.gen_wrap( + #[coverage(off)] + |x| { + if x.gen.rng.gen_bool(0.9) { + x.txn_state_view.snarked_ledger_hash + } else { + x.gen() + } + }, + ), + blockchain_length: self.gen_wrap( + #[coverage(off)] + |x| { + x.gen_wrap( + #[coverage(off)] + |x| { + if x.gen.rng.gen_bool(0.9) { + x.txn_state_view.blockchain_length + } else { + x.gen() + } + }, + ) + }, + ), + min_window_density: self.gen_wrap( + #[coverage(off)] + |x| { + x.gen_wrap( + #[coverage(off)] + |x| { + if x.gen.rng.gen_bool(0.9) { + x.txn_state_view.min_window_density + } else { + x.gen() + } + }, + ) + }, + ), + total_currency: self.gen_wrap( + #[coverage(off)] + |x| { + x.gen_wrap( + #[coverage(off)] + |x| { + if x.gen.rng.gen_bool(0.9) { + x.txn_state_view.total_currency + } else { + x.gen_from_account(account) + } + }, + ) + }, + ), + global_slot_since_genesis: self.gen_wrap( + #[coverage(off)] + |x| { + x.gen_wrap( + #[coverage(off)] + |x| x.gen(), + ) + }, + ), + + staking_epoch_data: if self.gen.rng.gen_bool(0.9) { + let epoch_data = self.txn_state_view.staking_epoch_data.clone(); + + zkapp_command::EpochData::new( + zkapp_command::EpochLedger { + hash: OrIgnore::Check(epoch_data.ledger.hash), + total_currency: OrIgnore::Check(ClosedInterval:: { + lower: epoch_data.ledger.total_currency.clone(), + upper: epoch_data.ledger.total_currency, + }), + }, + OrIgnore::Check(epoch_data.seed), + OrIgnore::Check(epoch_data.start_checkpoint), + OrIgnore::Check(epoch_data.lock_checkpoint), + OrIgnore::Check(ClosedInterval:: { + lower: epoch_data.epoch_length.clone(), + upper: epoch_data.epoch_length, + }), + ) + } else { + self.gen() + }, + next_epoch_data: if self.gen.rng.gen_bool(0.9) { + let epoch_data = self.txn_state_view.next_epoch_data.clone(); + zkapp_command::EpochData::new( + zkapp_command::EpochLedger { + hash: OrIgnore::Check(epoch_data.ledger.hash), + total_currency: OrIgnore::Check(ClosedInterval:: { + lower: epoch_data.ledger.total_currency.clone(), + upper: epoch_data.ledger.total_currency, + }), + }, + OrIgnore::Check(epoch_data.seed), + OrIgnore::Check(epoch_data.start_checkpoint), + OrIgnore::Check(epoch_data.lock_checkpoint), + OrIgnore::Check(ClosedInterval:: { + lower: epoch_data.epoch_length.clone(), + upper: epoch_data.epoch_length, + }), + ) + } else { + self.gen() + }, + } + } + } +} + +impl GeneratorFromAccount for FuzzerCtx { + #[coverage(off)] + fn gen_from_account(&mut self, account: &Account) -> zkapp_command::Account { + if self.gen.rng.gen_bool(0.9) { + zkapp_command::Account::accept() + } else { + zkapp_command::Account { + balance: self.gen_wrap( + #[coverage(off)] + |x| { + x.gen_wrap( + #[coverage(off)] + |x| x.gen_from_account(account), + ) + }, + ), + nonce: self.gen_wrap( + #[coverage(off)] + |x| { + x.gen_wrap( + #[coverage(off)] + |x| x.gen_from_account(account), + ) + }, + ), + receipt_chain_hash: self.gen_wrap( + #[coverage(off)] + |x| { + if x.gen.rng.gen_bool(0.9) { + account.receipt_chain_hash.0 + } else { + x.gen() + } + }, + ), + delegate: self.gen_wrap( + #[coverage(off)] + |x| { + let rnd_pk: CompressedPubKey = x.gen(); + account + .delegate + .as_ref() + .map( + #[coverage(off)] + |pk| { + if x.gen.rng.gen_bool(0.9) { + pk.clone() + } else { + rnd_pk.clone() + } + }, + ) + .unwrap_or(rnd_pk) + }, + ), + state: { + let rnd_state = array::from_fn( + #[coverage(off)] + |_| { + self.gen_wrap( + #[coverage(off)] + |x| x.gen(), + ) + }, + ); + + account + .zkapp + .as_ref() + .map( + #[coverage(off)] + |zkapp| { + if self.gen.rng.gen_bool(0.9) { + zkapp.app_state.map(OrIgnore::Check) + } else { + rnd_state.clone() + } + }, + ) + .unwrap_or(rnd_state) + }, + action_state: self.gen_wrap( + #[coverage(off)] + |x| x.gen(), + ), + proved_state: self.gen_wrap( + #[coverage(off)] + |x| { + let rnd_bool = x.gen.rng.gen_bool(0.5); + account + .zkapp + .as_ref() + .map( + #[coverage(off)] + |zkapp| { + if x.gen.rng.gen_bool(0.9) { + zkapp.proved_state + } else { + rnd_bool + } + }, + ) + .unwrap_or(rnd_bool) + }, + ), + is_new: self.gen_wrap( + #[coverage(off)] + |x| x.gen.rng.gen_bool(0.5), + ), + } + } + } +} + +impl GeneratorFromAccount for FuzzerCtx { + #[coverage(off)] + fn gen_from_account(&mut self, account: &Account) -> AccountPreconditions { + AccountPreconditions(self.gen_from_account(account)) + } +} + +impl GeneratorFromAccount for FuzzerCtx { + #[coverage(off)] + fn gen_from_account(&mut self, account: &Account) -> zkapp_command::Preconditions { + zkapp_command::Preconditions::new( + self.gen_from_account(account), + self.gen_from_account(account), + self.gen_wrap( + #[coverage(off)] + |x| { + x.gen_wrap( + #[coverage(off)] + |x| x.gen(), + ) + }, + ), + ) + } +} + +impl Generator for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> TokenId { + if self.gen.rng.gen_bool(0.9) { + TokenId::default() + } else { + TokenId(self.gen()) + } + } +} + +impl Generator for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> MayUseToken { + if self.gen.token_id.is_default() { + MayUseToken::No + } else { + match vec![0, 1, 2].choose(&mut self.gen.rng).unwrap() { + 0 => MayUseToken::No, + 1 => MayUseToken::ParentsOwnToken, + 2 => MayUseToken::InheritFromParent, + _ => unimplemented!(), + } + } + } +} + +impl GeneratorFromAccount for FuzzerCtx { + #[coverage(off)] + fn gen_from_account(&mut self, account: &Account) -> zkapp_command::Body { + zkapp_command::Body { + public_key: account.public_key.clone(), + token_id: self.gen.token_id.clone(), + update: self.gen_from_account(&account), + balance_change: self.gen_from_account(&account), + increment_nonce: self.gen.rng.gen_bool(0.5), + events: self.gen(), + actions: self.gen(), + call_data: self.gen(), + preconditions: self.gen_from_account(&account), + use_full_commitment: self.gen.rng.gen_bool(0.9), + implicit_account_creation_fee: self.gen.rng.gen_bool(0.1), + may_use_token: self.gen(), + authorization_kind: self.gen_from_account(&account), + } + } +} + +impl Generator for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> zkapp_command::Body { + let account = Account::empty(); + + zkapp_command::Body { + public_key: self.gen(), + token_id: self.gen(), + update: self.gen_from_account(&account), + balance_change: self.gen_from_account(&account), + increment_nonce: self.gen.rng.gen_bool(0.5), + events: self.gen(), + actions: self.gen(), + call_data: self.gen(), + preconditions: self.gen_from_account(&account), + use_full_commitment: self.gen.rng.gen_bool(0.9), + implicit_account_creation_fee: self.gen.rng.gen_bool(0.1), + may_use_token: self.gen(), + authorization_kind: self.gen_from_account(&account), + } + } +} + +pub trait GeneratorFromToken { + fn gen_from_token(&mut self, token_id: TokenId, depth: usize) -> T; +} + +impl GeneratorFromToken for FuzzerCtx { + #[coverage(off)] + fn gen_from_token(&mut self, token_id: TokenId, _depth: usize) -> AccountUpdate { + self.gen.token_id = token_id; + + let public_key = if self.gen.attempt_valid_zkapp || self.gen.rng.gen_bool(0.9) { + self.random_keypair().public.into_compressed() + } else { + self.gen() + }; + + // let public_key = self.random_keypair().public.into_compressed(); + let account = self.get_account(&public_key); + let body: zkapp_command::Body = if account.is_some() && self.gen.rng.gen_bool(0.9) { + self.gen_from_account(account.as_ref().unwrap()) + } else { + self.gen() + }; + + let authorization = match body.authorization_kind { + zkapp_command::AuthorizationKind::NoneGiven => zkapp_command::Control::NoneGiven, + zkapp_command::AuthorizationKind::Signature => { + zkapp_command::Control::Signature(Signature::dummy()) + } + zkapp_command::AuthorizationKind::Proof(_) => zkapp_command::Control::Proof(self.gen()), + }; + + AccountUpdate { + body, + authorization, + } + } +} + +impl GeneratorFromToken> for FuzzerCtx { + #[coverage(off)] + fn gen_from_token( + &mut self, + token_id: TokenId, + depth: usize, + ) -> zkapp_command::CallForest { + let mut forest = zkapp_command::CallForest::::new(); + let max_count = 3usize.saturating_sub(depth); + let count = self.gen.rng.gen_range(0..=max_count); + + for _ in 0..count { + let account_update: AccountUpdate = self.gen_from_token(token_id.clone(), depth); + let token_id = account_update.account_id().derive_token_id(); + let calls = if self.gen.rng.gen_bool(0.8) || depth >= 3 { + None + } else { + // recursion + Some(self.gen_from_token(token_id, depth + 1)) + }; + + forest = forest.cons(calls, account_update); + } + + forest + } +} + +#[coverage(off)] +pub fn sign_account_updates( + ctx: &mut FuzzerCtx, + signer: &mut impl Signer, + txn_commitment: &TransactionCommitment, + full_txn_commitment: &TransactionCommitment, + account_updates: &mut zkapp_command::CallForest, +) { + for acc_update in account_updates.0.iter_mut() { + let account_update = &mut acc_update.elt.account_update; + + let signature = match account_update.authorization { + zkapp_command::Control::Signature(_) => { + let kp = ctx + .find_keypair(&account_update.body.public_key) + .cloned() + .unwrap_or_else(|| ctx.gen()); + let input = match account_update.body.use_full_commitment { + true => full_txn_commitment, + false => txn_commitment, + }; + Some(signer.sign(&kp, &input)) + } + _ => None, + }; + + if let Some(signature) = signature { + account_update.authorization = zkapp_command::Control::Signature(signature); + } + + sign_account_updates( + ctx, + signer, + txn_commitment, + full_txn_commitment, + &mut acc_update.elt.calls, + ); + } +} + +impl Generator for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> ZkAppCommand { + self.gen.attempt_valid_zkapp = self.gen.rng.gen_bool(0.9); + + let mut zkapp_command = ZkAppCommand { + fee_payer: self.gen(), + account_updates: self.gen_from_token(TokenId::default(), 0), + memo: self.gen(), + }; + let (txn_commitment, full_txn_commitment) = get_transaction_commitments(&zkapp_command); + let mut signer = mina_signer::create_kimchi(NetworkId::TESTNET); + + let keypair = match self.find_keypair(&zkapp_command.fee_payer.body.public_key) { + Some(keypair) => keypair.clone(), + None => self.gen(), + }; + zkapp_command.fee_payer.authorization = signer.sign(&keypair, &full_txn_commitment); + + sign_account_updates( + self, + &mut signer, + &txn_commitment, + &full_txn_commitment, + &mut zkapp_command.account_updates, + ); + zkapp_command + } +} + +impl GeneratorFromAccount for FuzzerCtx { + #[coverage(off)] + fn gen_from_account(&mut self, account: &Account) -> PaymentPayload { + let is_source_account = self.gen.rng.gen_bool(0.9); + + let source_pk = if is_source_account { + account.public_key.clone() + } else { + self.gen() + }; + + let receiver_pk = if is_source_account && self.gen.rng.gen_bool(0.9) { + // same source & receiver + source_pk.clone() + } else { + self.gen() + }; + + PaymentPayload { + receiver_pk, + amount: self.gen_from_account(account), + } + } +} + +impl Generator for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> StakeDelegationPayload { + StakeDelegationPayload::SetDelegate { + new_delegate: self.gen(), + } + } +} + +#[coverage(off)] +fn sign_payload(keypair: &Keypair, payload: &SignedCommandPayload) -> Signature { + let tx = TransactionUnionPayload::of_user_command_payload(payload); + let mut signer = mina_signer::create_legacy(NetworkId::TESTNET); + signer.sign(keypair, &tx) +} + +impl Generator for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> SignedCommandPayload { + let fee_payer_pk = if self.gen.rng.gen_bool(0.8) { + self.random_keypair().public.into_compressed() + } else { + self.gen() + }; + + let account = self.get_account(&fee_payer_pk); + + let body = if account.is_some() && self.gen.rng.gen_bool(0.8) { + signed_command::Body::Payment(self.gen_from_account(account.as_ref().unwrap())) + } else { + signed_command::Body::StakeDelegation(self.gen()) + }; + + let fee = match account.as_ref() { + Some(account) => self.gen_from_account(account), + None => GeneratorRange64::gen_range(self, 0u64..=10_000_000u64), + }; + + let nonce = match account.as_ref() { + Some(account) => self.gen_from_account(account), + None => GeneratorRange32::gen_range(self, 0u32..=10_000_000u32), + }; + + SignedCommandPayload::create( + fee, + fee_payer_pk, + nonce, + self.gen_wrap( + #[coverage(off)] + |x| x.gen(), + ), + self.gen(), + body, + ) + } +} + +impl Generator for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> SignedCommand { + let payload: SignedCommandPayload = self.gen(); + let keypair = if self.gen.rng.gen_bool(0.9) { + self.find_keypair(&payload.common.fee_payer_pk) + .cloned() + .unwrap_or_else(|| self.gen()) + } else { + self.gen() + }; + + let signature = sign_payload(&keypair, &payload); + + SignedCommand { + payload, + signer: keypair.public.into_compressed(), + signature, + } + } +} + +impl Generator for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> UserCommand { + match vec![0, 1].choose(&mut self.gen.rng).unwrap() { + 0 => UserCommand::SignedCommand(Box::new(self.gen())), + 1 => UserCommand::ZkAppCommand(Box::new(self.gen())), + _ => unimplemented!(), + } + } +} + +impl Generator for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> Transaction { + Transaction::Command(self.gen()) + } +} + +impl Generator> for FuzzerCtx { + fn gen(&mut self) -> Number { + Number(self.gen.rng.gen()) + } +} + +impl Generator> for FuzzerCtx { + fn gen(&mut self) -> Number { + Number(self.gen.rng.gen()) + } +} + +impl Generator> for FuzzerCtx { + fn gen(&mut self) -> Number { + Number(self.gen.rng.gen()) + } +} + +impl Generator> for FuzzerCtx { + fn gen(&mut self) -> Number { + Number(self.gen.rng.gen()) + } +} + +impl_default_generator_for_wrapper_type!(FuzzerCtx, MinaBaseLedgerHash0StableV1); +impl_default_generator_for_wrapper_type!(FuzzerCtx, LedgerHash, MinaBaseLedgerHash0StableV1); + +impl_default_generator_for_wrapper_type!(FuzzerCtx, MinaBaseAccountIdDigestStableV1); +impl_default_generator_for_wrapper_type!( + FuzzerCtx, + TokenIdKeyHash, + MinaBaseAccountIdDigestStableV1 +); + +impl_default_generator_for_wrapper_type!(FuzzerCtx, MinaBasePendingCoinbaseStackHashStableV1); +impl_default_generator_for_wrapper_type!(FuzzerCtx, MinaBasePendingCoinbaseCoinbaseStackStableV1); +impl_default_generator_for_wrapper_type!(FuzzerCtx, MinaBaseStackFrameStableV1); +impl_default_generator_for_wrapper_type!(FuzzerCtx, MinaBaseCallStackDigestStableV1); + +impl_default_generator_for_wrapper_type!( + FuzzerCtx, + UnsignedExtendedUInt64Int64ForVersionTagsStableV1 +); +impl_default_generator_for_wrapper_type!(FuzzerCtx, CurrencyAmountStableV1); +impl_default_generator_for_wrapper_type!(FuzzerCtx, CurrencyFeeStableV1); + +impl_default_generator_for_wrapper_type!(FuzzerCtx, UnsignedExtendedUInt32StableV1); + +impl Generator for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> MinaBasePendingCoinbaseStateStackStableV1 { + let init: MinaBasePendingCoinbaseStackHashStableV1 = self.gen(); + let curr: MinaBasePendingCoinbaseStackHashStableV1 = self.gen(); + + MinaBasePendingCoinbaseStateStackStableV1 { + init: init.into(), + curr: curr.into(), + } + } +} + +impl Generator for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> MinaBasePendingCoinbaseStackVersionedStableV1 { + let data: MinaBasePendingCoinbaseCoinbaseStackStableV1 = self.gen(); + + MinaBasePendingCoinbaseStackVersionedStableV1 { + data: data.into(), + state: self.gen(), + } + } +} + +impl Generator for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> SgnStableV1 { + match self.gen.rng.gen_bool(0.5) { + true => SgnStableV1::Pos, + false => SgnStableV1::Neg, + } + } +} + +impl Generator for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> SignedAmount { + SignedAmount { + magnitude: self.gen(), + sgn: self.gen(), + } + } +} + +impl Generator for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> MinaBaseFeeExcessStableV1 { + MinaBaseFeeExcessStableV1( + TokenFeeExcess { + token: self.gen(), + amount: self.gen(), + }, + TokenFeeExcess { + token: self.gen(), + amount: self.gen(), + }, + ) + } +} + +impl Generator for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> MinaTransactionLogicZkappCommandLogicLocalStateValueStableV1 { + MinaTransactionLogicZkappCommandLogicLocalStateValueStableV1 { + stack_frame: self.gen(), + call_stack: self.gen(), + transaction_commitment: self.gen(), + full_transaction_commitment: self.gen(), + excess: self.gen(), + supply_increase: self.gen(), + ledger: self.gen(), + success: self.gen(), + account_update_index: self.gen(), + failure_status_tbl: MinaBaseTransactionStatusFailureCollectionStableV1(List::new()), + will_succeed: self.gen(), + } + } +} + +impl Generator for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> MinaStateBlockchainStateValueStableV2LedgerProofStatementSource { + MinaStateBlockchainStateValueStableV2LedgerProofStatementSource { + first_pass_ledger: self.gen(), + second_pass_ledger: self.gen(), + pending_coinbase_stack: self.gen(), + local_state: self.gen(), + } + } +} diff --git a/tools/fuzzing/src/invariants.rs b/tools/fuzzing/src/invariants.rs new file mode 100644 index 000000000..8e9bc10cd --- /dev/null +++ b/tools/fuzzing/src/invariants.rs @@ -0,0 +1,353 @@ +use ledger::{ + scan_state::{ + currency::TxnVersion, + transaction_logic::zkapp_command::{AccountUpdate, Control}, + }, + Account, AccountId, AuthRequired, ControlTag, Permissions, SetVerificationKey, TokenId, + ZkAppAccount, TXN_VERSION_CURRENT, +}; +use once_cell::sync::Lazy; +use std::sync::{Mutex, RwLock}; +use text_diff::{diff, Difference}; + +pub struct Checks { + pub caller_id: TokenId, + pub account_id: AccountId, + pub account_before: Account, + pub account_after: Option, + pub check_account: Permissions, + pub acc_update: AccountUpdate, // added for extra diagnostics +} + +pub static BREAK: Lazy> = Lazy::new( + #[coverage(off)] + || RwLock::new(false), +); +pub static STATE: Lazy>> = Lazy::new( + #[coverage(off)] + || RwLock::new(Vec::new()), +); +pub static LAST_RESULT: LastResult = LastResult::new(); + +pub struct LastResult { + result: Mutex>>, +} + +impl LastResult { + pub const fn new() -> Self { + Self { + result: Mutex::new(None), + } + } + + fn set(&self, result: Result<(), String>) { + *self.result.lock().unwrap() = Some(result); + } + + pub fn get(&self) -> Option> { + self.result.lock().unwrap().clone() + } + + pub fn take(&self) -> Option> { + self.result.lock().unwrap().take() + } +} + +impl Checks { + // We are duplicating the implementation here because we don't want changes in the logic to affect invariant checks + #[coverage(off)] + fn check_permission(auth: AuthRequired, tag: ControlTag) -> bool { + use AuthRequired as Auth; + use ControlTag as Tag; + + match (auth, tag) { + (Auth::Impossible, _) => false, + (Auth::None, _) => true, + (Auth::Proof, Tag::Proof) => true, + (Auth::Signature, Tag::Signature) => true, + (Auth::Either, Tag::Proof | Tag::Signature) => true, + (Auth::Signature, Tag::Proof) => false, + (Auth::Proof, Tag::Signature) => false, + (Auth::Proof | Auth::Signature | Auth::Either, Tag::NoneGiven) => false, + (Auth::Both, _) => unimplemented!(), + } + } + + #[coverage(off)] + fn setvk_auth(auth: AuthRequired, txn_version: TxnVersion) -> AuthRequired { + if txn_version <= TXN_VERSION_CURRENT { + if txn_version == TXN_VERSION_CURRENT { + auth + } else { + AuthRequired::Signature + } + } else { + panic!("invalid txn_version: {}", txn_version.as_u32()); + } + } + + #[coverage(off)] + pub fn new(caller_id: &TokenId, account_update: &AccountUpdate, account: &Account) -> Self { + let caller_id = caller_id.clone(); + let account_id = account_update.account_id(); + let account_before = account.clone(); + let tag = match account_update.authorization { + Control::Signature(_) => ControlTag::Signature, + Control::Proof(_) => ControlTag::Proof, + Control::NoneGiven => ControlTag::NoneGiven, + }; + let Permissions:: { + edit_state, + access, + send, + receive, + set_delegate, + set_permissions, + set_verification_key: SetVerificationKey { auth, txn_version }, + set_zkapp_uri, + edit_action_state, + set_token_symbol, + increment_nonce, + set_voting_for, + set_timing, + } = account_before.permissions; + let check_account = Permissions:: { + edit_state: !Self::check_permission(edit_state, tag), + access: !Self::check_permission(access, tag), + send: !Self::check_permission(send, tag), + receive: !Self::check_permission(receive, tag), + set_delegate: !Self::check_permission(set_delegate, tag), + set_permissions: !Self::check_permission(set_permissions, tag), + set_verification_key: SetVerificationKey { + auth: !Self::check_permission(Self::setvk_auth(auth, txn_version), tag), + txn_version, + }, + set_zkapp_uri: !Self::check_permission(set_zkapp_uri, tag), + edit_action_state: !Self::check_permission(edit_action_state, tag), + set_token_symbol: !Self::check_permission(set_token_symbol, tag), + increment_nonce: !Self::check_permission(increment_nonce, tag), + set_voting_for: !Self::check_permission(set_voting_for, tag), + set_timing: !Self::check_permission(set_timing, tag), + }; + + Self { + caller_id, + account_id, + account_before, + account_after: None, + check_account, + acc_update: account_update.clone(), + } + } + + #[coverage(off)] + fn check_authorization(caller_id: &TokenId) -> Result<(), String> { + for check in STATE.read().unwrap().iter().rev() { + // find parent's check + if caller_id == &check.account_id.derive_token_id() { + if check.check_account.access { + return Err("Invariant violation: caller access permission".to_string()); + } + + return Ok(()); + } + } + + panic!() + } + + #[coverage(off)] + pub fn add_after_account(&mut self, account: Account) { + self.account_after = Some(account); + } + + #[coverage(off)] + fn diagnostic(&self, err: &str) -> String { + let orig = format!("{:#?}", self.account_before); + let edit = format!("{:#?}", self.account_after.as_ref().unwrap()); + let split = " "; + let (_, changeset) = diff(orig.as_str(), edit.as_str(), split); + + let mut ret = String::new(); + + for seq in changeset { + match seq { + Difference::Same(ref x) => { + ret.push_str(x); + ret.push_str(split); + } + Difference::Add(ref x) => { + ret.push_str("\x1B[92m"); + ret.push_str(x); + ret.push_str("\x1B[0m"); + ret.push_str(split); + } + Difference::Rem(ref x) => { + ret.push_str("\x1B[91m"); + ret.push_str(x); + ret.push_str("\x1B[0m"); + ret.push_str(split); + } + } + } + + format!("{}\n{}\nUpdate:\n{:#?}\n", err, ret, self.acc_update) + } + + #[coverage(off)] + pub fn check_exit(&self) -> Result<(), String> { + let res = self._check_exit(); + LAST_RESULT.set(res.clone()); + res + } + + #[coverage(off)] + fn _check_exit(&self) -> Result<(), String> { + let Permissions:: { + edit_state, + access, + send, + receive, + set_delegate, + set_permissions, + set_verification_key: + SetVerificationKey { + auth: set_vk_auth, .. + }, + set_zkapp_uri, + edit_action_state, + set_token_symbol, + increment_nonce, + set_voting_for, + set_timing, + } = self.check_account; + + // Token owner approval of children account updates + if self.caller_id != TokenId::default() { + Self::check_authorization(&self.caller_id)?; + } + + let account = self.account_after.as_ref().unwrap(); + + if access && self.account_before != *account { + return Err(self.diagnostic("Invariant violation: access permission")); + } + + if send && self.account_before.balance > account.balance { + return Err(self.diagnostic("Invariant violation: send permission")); + } + + if receive && self.account_before.balance < account.balance { + return Err(self.diagnostic("Invariant violation: receive permission")); + } + + let is_change_from_none_to_default = self.account_before.delegate.is_none() + && account.delegate.as_ref() == Some(&account.public_key); + if set_delegate + && self.account_before.delegate != account.delegate + && !is_change_from_none_to_default + { + return Err(self.diagnostic("Invariant violation: set_delegate permission")); + } + + if set_permissions && self.account_before.permissions != account.permissions { + return Err(self.diagnostic("Invariant violation: set_permissions permission")); + } + + if set_token_symbol && self.account_before.token_symbol != account.token_symbol { + return Err(self.diagnostic("Invariant violation: set_token_symbol permission")); + } + + if increment_nonce && self.account_before.nonce != account.nonce { + return Err(self.diagnostic("Invariant violation: increment_nonce permission")); + } + + if set_voting_for && self.account_before.voting_for != account.voting_for { + return Err(self.diagnostic("Invariant violation: set_voting_for permission")); + } + + if set_timing && self.account_before.timing != account.timing { + return Err(self.diagnostic("Invariant violation: set_timing permission")); + } + + let default_values = ZkAppAccount::default(); + + if edit_state { + let invariant_violation = match &self.account_before.zkapp { + Some(zkapp) => account.zkapp.as_ref().map_or( + true, + #[coverage(off)] + |x| x.app_state != zkapp.app_state, + ), + None => account.zkapp.as_ref().map_or( + false, + #[coverage(off)] + |x| x.app_state != default_values.app_state, + ), + }; + + if invariant_violation { + return Err(self.diagnostic("Invariant violation: edit_state permission")); + } + } + + if set_vk_auth { + let invariant_violation = match &self.account_before.zkapp { + Some(zkapp) => account.zkapp.as_ref().map_or( + true, + #[coverage(off)] + |x| x.verification_key != zkapp.verification_key, + ), + None => account.zkapp.as_ref().map_or( + false, + #[coverage(off)] + |x| x.verification_key != default_values.verification_key, + ), + }; + + if invariant_violation { + return Err(self.diagnostic("Invariant violation: set_verification_key permission")); + } + } + + if set_zkapp_uri { + let invariant_violation = match &self.account_before.zkapp { + Some(zkapp) => account.zkapp.as_ref().map_or( + true, + #[coverage(off)] + |x| x.zkapp_uri != zkapp.zkapp_uri, + ), + None => account.zkapp.as_ref().map_or( + false, + #[coverage(off)] + |x| x.zkapp_uri != default_values.zkapp_uri, + ), + }; + + if invariant_violation { + return Err(self.diagnostic("Invariant violation: set_zkapp_uri permission")); + } + } + + if edit_action_state { + let invariant_violation = match &self.account_before.zkapp { + Some(zkapp) => account.zkapp.as_ref().map_or( + true, + #[coverage(off)] + |x| x.action_state != zkapp.action_state, + ), + None => account.zkapp.as_ref().map_or( + false, + #[coverage(off)] + |x| x.action_state != default_values.action_state, + ), + }; + + if invariant_violation { + return Err(self.diagnostic("Invariant violation: edit_action_state permission")); + } + } + + Ok(()) + } +} diff --git a/tools/fuzzing/src/main.rs b/tools/fuzzing/src/main.rs new file mode 100644 index 000000000..93c5f5014 --- /dev/null +++ b/tools/fuzzing/src/main.rs @@ -0,0 +1,309 @@ +#![cfg_attr(feature = "nightly", feature(coverage_attribute))] +#![cfg_attr(feature = "nightly", feature(stmt_expr_attributes))] + +#[cfg(feature = "nightly")] +pub mod transaction_fuzzer { + pub mod context; + pub mod coverage; + pub mod generator; + pub mod invariants; + pub mod mutator; + + use binprot::{ + macros::{BinProtRead, BinProtWrite}, + BinProtRead, BinProtSize, BinProtWrite, + }; + use context::{ApplyTxResult, FuzzerCtx, FuzzerCtxBuilder}; + use coverage::{ + cov::{Cov, FileCounters}, + reports::CoverageReport, + stats::Stats, + }; + use ledger::{ + scan_state::transaction_logic::{transaction_applied, UserCommand}, + Account, + }; + use mina_hasher::Fp; + use mina_p2p_messages::bigint::BigInt; + use openmina_core::constants::ConstraintConstantsUnversioned; + use std::io::{Read, Write}; + use std::{ + env, + process::{ChildStdin, ChildStdout}, + }; + + #[coverage(off)] + pub fn deserialize(r: &mut R) -> T { + let mut prefix_buf = [0u8; 4]; + r.read_exact(&mut prefix_buf).unwrap(); + // The OCaml process sends a len header for the binprot data, it seems we don't really need it but we must read it. + let _prefix_len = u32::from_be_bytes(prefix_buf); + T::binprot_read(r).unwrap() + } + + #[coverage(off)] + pub fn serialize(obj: &T, w: &mut W) { + let size = obj.binprot_size() as u32; + let prefix_buf: [u8; 4] = size.to_be_bytes(); + // The OCaml process expects a len header before the binprot data. + w.write_all(prefix_buf.as_slice()).unwrap(); + obj.binprot_write(w).unwrap(); + w.flush().unwrap(); + } + + pub struct CoverageStats { + cov: Cov, + file_counters: Vec, + pub rust: Option, + } + + impl CoverageStats { + #[coverage(off)] + pub fn new() -> Self { + let mut cov = Cov::new(); + let file_counters = cov.get_file_counters(); + Self { + cov, + file_counters, + rust: None, + } + } + + #[coverage(off)] + pub fn update_rust(&mut self) -> bool { + let rust_cov_stats = Stats::from_file_counters(&self.file_counters); + let coverage_increased = self.rust.is_none() + || rust_cov_stats.has_coverage_increased(&self.rust.as_ref().unwrap()); + + if coverage_increased { + let llvm_dump = self.cov.dump(); + let report_rust = CoverageReport::from_llvm_dump(&llvm_dump); + //println!("{}", report_rust); + println!("Saving coverage report (Rust)"); + report_rust.write_files("rust".to_string()); + } + + self.rust = Some(rust_cov_stats); + coverage_increased + } + + #[coverage(off)] + pub fn print(&self) { + if let Some(stats) = &self.rust { + println!( + "=== COV Rust ===\n{}", + stats + .filter_path(".cargo/") // unwanted files + .filter_path(".rustup/") + .filter_path("mina-p2p-messages/") + .filter_path("core/") + .filter_path("proofs/") + ); + } + } + } + + #[derive(BinProtWrite, Debug)] + enum Action { + SetConstraintConstants(ConstraintConstantsUnversioned), + SetInitialAccounts(Vec), + ApplyTx(UserCommand), + #[allow(dead_code)] + Exit, + } + + #[derive(BinProtRead, Debug)] + enum ActionOutput { + ConstraintConstantsSet, + InitialAccountsSet(BigInt), + TxApplied(ApplyTxResult), + ExitAck, + } + + #[coverage(off)] + fn ocaml_set_initial_accounts( + ctx: &mut FuzzerCtx, + stdin: &mut ChildStdin, + stdout: &mut ChildStdout, + ) -> Fp { + let action = Action::SetInitialAccounts(ctx.get_ledger_accounts()); + serialize(&action, stdin); + let output: ActionOutput = deserialize(stdout); + let ocaml_ledger_root_hash = match output { + ActionOutput::InitialAccountsSet(root_hash) => root_hash, + _ => panic!("Expected InitialAccountsSet"), + }; + let rust_ledger_root_hash = ctx.get_ledger_root(); + assert!(ocaml_ledger_root_hash == rust_ledger_root_hash.into()); + rust_ledger_root_hash + } + + #[coverage(off)] + fn ocaml_apply_transaction( + stdin: &mut ChildStdin, + stdout: &mut ChildStdout, + user_command: UserCommand, + ) -> ApplyTxResult { + let action = Action::ApplyTx(user_command); + serialize(&action, stdin); + let output: ActionOutput = deserialize(stdout); + match output { + ActionOutput::TxApplied(result) => { + for applied_tx in result.apply_result.iter() { + match &applied_tx.varying { + transaction_applied::Varying::Command(command_applied) => { + match command_applied { + transaction_applied::CommandApplied::SignedCommand( + _signed_command_applied, + ) => {} + transaction_applied::CommandApplied::ZkappCommand( + zkapp_command_applied, + ) => zkapp_command_applied + .command + .data + .account_updates + .accumulate_hashes(), // Needed because of delayed hashing + } + } + transaction_applied::Varying::FeeTransfer(_fee_transfer_applied) => {} + transaction_applied::Varying::Coinbase(_coinbase_applied) => {} + } + } + result + } + _ => panic!("Expected TxApplied"), + } + } + + #[coverage(off)] + fn ocaml_set_constraint_constants( + ctx: &mut FuzzerCtx, + stdin: &mut ChildStdin, + stdout: &mut ChildStdout, + ) { + let action = Action::SetConstraintConstants((&ctx.constraint_constants).into()); + serialize(&action, stdin); + let output: ActionOutput = deserialize(stdout); + match output { + ActionOutput::ConstraintConstantsSet => (), + _ => panic!("Expected ConstraintConstantsSet"), + } + } + + #[coverage(off)] + pub fn fuzz( + stdin: &mut ChildStdin, + stdout: &mut ChildStdout, + break_on_invariant: bool, + seed: u64, + minimum_fee: u64, + ) { + *invariants::BREAK.write().unwrap() = break_on_invariant; + let mut cov_stats = CoverageStats::new(); + let mut ctx = FuzzerCtxBuilder::new() + .seed(seed as u64) + .minimum_fee(minimum_fee as u64) + .initial_accounts(10) + .fuzzcases_path(env::var("FUZZCASES_PATH").unwrap_or("/tmp/".to_string())) + .build(); + + ocaml_set_constraint_constants(&mut ctx, stdin, stdout); + ocaml_set_initial_accounts(&mut ctx, stdin, stdout); + + let mut fuzzer_made_progress = false; + + for iteration in 0.. { + print!("Iteration {}\r", iteration); + std::io::stdout().flush().unwrap(); + + if (iteration % 5000) == 0 { + if fuzzer_made_progress { + fuzzer_made_progress = false; + ctx.take_snapshot(); + } else { + ctx.restore_snapshot(); + // Restore ledger in OCaml + ocaml_set_initial_accounts(&mut ctx, stdin, stdout); + } + } + + // Update coverage statistics every 100 iterations + if (iteration % 100) == 0 { + let update_rust_increased_coverage = cov_stats.update_rust(); + + if update_rust_increased_coverage { + fuzzer_made_progress = true; + cov_stats.print(); + } + } + + let user_command: UserCommand = ctx.random_user_command(); + let ocaml_apply_result = ocaml_apply_transaction(stdin, stdout, user_command.clone()); + + // Apply transaction on the Rust side + if ctx + .apply_transaction(&user_command, &ocaml_apply_result) + .is_err() + { + // Exiting due to inconsistent state + std::process::exit(0); + } + } + } + + #[coverage(off)] + pub fn reproduce(stdin: &mut ChildStdin, stdout: &mut ChildStdout, fuzzcase: &String) { + let mut ctx = FuzzerCtxBuilder::new().build(); + let user_command = ctx.load_fuzzcase(fuzzcase); + + ocaml_set_constraint_constants(&mut ctx, stdin, stdout); + ocaml_set_initial_accounts(&mut ctx, stdin, stdout); + + let ocaml_apply_result = ocaml_apply_transaction(stdin, stdout, user_command.clone()); + let rust_apply_result = ctx.apply_transaction(&user_command, &ocaml_apply_result); + + println!("apply_transaction: {:?}", rust_apply_result); + } +} + +fn main() { + #[cfg(feature = "nightly")] + { + use std::process::{Command, Stdio}; + + let matches = clap::Command::new("Transaction Fuzzer") + .arg( + clap::Arg::new("fuzzcase") + .short('f') + .long("fuzzcase") + .value_name("FILE"), + ) + .get_matches(); + + let mut child = Command::new( + &std::env::var("OCAML_TRANSACTION_FUZZER_PATH").unwrap_or_else(|_| { + format!( + "{}/mina/_build/default/src/app/transaction_fuzzer/transaction_fuzzer.exe", + std::env::var("HOME").unwrap() + ) + }), + ) + .arg("execute") + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::inherit()) + .spawn() + .expect("Failed to start OCaml process"); + + let stdin = child.stdin.as_mut().expect("Failed to open stdin"); + let stdout = child.stdout.as_mut().expect("Failed to open stdout"); + + if let Some(fuzzcase) = matches.get_one::("fuzzcase") { + println!("Reproducing fuzzcase from file: {}", fuzzcase); + transaction_fuzzer::reproduce(stdin, stdout, fuzzcase); + } else { + println!("Running the fuzzer..."); + transaction_fuzzer::fuzz(stdin, stdout, true, 42, 1000); + } + } +} diff --git a/tools/fuzzing/src/mutator.rs b/tools/fuzzing/src/mutator.rs new file mode 100644 index 000000000..29e1a6b51 --- /dev/null +++ b/tools/fuzzing/src/mutator.rs @@ -0,0 +1,1008 @@ +use super::{ + context::{FuzzerCtx, PermissionModel}, + generator::{ + sign_account_updates, Generator, GeneratorFromAccount, GeneratorRange32, GeneratorRange64, + GeneratorWrapper, + }, +}; +use crate::generator::gen_curve_point; +use ark_ff::Zero; +use ledger::{ + generators::zkapp_command_builder::get_transaction_commitments, + hash_with_kimchi, + scan_state::{ + currency::{Amount, Balance, Fee, MinMax, Nonce, Signed, Slot}, + transaction_logic::{ + zkapp_command::{ + self, AccountPreconditions, AccountUpdate, Body, ClosedInterval, FeePayer, + FeePayerBody, OrIgnore, Preconditions, SetOrKeep, Update, ZkAppCommand, + ZkAppPreconditions, + }, + Transaction, UserCommand, + }, + }, + Account, AuthRequired, Permissions, Timing, TokenId, TokenSymbol, VerificationKey, +}; +use mina_hasher::Fp; +use mina_p2p_messages::{ + array::ArrayN16, + bigint::BigInt, + pseq::PaddedSeq, + v2::{ + PicklesProofProofsVerified2ReprStableV2, PicklesProofProofsVerified2ReprStableV2PrevEvals, + PicklesProofProofsVerified2ReprStableV2PrevEvalsEvals, + PicklesProofProofsVerified2ReprStableV2PrevEvalsEvalsEvals, + PicklesWrapWireProofCommitmentsStableV1, PicklesWrapWireProofEvaluationsStableV1, + PicklesWrapWireProofStableV1, PicklesWrapWireProofStableV1Bulletproof, + TransactionSnarkProofStableV2, TransactionSnarkScanStateLedgerProofWithSokMessageStableV2, + TransactionSnarkStableV2, + }, +}; +use mina_signer::{CompressedPubKey, NetworkId, Signature, Signer}; +use rand::{ + distributions::{Alphanumeric, DistString}, + seq::SliceRandom, + Rng, +}; + +#[coverage(off)] +fn rand_elements(ctx: &mut FuzzerCtx, count: usize) -> Vec { + let elements: Vec = (0..count).collect(); + // We give more weight to smaller amount of elements since in general we want to perform fewer mutations + if let Ok(amount) = elements.choose_weighted( + &mut ctx.gen.rng, + #[coverage(off)] + |x| elements.len() - x, + ) { + elements + .choose_multiple(&mut ctx.gen.rng, *amount) + .cloned() + .collect() + } else { + Vec::new() + } +} + +pub trait Mutator { + fn mutate(&mut self, t: &mut T); +} + +impl Mutator for FuzzerCtx { + #[coverage(off)] + fn mutate(&mut self, t: &mut BigInt) { + *t = self.gen(); + } +} + +impl Mutator<(BigInt, BigInt)> for FuzzerCtx { + #[coverage(off)] + fn mutate(&mut self, t: &mut (BigInt, BigInt)) { + *t = gen_curve_point::(self); + } +} + +impl Mutator<((BigInt, BigInt), (BigInt, BigInt))> for FuzzerCtx { + #[coverage(off)] + fn mutate(&mut self, t: &mut ((BigInt, BigInt), (BigInt, BigInt))) { + if self.gen.rng.gen_bool(0.5) { + self.mutate(&mut t.0 .0); + } + + if self.gen.rng.gen_bool(0.5) { + self.mutate(&mut t.0 .1); + } + + if self.gen.rng.gen_bool(0.5) { + self.mutate(&mut t.1 .0); + } + + if self.gen.rng.gen_bool(0.5) { + self.mutate(&mut t.1 .1); + } + } +} + +impl Mutator<(Vec, Vec)> for FuzzerCtx { + #[coverage(off)] + fn mutate(&mut self, t: &mut (Vec, Vec)) { + if self.gen.rng.gen_bool(0.5) { + self.mutate(&mut t.0); + } + + if self.gen.rng.gen_bool(0.5) { + self.mutate(&mut t.1); + } + } +} + +impl Mutator<(ArrayN16, ArrayN16)> for FuzzerCtx +where + FuzzerCtx: Mutator>, +{ + #[coverage(off)] + fn mutate(&mut self, t: &mut (ArrayN16, ArrayN16)) { + if self.gen.rng.gen_bool(0.5) { + self.mutate(&mut t.0); + } + + if self.gen.rng.gen_bool(0.5) { + self.mutate(&mut t.1); + } + } +} + +impl Mutator> for FuzzerCtx +where + FuzzerCtx: Mutator, +{ + #[coverage(off)] + fn mutate(&mut self, t: &mut Vec) { + if t.is_empty() { + // TODO(binier): maybe gen + // t.lr = self.gen(); + return; + } + for i in rand_elements(self, t.len()) { + self.mutate(&mut t[i]); + } + } +} + +impl Mutator> for FuzzerCtx +where + FuzzerCtx: Mutator, +{ + #[coverage(off)] + fn mutate(&mut self, t: &mut PaddedSeq) { + for i in rand_elements(self, N) { + self.mutate(&mut t.0[i]) + } + } +} + +impl Mutator for FuzzerCtx { + #[coverage(off)] + fn mutate(&mut self, t: &mut FeePayerBody) { + //let account = self.get_account(&t.public_key).unwrap(); + + for option in rand_elements(self, 2) { + match option { + 0 => t.fee = GeneratorRange64::::gen_range(self, 0..=Fee::max().as_u64()), + 1 => { + t.valid_until = if self.gen.rng.gen_bool(0.5) { + Some(Slot::from_u32( + self.gen.rng.gen_range(0..=Slot::max().as_u32()), + )) + } else { + None + } + } + //2 => { + // t.nonce = GeneratorRange32::::gen_range(self, 0..=Nonce::max().as_u32()) + //} + _ => unimplemented!(), + } + } + } +} + +impl Mutator for FuzzerCtx { + #[coverage(off)] + fn mutate(&mut self, t: &mut FeePayer) { + self.mutate(&mut t.body) + } +} + +impl Mutator for FuzzerCtx { + #[coverage(off)] + fn mutate(&mut self, t: &mut Fp) { + *t = self.gen(); + } +} + +impl Mutator> for FuzzerCtx +where + FuzzerCtx: Mutator + Generator, +{ + #[coverage(off)] + fn mutate(&mut self, t: &mut SetOrKeep) { + match t { + SetOrKeep::Set(inner) => { + if self.gen.rng.gen_bool(0.5) { + self.mutate(inner) + } else { + *t = SetOrKeep::Keep; + } + } + SetOrKeep::Keep => *t = SetOrKeep::Set(self.gen()), + } + } +} + +impl Mutator<[T; N]> for FuzzerCtx +where + FuzzerCtx: Mutator, +{ + #[coverage(off)] + fn mutate(&mut self, t: &mut [T; N]) { + for i in rand_elements(self, t.len()) { + self.mutate(&mut t[i]) + } + } +} + +pub trait MutatorFromAccount { + fn mutate_from_account(&mut self, t: &mut T, account: &Account); +} + +impl MutatorFromAccount> for FuzzerCtx { + #[coverage(off)] + fn mutate_from_account(&mut self, t: &mut Permissions, account: &Account) { + let permission_model = self.find_permissions(&account.public_key).unwrap(); + + match permission_model { + PermissionModel::Any => { + for option in rand_elements(self, 11) { + match option { + 0 => t.edit_state = self.gen(), + 1 => t.send = self.gen(), + 2 => t.receive = self.gen(), + 3 => t.set_delegate = self.gen(), + 4 => t.set_permissions = self.gen(), + 5 => t.set_verification_key = self.gen(), + 6 => t.set_zkapp_uri = self.gen(), + 7 => t.edit_action_state = self.gen(), + 8 => t.set_token_symbol = self.gen(), + 9 => t.increment_nonce = self.gen(), + 10 => t.set_voting_for = self.gen(), + _ => unimplemented!(), + } + } + } + // Don't mutate permissions in the rest of the models + PermissionModel::Empty => (), + PermissionModel::Initial => (), + PermissionModel::Default => (), + PermissionModel::TokenOwner => (), + } + } +} + +impl MutatorFromAccount> for FuzzerCtx +where + FuzzerCtx: MutatorFromAccount + GeneratorFromAccount, +{ + #[coverage(off)] + fn mutate_from_account(&mut self, t: &mut SetOrKeep, account: &Account) { + match t { + SetOrKeep::Set(inner) => { + if self.gen.rng.gen_bool(0.5) { + self.mutate_from_account(inner, account) + } else { + *t = SetOrKeep::Keep; + } + } + SetOrKeep::Keep => *t = SetOrKeep::Set(self.gen_from_account(account)), + } + } +} + +impl MutatorFromAccount for FuzzerCtx { + #[coverage(off)] + fn mutate_from_account(&mut self, t: &mut zkapp_command::Timing, _account: &Account) { + for option in rand_elements(self, 5) { + match option { + 0 => t.initial_minimum_balance = self.gen(), + 1 => t.cliff_time = self.gen(), + 2 => t.cliff_amount = self.gen(), + 3 => t.vesting_period = self.gen(), + 4 => t.vesting_increment = self.gen(), + _ => unimplemented!(), + } + } + } +} + +impl MutatorFromAccount for FuzzerCtx { + #[coverage(off)] + fn mutate_from_account(&mut self, t: &mut Timing, _account: &Account) { + if let Timing::Timed { + initial_minimum_balance, + cliff_time, + cliff_amount, + vesting_period, + vesting_increment, + } = t + { + for option in rand_elements(self, 5) { + match option { + 0 => *initial_minimum_balance = self.gen(), + 1 => *cliff_time = self.gen(), + 2 => *cliff_amount = self.gen(), + 3 => *vesting_period = self.gen(), + 4 => *vesting_increment = self.gen(), + _ => unimplemented!(), + } + } + } else { + if self.gen.rng.gen_bool(0.5) { + *t = Timing::Timed { + initial_minimum_balance: self.gen(), + cliff_time: self.gen(), + cliff_amount: self.gen(), + vesting_period: self.gen(), + vesting_increment: self.gen(), + } + } + } + } +} + +impl MutatorFromAccount for FuzzerCtx { + #[coverage(off)] + fn mutate_from_account(&mut self, t: &mut Update, account: &Account) { + for option in rand_elements(self, 8) { + match option { + 0 => self.mutate(&mut t.app_state), + 1 => { + let keypair = if self.gen.rng.gen_bool(0.5) { + self.random_keypair() + } else { + self.gen() + }; + + t.delegate = SetOrKeep::Set(keypair.public.into_compressed()) + } + 2 => { + let data: VerificationKey = self.gen(); + let hash = if self.gen.rng.gen_bool(0.5) { + data.digest() + } else { + self.gen() + }; + + t.verification_key = SetOrKeep::Set(zkapp_command::WithHash { data, hash }); + } + 3 => self.mutate_from_account(&mut t.permissions, account), + 4 => { + t.zkapp_uri = self.gen_wrap( + #[coverage(off)] + |x| x.gen(), // TODO + ) + } + 5 => { + let rnd_len = self.gen.rng.gen_range(1..=6); + // TODO: fix n random chars for n random bytes + t.token_symbol = SetOrKeep::Set(TokenSymbol( + Alphanumeric.sample_string(&mut self.gen.rng, rnd_len), + )); + } + 6 => self.mutate_from_account(&mut t.timing, account), + 7 => t.voting_for = SetOrKeep::Set(self.gen()), + _ => unimplemented!(), + } + } + } +} + +impl Mutator> for FuzzerCtx +where + FuzzerCtx: Mutator + Generator, +{ + #[coverage(off)] + fn mutate(&mut self, t: &mut OrIgnore) { + match t { + OrIgnore::Check(inner) => { + if self.gen.rng.gen_bool(0.9) { + self.mutate(inner) + } else { + *t = OrIgnore::Ignore; + } + } + OrIgnore::Ignore => *t = OrIgnore::Check(self.gen()), + } + } +} + +impl MutatorFromAccount> for FuzzerCtx +where + FuzzerCtx: MutatorFromAccount + GeneratorFromAccount, +{ + #[coverage(off)] + fn mutate_from_account(&mut self, t: &mut OrIgnore, account: &Account) { + match t { + OrIgnore::Check(inner) => { + if self.gen.rng.gen_bool(0.9) { + self.mutate_from_account(inner, account) + } else { + *t = OrIgnore::Ignore; + } + } + OrIgnore::Ignore => *t = OrIgnore::Check(self.gen_from_account(account)), + } + } +} + +impl Mutator for FuzzerCtx { + #[coverage(off)] + fn mutate(&mut self, t: &mut CompressedPubKey) { + *t = self.gen(); + } +} + +impl Mutator for FuzzerCtx { + #[coverage(off)] + fn mutate(&mut self, t: &mut bool) { + *t = !*t; + } +} + +impl Mutator> for FuzzerCtx +where + FuzzerCtx: Mutator, +{ + #[coverage(off)] + fn mutate(&mut self, t: &mut ClosedInterval) { + for option in rand_elements(self, 8) { + match option { + 0 => self.mutate(&mut t.lower), + 1 => self.mutate(&mut t.upper), + _ => unimplemented!(), + } + } + } +} + +impl MutatorFromAccount for FuzzerCtx { + #[coverage(off)] + fn mutate_from_account(&mut self, t: &mut Balance, _account: &Account) { + //*t = self.gen_from_account(account); + *t = GeneratorRange64::::gen_range(self, 0..=Balance::max().as_u64()) + } +} + +impl MutatorFromAccount for FuzzerCtx { + #[coverage(off)] + fn mutate_from_account(&mut self, t: &mut Nonce, _account: &Account) { + //*t = self.gen_from_account(account); + *t = GeneratorRange32::::gen_range(self, 0..=u32::MAX); + } +} + +impl MutatorFromAccount> for FuzzerCtx +where + FuzzerCtx: MutatorFromAccount, +{ + #[coverage(off)] + fn mutate_from_account(&mut self, t: &mut ClosedInterval, account: &Account) { + for option in rand_elements(self, 8) { + match option { + 0 => self.mutate_from_account(&mut t.lower, account), + 1 => self.mutate_from_account(&mut t.upper, account), + _ => unimplemented!(), + } + } + } +} + +impl MutatorFromAccount for FuzzerCtx { + #[coverage(off)] + fn mutate_from_account(&mut self, t: &mut zkapp_command::Account, account: &Account) { + for option in rand_elements(self, 8) { + match option { + 0 => self.mutate_from_account(&mut t.balance, account), + 1 => self.mutate_from_account(&mut t.nonce, account), + 2 => self.mutate(&mut t.receipt_chain_hash), + 3 => self.mutate(&mut t.delegate), + 4 => self.mutate(&mut t.state), + 5 => self.mutate(&mut t.action_state), + 6 => self.mutate(&mut t.proved_state), + 7 => self.mutate(&mut t.is_new), + _ => unimplemented!(), + } + } + } +} + +impl MutatorFromAccount for FuzzerCtx { + #[coverage(off)] + fn mutate_from_account(&mut self, t: &mut AccountPreconditions, account: &Account) { + self.mutate_from_account(&mut t.0, account) + } +} + +impl Mutator for FuzzerCtx { + #[coverage(off)] + fn mutate(&mut self, t: &mut zkapp_command::EpochData) { + for option in rand_elements(self, 5) { + match option { + 0 => { + *t.ledger_mut() = zkapp_command::EpochLedger { + hash: OrIgnore::Check(self.gen()), + total_currency: OrIgnore::Check(self.gen_wrap( + #[coverage(off)] + |x| GeneratorRange64::::gen_range(x, 0..=u64::MAX), + )), + } + } + 1 => t.seed = OrIgnore::Check(self.gen()), + 2 => t.start_checkpoint = OrIgnore::Check(self.gen()), + 3 => t.lock_checkpoint = OrIgnore::Check(self.gen()), + 4 => { + t.epoch_length = OrIgnore::Check(self.gen_wrap( + #[coverage(off)] + |x| x.gen(), + )) + } + _ => unimplemented!(), + } + } + } +} + +impl MutatorFromAccount for FuzzerCtx { + #[coverage(off)] + fn mutate_from_account(&mut self, t: &mut ZkAppPreconditions, _account: &Account) { + for option in rand_elements(self, 7) { + match option { + 0 => t.snarked_ledger_hash = OrIgnore::Check(self.gen()), + 1 => { + let blockchain_length = self.gen_wrap( + #[coverage(off)] + |x| x.gen(), + ); + t.blockchain_length = OrIgnore::Check(blockchain_length); + } + 2 => { + let min_window_density = self.gen_wrap( + #[coverage(off)] + |x| x.gen(), + ); + t.min_window_density = OrIgnore::Check(min_window_density); + } + 3 => { + let total_currency = self.gen_wrap( + #[coverage(off)] + |x| GeneratorRange64::::gen_range(x, 0..=u64::MAX), + ); + t.total_currency = OrIgnore::Check(total_currency); + } + 4 => { + let global_slot_since_genesis = self.gen_wrap( + #[coverage(off)] + |x| Slot::from_u32(x.gen.rng.gen_range(0..Slot::max().as_u32())), + ); + t.global_slot_since_genesis = OrIgnore::Check(global_slot_since_genesis); + } + 5 => self.mutate(&mut t.staking_epoch_data), + 6 => self.mutate(&mut t.next_epoch_data), + _ => unimplemented!(), + } + } + } +} + +impl MutatorFromAccount for FuzzerCtx { + #[coverage(off)] + fn mutate_from_account(&mut self, t: &mut Preconditions, account: &Account) { + for option in rand_elements(self, 2) { + match option { + 0 => self.mutate_from_account(t.network_mut(), account), + 1 => self.mutate_from_account(&mut t.account, account), + _ => unimplemented!(), + } + } + } +} + +impl Mutator for FuzzerCtx { + #[coverage(off)] + fn mutate(&mut self, t: &mut Body) { + let account = self.get_account(&t.public_key).unwrap(); + + for option in rand_elements(self, 11) { + match option { + 0 => t.token_id = TokenId(self.gen()), + 1 => self.mutate_from_account(&mut t.update, &account), + 2 => { + t.balance_change = if self.gen.rng.gen_bool(0.5) { + let magnitude = + GeneratorRange64::::gen_range(self, 0..=Amount::max().as_u64()); + Signed::::create(magnitude, self.gen()) + } else { + Signed::::zero() + } + } + 3 => self.mutate(&mut t.increment_nonce), + 4 => t.events = self.gen(), + 5 => t.actions = self.gen(), + 6 => t.call_data = self.gen(), + 7 => self.mutate_from_account(&mut t.preconditions, &account), + 8 => self.mutate(&mut t.use_full_commitment), + 9 => (), // Can't mutate because it breaks binprot + 10 => { + let vk_hash = if self.gen.rng.gen_bool(0.5) + && account.zkapp.is_some() + && account.zkapp.as_ref().unwrap().verification_key.is_some() + { + account + .zkapp + .as_ref() + .unwrap() + .verification_key + .as_ref() + .unwrap() + .hash() + } else { + self.gen() + }; + + let options = vec![ + zkapp_command::AuthorizationKind::NoneGiven, + zkapp_command::AuthorizationKind::Signature, + zkapp_command::AuthorizationKind::Proof(vk_hash), + ]; + t.authorization_kind = options.choose(&mut self.gen.rng).unwrap().clone(); + } + _ => unimplemented!(), + } + } + } +} + +impl Mutator for FuzzerCtx { + #[coverage(off)] + fn mutate(&mut self, t: &mut AccountUpdate) { + for option in rand_elements(self, 2) { + match option { + 0 => self.mutate(&mut t.body), + 1 => { + if self.gen.rng.gen_bool(0.5) { + t.authorization = match t.body.authorization_kind { + zkapp_command::AuthorizationKind::NoneGiven => { + zkapp_command::Control::NoneGiven + } + zkapp_command::AuthorizationKind::Signature => { + zkapp_command::Control::Signature(Signature::dummy()) + } + zkapp_command::AuthorizationKind::Proof(_) => { + zkapp_command::Control::Proof(self.gen()) + } + }; + } else { + t.authorization = match vec![0, 1, 2].choose(&mut self.gen.rng).unwrap() { + 0 => zkapp_command::Control::NoneGiven, + 1 => zkapp_command::Control::Signature(Signature::dummy()), + 2 => zkapp_command::Control::Proof(self.gen()), + _ => unimplemented!(), + }; + } + } + _ => unimplemented!(), + } + } + } +} + +impl Mutator> for FuzzerCtx { + #[coverage(off)] + fn mutate(&mut self, t: &mut zkapp_command::CallForest) { + for i in rand_elements(self, t.0.len()) { + let tree_digest = { + let tree = &mut t.0[i].elt; + + for option in rand_elements(self, 2) { + match option { + 0 => { + self.mutate(&mut tree.account_update); + tree.account_update_digest = tree.account_update.digest(); + } + 1 => self.mutate(&mut tree.calls), + _ => unimplemented!(), + } + } + + tree.digest() + }; + + let h_tl = if let Some(x) = t.0.get(i + 1) { + x.stack_hash + } else { + Fp::zero() + }; + + t.0[i].stack_hash = hash_with_kimchi("MinaAcctUpdateCons", &[tree_digest, h_tl]); + } + } +} + +#[coverage(off)] +pub fn fix_nonces( + ctx: &mut FuzzerCtx, + account_updates: &mut zkapp_command::CallForest, +) { + for acc_update in account_updates.0.iter_mut() { + let account_update = &mut acc_update.elt.account_update; + + if let zkapp_command::Account { + nonce: OrIgnore::Check(_), + .. + } = &account_update.body.preconditions.account.0 + { + let account = ctx.get_account(&account_update.public_key()).unwrap(); + + account_update.body.preconditions.account.0.nonce = + OrIgnore::Check(ctx.gen_from_account(&account)); + } + + fix_nonces(ctx, &mut acc_update.elt.calls); + } +} + +impl Mutator for FuzzerCtx { + #[coverage(off)] + fn mutate(&mut self, t: &mut ZkAppCommand) { + for option in rand_elements(self, 3) { + match option { + 0 => self.mutate(&mut t.fee_payer), + 1 => self.mutate(&mut t.account_updates), + 2 => t.memo = self.gen(), + _ => unimplemented!(), + } + } + + // Fix fee_payer nonce. + let public_key = t.fee_payer.body.public_key.clone(); + let account = self.get_account(&public_key).unwrap(); + t.fee_payer.body.nonce = self.gen_from_account(&account); + + // Fix account updates nonces. + fix_nonces(self, &mut t.account_updates); + + let (txn_commitment, full_txn_commitment) = get_transaction_commitments(t); + let mut signer = mina_signer::create_kimchi(NetworkId::TESTNET); + + if self.gen.rng.gen_bool(0.9) { + let keypair = self.find_keypair(&t.fee_payer.body.public_key).unwrap(); + t.fee_payer.authorization = signer.sign(keypair, &full_txn_commitment); + } + + if self.gen.rng.gen_bool(0.9) { + sign_account_updates( + self, + &mut signer, + &txn_commitment, + &full_txn_commitment, + &mut t.account_updates, + ); + } + } +} + +impl Mutator for FuzzerCtx { + #[coverage(off)] + fn mutate(&mut self, t: &mut UserCommand) { + match t { + UserCommand::ZkAppCommand(zkapp_command) => self.mutate(zkapp_command.as_mut()), + _ => unimplemented!(), + } + } +} + +impl Mutator for FuzzerCtx { + #[coverage(off)] + fn mutate(&mut self, t: &mut Transaction) { + match t { + Transaction::Command(user_command) => self.mutate(user_command), + _ => unimplemented!(), + } + } +} + +// impl Mutator for FuzzerCtx { +// #[no_coverage] +// fn mutate(&mut self, t: &mut MinaStateSnarkedLedgerStateWithSokStableV2) { +// for option in rand_elements(self, 6) { +// match option { +// // 0 => self.mutate(&mut t.source), +// // 1 => self.mutate(&mut t.target), +// // 2 => self.mutate(&mut t.connecting_ledger_left), +// // 3 => self.mutate(&mut t.connecting_ledger_right), +// // 4 => self.mutate(&mut t.supply_increase), +// // 5 => self.mutate(&mut t.fee_excess), +// // // 6 => self.mutate(&mut t.sok_digest), +// _ => unimplemented!(), +// } +// } +// } +// } + +impl Mutator for FuzzerCtx { + #[coverage(off)] + fn mutate(&mut self, t: &mut PicklesProofProofsVerified2ReprStableV2PrevEvals) { + for option in rand_elements(self, 2) { + match option { + // 0 => self.mutate(&mut t.statement), + 0 => self.mutate(&mut t.evals), + 1 => self.mutate(&mut t.ft_eval1), + _ => unimplemented!(), + } + } + } +} + +impl Mutator> for FuzzerCtx +where + FuzzerCtx: Mutator>, +{ + #[coverage(off)] + fn mutate(&mut self, t: &mut ArrayN16) { + self.mutate(t.inner_mut()); + } +} + +impl Mutator for FuzzerCtx { + #[coverage(off)] + fn mutate(&mut self, t: &mut PicklesWrapWireProofStableV1Bulletproof) { + for option in rand_elements(self, 3) { + match option { + 0 => self.mutate(&mut t.lr), + 1 => self.mutate(&mut t.z_1), + 2 => self.mutate(&mut t.z_2), + 3 => self.mutate(&mut t.delta), + 4 => self.mutate(&mut t.challenge_polynomial_commitment), + _ => unimplemented!(), + } + } + } +} + +impl Mutator for FuzzerCtx { + #[coverage(off)] + fn mutate(&mut self, t: &mut PicklesProofProofsVerified2ReprStableV2PrevEvalsEvalsEvals) { + for option in rand_elements(self, 6) { + match option { + 0 => self.mutate(&mut t.w), + 1 => self.mutate(&mut t.coefficients), + 2 => self.mutate(&mut t.z), + 3 => self.mutate(&mut t.s), + 4 => self.mutate(&mut t.generic_selector), + 5 => self.mutate(&mut t.poseidon_selector), + _ => unimplemented!(), + } + } + } +} + +impl Mutator for FuzzerCtx { + #[coverage(off)] + fn mutate(&mut self, t: &mut PicklesProofProofsVerified2ReprStableV2PrevEvalsEvals) { + for option in rand_elements(self, 2) { + match option { + 0 => self.mutate(&mut t.public_input), + 1 => self.mutate(&mut t.evals), + _ => unimplemented!(), + } + } + } +} + +// impl Mutator for FuzzerCtx { +// #[coverage(off)] +// fn mutate(&mut self, t: &mut PicklesProofProofsVerified2ReprStableV2ProofOpenings) { +// for option in rand_elements(self, 3) { +// match option { +// 0 => self.mutate(&mut t.proof), +// 1 => self.mutate(&mut t.evals), +// 2 => self.mutate(&mut t.ft_eval1), +// _ => unimplemented!(), +// } +// } +// } +// } + +// impl Mutator for FuzzerCtx { +// #[coverage(off)] +// fn mutate(&mut self, t: &mut PicklesProofProofsVerified2ReprStableV2Proof) { +// for option in rand_elements(self, 2) { +// match option { +// // 0 => self.mutate(&mut t.statement), +// 0 => self.mutate(&mut t.messages), +// 1 => self.mutate(&mut t.openings), +// _ => unimplemented!(), +// } +// } +// } +// } + +impl Mutator for FuzzerCtx { + #[coverage(off)] + fn mutate(&mut self, t: &mut PicklesWrapWireProofCommitmentsStableV1) { + for option in rand_elements(self, 3) { + match option { + 0 => self.mutate(&mut t.w_comm), + 1 => self.mutate(&mut t.z_comm), + 2 => self.mutate(&mut t.t_comm), + _ => unreachable!(), + } + } + } +} + +impl Mutator for FuzzerCtx { + #[coverage(off)] + fn mutate(&mut self, t: &mut PicklesWrapWireProofEvaluationsStableV1) { + for option in rand_elements(self, 10) { + match option { + 0 => self.mutate(&mut t.w), + 1 => self.mutate(&mut t.coefficients), + 2 => self.mutate(&mut t.z), + 3 => self.mutate(&mut t.s), + 4 => self.mutate(&mut t.generic_selector), + 5 => self.mutate(&mut t.poseidon_selector), + 6 => self.mutate(&mut t.complete_add_selector), + 7 => self.mutate(&mut t.mul_selector), + 8 => self.mutate(&mut t.emul_selector), + 9 => self.mutate(&mut t.endomul_scalar_selector), + _ => unreachable!(), + } + } + } +} + +impl Mutator for FuzzerCtx { + #[coverage(off)] + fn mutate(&mut self, t: &mut PicklesWrapWireProofStableV1) { + for option in rand_elements(self, 4) { + match option { + 0 => self.mutate(&mut t.commitments), + 1 => self.mutate(&mut t.evaluations), + 2 => self.mutate(&mut t.ft_eval1), + 3 => self.mutate(&mut t.bulletproof), + _ => unimplemented!(), + } + } + } +} + +impl Mutator for FuzzerCtx { + #[coverage(off)] + fn mutate(&mut self, t: &mut PicklesProofProofsVerified2ReprStableV2) { + for option in rand_elements(self, 2) { + match option { + // 0 => self.mutate(&mut t.statement), + 0 => self.mutate(&mut t.prev_evals), + 1 => self.mutate(&mut t.proof), + _ => unimplemented!(), + } + } + } +} + +impl Mutator for FuzzerCtx { + #[coverage(off)] + fn mutate(&mut self, t: &mut TransactionSnarkProofStableV2) { + self.mutate(&mut t.0) + } +} + +impl Mutator for FuzzerCtx { + #[coverage(off)] + fn mutate(&mut self, t: &mut TransactionSnarkStableV2) { + self.mutate(&mut t.proof) + // for option in rand_elements(self, 2) { + // match option { + // 0 => self.mutate(&mut t.statement), + // 1 => self.mutate(&mut t.proof), + // _ => unimplemented!(), + // } + // } + } +} + +impl Mutator for FuzzerCtx { + #[coverage(off)] + fn mutate(&mut self, t: &mut TransactionSnarkScanStateLedgerProofWithSokMessageStableV2) { + self.mutate(&mut t.0 .0) + } +} diff --git a/tools/fuzzing/src/transaction_fuzzer/context.rs b/tools/fuzzing/src/transaction_fuzzer/context.rs new file mode 100644 index 000000000..dfacaf9a8 --- /dev/null +++ b/tools/fuzzing/src/transaction_fuzzer/context.rs @@ -0,0 +1,1371 @@ +use crate::transaction_fuzzer::{ + generator::{Generator, GeneratorRange32, GeneratorRange64}, + mutator::Mutator, + {deserialize, serialize}, +}; +use ark_ff::fields::arithmetic::InvalidBigInt; +use ark_ff::Zero; +use ledger::scan_state::currency::{Amount, Fee, Length, Magnitude, Nonce, Signed, Slot}; +use ledger::scan_state::transaction_logic::protocol_state::{ + protocol_state_view, EpochData, EpochLedger, ProtocolStateView, +}; +use ledger::scan_state::transaction_logic::transaction_applied::{ + signed_command_applied, CommandApplied, TransactionApplied, Varying, +}; +use ledger::scan_state::transaction_logic::{ + apply_transactions, Transaction, TransactionStatus, UserCommand, +}; +use ledger::sparse_ledger::LedgerIntf; +use ledger::staged_ledger::staged_ledger::StagedLedger; +use ledger::{dummy, Account, AccountId, Database, Mask, Timing, TokenId}; +use mina_hasher::Fp; +use mina_p2p_messages::binprot::SmallString1k; +use mina_p2p_messages::{ + bigint, binprot, + v2::{ + MinaTransactionLogicTransactionAppliedStableV2, + TransactionSnarkScanStateLedgerProofWithSokMessageStableV2, + }, +}; +use mina_signer::{CompressedPubKey, Keypair}; +use openmina_core::constants::ConstraintConstants; +use rand::{rngs::SmallRng, seq::SliceRandom, Rng, SeedableRng}; +use ring_buffer::RingBuffer; +use std::collections::HashMap; +use std::fmt::Debug; +use std::{fs, str::FromStr}; + +/// Same values when we run `dune runtest src/lib/staged_ledger -f` +pub const CONSTRAINT_CONSTANTS: ConstraintConstants = ConstraintConstants { + sub_windows_per_window: 11, + ledger_depth: 35, + work_delay: 2, + block_window_duration_ms: 180000, + transaction_capacity_log_2: 7, + pending_coinbase_depth: 5, + coinbase_amount: 720000000000, + supercharged_coinbase_factor: 2, + account_creation_fee: 1000000000, + fork: None, +}; + +// Taken from ocaml_tests +/// Same values when we run `dune runtest src/lib/staged_ledger -f` +#[coverage(off)] +fn dummy_state_view(global_slot_since_genesis: Option) -> ProtocolStateView { + // TODO: Use OCaml implementation, not hardcoded value + let f = #[coverage(off)] + |s: &str| Fp::from_str(s).unwrap(); + + ProtocolStateView { + snarked_ledger_hash: f( + "19095410909873291354237217869735884756874834695933531743203428046904386166496", + ), + blockchain_length: Length::from_u32(1), + min_window_density: Length::from_u32(77), + total_currency: Amount::from_u64(10016100000000000), + global_slot_since_genesis: global_slot_since_genesis.unwrap_or_else(Slot::zero), + staking_epoch_data: EpochData { + ledger: EpochLedger { + hash: f( + "19095410909873291354237217869735884756874834695933531743203428046904386166496", + ), + total_currency: Amount::from_u64(10016100000000000), + }, + seed: Fp::zero(), + start_checkpoint: Fp::zero(), + lock_checkpoint: Fp::zero(), + epoch_length: Length::from_u32(1), + }, + next_epoch_data: EpochData { + ledger: EpochLedger { + hash: f( + "19095410909873291354237217869735884756874834695933531743203428046904386166496", + ), + total_currency: Amount::from_u64(10016100000000000), + }, + seed: f( + "18512313064034685696641580142878809378857342939026666126913761777372978255172", + ), + start_checkpoint: Fp::zero(), + lock_checkpoint: f( + "9196091926153144288494889289330016873963015481670968646275122329689722912273", + ), + epoch_length: Length::from_u32(2), + }, + } +} + +#[coverage(off)] +pub fn dummy_state_and_view( + global_slot: Option, +) -> Result< + ( + mina_p2p_messages::v2::MinaStateProtocolStateValueStableV2, + ProtocolStateView, + ), + InvalidBigInt, +> { + let mut state = dummy::for_tests::dummy_protocol_state(); + + if let Some(global_slot) = global_slot { + let new_global_slot = global_slot; + + let global_slot_since_genesis = { + let since_genesis = &state.body.consensus_state.global_slot_since_genesis; + let curr = &state + .body + .consensus_state + .curr_global_slot_since_hard_fork + .slot_number; + + let since_genesis = Slot::from_u32(since_genesis.as_u32()); + let curr = Slot::from_u32(curr.as_u32()); + + (since_genesis.checked_sub(&curr).unwrap()) + .checked_add(&new_global_slot) + .unwrap() + }; + + let cs = &mut state.body.consensus_state; + cs.curr_global_slot_since_hard_fork.slot_number = (&new_global_slot).into(); + cs.global_slot_since_genesis = (&global_slot_since_genesis).into(); + }; + + let view = protocol_state_view(&state)?; + + Ok((state, view)) +} + +pub enum PermissionModel { + Any, // Allow any (random) combination of permissions + Empty, // Permissions are always set to None + Initial, // Permissions are always set to "user_default" set (signature only). + Default, // "default" permissions as set by SnarkyJS when deploying a zkApp. + TokenOwner, // permission set usually set in Token owner zkApps +} + +impl Clone for PermissionModel { + #[coverage(off)] + fn clone(&self) -> Self { + match self { + PermissionModel::Any => PermissionModel::Any, + PermissionModel::Empty => PermissionModel::Empty, + PermissionModel::Initial => PermissionModel::Initial, + PermissionModel::Default => PermissionModel::Default, + PermissionModel::TokenOwner => PermissionModel::TokenOwner, + } + } +} + +// #[derive(BinProtWrite, Debug)] +// pub struct TxProofCreateInputs { +// pub sok_message: MinaBaseSokMessageStableV1, +// pub snarked_ledger_state: MinaStateSnarkedLedgerStateStableV2, +// pub witness: TxWitness, +// } + +// #[derive(BinProtWrite, Debug)] +// pub struct TxWitness { +// pub transaction: Tx, +// pub first_pass_ledger: MinaBaseSparseLedgerBaseStableV2, +// pub second_pass_ledger: MinaBaseSparseLedgerBaseStableV2, +// pub protocol_state_body: MinaStateProtocolStateBodyValueStableV2, +// pub init_stack: MinaBasePendingCoinbaseStackVersionedStableV1, +// pub status: MinaBaseTransactionStatusStableV2, +// pub block_global_slot: UnsignedExtendedUInt32StableV1, +// } + +#[derive(Debug)] +pub struct ApplyTxResult { + root_hash: Fp, + pub apply_result: Vec, + error: String, +} + +impl binprot::BinProtRead for ApplyTxResult { + #[coverage(off)] + fn binprot_read(r: &mut R) -> Result + where + Self: Sized, + { + let root_hash: Fp = bigint::BigInt::binprot_read(r)? + .try_into() + .map_err(|x| binprot::Error::CustomError(Box::new(x)))?; + // Start of Selection + let apply_result = Vec::::binprot_read(r)? + .into_iter() + .map( + #[coverage(off)] + |MinaTransactionLogicTransactionAppliedStableV2 { + previous_hash, + varying, + }| { + let previous_hash = (&previous_hash.0) + .to_field() + .map_err(|x| binprot::Error::CustomError(Box::new(x)))?; + + let varying = (&varying) + .try_into() + .map_err(|x| binprot::Error::CustomError(Box::new(x)))?; + + Ok::(TransactionApplied { + previous_hash, + varying, + }) + }, + ) + .collect::, _>>()?; + let error: String = SmallString1k::binprot_read(r)?.0; + + Ok(ApplyTxResult { + root_hash, + apply_result, + error, + }) + } +} + +// // TODO: remove this type once `Transaction` implements `BinProtWrite`. +// #[derive(BinProtWrite, Debug, Clone)] +// pub enum Tx { +// UserCommand(UserCommand), +// } + +// impl From for Transaction { +// fn from(value: Tx) -> Self { +// match value { +// Tx::UserCommand(v) => Self::Command(v), +// } +// } +// } + +// impl From for Tx { +// fn from(value: Transaction) -> Self { +// match value { +// Transaction::Command(v) => Tx::UserCommand(v), +// _ => unimplemented!(), +// } +// } +// } + +pub enum LedgerKind { + Mask(Mask), + Staged(StagedLedger, Mask), +} + +impl Clone for LedgerKind { + #[coverage(off)] + fn clone(&self) -> Self { + match self { + Self::Mask(ledger) => Self::Mask(ledger.copy()), + Self::Staged(ledger, snarked_ledger) => { + Self::Staged(ledger.clone(), snarked_ledger.clone()) + } + } + } +} + +pub struct FuzzerState { + pub ledger: LedgerKind, + pub potential_senders: Vec<(Keypair, PermissionModel)>, + pub potential_new_accounts: Vec<(Keypair, PermissionModel)>, + pub cache_pool: RingBuffer, + pub cache_apply: RingBuffer, +} + +impl Clone for FuzzerState { + #[coverage(off)] + fn clone(&self) -> Self { + Self { + ledger: self.ledger.clone(), + potential_senders: self.potential_senders.clone(), + potential_new_accounts: self.potential_new_accounts.clone(), + cache_pool: self.cache_pool.clone(), + cache_apply: self.cache_apply.clone(), + } + } +} + +pub struct GeneratorCtx { + pub rng: SmallRng, + pub max_account_balance: u64, + pub minimum_fee: u64, + pub excess_fee: Signed, + pub token_id: TokenId, + pub tx_proof: Option, + pub nonces: HashMap, // TODO: implement hash trait for CompressedPubKey + /// Attempt to produce a valid zkapp + pub attempt_valid_zkapp: bool, +} + +pub struct FuzzerCtx { + pub constraint_constants: ConstraintConstants, + pub txn_state_view: ProtocolStateView, + pub fuzzcases_path: String, + pub gen: GeneratorCtx, + pub state: FuzzerState, + pub snapshots: RingBuffer, +} + +impl FuzzerCtx { + #[coverage(off)] + fn get_ledger_inner(&self) -> &Mask { + match &self.state.ledger { + LedgerKind::Mask(ledger) => ledger, + LedgerKind::Staged(ledger, _) => ledger.ledger_ref(), + } + } + + #[coverage(off)] + fn get_ledger_inner_mut(&mut self) -> &mut Mask { + match &mut self.state.ledger { + LedgerKind::Mask(ledger) => ledger, + LedgerKind::Staged(ledger, _) => ledger.ledger_mut(), + } + } + + #[coverage(off)] + fn get_snarked_ledger_inner_mut(&mut self) -> Option<&mut Mask> { + match &mut self.state.ledger { + LedgerKind::Mask(_) => None, + LedgerKind::Staged(_, snarked_ledger) => Some(snarked_ledger), + } + } + + // #[coverage(off)] + // fn set_snarked_ledger(&mut self, snarked_ledger: Mask) { + // match &mut self.state.ledger { + // LedgerKind::Mask(_) => panic!(), + // LedgerKind::Staged(_, old_snarked_ledger) => *old_snarked_ledger = snarked_ledger, + // } + // } + + // #[coverage(off)] + // fn get_staged_ledger(&mut self) -> &mut StagedLedger { + // match &mut self.state.ledger { + // LedgerKind::Staged(ledger, _) => ledger, + // _ => panic!(), + // } + // } + + #[coverage(off)] + pub fn get_snarked_ledger(&mut self) -> &mut Mask { + match &mut self.state.ledger { + LedgerKind::Staged(_, ledger) => ledger, + _ => panic!(), + } + } + + #[coverage(off)] + pub fn create_inital_accounts(&mut self, n: usize) { + for _ in 0..n { + loop { + let keypair: Keypair = self.gen(); + + if !self.state.potential_senders.iter().any( + #[coverage(off)] + |(kp, _)| kp.public == keypair.public, + ) { + let pk_compressed = keypair.public.into_compressed(); + let account_id = AccountId::new(pk_compressed, TokenId::default()); + let mut account = Account::initialize(&account_id); + + account.balance = GeneratorRange64::gen_range( + self, + 1_000_000_000_000..=self.gen.max_account_balance, + ); + account.nonce = GeneratorRange32::gen_range(self, 0..=u32::MAX); + account.timing = Timing::Untimed; + + let permission_model = self.gen(); + self.state + .potential_senders + .push((keypair, permission_model)); + + if let Some(snarked_ledger) = self.get_snarked_ledger_inner_mut() { + snarked_ledger + .create_new_account(account_id.clone(), account.clone()) + .unwrap(); + }; + + self.get_ledger_inner_mut() + .create_new_account(account_id, account) + .unwrap(); + + break; + } + } + } + } + + #[coverage(off)] + pub fn get_account(&mut self, pkey: &CompressedPubKey) -> Option { + let account_location = LedgerIntf::location_of_account( + self.get_ledger_inner(), + &AccountId::new(pkey.clone(), TokenId::default()), + ); + + account_location.map( + #[coverage(off)] + |location| *(LedgerIntf::get(self.get_ledger_inner(), &location).unwrap()).clone(), + ) + } + + #[coverage(off)] + pub fn find_sender(&mut self, pkey: &CompressedPubKey) -> Option<&(Keypair, PermissionModel)> { + self.state.potential_senders.iter().find( + #[coverage(off)] + |(kp, _)| kp.public.into_compressed() == *pkey, + ) + } + + #[coverage(off)] + pub fn find_permissions(&mut self, pkey: &CompressedPubKey) -> Option<&PermissionModel> { + self.find_sender(pkey).map( + #[coverage(off)] + |(_, pm)| pm, + ) + } + + #[coverage(off)] + pub fn find_keypair(&mut self, pkey: &CompressedPubKey) -> Option<&Keypair> { + self.find_sender(pkey).map( + #[coverage(off)] + |(kp, _)| kp, + ) + } + + #[coverage(off)] + pub fn random_keypair(&mut self) -> Keypair { + self.state + .potential_senders + .choose(&mut self.gen.rng) + .unwrap() + .0 + .clone() + } + + #[coverage(off)] + pub fn random_ntransactions(&mut self) -> usize { + self.gen.rng.gen_range(0..400) + } + + #[coverage(off)] + pub fn random_snark_worker_fee(&mut self) -> Fee { + let fee = self.gen.rng.gen_range(0..10_000_000); + Fee::from_u64(fee) + } + + #[coverage(off)] + pub fn random_user_command(&mut self) -> UserCommand { + if self.gen.rng.gen_bool(0.9) { + if !self.state.cache_apply.is_empty() { + // Pick transaction from the applied tx cache and mutate it + let index = self.gen.rng.gen_range(0..self.state.cache_apply.len()); + + if let Some(mut transaction) = self.state.cache_apply.get_relative(index).cloned() { + self.mutate(&mut transaction); + return transaction; + } + } + + // If we can't find a tx in the applied cache, try one from the pool cache + if self.gen.rng.gen_bool(0.5) && !self.state.cache_pool.is_empty() { + let index = self.gen.rng.gen_range(0..self.state.cache_pool.len()); + + if let Some(mut transaction) = self.state.cache_pool.get_relative(index).cloned() { + self.mutate(&mut transaction); + return transaction; + } + } + } + + // Generate random transaction + self.gen() + } + + #[coverage(off)] + pub fn random_tx_proof( + &mut self, + ) -> TransactionSnarkScanStateLedgerProofWithSokMessageStableV2 { + let mut proof = self + .gen + .tx_proof + .clone() + .expect("valid tx proof not set for FuzzerCtx"); + self.mutate(&mut proof); + proof + } + + // #[coverage(off)] + // pub fn random_create_tx_proof_inputs( + // &mut self, + // protocol_state_body: MinaStateProtocolStateBodyValueStableV2, + // mut verify_tx: F, + // ) -> Option<(bool, TxProofCreateInputs)> + // where + // F: FnMut(&Transaction) -> bool, + // { + // let state_body_hash = MinaHash::hash(&protocol_state_body); + // let block_global_slot = protocol_state_body + // .consensus_state + // .global_slot_since_genesis + // .clone(); + // let init_stack = protocol_state_body + // .blockchain_state + // .ledger_proof_statement + // .source + // .pending_coinbase_stack + // .clone(); + + // let tx = self.random_transaction(); + // let is_valid = verify_tx(&tx); + // let transaction = WithStatus::applied(tx.clone()); + // let tx = Tx::from(tx); + + // let ledger = self.get_ledger_inner().make_child(); + // let staged_ledger = + // StagedLedger::create_exn(self.constraint_constants.clone(), ledger).unwrap(); + // let apply_res = StagedLedger::update_ledger_and_get_statements( + // &self.constraint_constants, + // // TODO(binier): construct from passed protocol_state_body. + // self.txn_state_view.global_slot_since_genesis, + // staged_ledger.ledger(), + // &(&init_stack).into(), + // (vec![transaction.clone()], None), + // // TODO(binier): construct from passed protocol_state_body. + // &self.txn_state_view, + // ( + // // TODO(binier): use state hash instead. Not used anyways though. + // state_body_hash, + // state_body_hash, + // ), + // ); + // let tx_with_witness = match apply_res { + // Ok((txs_with_witness, ..)) => txs_with_witness.into_iter().next().unwrap(), + // Err(_) => return None, + // }; + + // Some(( + // is_valid, + // TxProofCreateInputs { + // sok_message: serde_json::from_value(serde_json::json!({ + // "fee":"25000000", + // "prover":"B62qn7G9oFofQDGiAoP8TmYce7185PjWJ39unqjr2v7EgsRDoFCFc1k" + // })) + // .unwrap(), + // snarked_ledger_state: (&tx_with_witness.statement).into(), + // witness: TxWitness { + // transaction: tx, + // first_pass_ledger: (&tx_with_witness.first_pass_ledger_witness).into(), + // second_pass_ledger: (&tx_with_witness.second_pass_ledger_witness).into(), + // protocol_state_body, + // // TODO(binier): should we somehow use value from `tx_with_witness`? + // init_stack, + // status: MinaBaseTransactionStatusStableV2::Applied, + // block_global_slot, + // }, + // }, + // )) + // } + + #[coverage(off)] + pub fn take_snapshot(&mut self) { + println!("Taking snapshot..."); + self.snapshots.push_back(self.state.clone()); + } + + #[coverage(off)] + pub fn restore_snapshot(&mut self) { + if !self.snapshots.is_empty() { + // Pick random snapshot + let index = self.gen.rng.gen_range(0..self.snapshots.len()); + + if let Some(state) = self.snapshots.get_relative(index).cloned() { + println!("Restoring snapshot {}...", index); + self.state = state; + } + } + } + + // #[coverage(off)] + // pub fn serialize_transaction(tx: &Transaction) -> Vec { + // /* + // We don't have generated types for Transaction, but we have one + // for UserCommand (MinaBaseUserCommandStableV2). Extract and + // serialize the inner UserCommand and let a OCaml wrapper build + // the transaction. + // */ + // match &tx { + // Transaction::Command(user_command) => serialize(user_command), + // _ => unimplemented!(), + // } + // } + + // #[coverage(off)] + // pub fn serialize_ledger(&self) -> Vec { + // serialize(&self.get_ledger_accounts()) + // } + + #[coverage(off)] + fn save_fuzzcase(&self, tx: &Transaction, filename: &String) { + let filename = self.fuzzcases_path.clone() + &filename + ".fuzzcase"; + + println!("Saving fuzzcase: {}", filename); + + let user_command = match tx { + Transaction::Command(user_command) => user_command.clone(), + _ => unimplemented!(), + }; + + let mut file = fs::OpenOptions::new() + .write(true) + .truncate(true) + .create(true) + .open(filename) + .unwrap(); + + serialize(&(self.get_ledger_accounts(), user_command), &mut file); + } + + #[coverage(off)] + pub fn load_fuzzcase(&mut self, file_path: &String) -> UserCommand { + println!("Loading fuzzcase: {}", file_path); + let bytes = fs::read(file_path).unwrap(); + let (accounts, user_command): (Vec, UserCommand) = + deserialize(&mut bytes.as_slice()); + + let depth = self.constraint_constants.ledger_depth as usize; + let root = Mask::new_root(Database::create(depth.try_into().unwrap())); + + *self.get_ledger_inner_mut() = root.make_child(); + + for account in accounts { + self.get_ledger_inner_mut() + .create_new_account(account.id(), account) + .unwrap(); + } + + user_command + } + + // #[coverage(off)] + // pub fn apply_staged_ledger_diff( + // &mut self, + // diff: Diff, + // global_slot: Slot, + // coinbase_receiver: CompressedPubKey, + // current_view: ProtocolStateView, + // state_hashes: (Fp, Fp), + // state_tbl: &HashMap, + // iteration: usize, + // ) -> Result, ()> { + // if iteration == 1271 { + // #[derive(Clone, Debug, PartialEq, BinProtRead, BinProtWrite)] + // struct State { + // scan_state: mina_p2p_messages::v2::TransactionSnarkScanStateStableV2, + // pending_coinbase_collection: mina_p2p_messages::v2::MinaBasePendingCoinbaseStableV2, + // states: Vec<( + // mina_p2p_messages::bigint::BigInt, + // mina_p2p_messages::v2::MinaStateProtocolStateValueStableV2, + // )>, + // snarked_ledger: Vec, + // expected_staged_ledger_merkle_root: mina_p2p_messages::bigint::BigInt, + // } + + // let sl = self.get_staged_ledger(); + // let sc = sl.scan_state.clone(); + // let pcc = sl.pending_coinbase_collection.clone(); + // let expected_staged_ledger_merkle_root = sl.ledger().clone().merkle_root(); + // let snarked_ledger = self.get_snarked_ledger(); + + // let state = State { + // scan_state: (&sc).into(), + // pending_coinbase_collection: (&pcc).into(), + // states: state_tbl + // .iter() + // .map(|(h, v)| (h.into(), v.clone())) + // .collect(), + // snarked_ledger: { + // snarked_ledger + // .to_list() + // .into_iter() + // .map(Into::into) + // .collect() + // // todo!() + // }, + // expected_staged_ledger_merkle_root: expected_staged_ledger_merkle_root.into(), + // }; + + // let mut file = std::fs::File::create("/tmp/state.bin").unwrap(); + // BinProtWrite::binprot_write(&state, &mut file).unwrap(); + // file.sync_all().unwrap(); + + // eprintln!("data saved"); + // } + + // let constraint_constants = self.constraint_constants.clone(); + + // let DiffResult { + // hash_after_applying, + // ledger_proof, + // pending_coinbase_update: _, + // } = self + // .get_staged_ledger() + // .apply( + // None, + // &constraint_constants, + // global_slot, + // diff, + // (), + // &Verifier, + // ¤t_view, + // state_hashes, + // coinbase_receiver, + // false, + // ) + // .map_err(|_| ())?; + // // .unwrap(); + + // if let Some((proof, _transactions)) = ledger_proof { + // self.update_snarked_ledger(state_tbl, proof) + // }; + + // self.get_staged_ledger().commit_and_reparent_to_root(); + + // Ok(hash_after_applying) + // } + + // #[coverage(off)] + // fn update_snarked_ledger( + // &mut self, + // state_tbl: &HashMap, + // proof: LedgerProof, + // ) { + // let target_snarked_ledger = { + // let stmt = proof.statement_ref(); + // stmt.target.first_pass_ledger + // }; + + // let apply_first_pass = |global_slot: Slot, + // txn_state_view: &ProtocolStateView, + // ledger: &mut Mask, + // transaction: &Transaction| { + // apply_transaction_first_pass( + // &CONSTRAINT_CONSTANTS, + // global_slot, + // txn_state_view, + // ledger, + // transaction, + // ) + // }; + + // let apply_second_pass = |ledger: &mut Mask, tx: TransactionPartiallyApplied| { + // apply_transaction_second_pass(&CONSTRAINT_CONSTANTS, ledger, tx) + // }; + + // let apply_first_pass_sparse_ledger = + // |global_slot: Slot, + // txn_state_view: &ProtocolStateView, + // sparse_ledger: &mut SparseLedger, + // transaction: &Transaction| { + // apply_transaction_first_pass( + // &CONSTRAINT_CONSTANTS, + // global_slot, + // txn_state_view, + // sparse_ledger, + // transaction, + // ) + // }; + + // let mut ledger = self.get_snarked_ledger().fuzzing_to_root(); + + // let get_state = |hash: Fp| Ok(state_tbl.get(&hash).cloned().unwrap()); + + // assert!(self + // .get_staged_ledger() + // .scan_state() + // .latest_ledger_proof() + // .is_some()); + + // self.get_staged_ledger() + // .scan_state() + // .get_snarked_ledger_sync( + // &mut ledger, + // get_state, + // apply_first_pass, + // apply_second_pass, + // apply_first_pass_sparse_ledger, + // ) + // .unwrap(); + + // eprintln!("#############################################################"); + // eprintln!(" NEW SNARKED LEDGER: {:?}", target_snarked_ledger); + // eprintln!("#############################################################"); + + // assert_eq!(ledger.merkle_root(), target_snarked_ledger); + // self.set_snarked_ledger(ledger); + // assert_eq!( + // self.get_snarked_ledger().merkle_root(), + // target_snarked_ledger + // ); + // } + + // #[coverage(off)] + // pub fn create_staged_ledger_diff( + // &mut self, + // txns: Vec, + // global_slot: Slot, + // prover: CompressedPubKey, + // coinbase_receiver: CompressedPubKey, + // current_view: ProtocolStateView, + // ocaml_result: &Result< + // ( + // StagedLedgerDiffDiffStableV2, + // Vec<(transaction_logic::valid::UserCommand, String)>, + // ), + // String, + // >, + // iteration: usize, + // snark_worker_fees: &mut Vec, + // ) -> Result, ()> { + // eprintln!(); + // eprintln!("###################################################"); + // eprintln!(" CREATE_STAGED_LEDGER_DIFF "); + // eprintln!("###################################################"); + + // eprintln!( + // "get_staged_ledger num_account={:?}", + // self.get_staged_ledger().ledger.account_locations().len() + // ); + // // get_staged_ledger + // self.gen.nonces.clear(); + + // let stmt_to_work_random_prover = |stmt: &Statement| -> Option { + // let fee = snark_worker_fees.pop().unwrap(); + // Some(Checked { + // fee, + // proofs: stmt.map(|statement| { + // LedgerProof::create( + // statement.clone(), + // SokDigest::default(), + // dummy::dummy_transaction_proof(), + // ) + // }), + // prover: prover.clone(), + // }) + // }; + + // let txns = txns + // .into_iter() + // .map(|tx| { + // let Transaction::Command(cmd) = tx else { + // unreachable!() + // }; + // cmd.to_valid() + // }) + // .collect(); + + // let constraint_constants = self.constraint_constants.clone(); + + // dbg!(global_slot); + + // let result = self.get_staged_ledger().create_diff( + // &constraint_constants, + // global_slot, + // None, + // coinbase_receiver, + // (), + // ¤t_view, + // txns, + // stmt_to_work_random_prover, + // false, // Always false on berkeleynet now + // ); + + // // FIXME: ignoring error messages + // if result.is_err() && ocaml_result.is_err() { + // return Ok(None); + // } + + // if !(result.is_ok() && ocaml_result.is_ok()) { + // println!( + // "!!! create_staged_ledger_diff mismatch between OCaml and Rust (result is_ok)\n{:?}\n{:?}\n", + // result, ocaml_result + // ); + + // //let bigint: num_bigint::BigUint = ledger.merkle_root().into(); + // //self.save_fuzzcase(tx, &bigint.to_string()); + // return Err(()); + // } + + // let (diff, invalid_cmds) = result.unwrap(); + // let (ocaml_diff, ocaml_invalid_cmds) = ocaml_result.as_ref().unwrap(); + + // if iteration == 1271 { + // let mut file = std::fs::File::create("/tmp/diff.bin").unwrap(); + // BinProtWrite::binprot_write(ocaml_diff, &mut file).unwrap(); + // file.sync_all().unwrap(); + // eprintln!("SAVED DIFF"); + // } + + // let diff = diff.forget(); + + // // FIXME: ignore error messages as work around for differences in string formatting between Rust and OCaml + // let rust_invalid_cmds: Vec<_> = invalid_cmds.iter().map(|x| x.0.clone()).collect(); + + // let ocaml_invalid_cmds2: Vec<_> = ocaml_invalid_cmds.iter().map(|x| x.0.clone()).collect(); + + // // Make sure we got same result + // if !(rust_invalid_cmds == ocaml_invalid_cmds2) { + // println!( + // "!!! create_staged_ledger_diff mismatch between OCaml and Rust (invalids)\n{}\n", + // self.diagnostic(&rust_invalid_cmds, &ocaml_invalid_cmds2) + // ); + + // eprintln!("last_string={:?}", ocaml_invalid_cmds.last().unwrap().1); + + // //let bigint: num_bigint::BigUint = ledger.merkle_root().into(); + // //self.save_fuzzcase(tx, &bigint.to_string()); + // return Err(()); + // } + + // let ocaml_diff: Diff = ocaml_diff.into(); + + // if !(diff == ocaml_diff) { + // println!( + // "!!! create_staged_ledger_diff mismatch between OCaml and Rust (diff)\n{}\n", + // self.diagnostic(&diff, &ocaml_diff) + // ); + // println!("!!! OCAML=\n{:?}\n", &ocaml_diff,); + + // //let bigint: num_bigint::BigUint = ledger.merkle_root().into(); + // //self.save_fuzzcase(tx, &bigint.to_string()); + // return Err(()); + // } + + // Ok(Some(diff)) + // } + + // #[coverage(off)] + // pub fn of_scan_state_pending_coinbases_and_snarked_ledger( + // &mut self, + // current_state: &MinaStateProtocolStateValueStableV2, + // state_tbl: &HashMap, + // iteration: usize, + // ) { + // eprintln!("#######################################################"); + // eprintln!("of_scan_state_pending_coinbases_and_snarked_ledger"); + // eprintln!("#######################################################"); + + // let get_state = |hash: Fp| state_tbl.get(&hash).cloned().unwrap(); + + // let mut snarked_ledger = self.get_snarked_ledger().fuzzing_to_root(); + // let sl = self.get_staged_ledger(); + // let expected_hash: StagedLedgerHash = sl.hash(); + // let expected_staged_ledger_merkle_root = sl.ledger.clone().merkle_root(); + + // dbg!(snarked_ledger.merkle_root()); + + // let new_staged_ledger = StagedLedger::of_scan_state_pending_coinbases_and_snarked_ledger( + // (), + // &CONSTRAINT_CONSTANTS, + // Verifier, + // sl.scan_state.clone(), + // snarked_ledger.copy(), + // { + // let registers: transaction_snark::Registers = (¤t_state + // .body + // .blockchain_state + // .ledger_proof_statement + // .target) + // .into(); + // registers.local_state + // }, + // expected_staged_ledger_merkle_root, + // sl.pending_coinbase_collection.clone(), + // get_state, + // ); + + // // if new_staged_ledger.is_err() || iteration == 370 { + + // // #[derive(Clone, Debug, PartialEq, binprot_derive::BinProtRead, BinProtWrite)] + // // struct State { + // // scan_state: mina_p2p_messages::v2::TransactionSnarkScanStateStableV2, + // // pending_coinbase_collection: mina_p2p_messages::v2::MinaBasePendingCoinbaseStableV2, + // // states: Vec<(mina_p2p_messages::bigint::BigInt, MinaStateProtocolStateValueStableV2)>, + // // snarked_ledger: Vec, + // // expected_staged_ledger_merkle_root: mina_p2p_messages::bigint::BigInt, + // // } + + // // let sc = sl.scan_state.clone(); + // // let pcc = sl.pending_coinbase_collection.clone(); + + // // let state = State { + // // scan_state: (&sc).into(), + // // pending_coinbase_collection: (&pcc).into(), + // // states: state_tbl.iter().map(|(h, v)| (h.into(), v.clone())).collect(), + // // snarked_ledger: { + // // use crate::BaseLedger; + // // snarked_ledger.to_list().into_iter().map(Into::into).collect() + // // // todo!() + // // }, + // // expected_staged_ledger_merkle_root: expected_staged_ledger_merkle_root.into(), + // // }; + + // // let mut file = std::fs::File::create("/tmp/state.bin").unwrap(); + // // BinProtWrite::binprot_write(&state, &mut file).unwrap(); + // // file.sync_all().unwrap(); + + // // eprintln!("data saved"); + // // } + + // let mut new_staged_ledger = new_staged_ledger.unwrap(); + + // assert_eq!(expected_hash, sl.hash()); + // assert_eq!(expected_hash, new_staged_ledger.hash()); + // eprintln!("#######################################################"); + // eprintln!("of_scan_state_pending_coinbases_and_snarked_ledger OK"); + // eprintln!("#######################################################"); + // } + + #[coverage(off)] + fn diagnostic(&self, applied: &impl Debug, applied_ocaml: &impl Debug) -> String { + use text_diff::{diff, Difference}; + + let orig = format!("{:#?}", applied); + let edit = format!("{:#?}", applied_ocaml); + let split = " "; + let (_, changeset) = diff(orig.as_str(), edit.as_str(), split); + + let mut ret = String::new(); + + for seq in changeset { + match seq { + Difference::Same(ref x) => { + ret.push_str(x); + ret.push_str(split); + } + Difference::Add(ref x) => { + ret.push_str("\x1B[92m"); + ret.push_str(x); + ret.push_str("\x1B[0m"); + ret.push_str(split); + } + Difference::Rem(ref x) => { + ret.push_str("\x1B[91m"); + ret.push_str(x); + ret.push_str("\x1B[0m"); + ret.push_str(split); + } + } + } + + ret + } + + #[coverage(off)] + pub fn apply_transaction( + &mut self, + user_command: &UserCommand, + expected_apply_result: &ApplyTxResult, + ) -> Result<(), ()> { + self.gen.nonces.clear(); + + let mut ledger = self.get_ledger_inner().make_child(); + + // If we called apply_transaction it means we passed the tx pool check, so add tx to the cache + if let UserCommand::ZkAppCommand(command) = user_command { + if !command.account_updates.is_empty() { + //println!("Storing in pool cache {:?}", tx); + self.state.cache_pool.push_back(user_command.clone()); + } + } + + //println!("tx: {:?}\n", tx); + let tx = Transaction::Command(user_command.clone()); + + let applied = apply_transactions( + &self.constraint_constants, + self.txn_state_view.global_slot_since_genesis, + &self.txn_state_view, + &mut ledger, + &[tx.clone()], + ); + + // println!( + // "tx: {:?}\n applied: {:?}\n expected: {:?}", + // tx, applied, expected_apply_result + // ); + + match applied { + Ok(applied) => { + // For now we work with one transaction at a time + let applied = &applied[0]; + + if expected_apply_result.apply_result.len() != 1 { + println!( + "!!! Apply failed in OCaml (error: {}) but it didn't in Rust: {:?}", + expected_apply_result.error, applied + ); + let bigint: num_bigint::BigUint = LedgerIntf::merkle_root(&mut ledger).into(); + self.save_fuzzcase(&tx, &bigint.to_string()); + return Err(()); + } else { + if applied != &expected_apply_result.apply_result[0] { + println!( + "!!! Apply result mismatch between OCaml and Rust\n{}\n", + self.diagnostic(applied, &expected_apply_result.apply_result[0]) + ); + + let bigint: num_bigint::BigUint = + LedgerIntf::merkle_root(&mut ledger).into(); + self.save_fuzzcase(&tx, &bigint.to_string()); + return Err(()); + } + } + + // Save applied transactions in the cache for later use (mutation) + if *applied.transaction_status() == TransactionStatus::Applied { + if let UserCommand::ZkAppCommand(command) = user_command { + if !command.account_updates.is_empty() { + //println!("Storing in apply cache {:?}", tx); + self.state.cache_apply.push_back(user_command.clone()); + } + } + } else { + //println!("{:?}", applied.transaction_status()); + } + + // Add new accounts created by the transaction to the potential senders list + let new_accounts = match &applied.varying { + Varying::Command(command) => match command { + CommandApplied::SignedCommand(cmd) => match &cmd.body { + signed_command_applied::Body::Payments { new_accounts } => { + Some(new_accounts) + } + _ => None, + }, + CommandApplied::ZkappCommand(cmd) => Some(&cmd.new_accounts), + }, + _ => unimplemented!(), + }; + + if let Some(new_accounts) = new_accounts { + let new_accounts = self.state.potential_new_accounts.iter().filter( + #[coverage(off)] + |(kp, _)| { + new_accounts.iter().any( + #[coverage(off)] + |acc| acc.public_key == kp.public.into_compressed(), + ) + }, + ); + + for acc in new_accounts { + if !self.state.potential_senders.iter().any( + #[coverage(off)] + |(kp, _)| kp.public == acc.0.public, + ) { + self.state.potential_senders.push(acc.clone()) + } + } + + self.state.potential_new_accounts.clear(); + } + } + Err(error_string) => { + // Currently disabled until invariants are fixed + if error_string.starts_with("Invariant violation") { + let bigint: num_bigint::BigUint = LedgerIntf::merkle_root(&mut ledger).into(); + self.save_fuzzcase(&tx, &bigint.to_string()); + return Err(()); + } + + if expected_apply_result.apply_result.len() == 1 { + println!( + "!!! Apply failed in Rust (error: {}) but it didn't in OCaml: {:?}", + error_string, &expected_apply_result.apply_result[0] + ); + let bigint: num_bigint::BigUint = LedgerIntf::merkle_root(&mut ledger).into(); + self.save_fuzzcase(&tx, &bigint.to_string()); + return Err(()); + } + } + } + + let rust_ledger_root_hash = LedgerIntf::merkle_root(&mut ledger); + + if &expected_apply_result.root_hash != &rust_ledger_root_hash { + println!( + "Ledger hash mismatch: {:?} != {:?} (expected)", + rust_ledger_root_hash, expected_apply_result.root_hash + ); + let bigint: num_bigint::BigUint = rust_ledger_root_hash.into(); + self.save_fuzzcase(&tx, &bigint.to_string()); + Err(()) + } else { + ledger.commit(); + Ok(()) + } + } + + #[coverage(off)] + pub fn get_ledger_root(&mut self) -> Fp { + LedgerIntf::merkle_root(self.get_ledger_inner_mut()) + } + + #[coverage(off)] + pub fn get_ledger_accounts(&self) -> Vec { + let locations = self.get_ledger_inner().account_locations(); + locations + .iter() + .map( + #[coverage(off)] + |x| *(LedgerIntf::get(self.get_ledger_inner(), x).unwrap()), + ) + .collect() + } +} + +pub struct FuzzerCtxBuilder { + constraint_constants: Option, + txn_state_view: Option, + fuzzcases_path: Option, + seed: u64, + minimum_fee: u64, + max_account_balance: u64, + initial_accounts: usize, + cache_size: usize, + snapshots_size: usize, + is_staged_ledger: bool, +} + +impl FuzzerCtxBuilder { + #[coverage(off)] + pub fn new() -> Self { + Self { + constraint_constants: None, + txn_state_view: None, + fuzzcases_path: None, + seed: 0, + minimum_fee: 1_000_000, // Sane default in case we don't obtain it from OCaml + max_account_balance: 1_000_000_000_000_000, + initial_accounts: 10, + cache_size: 4096, + snapshots_size: 128, + is_staged_ledger: false, + } + } + + #[coverage(off)] + pub fn constants(&mut self, constraint_constants: ConstraintConstants) -> &mut Self { + self.constraint_constants = Some(constraint_constants); + self + } + + #[coverage(off)] + pub fn state_view(&mut self, txn_state_view: ProtocolStateView) -> &mut Self { + self.txn_state_view = Some(txn_state_view); + self + } + + #[coverage(off)] + pub fn fuzzcases_path(&mut self, fuzzcases_path: String) -> &mut Self { + self.fuzzcases_path = Some(fuzzcases_path); + self + } + + #[coverage(off)] + pub fn seed(&mut self, seed: u64) -> &mut Self { + self.seed = seed; + self + } + + #[coverage(off)] + pub fn minimum_fee(&mut self, minimum_fee: u64) -> &mut Self { + self.minimum_fee = minimum_fee; + self + } + + #[coverage(off)] + pub fn initial_accounts(&mut self, initial_accounts: usize) -> &mut Self { + self.initial_accounts = initial_accounts; + self + } + + #[coverage(off)] + pub fn cache_size(&mut self, cache_size: usize) -> &mut Self { + assert!(cache_size != 0 && cache_size.is_power_of_two()); + self.cache_size = cache_size; + self + } + + #[coverage(off)] + pub fn snapshots_size(&mut self, snapshots_size: usize) -> &mut Self { + assert!(snapshots_size != 0 && snapshots_size.is_power_of_two()); + self.snapshots_size = snapshots_size; + self + } + + #[coverage(off)] + pub fn is_staged_ledger(&mut self, is_staged_ledger: bool) -> &mut Self { + self.is_staged_ledger = is_staged_ledger; + self + } + + #[coverage(off)] + pub fn build(&mut self) -> FuzzerCtx { + let constraint_constants = self + .constraint_constants + .clone() + .unwrap_or(CONSTRAINT_CONSTANTS); + let depth = constraint_constants.ledger_depth as usize; + let root = Mask::new_root(Database::create(depth.try_into().unwrap())); + let txn_state_view = self + .txn_state_view + .clone() + .unwrap_or(dummy_state_view(None)); + let fuzzcases_path = self.fuzzcases_path.clone().unwrap_or("./".to_string()); + + let ledger = match self.is_staged_ledger { + true => { + let snarked_ledger_mask = root.make_child().fuzzing_to_root(); + // let snarked_ledger_mask = root.make_child(); + LedgerKind::Staged( + StagedLedger::create_exn(constraint_constants.clone(), root.make_child()) + .unwrap(), + snarked_ledger_mask, + ) + } + false => LedgerKind::Mask(root.make_child()), + }; + + let mut ctx = FuzzerCtx { + constraint_constants, + txn_state_view, + fuzzcases_path, + gen: GeneratorCtx { + rng: SmallRng::seed_from_u64(self.seed), + minimum_fee: self.minimum_fee, + excess_fee: Signed::::zero(), + token_id: TokenId::default(), + tx_proof: None, + max_account_balance: self.max_account_balance, + nonces: HashMap::new(), + attempt_valid_zkapp: true, + }, + state: FuzzerState { + ledger, + potential_senders: Vec::new(), + potential_new_accounts: Vec::new(), + cache_pool: RingBuffer::with_capacity(self.cache_size), + cache_apply: RingBuffer::with_capacity(self.cache_size), + }, + snapshots: RingBuffer::with_capacity(self.snapshots_size), + }; + + ctx.create_inital_accounts(self.initial_accounts); + ctx + } +} diff --git a/tools/fuzzing/src/transaction_fuzzer/coverage/cov.rs b/tools/fuzzing/src/transaction_fuzzer/coverage/cov.rs new file mode 100644 index 000000000..001bf0456 --- /dev/null +++ b/tools/fuzzing/src/transaction_fuzzer/coverage/cov.rs @@ -0,0 +1,237 @@ +use core::slice; +use std::io::Cursor; + +use super::{ + covfun::{Counter, CounterExpression, FunCov, Header, Region, SourceRange}, + covmap::CovMap, + names::Names, + profile_data::ProfileData, + util::{get_counters, get_elf_sections, get_module_path}, +}; + +//#[derive(Debug)] +pub struct Sections { + pub covmap: Vec, + pub covfun: Vec, +} + +impl Sections { + #[coverage(off)] + pub fn new() -> Self { + let section_names = vec!["__llvm_covmap", "__llvm_covfun"]; + let sections = get_elf_sections(get_module_path(), §ion_names); + let covmap = §ions["__llvm_covmap"]; + let covmap_len = covmap.len() as u64; + let mut covmap_cursor = Cursor::new(covmap); + let mut covmap = Vec::new(); + + while covmap_cursor.position() < covmap_len { + covmap.push(CovMap::read(&mut covmap_cursor)); + } + + let covfun = §ions["__llvm_covfun"]; + let covfun_len = covfun.len() as u64; + let mut covfun_cursor = Cursor::new(covfun); + let mut covfun = Vec::new(); + + while covfun_cursor.position() < covfun_len { + covfun.push(FunCov::read(&mut covfun_cursor)); + } + + Self { covmap, covfun } + } +} + +#[derive(Debug, Clone)] +pub struct FunCounters { + pub name_hash: u64, + pub fun_hash: u64, + pub counters: &'static [i64], +} + +#[derive(Debug, Clone)] +pub struct FileCounters { + pub filename: String, + pub counters: Vec<&'static [i64]>, +} + +//#[derive(Debug)] +pub struct FileDump { + pub filename: String, + pub source_counters_vec: Vec>, +} + +pub struct Cov { + pub counters: &'static mut [i64], + pub names: Names, + pub data: ProfileData, + pub sections: Sections, +} + +impl Cov { + #[coverage(off)] + pub fn new() -> Self { + Self { + counters: unsafe { get_counters() }, + names: Names::new(), + data: ProfileData::new(), + sections: Sections::new(), + } + } + + #[coverage(off)] + pub fn get_functions_counters(&mut self) -> Vec { + let mut pos = 0; + self.data + .0 + .iter() + .filter_map( + #[coverage(off)] + |fun_control| { + if fun_control.func_hash == 0 { + None + } else { + let fun_counters = FunCounters { + name_hash: fun_control.name_hash, + fun_hash: fun_control.func_hash, + counters: unsafe { + slice::from_raw_parts( + self.counters[pos as usize..].as_ptr(), + fun_control.num_counters as usize, + ) + }, + }; + + assert_ne!(fun_counters.counters.len(), 0); + + pos += fun_control.num_counters; + Some(fun_counters) + } + }, + ) + .collect() + } + + #[coverage(off)] + fn for_file_cov(&mut self, mut f: F) + where + F: FnMut(String, &'static [i64], &Vec, &Vec), + { + let mut fun_counters_vec = self.get_functions_counters(); + + for covmap in self.sections.covmap.iter() { + for funcov in self.sections.covfun.iter() { + let record = &funcov.function_record; + + if record.translation_unit_hash == covmap.encoded_data_hash { + for (index, regions) in funcov.mapping_regions.iter() { + let filename = covmap.filenames.0[*index as usize].clone(); + + let pos = fun_counters_vec.iter().position( + #[coverage(off)] + |fc| { + fc.name_hash == record.name_hash && fc.fun_hash == record.func_hash + }, + ); + + if pos.is_none() { + continue; + } + + let fun_counters = fun_counters_vec.swap_remove(pos.unwrap()); + + f( + filename, + fun_counters.counters, + regions, + &funcov.expressions, + ) + } + } + } + } + } + + #[coverage(off)] + pub fn get_file_counters(&mut self) -> Vec { + let mut result: Vec = Vec::new(); + + self.for_file_cov( + #[coverage(off)] + |filename, counters, _, _| match result.iter_mut().find( + #[coverage(off)] + |c| c.filename == filename, + ) { + Some(existing_counters) => { + existing_counters.counters.push(counters); + } + None => { + let file_counters = FileCounters { + filename, + counters: vec![counters], + }; + + result.push(file_counters); + } + }, + ); + + result + } + + #[coverage(off)] + pub fn dump(&mut self) -> Vec { + let mut result: Vec = Vec::new(); + + self.for_file_cov( + #[coverage(off)] + |filename, counters, regions, expressions| { + let mut source_counters: Vec<(i64, SourceRange)> = Vec::new(); + + for region in regions.iter() { + let counter = match ®ion.header { + Header::Counter(counter) => match counter { + Counter::Zero => 0, + Counter::Reference(idx) => counters[*idx], + Counter::Substraction(idx) => { + let expr = &expressions[*idx]; + expr.resolve_sub(counters, expressions) + } + Counter::Addition(idx) => { + let expr = &expressions[*idx]; + expr.resolve_add(counters, expressions) + } + }, + Header::PseudoCounter(_pseudo_counter) => continue, // TODO + }; + + /* + FIXME?: in some cases I observe negative values + + SubExpr => CounterExpression { lhs: Reference(0), rhs: Reference(1) } + counter => -1632 + */ + //println!("counter => {:?}", counter); + source_counters.push((counter, region.source_range.clone())); + } + + match result.iter_mut().find( + #[coverage(off)] + |c| c.filename == filename, + ) { + Some(existing_file_dump) => { + existing_file_dump.source_counters_vec.push(source_counters); + } + None => { + result.push(FileDump { + filename, + source_counters_vec: vec![source_counters], + }); + } + } + }, + ); + + result + } +} diff --git a/tools/fuzzing/src/transaction_fuzzer/coverage/covfun.rs b/tools/fuzzing/src/transaction_fuzzer/coverage/covfun.rs new file mode 100644 index 000000000..499e34a93 --- /dev/null +++ b/tools/fuzzing/src/transaction_fuzzer/coverage/covfun.rs @@ -0,0 +1,248 @@ +use super::util::{cursor_align, read_int, Leb128}; +use std::io::Cursor; + +//#[derive(Debug)] +pub struct Record { + pub name_hash: u64, + pub data_len: u32, + pub func_hash: u64, + pub translation_unit_hash: u64, +} + +impl Record { + #[coverage(off)] + pub fn read(cursor: &mut Cursor<&Vec>) -> Self { + Self { + name_hash: read_int(cursor), + data_len: read_int(cursor), + func_hash: read_int(cursor), + translation_unit_hash: read_int(cursor), + } + } +} + +//#[derive(Debug)] +pub enum PseudoCounter { + ExpansionRegion(u64), + CodeRegion, + SkippedRegion, + BranchRegion, +} + +impl PseudoCounter { + #[coverage(off)] + pub fn new(value: u64) -> Self { + let data = value >> 1; + + match value & 1 { + 0 => match data { + 0 => Self::CodeRegion, + 2 => Self::SkippedRegion, + 4 => Self::BranchRegion, + _ => panic!(), + }, + _ => Self::ExpansionRegion(data), + } + } +} + +//#[derive(Debug)] +pub enum Counter { + Zero, + Reference(usize), + Substraction(usize), + Addition(usize), +} + +impl Counter { + #[coverage(off)] + pub fn new(value: u64) -> Self { + let idx = (value >> 2) as usize; + + match value & 0b11 { + 0 => Self::Zero, + 1 => Self::Reference(idx), + 2 => Self::Substraction(idx), + 3 => Self::Addition(idx), + _ => unreachable!(), + } + } + + #[coverage(off)] + pub fn read(cursor: &mut Cursor<&Vec>) -> Self { + Counter::new(u64::read_leb128(cursor)) + } +} + +//#[derive(Debug)] +pub enum Header { + PseudoCounter(PseudoCounter), + Counter(Counter), +} + +impl Header { + #[coverage(off)] + pub fn read(cursor: &mut Cursor<&Vec>) -> Self { + let header = u64::read_leb128(cursor); + let data = header >> 2; + + match header & 0b11 { + 0 => Self::PseudoCounter(PseudoCounter::new(data)), + _ => Self::Counter(Counter::new(header)), + } + } +} + +//#[derive(Debug)] +pub struct CounterExpression { + pub lhs: Counter, + pub rhs: Counter, +} + +impl CounterExpression { + #[coverage(off)] + pub fn read(cursor: &mut Cursor<&Vec>) -> Self { + Self { + lhs: Counter::read(cursor), + rhs: Counter::read(cursor), + } + } + + #[coverage(off)] + fn resolve_op(op: &Counter, counters: &'static [i64], expressions: &Vec) -> i64 { + match op { + Counter::Zero => 0, + Counter::Reference(idx) => counters[*idx], + Counter::Substraction(idx) => expressions[*idx].resolve_sub(counters, expressions), + Counter::Addition(idx) => expressions[*idx].resolve_add(counters, expressions), + } + } + + #[coverage(off)] + pub fn resolve_sub(&self, counters: &'static [i64], expressions: &Vec) -> i64 { + let lhs = Self::resolve_op(&self.lhs, counters, expressions); + let rhs = Self::resolve_op(&self.rhs, counters, expressions); + //println!("sub {} {}", lhs, rhs); + lhs.wrapping_sub(rhs) + } + + #[coverage(off)] + pub fn resolve_add(&self, counters: &'static [i64], expressions: &Vec) -> i64 { + let lhs = Self::resolve_op(&self.lhs, counters, expressions); + let rhs = Self::resolve_op(&self.rhs, counters, expressions); + //println!("add {} {}", lhs, rhs); + lhs.wrapping_add(rhs) + } +} + +//#[derive(Debug, Clone)] +pub struct SourceRange { + pub delta_line_start: usize, + pub column_start: usize, + pub num_lines: usize, + pub column_end: usize, +} + +impl Clone for SourceRange { + #[coverage(off)] + fn clone(&self) -> Self { + Self { + delta_line_start: self.delta_line_start, + column_start: self.column_start, + num_lines: self.num_lines, + column_end: self.column_end, + } + } +} + +impl SourceRange { + #[coverage(off)] + pub fn read(cursor: &mut Cursor<&Vec>) -> Self { + Self { + delta_line_start: usize::read_leb128(cursor), + column_start: usize::read_leb128(cursor), + num_lines: usize::read_leb128(cursor), + column_end: usize::read_leb128(cursor), + } + } +} + +//#[derive(Debug)] +pub struct Region { + pub header: Header, + pub source_range: SourceRange, +} + +impl Region { + #[coverage(off)] + pub fn read(cursor: &mut Cursor<&Vec>) -> Self { + Self { + header: Header::read(cursor), + source_range: SourceRange::read(cursor), + } + } +} + +//#[derive(Debug)] +pub struct FunCov { + pub function_record: Record, + pub expressions: Vec, + pub mapping_regions: Vec<(u64, Vec)>, +} + +impl FunCov { + #[coverage(off)] + pub fn read(cursor: &mut Cursor<&Vec>) -> Self { + let function_record = Record::read(cursor); + //let data_pos = cursor.position(); + + assert!(function_record.data_len > 0); + // numIndices : LEB128, filenameIndex0 : LEB128, filenameIndex1 : LEB128... + let num_indices = u64::read_leb128(cursor); + let file_id_mapping: Vec = (0..num_indices) + .map( + #[coverage(off)] + |_| u64::read_leb128(cursor), + ) + .collect(); + + // numExpressions : LEB128, expr0LHS : LEB128, expr0RHS : LEB128, expr1LHS : LEB128, expr1RHS : LEB128... + let num_expressions = u64::read_leb128(cursor); + let expressions: Vec = (0..num_expressions) + .map( + #[coverage(off)] + |_| CounterExpression::read(cursor), + ) + .collect(); + + // [numRegionArrays : LEB128, regionsForFile0, regionsForFile1, ...] + // Not actually included? + // let num_region_arrays = leb128::read::unsigned(&mut covfun_cursor).unwrap(); + + let mapping_regions: Vec<(u64, Vec)> = file_id_mapping + .iter() + .map( + #[coverage(off)] + |index| { + // [numRegions : LEB128, region0, region1, ...] + let num_regions = u64::read_leb128(cursor); + let regions: Vec = (0..num_regions) + .map( + #[coverage(off)] + |_| Region::read(cursor), + ) + .collect(); + (*index, regions) + }, + ) + .collect(); + + cursor_align::(cursor); + + Self { + function_record, + expressions, + mapping_regions, + } + } +} diff --git a/tools/fuzzing/src/transaction_fuzzer/coverage/covmap.rs b/tools/fuzzing/src/transaction_fuzzer/coverage/covmap.rs new file mode 100644 index 000000000..64ba54426 --- /dev/null +++ b/tools/fuzzing/src/transaction_fuzzer/coverage/covmap.rs @@ -0,0 +1,99 @@ +use super::util::{cursor_align, read_int, Leb128}; +use std::io::{Cursor, Read, Seek, SeekFrom}; + +//#[derive(Debug)] +#[allow(dead_code)] +pub struct Header { + zero: u32, + encoded_filenames_len: u32, + zero2: u32, + version: u32, +} + +impl Header { + #[coverage(off)] + pub fn read(cursor: &mut Cursor<&Vec>) -> Self { + let header = Self { + zero: read_int(cursor), + encoded_filenames_len: read_int(cursor), + zero2: read_int(cursor), + version: read_int(cursor), + }; + + assert!(header.zero == 0 && header.zero2 == 0); + header + } +} + +//#[derive(Debug)] +pub struct Filenames(pub Vec); + +impl Filenames { + #[coverage(off)] + pub fn read(cursor: &mut Cursor<&Vec>) -> Self { + let num_filenames = u64::read_leb128(cursor); + let uncompressed_len = usize::read_leb128(cursor); + let compressed_len = usize::read_leb128(cursor); + let pos = cursor.position() as usize; + + let mut filenames = Vec::new(); + let mut decompressed_buf = vec![0; uncompressed_len]; + let buf = if compressed_len != 0 { + flate2::Decompress::new(true) + .decompress( + &cursor.get_ref()[pos..pos + compressed_len], + &mut decompressed_buf[..], + flate2::FlushDecompress::Finish, + ) + .unwrap(); + decompressed_buf + } else { + cursor.get_ref()[pos..pos + uncompressed_len].to_vec() + }; + + let mut cursor = Cursor::new(&buf); + + while (cursor.position() as usize) < uncompressed_len { + filenames.push(Self::read_filename(&mut cursor)); + } + + assert!(filenames.len() == num_filenames as usize); + Self(filenames) + } + + #[coverage(off)] + fn read_filename(cursor: &mut Cursor<&Vec>) -> String { + let string_len = usize::read_leb128(cursor); + let mut output = vec![0; string_len]; + cursor.read_exact(output.as_mut_slice()).unwrap(); + String::from_utf8(output).unwrap() + } +} + +//#[derive(Debug)] +#[allow(dead_code)] +pub struct CovMap { + header: Header, + pub encoded_data_hash: u64, + pub filenames: Filenames, +} + +impl CovMap { + #[coverage(off)] + pub fn read(cursor: &mut Cursor<&Vec>) -> Self { + let header = Header::read(cursor); + let pos = cursor.position() as usize; + let end = pos + header.encoded_filenames_len as usize; + let digest = md5::compute(&cursor.get_ref()[pos..end]).0; + let encoded_data_hash: u64 = read_int(&mut Cursor::new(digest)); + let filenames = Filenames::read(cursor); + cursor.seek(SeekFrom::Start(end as u64)).unwrap(); + cursor_align::(cursor); + + Self { + header, + encoded_data_hash, + filenames, + } + } +} diff --git a/tools/fuzzing/src/transaction_fuzzer/coverage/mod.rs b/tools/fuzzing/src/transaction_fuzzer/coverage/mod.rs new file mode 100644 index 000000000..334455e12 --- /dev/null +++ b/tools/fuzzing/src/transaction_fuzzer/coverage/mod.rs @@ -0,0 +1,8 @@ +pub mod cov; +mod covfun; +mod covmap; +mod names; +mod profile_data; +pub mod reports; +pub mod stats; +mod util; diff --git a/tools/fuzzing/src/transaction_fuzzer/coverage/names.rs b/tools/fuzzing/src/transaction_fuzzer/coverage/names.rs new file mode 100644 index 000000000..814e980ae --- /dev/null +++ b/tools/fuzzing/src/transaction_fuzzer/coverage/names.rs @@ -0,0 +1,74 @@ +use std::io::{BufRead, Cursor, Seek, SeekFrom}; + +use super::util::{get_names, Leb128}; + +//#[derive(Debug)] +#[allow(dead_code)] +pub struct Names(Vec); + +impl Names { + #[coverage(off)] + pub fn new() -> Self { + let names_buf = unsafe { get_names() }.to_vec(); + let mut cursor = Cursor::new(&names_buf); + let mut output = Vec::new(); + let mut pos = cursor.position(); + let names_buf_len = names_buf.len() as u64; + + while pos < names_buf_len { + let uncompressed_len = u64::read_leb128(&mut cursor); + let compressed_len = u64::read_leb128(&mut cursor); + + pos = cursor.position(); + let start = pos as usize; + + if compressed_len == 0 { + let end = start + uncompressed_len as usize; + Self::read_names(&names_buf[start..end], &mut output); + pos += uncompressed_len; + } else { + let end = start + compressed_len as usize; + let decompressed_buf = + Self::decompress(&names_buf[start..end], uncompressed_len as usize); + Self::read_names(decompressed_buf.as_slice(), &mut output); + pos += compressed_len; + } + + cursor.seek(SeekFrom::Start(pos)).unwrap(); + } + + Self(output) + } + + #[coverage(off)] + fn read_names(names: &[u8], output: &mut Vec) { + let mut names = Cursor::new(names); + + loop { + let mut name = Vec::new(); + + if names.read_until(0x01, &mut name).unwrap() == 0 { + return; + } + + if *name.last().unwrap() == 0x01 { + name.pop(); + } + + output.push(String::from_utf8(name).unwrap()); + } + } + + #[coverage(off)] + fn decompress(compressed_buf: &[u8], uncompressed_len: usize) -> Vec { + let mut decompressed_buf = vec![0; uncompressed_len]; + flate2::Decompress::new(true) + .decompress( + compressed_buf, + &mut decompressed_buf, + flate2::FlushDecompress::Finish, + ) + .unwrap(); + decompressed_buf + } +} diff --git a/tools/fuzzing/src/transaction_fuzzer/coverage/profile_data.rs b/tools/fuzzing/src/transaction_fuzzer/coverage/profile_data.rs new file mode 100644 index 000000000..e139621ca --- /dev/null +++ b/tools/fuzzing/src/transaction_fuzzer/coverage/profile_data.rs @@ -0,0 +1,58 @@ +use super::util::{cursor_align, get_data, read_int}; +use std::{io::Cursor, mem}; + +// From LLVM's compiler-rt/include/profile/InstrProfData.inc +#[repr(packed)] +#[derive(Debug)] +pub struct FunControl { + pub name_hash: u64, + pub func_hash: u64, + pub relative_counter_ptr: u64, + pub relative_bitmap_ptr: u64, + pub function_ptr: u64, + pub values_ptr: u64, + pub num_counters: u32, + pub num_value_sites: u16, + pub num_bitmap_bytes: u32, +} + +impl FunControl { + #[coverage(off)] + pub fn read(cursor: &mut Cursor<&Vec>) -> Self { + Self { + name_hash: read_int(cursor), + func_hash: read_int(cursor), + relative_counter_ptr: read_int(cursor), + relative_bitmap_ptr: read_int(cursor), + function_ptr: read_int(cursor), + values_ptr: read_int(cursor), + num_counters: read_int(cursor), + num_value_sites: read_int(cursor), + num_bitmap_bytes: read_int(cursor), + } + } + + #[coverage(off)] + pub fn size() -> usize { + mem::size_of::() + } +} + +#[derive(Debug)] +pub struct ProfileData(pub Vec); + +impl ProfileData { + #[coverage(off)] + pub fn new() -> Self { + let data_buf = unsafe { get_data() }.to_vec(); + let mut cursor = Cursor::new(&data_buf); + let mut output = Vec::new(); + + while (cursor.position() as usize + FunControl::size()) < data_buf.len() { + output.push(FunControl::read(&mut cursor)); + cursor_align::(&mut cursor); + } + + Self(output) + } +} diff --git a/tools/fuzzing/src/transaction_fuzzer/coverage/reports.rs b/tools/fuzzing/src/transaction_fuzzer/coverage/reports.rs new file mode 100644 index 000000000..3364c1013 --- /dev/null +++ b/tools/fuzzing/src/transaction_fuzzer/coverage/reports.rs @@ -0,0 +1,642 @@ +use super::cov::FileDump; +use itertools::Itertools; +use std::fs::File; +use std::io::{BufRead, BufReader, Write}; +use std::{env, fmt, fs}; + +//#[derive(Debug, Clone, Serialize)] +pub struct LineCounter { + pub col_start: usize, + pub col_end: usize, + pub count: i64, +} + +impl Clone for LineCounter { + #[coverage(off)] + fn clone(&self) -> Self { + Self { + col_start: self.col_start, + col_end: self.col_end, + count: self.count, + } + } +} + +impl serde::Serialize for LineCounter { + #[coverage(off)] + fn serialize<__S>(&self, __serializer: __S) -> serde::__private::Result<__S::Ok, __S::Error> + where + __S: serde::Serializer, + { + let mut __serde_state = match serde::Serializer::serialize_struct( + __serializer, + "LineCounter", + false as usize + 1 + 1 + 1, + ) { + serde::__private::Ok(__val) => __val, + serde::__private::Err(__err) => { + return serde::__private::Err(__err); + } + }; + match serde::ser::SerializeStruct::serialize_field( + &mut __serde_state, + "col_start", + &self.col_start, + ) { + serde::__private::Ok(__val) => __val, + serde::__private::Err(__err) => { + return serde::__private::Err(__err); + } + }; + match serde::ser::SerializeStruct::serialize_field( + &mut __serde_state, + "col_end", + &self.col_end, + ) { + serde::__private::Ok(__val) => __val, + serde::__private::Err(__err) => { + return serde::__private::Err(__err); + } + }; + match serde::ser::SerializeStruct::serialize_field(&mut __serde_state, "count", &self.count) + { + serde::__private::Ok(__val) => __val, + serde::__private::Err(__err) => { + return serde::__private::Err(__err); + } + }; + serde::ser::SerializeStruct::end(__serde_state) + } +} + +impl LineCounter { + #[coverage(off)] + fn overlap(&self, rhs: &Self) -> bool { + self.col_start < rhs.col_end && rhs.col_start < self.col_end + } + + #[coverage(off)] + fn contains(&self, rhs: &Self) -> bool { + self.col_start < rhs.col_start && self.col_end > rhs.col_end + } + + #[coverage(off)] + fn merge(&self, rhs: &Self, count: i64) -> Self { + Self { + col_start: self.col_start.min(rhs.col_start), + col_end: self.col_end.max(rhs.col_end), + count, + } + } + + #[coverage(off)] + fn split3(&self, rhs: &Self) -> Vec { + vec![ + Self { + col_start: self.col_start, + col_end: rhs.col_start, + count: self.count, + }, + Self { + col_start: rhs.col_start, + col_end: rhs.col_end, + count: rhs.count, + }, + Self { + col_start: rhs.col_end, + col_end: self.col_end, + count: self.count, + }, + ] + } + + #[coverage(off)] + fn split2(&self, rhs: &Self) -> Vec { + vec![ + Self { + col_start: self.col_start, + col_end: rhs.col_start, + count: self.count, + }, + Self { + col_start: rhs.col_start, + col_end: rhs.col_end, + count: rhs.count, + }, + ] + } + + #[coverage(off)] + fn split(&self, rhs: &Self) -> Vec { + if self.contains(&rhs) { + self.split3(&rhs) + } else if rhs.contains(self) { + rhs.split3(self) + } else { + if self.col_start < rhs.col_start { + self.split2(rhs) + } else { + rhs.split2(self) + } + } + } + + #[coverage(off)] + pub fn join(&self, rhs: &Self) -> Option> { + if self.overlap(rhs) { + if self.count == rhs.count { + Some(vec![self.merge(rhs, self.count)]) + } else { + if self.col_start == rhs.col_start && self.col_end == rhs.col_end { + Some(vec![self.merge(rhs, self.count.max(rhs.count))]) + } else { + Some(self.split(rhs)) + } + } + } else { + None + } + } +} + +#[coverage(off)] +fn color_line_counters(line: &str, counters: &Vec) -> String { + let mut result = String::new(); + + if counters.is_empty() { + return line.to_string(); + } + + let mut line_color = 43; // light yellow + + for counter in counters.iter() { + if counter.count > 0 { + if line_color == 41 { + line_color = 43; + break; + } + + line_color = 42; // light green + } else { + if line_color == 42 { + line_color = 43; + break; + } + + line_color = 41; // light red + } + } + + result.push_str(&format!("\x1b[1;{}m\x1b[1;37m", line_color)); + + for (column, c) in line.chars().enumerate() { + let counter = counters.iter().find( + #[coverage(off)] + |LineCounter { + col_start, col_end, .. + }| *col_start <= column && *col_end >= column, + ); + + if let Some(counter) = counter { + if column == counter.col_start { + let color_code: u8 = if counter.count == 0 { 101 } else { 102 }; + result.push_str(&format!("\x1b[1;{}m\x1b[1;{}m", line_color, color_code)); + } + } + + result.push(c); + + if let Some(counter) = counter { + // avoid reset colors if there is another counter + if column == counter.col_end + && (counter.col_start == counter.col_end + || counters + .iter() + .find( + #[coverage(off)] + |LineCounter { col_start, .. }| *col_start == column, + ) + .is_none()) + { + result.push_str(&format!("\x1b[1;{}m\x1b[1;37m", line_color)); + } + } + } + + result.push_str("\x1b[0m"); + result +} + +//#[derive(Debug, Serialize)] +pub struct LineCoverage { + pub line: String, + pub counters: Vec, +} + +impl serde::Serialize for LineCoverage { + #[coverage(off)] + fn serialize<__S>(&self, __serializer: __S) -> serde::__private::Result<__S::Ok, __S::Error> + where + __S: serde::Serializer, + { + let mut __serde_state = match serde::Serializer::serialize_struct( + __serializer, + "LineCoverage", + false as usize + 1 + 1, + ) { + serde::__private::Ok(__val) => __val, + serde::__private::Err(__err) => { + return serde::__private::Err(__err); + } + }; + match serde::ser::SerializeStruct::serialize_field(&mut __serde_state, "line", &self.line) { + serde::__private::Ok(__val) => __val, + serde::__private::Err(__err) => { + return serde::__private::Err(__err); + } + }; + match serde::ser::SerializeStruct::serialize_field( + &mut __serde_state, + "counters", + &self.counters, + ) { + serde::__private::Ok(__val) => __val, + serde::__private::Err(__err) => { + return serde::__private::Err(__err); + } + }; + serde::ser::SerializeStruct::end(__serde_state) + } +} + +impl fmt::Display for LineCoverage { + #[coverage(off)] + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", color_line_counters(&self.line, &self.counters)) + } +} + +//#[derive(Debug, Serialize)] +pub struct FileCoverage { + pub filename: String, + pub lines: Vec, +} + +impl serde::Serialize for FileCoverage { + #[coverage(off)] + fn serialize<__S>(&self, __serializer: __S) -> serde::__private::Result<__S::Ok, __S::Error> + where + __S: serde::Serializer, + { + let mut __serde_state = match serde::Serializer::serialize_struct( + __serializer, + "FileCoverage", + false as usize + 1 + 1, + ) { + serde::__private::Ok(__val) => __val, + serde::__private::Err(__err) => { + return serde::__private::Err(__err); + } + }; + match serde::ser::SerializeStruct::serialize_field( + &mut __serde_state, + "filename", + &self.filename, + ) { + serde::__private::Ok(__val) => __val, + serde::__private::Err(__err) => { + return serde::__private::Err(__err); + } + }; + match serde::ser::SerializeStruct::serialize_field(&mut __serde_state, "lines", &self.lines) + { + serde::__private::Ok(__val) => __val, + serde::__private::Err(__err) => { + return serde::__private::Err(__err); + } + }; + serde::ser::SerializeStruct::end(__serde_state) + } +} + +impl FileCoverage { + #[coverage(off)] + pub fn covered_percent(&self) -> usize { + let mut total = 0; + let mut covered = 0; + + for line in self.lines.iter() { + total += line.counters.len(); + covered += line.counters.iter().fold( + 0, + #[coverage(off)] + |acc, lc| if lc.count != 0 { acc + 1 } else { acc }, + ); + } + + if total != 0 { + (covered * 100) / total + } else { + 0 + } + } +} + +impl fmt::Display for FileCoverage { + #[coverage(off)] + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}:\n", self.filename)?; + + for (i, line) in self.lines.iter().enumerate() { + write!(f, "{:>6}: {}\n", i, line)?; + } + + Ok(()) + } +} + +//#[derive(Debug)] +pub struct CoverageReport(Vec); + +impl fmt::Display for CoverageReport { + #[coverage(off)] + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + for file in self.0.iter() { + write!(f, "{}\n", file)?; + } + + Ok(()) + } +} + +impl CoverageReport { + #[coverage(off)] + pub fn write_files(&self, report_prefix: String) { + let sources_path = env::var("REPORTS_PATH").unwrap_or("/tmp/".to_string()); + let report_file_vec: Vec<(String, &FileCoverage)> = self + .0 + .iter() + .enumerate() + .map( + #[coverage(off)] + |(n, filecov)| (report_prefix.clone() + &n.to_string() + ".json", filecov), + ) + .collect(); + + let report_index: Vec<(String, usize, String)> = report_file_vec + .iter() + .map( + #[coverage(off)] + |(report_name, filecov)| { + ( + report_name.clone(), + filecov.covered_percent(), + filecov.filename.clone(), + ) + }, + ) + .collect(); + + let mut file = fs::OpenOptions::new() + .write(true) + .truncate(true) + .create(true) + .open(sources_path.clone() + &report_prefix + "index.json") + .unwrap(); + + file.write_all(serde_json::to_string(&report_index).unwrap().as_bytes()) + .unwrap(); + + for (report_name, filecov) in report_file_vec.iter() { + let mut file = fs::OpenOptions::new() + .write(true) + .truncate(true) + .create(true) + .open(sources_path.clone() + report_name) + .unwrap(); + + file.write_all(serde_json::to_string(filecov).unwrap().as_bytes()) + .unwrap(); + } + } + + #[coverage(off)] + fn merge_counters(counter: &LineCounter, counters: &mut Vec) { + for idx in 0.. { + if idx == counters.len() { + counters.push(counter.clone()); + break; + } + + if let Some(new_counters) = counters[idx].join(&counter) { + counters.remove(idx); + + for counter in new_counters.iter() { + Self::merge_counters(counter, counters); + } + + break; + } else { + if counters[idx].col_end > counter.col_end { + counters.insert(idx, counter.clone()); + break; + } + } + } + } + + #[coverage(off)] + pub fn from_llvm_dump(llvm_dump: &Vec) -> Self { + let sources_path = env::var("RUST_BUILD_PATH").unwrap_or_else(|_| { + env::current_dir() + .unwrap() + .parent() + .unwrap() + .parent() + .unwrap() + .to_str() + .unwrap() + .to_string() + }); + let mut file_coverage_vec = Vec::new(); + + for FileDump { + filename, + source_counters_vec, + } in llvm_dump.iter() + { + // Ignore external crates. Might want them back at some point? + if filename.starts_with("/rustc/") + || filename.contains("/.cargo/") + || filename.contains("/.rustup/") + { + continue; + } + + let path = if filename.starts_with("/") { + filename.clone() + } else { + sources_path.clone() + "/" + filename + }; + + let file = File::open(path).unwrap(); + let mut lines: Vec = BufReader::new(file) + .lines() + .map( + #[coverage(off)] + |line| LineCoverage { + line: line.unwrap(), + counters: Vec::new(), + }, + ) + .collect(); + + for source_counters in source_counters_vec.iter() { + let mut previous_line = 0; + + for (count, source_range) in source_counters.iter() { + let count = *count; + let start = previous_line + source_range.delta_line_start - 1; + let end = start + source_range.num_lines; + //println!("{:?}", source_range); + + for line in start..=end { + if line == lines.len() || lines[line].line.chars().count() == 0 { + continue; + } + + let col_start = if line == start { + source_range.column_start - 1 + } else { + 0 + }; + let mut col_end = if line == end { + source_range.column_end - 1 + } else { + lines[line].line.chars().count() + }; + + // Why can this happen? + if col_end < col_start { + col_end = col_start; + } + + let line_counter = LineCounter { + col_start, + col_end, + count, + }; + + Self::merge_counters(&line_counter, &mut lines[line].counters); + } + + previous_line += source_range.delta_line_start; + } + } + + let file_cov = FileCoverage { + filename: filename.clone(), + lines, + }; + + file_coverage_vec.push(file_cov); + } + + Self(file_coverage_vec) + } + + #[coverage(off)] + pub fn from_bisect_dump(bisect_dump: &Vec<(String, Vec, Vec)>) -> Self { + let sources_path = env::var("OCAML_BUILD_PATH").unwrap(); + let mut file_coverage_vec = Vec::new(); + + for (filename, points, counts) in bisect_dump.iter() { + let file_contents = fs::read(sources_path.clone() + filename).unwrap(); + let line_offset_vec: Vec = file_contents + .iter() + .enumerate() + .filter_map( + #[coverage(off)] + |(i, &x)| if x == b'\n' { Some(i) } else { None }, + ) + .collect(); + let mut lines: Vec = line_offset_vec + .iter() + .enumerate() + .map( + #[coverage(off)] + |(i, &end_pos)| { + let start_pos = if i == 0 { + 0 + } else { + line_offset_vec[i - 1] + 1 + }; + LineCoverage { + line: String::from_utf8((&file_contents[start_pos..end_pos]).to_vec()) + .unwrap(), + counters: Vec::new(), + } + }, + ) + .collect(); + + for (point, count) in points + .iter() + .zip(counts.iter()) + .filter_map( + #[coverage(off)] + |(point, count)| { + // FIXME: figure out why bisect-ppx is reporting point values such as -1 + if *point <= 0 { + None + } else { + Some(((*point - 1) as usize, *count)) + } + }, + ) + .sorted_by_key( + #[coverage(off)] + |(point, _count)| *point, + ) + { + let (line_num, _) = line_offset_vec + .iter() + .enumerate() + .find( + #[coverage(off)] + |(i, &offset)| point < offset || i + 1 == line_offset_vec.len(), + ) + .unwrap(); + + let col = if line_num == 0 { + point as usize + } else { + point as usize - line_offset_vec[line_num - 1] + }; + + // TODO: find a better way to convert bytes position to char position + let col_start = + String::from_utf8((&lines[line_num].line.as_bytes()[..col]).to_vec()) + .unwrap() + .chars() + .count(); + + // It seems there isn't an "end column" in bisect-ppx + let col_end = col_start; + + lines[line_num].counters.push(LineCounter { + col_start, + col_end, + count, + }) + } + + file_coverage_vec.push(FileCoverage { + filename: filename.clone(), + lines, + }) + } + + Self(file_coverage_vec) + } +} diff --git a/tools/fuzzing/src/transaction_fuzzer/coverage/stats.rs b/tools/fuzzing/src/transaction_fuzzer/coverage/stats.rs new file mode 100644 index 000000000..3bf473c9b --- /dev/null +++ b/tools/fuzzing/src/transaction_fuzzer/coverage/stats.rs @@ -0,0 +1,146 @@ +use super::cov::FileCounters; +use btreemultimap::BTreeMultiMap; +use std::fmt; + +//#[derive(Debug)] +pub struct Stats(BTreeMultiMap); + +impl Stats { + // If we are just interested in coverage statistics for some subset of source files + #[coverage(off)] + pub fn filter_filenames(&self, filenames: &Vec<&str>) -> Self { + let mut filtered_map = BTreeMultiMap::new(); + + for (k, v) in self.0.iter().filter( + #[coverage(off)] + |(_, (filename, _, _))| { + filenames.iter().any( + #[coverage(off)] + |name| filename.ends_with(name), + ) + }, + ) { + filtered_map.insert(*k, v.clone()); + } + + Self(filtered_map) + } + + #[coverage(off)] + pub fn filter_path(&self, path: &str) -> Self { + let mut filtered_map = BTreeMultiMap::new(); + + for (k, v) in self.0.iter().filter( + #[coverage(off)] + |(_, (filename, _, _))| !filename.contains(path), + ) { + filtered_map.insert(*k, v.clone()); + } + + Self(filtered_map) + } + + // If we are interested just in top `n` files with more coverage + #[coverage(off)] + pub fn filter_top(&self, n: usize) -> Self { + let mut filtered_map = BTreeMultiMap::new(); + + for (k, v) in self.0.clone().iter().rev().take(n) { + filtered_map.insert(*k, v.clone()); + } + + Self(filtered_map) + } + + // returns: (coverage percent, total counters, covered counters) + #[coverage(off)] + pub fn get_total(&self) -> (usize, usize, usize) { + let (total, covered) = self.0.iter().fold( + (0, 0), + #[coverage(off)] + |(acc_total, acc_covered), (_, (_, file_total, file_covered))| { + (acc_total + file_total, acc_covered + file_covered) + }, + ); + let cov_percent = if total > 0 { + (covered * 100) / total + } else { + 0 + }; + (cov_percent, total, covered) + } + + #[coverage(off)] + pub fn has_coverage_increased(&self, rhs: &Self) -> bool { + self.get_total().2 > rhs.get_total().2 + } + + #[coverage(off)] + pub fn from_file_counters(filecounters: &Vec) -> Self { + let mut result = BTreeMultiMap::new(); + + for FileCounters { + filename, counters, .. + } in filecounters.iter() + { + let (total, covered) = counters.iter().fold( + (0, 0), + #[coverage(off)] + |(total, covered), counters| { + ( + total + counters.len(), + covered + + counters.iter().fold( + 0, + #[coverage(off)] + |acc, counter| if *counter != 0 { acc + 1 } else { acc }, + ), + ) + }, + ); + + if total != 0 { + let cov_percent = (covered * 100) / total; + result.insert(cov_percent, (filename.clone(), total, covered)); + } + } + + Self(result) + } + + #[coverage(off)] + pub fn from_bisect_dump(bisect_dump: &Vec<(String, Vec, Vec)>) -> Self { + let mut result = BTreeMultiMap::new(); + + for (filename, _points, counts) in bisect_dump.iter() { + let covered = counts.iter().fold( + 0, + #[coverage(off)] + |acc, counter| if *counter != 0 { acc + 1 } else { acc }, + ); + let total = counts.len(); + + if total != 0 { + let cov_percent = (covered * 100) / total; + result.insert(cov_percent, (filename.clone(), total, covered)); + } + } + + Self(result) + } +} + +impl fmt::Display for Stats { + #[coverage(off)] + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + for (percent, (filename, total, covered)) in self.0.iter() { + write!( + f, + "{:>3}% {:>4}/{:>4}: {}\n", + percent, covered, total, filename + )?; + } + let (percent, total, covered) = self.get_total(); + write!(f, "{:>3}% {:>4}/{:>4}: Total\n", percent, covered, total) + } +} diff --git a/tools/fuzzing/src/transaction_fuzzer/coverage/util.rs b/tools/fuzzing/src/transaction_fuzzer/coverage/util.rs new file mode 100644 index 000000000..c8c323a28 --- /dev/null +++ b/tools/fuzzing/src/transaction_fuzzer/coverage/util.rs @@ -0,0 +1,136 @@ +use core::slice; +use std::{ + collections::HashMap, + io::Read, + io::{Cursor, Seek, SeekFrom}, + path::Path, +}; + +use bitvec::macros::internal::funty; +use object::{Object, ObjectSection}; + +extern "C" { + fn __llvm_profile_begin_counters() -> *mut i64; + fn __llvm_profile_end_counters() -> *mut i64; + fn __llvm_profile_get_num_counters(begin: *mut i64, end: *mut i64) -> i64; + fn __llvm_profile_begin_data() -> *const u8; + fn __llvm_profile_end_data() -> *const u8; + fn __llvm_profile_begin_names() -> *const u8; + fn __llvm_profile_end_names() -> *const u8; +} + +#[coverage(off)] +pub unsafe fn get_counters() -> &'static mut [i64] { + let begin = __llvm_profile_begin_counters(); + let end = __llvm_profile_end_counters(); + let num_counters = __llvm_profile_get_num_counters(begin, end); + slice::from_raw_parts_mut(begin, num_counters as usize) +} + +#[coverage(off)] +pub unsafe fn get_data() -> &'static [u8] { + let begin = __llvm_profile_begin_data(); + let end = __llvm_profile_end_data(); + slice::from_raw_parts(begin, end.offset_from(begin) as usize) +} + +#[coverage(off)] +pub unsafe fn get_names() -> &'static [u8] { + let begin = __llvm_profile_begin_names(); + let end = __llvm_profile_end_names(); + slice::from_raw_parts(begin, end.offset_from(begin) as usize) +} + +#[coverage(off)] +pub fn get_module_path() -> String { + let maps = rsprocmaps::from_path("/proc/self/maps").unwrap(); + + for map in maps { + let map = map.unwrap(); + + let rsprocmaps::AddressRange { begin, end } = map.address_range; + + if (begin..end).contains(&(get_module_path as u64)) { + if let rsprocmaps::Pathname::Path(path) = map.pathname { + return path; + } else { + panic!() + } + } + } + + panic!() +} + +#[coverage(off)] +pub fn get_elf_sections(module: String, section_names: &Vec<&str>) -> HashMap> { + let mut result = HashMap::new(); + let path = Path::new(&module); + + let bin_data = std::fs::read(path).unwrap(); + let obj_file = object::File::parse(&*bin_data).unwrap(); + + for section_name in section_names.iter() { + let section = obj_file.section_by_name(section_name).unwrap(); + result.insert(section_name.to_string(), section.data().unwrap().to_vec()); + } + + result +} + +#[coverage(off)] +pub fn read_int(cursor: &mut Cursor) -> T +where + T: funty::Numeric, + A: AsRef<[u8]>, +{ + let mut buf = [0u8; N]; + cursor.read_exact(buf.as_mut_slice()).unwrap(); + T::from_le_bytes(buf) +} + +pub trait Leb128 { + type T; + + fn read_leb128(cursor: &mut Cursor<&Vec>) -> Self::T; +} + +impl Leb128 for i64 { + type T = i64; + + #[coverage(off)] + fn read_leb128(cursor: &mut Cursor<&Vec>) -> Self::T { + leb128::read::signed(cursor).unwrap() + } +} + +impl Leb128 for u64 { + type T = u64; + + #[coverage(off)] + fn read_leb128(cursor: &mut Cursor<&Vec>) -> Self::T { + leb128::read::unsigned(cursor).unwrap() + } +} + +impl Leb128 for usize { + type T = usize; + + #[coverage(off)] + fn read_leb128(cursor: &mut Cursor<&Vec>) -> Self::T { + leb128::read::unsigned(cursor).unwrap() as usize + } +} + +#[coverage(off)] +fn align(pos: u64) -> u64 { + let alignment = std::mem::size_of::() as u64; + (pos + (alignment - 1)) & !(alignment - 1) +} + +#[coverage(off)] +pub fn cursor_align(cursor: &mut Cursor<&Vec>) { + cursor + .seek(SeekFrom::Start(align::(cursor.position()))) + .unwrap(); +} diff --git a/tools/fuzzing/src/transaction_fuzzer/generator.rs b/tools/fuzzing/src/transaction_fuzzer/generator.rs new file mode 100644 index 000000000..bd7870591 --- /dev/null +++ b/tools/fuzzing/src/transaction_fuzzer/generator.rs @@ -0,0 +1,2307 @@ +use ark_ec::AffineCurve; +use ark_ec::ProjectiveCurve; +use ark_ff::SquareRootField; +use ark_ff::{Field, UniformRand}; +use ledger::generators::zkapp_command_builder::get_transaction_commitments; +use ledger::proofs::field::FieldWitness; +use ledger::proofs::transaction::InnerCurve; +use ledger::scan_state::currency::{Magnitude, SlotSpan, TxnVersion}; +use ledger::VerificationKeyWire; +use ledger::{ + proofs::transaction::PlonkVerificationKeyEvals, + scan_state::{ + currency::{Amount, Balance, BlockTime, Fee, Length, MinMax, Nonce, Sgn, Signed, Slot}, + transaction_logic::{ + signed_command::{ + self, PaymentPayload, SignedCommand, SignedCommandPayload, StakeDelegationPayload, + }, + transaction_union_payload::TransactionUnionPayload, + zkapp_command::{ + self, AccountPreconditions, AccountUpdate, ClosedInterval, FeePayer, FeePayerBody, + MayUseToken, Numeric, OrIgnore, SetOrKeep, Update, ZkAppCommand, + }, + zkapp_statement::TransactionCommitment, + Memo, Transaction, UserCommand, + }, + }, + Account, AuthRequired, Permissions, ProofVerified, TokenId, TokenSymbol, VerificationKey, + VotingFor, ZkAppUri, +}; +use ledger::{SetVerificationKey, TXN_VERSION_CURRENT}; +use mina_curves::pasta::Fq; +use mina_hasher::Fp; +use mina_p2p_messages::array::ArrayN; +use mina_p2p_messages::list::List; +use mina_p2p_messages::v2::{ + PicklesProofProofsVerified2ReprStableV2StatementProofStateDeferredValuesPlonk, + PicklesWrapWireProofCommitmentsStableV1, PicklesWrapWireProofEvaluationsStableV1, + PicklesWrapWireProofStableV1, PicklesWrapWireProofStableV1Bulletproof, +}; +use mina_p2p_messages::{ + number::Number, + pseq::PaddedSeq, + v2::{ + CompositionTypesBranchDataDomainLog2StableV1, CompositionTypesBranchDataStableV1, + CompositionTypesDigestConstantStableV1, CurrencyAmountStableV1, CurrencyFeeStableV1, + LedgerHash, LimbVectorConstantHex64StableV1, MinaBaseAccountIdDigestStableV1, + MinaBaseCallStackDigestStableV1, MinaBaseFeeExcessStableV1, MinaBaseLedgerHash0StableV1, + MinaBasePendingCoinbaseCoinbaseStackStableV1, MinaBasePendingCoinbaseStackHashStableV1, + MinaBasePendingCoinbaseStackVersionedStableV1, MinaBasePendingCoinbaseStateStackStableV1, + MinaBaseStackFrameStableV1, MinaBaseTransactionStatusFailureCollectionStableV1, + MinaStateBlockchainStateValueStableV2LedgerProofStatementSource, + MinaTransactionLogicZkappCommandLogicLocalStateValueStableV1, + PicklesBaseProofsVerifiedStableV1, + PicklesProofProofsVerified2ReprStableV2MessagesForNextStepProof, + PicklesProofProofsVerified2ReprStableV2MessagesForNextWrapProof, + PicklesProofProofsVerified2ReprStableV2PrevEvals, + PicklesProofProofsVerified2ReprStableV2PrevEvalsEvals, + PicklesProofProofsVerified2ReprStableV2PrevEvalsEvalsEvals, + PicklesProofProofsVerified2ReprStableV2Statement, + PicklesProofProofsVerified2ReprStableV2StatementFp, + PicklesProofProofsVerified2ReprStableV2StatementProofState, + PicklesProofProofsVerified2ReprStableV2StatementProofStateDeferredValues, + PicklesProofProofsVerified2ReprStableV2StatementProofStateDeferredValuesPlonkFeatureFlags, + PicklesProofProofsVerifiedMaxStableV2, + PicklesReducedMessagesForNextProofOverSameFieldWrapChallengesVectorStableV2, + PicklesReducedMessagesForNextProofOverSameFieldWrapChallengesVectorStableV2A, + PicklesReducedMessagesForNextProofOverSameFieldWrapChallengesVectorStableV2AChallenge, + SgnStableV1, SignedAmount, TokenFeeExcess, TokenIdKeyHash, UnsignedExtendedUInt32StableV1, + UnsignedExtendedUInt64Int64ForVersionTagsStableV1, + }, +}; +use mina_signer::{ + CompressedPubKey, CurvePoint, Keypair, NetworkId, ScalarField, SecKey, Signature, Signer, +}; +use rand::seq::SliceRandom; +use rand::Rng; +use std::{array, iter, ops::RangeInclusive, sync::Arc}; +use tuple_map::TupleMap2; + +use super::context::{FuzzerCtx, PermissionModel}; + +macro_rules! impl_default_generator_for_wrapper_type { + ($fuzz_ctx: ty, $wrapper: tt) => { + impl Generator<$wrapper> for $fuzz_ctx { + #[coverage(off)] + fn gen(&mut self) -> $wrapper { + $wrapper(self.gen()) + } + } + }; + ($fuzz_ctx: ty, $wrapper: tt, $inner: ty) => { + impl Generator<$wrapper> for $fuzz_ctx { + #[coverage(off)] + fn gen(&mut self) -> $wrapper { + let inner: $inner = self.gen(); + inner.into() + } + } + }; +} + +pub trait Generator { + fn gen(&mut self) -> T; +} + +impl Generator for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> bool { + self.gen.rng.gen_bool(0.5) + } +} + +/* +impl Generator for FuzzerCtx { + // rnd_base_field + fn gen(&mut self) -> Fp { + let mut bf = None; + + // TODO: optimize by masking out MSBs from bytes and remove loop + while bf.is_none() { + let bytes = self.gen.rng.gen::<[u8; 32]>(); + bf = Fp::from_random_bytes_with_flags::(&bytes); + } + + bf.unwrap().0 + } +} +*/ + +impl Generator for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> Fp { + Fp::rand(&mut self.gen.rng) + } +} + +impl Generator for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> Slot { + if self.gen.rng.gen_bool(0.9) { + self.txn_state_view.global_slot_since_genesis + } else { + Slot::from_u32(self.gen.rng.gen_range(0..Slot::max().as_u32())) + } + } +} + +impl Generator for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> SlotSpan { + if self.gen.rng.gen_bool(0.9) { + SlotSpan::from_u32(self.txn_state_view.global_slot_since_genesis.as_u32()) + } else { + SlotSpan::from_u32(self.gen.rng.gen_range(0..SlotSpan::max().as_u32())) + } + } +} + +impl Generator for FuzzerCtx { + /* + Reimplement random key generation w/o the restriction on CryptoRgn trait. + Since we are only using this for fuzzing we want a faster (unsafe) Rng like SmallRng. + */ + #[coverage(off)] + fn gen(&mut self) -> SecKey { + let secret: ScalarField = ScalarField::rand(&mut self.gen.rng); + SecKey::new(secret) + } +} + +impl Generator for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> Keypair { + let sec_key: SecKey = self.gen(); + let scalar = sec_key.into_scalar(); + let public: CurvePoint = CurvePoint::prime_subgroup_generator() + .mul(scalar) + .into_affine(); + + let keypair = Keypair::from_parts_unsafe(scalar, public); + + if !self.state.potential_senders.iter().any( + #[coverage(off)] + |(kp, _)| kp.public == keypair.public, + ) { + let permission_model = self.gen(); + self.state + .potential_new_accounts + .push((keypair.clone(), permission_model)) + } + + keypair + } +} + +impl Generator for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> CompressedPubKey { + let keypair = if self.gen.rng.gen_bool(0.9) { + // use existing account + self.random_keypair() + } else { + // create new account + self.gen() + }; + + keypair.public.into_compressed() + } +} + +impl Generator for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> Memo { + Memo::with_number(self.gen.rng.gen()) + } +} + +impl + SquareRootField> Generator<(F, F)> for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> (F, F) { + /* + WARNING: we need to generate valid curve points to avoid binprot deserializarion + exceptions in the OCaml side. However this is an expensive task. + + TODO: a more efficient way of doing this? + */ + let mut x = F::rand(&mut self.gen.rng); + + loop { + let y_squared = x.square().mul(x).add(Into::::into(5)); + + if let Some(y) = y_squared.sqrt() { + return (x, y); + } + + x.add_assign(F::one()); + } + } +} + +impl Generator> for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> InnerCurve { + let (x, y) = self.gen(); + InnerCurve::::from((x, y)) + } +} + +impl Generator> for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> PlonkVerificationKeyEvals { + PlonkVerificationKeyEvals { + sigma: array::from_fn( + #[coverage(off)] + |_| self.gen(), + ), + coefficients: array::from_fn( + #[coverage(off)] + |_| self.gen(), + ), + generic: self.gen(), + psm: self.gen(), + complete_add: self.gen(), + mul: self.gen(), + emul: self.gen(), + endomul_scalar: self.gen(), + } + } +} + +impl Generator for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> VerificationKey { + VerificationKey { + max_proofs_verified: vec![ProofVerified::N0, ProofVerified::N1, ProofVerified::N2] + .choose(&mut self.gen.rng) + .unwrap() + .clone(), + actual_wrap_domain_size: vec![ProofVerified::N0, ProofVerified::N1, ProofVerified::N2] + .choose(&mut self.gen.rng) + .unwrap() + .clone(), + wrap_index: Box::new(self.gen()), + wrap_vk: None, // TODO + } + } +} + +/* +impl + Hashable> Generator> for FuzzerCtx +where + FuzzerCtx: Generator, +{ + fn gen(&mut self) -> zkapp_command::WithHash { + let data: T = self.gen(); + let hash = data.digest(); + zkapp_command::WithHash { data, hash } + } +} +*/ + +impl Generator> for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> zkapp_command::WithHash { + let data: VerificationKey = self.gen(); + let hash = data.digest(); + zkapp_command::WithHash { data, hash } + } +} + +impl Generator for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> AuthRequired { + *vec![ + AuthRequired::None, + AuthRequired::Either, + AuthRequired::Proof, + AuthRequired::Signature, + AuthRequired::Impossible, + //AuthRequired::Both, + ] + .choose(&mut self.gen.rng) + .unwrap() + } +} + +impl Generator for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> PermissionModel { + vec![ + PermissionModel::Any, + PermissionModel::Empty, + PermissionModel::Initial, + PermissionModel::Default, + PermissionModel::TokenOwner, + ] + .choose(&mut self.gen.rng) + .unwrap() + .clone() + } +} + +impl Generator for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> ZkAppUri { + /* + TODO: this needs to be fixed (assign a boundary) in the protocol since it is + possible to set a zkApp URI of arbitrary size. + + Since the field is opaque to the Mina protocol logic, randomly generating + URIs makes little sense and will consume a significant amount of ledger space. + */ + ZkAppUri::new() + } +} + +impl Generator for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> TokenSymbol { + /* + TokenSymbol must be <= 6 **bytes**. This boundary doesn't exist at type-level, + instead it is check by binprot after deserializing the *string* object: + https://github.com/MinaProtocol/mina/blob/develop/src/lib/mina_base/account.ml#L124 + + We will let this function generate strings larger than 6 bytes with low probability, + just to cover the error handling code, but must of the time we want to avoid failing + this check. + */ + if self.gen.rng.gen_bool(0.9) { + TokenSymbol::default() + } else { + let rnd_len = self.gen.rng.gen_range(0..=6); + TokenSymbol((0..rnd_len).map(|_| self.gen.rng.gen()).collect()) + } + } +} + +impl Generator for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> VotingFor { + VotingFor(self.gen()) + } +} + +impl Generator for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> zkapp_command::Events { + /* + An Event is a list of arrays of Fp, there doesn't seem to be any limit + neither in the size of the list or the array's size. The total size should + be bounded by the transport protocol (currently libp2p, ~32MB). + */ + + if self.gen.rng.gen_bool(0.9) { + zkapp_command::Events(Vec::new()) + } else { + // Generate random Events in the same fashion as Mina's generator (up to 5x3 elements). + let n = self.gen.rng.gen_range(0..=5); + + zkapp_command::Events( + (0..=n) + .map( + #[coverage(off)] + |_| { + let n = self.gen.rng.gen_range(0..=3); + zkapp_command::Event( + (0..=n) + .map( + #[coverage(off)] + |_| self.gen(), + ) + .collect(), + ) + }, + ) + .collect(), + ) + } + } +} + +impl Generator for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> zkapp_command::Actions { + // See comment in generator above + + if self.gen.rng.gen_bool(0.9) { + zkapp_command::Actions(Vec::new()) + } else { + let n = self.gen.rng.gen_range(0..=5); + + zkapp_command::Actions( + (0..=n) + .map( + #[coverage(off)] + |_| { + let n = self.gen.rng.gen_range(0..=3); + zkapp_command::Event( + (0..=n) + .map( + #[coverage(off)] + |_| self.gen(), + ) + .collect(), + ) + }, + ) + .collect(), + ) + } + } +} + +impl Generator for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> BlockTime { + self.gen.rng.gen() + } +} + +impl Generator for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> Length { + self.gen.rng.gen() + } +} + +pub trait GeneratorRange32 { + fn gen_range(&mut self, range: RangeInclusive) -> T; +} + +pub trait GeneratorRange64 { + fn gen_range(&mut self, range: RangeInclusive) -> T; +} + +impl GeneratorRange64 for FuzzerCtx { + #[coverage(off)] + fn gen_range(&mut self, range: RangeInclusive) -> Balance { + Balance::from_u64(self.gen.rng.gen_range(range)) + } +} + +impl GeneratorRange64 for FuzzerCtx { + #[coverage(off)] + fn gen_range(&mut self, range: RangeInclusive) -> Fee { + Fee::from_u64(self.gen.rng.gen_range(range)) + } +} + +impl GeneratorRange64 for FuzzerCtx { + #[coverage(off)] + fn gen_range(&mut self, range: RangeInclusive) -> Amount { + Amount::from_u64(self.gen.rng.gen_range(range)) + } +} + +impl GeneratorRange32 for FuzzerCtx { + #[coverage(off)] + fn gen_range(&mut self, range: RangeInclusive) -> Nonce { + Nonce::from_u32(self.gen.rng.gen_range(range)) + } +} + +impl GeneratorRange32 for FuzzerCtx { + #[coverage(off)] + fn gen_range(&mut self, range: RangeInclusive) -> Length { + Length::from_u32(self.gen.rng.gen_range(range)) + } +} + +pub trait GeneratorWrapper T> { + fn gen_wrap(&mut self, f: F) -> W; +} + +impl T> GeneratorWrapper, T, F> for FuzzerCtx { + #[coverage(off)] + fn gen_wrap(&mut self, mut f: F) -> Option { + if self.gen.rng.gen_bool(0.9) { + None + } else { + Some(f(self)) + } + } +} + +impl T> GeneratorWrapper, T, F> for FuzzerCtx { + #[coverage(off)] + fn gen_wrap(&mut self, mut f: F) -> OrIgnore { + if self.gen.rng.gen_bool(0.5) { + OrIgnore::Ignore + } else { + OrIgnore::Check(f(self)) + } + } +} + +impl T> GeneratorWrapper, T, F> for FuzzerCtx { + #[coverage(off)] + fn gen_wrap(&mut self, mut f: F) -> SetOrKeep { + if self.gen.rng.gen_bool(0.5) { + SetOrKeep::Keep + } else { + SetOrKeep::Set(f(self)) + } + } +} + +impl T> GeneratorWrapper, T, F> + for FuzzerCtx +{ + #[coverage(off)] + fn gen_wrap(&mut self, mut f: F) -> ClosedInterval { + if self.gen.rng.gen_bool(0.5) { + // constant case + let val = f(self); + + ClosedInterval { + lower: val.clone(), + upper: val, + } + } else { + ClosedInterval { + lower: f(self), + upper: f(self), + } + } + } +} + +impl Generator for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> Sgn { + if self.gen.rng.gen_bool(0.5) { + Sgn::Pos + } else { + Sgn::Neg + } + } +} + +pub trait GeneratorWrapperMany T> { + fn gen_wrap_many(&mut self, f: F) -> W; +} + +impl T, const N: u64> GeneratorWrapperMany, T, F> + for FuzzerCtx +{ + #[coverage(off)] + fn gen_wrap_many(&mut self, mut f: F) -> ArrayN { + iter::repeat_with( + #[coverage(off)] + || f(self), + ) + .take(N as usize) + .collect() + } +} + +impl Generator> for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> Numeric { + self.gen_wrap( + #[coverage(off)] + |x| { + x.gen_wrap( + #[coverage(off)] + |x| -> Amount { GeneratorRange64::gen_range(x, 0..=u64::MAX) }, + ) + }, + ) + } +} + +impl Generator> for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> Numeric { + self.gen_wrap( + #[coverage(off)] + |x| { + x.gen_wrap( + #[coverage(off)] + |x| -> Length { GeneratorRange32::gen_range(x, 0..=u32::MAX) }, + ) + }, + ) + } +} + +impl Generator for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> FeePayer { + let public_key = if self.gen.attempt_valid_zkapp || self.gen.rng.gen_bool(0.9) { + self.random_keypair().public.into_compressed() + } else { + self.gen() + }; + + let account = self.get_account(&public_key); + // FIXME: boundary at i64 MAX because OCaml side is encoding it as int + let max_fee = match account.as_ref() { + Some(account) if self.gen.attempt_valid_zkapp || self.gen.rng.gen_bool(0.9) => { + self.gen.minimum_fee.max(account.balance.as_u64()) + } + _ => self + .gen + .rng + .gen_range(self.gen.minimum_fee + 1..=10_000_000), + }; + + let fee: Fee = GeneratorRange64::gen_range(self, self.gen.minimum_fee..=max_fee); + + let nonce = match self.gen.nonces.get(&public_key.into_address()) { + Some(nonce) => *nonce, + None => account + .as_ref() + .map(|account| account.nonce) + .unwrap_or_else(|| { + if self.gen.rng.gen_bool(0.9) { + Nonce::from_u32(0) + } else { + GeneratorRange32::gen_range(self, 0..=u32::MAX) + } + }), + }; + + self.gen + .nonces + .insert(public_key.into_address(), nonce.incr()); + + FeePayer { + body: FeePayerBody { + public_key, + fee, + valid_until: self.gen_wrap( + #[coverage(off)] + |x| -> Slot { x.gen() }, + ), + nonce, + }, + // filled later when tx is complete + authorization: Signature::dummy(), + } + } +} + +impl Generator for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> zkapp_command::EpochData { + zkapp_command::EpochData::new( + zkapp_command::EpochLedger { + hash: self.gen_wrap( + #[coverage(off)] + |x| x.gen(), + ), + total_currency: self.gen(), + }, + self.gen_wrap( + #[coverage(off)] + |x| x.gen(), + ), + self.gen_wrap( + #[coverage(off)] + |x| x.gen(), + ), + self.gen_wrap( + #[coverage(off)] + |x| x.gen(), + ), + self.gen(), + ) + } +} + +impl Generator for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> LimbVectorConstantHex64StableV1 { + LimbVectorConstantHex64StableV1(Number(self.gen.rng.gen())) + } +} + +impl + Generator< + PicklesProofProofsVerified2ReprStableV2StatementProofStateDeferredValuesPlonkFeatureFlags, + > for FuzzerCtx +{ + #[coverage(off)] + fn gen( + &mut self, + ) -> PicklesProofProofsVerified2ReprStableV2StatementProofStateDeferredValuesPlonkFeatureFlags + { + PicklesProofProofsVerified2ReprStableV2StatementProofStateDeferredValuesPlonkFeatureFlags { + range_check0: self.gen.rng.gen_bool(0.5), + range_check1: self.gen.rng.gen_bool(0.5), + foreign_field_add: self.gen.rng.gen_bool(0.5), + foreign_field_mul: self.gen.rng.gen_bool(0.5), + xor: self.gen.rng.gen_bool(0.5), + rot: self.gen.rng.gen_bool(0.5), + lookup: self.gen.rng.gen_bool(0.5), + runtime_tables: self.gen.rng.gen_bool(0.5), + } + } +} + +impl Generator + for FuzzerCtx +{ + #[coverage(off)] + fn gen( + &mut self, + ) -> PicklesProofProofsVerified2ReprStableV2StatementProofStateDeferredValuesPlonk { + PicklesProofProofsVerified2ReprStableV2StatementProofStateDeferredValuesPlonk { + alpha: self.gen(), + beta: self.gen(), + gamma: self.gen(), + zeta: self.gen(), + joint_combiner: self.gen_wrap( + #[coverage(off)] + |x| x.gen(), + ), + feature_flags: self.gen(), + } + } +} + +impl Generator for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> mina_p2p_messages::bigint::BigInt { + mina_p2p_messages::bigint::BigInt::from(Generator::::gen(self)) + } +} + +impl Generator for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> PicklesProofProofsVerified2ReprStableV2StatementFp { + PicklesProofProofsVerified2ReprStableV2StatementFp::ShiftedValue(self.gen()) + } +} + +impl Generator + for FuzzerCtx +{ + #[coverage(off)] + fn gen( + &mut self, + ) -> PicklesReducedMessagesForNextProofOverSameFieldWrapChallengesVectorStableV2A { + PicklesReducedMessagesForNextProofOverSameFieldWrapChallengesVectorStableV2A { + prechallenge: self.gen(), + } + } +} + +impl Generator> for FuzzerCtx +where + FuzzerCtx: Generator, +{ + #[coverage(off)] + fn gen(&mut self) -> PaddedSeq { + PaddedSeq::(array::from_fn( + #[coverage(off)] + |_| self.gen(), + )) + } +} + +impl T, const N: usize> GeneratorWrapper, T, F> + for FuzzerCtx +{ + #[coverage(off)] + fn gen_wrap(&mut self, mut f: F) -> PaddedSeq { + PaddedSeq::(array::from_fn( + #[coverage(off)] + |_| f(self), + )) + } +} + +impl + Generator + for FuzzerCtx +{ + #[coverage(off)] + fn gen( + &mut self, + ) -> PicklesReducedMessagesForNextProofOverSameFieldWrapChallengesVectorStableV2AChallenge { + PicklesReducedMessagesForNextProofOverSameFieldWrapChallengesVectorStableV2AChallenge { + inner: self.gen(), + } + } +} + +impl Generator for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> CompositionTypesBranchDataDomainLog2StableV1 { + CompositionTypesBranchDataDomainLog2StableV1(mina_p2p_messages::char::Char( + self.gen.rng.gen(), + )) + } +} + +impl Generator for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> CompositionTypesBranchDataStableV1 { + CompositionTypesBranchDataStableV1 { + proofs_verified: vec![ + PicklesBaseProofsVerifiedStableV1::N0, + PicklesBaseProofsVerifiedStableV1::N1, + PicklesBaseProofsVerifiedStableV1::N2, + ] + .choose(&mut self.gen.rng) + .unwrap() + .clone(), + domain_log2: self.gen(), + } + } +} + +impl Generator + for FuzzerCtx +{ + #[coverage(off)] + fn gen(&mut self) -> PicklesProofProofsVerified2ReprStableV2StatementProofStateDeferredValues { + PicklesProofProofsVerified2ReprStableV2StatementProofStateDeferredValues { + plonk: self.gen(), + bulletproof_challenges: self.gen(), + branch_data: self.gen(), + } + } +} + +impl Generator for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> CompositionTypesDigestConstantStableV1 { + CompositionTypesDigestConstantStableV1(self.gen()) + } +} + +impl Generator + for FuzzerCtx +{ + #[coverage(off)] + fn gen( + &mut self, + ) -> PicklesReducedMessagesForNextProofOverSameFieldWrapChallengesVectorStableV2 { + PicklesReducedMessagesForNextProofOverSameFieldWrapChallengesVectorStableV2(self.gen()) + } +} + +#[coverage(off)] +pub fn gen_curve_point>( + ctx: &mut impl Generator<(T, T)>, +) -> ( + mina_p2p_messages::bigint::BigInt, + mina_p2p_messages::bigint::BigInt, +) +where + mina_p2p_messages::bigint::BigInt: From, +{ + Generator::<(T, T)>::gen(ctx).map(mina_p2p_messages::bigint::BigInt::from) +} + +#[coverage(off)] +pub fn gen_curve_point_many, const N: u64>( + ctx: &mut FuzzerCtx, +) -> ArrayN< + ( + mina_p2p_messages::bigint::BigInt, + mina_p2p_messages::bigint::BigInt, + ), + { N }, +> +where + mina_p2p_messages::bigint::BigInt: From, +{ + ctx.gen_wrap_many( + #[coverage(off)] + |x| gen_curve_point::(x), + ) +} + +#[coverage(off)] +pub fn gen_curve_point_many_unzip, const N: u64>( + ctx: &mut FuzzerCtx, +) -> ( + ArrayN, + ArrayN, +) +where + mina_p2p_messages::bigint::BigInt: From + Default, +{ + let (a, b): (Vec<_>, Vec<_>) = gen_curve_point_many::(ctx).into_iter().unzip(); + + ( + ArrayN::from_iter(a.into_iter()), + ArrayN::from_iter(b.into_iter()), + ) +} + +impl Generator for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> PicklesProofProofsVerified2ReprStableV2MessagesForNextWrapProof { + PicklesProofProofsVerified2ReprStableV2MessagesForNextWrapProof { + challenge_polynomial_commitment: gen_curve_point::(self), + old_bulletproof_challenges: self.gen(), + } + } +} + +impl Generator for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> PicklesProofProofsVerified2ReprStableV2StatementProofState { + PicklesProofProofsVerified2ReprStableV2StatementProofState { + deferred_values: self.gen(), + sponge_digest_before_evaluations: self.gen(), + messages_for_next_wrap_proof: self.gen(), + } + } +} + +impl Generator for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> PicklesProofProofsVerified2ReprStableV2MessagesForNextStepProof { + PicklesProofProofsVerified2ReprStableV2MessagesForNextStepProof { + app_state: (), + challenge_polynomial_commitments: List::one(gen_curve_point::(self)), // TODO: variable num of elements + old_bulletproof_challenges: List::one(self.gen_wrap( + // TODO: variable num of elements + #[coverage(off)] + |x| x.gen(), + )), + } + } +} + +impl Generator for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> PicklesProofProofsVerified2ReprStableV2Statement { + PicklesProofProofsVerified2ReprStableV2Statement { + proof_state: self.gen(), + messages_for_next_step_proof: self.gen(), + } + } +} + +// impl Generator for FuzzerCtx { +// #[coverage(off)] +// fn gen(&mut self) -> PicklesProofProofsVerified2ReprStableV2PrevEvalsEvalsEvalsLookupA { +// PicklesProofProofsVerified2ReprStableV2PrevEvalsEvalsEvalsLookupA { +// sorted: self.gen_wrap_many( +// #[coverage(off)] +// |x| gen_curve_point_many_unzip::(x, 1), +// 1, +// ), +// aggreg: gen_curve_point_many_unzip::(self, 1), +// table: gen_curve_point_many_unzip::(self, 1), +// runtime: self.gen_wrap( +// #[coverage(off)] +// |x| gen_curve_point_many_unzip::(x, 1), +// ), +// } +// } +// } + +impl Generator for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> PicklesProofProofsVerified2ReprStableV2PrevEvalsEvalsEvals { + PicklesProofProofsVerified2ReprStableV2PrevEvalsEvalsEvals { + w: self.gen_wrap( + #[coverage(off)] + |x| gen_curve_point_many_unzip::(x), + ), + complete_add_selector: gen_curve_point_many_unzip::(self), + mul_selector: gen_curve_point_many_unzip::(self), + emul_selector: gen_curve_point_many_unzip::(self), + endomul_scalar_selector: gen_curve_point_many_unzip::(self), + range_check0_selector: self.gen_wrap( + #[coverage(off)] + |x| gen_curve_point_many_unzip::(x), + ), + range_check1_selector: self.gen_wrap( + #[coverage(off)] + |x| gen_curve_point_many_unzip::(x), + ), + foreign_field_add_selector: self.gen_wrap( + #[coverage(off)] + |x| gen_curve_point_many_unzip::(x), + ), + foreign_field_mul_selector: self.gen_wrap( + #[coverage(off)] + |x| gen_curve_point_many_unzip::(x), + ), + xor_selector: self.gen_wrap( + #[coverage(off)] + |x| gen_curve_point_many_unzip::(x), + ), + rot_selector: self.gen_wrap( + #[coverage(off)] + |x| gen_curve_point_many_unzip::(x), + ), + lookup_aggregation: self.gen_wrap( + #[coverage(off)] + |x| gen_curve_point_many_unzip::(x), + ), + lookup_table: self.gen_wrap( + #[coverage(off)] + |x| gen_curve_point_many_unzip::(x), + ), + lookup_sorted: self.gen_wrap( + #[coverage(off)] + |x| { + x.gen_wrap( + #[coverage(off)] + |x| gen_curve_point_many_unzip::(x), + ) + }, + ), + runtime_lookup_table: self.gen_wrap( + #[coverage(off)] + |x| gen_curve_point_many_unzip::(x), + ), + runtime_lookup_table_selector: self.gen_wrap( + #[coverage(off)] + |x| gen_curve_point_many_unzip::(x), + ), + xor_lookup_selector: self.gen_wrap( + #[coverage(off)] + |x| gen_curve_point_many_unzip::(x), + ), + lookup_gate_lookup_selector: self.gen_wrap( + #[coverage(off)] + |x| gen_curve_point_many_unzip::(x), + ), + range_check_lookup_selector: self.gen_wrap( + #[coverage(off)] + |x| gen_curve_point_many_unzip::(x), + ), + foreign_field_mul_lookup_selector: self.gen_wrap( + #[coverage(off)] + |x| gen_curve_point_many_unzip::(x), + ), + coefficients: self.gen_wrap( + #[coverage(off)] + |x| gen_curve_point_many_unzip::(x), + ), + z: gen_curve_point_many_unzip::(self), + s: self.gen_wrap( + #[coverage(off)] + |x| gen_curve_point_many_unzip::(x), + ), + generic_selector: gen_curve_point_many_unzip::(self), + poseidon_selector: gen_curve_point_many_unzip::(self), + } + } +} + +impl Generator for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> PicklesProofProofsVerified2ReprStableV2PrevEvalsEvals { + PicklesProofProofsVerified2ReprStableV2PrevEvalsEvals { + public_input: gen_curve_point::(self), + evals: self.gen(), + } + } +} + +impl Generator for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> PicklesProofProofsVerified2ReprStableV2PrevEvals { + PicklesProofProofsVerified2ReprStableV2PrevEvals { + evals: self.gen(), + ft_eval1: mina_p2p_messages::bigint::BigInt::from(Generator::::gen(self)), + } + } +} + +// impl Generator for FuzzerCtx { +// #[coverage(off)] +// fn gen(&mut self) -> PicklesProofProofsVerified2ReprStableV2ProofMessagesLookupA { +// PicklesProofProofsVerified2ReprStableV2ProofMessagesLookupA { +// sorted: self.gen_wrap_many( +// #[coverage(off)] +// |x| gen_curve_point_many::(x, 1), +// 1, +// ), +// aggreg: gen_curve_point_many::(self, 1), +// runtime: self.gen_wrap( +// #[coverage(off)] +// |x| gen_curve_point_many::(x, 1), +// ), +// } +// } +// } + +// impl Generator for FuzzerCtx { +// #[coverage(off)] +// fn gen(&mut self) -> PicklesProofProofsVerified2ReprStableV2ProofMessages { +// PicklesProofProofsVerified2ReprStableV2ProofMessages { +// w_comm: self.gen_wrap( +// #[coverage(off)] +// |x| gen_curve_point_many::(x, 1), +// ), +// z_comm: gen_curve_point_many::(self, 1), +// t_comm: gen_curve_point_many::(self, 1), +// lookup: self.gen_wrap( +// #[coverage(off)] +// |x| x.gen(), +// ), +// } +// } +// } + +// impl Generator for FuzzerCtx { +// #[coverage(off)] +// fn gen(&mut self) -> PicklesProofProofsVerified2ReprStableV2ProofOpeningsProof { +// PicklesProofProofsVerified2ReprStableV2ProofOpeningsProof { +// lr: self.gen_wrap_many( +// #[coverage(off)] +// |x| (gen_curve_point::(x), gen_curve_point::(x)), +// 1, +// ), +// z_1: self.gen(), +// z_2: self.gen(), +// delta: gen_curve_point::(self), +// challenge_polynomial_commitment: gen_curve_point::(self), +// } +// } +// } + +// impl Generator for FuzzerCtx { +// #[coverage(off)] +// fn gen(&mut self) -> PicklesProofProofsVerified2ReprStableV2ProofOpenings { +// PicklesProofProofsVerified2ReprStableV2ProofOpenings { +// proof: self.gen(), +// evals: self.gen(), +// ft_eval1: self.gen(), +// } +// } +// } + +// impl Generator for FuzzerCtx { +// #[coverage(off)] +// fn gen(&mut self) -> PicklesProofProofsVerified2ReprStableV2Proof { +// PicklesProofProofsVerified2ReprStableV2Proof { +// messages: self.gen(), +// openings: self.gen(), +// } +// } +// } + +impl Generator for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> PicklesWrapWireProofCommitmentsStableV1 { + PicklesWrapWireProofCommitmentsStableV1 { + w_comm: self.gen_wrap( + #[coverage(off)] + |x| gen_curve_point::(x), + ), + z_comm: gen_curve_point::(self), + t_comm: self.gen_wrap( + #[coverage(off)] + |x| gen_curve_point::(x), + ), + } + } +} + +impl Generator for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> PicklesWrapWireProofStableV1Bulletproof { + PicklesWrapWireProofStableV1Bulletproof { + lr: self.gen_wrap_many( + #[coverage(off)] + |x| (gen_curve_point::(x), gen_curve_point::(x)), + ), + z_1: Generator::::gen(self).into(), + z_2: Generator::::gen(self).into(), + delta: gen_curve_point::(self), + challenge_polynomial_commitment: gen_curve_point::(self), + } + } +} + +impl Generator for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> PicklesWrapWireProofEvaluationsStableV1 { + PicklesWrapWireProofEvaluationsStableV1 { + w: self.gen_wrap( + #[coverage(off)] + |x| gen_curve_point::(x), + ), + coefficients: self.gen_wrap( + #[coverage(off)] + |x| gen_curve_point::(x), + ), + z: gen_curve_point::(self), + s: self.gen_wrap( + #[coverage(off)] + |x| gen_curve_point::(x), + ), + generic_selector: gen_curve_point::(self), + poseidon_selector: gen_curve_point::(self), + complete_add_selector: gen_curve_point::(self), + mul_selector: gen_curve_point::(self), + emul_selector: gen_curve_point::(self), + endomul_scalar_selector: gen_curve_point::(self), + } + } +} + +impl Generator for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> PicklesWrapWireProofStableV1 { + PicklesWrapWireProofStableV1 { + bulletproof: self.gen(), + evaluations: self.gen(), + ft_eval1: self.gen(), + commitments: self.gen(), + } + } +} + +impl Generator for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> PicklesProofProofsVerifiedMaxStableV2 { + PicklesProofProofsVerifiedMaxStableV2 { + statement: self.gen(), + prev_evals: self.gen(), + proof: self.gen(), + } + } +} + +impl Generator for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> zkapp_command::SideLoadedProof { + Arc::new(self.gen()) + } +} + +impl Generator> for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> SetVerificationKey { + SetVerificationKey { + auth: self.gen(), + txn_version: if self.gen.rng.gen_bool(0.9) { + TXN_VERSION_CURRENT + } else { + TxnVersion::from(self.gen.rng.gen()) + }, + } + } +} + +impl Generator for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> VerificationKeyWire { + let vk = VerificationKeyWire::new(self.gen()); + vk.hash(); + vk + } +} + +impl Generator for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> Balance { + GeneratorRange64::::gen_range(self, 0..=Balance::max().as_u64()) + } +} + +impl Generator for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> Amount { + GeneratorRange64::::gen_range(self, 0..=Amount::max().as_u64()) + } +} + +pub trait GeneratorFromAccount { + fn gen_from_account(&mut self, account: &Account) -> T; +} + +impl GeneratorFromAccount for FuzzerCtx { + #[coverage(off)] + fn gen_from_account(&mut self, account: &Account) -> zkapp_command::AuthorizationKind { + let vk_hash = if self.gen.rng.gen_bool(0.9) + && account.zkapp.is_some() + && account.zkapp.as_ref().unwrap().verification_key.is_some() + { + account + .zkapp + .as_ref() + .unwrap() + .verification_key + .as_ref() + .unwrap() + .hash() + } else { + self.gen() + }; + + let options = vec![ + zkapp_command::AuthorizationKind::NoneGiven, + zkapp_command::AuthorizationKind::Signature, + zkapp_command::AuthorizationKind::Proof(vk_hash), + ]; + + options.choose(&mut self.gen.rng).unwrap().clone() + } +} + +impl GeneratorFromAccount> for FuzzerCtx { + #[coverage(off)] + fn gen_from_account(&mut self, account: &Account) -> Permissions { + let permission_model = match self.find_permissions(&account.public_key) { + Some(model) => model, + None => [ + PermissionModel::Any, + PermissionModel::Empty, + PermissionModel::Initial, + PermissionModel::TokenOwner, + ] + .choose(&mut self.gen.rng) + .unwrap(), + }; + + match permission_model { + PermissionModel::Any => Permissions:: { + edit_state: self.gen(), + access: self.gen(), + send: self.gen(), + receive: self.gen(), + set_delegate: self.gen(), + set_permissions: self.gen(), + set_verification_key: self.gen(), + set_zkapp_uri: self.gen(), + edit_action_state: self.gen(), + set_token_symbol: self.gen(), + increment_nonce: self.gen(), + set_voting_for: self.gen(), + set_timing: self.gen(), + }, + PermissionModel::Empty => Permissions::::empty(), + PermissionModel::Initial => Permissions::::user_default(), + PermissionModel::Default => Permissions:: { + edit_state: AuthRequired::Proof, + access: AuthRequired::None, + send: AuthRequired::Signature, + receive: AuthRequired::None, + set_delegate: AuthRequired::Signature, + set_permissions: AuthRequired::Signature, + set_verification_key: SetVerificationKey:: { + auth: AuthRequired::Signature, + txn_version: TXN_VERSION_CURRENT, + }, + set_zkapp_uri: AuthRequired::Signature, + edit_action_state: AuthRequired::Proof, + set_token_symbol: AuthRequired::Signature, + increment_nonce: AuthRequired::Signature, + set_voting_for: AuthRequired::Signature, + set_timing: AuthRequired::Proof, + }, + PermissionModel::TokenOwner => Permissions:: { + edit_state: AuthRequired::Either, // Proof | Signature + access: AuthRequired::Either, // Proof | Signature + send: AuthRequired::Signature, + receive: AuthRequired::Proof, + set_delegate: AuthRequired::Signature, + set_permissions: AuthRequired::Signature, + set_verification_key: SetVerificationKey:: { + auth: AuthRequired::Signature, + txn_version: TXN_VERSION_CURRENT, + }, + set_zkapp_uri: AuthRequired::Signature, + edit_action_state: AuthRequired::Proof, + set_token_symbol: AuthRequired::Signature, + increment_nonce: AuthRequired::Signature, + set_voting_for: AuthRequired::Signature, + set_timing: AuthRequired::Proof, + }, + } + } +} + +impl GeneratorFromAccount> for FuzzerCtx +where + FuzzerCtx: GeneratorFromAccount, +{ + #[coverage(off)] + fn gen_from_account(&mut self, account: &Account) -> ClosedInterval { + ClosedInterval { + lower: self.gen_from_account(account), + upper: self.gen_from_account(account), + } + } +} + +impl GeneratorFromAccount for FuzzerCtx { + #[coverage(off)] + fn gen_from_account(&mut self, account: &Account) -> Fee { + GeneratorRange64::::gen_range(self, 0..=account.balance.as_u64() / 100) + } +} + +impl GeneratorFromAccount for FuzzerCtx { + #[coverage(off)] + fn gen_from_account(&mut self, account: &Account) -> Balance { + GeneratorRange64::::gen_range(self, 0..=(account.balance.as_u64() / 100)) + } +} + +impl GeneratorFromAccount> for FuzzerCtx { + #[coverage(off)] + fn gen_from_account(&mut self, account: &Account) -> Signed { + if self.gen.rng.gen_bool(0.9) { + Signed::::zero() + } else { + if self.gen.token_id == TokenId::default() { + if self.gen.excess_fee.is_zero() { + let magnitude = GeneratorRange64::::gen_range( + self, + 0..=(account.balance.as_u64().wrapping_div(10).saturating_mul(7)), + ); + self.gen.excess_fee = Signed::::create(magnitude, self.gen()); + self.gen.excess_fee + } else { + let ret = self.gen.excess_fee.negate(); + self.gen.excess_fee = Signed::::zero(); + ret + } + } else { + // Custom Tokens + let magnitude = GeneratorRange64::::gen_range(self, 0..=u64::MAX); + Signed::::create(magnitude, self.gen()) + } + } + } +} + +impl GeneratorFromAccount for FuzzerCtx { + #[coverage(off)] + fn gen_from_account(&mut self, account: &Account) -> Amount { + if self.gen.token_id == TokenId::default() && self.gen.rng.gen_bool(0.9) { + GeneratorRange64::::gen_range(self, 0..=account.balance.as_u64()) + } else { + // Custom Tokens + GeneratorRange64::::gen_range(self, 0..=u64::MAX) + } + } +} + +impl GeneratorFromAccount for FuzzerCtx { + #[coverage(off)] + fn gen_from_account(&mut self, account: &Account) -> zkapp_command::Timing { + if self.gen.rng.gen_bool(0.5) { + zkapp_command::Timing { + initial_minimum_balance: Balance::zero(), + cliff_time: Slot::zero(), + cliff_amount: Amount::zero(), + vesting_period: SlotSpan::from_u32(1), + vesting_increment: Amount::zero(), + } + } else { + zkapp_command::Timing { + initial_minimum_balance: self.gen_from_account(account), + cliff_time: self.gen(), + cliff_amount: self.gen_from_account(account), + vesting_period: self.gen(), + vesting_increment: self.gen_from_account(account), + } + } + } +} + +impl GeneratorFromAccount for FuzzerCtx { + #[coverage(off)] + fn gen_from_account(&mut self, account: &Account) -> Nonce { + let nonce = match self.gen.nonces.get(&account.public_key.into_address()) { + Some(nonce) => *nonce, + None => account.nonce, + }; + // We assume successful aplication + self.gen + .nonces + .insert(account.public_key.into_address(), nonce.incr()); + nonce + } +} + +impl GeneratorFromAccount for FuzzerCtx { + #[coverage(off)] + fn gen_from_account(&mut self, account: &Account) -> Update { + Update { + app_state: if self.gen.rng.gen_bool(0.9) { + array::from_fn( + #[coverage(off)] + |_| { + self.gen_wrap( + #[coverage(off)] + |x| x.gen(), + ) + }, + ) + } else { + // cover changing_entire_app_state + array::from_fn( + #[coverage(off)] + |_| SetOrKeep::Set(self.gen()), + ) + }, + delegate: self.gen_wrap( + #[coverage(off)] + |x| x.gen(), + ), + verification_key: self.gen_wrap( + #[coverage(off)] + |x| x.gen(), + ), + permissions: self.gen_wrap( + #[coverage(off)] + |x| x.gen_from_account(account), + ), + zkapp_uri: self.gen_wrap( + #[coverage(off)] + |x| x.gen(), + ), + token_symbol: self.gen_wrap( + #[coverage(off)] + |x| x.gen(), + ), + timing: self.gen_wrap( + #[coverage(off)] + |x| x.gen_from_account(account), + ), + voting_for: self.gen_wrap( + #[coverage(off)] + |x| x.gen(), + ), + } + } +} + +impl GeneratorFromAccount for FuzzerCtx { + #[coverage(off)] + fn gen_from_account(&mut self, account: &Account) -> zkapp_command::ZkAppPreconditions { + if self.gen.rng.gen_bool(0.9) { + zkapp_command::ZkAppPreconditions::accept() + } else { + zkapp_command::ZkAppPreconditions { + snarked_ledger_hash: self.gen_wrap( + #[coverage(off)] + |x| { + if x.gen.rng.gen_bool(0.9) { + x.txn_state_view.snarked_ledger_hash + } else { + x.gen() + } + }, + ), + blockchain_length: self.gen_wrap( + #[coverage(off)] + |x| { + x.gen_wrap( + #[coverage(off)] + |x| { + if x.gen.rng.gen_bool(0.9) { + x.txn_state_view.blockchain_length + } else { + x.gen() + } + }, + ) + }, + ), + min_window_density: self.gen_wrap( + #[coverage(off)] + |x| { + x.gen_wrap( + #[coverage(off)] + |x| { + if x.gen.rng.gen_bool(0.9) { + x.txn_state_view.min_window_density + } else { + x.gen() + } + }, + ) + }, + ), + total_currency: self.gen_wrap( + #[coverage(off)] + |x| { + x.gen_wrap( + #[coverage(off)] + |x| { + if x.gen.rng.gen_bool(0.9) { + x.txn_state_view.total_currency + } else { + x.gen_from_account(account) + } + }, + ) + }, + ), + global_slot_since_genesis: self.gen_wrap( + #[coverage(off)] + |x| { + x.gen_wrap( + #[coverage(off)] + |x| x.gen(), + ) + }, + ), + + staking_epoch_data: if self.gen.rng.gen_bool(0.9) { + let epoch_data = self.txn_state_view.staking_epoch_data.clone(); + + zkapp_command::EpochData::new( + zkapp_command::EpochLedger { + hash: OrIgnore::Check(epoch_data.ledger.hash), + total_currency: OrIgnore::Check(ClosedInterval:: { + lower: epoch_data.ledger.total_currency.clone(), + upper: epoch_data.ledger.total_currency, + }), + }, + OrIgnore::Check(epoch_data.seed), + OrIgnore::Check(epoch_data.start_checkpoint), + OrIgnore::Check(epoch_data.lock_checkpoint), + OrIgnore::Check(ClosedInterval:: { + lower: epoch_data.epoch_length.clone(), + upper: epoch_data.epoch_length, + }), + ) + } else { + self.gen() + }, + next_epoch_data: if self.gen.rng.gen_bool(0.9) { + let epoch_data = self.txn_state_view.next_epoch_data.clone(); + zkapp_command::EpochData::new( + zkapp_command::EpochLedger { + hash: OrIgnore::Check(epoch_data.ledger.hash), + total_currency: OrIgnore::Check(ClosedInterval:: { + lower: epoch_data.ledger.total_currency.clone(), + upper: epoch_data.ledger.total_currency, + }), + }, + OrIgnore::Check(epoch_data.seed), + OrIgnore::Check(epoch_data.start_checkpoint), + OrIgnore::Check(epoch_data.lock_checkpoint), + OrIgnore::Check(ClosedInterval:: { + lower: epoch_data.epoch_length.clone(), + upper: epoch_data.epoch_length, + }), + ) + } else { + self.gen() + }, + } + } + } +} + +impl GeneratorFromAccount for FuzzerCtx { + #[coverage(off)] + fn gen_from_account(&mut self, account: &Account) -> zkapp_command::Account { + if self.gen.rng.gen_bool(0.9) { + zkapp_command::Account::accept() + } else { + zkapp_command::Account { + balance: self.gen_wrap( + #[coverage(off)] + |x| { + x.gen_wrap( + #[coverage(off)] + |x| x.gen_from_account(account), + ) + }, + ), + nonce: self.gen_wrap( + #[coverage(off)] + |x| { + x.gen_wrap( + #[coverage(off)] + |x| x.gen_from_account(account), + ) + }, + ), + receipt_chain_hash: self.gen_wrap( + #[coverage(off)] + |x| { + if x.gen.rng.gen_bool(0.9) { + account.receipt_chain_hash.0 + } else { + x.gen() + } + }, + ), + delegate: self.gen_wrap( + #[coverage(off)] + |x| { + let rnd_pk: CompressedPubKey = x.gen(); + account + .delegate + .as_ref() + .map( + #[coverage(off)] + |pk| { + if x.gen.rng.gen_bool(0.9) { + pk.clone() + } else { + rnd_pk.clone() + } + }, + ) + .unwrap_or(rnd_pk) + }, + ), + state: { + let rnd_state = array::from_fn( + #[coverage(off)] + |_| { + self.gen_wrap( + #[coverage(off)] + |x| x.gen(), + ) + }, + ); + + account + .zkapp + .as_ref() + .map( + #[coverage(off)] + |zkapp| { + if self.gen.rng.gen_bool(0.9) { + zkapp.app_state.map(OrIgnore::Check) + } else { + rnd_state.clone() + } + }, + ) + .unwrap_or(rnd_state) + }, + action_state: self.gen_wrap( + #[coverage(off)] + |x| x.gen(), + ), + proved_state: self.gen_wrap( + #[coverage(off)] + |x| { + let rnd_bool = x.gen.rng.gen_bool(0.5); + account + .zkapp + .as_ref() + .map( + #[coverage(off)] + |zkapp| { + if x.gen.rng.gen_bool(0.9) { + zkapp.proved_state + } else { + rnd_bool + } + }, + ) + .unwrap_or(rnd_bool) + }, + ), + is_new: self.gen_wrap( + #[coverage(off)] + |x| x.gen.rng.gen_bool(0.5), + ), + } + } + } +} + +impl GeneratorFromAccount for FuzzerCtx { + #[coverage(off)] + fn gen_from_account(&mut self, account: &Account) -> AccountPreconditions { + AccountPreconditions(self.gen_from_account(account)) + } +} + +impl GeneratorFromAccount for FuzzerCtx { + #[coverage(off)] + fn gen_from_account(&mut self, account: &Account) -> zkapp_command::Preconditions { + zkapp_command::Preconditions::new( + self.gen_from_account(account), + self.gen_from_account(account), + self.gen_wrap( + #[coverage(off)] + |x| { + x.gen_wrap( + #[coverage(off)] + |x| x.gen(), + ) + }, + ), + ) + } +} + +impl Generator for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> TokenId { + if self.gen.rng.gen_bool(0.9) { + TokenId::default() + } else { + TokenId(self.gen()) + } + } +} + +impl Generator for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> MayUseToken { + if self.gen.token_id.is_default() { + MayUseToken::No + } else { + match vec![0, 1, 2].choose(&mut self.gen.rng).unwrap() { + 0 => MayUseToken::No, + 1 => MayUseToken::ParentsOwnToken, + 2 => MayUseToken::InheritFromParent, + _ => unimplemented!(), + } + } + } +} + +impl GeneratorFromAccount for FuzzerCtx { + #[coverage(off)] + fn gen_from_account(&mut self, account: &Account) -> zkapp_command::Body { + zkapp_command::Body { + public_key: account.public_key.clone(), + token_id: self.gen.token_id.clone(), + update: self.gen_from_account(&account), + balance_change: self.gen_from_account(&account), + increment_nonce: self.gen.rng.gen_bool(0.5), + events: self.gen(), + actions: self.gen(), + call_data: self.gen(), + preconditions: self.gen_from_account(&account), + use_full_commitment: self.gen.rng.gen_bool(0.9), + implicit_account_creation_fee: self.gen.rng.gen_bool(0.1), + may_use_token: self.gen(), + authorization_kind: self.gen_from_account(&account), + } + } +} + +impl Generator for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> zkapp_command::Body { + let account = Account::empty(); + + zkapp_command::Body { + public_key: self.gen(), + token_id: self.gen(), + update: self.gen_from_account(&account), + balance_change: self.gen_from_account(&account), + increment_nonce: self.gen.rng.gen_bool(0.5), + events: self.gen(), + actions: self.gen(), + call_data: self.gen(), + preconditions: self.gen_from_account(&account), + use_full_commitment: self.gen.rng.gen_bool(0.9), + implicit_account_creation_fee: self.gen.rng.gen_bool(0.1), + may_use_token: self.gen(), + authorization_kind: self.gen_from_account(&account), + } + } +} + +pub trait GeneratorFromToken { + fn gen_from_token(&mut self, token_id: TokenId, depth: usize) -> T; +} + +impl GeneratorFromToken for FuzzerCtx { + #[coverage(off)] + fn gen_from_token(&mut self, token_id: TokenId, _depth: usize) -> AccountUpdate { + self.gen.token_id = token_id; + + let public_key = if self.gen.attempt_valid_zkapp || self.gen.rng.gen_bool(0.9) { + self.random_keypair().public.into_compressed() + } else { + self.gen() + }; + + // let public_key = self.random_keypair().public.into_compressed(); + let account = self.get_account(&public_key); + let body: zkapp_command::Body = if account.is_some() && self.gen.rng.gen_bool(0.9) { + self.gen_from_account(account.as_ref().unwrap()) + } else { + self.gen() + }; + + let authorization = match body.authorization_kind { + zkapp_command::AuthorizationKind::NoneGiven => zkapp_command::Control::NoneGiven, + zkapp_command::AuthorizationKind::Signature => { + zkapp_command::Control::Signature(Signature::dummy()) + } + zkapp_command::AuthorizationKind::Proof(_) => zkapp_command::Control::Proof(self.gen()), + }; + + AccountUpdate { + body, + authorization, + } + } +} + +impl GeneratorFromToken> for FuzzerCtx { + #[coverage(off)] + fn gen_from_token( + &mut self, + token_id: TokenId, + depth: usize, + ) -> zkapp_command::CallForest { + let mut forest = zkapp_command::CallForest::::new(); + let max_count = 3usize.saturating_sub(depth); + let count = self.gen.rng.gen_range(0..=max_count); + + for _ in 0..count { + let account_update: AccountUpdate = self.gen_from_token(token_id.clone(), depth); + let token_id = account_update.account_id().derive_token_id(); + let calls = if self.gen.rng.gen_bool(0.8) || depth >= 3 { + None + } else { + // recursion + Some(self.gen_from_token(token_id, depth + 1)) + }; + + forest = forest.cons(calls, account_update); + } + + forest + } +} + +#[coverage(off)] +pub fn sign_account_updates( + ctx: &mut FuzzerCtx, + signer: &mut impl Signer, + txn_commitment: &TransactionCommitment, + full_txn_commitment: &TransactionCommitment, + account_updates: &mut zkapp_command::CallForest, +) { + for acc_update in account_updates.0.iter_mut() { + let account_update = &mut acc_update.elt.account_update; + + let signature = match account_update.authorization { + zkapp_command::Control::Signature(_) => { + let kp = ctx + .find_keypair(&account_update.body.public_key) + .cloned() + .unwrap_or_else(|| ctx.gen()); + let input = match account_update.body.use_full_commitment { + true => full_txn_commitment, + false => txn_commitment, + }; + Some(signer.sign(&kp, &input)) + } + _ => None, + }; + + if let Some(signature) = signature { + account_update.authorization = zkapp_command::Control::Signature(signature); + } + + sign_account_updates( + ctx, + signer, + txn_commitment, + full_txn_commitment, + &mut acc_update.elt.calls, + ); + } +} + +impl Generator for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> ZkAppCommand { + self.gen.attempt_valid_zkapp = self.gen.rng.gen_bool(0.9); + + let mut zkapp_command = ZkAppCommand { + fee_payer: self.gen(), + account_updates: self.gen_from_token(TokenId::default(), 0), + memo: self.gen(), + }; + let (txn_commitment, full_txn_commitment) = get_transaction_commitments(&zkapp_command); + let mut signer = mina_signer::create_kimchi(NetworkId::TESTNET); + + let keypair = match self.find_keypair(&zkapp_command.fee_payer.body.public_key) { + Some(keypair) => keypair.clone(), + None => self.gen(), + }; + zkapp_command.fee_payer.authorization = signer.sign(&keypair, &full_txn_commitment); + + sign_account_updates( + self, + &mut signer, + &txn_commitment, + &full_txn_commitment, + &mut zkapp_command.account_updates, + ); + zkapp_command + } +} + +impl GeneratorFromAccount for FuzzerCtx { + #[coverage(off)] + fn gen_from_account(&mut self, account: &Account) -> PaymentPayload { + let is_source_account = self.gen.rng.gen_bool(0.9); + + let source_pk = if is_source_account { + account.public_key.clone() + } else { + self.gen() + }; + + let receiver_pk = if is_source_account && self.gen.rng.gen_bool(0.9) { + // same source & receiver + source_pk.clone() + } else { + self.gen() + }; + + PaymentPayload { + receiver_pk, + amount: self.gen_from_account(account), + } + } +} + +impl Generator for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> StakeDelegationPayload { + StakeDelegationPayload::SetDelegate { + new_delegate: self.gen(), + } + } +} + +#[coverage(off)] +fn sign_payload(keypair: &Keypair, payload: &SignedCommandPayload) -> Signature { + let tx = TransactionUnionPayload::of_user_command_payload(payload); + let mut signer = mina_signer::create_legacy(NetworkId::TESTNET); + signer.sign(keypair, &tx) +} + +impl Generator for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> SignedCommandPayload { + let fee_payer_pk = if self.gen.rng.gen_bool(0.8) { + self.random_keypair().public.into_compressed() + } else { + self.gen() + }; + + let account = self.get_account(&fee_payer_pk); + + let body = if account.is_some() && self.gen.rng.gen_bool(0.8) { + signed_command::Body::Payment(self.gen_from_account(account.as_ref().unwrap())) + } else { + signed_command::Body::StakeDelegation(self.gen()) + }; + + let fee = match account.as_ref() { + Some(account) => self.gen_from_account(account), + None => GeneratorRange64::gen_range(self, 0u64..=10_000_000u64), + }; + + let nonce = match account.as_ref() { + Some(account) => self.gen_from_account(account), + None => GeneratorRange32::gen_range(self, 0u32..=10_000_000u32), + }; + + SignedCommandPayload::create( + fee, + fee_payer_pk, + nonce, + self.gen_wrap( + #[coverage(off)] + |x| x.gen(), + ), + self.gen(), + body, + ) + } +} + +impl Generator for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> SignedCommand { + let payload: SignedCommandPayload = self.gen(); + let keypair = if self.gen.rng.gen_bool(0.9) { + self.find_keypair(&payload.common.fee_payer_pk) + .cloned() + .unwrap_or_else(|| self.gen()) + } else { + self.gen() + }; + + let signature = sign_payload(&keypair, &payload); + + SignedCommand { + payload, + signer: keypair.public.into_compressed(), + signature, + } + } +} + +impl Generator for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> UserCommand { + match vec![0, 1].choose(&mut self.gen.rng).unwrap() { + 0 => UserCommand::SignedCommand(Box::new(self.gen())), + 1 => UserCommand::ZkAppCommand(Box::new(self.gen())), + _ => unimplemented!(), + } + } +} + +impl Generator for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> Transaction { + Transaction::Command(self.gen()) + } +} + +impl Generator> for FuzzerCtx { + fn gen(&mut self) -> Number { + Number(self.gen.rng.gen()) + } +} + +impl Generator> for FuzzerCtx { + fn gen(&mut self) -> Number { + Number(self.gen.rng.gen()) + } +} + +impl Generator> for FuzzerCtx { + fn gen(&mut self) -> Number { + Number(self.gen.rng.gen()) + } +} + +impl Generator> for FuzzerCtx { + fn gen(&mut self) -> Number { + Number(self.gen.rng.gen()) + } +} + +impl_default_generator_for_wrapper_type!(FuzzerCtx, MinaBaseLedgerHash0StableV1); +impl_default_generator_for_wrapper_type!(FuzzerCtx, LedgerHash, MinaBaseLedgerHash0StableV1); + +impl_default_generator_for_wrapper_type!(FuzzerCtx, MinaBaseAccountIdDigestStableV1); +impl_default_generator_for_wrapper_type!( + FuzzerCtx, + TokenIdKeyHash, + MinaBaseAccountIdDigestStableV1 +); + +impl_default_generator_for_wrapper_type!(FuzzerCtx, MinaBasePendingCoinbaseStackHashStableV1); +impl_default_generator_for_wrapper_type!(FuzzerCtx, MinaBasePendingCoinbaseCoinbaseStackStableV1); +impl_default_generator_for_wrapper_type!(FuzzerCtx, MinaBaseStackFrameStableV1); +impl_default_generator_for_wrapper_type!(FuzzerCtx, MinaBaseCallStackDigestStableV1); + +impl_default_generator_for_wrapper_type!( + FuzzerCtx, + UnsignedExtendedUInt64Int64ForVersionTagsStableV1 +); +impl_default_generator_for_wrapper_type!(FuzzerCtx, CurrencyAmountStableV1); +impl_default_generator_for_wrapper_type!(FuzzerCtx, CurrencyFeeStableV1); + +impl_default_generator_for_wrapper_type!(FuzzerCtx, UnsignedExtendedUInt32StableV1); + +impl Generator for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> MinaBasePendingCoinbaseStateStackStableV1 { + let init: MinaBasePendingCoinbaseStackHashStableV1 = self.gen(); + let curr: MinaBasePendingCoinbaseStackHashStableV1 = self.gen(); + + MinaBasePendingCoinbaseStateStackStableV1 { + init: init.into(), + curr: curr.into(), + } + } +} + +impl Generator for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> MinaBasePendingCoinbaseStackVersionedStableV1 { + let data: MinaBasePendingCoinbaseCoinbaseStackStableV1 = self.gen(); + + MinaBasePendingCoinbaseStackVersionedStableV1 { + data: data.into(), + state: self.gen(), + } + } +} + +impl Generator for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> SgnStableV1 { + match self.gen.rng.gen_bool(0.5) { + true => SgnStableV1::Pos, + false => SgnStableV1::Neg, + } + } +} + +impl Generator for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> SignedAmount { + SignedAmount { + magnitude: self.gen(), + sgn: self.gen(), + } + } +} + +impl Generator for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> MinaBaseFeeExcessStableV1 { + MinaBaseFeeExcessStableV1( + TokenFeeExcess { + token: self.gen(), + amount: self.gen(), + }, + TokenFeeExcess { + token: self.gen(), + amount: self.gen(), + }, + ) + } +} + +impl Generator for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> MinaTransactionLogicZkappCommandLogicLocalStateValueStableV1 { + MinaTransactionLogicZkappCommandLogicLocalStateValueStableV1 { + stack_frame: self.gen(), + call_stack: self.gen(), + transaction_commitment: self.gen(), + full_transaction_commitment: self.gen(), + excess: self.gen(), + supply_increase: self.gen(), + ledger: self.gen(), + success: self.gen(), + account_update_index: self.gen(), + failure_status_tbl: MinaBaseTransactionStatusFailureCollectionStableV1(List::new()), + will_succeed: self.gen(), + } + } +} + +impl Generator for FuzzerCtx { + #[coverage(off)] + fn gen(&mut self) -> MinaStateBlockchainStateValueStableV2LedgerProofStatementSource { + MinaStateBlockchainStateValueStableV2LedgerProofStatementSource { + first_pass_ledger: self.gen(), + second_pass_ledger: self.gen(), + pending_coinbase_stack: self.gen(), + local_state: self.gen(), + } + } +} diff --git a/tools/fuzzing/src/transaction_fuzzer/invariants.rs b/tools/fuzzing/src/transaction_fuzzer/invariants.rs new file mode 100644 index 000000000..8e9bc10cd --- /dev/null +++ b/tools/fuzzing/src/transaction_fuzzer/invariants.rs @@ -0,0 +1,353 @@ +use ledger::{ + scan_state::{ + currency::TxnVersion, + transaction_logic::zkapp_command::{AccountUpdate, Control}, + }, + Account, AccountId, AuthRequired, ControlTag, Permissions, SetVerificationKey, TokenId, + ZkAppAccount, TXN_VERSION_CURRENT, +}; +use once_cell::sync::Lazy; +use std::sync::{Mutex, RwLock}; +use text_diff::{diff, Difference}; + +pub struct Checks { + pub caller_id: TokenId, + pub account_id: AccountId, + pub account_before: Account, + pub account_after: Option, + pub check_account: Permissions, + pub acc_update: AccountUpdate, // added for extra diagnostics +} + +pub static BREAK: Lazy> = Lazy::new( + #[coverage(off)] + || RwLock::new(false), +); +pub static STATE: Lazy>> = Lazy::new( + #[coverage(off)] + || RwLock::new(Vec::new()), +); +pub static LAST_RESULT: LastResult = LastResult::new(); + +pub struct LastResult { + result: Mutex>>, +} + +impl LastResult { + pub const fn new() -> Self { + Self { + result: Mutex::new(None), + } + } + + fn set(&self, result: Result<(), String>) { + *self.result.lock().unwrap() = Some(result); + } + + pub fn get(&self) -> Option> { + self.result.lock().unwrap().clone() + } + + pub fn take(&self) -> Option> { + self.result.lock().unwrap().take() + } +} + +impl Checks { + // We are duplicating the implementation here because we don't want changes in the logic to affect invariant checks + #[coverage(off)] + fn check_permission(auth: AuthRequired, tag: ControlTag) -> bool { + use AuthRequired as Auth; + use ControlTag as Tag; + + match (auth, tag) { + (Auth::Impossible, _) => false, + (Auth::None, _) => true, + (Auth::Proof, Tag::Proof) => true, + (Auth::Signature, Tag::Signature) => true, + (Auth::Either, Tag::Proof | Tag::Signature) => true, + (Auth::Signature, Tag::Proof) => false, + (Auth::Proof, Tag::Signature) => false, + (Auth::Proof | Auth::Signature | Auth::Either, Tag::NoneGiven) => false, + (Auth::Both, _) => unimplemented!(), + } + } + + #[coverage(off)] + fn setvk_auth(auth: AuthRequired, txn_version: TxnVersion) -> AuthRequired { + if txn_version <= TXN_VERSION_CURRENT { + if txn_version == TXN_VERSION_CURRENT { + auth + } else { + AuthRequired::Signature + } + } else { + panic!("invalid txn_version: {}", txn_version.as_u32()); + } + } + + #[coverage(off)] + pub fn new(caller_id: &TokenId, account_update: &AccountUpdate, account: &Account) -> Self { + let caller_id = caller_id.clone(); + let account_id = account_update.account_id(); + let account_before = account.clone(); + let tag = match account_update.authorization { + Control::Signature(_) => ControlTag::Signature, + Control::Proof(_) => ControlTag::Proof, + Control::NoneGiven => ControlTag::NoneGiven, + }; + let Permissions:: { + edit_state, + access, + send, + receive, + set_delegate, + set_permissions, + set_verification_key: SetVerificationKey { auth, txn_version }, + set_zkapp_uri, + edit_action_state, + set_token_symbol, + increment_nonce, + set_voting_for, + set_timing, + } = account_before.permissions; + let check_account = Permissions:: { + edit_state: !Self::check_permission(edit_state, tag), + access: !Self::check_permission(access, tag), + send: !Self::check_permission(send, tag), + receive: !Self::check_permission(receive, tag), + set_delegate: !Self::check_permission(set_delegate, tag), + set_permissions: !Self::check_permission(set_permissions, tag), + set_verification_key: SetVerificationKey { + auth: !Self::check_permission(Self::setvk_auth(auth, txn_version), tag), + txn_version, + }, + set_zkapp_uri: !Self::check_permission(set_zkapp_uri, tag), + edit_action_state: !Self::check_permission(edit_action_state, tag), + set_token_symbol: !Self::check_permission(set_token_symbol, tag), + increment_nonce: !Self::check_permission(increment_nonce, tag), + set_voting_for: !Self::check_permission(set_voting_for, tag), + set_timing: !Self::check_permission(set_timing, tag), + }; + + Self { + caller_id, + account_id, + account_before, + account_after: None, + check_account, + acc_update: account_update.clone(), + } + } + + #[coverage(off)] + fn check_authorization(caller_id: &TokenId) -> Result<(), String> { + for check in STATE.read().unwrap().iter().rev() { + // find parent's check + if caller_id == &check.account_id.derive_token_id() { + if check.check_account.access { + return Err("Invariant violation: caller access permission".to_string()); + } + + return Ok(()); + } + } + + panic!() + } + + #[coverage(off)] + pub fn add_after_account(&mut self, account: Account) { + self.account_after = Some(account); + } + + #[coverage(off)] + fn diagnostic(&self, err: &str) -> String { + let orig = format!("{:#?}", self.account_before); + let edit = format!("{:#?}", self.account_after.as_ref().unwrap()); + let split = " "; + let (_, changeset) = diff(orig.as_str(), edit.as_str(), split); + + let mut ret = String::new(); + + for seq in changeset { + match seq { + Difference::Same(ref x) => { + ret.push_str(x); + ret.push_str(split); + } + Difference::Add(ref x) => { + ret.push_str("\x1B[92m"); + ret.push_str(x); + ret.push_str("\x1B[0m"); + ret.push_str(split); + } + Difference::Rem(ref x) => { + ret.push_str("\x1B[91m"); + ret.push_str(x); + ret.push_str("\x1B[0m"); + ret.push_str(split); + } + } + } + + format!("{}\n{}\nUpdate:\n{:#?}\n", err, ret, self.acc_update) + } + + #[coverage(off)] + pub fn check_exit(&self) -> Result<(), String> { + let res = self._check_exit(); + LAST_RESULT.set(res.clone()); + res + } + + #[coverage(off)] + fn _check_exit(&self) -> Result<(), String> { + let Permissions:: { + edit_state, + access, + send, + receive, + set_delegate, + set_permissions, + set_verification_key: + SetVerificationKey { + auth: set_vk_auth, .. + }, + set_zkapp_uri, + edit_action_state, + set_token_symbol, + increment_nonce, + set_voting_for, + set_timing, + } = self.check_account; + + // Token owner approval of children account updates + if self.caller_id != TokenId::default() { + Self::check_authorization(&self.caller_id)?; + } + + let account = self.account_after.as_ref().unwrap(); + + if access && self.account_before != *account { + return Err(self.diagnostic("Invariant violation: access permission")); + } + + if send && self.account_before.balance > account.balance { + return Err(self.diagnostic("Invariant violation: send permission")); + } + + if receive && self.account_before.balance < account.balance { + return Err(self.diagnostic("Invariant violation: receive permission")); + } + + let is_change_from_none_to_default = self.account_before.delegate.is_none() + && account.delegate.as_ref() == Some(&account.public_key); + if set_delegate + && self.account_before.delegate != account.delegate + && !is_change_from_none_to_default + { + return Err(self.diagnostic("Invariant violation: set_delegate permission")); + } + + if set_permissions && self.account_before.permissions != account.permissions { + return Err(self.diagnostic("Invariant violation: set_permissions permission")); + } + + if set_token_symbol && self.account_before.token_symbol != account.token_symbol { + return Err(self.diagnostic("Invariant violation: set_token_symbol permission")); + } + + if increment_nonce && self.account_before.nonce != account.nonce { + return Err(self.diagnostic("Invariant violation: increment_nonce permission")); + } + + if set_voting_for && self.account_before.voting_for != account.voting_for { + return Err(self.diagnostic("Invariant violation: set_voting_for permission")); + } + + if set_timing && self.account_before.timing != account.timing { + return Err(self.diagnostic("Invariant violation: set_timing permission")); + } + + let default_values = ZkAppAccount::default(); + + if edit_state { + let invariant_violation = match &self.account_before.zkapp { + Some(zkapp) => account.zkapp.as_ref().map_or( + true, + #[coverage(off)] + |x| x.app_state != zkapp.app_state, + ), + None => account.zkapp.as_ref().map_or( + false, + #[coverage(off)] + |x| x.app_state != default_values.app_state, + ), + }; + + if invariant_violation { + return Err(self.diagnostic("Invariant violation: edit_state permission")); + } + } + + if set_vk_auth { + let invariant_violation = match &self.account_before.zkapp { + Some(zkapp) => account.zkapp.as_ref().map_or( + true, + #[coverage(off)] + |x| x.verification_key != zkapp.verification_key, + ), + None => account.zkapp.as_ref().map_or( + false, + #[coverage(off)] + |x| x.verification_key != default_values.verification_key, + ), + }; + + if invariant_violation { + return Err(self.diagnostic("Invariant violation: set_verification_key permission")); + } + } + + if set_zkapp_uri { + let invariant_violation = match &self.account_before.zkapp { + Some(zkapp) => account.zkapp.as_ref().map_or( + true, + #[coverage(off)] + |x| x.zkapp_uri != zkapp.zkapp_uri, + ), + None => account.zkapp.as_ref().map_or( + false, + #[coverage(off)] + |x| x.zkapp_uri != default_values.zkapp_uri, + ), + }; + + if invariant_violation { + return Err(self.diagnostic("Invariant violation: set_zkapp_uri permission")); + } + } + + if edit_action_state { + let invariant_violation = match &self.account_before.zkapp { + Some(zkapp) => account.zkapp.as_ref().map_or( + true, + #[coverage(off)] + |x| x.action_state != zkapp.action_state, + ), + None => account.zkapp.as_ref().map_or( + false, + #[coverage(off)] + |x| x.action_state != default_values.action_state, + ), + }; + + if invariant_violation { + return Err(self.diagnostic("Invariant violation: edit_action_state permission")); + } + } + + Ok(()) + } +} diff --git a/tools/fuzzing/src/transaction_fuzzer/mutator.rs b/tools/fuzzing/src/transaction_fuzzer/mutator.rs new file mode 100644 index 000000000..ab938d6e0 --- /dev/null +++ b/tools/fuzzing/src/transaction_fuzzer/mutator.rs @@ -0,0 +1,1006 @@ +use super::{ + context::{FuzzerCtx, PermissionModel}, + generator::{ + sign_account_updates, Generator, GeneratorFromAccount, GeneratorRange32, GeneratorRange64, + GeneratorWrapper, + }, +}; +use crate::transaction_fuzzer::generator::gen_curve_point; +use ark_ff::Zero; +use ledger::{ + generators::zkapp_command_builder::get_transaction_commitments, + hash_with_kimchi, + scan_state::{ + currency::{Amount, Balance, Fee, MinMax, Nonce, Signed, Slot}, + transaction_logic::{ + zkapp_command::{ + self, AccountPreconditions, AccountUpdate, Body, ClosedInterval, FeePayer, + FeePayerBody, OrIgnore, Preconditions, SetOrKeep, Update, ZkAppCommand, + ZkAppPreconditions, + }, + Transaction, UserCommand, + }, + }, + Account, AuthRequired, MutableFp, Permissions, Timing, TokenId, TokenSymbol, VerificationKey, + VerificationKeyWire, +}; +use mina_hasher::Fp; +use mina_p2p_messages::{ + array::ArrayN16, + bigint::BigInt, + pseq::PaddedSeq, + v2::{ + PicklesProofProofsVerified2ReprStableV2, PicklesProofProofsVerified2ReprStableV2PrevEvals, + PicklesProofProofsVerified2ReprStableV2PrevEvalsEvals, + PicklesProofProofsVerified2ReprStableV2PrevEvalsEvalsEvals, + PicklesWrapWireProofCommitmentsStableV1, PicklesWrapWireProofEvaluationsStableV1, + PicklesWrapWireProofStableV1, PicklesWrapWireProofStableV1Bulletproof, + TransactionSnarkProofStableV2, TransactionSnarkScanStateLedgerProofWithSokMessageStableV2, + TransactionSnarkStableV2, + }, +}; +use mina_signer::{CompressedPubKey, NetworkId, Signature, Signer}; +use rand::{seq::SliceRandom, Rng}; + +#[coverage(off)] +fn rand_elements(ctx: &mut FuzzerCtx, count: usize) -> Vec { + let elements: Vec = (0..count).collect(); + // We give more weight to smaller amount of elements since in general we want to perform fewer mutations + if let Ok(amount) = elements.choose_weighted( + &mut ctx.gen.rng, + #[coverage(off)] + |x| elements.len() - x, + ) { + elements + .choose_multiple(&mut ctx.gen.rng, *amount) + .cloned() + .collect() + } else { + Vec::new() + } +} + +pub trait Mutator { + fn mutate(&mut self, t: &mut T); +} + +impl Mutator for FuzzerCtx { + #[coverage(off)] + fn mutate(&mut self, t: &mut BigInt) { + *t = self.gen(); + } +} + +impl Mutator<(BigInt, BigInt)> for FuzzerCtx { + #[coverage(off)] + fn mutate(&mut self, t: &mut (BigInt, BigInt)) { + *t = gen_curve_point::(self); + } +} + +impl Mutator<((BigInt, BigInt), (BigInt, BigInt))> for FuzzerCtx { + #[coverage(off)] + fn mutate(&mut self, t: &mut ((BigInt, BigInt), (BigInt, BigInt))) { + if self.gen.rng.gen_bool(0.5) { + self.mutate(&mut t.0 .0); + } + + if self.gen.rng.gen_bool(0.5) { + self.mutate(&mut t.0 .1); + } + + if self.gen.rng.gen_bool(0.5) { + self.mutate(&mut t.1 .0); + } + + if self.gen.rng.gen_bool(0.5) { + self.mutate(&mut t.1 .1); + } + } +} + +impl Mutator<(Vec, Vec)> for FuzzerCtx { + #[coverage(off)] + fn mutate(&mut self, t: &mut (Vec, Vec)) { + if self.gen.rng.gen_bool(0.5) { + self.mutate(&mut t.0); + } + + if self.gen.rng.gen_bool(0.5) { + self.mutate(&mut t.1); + } + } +} + +impl Mutator<(ArrayN16, ArrayN16)> for FuzzerCtx +where + FuzzerCtx: Mutator>, +{ + #[coverage(off)] + fn mutate(&mut self, t: &mut (ArrayN16, ArrayN16)) { + if self.gen.rng.gen_bool(0.5) { + self.mutate(&mut t.0); + } + + if self.gen.rng.gen_bool(0.5) { + self.mutate(&mut t.1); + } + } +} + +impl Mutator> for FuzzerCtx +where + FuzzerCtx: Mutator, +{ + #[coverage(off)] + fn mutate(&mut self, t: &mut Vec) { + if t.is_empty() { + // TODO(binier): maybe gen + // t.lr = self.gen(); + return; + } + for i in rand_elements(self, t.len()) { + self.mutate(&mut t[i]); + } + } +} + +impl Mutator> for FuzzerCtx +where + FuzzerCtx: Mutator, +{ + #[coverage(off)] + fn mutate(&mut self, t: &mut PaddedSeq) { + for i in rand_elements(self, N) { + self.mutate(&mut t.0[i]) + } + } +} + +impl Mutator for FuzzerCtx { + #[coverage(off)] + fn mutate(&mut self, t: &mut FeePayerBody) { + //let account = self.get_account(&t.public_key).unwrap(); + + for option in rand_elements(self, 2) { + match option { + 0 => t.fee = GeneratorRange64::::gen_range(self, 0..=Fee::max().as_u64()), + 1 => { + t.valid_until = if self.gen.rng.gen_bool(0.5) { + Some(Slot::from_u32( + self.gen.rng.gen_range(0..=Slot::max().as_u32()), + )) + } else { + None + } + } + //2 => { + // t.nonce = GeneratorRange32::::gen_range(self, 0..=Nonce::max().as_u32()) + //} + _ => unimplemented!(), + } + } + } +} + +impl Mutator for FuzzerCtx { + #[coverage(off)] + fn mutate(&mut self, t: &mut FeePayer) { + self.mutate(&mut t.body) + } +} + +impl Mutator for FuzzerCtx { + #[coverage(off)] + fn mutate(&mut self, t: &mut Fp) { + *t = self.gen(); + } +} + +impl Mutator> for FuzzerCtx +where + FuzzerCtx: Mutator + Generator, +{ + #[coverage(off)] + fn mutate(&mut self, t: &mut SetOrKeep) { + match t { + SetOrKeep::Set(inner) => { + if self.gen.rng.gen_bool(0.5) { + self.mutate(inner) + } else { + *t = SetOrKeep::Keep; + } + } + SetOrKeep::Keep => *t = SetOrKeep::Set(self.gen()), + } + } +} + +impl Mutator<[T; N]> for FuzzerCtx +where + FuzzerCtx: Mutator, +{ + #[coverage(off)] + fn mutate(&mut self, t: &mut [T; N]) { + for i in rand_elements(self, t.len()) { + self.mutate(&mut t[i]) + } + } +} + +pub trait MutatorFromAccount { + fn mutate_from_account(&mut self, t: &mut T, account: &Account); +} + +impl MutatorFromAccount> for FuzzerCtx { + #[coverage(off)] + fn mutate_from_account(&mut self, t: &mut Permissions, account: &Account) { + let permission_model = self.find_permissions(&account.public_key).unwrap(); + + match permission_model { + PermissionModel::Any => { + for option in rand_elements(self, 11) { + match option { + 0 => t.edit_state = self.gen(), + 1 => t.send = self.gen(), + 2 => t.receive = self.gen(), + 3 => t.set_delegate = self.gen(), + 4 => t.set_permissions = self.gen(), + 5 => t.set_verification_key = self.gen(), + 6 => t.set_zkapp_uri = self.gen(), + 7 => t.edit_action_state = self.gen(), + 8 => t.set_token_symbol = self.gen(), + 9 => t.increment_nonce = self.gen(), + 10 => t.set_voting_for = self.gen(), + _ => unimplemented!(), + } + } + } + // Don't mutate permissions in the rest of the models + PermissionModel::Empty => (), + PermissionModel::Initial => (), + PermissionModel::Default => (), + PermissionModel::TokenOwner => (), + } + } +} + +impl MutatorFromAccount> for FuzzerCtx +where + FuzzerCtx: MutatorFromAccount + GeneratorFromAccount, +{ + #[coverage(off)] + fn mutate_from_account(&mut self, t: &mut SetOrKeep, account: &Account) { + match t { + SetOrKeep::Set(inner) => { + if self.gen.rng.gen_bool(0.5) { + self.mutate_from_account(inner, account) + } else { + *t = SetOrKeep::Keep; + } + } + SetOrKeep::Keep => *t = SetOrKeep::Set(self.gen_from_account(account)), + } + } +} + +impl MutatorFromAccount for FuzzerCtx { + #[coverage(off)] + fn mutate_from_account(&mut self, t: &mut zkapp_command::Timing, _account: &Account) { + for option in rand_elements(self, 5) { + match option { + 0 => t.initial_minimum_balance = self.gen(), + 1 => t.cliff_time = self.gen(), + 2 => t.cliff_amount = self.gen(), + 3 => t.vesting_period = self.gen(), + 4 => t.vesting_increment = self.gen(), + _ => unimplemented!(), + } + } + } +} + +impl MutatorFromAccount for FuzzerCtx { + #[coverage(off)] + fn mutate_from_account(&mut self, t: &mut Timing, _account: &Account) { + if let Timing::Timed { + initial_minimum_balance, + cliff_time, + cliff_amount, + vesting_period, + vesting_increment, + } = t + { + for option in rand_elements(self, 5) { + match option { + 0 => *initial_minimum_balance = self.gen(), + 1 => *cliff_time = self.gen(), + 2 => *cliff_amount = self.gen(), + 3 => *vesting_period = self.gen(), + 4 => *vesting_increment = self.gen(), + _ => unimplemented!(), + } + } + } else { + if self.gen.rng.gen_bool(0.5) { + *t = Timing::Timed { + initial_minimum_balance: self.gen(), + cliff_time: self.gen(), + cliff_amount: self.gen(), + vesting_period: self.gen(), + vesting_increment: self.gen(), + } + } + } + } +} + +impl MutatorFromAccount for FuzzerCtx { + #[coverage(off)] + fn mutate_from_account(&mut self, t: &mut Update, account: &Account) { + for option in rand_elements(self, 8) { + match option { + 0 => self.mutate(&mut t.app_state), + 1 => { + let keypair = if self.gen.rng.gen_bool(0.5) { + self.random_keypair() + } else { + self.gen() + }; + + t.delegate = SetOrKeep::Set(keypair.public.into_compressed()) + } + 2 => { + let data: VerificationKey = self.gen(); + let hash = if self.gen.rng.gen_bool(0.5) { + data.digest() + } else { + self.gen() + }; + + t.verification_key = SetOrKeep::Set(VerificationKeyWire::with_hash(data, hash)); + } + 3 => self.mutate_from_account(&mut t.permissions, account), + 4 => { + t.zkapp_uri = self.gen_wrap( + #[coverage(off)] + |x| x.gen(), // TODO + ) + } + 5 => { + let rnd_len = self.gen.rng.gen_range(0..=6); + t.token_symbol = SetOrKeep::Set(TokenSymbol( + (0..rnd_len).map(|_| self.gen.rng.gen()).collect(), + )); + } + 6 => self.mutate_from_account(&mut t.timing, account), + 7 => t.voting_for = SetOrKeep::Set(self.gen()), + _ => unimplemented!(), + } + } + } +} + +impl Mutator> for FuzzerCtx +where + FuzzerCtx: Mutator + Generator, +{ + #[coverage(off)] + fn mutate(&mut self, t: &mut OrIgnore) { + match t { + OrIgnore::Check(inner) => { + if self.gen.rng.gen_bool(0.9) { + self.mutate(inner) + } else { + *t = OrIgnore::Ignore; + } + } + OrIgnore::Ignore => *t = OrIgnore::Check(self.gen()), + } + } +} + +impl MutatorFromAccount> for FuzzerCtx +where + FuzzerCtx: MutatorFromAccount + GeneratorFromAccount, +{ + #[coverage(off)] + fn mutate_from_account(&mut self, t: &mut OrIgnore, account: &Account) { + match t { + OrIgnore::Check(inner) => { + if self.gen.rng.gen_bool(0.9) { + self.mutate_from_account(inner, account) + } else { + *t = OrIgnore::Ignore; + } + } + OrIgnore::Ignore => *t = OrIgnore::Check(self.gen_from_account(account)), + } + } +} + +impl Mutator for FuzzerCtx { + #[coverage(off)] + fn mutate(&mut self, t: &mut CompressedPubKey) { + *t = self.gen(); + } +} + +impl Mutator for FuzzerCtx { + #[coverage(off)] + fn mutate(&mut self, t: &mut bool) { + *t = !*t; + } +} + +impl Mutator> for FuzzerCtx +where + FuzzerCtx: Mutator, +{ + #[coverage(off)] + fn mutate(&mut self, t: &mut ClosedInterval) { + for option in rand_elements(self, 8) { + match option { + 0 => self.mutate(&mut t.lower), + 1 => self.mutate(&mut t.upper), + _ => unimplemented!(), + } + } + } +} + +impl MutatorFromAccount for FuzzerCtx { + #[coverage(off)] + fn mutate_from_account(&mut self, t: &mut Balance, _account: &Account) { + //*t = self.gen_from_account(account); + *t = GeneratorRange64::::gen_range(self, 0..=Balance::max().as_u64()) + } +} + +impl MutatorFromAccount for FuzzerCtx { + #[coverage(off)] + fn mutate_from_account(&mut self, t: &mut Nonce, _account: &Account) { + //*t = self.gen_from_account(account); + *t = GeneratorRange32::::gen_range(self, 0..=u32::MAX); + } +} + +impl MutatorFromAccount> for FuzzerCtx +where + FuzzerCtx: MutatorFromAccount, +{ + #[coverage(off)] + fn mutate_from_account(&mut self, t: &mut ClosedInterval, account: &Account) { + for option in rand_elements(self, 8) { + match option { + 0 => self.mutate_from_account(&mut t.lower, account), + 1 => self.mutate_from_account(&mut t.upper, account), + _ => unimplemented!(), + } + } + } +} + +impl MutatorFromAccount for FuzzerCtx { + #[coverage(off)] + fn mutate_from_account(&mut self, t: &mut zkapp_command::Account, account: &Account) { + for option in rand_elements(self, 8) { + match option { + 0 => self.mutate_from_account(&mut t.balance, account), + 1 => self.mutate_from_account(&mut t.nonce, account), + 2 => self.mutate(&mut t.receipt_chain_hash), + 3 => self.mutate(&mut t.delegate), + 4 => self.mutate(&mut t.state), + 5 => self.mutate(&mut t.action_state), + 6 => self.mutate(&mut t.proved_state), + 7 => self.mutate(&mut t.is_new), + _ => unimplemented!(), + } + } + } +} + +impl MutatorFromAccount for FuzzerCtx { + #[coverage(off)] + fn mutate_from_account(&mut self, t: &mut AccountPreconditions, account: &Account) { + self.mutate_from_account(&mut t.0, account) + } +} + +impl Mutator for FuzzerCtx { + #[coverage(off)] + fn mutate(&mut self, t: &mut zkapp_command::EpochData) { + for option in rand_elements(self, 5) { + match option { + 0 => { + *t.ledger_mut() = zkapp_command::EpochLedger { + hash: OrIgnore::Check(self.gen()), + total_currency: OrIgnore::Check(self.gen_wrap( + #[coverage(off)] + |x| GeneratorRange64::::gen_range(x, 0..=u64::MAX), + )), + } + } + 1 => t.seed = OrIgnore::Check(self.gen()), + 2 => t.start_checkpoint = OrIgnore::Check(self.gen()), + 3 => t.lock_checkpoint = OrIgnore::Check(self.gen()), + 4 => { + t.epoch_length = OrIgnore::Check(self.gen_wrap( + #[coverage(off)] + |x| x.gen(), + )) + } + _ => unimplemented!(), + } + } + } +} + +impl MutatorFromAccount for FuzzerCtx { + #[coverage(off)] + fn mutate_from_account(&mut self, t: &mut ZkAppPreconditions, _account: &Account) { + for option in rand_elements(self, 7) { + match option { + 0 => t.snarked_ledger_hash = OrIgnore::Check(self.gen()), + 1 => { + let blockchain_length = self.gen_wrap( + #[coverage(off)] + |x| x.gen(), + ); + t.blockchain_length = OrIgnore::Check(blockchain_length); + } + 2 => { + let min_window_density = self.gen_wrap( + #[coverage(off)] + |x| x.gen(), + ); + t.min_window_density = OrIgnore::Check(min_window_density); + } + 3 => { + let total_currency = self.gen_wrap( + #[coverage(off)] + |x| GeneratorRange64::::gen_range(x, 0..=u64::MAX), + ); + t.total_currency = OrIgnore::Check(total_currency); + } + 4 => { + let global_slot_since_genesis = self.gen_wrap( + #[coverage(off)] + |x| Slot::from_u32(x.gen.rng.gen_range(0..Slot::max().as_u32())), + ); + t.global_slot_since_genesis = OrIgnore::Check(global_slot_since_genesis); + } + 5 => self.mutate(&mut t.staking_epoch_data), + 6 => self.mutate(&mut t.next_epoch_data), + _ => unimplemented!(), + } + } + } +} + +impl MutatorFromAccount for FuzzerCtx { + #[coverage(off)] + fn mutate_from_account(&mut self, t: &mut Preconditions, account: &Account) { + for option in rand_elements(self, 2) { + match option { + 0 => self.mutate_from_account(t.network_mut(), account), + 1 => self.mutate_from_account(&mut t.account, account), + _ => unimplemented!(), + } + } + } +} + +impl Mutator for FuzzerCtx { + #[coverage(off)] + fn mutate(&mut self, t: &mut Body) { + let account = self.get_account(&t.public_key).unwrap(); + + for option in rand_elements(self, 11) { + match option { + 0 => t.token_id = TokenId(self.gen()), + 1 => self.mutate_from_account(&mut t.update, &account), + 2 => { + t.balance_change = if self.gen.rng.gen_bool(0.5) { + let magnitude = + GeneratorRange64::::gen_range(self, 0..=Amount::max().as_u64()); + Signed::::create(magnitude, self.gen()) + } else { + Signed::::zero() + } + } + 3 => self.mutate(&mut t.increment_nonce), + 4 => t.events = self.gen(), + 5 => t.actions = self.gen(), + 6 => t.call_data = self.gen(), + 7 => self.mutate_from_account(&mut t.preconditions, &account), + 8 => self.mutate(&mut t.use_full_commitment), + 9 => (), // Can't mutate because it breaks binprot + 10 => { + let vk_hash = if self.gen.rng.gen_bool(0.5) + && account.zkapp.is_some() + && account.zkapp.as_ref().unwrap().verification_key.is_some() + { + account + .zkapp + .as_ref() + .unwrap() + .verification_key + .as_ref() + .unwrap() + .hash() + } else { + self.gen() + }; + + let options = vec![ + zkapp_command::AuthorizationKind::NoneGiven, + zkapp_command::AuthorizationKind::Signature, + zkapp_command::AuthorizationKind::Proof(vk_hash), + ]; + t.authorization_kind = options.choose(&mut self.gen.rng).unwrap().clone(); + } + _ => unimplemented!(), + } + } + } +} + +impl Mutator for FuzzerCtx { + #[coverage(off)] + fn mutate(&mut self, t: &mut AccountUpdate) { + for option in rand_elements(self, 2) { + match option { + 0 => self.mutate(&mut t.body), + 1 => { + if self.gen.rng.gen_bool(0.5) { + t.authorization = match t.body.authorization_kind { + zkapp_command::AuthorizationKind::NoneGiven => { + zkapp_command::Control::NoneGiven + } + zkapp_command::AuthorizationKind::Signature => { + zkapp_command::Control::Signature(Signature::dummy()) + } + zkapp_command::AuthorizationKind::Proof(_) => { + zkapp_command::Control::Proof(self.gen()) + } + }; + } else { + t.authorization = match vec![0, 1, 2].choose(&mut self.gen.rng).unwrap() { + 0 => zkapp_command::Control::NoneGiven, + 1 => zkapp_command::Control::Signature(Signature::dummy()), + 2 => zkapp_command::Control::Proof(self.gen()), + _ => unimplemented!(), + }; + } + } + _ => unimplemented!(), + } + } + } +} + +impl Mutator> for FuzzerCtx { + #[coverage(off)] + fn mutate(&mut self, t: &mut zkapp_command::CallForest) { + for i in rand_elements(self, t.0.len()) { + let tree_digest = { + let tree = &mut t.0[i].elt; + + for option in rand_elements(self, 2) { + match option { + 0 => { + self.mutate(&mut tree.account_update); + tree.account_update_digest = + MutableFp::new(tree.account_update.digest()); + } + 1 => self.mutate(&mut tree.calls), + _ => unimplemented!(), + } + } + + tree.digest() + }; + + let h_tl = if let Some(x) = t.0.get(i + 1) { + x.stack_hash.get().unwrap() + } else { + Fp::zero() + }; + + t.0[i].stack_hash = + MutableFp::new(hash_with_kimchi("MinaAcctUpdateCons", &[tree_digest, h_tl])); + } + } +} + +#[coverage(off)] +pub fn fix_nonces( + ctx: &mut FuzzerCtx, + account_updates: &mut zkapp_command::CallForest, +) { + for acc_update in account_updates.0.iter_mut() { + let account_update = &mut acc_update.elt.account_update; + + if let zkapp_command::Account { + nonce: OrIgnore::Check(_), + .. + } = &account_update.body.preconditions.account.0 + { + let account = ctx.get_account(&account_update.public_key()).unwrap(); + + account_update.body.preconditions.account.0.nonce = + OrIgnore::Check(ctx.gen_from_account(&account)); + } + + fix_nonces(ctx, &mut acc_update.elt.calls); + } +} + +impl Mutator for FuzzerCtx { + #[coverage(off)] + fn mutate(&mut self, t: &mut ZkAppCommand) { + for option in rand_elements(self, 3) { + match option { + 0 => self.mutate(&mut t.fee_payer), + 1 => self.mutate(&mut t.account_updates), + 2 => t.memo = self.gen(), + _ => unimplemented!(), + } + } + + // Fix fee_payer nonce. + let public_key = t.fee_payer.body.public_key.clone(); + let account = self.get_account(&public_key).unwrap(); + t.fee_payer.body.nonce = self.gen_from_account(&account); + + // Fix account updates nonces. + fix_nonces(self, &mut t.account_updates); + + let (txn_commitment, full_txn_commitment) = get_transaction_commitments(t); + let mut signer = mina_signer::create_kimchi(NetworkId::TESTNET); + + if self.gen.rng.gen_bool(0.9) { + let keypair = self.find_keypair(&t.fee_payer.body.public_key).unwrap(); + t.fee_payer.authorization = signer.sign(keypair, &full_txn_commitment); + } + + if self.gen.rng.gen_bool(0.9) { + sign_account_updates( + self, + &mut signer, + &txn_commitment, + &full_txn_commitment, + &mut t.account_updates, + ); + } + } +} + +impl Mutator for FuzzerCtx { + #[coverage(off)] + fn mutate(&mut self, t: &mut UserCommand) { + match t { + UserCommand::ZkAppCommand(zkapp_command) => self.mutate(zkapp_command.as_mut()), + _ => unimplemented!(), + } + } +} + +impl Mutator for FuzzerCtx { + #[coverage(off)] + fn mutate(&mut self, t: &mut Transaction) { + match t { + Transaction::Command(user_command) => self.mutate(user_command), + _ => unimplemented!(), + } + } +} + +// impl Mutator for FuzzerCtx { +// #[no_coverage] +// fn mutate(&mut self, t: &mut MinaStateSnarkedLedgerStateWithSokStableV2) { +// for option in rand_elements(self, 6) { +// match option { +// // 0 => self.mutate(&mut t.source), +// // 1 => self.mutate(&mut t.target), +// // 2 => self.mutate(&mut t.connecting_ledger_left), +// // 3 => self.mutate(&mut t.connecting_ledger_right), +// // 4 => self.mutate(&mut t.supply_increase), +// // 5 => self.mutate(&mut t.fee_excess), +// // // 6 => self.mutate(&mut t.sok_digest), +// _ => unimplemented!(), +// } +// } +// } +// } + +impl Mutator for FuzzerCtx { + #[coverage(off)] + fn mutate(&mut self, t: &mut PicklesProofProofsVerified2ReprStableV2PrevEvals) { + for option in rand_elements(self, 2) { + match option { + // 0 => self.mutate(&mut t.statement), + 0 => self.mutate(&mut t.evals), + 1 => self.mutate(&mut t.ft_eval1), + _ => unimplemented!(), + } + } + } +} + +impl Mutator> for FuzzerCtx +where + FuzzerCtx: Mutator>, +{ + #[coverage(off)] + fn mutate(&mut self, t: &mut ArrayN16) { + self.mutate(t.inner_mut()); + } +} + +impl Mutator for FuzzerCtx { + #[coverage(off)] + fn mutate(&mut self, t: &mut PicklesWrapWireProofStableV1Bulletproof) { + for option in rand_elements(self, 3) { + match option { + 0 => self.mutate(&mut t.lr), + 1 => self.mutate(&mut t.z_1), + 2 => self.mutate(&mut t.z_2), + 3 => self.mutate(&mut t.delta), + 4 => self.mutate(&mut t.challenge_polynomial_commitment), + _ => unimplemented!(), + } + } + } +} + +impl Mutator for FuzzerCtx { + #[coverage(off)] + fn mutate(&mut self, t: &mut PicklesProofProofsVerified2ReprStableV2PrevEvalsEvalsEvals) { + for option in rand_elements(self, 6) { + match option { + 0 => self.mutate(&mut t.w), + 1 => self.mutate(&mut t.coefficients), + 2 => self.mutate(&mut t.z), + 3 => self.mutate(&mut t.s), + 4 => self.mutate(&mut t.generic_selector), + 5 => self.mutate(&mut t.poseidon_selector), + _ => unimplemented!(), + } + } + } +} + +impl Mutator for FuzzerCtx { + #[coverage(off)] + fn mutate(&mut self, t: &mut PicklesProofProofsVerified2ReprStableV2PrevEvalsEvals) { + for option in rand_elements(self, 2) { + match option { + 0 => self.mutate(&mut t.public_input), + 1 => self.mutate(&mut t.evals), + _ => unimplemented!(), + } + } + } +} + +// impl Mutator for FuzzerCtx { +// #[coverage(off)] +// fn mutate(&mut self, t: &mut PicklesProofProofsVerified2ReprStableV2ProofOpenings) { +// for option in rand_elements(self, 3) { +// match option { +// 0 => self.mutate(&mut t.proof), +// 1 => self.mutate(&mut t.evals), +// 2 => self.mutate(&mut t.ft_eval1), +// _ => unimplemented!(), +// } +// } +// } +// } + +// impl Mutator for FuzzerCtx { +// #[coverage(off)] +// fn mutate(&mut self, t: &mut PicklesProofProofsVerified2ReprStableV2Proof) { +// for option in rand_elements(self, 2) { +// match option { +// // 0 => self.mutate(&mut t.statement), +// 0 => self.mutate(&mut t.messages), +// 1 => self.mutate(&mut t.openings), +// _ => unimplemented!(), +// } +// } +// } +// } + +impl Mutator for FuzzerCtx { + #[coverage(off)] + fn mutate(&mut self, t: &mut PicklesWrapWireProofCommitmentsStableV1) { + for option in rand_elements(self, 3) { + match option { + 0 => self.mutate(&mut t.w_comm), + 1 => self.mutate(&mut t.z_comm), + 2 => self.mutate(&mut t.t_comm), + _ => unreachable!(), + } + } + } +} + +impl Mutator for FuzzerCtx { + #[coverage(off)] + fn mutate(&mut self, t: &mut PicklesWrapWireProofEvaluationsStableV1) { + for option in rand_elements(self, 10) { + match option { + 0 => self.mutate(&mut t.w), + 1 => self.mutate(&mut t.coefficients), + 2 => self.mutate(&mut t.z), + 3 => self.mutate(&mut t.s), + 4 => self.mutate(&mut t.generic_selector), + 5 => self.mutate(&mut t.poseidon_selector), + 6 => self.mutate(&mut t.complete_add_selector), + 7 => self.mutate(&mut t.mul_selector), + 8 => self.mutate(&mut t.emul_selector), + 9 => self.mutate(&mut t.endomul_scalar_selector), + _ => unreachable!(), + } + } + } +} + +impl Mutator for FuzzerCtx { + #[coverage(off)] + fn mutate(&mut self, t: &mut PicklesWrapWireProofStableV1) { + for option in rand_elements(self, 4) { + match option { + 0 => self.mutate(&mut t.commitments), + 1 => self.mutate(&mut t.evaluations), + 2 => self.mutate(&mut t.ft_eval1), + 3 => self.mutate(&mut t.bulletproof), + _ => unimplemented!(), + } + } + } +} + +impl Mutator for FuzzerCtx { + #[coverage(off)] + fn mutate(&mut self, t: &mut PicklesProofProofsVerified2ReprStableV2) { + for option in rand_elements(self, 2) { + match option { + // 0 => self.mutate(&mut t.statement), + 0 => self.mutate(&mut t.prev_evals), + 1 => self.mutate(&mut t.proof), + _ => unimplemented!(), + } + } + } +} + +impl Mutator for FuzzerCtx { + #[coverage(off)] + fn mutate(&mut self, t: &mut TransactionSnarkProofStableV2) { + self.mutate(&mut t.0) + } +} + +impl Mutator for FuzzerCtx { + #[coverage(off)] + fn mutate(&mut self, t: &mut TransactionSnarkStableV2) { + self.mutate(&mut t.proof) + // for option in rand_elements(self, 2) { + // match option { + // 0 => self.mutate(&mut t.statement), + // 1 => self.mutate(&mut t.proof), + // _ => unimplemented!(), + // } + // } + } +} + +impl Mutator for FuzzerCtx { + #[coverage(off)] + fn mutate(&mut self, t: &mut TransactionSnarkScanStateLedgerProofWithSokMessageStableV2) { + self.mutate(&mut t.0 .0) + } +} diff --git a/ledger/src/ffi/util.rs b/tools/fuzzing/src/util.rs similarity index 83% rename from ledger/src/ffi/util.rs rename to tools/fuzzing/src/util.rs index 0e3025c86..f239e0dff 100644 --- a/ledger/src/ffi/util.rs +++ b/tools/fuzzing/src/util.rs @@ -3,22 +3,11 @@ use std::{collections::HashSet, hash::Hash, io::Cursor}; use binprot::{BinProtRead, BinProtWrite}; use mina_hasher::Fp; use mina_p2p_messages::bigint::BigInt; -use mina_p2p_messages::binprot; use ocaml_interop::*; use crate::{Account, AccountIndex, Address}; -pub fn deserialize(bytes: &[u8]) -> T { - let mut cursor = Cursor::new(bytes); - T::binprot_read(&mut cursor).unwrap() -} - -pub fn serialize(obj: &T) -> Vec { - let mut bytes = Vec::with_capacity(10000); // TODO: fix this - obj.binprot_write(&mut bytes).unwrap(); - bytes -} - +#[no_coverage] pub fn get_list_of(rt: &mut &mut OCamlRuntime, list: OCamlRef>) -> Vec where T: BinProtRead, @@ -35,6 +24,7 @@ where list } +#[no_coverage] pub fn get_set_of( rt: &mut &mut OCamlRuntime, list: OCamlRef>, @@ -54,10 +44,11 @@ where set } +#[no_coverage] pub fn get_list_addr_account( rt: &mut &mut OCamlRuntime, list: OCamlRef>, -) -> Vec<(Address, Box)> { +) -> Vec<(Address, Account)> { let mut list_ref = rt.get(list); let mut list = Vec::with_capacity(2048); @@ -67,7 +58,7 @@ pub fn get_list_addr_account( let addr = Address::try_from(addr).unwrap(); let object: Account = deserialize(account); - list.push((addr, Box::new(object))); + list.push((addr, object)); list_ref = tail; } @@ -75,11 +66,13 @@ pub fn get_list_addr_account( list } +#[no_coverage] pub fn get_addr(rt: &mut &mut OCamlRuntime, addr: OCamlRef) -> Address { let addr_ref = rt.get(addr); Address::try_from(addr_ref.as_str()).unwrap() } +#[no_coverage] pub fn get(rt: &mut &mut OCamlRuntime, object: OCamlRef) -> T where T: BinProtRead, @@ -88,12 +81,14 @@ where deserialize(object_ref.as_bytes()) } +#[no_coverage] pub fn get_index(rt: &mut &mut OCamlRuntime, index: OCamlRef) -> AccountIndex { let index: i64 = index.to_rust(rt); let index: u64 = index.try_into().unwrap(); AccountIndex(index) } +#[no_coverage] pub fn hash_to_ocaml(hash: Fp) -> Vec { let hash: BigInt = hash.into(); serialize(&hash)