-
Notifications
You must be signed in to change notification settings - Fork 125
PI refactor #138
base: main
Are you sure you want to change the base?
PI refactor #138
Conversation
meta.create_gate("keccak(rpi)", |meta| { | ||
cb.restart(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Much cleaner to just move everything in a single gate like the MPT circuit, then also no restart necessary (and I guess not always restarted now, so constraints are added multiple times now per gate because they accumulate)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Glad that you like it :) No restart needed.
What do you mean accumulate tho? If you're talking about block_acc
it should be applied per row within the left field bytes section.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I meant that the multiple gates introduces extra things people can forget and make errors with. Because each gate does build_constraints
, if you don't call restart
between the different gates, constraints will keep getting added to the list in the constrain builder and those will keep being added when calling build_constraints
, so that will also add the constraints in the previous gates again.
Conclusion: gates bad! haha
require!(a!(rpi_field_bytes_acc) => a!(rpi_field_bytes)); | ||
}); | ||
}); | ||
cb.build_constraints(None) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why did you add an option for an additional selector here? People should just use ifx
instead no?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because I don't like too much indentation with lots of ifx
😅 (aesthetically unpleasing given macro's ! and parentheses) and I feel like if there's must be a fixed selector enabling of circuits, why don't we generate the constraint "anchoring" this gate.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you write normal rust code, would you introduce a gate like construct to make it cleaner? If the answer is no, why would you do it here? Does normal rust code have too many nested if's? I don't think so, you just split things up over multiple functions in a way that makes sense. So why not do exactly the same with circuits? The whole of MPT is written using a single gate, I don't think there was any problem there with this because it's split up mostly like you would split up a normal rust program.
why don't we generate the constraint "anchoring" this gate.
Because then the flow control is split into multiple parts and gates don't allow if/else statements, so I find it harder to read and it's not something normal programmers are used to.
It's also not possible to do gates inside gates. So even if you like using gates, it's impossible to use it except on the top level of the program. So it's a strange concept to rely on because if you like to use it to split up your program, it only works for tiny programs that have only 1 level complexity, anything more complex does require using if/else statement.
It's also not directly compatible with regions/context management, so this would require a different path to make it behave like the normal control flow using ifs/matches.
I have one concern tho... @Brechtpd do you think for |
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The version in taiko_pi_circuit_.rs
I think could also potentially work, but yeah the cost of the current version will be much higher because of many more columns and A LOT more lookups, so the performance will be much worse. But to solve that it just needs to be possible to make the CellManager be a single column (or maybe two columns), which seems to be possible to me.
On the other hand, this will be very close to just using the taiko_pi_circuit.rs
version where everything is still assumed to be on a single row, and just put every cell used on that row in a cellmanager. The cellmanager doesn't do much in this case, but it still does away with having to assign things to columns instead of allocated cells. I currently lean towards this approach because it will be easier.
To print the most important info for the circuit performance:
println!("max expression degree: {}", meta.degree());
println!("num lookups: {}", meta.lookups().len());
println!("num advices: {}", meta.num_advice_columns());
println!("num fixed: {}", meta.num_fixed_columns());
cb.set_cell_manager(state_cm.clone()); | ||
let block_number = [();2].map(|_| cb.query_one(PiCellType::Storage1)); | ||
let keccak_output = [();2].map(|_| cb.query_one(PiCellType::Storage2)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Doe these need to be stored in a separate CellManager? Seems like these could just be stored somewhere in the main CellManager and it should still be okay? keccak_output
requires a copy constraint I think which should be possible I think, block_number
doesn't seem to require anything special, just some storage cell, unless I miss something.
is_field: Cell<F>, | ||
is_first: Cell<F>, | ||
is_last: Cell<F>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These are advice cells, but seems like they should be fixed cells? (should not be possible for the prover to set these values).
); | ||
let q_enable = meta.complex_selector(); | ||
let public_input = meta.instance_column(); | ||
let block_acc = meta.advice_column_in(SecondPhase); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not also put it in the CellManager and just use rot
with the height?
(PiCellType::Storage1, 3, 1, false) | ||
], | ||
0, | ||
1, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think with a single CellManager it will be easier to just make this height be whatever, and then the other code should be able to use the same height in rot
to check between the different states, and in the witness assignment.
For A5 integration, after functionality test, please merge this PR to feat/a5-testnet branch. |
e6b14de
to
54c1764
Compare
const SIGNAL_ROOT: usize = 3; | ||
const GRAFFITI: usize = 4; | ||
const PROVER: usize = 5; | ||
const S1: PiCellType = PiCellType::Storage1; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
StoragePhase1
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure of you like :)
|
||
/// PublicData contains all the values that the PiCircuit receives as input | ||
const PADDING_LEN: usize = 32; | ||
const META_HASH: usize = 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
enum?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think const is better cuz enum is wordy, you have to type somethingPiField::MetaHash
.
self.field.iter().map(|f| f.expr()).collect() | ||
} | ||
|
||
fn acc(&self, r: Expression<F>) -> Expression<F> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
rlc_acc or rlc?
|
||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] | ||
enum PiCellType { | ||
Storage1, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
StoragePhase1/2
fn mock_public_data() -> PublicData<Fr> { | ||
let mut evidence = PublicData::default(); | ||
evidence.set_field(PARENT_HASH, OMMERS_HASH.to_fixed_bytes().to_vec()); | ||
evidence.set_field(BLOCK_HASH, OMMERS_HASH.to_fixed_bytes().to_vec()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
init address once as it has different data format
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You mean mock address for prover?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, at least test different type once
assign!(region, self.block_hash.0, 0 => (evidence.block_context.number).as_u64().scalar()); | ||
assign!(region, self.block_hash.2, 0 => evidence.assignment_acc(BLOCK_HASH, evm_word)); | ||
|
||
let _acc = F::ZERO; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
useless variable??
field10, | ||
impl<F: Field> PublicData<F> { | ||
fn new(block: &witness::Block<F>) -> Self { | ||
let meta_hash = Token::FixedBytes(block.protocol_instance.meta_hash.hash().to_word().to_be_bytes().to_vec()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hash.hash() for what??
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
meta_hash here is already a hash, no need hash again.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the old version meta_hash is a struct MetaHash
which returns the actual hash when you call hash()
on itself, but yea now it's already a hash.
Description
Refactor the PI circuit in circuit-tool style.