Skip to content

Commit

Permalink
Merge branch 'main' into l2_integration_test
Browse files Browse the repository at this point in the history
  • Loading branch information
ManuelBilbao committed Nov 7, 2024
2 parents cc44926 + 134bc94 commit 224c7d8
Show file tree
Hide file tree
Showing 36 changed files with 1,159 additions and 229 deletions.
32 changes: 32 additions & 0 deletions .github/workflows/l2_contracts.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
name: L2 Contracts CI
on:
push:
branches: ["main"]
paths:
- "crates/l2/contracts/**"
pull_request:
branches: ["**"]
paths:
- "crates/l2/contracts/**"

concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true

jobs:
test_compilation:
name: Compile Contracts
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v4
- name: Rust toolchain install
uses: dtolnay/rust-toolchain@stable
- name: Install solc
uses: pontem-network/get-solc@master
- name: Caching
uses: Swatinem/rust-cache@v2
- name: Run test of deployer.rs
run: |
cd crates/l2/contracts
cargo test
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ SPECTEST_VECTORS_DIR := cmd/ef_tests/vectors

CRATE ?= *
test: $(SPECTEST_VECTORS_DIR) ## 🧪 Run each crate's tests
cargo test -p '$(CRATE)' --workspace --exclude ethereum_rust-prover
cargo test -p '$(CRATE)' --workspace --exclude ethereum_rust-prover -- --skip test_contract_compilation

clean: clean-vectors ## 🧹 Remove build artifacts
cargo clean
Expand Down
11 changes: 6 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -288,11 +288,12 @@ At a high level, the following new parts are added to the node:
| 1 | The network supports basic L2 functionality, allowing users to deposit and withdraw funds to join and exit the network, while also interacting with the network as they do normally on the Ethereum network (deploying contracts, sending transactions, etc). ||
| 2 | The block execution is proven with a RISC-V zkVM and the proof is verified by the Verifier L1 contract. | 🏗️ |
| 3 | The network now commits to state diffs instead of the full state, lowering the commit transactions costs. These diffs are also submitted in compressed form, further reducing costs. It also supports EIP 4844 for L1 commit transactions, which means state diffs are sent as blob sidecars instead of calldata. ||
| 4 | The L2 can also be deployed using a custom native token, meaning that a certain ERC20 can be the common currency that's used for paying network fees. ||
| 5 | The L2 has added security mechanisms in place, running on Trusted Execution Environments and Multi Prover setup where multiple guarantees (Execution on TEEs, zkVMs/proving systems) are required for settlement on the L1. This better protects against possible security bugs on implementations. ||
| 6 | The L2 supports native account abstraction following EIP 7702, allowing for custom transaction validation logic and paymaster flows. ||
| 7 | The network can be run as a Based Contestable Rollup, meaning sequencing is done by the Ethereum Validator set; transactions are sent to a private mempool and L1 Validators that opt into the L2 sequencing propose blocks for the L2 on every L1 block. ||
| 8 | The L2 can be initialized in Validium Mode, meaning the Data Availability layer is no longer the L1, but rather a DA layer of the user's choice. ||
| 4 | The L2 supports native account abstraction following EIP 7702, allowing for custom transaction validation logic and paymaster flows. ||
| 5 | Support multiple L2s sharing the same bridge contract on L1 for seamless interoperability. ||
| 6 | The L2 can also be deployed using a custom native token, meaning that a certain ERC20 can be the common currency that's used for paying network fees. ||
| 7 | The L2 has added security mechanisms in place, running on Trusted Execution Environments and Multi Prover setup where multiple guarantees (Execution on TEEs, zkVMs/proving systems) are required for settlement on the L1. This better protects against possible security bugs on implementations. ||
| 8 | The network can be run as a Based Rollup, meaning sequencing is done by the Ethereum Validator set; transactions are sent to a private mempool and L1 Validators that opt into the L2 sequencing propose blocks for the L2 on every L1 block. ||
| 9 | The L2 can be initialized in Validium Mode, meaning the Data Availability layer is no longer the L1, but rather a DA layer of the user's choice. ||

### Milestone 0

Expand Down
1 change: 1 addition & 0 deletions cmd/ethereum_rust/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,4 @@ path = "./ethereum_rust.rs"
default = []
dev = ["dep:ethereum_rust-dev"]
l2 = ["ethereum_rust-vm/l2"]
levm = ["ethereum_rust-vm/levm", "ethereum_rust-blockchain/levm"]
2 changes: 2 additions & 0 deletions crates/blockchain/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ thiserror.workspace = true
sha3.workspace = true
tracing.workspace = true
bytes.workspace = true
cfg-if = "1.0.0"

ethereum_rust-rlp.workspace = true
ethereum_rust-core = { path = "../common", default-features = false }
Expand All @@ -29,3 +30,4 @@ libmdbx = [
"ethereum_rust-storage/default",
"ethereum_rust-vm/libmdbx",
]
levm = ["ethereum_rust-vm/levm"]
50 changes: 47 additions & 3 deletions crates/blockchain/blockchain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,7 @@ use ethereum_rust_core::H256;

use ethereum_rust_storage::error::StoreError;
use ethereum_rust_storage::Store;
use ethereum_rust_vm::{
evm_state, execute_block, get_state_transitions, spec_id, EvmState, SpecId,
};
use ethereum_rust_vm::{evm_state, execute_block, spec_id, EvmState, SpecId};

