Implement constraints in state circuit spec#462
Conversation
| } | ||
|
|
||
| fn configure(meta: &mut ConstraintSystem<F>) -> Self::Config { | ||
| let selector = meta.fixed_column(); |
There was a problem hiding this comment.
what about changing selector into advice column? Since it is not "fixed" now. ( We assign "1" to it only when a row is non empty, so this column is dynamic.)
There was a problem hiding this comment.
There needs to be a fixed column or selector involved in each of the constraints, or the MockProver will return a ConstraintPoisoned error when it tries to verify.
We can fill up all the otherwise empty rows with Rw::Start's so that the selector column will not be dynamic. I haven't quite figured out how to do this without massively slowing down the tests though.
There was a problem hiding this comment.
verify_at_rows can be helpful for this problem.
There was a problem hiding this comment.
"fill up all the otherwise empty rows with Rw::Start" is also a bit counterintuitive..
Maybe we can merge this PR first(since we don't know how to fix it very cleanly now?), then revisit this problem (make sure vk generated from one circuit instance can verify another circuit) later.
|
Bench failed with the following error: |
related to #458 |
34eb52f to
340f324
Compare
miha-stopar
left a comment
There was a problem hiding this comment.
I really like the new structure, it's really nice and easily readable! I just did a first pass though, will do some more reviewing in the next days.
| // q.rw_counter.value.clone()); }); | ||
| } | ||
|
|
||
| fn build_account_destructed_constraints(&mut self, q: &Queries<F>) { |
There was a problem hiding this comment.
A similar method for when an account is created is probably needed?
Also, an MPT lookup is needed here too, right (whenever something changes in the state trie)?
There was a problem hiding this comment.
These are the constraints for rows that match Rw::AccountDestructed. I believe that up to three Rw::Account will exist in the rw table for account creations? The constraints for these rows are in the build_account_constraints function.
I agree MPT lookup(s) will be required here. I need to review the specs to see what our plans (if any) are for handling account desctruction rows.
There was a problem hiding this comment.
There was a problem hiding this comment.
Regarding the account destructed, I remember from a discussion with @han0110 that he mentioned that after a SELFDESTRUCT the account is not immediately deleted. Looking at the yellowpaper I see:
SELFDESTRUCT: Halt execution and register account for later deletion.
So an account destructed row is not directly connected to the MPT, but we need to figure out a way to keep track of all pending account deletes, and at the step EndTx process all those deletions. Maybe we need to introduce a new tag in the state that signals an account deletion (which must match a previous non-reverted account destruction). Currently that's open to be specified.
Regarding account creation, I guess there are several ways to create an account:
- Send balance to an empty account.
- Tx with
to = 0x0, CREATE, CREATE2
1 and 2 set different account fields.
I would assume that a non-existing account has the fields to a default value, and on account creation some of those fields are set to a different value. This makes me wonder if we need to explicitly set all the fields, or just the ones that get a non-default value; and I also wonder how the MPT handles non-existing accounts VS existing accounts with default values in their fields.
There was a problem hiding this comment.
Yes, the accounts are deleted when root is recomputed.
Non-existing accounts are not stored in the trie (no leaf at the position specified by the account address), while for the account with default values there is a leaf which contains default values. However, in MPT circuit, when there is no account, a placeholder leaf with default values is set, so the lookup should contain default value as previous value. Because currently MPT circuit allows only one change at a time, there would be four lookups needed for the account creation. Just working on that actually. Let me prepare a hackmd note on that.
There was a problem hiding this comment.
I am not very familiar with the current mpt circuit. But just a remainder: "However, in MPT circuit, when there is no account, a placeholder leaf with default values is set, so the lookup should contain default value as previous value.", maybe we should be very careful about the soundness if we allow both "non exist account(not exist in tree)" and "account with default value", where same semantic trees can have different roots.
There was a problem hiding this comment.
I think this EIP can be very useful when reasoning about empty accounts and how they are represented in the MPT: https://eips.ethereum.org/EIPS/eip-161
There was a problem hiding this comment.
I am not very familiar with the current mpt circuit. But just a remainder: "However, in MPT circuit, when there is no account, a placeholder leaf with default values is set, so the lookup should contain default value as previous value.", maybe we should be very careful about the soundness if we allow both "non exist account(not exist in tree)" and "account with default value", where same semantic trees can have different roots.
Agree, but I think I just didn't phrase it clearly. The two tries (with non-existent account and account with default values) are not equivalent in the MPT circuit, it's just that in case of a non-existent account a placeholder leaf is added not to break the MPT circuit layout. This placeholder leaf is then ignored in the circuit, but the circuit of course checks that this leaf is actually empty - this check happens in the branch (below this branch a placeholder leaf resides).
| // 30 limbs in total -> can fit into two field elements | ||
| #[derive(Clone)] | ||
| pub struct Config<F: Field> { | ||
| diff_1: Column<Advice>, |
There was a problem hiding this comment.
Would it be possible to add a short description of this chip and especially of diff_1 and diff_2 fields?
There was a problem hiding this comment.
I've added a comment describing this chip. I would love to hear any suggestions for better names for diff_1 and diff_2.
There was a problem hiding this comment.
Thanks, it makes reading the code much easier! For diff_1 and diff_2 naming I am not sure - perhaps something that would give some intuition, like aggregated_limbs_1, aggregated_limbs_2? It would be also good perhaps to add some rationale why limbs are split into two parts, it's because of field overflow, right?
There was a problem hiding this comment.
Why not just encode 6 fields into two 240-bit values, A_hi and A_lo? Then you just need to compare two 240-bit values, and only the diff needs to be encoded in 16-bit limbs for range check.
There was a problem hiding this comment.
That would definately be easier to understand and explain, but it would require 30 advice columns for the limbs of the diff_hi and diff_lo instead of the 4 we use now.
There was a problem hiding this comment.
agree. Proving cost of adding new gates is much smaller than adding new columns.
| #[derive(Clone)] | ||
| pub struct Queries<F: Field, const N: usize> { | ||
| pub encoded: Expression<F>, | ||
| pub encoded_prev: Expression<F>, |
There was a problem hiding this comment.
want to know usage of encoded_prev, don't see its constraint anywhere
There was a problem hiding this comment.
it's not used in the constraints anywhere for consistency with the specs in https://github.com/appliedzkp/zkevm-specs/blob/master/src/zkevm_specs/state.py, but those constraints will need to be added.
| .find(|(_, (a, b))| a != b); | ||
| let (index, (cur_limb, prev_limb)) = find_result.expect("repeated rw counter"); | ||
|
|
||
| let mut diff_1 = F::from((cur_limb - prev_limb).into()); |
There was a problem hiding this comment.
Doesn't this diff_1 computation imply diff_1 can only be Ai - Bi and not one of the following (cumulative) values specified above in the comment (and also used in the constraint):
// C0 = A0 - B0,
// C1 = C0 << 16 + A1 - B1,
// ...
// C14 = C13 << 16 + A14 - B14,
The constraints don't fail because Ci are 0 by having diff_1 computed this way.
The diff_1 computation implies only one change can occur in the first 15 limbs. Could be some explanation added why this is so? Also, specs repo doesn't seem to have info how lexicographic ordering is to be implemented, could that be added?
There was a problem hiding this comment.
I think it's more clear to view it the other way around. The only way that diff_1 can only be one of the 15 values C0, ..., C14, and that diff_1 can fit into 16 bits is for diff_1 to be the first Ai - Bi such that Aj = Bj for j < i.
You're right that multiple difference can occur between the first 15 pairs of limbs, but (I think) that the constraints are such that diff_1 has to be the value of the most significant non-zero limb difference.
The specs are for lexicographic ordering in the spec repo are in the code here: https://github.com/scroll-tech/zkevm-specs/blob/master/src/zkevm_specs/state.py#L355. I plan to update that section and the book once the implementation is finalized here. I had to make some changes to how the bit-packing of the keys worked so that everything could fit into 2 field elements.
There was a problem hiding this comment.
I think I got it now, thanks!
I think it would be helpful to add such details as above to the comments and docs. Perhaps write something like (if I understood correctly):
Assignment: diff_i is change in the 15 limbs that occurs first (if prev = (2, 1, 2, ...), curr = (2, 1, 6, ...), then diff_i = 4.
Constraint: diff_i will be equal to the first non-zero Ci.
Perhaps you could rename diff_1, diff_2 to first_diff_high, first_diff_low?
There was a problem hiding this comment.
I've renamed them to upper_limb_difference and lower_limb_difference.
f6b97c6 to
eb88bc4
Compare
| prover.verify()?; | ||
| let state_circuit = StateCircuit::new(block.randomness, block.rws); | ||
| let power_of_randomness = state_circuit.instance(); | ||
| let prover = MockProver::<Fr>::run(18, &state_circuit, power_of_randomness).unwrap(); |
There was a problem hiding this comment.
There was a problem hiding this comment.
what do you think about setting enable_state_circuit_test to be false for evm circuit tests, once we add more state circuit tests?
There was a problem hiding this comment.
what do you think about setting
enable_state_circuit_testto be false for evm circuit tests, once we add more state circuit tests?
This sounds like a good tradeoff to me.
|
I assume that currently this PR is assigned to be reviewed by @miha-stopar (from the EF side) and either icemelon OR lispc OR DreamWuGit (from scroll-tech side). Let me know if you'd like me to review it as well at any point! |
|
@miha-stopar , can you take a look at this again, please? |
| } | ||
|
|
||
| fn configure(meta: &mut ConstraintSystem<F>) -> Self::Config { | ||
| let selector = meta.fixed_column(); |
4746ee2 to
fb920b8
Compare
This implements the constraints found in https://github.com/appliedzkp/zkevm-specs/blob/master/src/zkevm_specs/state.py, except for the https://github.com/appliedzkp/zkevm-specs/blob/f33ae436011cde6cf76b92e316e00662cd4ef2e1/src/zkevm_specs/state.py#L178 and https://github.com/appliedzkp/zkevm-specs/blob/master/src/zkevm_specs/state.py#L209. Adding those will require modifying the bus mapping to add the Rw::Start rows, so I will do it in a separate PR.