Skip to content

Commit e46e7d4

Browse files
AntonD3antoniolocascio0xVolosnikov
authored
feat: minimal set of features to support gateway (#430)
## What ❔ 2 features to support gateway: - read multichain root from `MesageRoot` contract and include it in the full logs root - support free(0 gas price) l1 -> l2 transactions ## Is this a breaking change? - [x] Yes - [ ] No It's backward compatible for forward run(taking into account that there are no 0 gas price l1 -> l2 transactions). But full logs root changed(changes in the sequencer required) ## Checklist <!-- Check your PR fulfills the following items. --> <!-- For draft PRs check the boxes as you complete them. --> - [x] PR title corresponds to the body of PR (we generate changelog entries from PRs). - [x] Tests for the changes have been added / updated. - [x] Documentation comments have been added / updated. - [x] Code has been formatted. --------- Co-authored-by: antoniolocascio <antonio.locascio1@gmail.com> Co-authored-by: Vladislav Volosnikov <Volosnikov.apmath@gmail.com>
1 parent dc509b3 commit e46e7d4

File tree

8 files changed

+169
-35
lines changed

8 files changed

+169
-35
lines changed

basic_bootloader/src/bootloader/block_flow/zk/batch_data.rs

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ pub struct ZKBatchDataKeeper<A: alloc::alloc::Allocator, O: IOOracle> {
2727
pub logs_storage: ArrayVec<Bytes32, 16384>,
2828
enforced_txs_accumulator: TransactionsRollingKeccakHasher,
2929
upgrade_tx_hash: Option<Bytes32>,
30+
multichain_root: Bytes32,
3031
interop_roots_rolling_hash: Bytes32,
3132
settlement_layer_chain_id: Option<U256>,
3233
}
@@ -46,6 +47,7 @@ impl<A: alloc::alloc::Allocator, O: IOOracle> ZKBatchDataKeeper<A, O> {
4647
// keccak256([])
4748
enforced_txs_accumulator: TransactionsRollingKeccakHasher::empty(),
4849
upgrade_tx_hash: None,
50+
multichain_root: Bytes32::zero(),
4951
interop_roots_rolling_hash: Bytes32::ZERO,
5052
settlement_layer_chain_id: None,
5153
}
@@ -62,6 +64,7 @@ impl<A: alloc::alloc::Allocator, O: IOOracle> ZKBatchDataKeeper<A, O> {
6264
block_timestamp: u64,
6365
chain_id: U256,
6466
upgrade_tx_hash: Bytes32,
67+
multichain_root: Bytes32,
6568
interop_roots: impl Iterator<Item = &'a InteropRoot>,
6669
settlement_layer_chain_id: U256,
6770
) {
@@ -88,6 +91,8 @@ impl<A: alloc::alloc::Allocator, O: IOOracle> ZKBatchDataKeeper<A, O> {
8891
Some(settlement_layer_chain_id)
8992
);
9093
}
94+
// we always override multichain root with latest
95+
self.multichain_root = multichain_root;
9196

9297
self.interop_roots_rolling_hash = calculate_interop_roots_rolling_hash(
9398
self.interop_roots_rolling_hash,
@@ -102,10 +107,10 @@ impl<A: alloc::alloc::Allocator, O: IOOracle> ZKBatchDataKeeper<A, O> {
102107
pub fn into_public_input(self, mut logger: impl Logger, oracle: &mut O) -> BatchPublicInput {
103108
assert!(!self.is_first_block);
104109

105-
let mut full_root_hasher = crypto::sha3::Keccak256::new();
106-
full_root_hasher.update(Self::l2_logs_root(self.logs_storage).as_u8_ref());
107-
full_root_hasher.update([0u8; 32]); // aggregated root 0 for now
108-
let full_l2_to_l1_logs_root = full_root_hasher.finalize();
110+
let mut chain_batch_root_hasher = crypto::sha3::Keccak256::new();
111+
chain_batch_root_hasher.update(Self::l2_logs_root(self.logs_storage).as_u8_ref());
112+
chain_batch_root_hasher.update(self.multichain_root.as_u8_ref());
113+
let chain_batch_root = chain_batch_root_hasher.finalize();
109114

110115
let (priority_operations_hash, number_of_layer_1_txs) =
111116
self.enforced_txs_accumulator.finish();
@@ -117,7 +122,7 @@ impl<A: alloc::alloc::Allocator, O: IOOracle> ZKBatchDataKeeper<A, O> {
117122
pubdata_commitment: self.da_commitment_generator.unwrap().finalize(oracle),
118123
number_of_layer_1_txs: U256::from(number_of_layer_1_txs),
119124
priority_operations_hash,
120-
l2_logs_tree_root: full_l2_to_l1_logs_root.into(),
125+
l2_logs_tree_root: chain_batch_root.into(),
121126
upgrade_tx_hash: self.upgrade_tx_hash.unwrap(),
122127
interop_roots_rolling_hash: self.interop_roots_rolling_hash,
123128
settlement_layer_chain_id: self.settlement_layer_chain_id.unwrap(),

basic_bootloader/src/bootloader/block_flow/zk/post_tx_op/mod.rs

Lines changed: 134 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use basic_system::system_implementation::system::FullIO;
55
use core::alloc::Allocator;
66
use crypto::MiniDigest;
77
use ruint::aliases::U256;
8-
use system_hooks::addresses_constants::SYSTEM_CONTEXT_ADDRESS;
8+
use system_hooks::addresses_constants::{MESSAGE_ROOT_ADDRESS, SYSTEM_CONTEXT_ADDRESS};
99
use zk_ee::common_structs::interop_root_storage::InteropRoot;
1010
use zk_ee::memory::stack_trait::StackFactory;
1111
use zk_ee::oracle::IOOracle;
@@ -162,3 +162,136 @@ pub fn read_settlement_layer_chain_id<
162162
.expect("must read SystemContext SL chain id");
163163
U256::from_be_bytes(chain_id.as_u8_array())
164164
}
165+
166+
///
167+
/// Reads multichain root from the L2MessageRoot(0x10005) contract.
168+
///
169+
/// Multichain root is the commitment to l2 to l1 logs from the chains that settles on top of current.
170+
/// It's not zero if the current chain is used as the settlement layer.
171+
///
172+
pub fn read_multichain_root<
173+
A: Allocator + Clone + Default,
174+
R: Resources,
175+
P: StorageAccessPolicy<R, Bytes32> + Default,
176+
SF: StackFactory<N>,
177+
const N: usize,
178+
O: IOOracle,
179+
const PROOF_ENV: bool,
180+
>(
181+
io: &mut FullIO<
182+
A,
183+
R,
184+
P,
185+
SF,
186+
N,
187+
O,
188+
FlatTreeWithAccountsUnderHashesStorageModel<A, R, P, SF, N, PROOF_ENV>,
189+
PROOF_ENV,
190+
>,
191+
) -> Bytes32 {
192+
use zk_ee::system::IOSubsystem;
193+
194+
const SHARED_TREE_HEIGHT_STORAGE_SLOT: [u8; 32] = [
195+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
196+
0, 4,
197+
];
198+
let mut inf_resources = R::FORMAL_INFINITE;
199+
200+
// we need to read self._nodes[self._height][0]
201+
let tree_height = io
202+
.storage_read::<false>(
203+
ExecutionEnvironmentType::NoEE,
204+
&mut inf_resources,
205+
&MESSAGE_ROOT_ADDRESS,
206+
&Bytes32::from_array(SHARED_TREE_HEIGHT_STORAGE_SLOT),
207+
)
208+
.expect("must read MessageRoot shared tree height");
209+
210+
let root_slot = calculate_multichain_root_slot(tree_height);
211+
212+
io.storage_read::<false>(
213+
ExecutionEnvironmentType::NoEE,
214+
&mut inf_resources,
215+
&MESSAGE_ROOT_ADDRESS,
216+
&root_slot,
217+
)
218+
.expect("must read MessageRoot shared tree height")
219+
}
220+
221+
///
222+
/// Calculates storage slot of multichain tree root in L2MessageRoot(0x10005) contract.
223+
///
224+
/// By convention storage slot for it should stay the same(depend only on `tree_height`).
225+
/// In fact, it's solidity dynamic array access `_nodes[height][0]`, which is located on slot 6.
226+
///
227+
fn calculate_multichain_root_slot(tree_height: Bytes32) -> Bytes32 {
228+
use core::ops::Add;
229+
// keccak256(0x0000000000000000000000000000000000000000000000000000000000000006)
230+
const NODES_FIRST_ELEMENT_SLOT: [u8; 32] = [
231+
0xf6, 0x52, 0x22, 0x23, 0x13, 0xe2, 0x84, 0x59, 0x52, 0x8d, 0x92, 0x0b, 0x65, 0x11, 0x5c,
232+
0x16, 0xc0, 0x4f, 0x3e, 0xfc, 0x82, 0xaa, 0xed, 0xc9, 0x7b, 0xe5, 0x9f, 0x3f, 0x37, 0x7c,
233+
0x0d, 0x3f,
234+
];
235+
236+
// _nodes[height] slot
237+
let nodes_height_array_slot = U256::from_be_bytes(NODES_FIRST_ELEMENT_SLOT)
238+
.add(U256::from_be_bytes(tree_height.as_u8_array()));
239+
let mut hasher = crypto::sha3::Keccak256::new();
240+
hasher.update(nodes_height_array_slot.to_be_bytes::<32>());
241+
// _nodes[height][0]
242+
Bytes32::from_array(hasher.finalize())
243+
}
244+
245+
#[cfg(test)]
246+
mod tests {
247+
use super::*;
248+
249+
// test cases data was made using actual solidity implementation and debug data for read tx:
250+
// height 1: 0x768c3a22b1e4688c94525eb9bc2cf1ce7601fc9e871dc6e10fc44f0f06340ce1
251+
// height 3: 0x38ace9b5569ba016113e31884532182bc747997e743c0b7f9c307302b5f83760
252+
// height 4: 0x35817d789b7a6dbe8b95b0f21e189fb26d3d329de699cac7a267a9568298e0a5
253+
#[test]
254+
fn test_calculate_multichain_root_slot_tree_height_1() {
255+
let tree_height = [
256+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
257+
0, 0, 1,
258+
];
259+
let root_slot = calculate_multichain_root_slot(Bytes32::from_array(tree_height));
260+
261+
assert_eq!(
262+
root_slot.as_u8_array().to_vec(),
263+
hex::decode("768c3a22b1e4688c94525eb9bc2cf1ce7601fc9e871dc6e10fc44f0f06340ce1")
264+
.unwrap()
265+
);
266+
}
267+
268+
#[test]
269+
fn test_calculate_multichain_root_slot_tree_height_3() {
270+
let tree_height = [
271+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
272+
0, 0, 3,
273+
];
274+
let root_slot = calculate_multichain_root_slot(Bytes32::from_array(tree_height));
275+
276+
assert_eq!(
277+
root_slot.as_u8_array().to_vec(),
278+
hex::decode("38ace9b5569ba016113e31884532182bc747997e743c0b7f9c307302b5f83760")
279+
.unwrap()
280+
);
281+
}
282+
283+
#[test]
284+
fn test_calculate_multichain_root_slot_tree_height_4() {
285+
let tree_height = [
286+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
287+
0, 0, 4,
288+
];
289+
let root_slot = calculate_multichain_root_slot(Bytes32::from_array(tree_height));
290+
291+
assert_eq!(
292+
root_slot.as_u8_array().to_vec(),
293+
hex::decode("35817d789b7a6dbe8b95b0f21e189fb26d3d329de699cac7a267a9568298e0a5")
294+
.unwrap()
295+
);
296+
}
297+
}

basic_bootloader/src/bootloader/block_flow/zk/post_tx_op/post_tx_op_proving_multiblock_batch.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,8 @@ where
119119
assert_eq!(new_settlement_layer_chain_id, &settlement_layer_chain_id)
120120
}
121121

122+
let multichain_root = read_multichain_root(&mut io);
123+
122124
let (mut state_commitment, last_block_timestamp) = {
123125
let proof_data: ProofData<FlatStorageCommitment<TREE_HEIGHT>> =
124126
ZKProofDataQuery::get(&mut io.oracle, &())
@@ -182,6 +184,7 @@ where
182184
metadata.block_timestamp(),
183185
U256::from(metadata.chain_id()),
184186
upgrade_tx_hash,
187+
multichain_root,
185188
io.interop_root_storage.iter(),
186189
settlement_layer_chain_id,
187190
);

basic_bootloader/src/bootloader/block_flow/zk/post_tx_op/post_tx_op_proving_singleblock_batch.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,9 +94,11 @@ where
9494
&mut io,
9595
);
9696

97+
let multichain_root = read_multichain_root(&mut io);
98+
9799
let mut full_root_hasher = crypto::sha3::Keccak256::new();
98100
full_root_hasher.update(io.logs_storage.tree_root().as_u8_ref());
99-
full_root_hasher.update([0u8; 32]); // aggregated root 0 for now
101+
full_root_hasher.update(multichain_root.as_u8_ref());
100102
let full_l2_to_l1_logs_root = full_root_hasher.finalize().into();
101103

102104
let (priority_operations_hash, number_of_layer_1_txs) =

basic_bootloader/src/bootloader/block_flow/zk/post_tx_op/public_input.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ pub struct BatchOutput {
6767
/// L2 logs tree root.
6868
/// Note that it's full root, it's keccak256 of:
6969
/// - merkle root of l2 -> l1 logs in the batch .
70-
/// - aggregated root - commitment to logs emitted on chains that settle on the current.
70+
/// - multichain root - commitment to logs emitted on chains that settle on the current.
7171
pub l2_logs_tree_root: Bytes32,
7272
/// Protocol upgrade tx hash (0 if there wasn't)
7373
pub upgrade_tx_hash: Bytes32,

basic_bootloader/src/bootloader/constants.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ pub const MAX_CALLSTACK_DEPTH: usize = 1025;
2525
/// 32 for the suggested_signed_hash and 32 for the offset itself.
2626
pub const TX_CALLDATA_OFFSET: usize = 0x60;
2727

28-
/// Maximum value of gas that can be represented as ergs in a u64.
28+
/// Maximum value of gas that can be represented as ergs in an u64.
2929
pub const MAX_BLOCK_GAS_LIMIT: u64 = u64::MAX / ERGS_PER_GAS;
3030

3131
// Just for EVM compatibility.
@@ -91,6 +91,6 @@ pub const TESTER_NATIVE_PER_GAS: u64 = 25_000;
9191
// TODO (EVM-1157): find a reasonable value for it.
9292
pub const L1_TX_NATIVE_PRICE: U256 = U256::from_limbs([10, 0, 0, 0]);
9393

94-
// Upgrade transactions are expected to have ~72 million gas. We will use enough
94+
// Upgrade, service and gateway mailbox transactions are expected to have ~72 million gas. We will use enough
9595
// gas to ensure that multiplied by the 72 million they exceed the native computational limit.
96-
pub const UPGRADE_TX_NATIVE_PER_GAS: u64 = 10000;
96+
pub const FREE_L1_TX_NATIVE_PER_GAS: u64 = 10000;

basic_bootloader/src/bootloader/transaction_flow/zk/process_l1_transaction.rs

Lines changed: 8 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use crate::bootloader::config::BasicBootloaderExecutionConfig;
22
use crate::bootloader::constants::{
3-
L1_TX_INTRINSIC_NATIVE_COST, L1_TX_INTRINSIC_PUBDATA, L1_TX_NATIVE_PRICE,
4-
UPGRADE_TX_NATIVE_PER_GAS,
3+
FREE_L1_TX_NATIVE_PER_GAS, L1_TX_INTRINSIC_NATIVE_COST, L1_TX_INTRINSIC_PUBDATA,
4+
L1_TX_NATIVE_PRICE,
55
};
66
use crate::bootloader::errors::BootloaderInterfaceError;
77
use crate::bootloader::errors::TxError;
@@ -90,7 +90,7 @@ where
9090
native_per_gas,
9191
native_per_pubdata,
9292
minimal_gas_used,
93-
} = prepare_and_check_resources::<S, Config>(
93+
} = prepare_and_check_resources::<S>(
9494
system,
9595
transaction,
9696
is_priority_op,
@@ -366,11 +366,7 @@ struct ResourceAndFeeInfo<S: EthereumLikeTypes> {
366366
/// The approach is to use saturating arithmetic and emit a system
367367
/// log if this situation ever happens.
368368
///
369-
fn prepare_and_check_resources<
370-
'a,
371-
S: EthereumLikeTypes + 'a,
372-
Config: BasicBootloaderExecutionConfig,
373-
>(
369+
fn prepare_and_check_resources<'a, S: EthereumLikeTypes + 'a>(
374370
system: &mut System<S>,
375371
transaction: &AbiEncodedTransaction<S::Allocator>,
376372
is_priority_op: bool,
@@ -386,17 +382,8 @@ where
386382
// For L1->L2 txs, we use a constant native price to avoid censorship.
387383
let native_price = L1_TX_NATIVE_PRICE;
388384
let native_per_gas = if is_priority_op {
389-
if Config::SIMULATION && gas_price.is_zero() {
390-
// For simulation, if gas price isn't set, we use base fee
391-
// for native calculation
392-
u256_try_to_u64(&system.get_eip1559_basefee().div_ceil(native_price))
393-
.unwrap_or_else(|| {
394-
system_log!(
395-
system,
396-
"Native per gas calculation for L1 tx simulation overflows, using saturated arithmetic instead"
397-
);
398-
u64::MAX
399-
})
385+
if gas_price.is_zero() {
386+
FREE_L1_TX_NATIVE_PER_GAS
400387
} else {
401388
u256_try_to_u64(&gas_price.div_ceil(native_price))
402389
.unwrap_or_else(|| {
@@ -407,7 +394,8 @@ where
407394
})
408395
}
409396
} else {
410-
UPGRADE_TX_NATIVE_PER_GAS
397+
// Upgrade txs are paid by the protocol, so we use a fixed native per gas
398+
FREE_L1_TX_NATIVE_PER_GAS
411399
};
412400

413401
let native_per_pubdata = (gas_per_pubdata as u64)

system_hooks/src/addresses_constants.rs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,16 +30,19 @@ pub const L2_BASE_TOKEN_ADDRESS: B160 = B160::from_limbs([L2_BASE_TOKEN_ADDRESS_
3030
pub const MINT_HOOK_ADDRESS_LOW: u16 = 0x7100;
3131
pub const MINT_HOOK_ADDRESS: B160 = B160::from_limbs([MINT_HOOK_ADDRESS_LOW as u64, 0, 0]);
3232

33-
// Treasury contract used for "minting" base tokens on L2
34-
pub const BASE_TOKEN_HOLDER_ADDRESS_LOW: u32 = 0x10011;
35-
pub const BASE_TOKEN_HOLDER_ADDRESS: B160 =
36-
B160::from_limbs([BASE_TOKEN_HOLDER_ADDRESS_LOW as u64, 0, 0]);
33+
// L2 message root storage contract
34+
pub const MESSAGE_ROOT_ADDRESS: B160 = B160::from_limbs([0x10005, 0, 0]);
3735

3836
// L2 interop root storage system contract
3937
pub const L2_INTEROP_ROOT_STORAGE_ADDRESS_LOW: u32 = 0x10008;
4038
pub const L2_INTEROP_ROOT_STORAGE_ADDRESS: B160 =
4139
B160::from_limbs([L2_INTEROP_ROOT_STORAGE_ADDRESS_LOW as u64, 0, 0]);
4240

41+
// Treasury contract used for "minting" base tokens on L2
42+
pub const BASE_TOKEN_HOLDER_ADDRESS_LOW: u32 = 0x10011;
43+
pub const BASE_TOKEN_HOLDER_ADDRESS: B160 =
44+
B160::from_limbs([BASE_TOKEN_HOLDER_ADDRESS_LOW as u64, 0, 0]);
45+
4346
// ERA VM system contracts (in fact we need implement only the methods that should be available for user contracts)
4447
// TODO: may be better to implement as ifs inside EraVM EE
4548
pub const ACCOUNT_CODE_STORAGE_STORAGE_ADDRESS: B160 = B160::from_limbs([0x8002, 0, 0]);

0 commit comments

Comments
 (0)