Skip to content

Commit

Permalink
Merge branch 'main' into snap-trie-nodes
Browse files Browse the repository at this point in the history
  • Loading branch information
fmoletta authored Nov 8, 2024
2 parents 76640ef + 958fcc8 commit 6e146ee
Show file tree
Hide file tree
Showing 75 changed files with 2,612 additions and 743 deletions.
1 change: 1 addition & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ on:
- "LICENSE"
- "**/README.md"
- "**/docs/**"
- "crates/vm/levm/**" # We ran this in a separate workflow

concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
Expand Down
6 changes: 6 additions & 0 deletions .github/workflows/hive_and_assertoor.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,12 @@ jobs:
- simulation: snap
name: "Devp2p snap tests"
run_command: make run-hive-on-latest SIMULATION=devp2p TEST_PATTERN="/AccountRange|StorageRanges|ByteCodes|TrieNodes"
- simulation: eth
name: "Devp2p eth tests"
run_command: make run-hive SIMULATION=devp2p TEST_PATTERN="eth/getblockheaders"
- simulation: eth
name: "Devp2p eth tests"
run_command: make run-hive SIMULATION=devp2p TEST_PATTERN="eth/getblockheaders|getblockbodies"
- simulation: engine
name: "Engine tests"
run_command: make run-hive-on-latest SIMULATION=ethereum/engine TEST_PATTERN="/Blob Transactions On Block 1, Cancun Genesis|Blob Transactions On Block 1, Shanghai Genesis|Blob Transaction Ordering, Single Account, Single Blob|Blob Transaction Ordering, Single Account, Dual Blob|Blob Transaction Ordering, Multiple Accounts|Replace Blob Transactions|Parallel Blob Transactions|ForkchoiceUpdatedV3 Modifies Payload ID on Different Beacon Root|NewPayloadV3 After Cancun|NewPayloadV3 Versioned Hashes|ForkchoiceUpdated Version on Payload Request"
Expand Down
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
44 changes: 44 additions & 0 deletions .github/workflows/levm_ef.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
name: LEVM EF Tests

on:
merge_group:
paths:
- "crates/vm/levm/**"
pull_request:
paths:
- "crates/vm/levm/**"
branches: ["*"]

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

env:
CARGO_TERM_COLOR: always
RUST_VERSION: 1.79.0

jobs:
test:
name: EF Tests
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v4

- name: Rustup toolchain install
uses: dtolnay/rust-toolchain@stable
with:
toolchain: ${{ env.RUST_VERSION }}

- name: Caching
uses: Swatinem/rust-cache@v2

- name: Download EF Tests
run: |
cd crates/vm/levm
make download-ef-tests
- name: Run tests
run: |
cd crates/vm/levm
make run-ef-tests
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,4 @@ jsonwebtoken = "9.3.0"
rand = "0.8.5"
cfg-if = "1.0.0"
reqwest = { version = "0.12.7", features = ["json"] }
snap = "1.1.1"
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"]
16 changes: 15 additions & 1 deletion cmd/ethereum_rust/ethereum_rust.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ use std::{
net::{Ipv4Addr, SocketAddr, ToSocketAddrs},
};
use tokio_util::task::TaskTracker;
use tracing::{info, warn};
use tracing::{error, info, warn};
use tracing_subscriber::filter::Directive;
use tracing_subscriber::{EnvFilter, FmtSubscriber};
mod cli;
Expand Down Expand Up @@ -137,6 +137,20 @@ async fn main() {
block.header.number, hash, error
);
}
if store
.update_latest_block_number(block.header.number)
.is_err()
{
error!("Fatal: added block {} but could not update the block number -- aborting block import", block.header.number);
break;
};
if store
.set_canonical_block(block.header.number, hash)
.is_err()
{
error!("Fatal: added block {} but could not set it as canonical -- aborting block import", block.header.number);
break;
};
}
if let Some(last_block) = blocks.last() {
let hash = last_block.hash();
Expand Down
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
18 changes: 18 additions & 0 deletions crates/blockchain/mempool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,24 @@ pub fn remove_transaction(hash: &H256, store: &Store) -> Result<(), StoreError>
store.remove_transaction_from_pool(hash)
}

pub fn get_nonce(address: &Address, store: &Store) -> Result<Option<u64>, MempoolError> {
let pending_filter = PendingTxFilter {
min_tip: None,
base_fee: None,
blob_fee: None,
only_plain_txs: false,
only_blob_txs: false,
};

let pending_txs = filter_transactions(&pending_filter, store)?;
let nonce = match pending_txs.get(address) {
Some(txs) => txs.last().map(|tx| tx.nonce() + 1),
None => None,
};

Ok(nonce)
}

#[derive(Debug, Default)]
pub struct PendingTxFilter {
pub min_tip: Option<u64>,
Expand Down
1 change: 1 addition & 0 deletions crates/common/rlp/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ bytes.workspace = true
hex.workspace = true
lazy_static.workspace = true
ethereum-types.workspace = true
snap.workspace = true

[dev-dependencies]
hex-literal.workspace = true
Expand Down
46 changes: 38 additions & 8 deletions crates/common/rlp/decode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,19 +45,25 @@ impl RLPDecode for bool {

impl RLPDecode for u8 {
fn decode_unfinished(rlp: &[u8]) -> Result<(Self, &[u8]), RLPDecodeError> {
if rlp.is_empty() {
return Err(RLPDecodeError::InvalidLength);
}

match rlp[0] {
let first_byte = rlp.first().ok_or(RLPDecodeError::InvalidLength)?;
match first_byte {
// Single byte in the range [0x00, 0x7f]
0..=0x7f => Ok((rlp[0], &rlp[1..])),
0..=0x7f => {
let rest = rlp.get(1..).ok_or(RLPDecodeError::MalformedData)?;
Ok((*first_byte, rest))
}

// RLP_NULL represents zero
RLP_NULL => Ok((0, &rlp[1..])),
&RLP_NULL => {
let rest = rlp.get(1..).ok_or(RLPDecodeError::MalformedData)?;
Ok((0, rest))
}

// Two bytes, where the first byte is RLP_NULL + 1
x if rlp.len() >= 2 && x == RLP_NULL + 1 => Ok((rlp[1], &rlp[2..])),
x if rlp.len() >= 2 && *x == RLP_NULL + 1 => {
let rest = rlp.get(2..).ok_or(RLPDecodeError::MalformedData)?;
Ok((rlp[1], rest))
}

// Any other case is invalid for u8
_ => Err(RLPDecodeError::MalformedData),
Expand Down Expand Up @@ -330,6 +336,30 @@ impl<T1: RLPDecode, T2: RLPDecode, T3: RLPDecode> RLPDecode for (T1, T2, T3) {
}
}

// This implementation is useful when the message is a list with elements of mixed types
// for example, the P2P message 'GetBlockHeaders', mixes hashes and numbers.
impl<T1: RLPDecode, T2: RLPDecode, T3: RLPDecode, T4: RLPDecode> RLPDecode for (T1, T2, T3, T4) {
fn decode_unfinished(rlp: &[u8]) -> Result<(Self, &[u8]), RLPDecodeError> {
if rlp.is_empty() {
return Err(RLPDecodeError::InvalidLength);
}
let (is_list, payload, input_rest) = decode_rlp_item(rlp)?;
if !is_list {
return Err(RLPDecodeError::MalformedData);
}
let (first, first_rest) = T1::decode_unfinished(payload)?;
let (second, second_rest) = T2::decode_unfinished(first_rest)?;
let (third, third_rest) = T3::decode_unfinished(second_rest)?;
let (fourth, fourth_rest) = T4::decode_unfinished(third_rest)?;
// check that there is no more data to decode after the fourth element.
if !fourth_rest.is_empty() {
return Err(RLPDecodeError::MalformedData);
}

Ok(((first, second, third, fourth), input_rest))
}
}

/// Decodes an RLP item from a slice of bytes.
/// It returns a 3-element tuple with the following elements:
/// - A boolean indicating if the item is a list or not.
Expand Down
Loading

0 comments on commit 6e146ee

Please sign in to comment.