//TODO: Implement a struct Chain or BlockChain to encapsulate
//functionality and canonical chain state and config
Expand All @@ -27,7 +25,10 @@ use ethereum_rust_vm::{
/// canonical chain/head. Fork choice needs to be updated for that in a separate step.
///
/// Performs pre and post execution validation, and updates the database with the post state.
#[cfg(not(feature = "levm"))]
pub fn add_block(block: &Block, storage: &Store) -> Result<(), ChainError> {
use ethereum_rust_vm::get_state_transitions;

let block_hash = block.header.compute_block_hash();

// Validate if it can be the new head and find the parent
Expand Down Expand Up @@ -63,6 +64,49 @@ pub fn add_block(block: &Block, storage: &Store) -> Result<(), ChainError> {
Ok(())
}

/// Adds a new block to the store. It may or may not be canonical, as long as its ancestry links
/// with the canonical chain and its parent's post-state is calculated. It doesn't modify the
/// canonical chain/head. Fork choice needs to be updated for that in a separate step.
///
/// Performs pre and post execution validation, and updates the database with the post state.
#[cfg(feature = "levm")]
pub fn add_block(block: &Block, storage: &Store) -> Result<(), ChainError> {
let block_hash = block.header.compute_block_hash();

// Validate if it can be the new head and find the parent
let Ok(parent_header) = find_parent_header(&block.header, storage) else {
// If the parent is not present, we store it as pending.
storage.add_pending_block(block.clone())?;
return Err(ChainError::ParentNotFound);
};
let mut state = evm_state(storage.clone(), block.header.parent_hash);

// Validate the block pre-execution
validate_block(block, &parent_header, &state)?;

let (receipts, account_updates) = execute_block(block, &mut state)?;

// Note: these is commented because it is still being used in development.
// dbg!(&account_updates);

validate_gas_used(&receipts, &block.header)?;

// Apply the account updates over the last block's state and compute the new state root
let new_state_root = state
.database()
.ok_or(ChainError::StoreError(StoreError::MissingStore))?
.apply_account_updates(block.header.parent_hash, &account_updates)?
.ok_or(ChainError::ParentStateNotFound)?;

// Check state root matches the one in block header after execution
validate_state_root(&block.header, new_state_root)?;

store_block(storage, block.clone())?;
store_receipts(storage, receipts, block_hash)?;

Ok(())
}

/// Stores block and header in the database
pub fn store_block(storage: &Store, block: Block) -> Result<(), ChainError> {
storage.add_block(block)?;
Expand Down
2 changes: 2 additions & 0 deletions crates/blockchain/dev/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ ethereum-types.workspace = true
hex.workspace = true
reqwest = { version = "0.12.7", features = ["json"] }
envy = "0.4.2"
keccak-hash = "0.10.0"
sha2 = "0.10.8"

[lib]
path = "./dev.rs"
14 changes: 13 additions & 1 deletion crates/blockchain/dev/block_producer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use crate::utils::engine_client::EngineClient;
use bytes::Bytes;
use ethereum_rust_rpc::types::fork_choice::{ForkChoiceState, PayloadAttributesV3};
use ethereum_types::H256;
use sha2::{Digest, Sha256};
use std::{
net::SocketAddr,
time::{SystemTime, UNIX_EPOCH},
Expand Down Expand Up @@ -62,7 +63,18 @@ pub async fn start_block_producer(
let payload_status = match engine_client
.engine_new_payload_v3(
execution_payload_response.execution_payload,
Default::default(),
execution_payload_response
.blobs_bundle
.commitments
.iter()
.map(|commitment| {
let mut hasher = Sha256::new();
hasher.update(commitment);
let mut hash = hasher.finalize();
hash[0] = 0x01;
H256::from_slice(&hash)
})
.collect(),
Default::default(),
)
.await
Expand Down
54 changes: 51 additions & 3 deletions crates/common/types/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -851,6 +851,9 @@ impl PrivilegedTxType {
}

impl PrivilegedL2Transaction {
/// Returns the formated hash of the withdrawal transaction,
/// or None if the transaction is not a withdrawal.
/// The hash is computed as keccak256(to || value || tx_hash)
pub fn get_withdrawal_hash(&self) -> Option<H256> {
match self.tx_type {
PrivilegedTxType::Withdrawal => {
Expand All @@ -872,6 +875,26 @@ impl PrivilegedL2Transaction {
_ => None,
}
}

/// Returns the formated hash of the deposit transaction,
/// or None if the transaction is not a deposit.
/// The hash is computed as keccak256(to || value)
pub fn get_deposit_hash(&self) -> Option<H256> {
match self.tx_type {
PrivilegedTxType::Deposit => {
let to = match self.to {
TxKind::Call(to) => to,
_ => return None,
};

let value = &mut [0u8; 32];
self.value.to_big_endian(value);

Some(keccak_hash::keccak([to.as_bytes(), value].concat()))
}
_ => None,
}
}
}

/// Canonical Transaction Encoding
Expand Down Expand Up @@ -1594,16 +1617,15 @@ mod serde_impl {
pub gas: Option<u64>,
#[serde(default)]
pub value: U256,
#[serde(default, with = "crate::serde_utils::bytes", alias = "data")]
#[serde(default, with = "crate::serde_utils::bytes")]
pub input: Bytes,
#[serde(default, with = "crate::serde_utils::u64::hex_str")]
pub gas_price: u64,
#[serde(default, with = "crate::serde_utils::u64::hex_str_opt")]
pub max_priority_fee_per_gas: Option<u64>,
#[serde(default, with = "crate::serde_utils::u64::hex_str_opt")]
pub max_fee_per_gas: Option<u64>,
#[serde(default, with = "crate::serde_utils::u64::hex_str_opt")]
pub max_fee_per_blob_gas: Option<u64>,
pub max_fee_per_blob_gas: Option<U256>,
#[serde(default)]
pub access_list: Vec<AccessListEntry>,
#[serde(default)]
Expand Down Expand Up @@ -1639,6 +1661,32 @@ mod serde_impl {
}
}
}

impl From<EIP4844Transaction> for GenericTransaction {
fn from(value: EIP4844Transaction) -> Self {
Self {
r#type: TxType::EIP4844,
nonce: value.nonce,
to: TxKind::Call(value.to),
gas: Some(value.gas),
value: value.value,
input: value.data,
gas_price: value.max_fee_per_gas,
max_priority_fee_per_gas: Some(value.max_priority_fee_per_gas),
max_fee_per_gas: Some(value.max_fee_per_gas),
max_fee_per_blob_gas: Some(value.max_fee_per_blob_gas),
access_list: value
.access_list
.iter()
.map(AccessListEntry::from)
.collect(),
blob_versioned_hashes: value.blob_versioned_hashes,
blobs: vec![],
chain_id: None,
..Default::default()
}
}
}
}

mod mempool {
Expand Down
2 changes: 2 additions & 0 deletions crates/l2/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ libsecp256k1 = "0.7.1"
keccak-hash = "0.10.0"
envy = "0.4.2"
thiserror.workspace = true
sha2 = "0.10.8"
c-kzg = "^1.0.3"

# risc0
risc0-zkvm = { version = "1.1.2" }
Expand Down
34 changes: 34 additions & 0 deletions crates/l2/contracts/deployer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -392,3 +392,37 @@ async fn wait_for_transaction_receipt(tx_hash: H256, eth_client: &EthClient) {
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
}
}

#[cfg(test)]
mod test {
use crate::{compile_contracts, download_contract_deps};
use std::env;

#[test]
fn test_contract_compilation() {
let binding = env::current_dir().unwrap();
let parent_dir = binding.parent().unwrap();

env::set_current_dir(parent_dir).expect("Failed to change directory");

let solc_out = parent_dir.join("contracts/solc_out");
let lib = parent_dir.join("contracts/lib");

if let Err(e) = std::fs::remove_dir_all(&solc_out) {
if e.kind() != std::io::ErrorKind::NotFound {
panic!();
}
}
if let Err(e) = std::fs::remove_dir_all(&lib) {
if e.kind() != std::io::ErrorKind::NotFound {
panic!();
}
}

download_contract_deps();
compile_contracts();

std::fs::remove_dir_all(solc_out).unwrap();
std::fs::remove_dir_all(lib).unwrap();
}
}
28 changes: 25 additions & 3 deletions crates/l2/contracts/src/l1/CommonBridge.sol
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,9 @@ contract CommonBridge is ICommonBridge, Ownable, ReentrancyGuard {

// TODO: Build the tx.
bytes32 l2MintTxHash = keccak256(abi.encodePacked("dummyl2MintTxHash"));
depositLogs.push(keccak256(abi.encodePacked(to, msg.value)));
depositLogs.push(
keccak256(bytes.concat(bytes20(to), bytes32(msg.value)))
);
emit DepositInitiated(msg.value, to, l2MintTxHash);
}

Expand All @@ -70,10 +72,30 @@ contract CommonBridge is ICommonBridge, Ownable, ReentrancyGuard {
}

/// @inheritdoc ICommonBridge
function removeDepositLogs(uint number) public onlyOnChainProposer {
function getDepositLogsVersionedHash(
uint16 number
) public view returns (bytes32) {
require(number > 0, "CommonBridge: number is zero (get)");
require(
uint256(number) <= depositLogs.length,
"CommonBridge: number is greater than the length of depositLogs (get)"
);

bytes memory logs;
for (uint i = 0; i < number; i++) {
logs = bytes.concat(logs, depositLogs[i]);
}

return
bytes32(bytes2(number)) |
bytes32(uint256(uint240(uint256(keccak256(logs)))));
}

/// @inheritdoc ICommonBridge
function removeDepositLogs(uint16 number) public onlyOnChainProposer {
require(
number <= depositLogs.length,
"CommonBridge: number is greater than the length of depositLogs"
"CommonBridge: number is greater than the length of depositLogs (remove)"
);

for (uint i = 0; i < depositLogs.length - number; i++) {
Expand Down
Loading

0 comments on commit 224c7d8

Please sign in to comment.