-
Notifications
You must be signed in to change notification settings - Fork 238
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Program deletes items from array incorrectly, fixes if we print the array (RC issue?) #7192
Comments
Hey Alvaro, could you try and minimize this reproduction so that we can pull it out of aztec-packages to track it here? Also have you confirmed that this issue exists in the builds of nargo from this repository? |
The behavior with current noir repo master nargo is "worse": always fails in unconstrained |
Sounds very similar to #7163 |
NOTE: I merged After some debugging, the problem now isn't that the item is deleted; it's that it's not added to a copy, but rather the original array. It's a bit difficult to follow the route of the data through the code, but roughly:
Where things go wrong is that in step 3 instead of appending to a new array, we append to the previous data array as storage. Then, in step 5, it looks like the previous data storage already had those items, and then in step 6 we try to compare notes beyond where the data was appended, skipping the added items, comparing empty ones to the private call. As a concrete example the Prover.toml has 0 previous notes, 1 new note, which gets correctly added to the output, but then instead of comparing As a reminder in #7163 the problem was that the array was similarly used as storage for the I'll now try to see what this looks like in the SSA, and then whether I can add it to the test case. |
For reference what fn propagate_note_hashes(&mut self, private_call_public_inputs: PrivateCircuitPublicInputs) {
// BUG: If we delete this print, the circuit will fail or pass, depending on nargo version
// println(self.public_inputs.end.note_hashes);
let note_hashes = private_call_public_inputs.note_hashes;
for i in 0..note_hashes.len() {
let note_hash = note_hashes[i];
if note_hash.value != 0 {
self.public_inputs.end.note_hashes.push(note_hash.scope(
private_call_public_inputs.call_context.contract_address,
));
}
}
} Here's a skeleton of its initial SSA: brillig(inline) fn propagate_note_hashes f177 {
b0(..., v49: &mut [(Field, u32, Field); 64], ..., v84: [(Field, u32); 16], ...):
...
v135 = load v49 -> [(Field, u32, Field); 64] // self.public_inputs.end.note_hashes
inc_rc v135
...
jmp b1(u32 0)
b1(v133: u32):
v209 = lt v133, u32 16
jmpif v209 then: b3, else: b2
b2():
v210 = load v49 -> [(Field, u32, Field); 64]
dec_rc v210
...
return // Nothing returned
b3():
v213 = unchecked_mul v133, u32 2
v214 = array_get v84, index v213 -> Field // private_call_public_inputs.note_hashes[i]
v216 = unchecked_add v213, u32 1
v217 = array_get v84, index v216 -> u32
v219 = eq v214, Field 0
v220 = not v219
jmpif v220 then: b4, else: b5
b4():
v222, v223, v224 = call f180(v214, v217, v72) -> (Field, u32, Field)
call f179(v49, v50, v222, v223, v224) // self.public_inputs.end.note_hashes.push(...)
jmp b5()
b5():
v226 = unchecked_add v133, u32 1
jmp b1(v226)
} What's different from the previous case is that the array in question does not appear in an output position in this function, which means the fix applied in that issue, which is to preserve |
After preprocessing function, the array does seem to appear in an output position of brillig(inline) fn generate_output f3 {
b0(..., v46: [(Field, u32, Field); 64], ...):
...
b66():
...
inc_rc v46
...
v613 = allocate -> &mut [(Field, u32, Field); 64] // self.public_inputs.end.note_hashes
store v46 at v613
...
jmp b67(u32 0)
b67(v167: u32):
v636 = lt v167, u32 16
jmpif v636 then: b79, else: b68
b68():
v637 = load v613 -> [(Field, u32, Field); 64]
dec_rc v637
...
jmp b69(u32 0)
b69(v168: u32): // for i in 0..note_hashes.len()
v639 = lt v168, u32 16
jmpif v639 then: b76, else: b70
b70():
v640 = load v613 -> [(Field, u32, Field); 64] // self.public_inputs.end.note_hashes
dec_rc v640
...
inc_rc v640 // inc_rc preserved, but it follows dec_rc and IIRC it's a signed counter
v642 = load v614 -> u32 // len of the BoundedVec
v683 = make_array b"{\"kind\":\"struct\",\"name\":\"BoundedVec\",\"fields\":[[\"storage\",{\"kind\":\"array\",\"length\":64,\"type\":{\"kind\":\"struct\",\"name\":\"ScopedNoteHash\",\"fields\":[[\"note_hash\",{\"kind\":\"struct\",\"name\":\"NoteHash\",\"fields\":[[\"value\",{\"kind\":\"field\"}],[\"counter\",{\"kind\":\"unsignedinteger\",\"width\":32}]]}],[\"contract_address\",{\"kind\":\"struct\",\"name\":\"AztecAddress\",\"fields\":[[\"inner\",{\"kind\":\"field\"}]]}]]}}],[\"len\",{\"kind\":\"unsignedinteger\",\"width\":32}]]}"
call print(u1 1, v640, v642, v683, u1 0)
jmp b71(u32 0)
b71(v169: u32):
v685 = lt v169, u32 16
jmpif v685 then: b73, else: b72
b72(): // return block
v686 = load v613 -> [(Field, u32, Field); 64] // self.public_inputs.end.note_hashes
dec_rc v686
...
dec_rc v686
...
dec_rc v686
...
inc_rc v686
...
inc_rc v686
...
return ..., v686, ... // array in return position
b73(): // let note_hash = note_hashes[i];
v746 = unchecked_mul v169, u32 2
v747 = array_get v78, index v746 -> Field
v748 = unchecked_add v746, u32 1
v749 = array_get v78, index v748 -> u32
v750 = eq v747, Field 0
jmpif v750 then: b75, else: b74
b74(): // if note_hash.value != 0
v751 = load v613 -> [(Field, u32, Field); 64] // self.public_inputs.end.note_hashes
v752 = load v614 -> u32 // len of the BoundedVec
v753 = lt v752, u32 64
constrain v753 == u1 1, "push out of bounds"
v754 = unchecked_mul v752, u32 3
v755 = array_set v751, index v754, value v747 // self.public_inputs.end.note_hashes.push(...)
v756 = unchecked_add v754, u32 1
v757 = array_set v755, index v756, value v749
v758 = unchecked_add v756, u32 1
v759 = array_set v757, index v758, value v66
v760 = add v752, u32 1
store v759 at v613
store v760 at v614
jmp b75()
b75(): // else of `if note_hash.value != 0``
v761 = unchecked_add v169, u32 1
jmp b71(v761)
I'll see what's different when the |
Here's the difference when the brillig(inline) fn generate_output f3 {
b0(..., v46: [(Field, u32, Field); 64], ...):
...
b66():
...
inc_rc v46
...
v613 = allocate -> &mut [(Field, u32, Field); 64]
store v46 at v613
...
jmp b67(u32 0)
b67(v167: u32):
v636 = lt v167, u32 16
jmpif v636 then: b79, else: b68
b68():
v637 = load v613 -> [(Field, u32, Field); 64]
dec_rc v637
...
jmp b69(u32 0)
b69(v168: u32):
v639 = lt v168, u32 16
jmpif v639 then: b76, else: b70
b70():
v640 = load v613 -> [(Field, u32, Field); 64]
dec_rc v640
... // inc_rc removed because `print` is removed
jmp b71(u32 0)
b71(v169: u32):
...
// array_store in b74 is the same as above It's literally just once |
I fixed this with AztecProtocol/aztec-packages@4af0186 on the I'm now checking if it fixes the test failure on BTW the refcount being equal to 1 was confirmed by using
EDIT: Ignore this, it looks like it broke other tests in |
Aim
Doing unrelated changes to a kernel circuit, triggered this bug.
Expected Behavior
No corruption
Bug
../../noir/noir-repo/target/release/nargo execute --package private_kernel_inner_simulated
../../noir/noir-repo/target/release/nargo execute --package private_kernel_inner_simulated
Possibly related to:
AztecProtocol/aztec-packages#10558
To Reproduce
Workaround
None
Workaround Description
No response
Additional Context
No response
Project Impact
None
Blocker Context
No response
Nargo Version
No response
NoirJS Version
No response
Proving Backend Tooling & Version
No response
Would you like to submit a PR for this Issue?
None
Support Needs
No response
The text was updated successfully, but these errors were encountered: