diff --git a/.tool-versions b/.tool-versions index 0070a0b1b..d4e705550 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1 +1,2 @@ rust 1.80.1 +# golang 1.23.2 diff --git a/README.md b/README.md index a44f5f587..ab050d927 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,8 @@ The following links, repos, companies and projects have been important in the de - [Optimism](https://www.optimism.io/) - [Arbitrum](https://arbitrum.io/) - [Geth](https://github.com/ethereum/go-ethereum) +- [Taiko](https://taiko.xyz/) +- [RISC Zero](https://risczero.com/) - [SP1](https://github.com/succinctlabs/sp1) - [Aleo](https://aleo.org/) - [Neptune](https://neptune.cash/) @@ -71,16 +73,33 @@ The main differences between this mode and regular Ethereum Rust are: ### Table of Contents -- [Roadmap](#roadmap) +- [Lambda Ethereum Rust Execution Client](#lambda-ethereum-rust-execution-client) + - [๐Ÿ“š References and acknowledgements](#-references-and-acknowledgements) + - [Philosophy](#philosophy) + - [Design Principles](#design-principles) +- [L1 and L2 support](#l1-and-l2-support) +- [Lambda Ethereum Rust L2](#lambda-ethereum-rust-l2) +- [Lambda Ethereum Rust L1](#lambda-ethereum-rust-l1) + - [Table of Contents](#table-of-contents) + - [Roadmap](#roadmap) - [Milestone 1: Read-only RPC Node Support](#milestone-1-read-only-rpc-node-support) - - [Milestone 2: History & Reorgs](#milestone-2-history--reorgs) + - [Milestone 2: History \& Reorgs](#milestone-2-history--reorgs) - [Milestone 3: Block building](#milestone-3-block-building) - [Milestone 4: P2P Network](#milestone-4-p2p-network) - - [Milestone 5: Syncing](#milestone-5-syncing) -- [Quick Start](#quick-start-l1-localnet) -- [Dev Setup](#dev-setup) -- [CLI Commands](#cli-commands) -- [Documentation](#crates-documentation) + - [Milestone 5: State Sync](#milestone-5-state-sync) + - [Quick Start (L1 localnet)](#quick-start-l1-localnet) + - [Prerequisites](#prerequisites) + - [Dev Setup](#dev-setup) + - [Build](#build) + - [Rust](#rust) + - [Database](#database) + - [Test](#test) + - [Ethereum Foundation Tests](#ethereum-foundation-tests) + - [Crate Specific Tests](#crate-specific-tests) + - [Hive Tests](#hive-tests) + - [Run](#run) + - [CLI Commands](#cli-commands) +- [Crates documentation](#crates-documentation) ## Roadmap @@ -250,6 +269,23 @@ make test CRATE="ethereum_rust-blockchain" Finally, we have End-to-End tests with hive. Hive is a system which simply sends RPC commands to our node, and expects a certain response. You can read more about it [here](https://github.com/ethereum/hive/blob/master/docs/overview.md). + +###### Prereqs +We need to have go installed for the first time we run hive, an easy way to do this is adding the asdf go plugin: + +```shell +asdf plugin add golang https://github.com/asdf-community/asdf-golang.git + +# If you need to se GOROOT please follow: https://github.com/asdf-community/asdf-golang?tab=readme-ov-file#goroot +``` + +And uncommenting the golang line in the asdf `.tool-versions` file: +``` +rust 1.80.1 +golang 1.23.2 +``` + +###### Running Simulations Hive tests are categorized by "simulations', and test instances can be filtered with a regex: ```bash make run-hive-debug SIMULATION= TEST_PATTERN= diff --git a/crates/l2/Makefile b/crates/l2/Makefile index 838b243f9..72e88081f 100644 --- a/crates/l2/Makefile +++ b/crates/l2/Makefile @@ -1,69 +1,70 @@ -.PHONY: init down clean init-local-l1 down-local-l1 clean-local-l1 init-l2 down-l2 deploy-l1 deploy-block-executor deploy-inbox setup-prover +.DEFAULT_GOAL := init + +.PHONY: help init down clean init-local-l1 down-local-l1 clean-local-l1 init-l2 down-l2 deploy-l1 deploy-block-executor deploy-inbox setup-prover L2_GENESIS_FILE_PATH=../../test_data/genesis-l2.json -init: init-local-l1 contract-deps setup-prover deploy-l1 init-l2 +help: ## ๐Ÿ“š Show help for each of the Makefile recipes + @grep -E '^[a-zA-Z0-9_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' -down: down-local-l1 down-l2 +init: init-local-l1 contract-deps deploy-l1 init-l2 ## ๐Ÿš€ Initializes a localnet with Lambda Ethereum Rust client as both L1 and L2 -clean: clean-local-l1 clean-contract-deps +down: down-local-l1 down-l2 ## ๐Ÿ›‘ Shuts down the localnet -restart: restart-local-l1 restart-contract-deps restart-l2 +clean: clean-contract-deps ## ๐Ÿงน Cleans the localnet -# Contracts +restart: restart-local-l1 restart-contract-deps restart-l2 ## ๐Ÿ”„ Restarts the localnet + +cli: ## ๐Ÿ› ๏ธ Builds the L2 Lambda Ethereum Rust CLI + cargo build --release --manifest-path ${ETHEREUM_RUST_PATH}/cmd/ethereum_rust/Cargo.toml + +# Variables + +ETHEREUM_RUST_PATH=$(shell pwd)/../../ +ETHEREUM_RUST_BIN_PATH=$(ETHEREUM_RUST_PATH)/target/release/ethereum_rust +ETHEREUM_RUST_DEV_DOCKER_COMPOSE_PATH=$(ETHEREUM_RUST_PATH)/crates/blockchain/dev/docker-compose-dev.yaml FOUNDRY_PROJECT_HOME=$(shell pwd)/contracts L1_RPC_URL=http://localhost:8545 L1_PRIVATE_KEY=0x385c546456b6a603a1cfcaa9ec9494ba4832da08dd6bcf4de9a71e4a01b74924 -contract-deps: - mkdir -p ${FOUNDRY_PROJECT_HOME} - forge install foundry-rs/forge-std --no-git --root ${FOUNDRY_PROJECT_HOME} - forge install succinctlabs/sp1-contracts --no-git --root ${FOUNDRY_PROJECT_HOME} +# Local L1 -clean-contract-deps: - rm -rf contracts/lib +init-local-l1: ## ๐Ÿš€ Initializes an L1 Lambda Ethereum Rust Client + docker compose -f ${ETHEREUM_RUST_DEV_DOCKER_COMPOSE_PATH} up -d -restart-contract-deps: clean-contract-deps contract-deps +down-local-l1: ## ๐Ÿ›‘ Shuts down the L1 Lambda Ethereum Rust Client + docker compose -f ${ETHEREUM_RUST_DEV_DOCKER_COMPOSE_PATH} down -deploy-l1: - cd ${FOUNDRY_PROJECT_HOME} && \ - forge script script/DeployL1.s.sol:DeployL1Script --rpc-url ${L1_RPC_URL} --private-key ${L1_PRIVATE_KEY} --broadcast +restart-local-l1: down-local-l1 init-local-l1 ## ๐Ÿ”„ Restarts the L1 Lambda Ethereum Rust Client -deploy-block-executor: - forge create ${FOUNDRY_PROJECT_HOME}/src/l1/OnChainOperator.sol:OnChainOperator --rpc-url ${L1_RPC_URL} --private-key ${L1_PRIVATE_KEY} +# Contracts -deploy-inbox: - forge create ${FOUNDRY_PROJECT_HOME}/src/l1/Inbox.sol:Inbox --rpc-url ${L1_RPC_URL} --private-key ${L1_PRIVATE_KEY} +contract-deps: ## ๐Ÿ“ฆ Installs the dependencies for the L1 contracts + mkdir -p ${FOUNDRY_PROJECT_HOME} + forge install foundry-rs/forge-std --no-git --root ${FOUNDRY_PROJECT_HOME} || exit 0 -# Local L1 +clean-contract-deps: ## ๐Ÿงน Cleans the dependencies for the L1 contracts. + rm -rf contracts/lib -init-local-l1: - mkdir -p volumes/ volumes/reth volumes/reth/data - docker compose -f docker-compose-l2.yml up -d +restart-contract-deps: clean-contract-deps contract-deps ## ๐Ÿ”„ Restarts the dependencies for the L1 contracts. -down-local-l1: - docker compose -f docker-compose-l2.yml down +deploy-l1: ## ๐Ÿ“œ Deploys the L1 contracts + cd ${FOUNDRY_PROJECT_HOME} && \ + forge script script/DeployL1.s.sol:DeployL1Script --rpc-url ${L1_RPC_URL} --private-key ${L1_PRIVATE_KEY} --broadcast --use $$(which solc) -clean-local-l1: - rm -rf volumes/ +deploy-on-chain-operator: ## ๐Ÿ“œ Deploys the OnChainOperator contract in L1 + forge create ${FOUNDRY_PROJECT_HOME}/src/l1/OnChainOperator.sol:OnChainOperator --rpc-url ${L1_RPC_URL} --private-key ${L1_PRIVATE_KEY} -restart-local-l1: down-local-l1 clean-local-l1 init-local-l1 +deploy-bridge: ## ๐Ÿ“œ Deploys the CommonBridge contract in L1 + forge create ${FOUNDRY_PROJECT_HOME}/src/l1/CommonBridge.sol:CommonBridge --rpc-url ${L1_RPC_URL} --private-key ${L1_PRIVATE_KEY} # L2 -init-l2: +init-l2: ## ๐Ÿš€ Initializes an L2 Lambda Ethereum Rust Client cargo run --release --manifest-path ../../Cargo.toml --bin ethereum_rust --features l2 -- --network ${L2_GENESIS_FILE_PATH} --http.port 1729 -down-l2: +down-l2: ## ๐Ÿ›‘ Shuts down the L2 Lambda Ethereum Rust Client pkill -f ethereum_rust || exit 0 -restart-l2: down-l2 init-l2 - -# Prover - -SP1_PROGRAM_PATH=$(shell pwd)/prover/sp1/program - -setup-prover: - cd ${SP1_PROGRAM_PATH} && \ - cargo prove build +restart-l2: down-l2 init-l2 ## ๐Ÿ”„ Restarts the L2 Lambda Ethereum Rust Client diff --git a/crates/l2/README.md b/crates/l2/README.md index 20a75a3f8..2c8043b96 100644 --- a/crates/l2/README.md +++ b/crates/l2/README.md @@ -2,32 +2,37 @@ ## Table of Contents -- [Table of Contents](#table-of-contents) -- [Roadmap](#roadmap) - - [Milestone 0](#milestone-0) - - [Status](#status) - - [Milestone 1: MVP](#milestone-1-mvp) - - [Status](#status-1) - - [Milestone 2: State diffs + Data compression + EIP 4844 (Blobs)](#milestone-2-state-diffs--data-compression--eip-4844-blobs) - - [Status](#status-2) - - [Milestone 3: Custom Native token](#milestone-3-custom-native-token) - - [Milestone 4: Based Contestable Rollup](#milestone-4-based-contestable-rollup) - - [Milestone 5: Security (TEEs and Multi Prover support)](#milestone-5-security-tees-and-multi-prover-support) - - [Status](#status-3) - - [Milestone 6: Account Abstraction](#milestone-6-account-abstraction) - - [Status](#status-4) - - [Milestone 7: Validium](#milestone-7-validium) - - [Status](#status-5) -- [Prerequisites](#prerequisites) - - [Foundry](#foundry) -- [How to run](#how-to-run) - - [Install `ethereum_rust_l2` CLI](#install-ethereum_rust_l2-cli) - - [Configure your network](#configure-your-network) - - [Initialize the network](#initialize-the-network) - - [Restarting the network](#restarting-the-network) -- [Local L1 Rich Wallets](#local-l1-rich-wallets) -- [Docs](#docs) -- [๐Ÿ“š References and acknowledgements](#-references-and-acknowledgements) +- [Ethereum Rust L2](#ethereum-rust-l2) + - [Table of Contents](#table-of-contents) + - [Roadmap](#roadmap) + - [Milestone 0](#milestone-0) + - [Status](#status) + - [Milestone 1: MVP](#milestone-1-mvp) + - [Status](#status-1) + - [Milestone 2: Block Execution Proofs](#milestone-2-block-execution-proofs) + - [Status](#status-2) + - [Milestone 3: State diffs + Data compression + EIP 4844 (Blobs)](#milestone-3-state-diffs--data-compression--eip-4844-blobs) + - [Status](#status-3) + - [Milestone 4: Custom Native token](#milestone-4-custom-native-token) + - [Status](#status-4) + - [Milestone 5: Based Contestable Rollup](#milestone-5-based-contestable-rollup) + - [Status](#status-5) + - [Milestone 6: Security (TEEs and Multi Prover support)](#milestone-6-security-tees-and-multi-prover-support) + - [Status](#status-6) + - [Milestone 7: Account Abstraction](#milestone-7-account-abstraction) + - [Status](#status-7) + - [Milestone 8: Validium](#milestone-8-validium) + - [Status](#status-8) + - [Prerequisites](#prerequisites) + - [Foundry](#foundry) + - [How to run](#how-to-run) + - [Install `ethereum_rust_l2` CLI](#install-ethereum_rust_l2-cli) + - [Configure your network](#configure-your-network) + - [Initialize the network](#initialize-the-network) + - [Restarting the network](#restarting-the-network) + - [Local L1 Rich Wallets](#local-l1-rich-wallets) + - [Docs](#docs) + - [๐Ÿ“š References and acknowledgements](#-references-and-acknowledgements) ## Roadmap @@ -35,11 +40,13 @@ | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------ | | 0 | Users can deposit Eth in the L1 (Ethereum) and receive the corresponding funds on the L2. | โœ… | | 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 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. | โŒ | -| 3 | 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. | โŒ | -| 4 | 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. | โŒ | -| 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 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. The L2 supports native account abstraction following EIP 4337, allowing for custom transaction validation logic and paymaster flows. | โŒ | +| 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 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. | โŒ | +| 6 | 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. | โŒ | +| 7 | The L2 supports native account abstraction following EIP 4337, allowing for custom transaction validation logic and paymaster flows. | โŒ | +| 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. | โŒ | ### Milestone 0 @@ -73,11 +80,23 @@ The network supports basic L2 functionality, allowing users to deposit and withd | Contracts | `CommonBridge` | Withdraw method implementation | โŒ | | | `OnChainOperator` | Commit and verify implementation | ๐Ÿ—๏ธ | | | `Verifier` | verifier | ๐Ÿ—๏ธ | -| Proposer | `Prover Server` | Feeds the `Prover Client` with block data to be proven and delivers proofs to the `L1TxSender` for L1 verification | ๐Ÿ—๏ธ | | | Withdraw transactions handling | New transaction type for burning funds on L2 and unlock funds on L1 | ๐Ÿ—๏ธ | | Prover | `Prover Client` | Asks for block execution data to prove, generates proofs of execution and submits proofs to the `Prover Server` | ๐Ÿ—๏ธ | -### Milestone 2: State diffs + Data compression + EIP 4844 (Blobs) +### Milestone 2: Block Execution Proofs + +The L2's block execution is proven with a RISC-V zkVM and the proof is verified by the Verifier L1 contract. This work is being done in parallel with other milestones as it doesn't block anything else. + +#### Status + +| | Name | Description | Status | +| --------- | ------------------------------ | ------------------------------------------------------------------------------------------------------------------ | ------ | +| VM | | `Return` the storage touched on block execution to pass the prover as a witness | ๐Ÿ—๏ธ | +| Contracts | `OnChainOperator` | Call the actual SNARK proof verification on the `verify` function implementation | ๐Ÿ—๏ธ | +| Proposer | `Prover Server` | Feeds the `Prover Client` with block data to be proven and delivers proofs to the `L1TxSender` for L1 verification | ๐Ÿ—๏ธ | +| Prover | `Prover Client` | Asks for block execution data to prove, generates proofs of execution and submits proofs to the `Prover Server` | ๐Ÿ—๏ธ | + +### Milestone 3: State diffs + Data compression + EIP 4844 (Blobs) 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. @@ -97,10 +116,12 @@ It also supports EIP 4844 for L1 commit transactions, which means state diffs ar | CLI | `reconstruct-state` | Add a command for reconstructing the state | โŒ | | | `init` | Adapt network initialization to either send blobs or calldata | โŒ | -### Milestone 3: Custom Native token +### Milestone 4: Custom Native token 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. +#### Status + | | Name | Description | Status | | --- | -------------- | ----------------------------------------------------------------------------------------- | ------ | | | `CommonBridge` | For native token withdrawals, infer the native token and reimburse the user in that token | โŒ | @@ -109,17 +130,19 @@ The L2 can also be deployed using a custom native token, meaning that a certain | | `deposit` | Handle native token deposits | โŒ | | | `withdraw` | Handle native token withdrawals | โŒ | -### Milestone 4: Based Contestable Rollup +### Milestone 5: Based Contestable Rollup 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. +#### Status + | | Name | Description | Status | | --- | ----------------- | ------------------------------------------------------------------------------ | ------ | | | `OnChainOperator` | Add methods for proposing new blocks so the sequencing can be done from the L1 | โŒ | TODO: Expand on this. -### Milestone 5: Security (TEEs and Multi Prover support) +### Milestone 6: Security (TEEs and Multi Prover support) 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. @@ -131,7 +154,7 @@ The L2 has added security mechanisms in place, running on Trusted Execution Envi | Contracts | | Support verifying multiple different zkVM executions | โŒ | | VM | | Support running the operator on a TEE environment | โŒ | -### Milestone 6: Account Abstraction +### Milestone 7: Account Abstraction The L2 supports native account abstraction following EIP 7702, allowing for custom transaction validation logic and paymaster flows. @@ -142,7 +165,7 @@ The L2 supports native account abstraction following EIP 7702, allowing for cust TODO: Expand on account abstraction tasks. -### Milestone 7: Validium +### Milestone 8: Validium 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. @@ -174,42 +197,17 @@ The L2 can be initialized in Validium Mode, meaning the Data Availability layer ## How to run -### Install `ethereum_rust_l2` CLI - -First of all, you need to install the `ethereum_rust_l2` CLI. You can do that by running the command below at the root of this repo: - -``` -cargo install --path cmd/ethereum_rust_l2 -``` - -> [!IMPORTANT] -> Most of the CLI interaction needs a configuration to be set. You can set a configuration with the `config` command. - -### Configure your network - -> [!TIP] -> You can create multiple configurations and switch between them. - -``` -ethereum_rust_l2 config create -``` - -![](../../cmd/ethereum_rust_l2/assets/config_create.cast.gif) - ### Initialize the network > [!IMPORTANT] -> Before this step, make sure the Docker daemon is running. - -> [!IMPORTANT] -> Add the SPI_PROVER=mock env variable to the command (to run the prover you need ). +> Before this step: +> 1. make sure the Docker daemon is running. +> 2. make sure you have created a `.env` file following the `.env.example` file. ``` -ethereum_rust_l2 stack init +make ``` -![](../../cmd/ethereum_rust_l2/assets/stack_init.cast.gif) - This will setup a local Ethereum network as the L1, deploy all the needed contracts on it, then start an Ethereum Rust L2 node pointing to it. ### Restarting the network @@ -218,11 +216,9 @@ This will setup a local Ethereum network as the L1, deploy all the needed contra > This command will cleanup your running L1 and L2 nodes. ``` -ethereum_rust_l2 stack restart +make restart ``` -![](../../cmd/ethereum_rust_l2/assets/stack_restart.cast.gif) - ## Local L1 Rich Wallets Most of them are [here](https://github.com/ethpandaops/ethereum-package/blob/main/src/prelaunch_data_generator/genesis_constants/genesis_constants.star), but there's an extra one: diff --git a/crates/networking/p2p/README.md b/crates/networking/p2p/README.md index af922d588..39030074b 100644 --- a/crates/networking/p2p/README.md +++ b/crates/networking/p2p/README.md @@ -5,7 +5,7 @@ The network crate handles the ethereum networking protocols. This involves: - [Discovery protocol](#discovery-protocol): built on top of udp and it is how we discover new nodes. - devP2P: sits on top of tcp and is where the actual blockchain information exchange happens. -The official spec can be found [here](https://github.com/ethereum/devp2p/tree/master). +Implementation follows the official spec which can be found [here](https://github.com/ethereum/devp2p/tree/master). Also, we've inspired in some [geth code](https://github.com/ethereum/go-ethereum/tree/master/p2p/discover). ## Discovery protocol @@ -69,7 +69,7 @@ Re-validations are tasks that are implemented as intervals, that is: they run an - otherwise: we decrement the liveness by a third of its value. 3. If the liveness field is 0, we delete it and insert a new one from the replacements table. -Liveness is a field that provides us with good criteria of which nodes are connected and we "trust" more. This trustiness is useful when deciding if we want to store this node in the database to use it as a future seeder or when establishing a connection in p2p. +Liveness checks are not part of the spec but are taken from geth, see [here](https://github.com/ethereum/go-ethereum/blob/master/p2p/discover/table_reval.go). This field is useful because it provides us with good criteria of which nodes are connected and we "trust" more. This trustiness is useful when deciding if we want to store this node in the database to use it as a future seeder or when establishing a connection in p2p. Re-validations are another point of potential improvement. While it may be fine for now to keep simplicity at max, pinging the last recently pinged peers becomes quite expensive as the number of peers in the table increases. And it also isn't very "just" in selecting nodes so that they get their liveness increased so we trust them more and we might consider them as a seeder. A possible improvement could be: @@ -79,6 +79,8 @@ Re-validations are another point of potential improvement. While it may be fine - When picking a node to ping, we would do it randomly, which is the best form of justice for a node to become trusted by us. - When a node from `b` responds successfully, we move it to `a`, and when one from `a` does not respond, we move it to `b`. +This improvement follows somewhat what geth does, see [here](https://github.com/ethereum/go-ethereum/blob/master/p2p/discover/table_reval.go). + ### Recursive Lookups Recursive lookups are as with re-validations implemented as intervals. Their current flow is as follows: @@ -92,6 +94,8 @@ Recursive lookups are as with re-validations implemented as intervals. Their cur 4. We wait for the neighbors' response and push or replace those who are closer to the potential peers. 5. We select three other nodes from the potential peers vector and do the same until one lookup has no node to ask. +The way to do lookups aren't part of the spec. Our implementation aligns with geth approach, see [here](https://github.com/ethereum/go-ethereum/blob/master/p2p/discover/v4_udp.go#L282-L310). + ### An example of how you might build a network Finally, here is an example of how you could build a network and see how they connect each other: diff --git a/crates/networking/p2p/rlpx/eth.rs b/crates/networking/p2p/rlpx/eth.rs index 6f8fa36f6..8f9fc09e2 100644 --- a/crates/networking/p2p/rlpx/eth.rs +++ b/crates/networking/p2p/rlpx/eth.rs @@ -1,3 +1,4 @@ +use super::{message::RLPxMessage, utils::snappy_encode}; use bytes::BufMut; use ethereum_rust_core::{ types::{BlockBody, BlockHash, BlockHeader, BlockNumber, ForkId}, @@ -15,7 +16,7 @@ use snap::raw::Decoder as SnappyDecoder; pub const ETH_VERSION: u32 = 68; pub const HASH_FIRST_BYTE_DECODER: u8 = 160; -use super::{message::RLPxMessage, utils::snappy_encode}; +mod transactions; #[derive(Debug)] pub(crate) struct StatusMessage { diff --git a/crates/networking/p2p/rlpx/eth/transactions.rs b/crates/networking/p2p/rlpx/eth/transactions.rs new file mode 100644 index 000000000..4923e3b19 --- /dev/null +++ b/crates/networking/p2p/rlpx/eth/transactions.rs @@ -0,0 +1,266 @@ +use bytes::BufMut; +use ethereum_rust_core::{types::Transaction, H256}; +use ethereum_rust_rlp::{ + error::{RLPDecodeError, RLPEncodeError}, + structs::{Decoder, Encoder}, +}; +use snap::raw::Decoder as SnappyDecoder; + +use crate::rlpx::message::RLPxMessage; + +use super::snappy_encode; + +// https://github.com/ethereum/devp2p/blob/master/caps/eth.md#transactions-0x02 +// Broadcast message +#[derive(Debug)] +pub(crate) struct Transactions { + transactions: Vec, +} + +impl Transactions { + pub fn new(transactions: Vec) -> Self { + Self { transactions } + } +} + +impl RLPxMessage for Transactions { + fn encode(&self, buf: &mut dyn BufMut) -> Result<(), RLPEncodeError> { + let mut encoded_data = vec![]; + Encoder::new(&mut encoded_data) + .encode_field(&self.transactions) + .finish(); + + let msg_data = snappy_encode(encoded_data)?; + buf.put_slice(&msg_data); + Ok(()) + } + + fn decode(msg_data: &[u8]) -> Result { + let mut snappy_decoder = SnappyDecoder::new(); + let decompressed_data = snappy_decoder + .decompress_vec(msg_data) + .map_err(|e| RLPDecodeError::Custom(e.to_string()))?; + let decoder = Decoder::new(&decompressed_data)?; + let (transactions, _): (Vec, _) = decoder.decode_field("transactions")?; + + Ok(Self::new(transactions)) + } +} + +// https://github.com/ethereum/devp2p/blob/master/caps/eth.md#newpooledtransactionhashes-0x08 +// Broadcast message +#[derive(Debug)] +pub(crate) struct NewPooledTransactionHashes { + transaction_types: Vec, + transaction_sizes: Vec, + transaction_hashes: Vec, +} + +impl NewPooledTransactionHashes { + // delete this after we use this in the main loop + #[allow(dead_code)] + pub fn new(transactions: Vec) -> Self { + let transactions_len = transactions.len(); + let mut transaction_types = Vec::with_capacity(transactions_len); + let mut transaction_sizes = Vec::with_capacity(transactions_len); + let mut transaction_hashes = Vec::with_capacity(transactions_len); + for transaction in transactions { + let transaction_type = transaction.tx_type(); + transaction_types.push(transaction_type as u8); + // size is defined as the len of the concatenation of tx_type and the tx_data + // as the tx_type goes from 0x00 to 0xff, the size of tx_type is 1 byte + let transaction_size = 1 + transaction.data().len(); + transaction_sizes.push(transaction_size); + let transaction_hash = transaction.compute_hash(); + transaction_hashes.push(transaction_hash); + } + Self { + transaction_types, + transaction_sizes, + transaction_hashes, + } + } +} + +impl RLPxMessage for NewPooledTransactionHashes { + fn encode(&self, buf: &mut dyn BufMut) -> Result<(), RLPEncodeError> { + let mut encoded_data = vec![]; + Encoder::new(&mut encoded_data) + .encode_field(&self.transaction_types) + .encode_field(&self.transaction_sizes) + .encode_field(&self.transaction_hashes) + .finish(); + + let msg_data = snappy_encode(encoded_data)?; + buf.put_slice(&msg_data); + Ok(()) + } + + fn decode(msg_data: &[u8]) -> Result { + let mut snappy_decoder = SnappyDecoder::new(); + let decompressed_data = snappy_decoder + .decompress_vec(msg_data) + .map_err(|e| RLPDecodeError::Custom(e.to_string()))?; + let decoder = Decoder::new(&decompressed_data)?; + let (transaction_types, decoder): (Vec, _) = + decoder.decode_field("transactionTypes")?; + let (transaction_sizes, decoder): (Vec, _) = + decoder.decode_field("transactionSizes")?; + let (transaction_hashes, _): (Vec, _) = decoder.decode_field("transactionHashes")?; + + if transaction_hashes.len() == transaction_sizes.len() + && transaction_sizes.len() == transaction_types.len() + { + Ok(Self { + transaction_types, + transaction_sizes, + transaction_hashes, + }) + } else { + Err(RLPDecodeError::Custom( + "transaction_hashes, transaction_sizes and transaction_types must have the same length" + .to_string(), + )) + } + } +} + +// https://github.com/ethereum/devp2p/blob/master/caps/eth.md#getpooledtransactions-0x09 +#[derive(Debug)] +pub(crate) struct GetPooledTransactions { + // id is a u64 chosen by the requesting peer, the responding peer must mirror the value for the response + // https://github.com/ethereum/devp2p/blob/master/caps/eth.md#protocol-messages + id: u64, + transaction_hashes: Vec, +} + +impl GetPooledTransactions { + pub fn new(id: u64, transaction_hashes: Vec) -> Self { + Self { + transaction_hashes, + id, + } + } +} + +impl RLPxMessage for GetPooledTransactions { + fn encode(&self, buf: &mut dyn BufMut) -> Result<(), RLPEncodeError> { + let mut encoded_data = vec![]; + Encoder::new(&mut encoded_data) + .encode_field(&self.id) + .encode_field(&self.transaction_hashes) + .finish(); + + let msg_data = snappy_encode(encoded_data)?; + buf.put_slice(&msg_data); + Ok(()) + } + + fn decode(msg_data: &[u8]) -> Result { + let mut snappy_decoder = SnappyDecoder::new(); + let decompressed_data = snappy_decoder + .decompress_vec(msg_data) + .map_err(|e| RLPDecodeError::Custom(e.to_string()))?; + let decoder = Decoder::new(&decompressed_data)?; + let (id, decoder): (u64, _) = decoder.decode_field("request-id")?; + let (transaction_hashes, _): (Vec, _) = decoder.decode_field("transactionHashes")?; + + Ok(Self::new(id, transaction_hashes)) + } +} + +// https://github.com/ethereum/devp2p/blob/master/caps/eth.md#pooledtransactions-0x0a +pub(crate) struct PooledTransactions { + // id is a u64 chosen by the requesting peer, the responding peer must mirror the value for the response + // https://github.com/ethereum/devp2p/blob/master/caps/eth.md#protocol-messages + id: u64, + pooled_transactions: Vec, +} + +impl PooledTransactions { + pub fn new(id: u64, pooled_transactions: Vec) -> Self { + Self { + pooled_transactions, + id, + } + } +} + +impl RLPxMessage for PooledTransactions { + fn encode(&self, buf: &mut dyn BufMut) -> Result<(), RLPEncodeError> { + let mut encoded_data = vec![]; + Encoder::new(&mut encoded_data) + .encode_field(&self.id) + .encode_field(&self.pooled_transactions) + .finish(); + let msg_data = snappy_encode(encoded_data)?; + buf.put_slice(&msg_data); + Ok(()) + } + + fn decode(msg_data: &[u8]) -> Result { + let mut snappy_decoder = SnappyDecoder::new(); + let decompressed_data = snappy_decoder + .decompress_vec(msg_data) + .map_err(|e| RLPDecodeError::Custom(e.to_string()))?; + let decoder = Decoder::new(&decompressed_data)?; + let (id, decoder): (u64, _) = decoder.decode_field("request-id")?; + let (pooled_transactions, _): (Vec, _) = + decoder.decode_field("pooledTransactions")?; + + Ok(Self::new(id, pooled_transactions)) + } +} + +#[cfg(test)] +mod tests { + use ethereum_rust_core::{types::Transaction, H256}; + + use crate::rlpx::{ + eth::transactions::{GetPooledTransactions, PooledTransactions}, + message::RLPxMessage, + }; + + #[test] + fn get_pooled_transactions_empty_message() { + let transaction_hashes = vec![]; + let get_pooled_transactions = GetPooledTransactions::new(1, transaction_hashes.clone()); + + let mut buf = Vec::new(); + get_pooled_transactions.encode(&mut buf).unwrap(); + + let decoded = GetPooledTransactions::decode(&buf).unwrap(); + assert_eq!(decoded.id, 1); + assert_eq!(decoded.transaction_hashes, transaction_hashes); + } + + #[test] + fn get_pooled_transactions_not_empty_message() { + let transaction_hashes = vec![ + H256::from_low_u64_be(1), + H256::from_low_u64_be(2), + H256::from_low_u64_be(3), + ]; + let get_pooled_transactions = GetPooledTransactions::new(1, transaction_hashes.clone()); + + let mut buf = Vec::new(); + get_pooled_transactions.encode(&mut buf).unwrap(); + + let decoded = GetPooledTransactions::decode(&buf).unwrap(); + assert_eq!(decoded.id, 1); + assert_eq!(decoded.transaction_hashes, transaction_hashes); + } + + #[test] + fn pooled_transactions_of_one_type() { + let transaction1 = Transaction::LegacyTransaction(Default::default()); + let pooled_transactions = vec![transaction1.clone()]; + let pooled_transactions = PooledTransactions::new(1, pooled_transactions); + + let mut buf = Vec::new(); + pooled_transactions.encode(&mut buf).unwrap(); + let decoded = PooledTransactions::decode(&buf).unwrap(); + assert_eq!(decoded.id, 1); + assert_eq!(decoded.pooled_transactions, vec![transaction1]); + } +} diff --git a/crates/storage/trie/node_hash.rs b/crates/storage/trie/node_hash.rs index efb5e52de..868b3479b 100644 --- a/crates/storage/trie/node_hash.rs +++ b/crates/storage/trie/node_hash.rs @@ -189,7 +189,7 @@ impl NodeEncoder { l => { let l_len = compute_byte_usage(l); self.write_raw(&[long_base + l_len as u8]); - self.write_raw(&l.to_be_bytes()[size_of::() - l_len..]); + self.write_raw(&l.to_be_bytes()[core::mem::size_of::() - l_len..]); } } } diff --git a/crates/vm/levm/Makefile b/crates/vm/levm/Makefile index d2e12627a..aeefc232d 100644 --- a/crates/vm/levm/Makefile +++ b/crates/vm/levm/Makefile @@ -9,7 +9,7 @@ test: ## ๐Ÿงช Runs all tests except Ethereum tests cargo test -p ethereum_rust-levm lint: ## ๐Ÿงน Linter check - cargo clippy --all-targets --all-features --workspace -- -D warnings + cargo clippy --all-targets --all-features -- -D warnings fmt: ## ๐Ÿ“„ Runs rustfmt cargo fmt --all diff --git a/crates/vm/levm/src/constants.rs b/crates/vm/levm/src/constants.rs index 9fd2c2ea6..c2f8ae1b7 100644 --- a/crates/vm/levm/src/constants.rs +++ b/crates/vm/levm/src/constants.rs @@ -111,11 +111,6 @@ pub const MEMORY_EXPANSION_QUOTIENT: usize = 512; // Transaction costs in gas (in wei) pub const TX_BASE_COST: U256 = U256([21000, 0, 0, 0]); -pub const TX_DATA_COST_PER_NON_ZERO: u64 = 16; -pub const TX_DATA_COST_PER_ZERO: u64 = 4; -pub const TX_CREATE_COST: u64 = 32000; -pub const TX_ACCESS_LIST_ADDRESS_COST: u64 = 2400; -pub const TX_ACCESS_LIST_STORAGE_KEY_COST: u64 = 1900; pub const MAX_CODE_SIZE: usize = 0x6000; pub const MAX_CREATE_CODE_SIZE: usize = 2 * MAX_CODE_SIZE; diff --git a/crates/vm/levm/src/errors.rs b/crates/vm/levm/src/errors.rs index 3cf66e9fc..5f0829aa6 100644 --- a/crates/vm/levm/src/errors.rs +++ b/crates/vm/levm/src/errors.rs @@ -12,6 +12,10 @@ pub enum VMError { OverflowInArithmeticOp, FatalError, InvalidTransaction, + SenderAccountDoesNotExist, + SenderAccountShouldNotHaveBytecode, + SenderBalanceShouldContainTransferValue, + GasPriceIsLowerThanBaseFee, } pub enum OpcodeSuccess { diff --git a/crates/vm/levm/src/vm.rs b/crates/vm/levm/src/vm.rs index 3d5cd378a..e44726e6f 100644 --- a/crates/vm/levm/src/vm.rs +++ b/crates/vm/levm/src/vm.rs @@ -384,10 +384,47 @@ impl VM { } } + /// Based on Ethereum yellow paper's initial tests of intrinsic validity (Section 6). The last version is + /// Shanghai, so there are probably missing Cancun validations. The intrinsic validations are: + /// + /// (1) The transaction is well-formed RLP, with no additional trailing bytes; + /// (2) The transaction signature is valid; + /// (3) The transaction nonce is valid (equivalent to the sender account's + /// current nonce); + /// (4) The sender account has no contract code deployed (see EIP-3607). + /// (5) The gas limit is no smaller than the intrinsic gas, used by the + /// transaction; + /// (6) The sender account balance contains at least the cost, required in + /// up-front payment; + /// (7) The max fee per gas, in the case of type 2 transactions, or gasPrice, + /// in the case of type 0 and type 1 transactions, is greater than or equal to + /// the blockโ€™s base fee; + /// (8) For type 2 transactions, max priority fee per fas, must be no larger + /// than max fee per fas. + fn validate_transaction(&self) -> Result<(), VMError> { + // Validations (1), (2), (3), (5), and (8) are assumed done in upper layers. + let sender_account = match self.db.accounts.get(&self.env.origin) { + Some(acc) => acc, + None => return Err(VMError::SenderAccountDoesNotExist), + }; + // (4) + if sender_account.has_code() { + return Err(VMError::SenderAccountShouldNotHaveBytecode); + } + // (6) + if sender_account.balance < self.call_frames[0].msg_value { + return Err(VMError::SenderBalanceShouldContainTransferValue); + } + // (7) + if self.env.gas_price < self.env.base_fee_per_gas { + return Err(VMError::GasPriceIsLowerThanBaseFee); + } + Ok(()) + } + pub fn transact(&mut self) -> Result { - // let account = self.db.accounts.get(&self.env.origin).unwrap(); + self.validate_transaction()?; - // TODO: Add transaction validation. let initial_gas = Default::default(); self.env.consumed_gas = initial_gas; diff --git a/test_data/network_params.yaml b/test_data/network_params.yaml index 91780d685..0799964ff 100644 --- a/test_data/network_params.yaml +++ b/test_data/network_params.yaml @@ -9,6 +9,9 @@ participants: additional_services: - assertoor - dora + - el_forkmon + - tx_spammer + - blob_spammer assertoor_params: run_stability_check: true