diff --git a/.gitattributes b/.gitattributes index 4157440bf2..d557c00bf8 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1 +1,3 @@ -CHANGELOG.md merge=union \ No newline at end of file +CHANGELOG.md merge=union +bin/bench-transaction/bench-tx.json linguist-generated=true +Cargo.lock linguist-generated=true diff --git a/CHANGELOG.md b/CHANGELOG.md index 0301ff4379..3a32ec07d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ ### Changes +- Keyed `bench-tx.json`'s per-note cycle counts by note label instead of note commitment, and added `total_cycles` to each scenario ([#3726](https://github.com/0xMiden/protocol/pull/3726)). - [BREAKING] Refactored `AccountVaultDelta` to track generic assets. `FungibleAssetDelta`, `NonFungibleAssetDelta` and `NonFungibleDeltaAction` were removed ([3485](https://github.com/0xMiden/protocol/pull/3485)). - [BREAKING] Moved the internal shared helpers of `miden::protocol::input_note`, `miden::protocol::active_note`, and the note memory-write helpers into private `input_note_internal` and `note_internal` modules ([#3501](https://github.com/0xMiden/protocol/pull/3501)). - [BREAKING] Changed asset callbacks into validation-only interfaces that return no asset value; the transaction kernel retains and uses the original value, preventing callbacks from modifying it. The kernel commitment changes ([#3505](https://github.com/0xMiden/protocol/issues/3505), [#3513](https://github.com/0xMiden/protocol/pull/3513)). diff --git a/Cargo.lock b/Cargo.lock index 6994b08f3f..5d0bf88d10 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -577,7 +577,6 @@ dependencies = [ "miden-testing", "miden-tx", "rand 0.10.2", - "rstest", "serde", "serde_json", "tokio", diff --git a/bin/bench-transaction/Cargo.toml b/bin/bench-transaction/Cargo.toml index 3e32af16ec..62a4ad664e 100644 --- a/bin/bench-transaction/Cargo.toml +++ b/bin/bench-transaction/Cargo.toml @@ -64,4 +64,3 @@ tokio = { optional = true, workspace = true } [dev-dependencies] criterion = { features = ["async_tokio", "html_reports"], workspace = true } -rstest = { workspace = true } diff --git a/bin/bench-transaction/README.md b/bin/bench-transaction/README.md index 8befbaf0a8..6401573e08 100644 --- a/bin/bench-transaction/README.md +++ b/bin/bench-transaction/README.md @@ -40,7 +40,7 @@ Regenerate the tables (and `bench-tx.json`) with: make update-note-costs ``` -Freshness is enforced in CI: the `checked_in_cost_matches_benched_cycles` snapshot tests in `src/note_costs.rs` re-execute every priced scenario during the regular test run and fail when a measured cost drifts more than 5% from its checked-in constant. Drift within the tolerance (from unrelated changes landing on the base branch) is absorbed without regeneration - fee-wise this is safe, since the fee is logarithmic in cycles and the pricing safety margin dwarfs it. A PR that meaningfully changes cycle counts must run `make update-note-costs` and commit the updated tables - which doubles as review signal, since cost regressions show up as table diffs. +Freshness is enforced in CI: the `checked_in_note_costs_match_executed_scenarios` snapshot test in `src/note_costs.rs` re-executes every priced scenario during the regular test run and fails when a measured cost drifts more than 5% from its checked-in constant, or when a note's declared `created_notes` do not match what its scenarios actually create. It walks `PricedNote::all` - so all priced notes are covered. Drift within the tolerance (from unrelated changes landing on the base branch) is absorbed without regeneration - fee-wise this is safe, since the fee is logarithmic in cycles and the pricing safety margin dwarfs it. A PR that meaningfully changes cycle counts must run `make update-note-costs` and commit the updated tables - which doubles as review signal, since cost regressions show up as table diffs. ### Benchmark Groups @@ -49,8 +49,12 @@ Each of the above transactions is measured in two groups: For each transaction, data is collected on the number of cycles required to complete: - Prologue + - `total_cycles`: the sum of the prologue, notes-processing, tx-script and epilogue intervals, + and the figure the cost tables above are derived from - All notes processing - - Each note execution + - Each note execution, in consumption order and labelled by note kind (`P2ID`, or `P2ID#0` / + `P2ID#1` when a scenario consumes several notes of one kind). Notes whose script is neither a + standard nor an agglayer one are labelled `UNKNOWN`. - Transaction script processing - Epilogue: - Total number of cycles diff --git a/bin/bench-transaction/bench-tx.json b/bin/bench-transaction/bench-tx.json index 894c10c53b..fa49731841 100644 --- a/bin/bench-transaction/bench-tx.json +++ b/bin/bench-transaction/bench-tx.json @@ -1,23 +1,27 @@ { "consume single P2ID note with Falcon signing": { - "prologue": 4492, - "notes_processing": 2151, - "note_execution": { - "0xc4e2427e43c7965071b694bff219dd2b4f668978639085cf4df6e54972aa2426": 2109 - }, + "prologue": 4513, + "total_cycles": 80329, + "notes_processing": 2173, + "note_execution": [ + { + "note": "P2ID", + "cycles": 2131 + } + ], "tx_script_processing": 41, "epilogue": { - "total": 73556, - "auth_procedure": 72437 + "total": 73602, + "auth_procedure": 72465 }, "trace": { - "core_rows": 80284, - "chiplets_rows": 11351, - "poseidon2_permutation_rows": 54160, - "range_rows": 20521, + "core_rows": 80373, + "chiplets_rows": 11391, + "poseidon2_permutation_rows": 54352, + "range_rows": 20579, "chiplets_shape": { - "hasher_rows": 8360, - "bitwise_rows": 592, + "hasher_rows": 8384, + "bitwise_rows": 608, "memory_rows": 2397, "kernel_rom_rows": 1, "ace_rows": 0 @@ -25,24 +29,28 @@ } }, "consume single P2ID note with ECDSA signing": { - "prologue": 4492, - "notes_processing": 2151, - "note_execution": { - "0x7f66e8f045f7dd99060ad8623e15bb2f18e333af0ef1c51156ebe43e1a08bd77": 2109 - }, + "prologue": 4513, + "total_cycles": 12691, + "notes_processing": 2173, + "note_execution": [ + { + "note": "P2ID", + "cycles": 2131 + } + ], "tx_script_processing": 41, "epilogue": { - "total": 5918, - "auth_procedure": 4799 + "total": 5964, + "auth_procedure": 4827 }, "trace": { - "core_rows": 12646, - "chiplets_rows": 5608, - "poseidon2_permutation_rows": 19056, - "range_rows": 1859, + "core_rows": 12735, + "chiplets_rows": 5656, + "poseidon2_permutation_rows": 19248, + "range_rows": 1901, "chiplets_shape": { - "hasher_rows": 4008, - "bitwise_rows": 848, + "hasher_rows": 4040, + "bitwise_rows": 864, "memory_rows": 750, "kernel_rom_rows": 1, "ace_rows": 0 @@ -50,25 +58,32 @@ } }, "consume two P2ID notes with Falcon signing": { - "prologue": 5328, - "notes_processing": 4484, - "note_execution": { - "0x3dda3c3c3a56ee37e27a6cb453913fec8663269d75aa7f4cf66fc31771401939": 2109, - "0x811b72abe68d792c3bd5c57464a8dc3c803703e741cb8630ca25bb8a2cea31a7": 2324 - }, + "prologue": 5372, + "total_cycles": 83471, + "notes_processing": 4528, + "note_execution": [ + { + "note": "P2ID#0", + "cycles": 2131 + }, + { + "note": "P2ID#1", + "cycles": 2346 + } + ], "tx_script_processing": 41, "epilogue": { - "total": 73484, - "auth_procedure": 72401 + "total": 73530, + "auth_procedure": 72429 }, "trace": { - "core_rows": 83381, - "chiplets_rows": 13360, - "poseidon2_permutation_rows": 54784, - "range_rows": 20457, + "core_rows": 83515, + "chiplets_rows": 13424, + "poseidon2_permutation_rows": 54960, + "range_rows": 20549, "chiplets_shape": { - "hasher_rows": 9880, - "bitwise_rows": 944, + "hasher_rows": 9912, + "bitwise_rows": 976, "memory_rows": 2534, "kernel_rom_rows": 1, "ace_rows": 0 @@ -76,25 +91,32 @@ } }, "consume two P2ID notes with ECDSA signing": { - "prologue": 5328, - "notes_processing": 4484, - "note_execution": { - "0x3dda3c3c3a56ee37e27a6cb453913fec8663269d75aa7f4cf66fc31771401939": 2109, - "0x811b72abe68d792c3bd5c57464a8dc3c803703e741cb8630ca25bb8a2cea31a7": 2324 - }, + "prologue": 5372, + "total_cycles": 15833, + "notes_processing": 4528, + "note_execution": [ + { + "note": "P2ID#0", + "cycles": 2131 + }, + { + "note": "P2ID#1", + "cycles": 2346 + } + ], "tx_script_processing": 41, "epilogue": { - "total": 5846, - "auth_procedure": 4763 + "total": 5892, + "auth_procedure": 4791 }, "trace": { - "core_rows": 15743, - "chiplets_rows": 7617, - "poseidon2_permutation_rows": 19696, - "range_rows": 1441, + "core_rows": 15877, + "chiplets_rows": 7689, + "poseidon2_permutation_rows": 19872, + "range_rows": 1457, "chiplets_shape": { - "hasher_rows": 5528, - "bitwise_rows": 1200, + "hasher_rows": 5568, + "bitwise_rows": 1232, "memory_rows": 887, "kernel_rom_rows": 1, "ace_rows": 0 @@ -103,21 +125,22 @@ }, "create single P2ID note with Falcon signing": { "prologue": 1876, + "total_cycles": 78929, "notes_processing": 35, - "note_execution": {}, - "tx_script_processing": 1854, + "note_execution": [], + "tx_script_processing": 1883, "epilogue": { - "total": 75067, - "auth_procedure": 73021 + "total": 75135, + "auth_procedure": 73049 }, "trace": { - "core_rows": 78876, - "chiplets_rows": 10845, - "poseidon2_permutation_rows": 52464, - "range_rows": 20463, + "core_rows": 78973, + "chiplets_rows": 10893, + "poseidon2_permutation_rows": 52640, + "range_rows": 20351, "chiplets_shape": { - "hasher_rows": 8008, - "bitwise_rows": 544, + "hasher_rows": 8040, + "bitwise_rows": 560, "memory_rows": 2291, "kernel_rom_rows": 1, "ace_rows": 0 @@ -126,21 +149,22 @@ }, "create single P2ID note with ECDSA signing": { "prologue": 1876, + "total_cycles": 11291, "notes_processing": 35, - "note_execution": {}, - "tx_script_processing": 1854, + "note_execution": [], + "tx_script_processing": 1883, "epilogue": { - "total": 7429, - "auth_procedure": 5383 + "total": 7497, + "auth_procedure": 5411 }, "trace": { - "core_rows": 11238, - "chiplets_rows": 5102, - "poseidon2_permutation_rows": 17360, - "range_rows": 1271, + "core_rows": 11335, + "chiplets_rows": 5150, + "poseidon2_permutation_rows": 17536, + "range_rows": 1275, "chiplets_shape": { - "hasher_rows": 3656, - "bitwise_rows": 800, + "hasher_rows": 3688, + "bitwise_rows": 816, "memory_rows": 644, "kernel_rom_rows": 1, "ace_rows": 0 @@ -149,22 +173,26 @@ }, "consume CLAIM note (L1 to Miden)": { "prologue": 4821, - "notes_processing": 28305, - "note_execution": { - "0xb3af39ecb1ce4d350adbc2c16f9fd10f3ccd7e30f1fa482d53114a213829fa4b": 28263 - }, + "total_cycles": 49745, + "notes_processing": 28303, + "note_execution": [ + { + "note": "CLAIM", + "cycles": 28261 + } + ], "tx_script_processing": 41, "epilogue": { - "total": 16564, - "auth_procedure": 11312 + "total": 16580, + "auth_procedure": 11311 }, "trace": { - "core_rows": 49775, - "chiplets_rows": 20164, - "poseidon2_permutation_rows": 41072, - "range_rows": 3501, + "core_rows": 49789, + "chiplets_rows": 20172, + "poseidon2_permutation_rows": 41136, + "range_rows": 3489, "chiplets_shape": { - "hasher_rows": 13184, + "hasher_rows": 13192, "bitwise_rows": 2760, "memory_rows": 4218, "kernel_rom_rows": 1, @@ -174,22 +202,26 @@ }, "consume CLAIM note (L2 to Miden)": { "prologue": 4821, - "notes_processing": 38467, - "note_execution": { - "0x10fe6fd6fef122257048c92009b43e6df141e09cf1c75f99cc8d2f92017bde43": 38425 - }, + "total_cycles": 59907, + "notes_processing": 38465, + "note_execution": [ + { + "note": "CLAIM", + "cycles": 38423 + } + ], "tx_script_processing": 41, "epilogue": { - "total": 16564, - "auth_procedure": 11312 + "total": 16580, + "auth_procedure": 11311 }, "trace": { - "core_rows": 59937, - "chiplets_rows": 22914, - "poseidon2_permutation_rows": 43216, - "range_rows": 3699, + "core_rows": 59951, + "chiplets_rows": 22922, + "poseidon2_permutation_rows": 43280, + "range_rows": 3685, "chiplets_shape": { - "hasher_rows": 14736, + "hasher_rows": 14744, "bitwise_rows": 3016, "memory_rows": 5160, "kernel_rom_rows": 1, @@ -198,24 +230,28 @@ } }, "consume B2AGG note (bridge-out)": { - "prologue": 5106, - "notes_processing": 119258, - "note_execution": { - "0xe81d88c72f6fbfe4453b450f29d2c107eb04907da068a61beb1bc90da0a56ec2": 119216 - }, + "prologue": 5128, + "total_cycles": 149572, + "notes_processing": 119280, + "note_execution": [ + { + "note": "B2AGG", + "cycles": 119238 + } + ], "tx_script_processing": 41, "epilogue": { - "total": 25085, - "auth_procedure": 10151 + "total": 25123, + "auth_procedure": 10150 }, "trace": { - "core_rows": 149534, - "chiplets_rows": 71108, - "poseidon2_permutation_rows": 113680, - "range_rows": 4887, + "core_rows": 149616, + "chiplets_rows": 71148, + "poseidon2_permutation_rows": 113744, + "range_rows": 4905, "chiplets_shape": { - "hasher_rows": 57160, - "bitwise_rows": 3552, + "hasher_rows": 57176, + "bitwise_rows": 3576, "memory_rows": 10394, "kernel_rom_rows": 1, "ace_rows": 0 @@ -223,24 +259,28 @@ } }, "consume B2AGG note (bridge-out, 2^31 leaves)": { - "prologue": 5106, - "notes_processing": 117567, - "note_execution": { - "0xe81d88c72f6fbfe4453b450f29d2c107eb04907da068a61beb1bc90da0a56ec2": 117525 - }, + "prologue": 5128, + "total_cycles": 147593, + "notes_processing": 117589, + "note_execution": [ + { + "note": "B2AGG", + "cycles": 117547 + } + ], "tx_script_processing": 41, "epilogue": { - "total": 24797, - "auth_procedure": 10151 + "total": 24835, + "auth_procedure": 10150 }, "trace": { - "core_rows": 147555, - "chiplets_rows": 70129, - "poseidon2_permutation_rows": 115072, - "range_rows": 4941, + "core_rows": 147637, + "chiplets_rows": 70177, + "poseidon2_permutation_rows": 115136, + "range_rows": 4939, "chiplets_shape": { - "hasher_rows": 56304, - "bitwise_rows": 3552, + "hasher_rows": 56328, + "bitwise_rows": 3576, "memory_rows": 10271, "kernel_rom_rows": 1, "ace_rows": 0 @@ -248,24 +288,28 @@ } }, "consume B2AGG note (bridge-out, 2^31-1 leaves)": { - "prologue": 5106, - "notes_processing": 63398, - "note_execution": { - "0xe81d88c72f6fbfe4453b450f29d2c107eb04907da068a61beb1bc90da0a56ec2": 63356 - }, + "prologue": 5128, + "total_cycles": 84784, + "notes_processing": 63420, + "note_execution": [ + { + "note": "B2AGG", + "cycles": 63378 + } + ], "tx_script_processing": 41, "epilogue": { - "total": 16157, - "auth_procedure": 10151 + "total": 16195, + "auth_procedure": 10150 }, "trace": { - "core_rows": 84746, - "chiplets_rows": 40271, - "poseidon2_permutation_rows": 51504, - "range_rows": 3781, + "core_rows": 84828, + "chiplets_rows": 40319, + "poseidon2_permutation_rows": 51568, + "range_rows": 3855, "chiplets_shape": { - "hasher_rows": 30136, - "bitwise_rows": 3552, + "hasher_rows": 30160, + "bitwise_rows": 3576, "memory_rows": 6581, "kernel_rom_rows": 1, "ace_rows": 0 @@ -273,24 +317,28 @@ } }, "consume P2ID note (network account)": { - "prologue": 3868, - "notes_processing": 2151, - "note_execution": { - "0x78438b9aa97434c7c975348963bd663562965e1f1db79254ecaaa9d41fc8c96e": 2109 - }, + "prologue": 3890, + "total_cycles": 18397, + "notes_processing": 2173, + "note_execution": [ + { + "note": "P2ID", + "cycles": 2131 + } + ], "tx_script_processing": 41, "epilogue": { - "total": 12227, - "auth_procedure": 9004 + "total": 12293, + "auth_procedure": 9030 }, "trace": { - "core_rows": 18331, - "chiplets_rows": 8329, - "poseidon2_permutation_rows": 25680, - "range_rows": 1475, + "core_rows": 18441, + "chiplets_rows": 8393, + "poseidon2_permutation_rows": 25824, + "range_rows": 1493, "chiplets_shape": { - "hasher_rows": 6400, - "bitwise_rows": 832, + "hasher_rows": 6432, + "bitwise_rows": 864, "memory_rows": 1095, "kernel_rom_rows": 1, "ace_rows": 0 @@ -298,24 +346,28 @@ } }, "consume P2ID note (16 assets, network account)": { - "prologue": 12043, - "notes_processing": 29525, - "note_execution": { - "0x3dc96c9a47952e4eb15af508916f3fef731cf2cd58e25f717975463d20aef230": 29483 - }, + "prologue": 12335, + "total_cycles": 56965, + "notes_processing": 29817, + "note_execution": [ + { + "note": "P2ID", + "cycles": 29775 + } + ], "tx_script_processing": 41, "epilogue": { - "total": 14706, - "auth_procedure": 9323 + "total": 14772, + "auth_procedure": 9349 }, "trace": { - "core_rows": 56359, - "chiplets_rows": 32489, - "poseidon2_permutation_rows": 42240, - "range_rows": 3635, + "core_rows": 57009, + "chiplets_rows": 32849, + "poseidon2_permutation_rows": 42320, + "range_rows": 3561, "chiplets_shape": { - "hasher_rows": 24800, - "bitwise_rows": 4792, + "hasher_rows": 24888, + "bitwise_rows": 5064, "memory_rows": 2895, "kernel_rom_rows": 1, "ace_rows": 0 @@ -323,24 +375,28 @@ } }, "consume P2IDE note (claim, network account)": { - "prologue": 3868, - "notes_processing": 2259, - "note_execution": { - "0x63eabc40d31379b86e0ded89177dbabf362e0fcfd67e02182e5db0f9df5c5f7d": 2217 - }, + "prologue": 3890, + "total_cycles": 18505, + "notes_processing": 2281, + "note_execution": [ + { + "note": "P2IDE", + "cycles": 2239 + } + ], "tx_script_processing": 41, "epilogue": { - "total": 12227, - "auth_procedure": 9004 + "total": 12293, + "auth_procedure": 9030 }, "trace": { - "core_rows": 18439, - "chiplets_rows": 8365, - "poseidon2_permutation_rows": 25776, - "range_rows": 1467, + "core_rows": 18549, + "chiplets_rows": 8421, + "poseidon2_permutation_rows": 25920, + "range_rows": 1489, "chiplets_shape": { - "hasher_rows": 6432, - "bitwise_rows": 832, + "hasher_rows": 6456, + "bitwise_rows": 864, "memory_rows": 1099, "kernel_rom_rows": 1, "ace_rows": 0 @@ -348,24 +404,28 @@ } }, "consume P2IDE note (claim, 16 assets, network account)": { - "prologue": 12043, - "notes_processing": 29633, - "note_execution": { - "0x7629580d7445907dd8d5674f42d8a03706bb1d5e925fc8bc9f5814eaf1817f02": 29591 - }, + "prologue": 12335, + "total_cycles": 57073, + "notes_processing": 29925, + "note_execution": [ + { + "note": "P2IDE", + "cycles": 29883 + } + ], "tx_script_processing": 41, "epilogue": { - "total": 14706, - "auth_procedure": 9323 + "total": 14772, + "auth_procedure": 9349 }, "trace": { - "core_rows": 56467, - "chiplets_rows": 32525, - "poseidon2_permutation_rows": 42336, - "range_rows": 3647, + "core_rows": 57117, + "chiplets_rows": 32877, + "poseidon2_permutation_rows": 42416, + "range_rows": 3575, "chiplets_shape": { - "hasher_rows": 24832, - "bitwise_rows": 4792, + "hasher_rows": 24912, + "bitwise_rows": 5064, "memory_rows": 2899, "kernel_rom_rows": 1, "ace_rows": 0 @@ -373,24 +433,28 @@ } }, "consume P2IDE note (reclaim, network account)": { - "prologue": 3971, - "notes_processing": 2311, - "note_execution": { - "0x0cb8c3d38910a1dacb14737ce17290bb10bd2102cc312aab73aeefa6c707430f": 2269 - }, + "prologue": 3993, + "total_cycles": 18660, + "notes_processing": 2333, + "note_execution": [ + { + "note": "P2IDE", + "cycles": 2291 + } + ], "tx_script_processing": 41, "epilogue": { - "total": 12227, - "auth_procedure": 9004 + "total": 12293, + "auth_procedure": 9030 }, "trace": { - "core_rows": 18594, - "chiplets_rows": 8396, - "poseidon2_permutation_rows": 25968, - "range_rows": 1511, + "core_rows": 18704, + "chiplets_rows": 8452, + "poseidon2_permutation_rows": 26112, + "range_rows": 1507, "chiplets_shape": { - "hasher_rows": 6456, - "bitwise_rows": 832, + "hasher_rows": 6480, + "bitwise_rows": 864, "memory_rows": 1106, "kernel_rom_rows": 1, "ace_rows": 0 @@ -398,24 +462,28 @@ } }, "consume SWAP note (public payback, network account)": { - "prologue": 3877, - "notes_processing": 4490, - "note_execution": { - "0x82d7f7ae0d13f63dae7e20e1cd20c33291c91edb9cdac8b26c249356d50326f1": 4448 - }, + "prologue": 3899, + "total_cycles": 22432, + "notes_processing": 4537, + "note_execution": [ + { + "note": "SWAP", + "cycles": 4495 + } + ], "tx_script_processing": 41, "epilogue": { - "total": 13871, - "auth_procedure": 9525 + "total": 13955, + "auth_procedure": 9551 }, "trace": { - "core_rows": 22323, - "chiplets_rows": 10379, - "poseidon2_permutation_rows": 27872, - "range_rows": 1779, + "core_rows": 22476, + "chiplets_rows": 10459, + "poseidon2_permutation_rows": 27952, + "range_rows": 1757, "chiplets_shape": { - "hasher_rows": 7976, - "bitwise_rows": 1152, + "hasher_rows": 8008, + "bitwise_rows": 1200, "memory_rows": 1249, "kernel_rom_rows": 1, "ace_rows": 0 @@ -423,24 +491,28 @@ } }, "consume SWAP note (private payback, network account)": { - "prologue": 3877, - "notes_processing": 3987, - "note_execution": { - "0x380f3b2672cde5e59ac03e7d616b374526eeb55b36c26058c579b1adb291e447": 3945 - }, + "prologue": 3899, + "total_cycles": 21929, + "notes_processing": 4034, + "note_execution": [ + { + "note": "SWAP", + "cycles": 3992 + } + ], "tx_script_processing": 41, "epilogue": { - "total": 13871, - "auth_procedure": 9525 + "total": 13955, + "auth_procedure": 9551 }, "trace": { - "core_rows": 21820, - "chiplets_rows": 10258, - "poseidon2_permutation_rows": 27552, - "range_rows": 1757, + "core_rows": 21973, + "chiplets_rows": 10338, + "poseidon2_permutation_rows": 27632, + "range_rows": 1753, "chiplets_shape": { - "hasher_rows": 7872, - "bitwise_rows": 1152, + "hasher_rows": 7904, + "bitwise_rows": 1200, "memory_rows": 1232, "kernel_rom_rows": 1, "ace_rows": 0 @@ -448,24 +520,28 @@ } }, "consume PSWAP note (full fill, network account)": { - "prologue": 3868, - "notes_processing": 7286, - "note_execution": { - "0x72d4eff497e5ed31de0b876e71a833c0d417d697f884f6adc770989aa0297182": 7244 - }, + "prologue": 3890, + "total_cycles": 25262, + "notes_processing": 7336, + "note_execution": [ + { + "note": "PSWAP", + "cycles": 7294 + } + ], "tx_script_processing": 41, "epilogue": { - "total": 13907, - "auth_procedure": 9525 + "total": 13995, + "auth_procedure": 9551 }, "trace": { - "core_rows": 25146, - "chiplets_rows": 11216, + "core_rows": 25306, + "chiplets_rows": 11304, "poseidon2_permutation_rows": 30496, - "range_rows": 1833, + "range_rows": 1829, "chiplets_shape": { - "hasher_rows": 8496, - "bitwise_rows": 1280, + "hasher_rows": 8536, + "bitwise_rows": 1328, "memory_rows": 1438, "kernel_rom_rows": 1, "ace_rows": 0 @@ -473,24 +549,28 @@ } }, "consume PSWAP note (partial fill, network account)": { - "prologue": 3868, - "notes_processing": 9764, - "note_execution": { - "0x72d4eff497e5ed31de0b876e71a833c0d417d697f884f6adc770989aa0297182": 9722 - }, + "prologue": 3890, + "total_cycles": 29179, + "notes_processing": 9836, + "note_execution": [ + { + "note": "PSWAP", + "cycles": 9794 + } + ], "tx_script_processing": 41, "epilogue": { - "total": 15302, - "auth_procedure": 9729 + "total": 15412, + "auth_procedure": 9755 }, "trace": { - "core_rows": 29019, - "chiplets_rows": 12964, + "core_rows": 29223, + "chiplets_rows": 13076, "poseidon2_permutation_rows": 32288, - "range_rows": 1937, + "range_rows": 1973, "chiplets_shape": { - "hasher_rows": 9704, - "bitwise_rows": 1656, + "hasher_rows": 9752, + "bitwise_rows": 1720, "memory_rows": 1602, "kernel_rom_rows": 1, "ace_rows": 0 @@ -499,24 +579,28 @@ }, "consume MINT note (fungible faucet, network account)": { "prologue": 5614, - "notes_processing": 7929, - "note_execution": { - "0x4a83ca97c7bf0131e229dafb19dc5098f001bcde3ade01f64fa6ad366211623f": 7887 - }, + "total_cycles": 33864, + "notes_processing": 9183, + "note_execution": [ + { + "note": "MINT", + "cycles": 9141 + } + ], "tx_script_processing": 41, "epilogue": { - "total": 18938, - "auth_procedure": 9280 + "total": 19026, + "auth_procedure": 9306 }, "trace": { - "core_rows": 32566, - "chiplets_rows": 13031, - "poseidon2_permutation_rows": 28368, - "range_rows": 2853, + "core_rows": 33908, + "chiplets_rows": 13366, + "poseidon2_permutation_rows": 28448, + "range_rows": 2923, "chiplets_shape": { - "hasher_rows": 9792, - "bitwise_rows": 1152, - "memory_rows": 2085, + "hasher_rows": 10056, + "bitwise_rows": 1200, + "memory_rows": 2108, "kernel_rom_rows": 1, "ace_rows": 0 } @@ -524,48 +608,56 @@ }, "consume MINT note (non-fungible faucet, network account)": { "prologue": 5663, - "notes_processing": 10838, - "note_execution": { - "0xc95ff03a78dc62970c83d47146a48dc73162836fbaedf444f64f7adbc1c29181": 10796 - }, + "total_cycles": 37388, + "notes_processing": 12388, + "note_execution": [ + { + "note": "MINT", + "cycles": 12346 + } + ], "tx_script_processing": 41, "epilogue": { - "total": 19212, - "auth_procedure": 9289 + "total": 19296, + "auth_procedure": 9315 }, "trace": { - "core_rows": 35798, - "chiplets_rows": 14146, - "poseidon2_permutation_rows": 30240, - "range_rows": 3049, + "core_rows": 37432, + "chiplets_rows": 14543, + "poseidon2_permutation_rows": 30288, + "range_rows": 3041, "chiplets_shape": { - "hasher_rows": 10872, - "bitwise_rows": 1056, - "memory_rows": 2216, + "hasher_rows": 11192, + "bitwise_rows": 1104, + "memory_rows": 2245, "kernel_rom_rows": 1, "ace_rows": 0 } } }, "consume BURN note (network account)": { - "prologue": 6195, - "notes_processing": 4495, - "note_execution": { - "0x3fc3626c806a146f13e1ac9e59c0eac978fb59766d51c45911444bcad9b1f20f": 4453 - }, + "prologue": 6217, + "total_cycles": 28550, + "notes_processing": 4527, + "note_execution": [ + { + "note": "BURN", + "cycles": 4485 + } + ], "tx_script_processing": 41, "epilogue": { - "total": 17699, - "auth_procedure": 9078 + "total": 17765, + "auth_procedure": 9104 }, "trace": { - "core_rows": 28474, - "chiplets_rows": 11610, - "poseidon2_permutation_rows": 27552, - "range_rows": 2619, + "core_rows": 28594, + "chiplets_rows": 11666, + "poseidon2_permutation_rows": 27600, + "range_rows": 2599, "chiplets_shape": { - "hasher_rows": 8840, - "bitwise_rows": 888, + "hasher_rows": 8864, + "bitwise_rows": 920, "memory_rows": 1880, "kernel_rom_rows": 1, "ace_rows": 0 @@ -574,23 +666,27 @@ }, "consume FAUCET_POLICY_CONFIG note (network account)": { "prologue": 5549, + "total_cycles": 26794, "notes_processing": 3592, - "note_execution": { - "0x1f2ce020e34fbcc888d18a7dd727829d1f1795fbb5c90927db0c51cf26bcab0b": 3550 - }, + "note_execution": [ + { + "note": "FAUCET_POLICY_CONFIG", + "cycles": 3550 + } + ], "tx_script_processing": 41, "epilogue": { - "total": 17546, - "auth_procedure": 9069 + "total": 17612, + "auth_procedure": 9095 }, "trace": { - "core_rows": 26772, - "chiplets_rows": 10220, - "poseidon2_permutation_rows": 26144, - "range_rows": 2439, + "core_rows": 26838, + "chiplets_rows": 10252, + "poseidon2_permutation_rows": 26224, + "range_rows": 2445, "chiplets_shape": { - "hasher_rows": 7896, - "bitwise_rows": 568, + "hasher_rows": 7912, + "bitwise_rows": 584, "memory_rows": 1754, "kernel_rom_rows": 1, "ace_rows": 0 @@ -599,23 +695,27 @@ }, "consume FAUCET_METADATA_CONFIG note (network account)": { "prologue": 5000, + "total_cycles": 27265, "notes_processing": 6023, - "note_execution": { - "0xc5d07af771df01abcd0fb395b32d26f7ba9205f09d4360ecca7876101017ecf4": 5981 - }, + "note_execution": [ + { + "note": "FAUCET_METADATA_CONFIG", + "cycles": 5981 + } + ], "tx_script_processing": 41, "epilogue": { - "total": 16135, - "auth_procedure": 8988 + "total": 16201, + "auth_procedure": 9014 }, "trace": { - "core_rows": 27243, - "chiplets_rows": 10321, - "poseidon2_permutation_rows": 25440, + "core_rows": 27309, + "chiplets_rows": 10353, + "poseidon2_permutation_rows": 25520, "range_rows": 2403, "chiplets_shape": { - "hasher_rows": 7920, - "bitwise_rows": 576, + "hasher_rows": 7936, + "bitwise_rows": 592, "memory_rows": 1823, "kernel_rom_rows": 1, "ace_rows": 0 @@ -624,23 +724,27 @@ }, "consume MIN_BURN_AMOUNT_CONFIG note (network account)": { "prologue": 5606, + "total_cycles": 25830, "notes_processing": 2418, - "note_execution": { - "0x7aeb862ad5ae354ca222f42cb76f60de52b8cfc78a8c540bce55b8b6a2dc6e81": 2376 - }, + "note_execution": [ + { + "note": "MIN_BURN_AMOUNT_CONFIG", + "cycles": 2376 + } + ], "tx_script_processing": 41, "epilogue": { - "total": 17699, - "auth_procedure": 9078 + "total": 17765, + "auth_procedure": 9104 }, "trace": { - "core_rows": 25808, - "chiplets_rows": 9830, - "poseidon2_permutation_rows": 24736, - "range_rows": 2463, + "core_rows": 25874, + "chiplets_rows": 9870, + "poseidon2_permutation_rows": 24816, + "range_rows": 2459, "chiplets_shape": { - "hasher_rows": 7552, - "bitwise_rows": 568, + "hasher_rows": 7576, + "bitwise_rows": 584, "memory_rows": 1708, "kernel_rom_rows": 1, "ace_rows": 0 @@ -649,23 +753,27 @@ }, "consume ALLOWLIST_CONFIG note (network account)": { "prologue": 5606, + "total_cycles": 26576, "notes_processing": 2985, - "note_execution": { - "0xc27f85d59a55783f8405b4aaa258e2bbc0789f9b5d600a9fc6b0b648fbb08854": 2943 - }, + "note_execution": [ + { + "note": "ALLOWLIST_CONFIG", + "cycles": 2943 + } + ], "tx_script_processing": 41, "epilogue": { - "total": 17878, - "auth_procedure": 9078 + "total": 17944, + "auth_procedure": 9104 }, "trace": { - "core_rows": 26554, - "chiplets_rows": 10464, - "poseidon2_permutation_rows": 27280, - "range_rows": 2497, + "core_rows": 26620, + "chiplets_rows": 10496, + "poseidon2_permutation_rows": 27360, + "range_rows": 2475, "chiplets_shape": { - "hasher_rows": 8112, - "bitwise_rows": 568, + "hasher_rows": 8128, + "bitwise_rows": 584, "memory_rows": 1782, "kernel_rom_rows": 1, "ace_rows": 0 @@ -674,23 +782,27 @@ }, "consume BLOCKLIST_CONFIG note (network account)": { "prologue": 5606, + "total_cycles": 26576, "notes_processing": 2985, - "note_execution": { - "0x57e359b416c6054eaaee8ec6d101dfa7ccd1e8897014e6004cd8b87e1b82ba77": 2943 - }, + "note_execution": [ + { + "note": "BLOCKLIST_CONFIG", + "cycles": 2943 + } + ], "tx_script_processing": 41, "epilogue": { - "total": 17878, - "auth_procedure": 9078 + "total": 17944, + "auth_procedure": 9104 }, "trace": { - "core_rows": 26554, - "chiplets_rows": 10464, - "poseidon2_permutation_rows": 27104, - "range_rows": 2513, + "core_rows": 26620, + "chiplets_rows": 10496, + "poseidon2_permutation_rows": 27184, + "range_rows": 2495, "chiplets_shape": { - "hasher_rows": 8112, - "bitwise_rows": 568, + "hasher_rows": 8128, + "bitwise_rows": 584, "memory_rows": 1782, "kernel_rom_rows": 1, "ace_rows": 0 @@ -699,23 +811,27 @@ }, "consume PAUSE_CONFIG note (network account)": { "prologue": 3476, + "total_cycles": 18308, "notes_processing": 2433, - "note_execution": { - "0xebe32aa38938bfc16f277b8542e58ed4b6b7a4ea9be764972291b965299f59a6": 2391 - }, + "note_execution": [ + { + "note": "PAUSE_CONFIG", + "cycles": 2391 + } + ], "tx_script_processing": 41, "epilogue": { - "total": 12292, - "auth_procedure": 8763 + "total": 12358, + "auth_procedure": 8789 }, "trace": { - "core_rows": 18286, - "chiplets_rows": 7506, - "poseidon2_permutation_rows": 23536, - "range_rows": 1569, + "core_rows": 18352, + "chiplets_rows": 7538, + "poseidon2_permutation_rows": 23616, + "range_rows": 1567, "chiplets_shape": { - "hasher_rows": 5792, - "bitwise_rows": 568, + "hasher_rows": 5808, + "bitwise_rows": 584, "memory_rows": 1144, "kernel_rom_rows": 1, "ace_rows": 0 @@ -724,23 +840,27 @@ }, "consume OWNER_CONFIG note (network account)": { "prologue": 3335, + "total_cycles": 17889, "notes_processing": 2461, - "note_execution": { - "0x413694d4ebf2f4f4ad41eb749201f02c2d3e4c08da7f36eb957ccab987124fb7": 2419 - }, + "note_execution": [ + { + "note": "OWNER_CONFIG", + "cycles": 2419 + } + ], "tx_script_processing": 41, "epilogue": { - "total": 11986, - "auth_procedure": 8745 + "total": 12052, + "auth_procedure": 8771 }, "trace": { - "core_rows": 17867, - "chiplets_rows": 7386, - "poseidon2_permutation_rows": 23392, - "range_rows": 1503, + "core_rows": 17933, + "chiplets_rows": 7426, + "poseidon2_permutation_rows": 23472, + "range_rows": 1505, "chiplets_shape": { - "hasher_rows": 5688, - "bitwise_rows": 584, + "hasher_rows": 5712, + "bitwise_rows": 600, "memory_rows": 1112, "kernel_rom_rows": 1, "ace_rows": 0 @@ -749,23 +869,27 @@ }, "consume RBAC_CONFIG note (network account)": { "prologue": 3514, + "total_cycles": 21712, "notes_processing": 5257, - "note_execution": { - "0xe8b49ff8fb19d28da5c7e5266fe1a243b31cbb6e350636449d856e180797e81d": 5215 - }, + "note_execution": [ + { + "note": "RBAC_CONFIG", + "cycles": 5215 + } + ], "tx_script_processing": 41, "epilogue": { - "total": 12834, - "auth_procedure": 8772 + "total": 12900, + "auth_procedure": 8798 }, "trace": { - "core_rows": 21690, - "chiplets_rows": 9844, - "poseidon2_permutation_rows": 29824, - "range_rows": 1665, + "core_rows": 21756, + "chiplets_rows": 9884, + "poseidon2_permutation_rows": 29904, + "range_rows": 1687, "chiplets_shape": { - "hasher_rows": 7832, - "bitwise_rows": 584, + "hasher_rows": 7856, + "bitwise_rows": 600, "memory_rows": 1426, "kernel_rom_rows": 1, "ace_rows": 0 @@ -774,23 +898,27 @@ }, "consume NETWORK_ACCOUNT_CONFIG note (network account)": { "prologue": 3419, + "total_cycles": 18789, "notes_processing": 2958, - "note_execution": { - "0x89ace2ea72ffd23f3252bf317ed8155bf1e79fc769d166f538487c5b74d646c6": 2916 - }, + "note_execution": [ + { + "note": "NETWORK_ACCOUNT_CONFIG", + "cycles": 2916 + } + ], "tx_script_processing": 41, "epilogue": { - "total": 12305, - "auth_procedure": 8754 + "total": 12371, + "auth_procedure": 8780 }, "trace": { - "core_rows": 18767, - "chiplets_rows": 8042, - "poseidon2_permutation_rows": 26080, - "range_rows": 1557, + "core_rows": 18833, + "chiplets_rows": 8082, + "poseidon2_permutation_rows": 26160, + "range_rows": 1581, "chiplets_shape": { - "hasher_rows": 6280, - "bitwise_rows": 568, + "hasher_rows": 6304, + "bitwise_rows": 584, "memory_rows": 1192, "kernel_rom_rows": 1, "ace_rows": 0 @@ -799,23 +927,27 @@ }, "consume CONSTANT_FEE_POLICY_CONFIG note (network account)": { "prologue": 3475, + "total_cycles": 19683, "notes_processing": 3643, - "note_execution": { - "0xa7d88a5ea6ffa94733184dccae85c121eaa85ec4b00e6231fe0305de18a2782d": 3601 - }, + "note_execution": [ + { + "note": "CONSTANT_FEE_POLICY_CONFIG", + "cycles": 3601 + } + ], "tx_script_processing": 41, "epilogue": { - "total": 12458, - "auth_procedure": 8763 + "total": 12524, + "auth_procedure": 8789 }, "trace": { - "core_rows": 19661, - "chiplets_rows": 8336, - "poseidon2_permutation_rows": 26192, - "range_rows": 1601, + "core_rows": 19727, + "chiplets_rows": 8368, + "poseidon2_permutation_rows": 26272, + "range_rows": 1605, "chiplets_shape": { - "hasher_rows": 6496, - "bitwise_rows": 568, + "hasher_rows": 6512, + "bitwise_rows": 584, "memory_rows": 1270, "kernel_rom_rows": 1, "ace_rows": 0 @@ -823,25 +955,32 @@ } }, "consume FEE_SPONSORSHIP note with feature note (network account)": { - "prologue": 5071, + "prologue": 5093, + "total_cycles": 21249, "notes_processing": 1155, - "note_execution": { - "0x6199ee3ed355b92a02eb0d8f15891778431c64c1de383fb8d12a79c8cd7820b6": 648, - "0xaa511953232bfdd8bde965cf9b0cca4b57e4a9d9a83ba375ea8002caec40eff1": 456 - }, + "note_execution": [ + { + "note": "UNKNOWN", + "cycles": 456 + }, + { + "note": "FEE_SPONSORSHIP", + "cycles": 648 + } + ], "tx_script_processing": 41, "epilogue": { - "total": 14872, - "auth_procedure": 11793 + "total": 14960, + "auth_procedure": 11841 }, "trace": { - "core_rows": 21183, - "chiplets_rows": 9444, - "poseidon2_permutation_rows": 25984, + "core_rows": 21293, + "chiplets_rows": 9500, + "poseidon2_permutation_rows": 26064, "range_rows": 1565, "chiplets_shape": { - "hasher_rows": 7320, - "bitwise_rows": 904, + "hasher_rows": 7344, + "bitwise_rows": 936, "memory_rows": 1218, "kernel_rom_rows": 1, "ace_rows": 0 @@ -849,24 +988,28 @@ } }, "consume FEE_SPONSORSHIP note (reclaim)": { - "prologue": 3754, - "notes_processing": 2843, - "note_execution": { - "0x69c307c81334697abfed0ba239cb2a5f3357658acb54bb3d3792c16a774d081b": 2801 - }, + "prologue": 3776, + "total_cycles": 16945, + "notes_processing": 2865, + "note_execution": [ + { + "note": "FEE_SPONSORSHIP", + "cycles": 2823 + } + ], "tx_script_processing": 41, "epilogue": { - "total": 10168, - "auth_procedure": 7946 + "total": 10263, + "auth_procedure": 8001 }, "trace": { - "core_rows": 16850, - "chiplets_rows": 7730, - "poseidon2_permutation_rows": 23392, - "range_rows": 1553, + "core_rows": 16989, + "chiplets_rows": 7802, + "poseidon2_permutation_rows": 23568, + "range_rows": 1571, "chiplets_shape": { - "hasher_rows": 5712, - "bitwise_rows": 1152, + "hasher_rows": 5752, + "bitwise_rows": 1184, "memory_rows": 864, "kernel_rom_rows": 1, "ace_rows": 0 @@ -875,23 +1018,27 @@ }, "consume CLAIM note (L1 to Miden, with fee payment)": { "prologue": 4821, - "notes_processing": 28305, - "note_execution": { - "0xb3af39ecb1ce4d350adbc2c16f9fd10f3ccd7e30f1fa482d53114a213829fa4b": 28263 - }, + "total_cycles": 53839, + "notes_processing": 28303, + "note_execution": [ + { + "note": "CLAIM", + "cycles": 28261 + } + ], "tx_script_processing": 41, "epilogue": { - "total": 20608, - "auth_procedure": 13974 + "total": 20674, + "auth_procedure": 14000 }, "trace": { - "core_rows": 53819, - "chiplets_rows": 22282, - "poseidon2_permutation_rows": 46336, - "range_rows": 3633, + "core_rows": 53883, + "chiplets_rows": 22314, + "poseidon2_permutation_rows": 46416, + "range_rows": 3557, "chiplets_shape": { - "hasher_rows": 14816, - "bitwise_rows": 3096, + "hasher_rows": 14832, + "bitwise_rows": 3112, "memory_rows": 4368, "kernel_rom_rows": 1, "ace_rows": 0 @@ -900,23 +1047,27 @@ }, "consume CLAIM note (L2 to Miden, with fee payment)": { "prologue": 4821, - "notes_processing": 38467, - "note_execution": { - "0x10fe6fd6fef122257048c92009b43e6df141e09cf1c75f99cc8d2f92017bde43": 38425 - }, + "total_cycles": 64001, + "notes_processing": 38465, + "note_execution": [ + { + "note": "CLAIM", + "cycles": 38423 + } + ], "tx_script_processing": 41, "epilogue": { - "total": 20608, - "auth_procedure": 13974 + "total": 20674, + "auth_procedure": 14000 }, "trace": { - "core_rows": 63981, - "chiplets_rows": 25032, - "poseidon2_permutation_rows": 48480, - "range_rows": 3871, + "core_rows": 64045, + "chiplets_rows": 25064, + "poseidon2_permutation_rows": 48560, + "range_rows": 3821, "chiplets_shape": { - "hasher_rows": 16368, - "bitwise_rows": 3352, + "hasher_rows": 16384, + "bitwise_rows": 3368, "memory_rows": 5310, "kernel_rom_rows": 1, "ace_rows": 0 @@ -924,24 +1075,28 @@ } }, "consume B2AGG note (bridge-out, with fee payment)": { - "prologue": 5106, - "notes_processing": 119258, - "note_execution": { - "0xe81d88c72f6fbfe4453b450f29d2c107eb04907da068a61beb1bc90da0a56ec2": 119216 - }, + "prologue": 5128, + "total_cycles": 153666, + "notes_processing": 119280, + "note_execution": [ + { + "note": "B2AGG", + "cycles": 119238 + } + ], "tx_script_processing": 41, "epilogue": { - "total": 29129, - "auth_procedure": 12813 + "total": 29217, + "auth_procedure": 12839 }, "trace": { - "core_rows": 153578, - "chiplets_rows": 73218, - "poseidon2_permutation_rows": 117808, - "range_rows": 5063, + "core_rows": 153710, + "chiplets_rows": 73290, + "poseidon2_permutation_rows": 117856, + "range_rows": 5061, "chiplets_shape": { - "hasher_rows": 58784, - "bitwise_rows": 3888, + "hasher_rows": 58816, + "bitwise_rows": 3928, "memory_rows": 10544, "kernel_rom_rows": 1, "ace_rows": 0 @@ -949,24 +1104,28 @@ } }, "consume B2AGG note (bridge-out, 2^31-1 leaves, with fee payment)": { - "prologue": 5106, - "notes_processing": 63398, - "note_execution": { - "0xe81d88c72f6fbfe4453b450f29d2c107eb04907da068a61beb1bc90da0a56ec2": 63356 - }, + "prologue": 5128, + "total_cycles": 88878, + "notes_processing": 63420, + "note_execution": [ + { + "note": "B2AGG", + "cycles": 63378 + } + ], "tx_script_processing": 41, "epilogue": { - "total": 20201, - "auth_procedure": 12813 + "total": 20289, + "auth_procedure": 12839 }, "trace": { - "core_rows": 88790, - "chiplets_rows": 42389, - "poseidon2_permutation_rows": 55600, - "range_rows": 3957, + "core_rows": 88922, + "chiplets_rows": 42461, + "poseidon2_permutation_rows": 55664, + "range_rows": 3993, "chiplets_shape": { - "hasher_rows": 31768, - "bitwise_rows": 3888, + "hasher_rows": 31800, + "bitwise_rows": 3928, "memory_rows": 6731, "kernel_rom_rows": 1, "ace_rows": 0 @@ -975,23 +1134,27 @@ }, "consume CONFIG_AGG_BRIDGE note (with fee payment)": { "prologue": 4508, + "total_cycles": 33909, "notes_processing": 13318, - "note_execution": { - "0x4a95146177b6458432d5a1f60f8ade60686c30743fcc89271463bc1bf577ff97": 13276 - }, + "note_execution": [ + { + "note": "CONFIG_AGG_BRIDGE", + "cycles": 13276 + } + ], "tx_script_processing": 41, "epilogue": { - "total": 15976, - "auth_procedure": 8916 + "total": 16042, + "auth_procedure": 8942 }, "trace": { - "core_rows": 33887, - "chiplets_rows": 15156, - "poseidon2_permutation_rows": 37200, - "range_rows": 2449, + "core_rows": 33953, + "chiplets_rows": 15188, + "poseidon2_permutation_rows": 37280, + "range_rows": 2481, "chiplets_shape": { - "hasher_rows": 12168, - "bitwise_rows": 624, + "hasher_rows": 12184, + "bitwise_rows": 640, "memory_rows": 2362, "kernel_rom_rows": 1, "ace_rows": 0 @@ -1000,23 +1163,27 @@ }, "consume DEREGISTER_AGG_FAUCET note (with fee payment)": { "prologue": 4549, + "total_cycles": 33260, "notes_processing": 12628, - "note_execution": { - "0xe4cef1d192ed7353b3cdb747f5b6beec5419c36c49d96a6d954e3c694ecc9653": 12586 - }, + "note_execution": [ + { + "note": "DEREGISTER_AGG_FAUCET", + "cycles": 12586 + } + ], "tx_script_processing": 41, "epilogue": { - "total": 15976, - "auth_procedure": 8916 + "total": 16042, + "auth_procedure": 8942 }, "trace": { - "core_rows": 33238, - "chiplets_rows": 14786, - "poseidon2_permutation_rows": 35984, - "range_rows": 2361, + "core_rows": 33304, + "chiplets_rows": 14818, + "poseidon2_permutation_rows": 36064, + "range_rows": 2369, "chiplets_shape": { - "hasher_rows": 11936, - "bitwise_rows": 616, + "hasher_rows": 11952, + "bitwise_rows": 632, "memory_rows": 2232, "kernel_rom_rows": 1, "ace_rows": 0 @@ -1025,23 +1192,27 @@ }, "consume UPDATE_GER note (with fee payment)": { "prologue": 4490, + "total_cycles": 24120, "notes_processing": 4347, - "note_execution": { - "0x0f17499940516b9743f89ef52d6cb1d3bb002d1c196e033af53a792302987c9a": 4305 - }, + "note_execution": [ + { + "note": "UPDATE_GER", + "cycles": 4305 + } + ], "tx_script_processing": 41, "epilogue": { - "total": 15176, - "auth_procedure": 8916 + "total": 15242, + "auth_procedure": 8942 }, "trace": { - "core_rows": 24098, - "chiplets_rows": 10019, - "poseidon2_permutation_rows": 29424, - "range_rows": 2099, + "core_rows": 24164, + "chiplets_rows": 10051, + "poseidon2_permutation_rows": 29504, + "range_rows": 2095, "chiplets_shape": { - "hasher_rows": 7808, - "bitwise_rows": 568, + "hasher_rows": 7824, + "bitwise_rows": 584, "memory_rows": 1641, "kernel_rom_rows": 1, "ace_rows": 0 @@ -1050,23 +1221,27 @@ }, "consume REMOVE_GER note (with fee payment)": { "prologue": 4549, + "total_cycles": 25239, "notes_processing": 5371, - "note_execution": { - "0x7305f2b4fc451fa2cdb9c1799357f8b43637ea09330da85535339a02fc427395": 5329 - }, + "note_execution": [ + { + "note": "REMOVE_GER", + "cycles": 5329 + } + ], "tx_script_processing": 41, "epilogue": { - "total": 15212, - "auth_procedure": 8916 + "total": 15278, + "auth_procedure": 8942 }, "trace": { - "core_rows": 25217, - "chiplets_rows": 10335, - "poseidon2_permutation_rows": 29920, - "range_rows": 2135, + "core_rows": 25283, + "chiplets_rows": 10367, + "poseidon2_permutation_rows": 30000, + "range_rows": 2177, "chiplets_shape": { - "hasher_rows": 8040, - "bitwise_rows": 568, + "hasher_rows": 8056, + "bitwise_rows": 584, "memory_rows": 1725, "kernel_rom_rows": 1, "ace_rows": 0 diff --git a/bin/bench-transaction/src/cycle_counting_benchmarks/trace_capture.rs b/bin/bench-transaction/src/cycle_counting_benchmarks/trace_capture.rs index eac07f652f..de873183d6 100644 --- a/bin/bench-transaction/src/cycle_counting_benchmarks/trace_capture.rs +++ b/bin/bench-transaction/src/cycle_counting_benchmarks/trace_capture.rs @@ -15,8 +15,11 @@ use miden_tx::{ TransactionProverHost, }; +use crate::note_labels::NoteLabels; + /// Executes the transaction, then replays its inputs through the trace-build path to capture a -/// `TraceLenSummary`. +/// `TraceLenSummary`. Also resolves the stable labels of the transaction's input notes, which the +/// measurements are reported under. /// /// Two passes: `TransactionExecutor` first so the authenticator resolves any required signatures /// into the `ExecutedTransaction`'s inputs, then `LocalTransactionProver::prove`'s trace-build @@ -24,16 +27,18 @@ use miden_tx::{ /// per-iteration. pub async fn capture_measurements_and_trace_summary( mock_tx: MockTransaction, -) -> Result<(TransactionMeasurements, TraceLenSummary)> { +) -> Result<(TransactionMeasurements, TraceLenSummary, NoteLabels)> { let executed = mock_tx .execute() .await .context("pre-execution (to resolve signatures) failed")?; let (tx_inputs, _tx_outputs, _account_patch, measurements) = executed.into_parts(); + // resolved before `tx_inputs` is consumed by the trace-build pass + let note_labels = NoteLabels::from_inputs(&tx_inputs)?; let trace_summary = build_trace_summary(tx_inputs).await?; - Ok((measurements, trace_summary)) + Ok((measurements, trace_summary, note_labels)) } // TODO(#2841): integrate `TraceLenSummary` into `TransactionMeasurements` so we can drop this diff --git a/bin/bench-transaction/src/cycle_counting_benchmarks/utils.rs b/bin/bench-transaction/src/cycle_counting_benchmarks/utils.rs index 62e1c37c29..5f276d05bc 100644 --- a/bin/bench-transaction/src/cycle_counting_benchmarks/utils.rs +++ b/bin/bench-transaction/src/cycle_counting_benchmarks/utils.rs @@ -1,6 +1,3 @@ -extern crate alloc; -pub use alloc::collections::BTreeMap; -pub use alloc::string::String; use std::fs::{read_to_string, write}; use std::path::Path; @@ -11,6 +8,7 @@ use serde::Serialize; use serde_json::{Value, from_str, to_string_pretty}; use super::ExecutionBenchmark; +use crate::note_labels::{NoteLabels, measured_note_key}; // MEASUREMENTS PRINTER // ================================================================================================ @@ -20,35 +18,57 @@ use super::ExecutionBenchmark; #[derive(Debug, Clone, Serialize)] pub struct MeasurementsPrinter { prologue: usize, + total_cycles: usize, notes_processing: usize, - note_execution: BTreeMap, + note_execution: Vec, tx_script_processing: usize, epilogue: EpilogueMeasurements, trace: TraceMeasurements, } impl MeasurementsPrinter { - pub fn from_parts(measurements: TransactionMeasurements, trace: TraceLenSummary) -> Self { - let note_execution_map = measurements + pub fn from_parts( + measurements: TransactionMeasurements, + trace: TraceLenSummary, + note_labels: &NoteLabels, + ) -> anyhow::Result { + let note_execution = measurements .note_execution .iter() - .map(|(id, len)| (id.to_hex(), *len)) - .collect(); - - MeasurementsPrinter { + .map(|(measured, cycles)| { + let note = note_labels.label(measured_note_key(*measured)).with_context(|| { + format!("measured note key {measured} matches no input note of the transaction") + })?; + Ok(NoteExecution { note: note.to_string(), cycles: *cycles }) + }) + .collect::>>()?; + + Ok(MeasurementsPrinter { prologue: measurements.prologue, + total_cycles: measurements.total_cycles(), notes_processing: measurements.notes_processing, - note_execution: note_execution_map, + note_execution, tx_script_processing: measurements.tx_script_processing, epilogue: EpilogueMeasurements::from_parts( measurements.epilogue, measurements.auth_procedure, ), trace: TraceMeasurements::from(trace), - } + }) } } +/// Cycles spent executing one input note. +/// +/// `note` is the note's label; its `#N` suffix, when present, is the note's ordinal among the +/// same-kind notes in *input-note* order, which need not match this entry's position in the +/// measurement sequence. +#[derive(Debug, Clone, Serialize)] +struct NoteExecution { + note: String, + cycles: usize, +} + /// Helper structure holding the cycle count for different intervals in the epilogue, namely: /// - `total` interval holds the total number of cycles required to execute the epilogue /// - `auth_procedure` interval holds the number of cycles required to execute the authentication @@ -136,17 +156,39 @@ pub fn write_bench_results_to_json( #[cfg(test)] mod tests { use miden_processor::trace::{ChipletsLengths, TraceLenSummary}; + use miden_protocol::Word; + use miden_protocol::note::{NoteDetailsCommitment, NoteId}; + use miden_protocol::transaction::TransactionMeasurements; + use miden_standards::note::StandardNote; use serde::Deserialize; - use super::{ExecutionBenchmark, TraceMeasurements}; + use super::{ExecutionBenchmark, MeasurementsPrinter, TraceMeasurements}; + use crate::note_labels::NoteLabels; - /// Minimal mirror of the bench-tx.json `trace` section used to validate the committed file - /// against the producer's contract. + /// Minimal mirror of a bench-tx.json scenario, used to validate the committed file against + /// the producer's contract. #[derive(Deserialize)] struct ScenarioForTest { + prologue: u64, + total_cycles: u64, + notes_processing: u64, + note_execution: Vec, + tx_script_processing: u64, + epilogue: EpilogueForTest, trace: TraceForTest, } + #[derive(Deserialize)] + struct NoteExecutionForTest { + note: String, + cycles: u64, + } + + #[derive(Deserialize)] + struct EpilogueForTest { + total: u64, + } + #[derive(Deserialize)] struct TraceForTest { core_rows: u64, @@ -247,12 +289,42 @@ mod tests { t.poseidon2_permutation_rows.next_power_of_two().max(MIN_TRACE_LEN) } - fn parse_and_assert_trace_contract(name: &str, raw: &serde_json::Value) -> TraceForTest { + fn parse_and_assert_scenario_contract(name: &str, raw: &serde_json::Value) -> TraceForTest { let scenario: ScenarioForTest = serde_json::from_value(raw.clone()) .unwrap_or_else(|err| panic!("scenario `{name}` does not match the schema: {err}")); let trace = &scenario.trace; let chiplets_shape = &trace.chiplets_shape; + assert_eq!( + scenario.total_cycles, + scenario.prologue + + scenario.notes_processing + + scenario.tx_script_processing + + scenario.epilogue.total, + "{name}: total_cycles must be the sum of the measured stages", + ); + + // only the note-creating scenarios consume nothing + assert_eq!( + scenario.note_execution.is_empty(), + name.starts_with("create"), + "{name}: a consuming scenario must measure at least one note, a creating one none", + ); + for entry in &scenario.note_execution { + assert!( + !entry.note.is_empty() && !entry.note.starts_with("0x"), + "{name}: note_execution should be keyed by a label, found `{}`", + entry.note, + ); + assert!(entry.cycles > 0, "{name}: note `{}` should cost > 0 cycles", entry.note); + } + let note_cycles: u64 = scenario.note_execution.iter().map(|entry| entry.cycles).sum(); + assert!( + note_cycles <= scenario.notes_processing, + "{name}: per-note cycles ({note_cycles}) exceed notes_processing ({})", + scenario.notes_processing, + ); + assert!(trace.core_rows > 0, "{name}: core_rows should be > 0"); assert!(trace.chiplets_rows > 0, "{name}: chiplets_rows should be > 0"); assert!( @@ -284,7 +356,7 @@ mod tests { let raw = scenarios .get(name) .unwrap_or_else(|| panic!("scenario `{name}` is missing from bench-tx.json")); - let trace = parse_and_assert_trace_contract(name, raw); + let trace = parse_and_assert_scenario_contract(name, raw); let core_side = padded_core_side(&trace); let chiplets = padded_chiplets(&trace); @@ -306,7 +378,7 @@ mod tests { } #[test] - fn committed_bench_tx_matches_trace_contract() { + fn committed_bench_tx_matches_producer_contract() { let parsed: serde_json::Value = serde_json::from_str(COMMITTED_BENCH_TX_JSON) .expect("bench-tx.json should be valid JSON"); let scenarios = parsed.as_object().expect("bench-tx.json should contain an object"); @@ -316,7 +388,7 @@ mod tests { "bench-tx.json should contain every ExecutionBenchmark scenario", ); for (name, raw) in scenarios { - parse_and_assert_trace_contract(name, raw); + parse_and_assert_scenario_contract(name, raw); } for expected in COMMITTED_SCENARIO_EXPECTATIONS { assert_scenario(&parsed, expected); @@ -343,6 +415,82 @@ mod tests { assert_eq!(measurements.range_rows, summary.range_trace_len()); } + // MEASUREMENTS PRINTER + // -------------------------------------------------------------------------------------------- + + /// A note key, in both the form the input notes are labelled by and the form a measurement + /// entry reports it as. + fn note_key(seed: u32) -> (NoteDetailsCommitment, NoteId) { + let word = Word::from([seed, seed, seed, seed]); + (NoteDetailsCommitment::from_raw(word), NoteId::from_raw(word)) + } + + fn trace_summary() -> TraceLenSummary { + TraceLenSummary::new(10, 20, ChipletsLengths::from_parts(30, 40, 50, 60, 70)) + } + + /// Measurements whose stages sum to a `total_cycles` of 100. + fn measurements(note_execution: Vec<(NoteId, usize)>) -> TransactionMeasurements { + TransactionMeasurements { + prologue: 10, + notes_processing: 20, + note_execution, + tx_script_processing: 30, + epilogue: 40, + auth_procedure: 5, + } + } + + /// The emitted entries keep the order the kernel measured the notes in - not the order the + /// labels were resolved in, and not a sort by label - which is what makes a cycle-count change + /// a one-line diff. + #[test] + fn note_execution_keeps_measurement_order_and_uses_labels() { + let ((first, first_measured), (second, second_measured)) = (note_key(1), note_key(2)); + let labels = NoteLabels::from_script_roots( + [ + (first, StandardNote::P2ID.script_root()), + (second, StandardNote::P2ID.script_root()), + ] + .into_iter(), + ) + .expect("note keys are distinct"); + + let printer = MeasurementsPrinter::from_parts( + measurements(vec![(second_measured, 200), (first_measured, 100)]), + trace_summary(), + &labels, + ) + .expect("every measured note is labelled"); + + let entries: Vec<(&str, usize)> = printer + .note_execution + .iter() + .map(|entry| (entry.note.as_str(), entry.cycles)) + .collect(); + assert_eq!(entries, vec![("P2ID#1", 200), ("P2ID#0", 100)]); + assert_eq!(printer.total_cycles, 100); + } + + /// A measured note that is not among the transaction's input notes is a defect in the join, + /// not a note of an unrecognised kind, so it aborts the run rather than being labelled. + #[test] + fn measured_note_without_a_label_is_an_error() { + let labels = NoteLabels::from_script_roots(core::iter::empty()).expect("no notes to label"); + + let err = MeasurementsPrinter::from_parts( + measurements(vec![(note_key(1).1, 100)]), + trace_summary(), + &labels, + ) + .expect_err("an unlabelled note must not be silently emitted"); + + assert!( + err.to_string().contains("matches no input note of the transaction"), + "unexpected error: {err}", + ); + } + #[test] fn trace_measurements_preserve_poseidon2_permutation_rows() { let summary = TraceLenSummary::new_with_padded( diff --git a/bin/bench-transaction/src/lib.rs b/bin/bench-transaction/src/lib.rs index 844f7a1b13..fc1d07a874 100644 --- a/bin/bench-transaction/src/lib.rs +++ b/bin/bench-transaction/src/lib.rs @@ -6,3 +6,5 @@ pub mod context_setups; pub mod cycle_counting_benchmarks; #[cfg(feature = "concurrent")] pub mod note_costs; +#[cfg(feature = "concurrent")] +pub mod note_labels; diff --git a/bin/bench-transaction/src/main.rs b/bin/bench-transaction/src/main.rs index b5d538e75d..abc3eae897 100644 --- a/bin/bench-transaction/src/main.rs +++ b/bin/bench-transaction/src/main.rs @@ -18,12 +18,14 @@ async fn run_scenario( let mock_tx = build_benchmark_context(bench) .await .with_context(|| format!("failed to build mock transaction for `{bench}`"))?; - let (measurements, trace) = capture_measurements_and_trace_summary(mock_tx) + let (measurements, trace, note_labels) = capture_measurements_and_trace_summary(mock_tx) .await .with_context(|| format!("failed to capture measurements for `{bench}`"))?; let total_cycles = u32::try_from(measurements.total_cycles()) .context("total cycle count does not fit into u32")?; - Ok((bench, MeasurementsPrinter::from_parts(measurements, trace), total_cycles)) + let printer = MeasurementsPrinter::from_parts(measurements, trace, ¬e_labels) + .with_context(|| format!("failed to render measurements for `{bench}`"))?; + Ok((bench, printer, total_cycles)) } #[tokio::main(flavor = "current_thread")] diff --git a/bin/bench-transaction/src/note_costs.rs b/bin/bench-transaction/src/note_costs.rs index be840d4243..95c8ccdfcb 100644 --- a/bin/bench-transaction/src/note_costs.rs +++ b/bin/bench-transaction/src/note_costs.rs @@ -14,8 +14,6 @@ use miden_protocol::note::NoteScriptRoot; use miden_standards::note::StandardNote; use miden_standards::note::costs::NoteCost; -#[cfg(test)] -use crate::context_setups::build_benchmark_context; use crate::cycle_counting_benchmarks::ExecutionBenchmark; /// Header line of the generated table files. @@ -238,37 +236,6 @@ fn path_label(bench: ExecutionBenchmark) -> &'static str { } } -/// Executes the note's benchmark scenarios and returns each scenario's total cycle count. -#[cfg(test)] -async fn benched_path_cycles(note: PricedNote) -> Result> { - let mut path_cycles = Vec::new(); - for &bench in note.scenarios() { - let mock_tx = build_benchmark_context(bench) - .await - .with_context(|| format!("failed to build mock transaction for `{bench}`"))?; - let executed = mock_tx - .execute() - .await - .with_context(|| format!("failed to execute transaction for `{bench}`"))?; - let cycles = u32::try_from(executed.measurements().total_cycles()) - .context("total cycle count does not fit into u32")?; - path_cycles.push((bench, cycles)); - } - Ok(path_cycles) -} - -/// Executes the note's benchmark scenarios and returns the maximum total cycle count. -#[cfg(test)] -async fn benched_cycles(note: PricedNote) -> Result { - let max_cycles = benched_path_cycles(note) - .await? - .into_iter() - .map(|(_, cycles)| cycles) - .max() - .expect("every priced note has at least one scenario"); - Ok(max_cycles) -} - /// Rewrites the two generated cost table files from the given per-scenario cycle counts /// (typically the measurements the benchmark run just collected, so nothing is executed /// twice). @@ -357,9 +324,10 @@ mod tests { use miden_protocol::transaction::RawOutputNote; use miden_standards::note::TxFeeNote; use miden_tx::NetworkNotePricer; - use rstest::rstest; use super::*; + use crate::context_setups::build_benchmark_context; + use crate::note_labels::{NoteLabels, measured_note_key}; /// Relative drift (in percent) tolerated between a measured cost and its checked-in /// constant before the snapshot check fails. @@ -406,82 +374,126 @@ mod tests { assert_eq!(subjects["P2ID"], "a P2ID"); } - /// Ties the hand-maintained created-notes metadata backing recursive pricing to actual - /// execution: every priced scenario is executed, and the script roots of the full output - /// notes other than the TX_FEE note must be exactly the consumed note's declared - /// `created_notes`, so both under- and over-declaration are caught. + /// What executing one priced note's benchmark scenarios established. + struct ExecutedScenarios { + /// Maximum total cycle count across the note's benchmarked paths - the figure its cost + /// table constant holds. + max_cycles: u32, + /// Script roots of the full output notes the scenarios created, other than the TX_FEE + /// note, tagged with the scenario that created each. + created: Vec<(ExecutionBenchmark, NoteScriptRoot)>, + } + + /// Executes each of the note's benchmark scenarios exactly once, collecting everything the + /// checked-in [`NoteCost`] is asserted against. + /// + /// Also validates the label join that `bench-tx.json`'s generator depends on. Only a real + /// execution exercises it - see the TODO on [`measured_note_key`] - and these are the only + /// real executions in the test suite, so the check rides along here instead of paying for its + /// own. A broken join is an infrastructure failure, so it aborts rather than accumulating. + async fn execute_scenarios(note: PricedNote) -> Result { + let mut max_cycles = 0; + let mut created = Vec::new(); + + for &bench in note.scenarios() { + let executed = build_benchmark_context(bench) + .await + .with_context(|| format!("failed to build mock transaction for `{bench}`"))? + .execute() + .await + .with_context(|| format!("failed to execute transaction for `{bench}`"))?; + + let labels = NoteLabels::from_inputs(executed.tx_inputs()) + .with_context(|| format!("failed to label the input notes of `{bench}`"))?; + for (measured, _) in &executed.measurements().note_execution { + anyhow::ensure!( + labels.label(measured_note_key(*measured)).is_some(), + "scenario `{bench}` measured note key {measured}, which matches none of its \ + input notes", + ); + } + + let cycles = u32::try_from(executed.measurements().total_cycles()) + .context("total cycle count does not fit into u32")?; + max_cycles = max_cycles.max(cycles); + + for output_note in executed.output_notes().iter() { + let RawOutputNote::Full(output_note) = output_note else { + continue; + }; + let root = output_note.script().root(); + if root != TxFeeNote::script_root() { + created.push((bench, root)); + } + } + } + + Ok(ExecutedScenarios { max_cycles, created }) + } + + /// Snapshot check tying the checked-in [`NoteCost`] of every note in [`PricedNote::all`] to a + /// real execution of its benchmark scenarios. Two properties are asserted off the same run: + /// + /// 1. the measured maximum cycle count is within [`DRIFT_TOLERANCE_PERCENT`] of the compiled-in + /// constant, catching kernel, standards, or agglayer changes that meaningfully shift a + /// note's consumption cost without the tables having been regenerated; + /// 2. the script roots of the full output notes other than the TX_FEE note are exactly the + /// note's declared `created_notes`, the hand-maintained metadata backing recursive pricing, + /// so both under- and over-declaration are caught. #[tokio::test] - async fn created_notes_cover_executed_output_notes() -> Result<()> { + async fn checked_in_note_costs_match_executed_scenarios() -> Result<()> { + let mut stale_costs = Vec::new(); + let mut misdeclared_created_notes = Vec::new(); + for ¬e in PricedNote::all() { + let executed = execute_scenarios(note).await?; + + let measured = executed.max_cycles; + let (measured_scaled, committed) = + (u64::from(measured) * 100, u64::from(note.committed_cycles())); + let within_tolerance = measured_scaled <= committed * (100 + DRIFT_TOLERANCE_PERCENT) + && measured_scaled >= committed * (100 - DRIFT_TOLERANCE_PERCENT); + if !within_tolerance { + stale_costs.push(format!( + "{}: measured {measured} cycles vs checked-in {committed}", + note.name(), + )); + } + let declared: BTreeSet = note.note_cost().created_notes().iter().copied().collect(); - - let mut observed = BTreeSet::new(); - for &bench in note.scenarios() { - let executed = build_benchmark_context(bench).await?.execute().await?; - for output_note in executed.output_notes().iter() { - let RawOutputNote::Full(created) = output_note else { - continue; - }; - let root = created.script().root(); - if root == TxFeeNote::script_root() { - continue; - } - assert!( - declared.contains(&root), - "scenario `{bench}` created a note with undeclared script root {root}", - ); - observed.insert(root); - } + let observed: BTreeSet = + executed.created.iter().map(|(_, root)| *root).collect(); + for (bench, root) in executed.created.iter().filter(|(_, r)| !declared.contains(r)) { + misdeclared_created_notes + .push(format!("{}: `{bench}` created undeclared {root}", note.name())); + } + for root in declared.difference(&observed) { + misdeclared_created_notes + .push(format!("{}: declared {root} is never created", note.name())); } + } - assert_eq!(observed, declared, "declared created notes were not observed for {note:?}"); + let mut failures = Vec::new(); + if !stale_costs.is_empty() { + failures.push(format!( + "cost table stale for {} note(s), each more than {DRIFT_TOLERANCE_PERCENT}% from \ + its checked-in constant: {}. Run `make update-note-costs` and commit the updated \ + tables", + stale_costs.len(), + stale_costs.join("; "), + )); } - Ok(()) - } + if !misdeclared_created_notes.is_empty() { + failures.push(format!( + "`created_notes` misdeclared for {} note(s): {}. Fix the declaration in the \ + note's `NoteCost`; regenerating the tables does not touch it", + misdeclared_created_notes.len(), + misdeclared_created_notes.join("; "), + )); + } + assert!(failures.is_empty(), "{}", failures.join("\n")); - /// Snapshot check enforcing freshness of the checked-in cost tables: re-executes each priced - /// note's benchmark scenarios and compares the measured maximum against the compiled-in - /// constant, failing when they diverge by more than [`DRIFT_TOLERANCE_PERCENT`]. Catches - /// kernel, standards, or agglayer changes that meaningfully shift a note's consumption cost - /// without the tables having been regenerated. - #[rstest] - #[case::p2id(PricedNote::Standard(StandardNote::P2ID))] - #[case::p2ide(PricedNote::Standard(StandardNote::P2IDE))] - #[case::swap(PricedNote::Standard(StandardNote::SWAP))] - #[case::pswap(PricedNote::Standard(StandardNote::PSWAP))] - #[case::mint(PricedNote::Standard(StandardNote::MINT))] - #[case::burn(PricedNote::Standard(StandardNote::BURN))] - #[case::faucet_policy_config(PricedNote::Standard(StandardNote::FAUCET_POLICY_CONFIG))] - #[case::faucet_metadata_config(PricedNote::Standard(StandardNote::FAUCET_METADATA_CONFIG))] - #[case::min_burn_amount_config(PricedNote::Standard(StandardNote::MIN_BURN_AMOUNT_CONFIG))] - #[case::allowlist_config(PricedNote::Standard(StandardNote::ALLOWLIST_CONFIG))] - #[case::blocklist_config(PricedNote::Standard(StandardNote::BLOCKLIST_CONFIG))] - #[case::pause_config(PricedNote::Standard(StandardNote::PAUSE_CONFIG))] - #[case::owner_config(PricedNote::Standard(StandardNote::OWNER_CONFIG))] - #[case::rbac_config(PricedNote::Standard(StandardNote::RBAC_CONFIG))] - #[case::network_account_config(PricedNote::Standard(StandardNote::NETWORK_ACCOUNT_CONFIG))] - #[case::fee_sponsorship(PricedNote::Standard(StandardNote::FEE_SPONSORSHIP))] - #[case::claim(PricedNote::Agglayer(AgglayerNote::CLAIM))] - #[case::b2agg(PricedNote::Agglayer(AgglayerNote::B2AGG))] - #[case::config_agg_bridge(PricedNote::Agglayer(AgglayerNote::CONFIG_AGG_BRIDGE))] - #[case::deregister_agg_faucet(PricedNote::Agglayer(AgglayerNote::DEREGISTER_AGG_FAUCET))] - #[case::update_ger(PricedNote::Agglayer(AgglayerNote::UPDATE_GER))] - #[case::remove_ger(PricedNote::Agglayer(AgglayerNote::REMOVE_GER))] - #[tokio::test] - async fn checked_in_cost_matches_benched_cycles(#[case] note: PricedNote) -> Result<()> { - let measured = benched_cycles(note).await?; - let committed = note.committed_cycles(); - - let (measured_scaled, committed) = (u64::from(measured) * 100, u64::from(committed)); - let within_tolerance = measured_scaled <= committed * (100 + DRIFT_TOLERANCE_PERCENT) - && measured_scaled >= committed * (100 - DRIFT_TOLERANCE_PERCENT); - assert!( - within_tolerance, - "cost table stale for {note:?}: measured {measured} cycles vs checked-in \ - {committed} (more than {DRIFT_TOLERANCE_PERCENT}% apart): run `make \ - update-note-costs` and commit the updated tables", - ); Ok(()) } diff --git a/bin/bench-transaction/src/note_labels.rs b/bin/bench-transaction/src/note_labels.rs new file mode 100644 index 0000000000..79bf4ef061 --- /dev/null +++ b/bin/bench-transaction/src/note_labels.rs @@ -0,0 +1,230 @@ +//! Stable labels for the input notes of a benchmark scenario. +//! +//! A note's identity commits to its recipient - script root, inputs and serial number - which +//! makes it unusable as a key in a checked-in artifact: any edit to an inlined note script renames +//! every per-note key. A label instead names the note's kind, resolved from its script root, and +//! changes only when that kind, its multiplicity within the transaction, or the note's position +//! among the same-kind notes changes. +//! +//! Labels are keyed by the note's details commitment, which is what a measurement entry carries +//! today; see `measured_note_key`. + +use std::collections::{BTreeMap, BTreeSet}; + +use anyhow::{Result, ensure}; +use miden_agglayer::AgglayerNote; +use miden_protocol::note::{NoteDetailsCommitment, NoteId, NoteScriptRoot}; +use miden_protocol::transaction::TransactionInputs; +use miden_standards::note::StandardNote; + +/// Label of an input note whose script root belongs to neither the standard nor the agglayer set. +/// +/// Reachable in the ordinary course: the fee-sponsorship scenarios consume a sponsored feature +/// note whose script is assembled inline by the test fixtures. Distinct inline-script notes all +/// collapse onto this one kind, so several of them in a single transaction are told apart only by +/// their occurrence index - that is, by consumption order. +const UNKNOWN_NOTE_LABEL: &str = "UNKNOWN"; + +/// Labels of a transaction's input notes, in consumption order. +#[derive(Debug)] +pub struct NoteLabels(Vec<(NoteDetailsCommitment, String)>); + +impl NoteLabels { + /// Resolves a label for each input note of the transaction. + /// + /// A note that is the only one of its kind in the transaction gets the bare kind name + /// (`P2ID`). When a transaction consumes several notes of one kind, each is suffixed with its + /// index among that kind's occurrences, in input-note order (`P2ID#0`, `P2ID#1`). + /// + /// # Errors + /// Returns an error if two input notes share a details commitment, which would make the join + /// in [`NoteLabels::label`] ambiguous. See `measured_note_key`. + pub fn from_inputs(inputs: &TransactionInputs) -> Result { + Self::from_script_roots(inputs.input_notes().iter().map(|input_note| { + (input_note.note().details_commitment(), input_note.note().script().root()) + })) + } + + /// Returns the label of the given note, or `None` if it is not one of the labelled input + /// notes. + /// + /// A miss is distinct from `UNKNOWN_NOTE_LABEL`, which is itself a legitimate label: callers + /// join the kernel-reported measurement key against these labels, and a key that fails to + /// resolve is a defect in that join rather than an unrecognised note kind. + pub fn label(&self, note: NoteDetailsCommitment) -> Option<&str> { + self.0 + .iter() + .find(|(labelled, _)| *labelled == note) + .map(|(_, label)| label.as_str()) + } + + /// Labels the notes identified by the given `(details commitment, script root)` pairs, in + /// consumption order. + /// + /// Two passes: whether a kind needs an index suffix is only known once every note has been + /// seen. + pub(crate) fn from_script_roots( + notes: impl Iterator, + ) -> Result { + let kinds: Vec<(NoteDetailsCommitment, &'static str)> = + notes.map(|(note, root)| (note, note_kind(root))).collect(); + + // The details commitment excludes the metadata and attachments that the nullifier binds, + // so unlike a note ID it is not unique among a transaction's input notes. Two notes + // sharing one would both resolve to the first one's label, publishing one note's cycle + // count twice and dropping the other's. No benchmark scenario does this today; fail + // loudly rather than silently misattribute if one ever does. + let mut seen = BTreeSet::new(); + for (note, _) in &kinds { + ensure!( + seen.insert(*note), + "input notes share the details commitment {}, so their labels would be ambiguous", + note.to_hex(), + ); + } + + let mut occurrences: BTreeMap<&'static str, usize> = BTreeMap::new(); + for (_, kind) in &kinds { + *occurrences.entry(kind).or_default() += 1; + } + + let mut next_index: BTreeMap<&'static str, usize> = BTreeMap::new(); + let labels = kinds + .into_iter() + .map(|(note, kind)| { + let index = next_index.entry(kind).or_default(); + let label = if occurrences[kind] > 1 { + format!("{kind}#{index}") + } else { + kind.to_string() + }; + *index += 1; + (note, label) + }) + .collect(); + + Ok(Self(labels)) + } +} + +/// Reinterprets the note key of a `TransactionMeasurements::note_execution` entry as the details +/// commitment it actually holds, so it can be looked up with [`NoteLabels::label`]. +/// +/// TODO(#3724): the entry is typed `(NoteId, usize)`, but the host reads the input note segment's +/// base word - the details commitment - rather than the ID cached at `INPUT_NOTE_ID_OFFSET`, so +/// the key is a details commitment wearing a `NoteId`. Once +/// lands the key becomes a real note ID: key +/// [`NoteLabels`] by `NoteId`, resolve it from `input_note.note().id()`, and delete this. +/// +/// That switch is not graceful - a real note ID never equals a details commitment, so *every* +/// lookup misses at once and the generator aborts on its first scenario. Whoever lands #3724 has +/// to make this change in the same PR. +pub(crate) fn measured_note_key(measured: NoteId) -> NoteDetailsCommitment { + NoteDetailsCommitment::from_raw(measured.as_word()) +} + +/// Returns the name of the note kind the script root identifies, or [`UNKNOWN_NOTE_LABEL`] if it +/// matches no known note. +fn note_kind(root: NoteScriptRoot) -> &'static str { + StandardNote::from_script_root(root) + .map(|note| note.name()) + .or_else(|| AgglayerNote::from_script_root(root).map(|note| note.name())) + .unwrap_or(UNKNOWN_NOTE_LABEL) +} + +#[cfg(test)] +mod tests { + use miden_protocol::Word; + + use super::*; + + /// Builds a note key that is distinct per `seed` and unrelated to any script root, so the + /// tests exercise labelling independently of how real notes are committed to. + fn note(seed: u32) -> NoteDetailsCommitment { + NoteDetailsCommitment::from_raw(Word::from([seed, seed, seed, seed])) + } + + fn labels(notes: &[(NoteDetailsCommitment, NoteScriptRoot)]) -> NoteLabels { + NoteLabels::from_script_roots(notes.iter().copied()).expect("note keys are distinct") + } + + #[test] + fn sole_note_of_a_kind_keeps_the_bare_name() { + let (p2id, claim) = (note(1), note(2)); + let labels = labels(&[ + (p2id, StandardNote::P2ID.script_root()), + (claim, AgglayerNote::CLAIM.script_root()), + ]); + + assert_eq!(labels.label(p2id), Some("P2ID")); + assert_eq!(labels.label(claim), Some("CLAIM")); + } + + #[test] + fn repeated_kind_is_indexed_in_input_order() { + let (first, second, third) = (note(1), note(2), note(3)); + let labels = labels(&[ + (first, StandardNote::P2ID.script_root()), + (second, StandardNote::P2ID.script_root()), + (third, StandardNote::P2ID.script_root()), + ]); + + assert_eq!(labels.label(first), Some("P2ID#0")); + assert_eq!(labels.label(second), Some("P2ID#1")); + assert_eq!(labels.label(third), Some("P2ID#2")); + } + + #[test] + fn kinds_are_indexed_independently_of_each_other() { + let (p2id, p2ide, other_p2id) = (note(1), note(2), note(3)); + let labels = labels(&[ + (p2id, StandardNote::P2ID.script_root()), + (p2ide, StandardNote::P2IDE.script_root()), + (other_p2id, StandardNote::P2ID.script_root()), + ]); + + assert_eq!(labels.label(p2id), Some("P2ID#0")); + assert_eq!(labels.label(other_p2id), Some("P2ID#1")); + assert_eq!(labels.label(p2ide), Some("P2IDE"), "a lone P2IDE must not be indexed"); + } + + /// The placeholder is a label like any other - it is indexed when repeated, and it stays + /// distinguishable from a lookup miss, which returns `None`. + #[test] + fn unrecognised_script_roots_share_the_indexed_placeholder() { + let (first, second, absent) = (note(1), note(2), note(3)); + let unrecognised = NoteScriptRoot::from_raw(Word::from([9u32; 4])); + let labels = labels(&[(first, unrecognised), (second, unrecognised)]); + + assert_eq!(labels.label(first), Some("UNKNOWN#0")); + assert_eq!(labels.label(second), Some("UNKNOWN#1")); + assert_eq!(labels.label(absent), None, "an unlabelled note must not read as UNKNOWN"); + } + + /// The measurement key round-trips into the details commitment the input notes are keyed by, + /// so the join in `MeasurementsPrinter::from_parts` resolves. + #[test] + fn measured_note_key_preserves_the_underlying_word() { + let commitment = note(7); + + assert_eq!(measured_note_key(NoteId::from_raw(commitment.as_word())), commitment); + } + + /// Unlike a note ID, a details commitment is not unique among input notes: it excludes the + /// metadata the nullifier binds. Labelling must refuse rather than misattribute. + #[test] + fn duplicate_details_commitments_are_rejected() { + let shared = note(1); + + let err = NoteLabels::from_script_roots( + [ + (shared, StandardNote::P2ID.script_root()), + (shared, StandardNote::P2ID.script_root()), + ] + .into_iter(), + ) + .expect_err("a shared details commitment must not be labelled"); + + assert!(err.to_string().contains("share the details commitment"), "unexpected: {err}"); + } +}