diff --git a/CHANGELOG.md b/CHANGELOG.md index fefc028..e6d6116 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- `ev-dev` binary (`bin/ev-dev`): one-command local development chain with pre-funded Hardhat accounts, similar to Anvil or Hardhat Node - Transaction sponsor service (`bin/sponsor-service`) for signing EvNode transactions on behalf of users via JSON-RPC proxy ([#141](https://github.com/evstack/ev-reth/pull/141)) ### Changed @@ -17,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed +- Payload builder now pulls pending transactions from the txpool in `--dev` mode, fixing `cast send` and other RPC-submitted transactions not being included in blocks - Txpool now uses sponsor balance for pending/queued ordering in sponsored EvNode transactions, and validates executor balance separately for call value transfers ([#141](https://github.com/evstack/ev-reth/pull/141)) ## [0.3.0] - 2026-02-23 diff --git a/Cargo.lock b/Cargo.lock index 6a9f8eb..bd47e8f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2897,6 +2897,24 @@ dependencies = [ "tracing", ] +[[package]] +name = "ev-dev" +version = "0.1.0" +dependencies = [ + "alloy-primitives", + "alloy-signer-local", + "clap", + "ev-node", + "evolve-ev-reth", + "eyre", + "reth-cli-util", + "reth-ethereum-cli", + "serde_json", + "tempfile", + "tokio", + "tracing", +] + [[package]] name = "ev-node" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index ea113a7..8a332ed 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,7 @@ [workspace] resolver = "2" members = [ + "bin/ev-dev", "bin/ev-reth", "crates/common", "crates/ev-primitives", diff --git a/README.md b/README.md index 5f1af9d..d39ea6f 100644 --- a/README.md +++ b/README.md @@ -482,10 +482,17 @@ All standard Reth configuration options are supported. Key options for Evolve in ``` ev-reth/ ├── bin/ -│ └── ev-reth/ # Main binary +│ ├── ev-reth/ # Main binary +│ │ ├── Cargo.toml +│ │ └── src/ +│ │ └── main.rs # Binary with Engine API integration +│ └── ev-dev/ # Local dev chain (like Hardhat Node / Anvil) │ ├── Cargo.toml +│ ├── README.md # Usage guide with tool integrations +│ ├── assets/ +│ │ └── devnet-genesis.json │ └── src/ -│ └── main.rs # Binary with Engine API integration +│ └── main.rs ├── crates/ │ ├── common/ # Shared utilities and constants │ │ ├── Cargo.toml diff --git a/bin/ev-dev/Cargo.toml b/bin/ev-dev/Cargo.toml new file mode 100644 index 0000000..99a72d1 --- /dev/null +++ b/bin/ev-dev/Cargo.toml @@ -0,0 +1,48 @@ +[package] +name = "ev-dev" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true +description = "One-command local dev chain for ev-reth" + +[[bin]] +name = "ev-dev" +path = "src/main.rs" + +[dependencies] +# Core evolve crates +ev-node = { path = "../../crates/node" } +evolve-ev-reth = { path = "../../crates/evolve" } + +# Reth CLI and core dependencies +reth-cli-util.workspace = true +reth-ethereum-cli.workspace = true + +# Alloy dependencies +alloy-signer-local.workspace = true +alloy-primitives.workspace = true + +# Core dependencies +eyre.workspace = true +tracing.workspace = true +tokio = { workspace = true, features = ["full"] } +clap = { workspace = true, features = ["derive", "env"] } +tempfile.workspace = true +serde_json.workspace = true + +[lints] +workspace = true + +[features] +default = ["jemalloc"] + +jemalloc = ["reth-cli-util/jemalloc", "reth-ethereum-cli/jemalloc"] +jemalloc-prof = ["reth-cli-util/jemalloc-prof"] +tracy-allocator = ["reth-cli-util/tracy-allocator"] + +asm-keccak = ["reth-ethereum-cli/asm-keccak"] + +dev = ["reth-ethereum-cli/dev"] diff --git a/bin/ev-dev/README.md b/bin/ev-dev/README.md new file mode 100644 index 0000000..39a615f --- /dev/null +++ b/bin/ev-dev/README.md @@ -0,0 +1,212 @@ +# ev-dev + +One-command local development chain for Evolve. Think of it as the Evolve equivalent of [Hardhat Node](https://hardhat.org/hardhat-network/docs/overview) or [Anvil](https://book.getfoundry.sh/reference/anvil/). + +## Quick Start + +```bash +# Build and run +just dev-chain + +# Or build separately +just build-ev-dev +./target/release/ev-dev +``` + +The chain starts immediately with 10 pre-funded accounts, each holding 1,000,000 ETH. + +## CLI Options + +``` +ev-dev [OPTIONS] +``` + +| Flag | Default | Description | +|------|---------|-------------| +| `--host` | `127.0.0.1` | Host to bind HTTP/WS RPC server | +| `--port` | `8545` | Port for HTTP/WS RPC server | +| `--block-time` | `1` | Block time in seconds (`0` = mine on transaction) | +| `--silent` | `false` | Suppress the startup banner | +| `--accounts` | `10` | Number of accounts to display (1-20) | + +### Examples + +```bash +# Mine blocks only when transactions arrive +ev-dev --block-time 0 + +# Listen on all interfaces (useful inside Docker/VMs) +ev-dev --host 0.0.0.0 + +# Custom port, faster blocks +ev-dev --port 9545 --block-time 2 +``` + +## Chain Details + +| Property | Value | +|----------|-------| +| Chain ID | `1234` | +| Gas limit | 30,000,000 | +| Base fee | 1 Gwei | +| Contract size limit | 128 KB | +| Hardforks | All enabled at genesis (through Cancun) | + +## Pre-funded Accounts + +Accounts are derived from the standard Hardhat mnemonic: + +``` +test test test test test test test test test test test junk +``` + +Derivation path: `m/44'/60'/0'/0/{index}` + +| # | Address | Private Key | +|---|---------|-------------| +| 0 | `0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266` | `0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80` | +| 1 | `0x70997970C51812dc3A010C7d01b50e0d17dc79C8` | `0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d` | +| 2 | `0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC` | `0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a` | +| 3 | `0x90F79bf6EB2c4f870365E785982E1f101E93b906` | `0x7c852118294e51e653712a81e05800f419141751be58f605c371e15141b007a6` | +| 4 | `0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65` | `0x47e179ec197488593b187f80a00eb0da91f1b9d0b13f8733639f19c30a34926a` | +| 5 | `0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc` | `0x8b3a350cf5c34c9194ca85829a2df0ec3153be0318b5e2d3348e872092edffba` | +| 6 | `0x976EA74026E726554dB657fA54763abd0C3a0aa9` | `0x92db14e403b83dfe3df233f83dfa3a0d7096f21ca9b0d6d6b8d88b2b4ec1564e` | +| 7 | `0x14dC79964da2C08dba798bBb5d93A585CAa97F90` | `0x4bbbf85ce3377467afe5d46f804f221813b2bb87f24d81f60f1fcdbf7cbf4356` | +| 8 | `0x23618e81E3f5cdF7f54C3d65f7FBc0aBf5B21E8f` | `0xdbda1821b80551c9d65939329250298aa3472ba22feea921c0cf5d620ea67b97` | +| 9 | `0xa0Ee7A142d267C1f36714E4a8F75612F20a79720` | `0x2a871d0798f97d79848a013d4936a73bf4cc922c825d33c1cf7073dff6d409c6` | + +> **WARNING**: These accounts and their private keys are publicly known. Any funds sent to them on a real network **will be lost**. + +## Using with Common Tools + +### Foundry (cast / forge) + +```bash +# Check balance +cast balance 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 --rpc-url http://127.0.0.1:8545 + +# Send ETH +cast send 0x70997970C51812dc3A010C7d01b50e0d17dc79C8 \ + --value 1ether \ + --private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \ + --rpc-url http://127.0.0.1:8545 + +# Deploy a contract +forge create src/MyContract.sol:MyContract \ + --rpc-url http://127.0.0.1:8545 \ + --private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 + +# Run Forge tests against ev-dev +forge test --fork-url http://127.0.0.1:8545 +``` + +### Hardhat + +In `hardhat.config.js`: + +```js +module.exports = { + networks: { + evdev: { + url: "http://127.0.0.1:8545", + chainId: 1234, + accounts: [ + "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", + "0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d", + ], + }, + }, +}; +``` + +```bash +npx hardhat run scripts/deploy.js --network evdev +``` + +### ethers.js / viem + +```js +// ethers.js v6 +import { JsonRpcProvider, Wallet } from "ethers"; + +const provider = new JsonRpcProvider("http://127.0.0.1:8545"); +const wallet = new Wallet( + "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", + provider +); +``` + +```js +// viem +import { createWalletClient, http } from "viem"; +import { privateKeyToAccount } from "viem/accounts"; +import { defineChain } from "viem"; + +const evdev = defineChain({ + id: 1234, + name: "ev-dev", + nativeCurrency: { name: "Ether", symbol: "ETH", decimals: 18 }, + rpcUrls: { + default: { http: ["http://127.0.0.1:8545"] }, + }, +}); + +const account = privateKeyToAccount( + "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" +); +const client = createWalletClient({ account, chain: evdev, transport: http() }); +``` + +### curl (raw JSON-RPC) + +```bash +# Get block number +curl -s http://127.0.0.1:8545 \ + -H "Content-Type: application/json" \ + -d '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}' + +# Get chain ID +curl -s http://127.0.0.1:8545 \ + -H "Content-Type: application/json" \ + -d '{"jsonrpc":"2.0","method":"eth_chainId","params":[],"id":1}' + +# Get pending transactions from txpool +curl -s http://127.0.0.1:8545 \ + -H "Content-Type: application/json" \ + -d '{"jsonrpc":"2.0","method":"txpoolExt_getTxs","params":[],"id":1}' +``` + +## Available RPC Namespaces + +The following namespaces are enabled by default over both HTTP and WebSocket: + +- `eth` — standard Ethereum JSON-RPC +- `net` — network info +- `web3` — client version, SHA3 +- `txpool` — transaction pool inspection +- `debug` — debug namespace (traceCall, traceTransaction, etc.) +- `trace` — OpenEthereum-compatible trace namespace + +Additionally, the custom `txpoolExt` namespace is available: + +- `txpoolExt_getTxs` — returns pending transactions as RLP-encoded bytes + +## Evolve-specific Features + +ev-dev includes all Evolve customizations out of the box: + +- **Base fee redirect**: Base fees are sent to `0x00...00fe` instead of being burned +- **128 KB contract size limit**: Deploy contracts up to 128 KB (vs Ethereum's 24 KB) +- **Mint precompile**: Native minting precompile is active, admin is account `0` (`0xf39F...2266`) +- **EvNode transactions (type 0x76)**: Batch calls and sponsored transactions are supported + +## How It Works + +ev-dev is a thin wrapper around the full `ev-reth` node. On startup it: + +1. Writes the embedded devnet genesis to a temp file +2. Creates a temporary data directory (clean state every run) +3. Launches `ev-reth` in `--dev` mode with networking disabled +4. Exposes HTTP and WebSocket RPC on the configured host/port + +Each run starts from a fresh genesis — there is no persistent state between restarts. diff --git a/bin/ev-dev/assets/devnet-genesis.json b/bin/ev-dev/assets/devnet-genesis.json new file mode 100644 index 0000000..967352d --- /dev/null +++ b/bin/ev-dev/assets/devnet-genesis.json @@ -0,0 +1,115 @@ +{ + "config": { + "chainId": 1234, + "homesteadBlock": 0, + "daoForkSupport": true, + "eip150Block": 0, + "eip155Block": 0, + "eip158Block": 0, + "byzantiumBlock": 0, + "constantinopleBlock": 0, + "petersburgBlock": 0, + "istanbulBlock": 0, + "muirGlacierBlock": 0, + "berlinBlock": 0, + "londonBlock": 0, + "arrowGlacierBlock": 0, + "grayGlacierBlock": 0, + "shanghaiTime": 0, + "cancunTime": 0, + "terminalTotalDifficulty": "0x0", + "terminalTotalDifficultyPassed": true, + "evolve": { + "baseFeeSink": "0x00000000000000000000000000000000000000fe", + "baseFeeRedirectActivationHeight": 0, + "baseFeeMaxChangeDenominator": 5000, + "baseFeeElasticityMultiplier": 10, + "initialBaseFeePerGas": 1000000000, + "mintAdmin": "0x000000000000000000000000000000000000Ad00", + "mintPrecompileActivationHeight": 0, + "contractSizeLimit": 131072, + "contractSizeLimitActivationHeight": 0 + } + }, + "nonce": "0x0", + "timestamp": "0x0", + "extraData": "0x00", + "gasLimit": "0x1c9c380", + "difficulty": "0x0", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "coinbase": "0x0000000000000000000000000000000000000000", + "baseFeePerGas": "0x3b9aca00", + "alloc": { + "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266": { + "balance": "0xd3c21bcecceda1000000" + }, + "0x70997970c51812dc3a010c7d01b50e0d17dc79c8": { + "balance": "0xd3c21bcecceda1000000" + }, + "0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc": { + "balance": "0xd3c21bcecceda1000000" + }, + "0x90f79bf6eb2c4f870365e785982e1f101e93b906": { + "balance": "0xd3c21bcecceda1000000" + }, + "0x15d34aaf54267db7d7c367839aaf71a00a2c6a65": { + "balance": "0xd3c21bcecceda1000000" + }, + "0x9965507d1a55bcc2695c58ba16fb37d819b0a4dc": { + "balance": "0xd3c21bcecceda1000000" + }, + "0x976ea74026e726554db657fa54763abd0c3a0aa9": { + "balance": "0xd3c21bcecceda1000000" + }, + "0x14dc79964da2c08b23698b3d3cc7ca32193d9955": { + "balance": "0xd3c21bcecceda1000000" + }, + "0x23618e81e3f5cdf7f54c3d65f7fbc0abf5b21e8f": { + "balance": "0xd3c21bcecceda1000000" + }, + "0xa0ee7a142d267c1f36714e4a8f75612f20a79720": { + "balance": "0xd3c21bcecceda1000000" + }, + "0xbcd4042de499d14e55001ccbb24a551f3b954096": { + "balance": "0xd3c21bcecceda1000000" + }, + "0x71be63f3384f5fb98995898a86b02fb2426c5788": { + "balance": "0xd3c21bcecceda1000000" + }, + "0xfabb0ac9d68b0b445fb7357272ff202c5651694a": { + "balance": "0xd3c21bcecceda1000000" + }, + "0x1cbd3b2770909d4e10f157cabc84c7264073c9ec": { + "balance": "0xd3c21bcecceda1000000" + }, + "0xdf3e18d64bc6a983f673ab319ccae4f1a57c7097": { + "balance": "0xd3c21bcecceda1000000" + }, + "0xcd3b766ccdd6ae721141f452c550ca635964ce71": { + "balance": "0xd3c21bcecceda1000000" + }, + "0x2546bcd3c84621e976d8185a91a922ae77ecec30": { + "balance": "0xd3c21bcecceda1000000" + }, + "0xbda5747bfd65f08deb54cb465eb87d40e51b197e": { + "balance": "0xd3c21bcecceda1000000" + }, + "0xdd2fd4581271e230360230f9337d5c0430bf44c0": { + "balance": "0xd3c21bcecceda1000000" + }, + "0x8626f6940e2eb28930efb4cef49b2d1f2c9c1199": { + "balance": "0xd3c21bcecceda1000000" + }, + "0x000000000000000000000000000000000000Ad00": { + "balance": "0x0", + "code": "0x60806040526004361061007e575f3560e01c80638da5cb5b1161004d5780638da5cb5b1461012d578063e30c397814610157578063f2fde38b14610181578063fa4bb79d146101a957610085565b806318dfb3c7146100895780631cff79cd146100c557806379ba5097146101015780638b5298541461011757610085565b3661008557005b5f5ffd5b348015610094575f5ffd5b506100af60048036038101906100aa9190610cf8565b6101e5565b6040516100bc9190610ea1565b60405180910390f35b3480156100d0575f5ffd5b506100eb60048036038101906100e69190610f70565b6104d9565b6040516100f89190611015565b60405180910390f35b34801561010c575f5ffd5b5061011561066c565b005b348015610122575f5ffd5b5061012b6107ed565b005b348015610138575f5ffd5b506101416108b4565b60405161014e9190611044565b60405180910390f35b348015610162575f5ffd5b5061016b6108d8565b6040516101789190611044565b60405180910390f35b34801561018c575f5ffd5b506101a760048036038101906101a2919061105d565b6108fd565b005b3480156101b4575f5ffd5b506101cf60048036038101906101ca91906110bb565b610aa4565b6040516101dc9190611015565b60405180910390f35b60605f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161461026c576040517f30cd747100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8282905085859050146102ab576040517fff633a3800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8484905067ffffffffffffffff8111156102c8576102c761112c565b5b6040519080825280602002602001820160405280156102fb57816020015b60608152602001906001900390816102e65790505b5090505f5f90505b858590508110156104d0575f5f87878481811061032357610322611159565b5b9050602002016020810190610338919061105d565b73ffffffffffffffffffffffffffffffffffffffff1686868581811061036157610360611159565b5b90506020028101906103739190611192565b604051610381929190611230565b5f604051808303815f865af19150503d805f81146103ba576040519150601f19603f3d011682016040523d82523d5f602084013e6103bf565b606091505b50915091508161040657806040517fa5fa8d2b0000000000000000000000000000000000000000000000000000000081526004016103fd9190611015565b60405180910390fd5b87878481811061041957610418611159565b5b905060200201602081019061042e919061105d565b73ffffffffffffffffffffffffffffffffffffffff167fc96720f35dd524e76ea92971ce13d08e9a17816bf3b0008a7083e6032354ebb587878681811061047857610477611159565b5b905060200281019061048a9190611192565b8460405161049a93929190611274565b60405180910390a2808484815181106104b6576104b5611159565b5b602002602001018190525050508080600101915050610303565b50949350505050565b60605f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610560576040517f30cd747100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f5f8573ffffffffffffffffffffffffffffffffffffffff168585604051610589929190611230565b5f604051808303815f865af19150503d805f81146105c2576040519150601f19603f3d011682016040523d82523d5f602084013e6105c7565b606091505b50915091508161060e57806040517fa5fa8d2b0000000000000000000000000000000000000000000000000000000081526004016106059190611015565b60405180910390fd5b8573ffffffffffffffffffffffffffffffffffffffff167fc96720f35dd524e76ea92971ce13d08e9a17816bf3b0008a7083e6032354ebb586868460405161065893929190611274565b60405180910390a280925050509392505050565b60015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146106f2576040517f1853971c00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b3373ffffffffffffffffffffffffffffffffffffffff165f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e060405160405180910390a3335f5f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505f60015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550565b5f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610872576040517f30cd747100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f60015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550565b5f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b5f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610982576040517f30cd747100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16036109e7576040517fd92e233d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8060015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508073ffffffffffffffffffffffffffffffffffffffff165f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e2270060405160405180910390a350565b60605f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610b2b576040517f30cd747100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f5f8673ffffffffffffffffffffffffffffffffffffffff16848787604051610b55929190611230565b5f6040518083038185875af1925050503d805f8114610b8f576040519150601f19603f3d011682016040523d82523d5f602084013e610b94565b606091505b509150915081610bdb57806040517fa5fa8d2b000000000000000000000000000000000000000000000000000000008152600401610bd29190611015565b60405180910390fd5b8673ffffffffffffffffffffffffffffffffffffffff167fc96720f35dd524e76ea92971ce13d08e9a17816bf3b0008a7083e6032354ebb5878784604051610c2593929190611274565b60405180910390a28092505050949350505050565b5f5ffd5b5f5ffd5b5f5ffd5b5f5ffd5b5f5ffd5b5f5f83601f840112610c6357610c62610c42565b5b8235905067ffffffffffffffff811115610c8057610c7f610c46565b5b602083019150836020820283011115610c9c57610c9b610c4a565b5b9250929050565b5f5f83601f840112610cb857610cb7610c42565b5b8235905067ffffffffffffffff811115610cd557610cd4610c46565b5b602083019150836020820283011115610cf157610cf0610c4a565b5b9250929050565b5f5f5f5f60408587031215610d1057610d0f610c3a565b5b5f85013567ffffffffffffffff811115610d2d57610d2c610c3e565b5b610d3987828801610c4e565b9450945050602085013567ffffffffffffffff811115610d5c57610d5b610c3e565b5b610d6887828801610ca3565b925092505092959194509250565b5f81519050919050565b5f82825260208201905092915050565b5f819050602082019050919050565b5f81519050919050565b5f82825260208201905092915050565b8281835e5f83830152505050565b5f601f19601f8301169050919050565b5f610de182610d9f565b610deb8185610da9565b9350610dfb818560208601610db9565b610e0481610dc7565b840191505092915050565b5f610e1a8383610dd7565b905092915050565b5f602082019050919050565b5f610e3882610d76565b610e428185610d80565b935083602082028501610e5485610d90565b805f5b85811015610e8f5784840389528151610e708582610e0f565b9450610e7b83610e22565b925060208a01995050600181019050610e57565b50829750879550505050505092915050565b5f6020820190508181035f830152610eb98184610e2e565b905092915050565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f610eea82610ec1565b9050919050565b610efa81610ee0565b8114610f04575f5ffd5b50565b5f81359050610f1581610ef1565b92915050565b5f5f83601f840112610f3057610f2f610c42565b5b8235905067ffffffffffffffff811115610f4d57610f4c610c46565b5b602083019150836001820283011115610f6957610f68610c4a565b5b9250929050565b5f5f5f60408486031215610f8757610f86610c3a565b5b5f610f9486828701610f07565b935050602084013567ffffffffffffffff811115610fb557610fb4610c3e565b5b610fc186828701610f1b565b92509250509250925092565b5f82825260208201905092915050565b5f610fe782610d9f565b610ff18185610fcd565b9350611001818560208601610db9565b61100a81610dc7565b840191505092915050565b5f6020820190508181035f83015261102d8184610fdd565b905092915050565b61103e81610ee0565b82525050565b5f6020820190506110575f830184611035565b92915050565b5f6020828403121561107257611071610c3a565b5b5f61107f84828501610f07565b91505092915050565b5f819050919050565b61109a81611088565b81146110a4575f5ffd5b50565b5f813590506110b581611091565b92915050565b5f5f5f5f606085870312156110d3576110d2610c3a565b5b5f6110e087828801610f07565b945050602085013567ffffffffffffffff81111561110157611100610c3e565b5b61110d87828801610f1b565b93509350506040611120878288016110a7565b91505092959194509250565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b5f5ffd5b5f5ffd5b5f5ffd5b5f5f833560016020038436030381126111ae576111ad611186565b5b80840192508235915067ffffffffffffffff8211156111d0576111cf61118a565b5b6020830192506001820236038313156111ec576111eb61118e565b5b509250929050565b5f81905092915050565b828183375f83830152505050565b5f61121783856111f4565b93506112248385846111fe565b82840190509392505050565b5f61123c82848661120c565b91508190509392505050565b5f6112538385610fcd565b93506112608385846111fe565b61126983610dc7565b840190509392505050565b5f6040820190508181035f83015261128d818587611248565b905081810360208301526112a18184610fdd565b905094935050505056fea26469706673582212201029704c8e76cc8133cedd39a8adbebfe979b8809644c7f5e9cff417e23119d464736f6c634300081e0033", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000000": "0x000000000000000000000000f39Fd6e51aad88F6F4ce6aB8827279cffFb92266" + } + }, + "0x0101010101010101010101010101010101010101": { + "balance": "0x1" + } + }, + "number": "0x0" +} diff --git a/bin/ev-dev/src/main.rs b/bin/ev-dev/src/main.rs new file mode 100644 index 0000000..7ed4e65 --- /dev/null +++ b/bin/ev-dev/src/main.rs @@ -0,0 +1,224 @@ +//! ev-dev: one-command local dev chain for ev-reth. +//! +//! Spins up a fully functional Evolve chain with funded accounts, +//! similar to Hardhat Node or Anvil. + +#![allow(missing_docs, rustdoc::missing_crate_level_docs)] + +use alloy_signer_local::{coins_bip39::English, MnemonicBuilder}; +use clap::Parser; +use evolve_ev_reth::{ + config::EvolveConfig, + rpc::txpool::{EvolveTxpoolApiImpl, EvolveTxpoolApiServer}, +}; +use reth_ethereum_cli::Cli; +use std::io::Write; +use tracing::info; + +use ev_node::{EvolveArgs, EvolveChainSpecParser, EvolveNode}; + +#[global_allocator] +static ALLOC: reth_cli_util::allocator::Allocator = reth_cli_util::allocator::new_allocator(); + +const DEVNET_GENESIS: &str = include_str!("../assets/devnet-genesis.json"); +const HARDHAT_MNEMONIC: &str = "test test test test test test test test test test test junk"; + +fn parse_accounts(s: &str) -> Result { + let n: usize = s.parse().map_err(|e| format!("{e}"))?; + if (1..=20).contains(&n) { + Ok(n) + } else { + Err(format!("{n} is not in 1..=20")) + } +} + +/// Local dev chain for ev-reth with pre-funded accounts. +#[derive(Parser, Debug)] +#[command(name = "ev-dev", about = "One-command local Evolve dev chain")] +struct EvDevArgs { + /// Host to bind HTTP/WS RPC server + #[arg(long, default_value = "127.0.0.1")] + host: String, + + /// Port for HTTP/WS RPC server + #[arg(long, default_value_t = 8545)] + port: u16, + + /// Block time in seconds (0 = mine on tx) + #[arg(long, default_value_t = 1)] + block_time: u64, + + /// Suppress the startup banner + #[arg(long, default_value_t = false)] + silent: bool, + + /// Number of accounts to display (1..=20) + #[arg(long, default_value_t = 10, value_parser = parse_accounts)] + accounts: usize, +} + +fn derive_keys(count: usize) -> Vec<(String, String)> { + (0..count) + .map(|i| { + let signer = MnemonicBuilder::::default() + .phrase(HARDHAT_MNEMONIC) + .index(i as u32) + .expect("valid derivation index") + .build() + .expect("valid key derivation"); + let address = signer.address(); + let key_bytes = signer.credential().to_bytes(); + ( + format!("{address}"), + format!("0x{}", alloy_primitives::hex::encode(key_bytes)), + ) + }) + .collect() +} + +fn chain_id_from_genesis() -> u64 { + let genesis: serde_json::Value = + serde_json::from_str(DEVNET_GENESIS).expect("valid genesis JSON"); + genesis["config"]["chainId"] + .as_u64() + .expect("genesis must have config.chainId") +} + +fn print_banner(args: &EvDevArgs) { + let accounts = derive_keys(args.accounts); + + println!(); + println!(r" _ "); + println!(r" | | "); + println!(r" _____ _____ __| | _____ __"); + println!(r" / _ \ \ / /___/ / _` |/ _ \ \ / /"); + println!(r" | __/\ V / | (_| | __/\ V / "); + println!(r" \___| \_/ \__,_|\___| \_/ "); + println!(); + println!(" Evolve Local Development Chain"); + println!(" =============================="); + println!(); + println!("Chain ID: {}", chain_id_from_genesis()); + println!("RPC URL: http://{}:{}", args.host, args.port); + println!( + "Block time: {}", + if args.block_time == 0 { + "auto (mine on tx)".to_string() + } else { + format!("{}s", args.block_time) + } + ); + println!(); + println!("Available Accounts"); + println!("=================="); + for (i, (addr, _)) in accounts.iter().enumerate() { + println!("({i}) {addr} (1000000 ETH)"); + } + println!(); + println!("Private Keys"); + println!("=================="); + for (i, (_, key)) in accounts.iter().enumerate() { + println!("({i}) {key}"); + } + println!(); + println!("Mnemonic: {HARDHAT_MNEMONIC}"); + println!("Derivation path: m/44'/60'/0'/0/{{index}}"); + println!(); + println!("WARNING: These accounts and keys are publicly known."); + println!("Any funds sent to them on mainnet WILL BE LOST."); + println!(); +} + +fn main() { + reth_cli_util::sigsegv_handler::install(); + + if std::env::var_os("RUST_BACKTRACE").is_none() { + std::env::set_var("RUST_BACKTRACE", "1"); + } + + let dev_args = EvDevArgs::parse(); + + if !dev_args.silent { + print_banner(&dev_args); + } + + // Write genesis to a temp file that lives for the process duration + let mut genesis_file = + tempfile::NamedTempFile::new().expect("failed to create temp genesis file"); + genesis_file + .write_all(DEVNET_GENESIS.as_bytes()) + .expect("failed to write genesis"); + let genesis_path = genesis_file + .path() + .to_str() + .expect("valid path") + .to_string(); + + // Use a temp data directory so each run starts with clean state + let datadir = tempfile::TempDir::new().expect("failed to create temp data dir"); + let datadir_path = datadir.path().to_str().expect("valid path").to_string(); + + let mut args = vec![ + "ev-dev".to_string(), + "node".to_string(), + "--dev".to_string(), + "--chain".to_string(), + genesis_path, + "--datadir".to_string(), + datadir_path, + "--http".to_string(), + "--http.addr".to_string(), + dev_args.host.clone(), + "--http.port".to_string(), + dev_args.port.to_string(), + "--http.api".to_string(), + "eth,net,web3,txpool,debug,trace".to_string(), + "--http.corsdomain".to_string(), + "*".to_string(), + "--ws".to_string(), + "--ws.addr".to_string(), + dev_args.host.clone(), + "--ws.port".to_string(), + dev_args.port.to_string(), + "--ws.api".to_string(), + "eth,net,web3,txpool,debug,trace".to_string(), + "--disable-discovery".to_string(), + "--no-persist-peers".to_string(), + "--port".to_string(), + "0".to_string(), + ]; + + if dev_args.block_time > 0 { + args.push("--dev.block-time".to_string()); + args.push(format!("{}s", dev_args.block_time)); + } + + let cli = match Cli::::try_parse_from(args) { + Ok(cli) => cli, + Err(err) => { + eprintln!("{err}"); + std::process::exit(2); + } + }; + + if let Err(err) = cli.run(|builder, _evolve_args| async move { + info!("=== EV-DEV: Starting local development chain ==="); + let handle = builder + .node(EvolveNode::new()) + .extend_rpc_modules(move |ctx| { + let evolve_cfg = EvolveConfig::default(); + let evolve_txpool = + EvolveTxpoolApiImpl::new(ctx.pool().clone(), evolve_cfg.max_txpool_bytes); + ctx.modules.merge_configured(evolve_txpool.into_rpc())?; + Ok(()) + }) + .launch_with_debug_capabilities() + .await?; + + info!("=== EV-DEV: Local chain running - RPC ready ==="); + handle.node_exit_future.await + }) { + eprintln!("Error: {err:?}"); + std::process::exit(1); + } +} diff --git a/crates/node/src/node.rs b/crates/node/src/node.rs index 4ee48ae..9f1e588 100644 --- a/crates/node/src/node.rs +++ b/crates/node/src/node.rs @@ -6,20 +6,23 @@ use alloy_rpc_types::engine::{ ExecutionPayloadV1, }; use ev_primitives::EvPrimitives; +use reth_engine_local::LocalPayloadAttributesBuilder; use reth_ethereum::{ chainspec::ChainSpec, node::{ - api::{EngineTypes, FullNodeTypes, NodeTypes, PayloadTypes}, + api::{EngineTypes, FullNodeComponents, FullNodeTypes, NodeTypes, PayloadTypes}, builder::{ components::{BasicPayloadServiceBuilder, ComponentsBuilder}, rpc::RpcAddOns, - Node, NodeAdapter, + DebugNode, Node, NodeAdapter, }, node::EthereumNetworkBuilder, }, }; +use reth_payload_primitives::PayloadAttributesBuilder; use reth_primitives_traits::SealedBlock; use serde::{Deserialize, Serialize}; +use std::sync::Arc; use tracing::info; use crate::{ @@ -119,6 +122,22 @@ where } } +impl> DebugNode for EvolveNode { + type RpcBlock = alloy_rpc_types::Block; + + fn rpc_to_primitive_block(rpc_block: Self::RpcBlock) -> ev_primitives::Block { + let eth_block: reth_ethereum_primitives::Block = + rpc_block.into_consensus().convert_transactions(); + eth_block.map_transactions(ev_primitives::EvTxEnvelope::Ethereum) + } + + fn local_payload_attributes_builder( + chain_spec: &Self::ChainSpec, + ) -> impl PayloadAttributesBuilder<::PayloadAttributes> { + LocalPayloadAttributesBuilder::new(Arc::new(chain_spec.clone())) + } +} + /// Helper logging to announce node startup with args. pub fn log_startup() { info!("=== EV-RETH: Evolve node mode enabled ==="); diff --git a/crates/node/src/payload_service.rs b/crates/node/src/payload_service.rs index e12075a..dc2e8de 100644 --- a/crates/node/src/payload_service.rs +++ b/crates/node/src/payload_service.rs @@ -55,12 +55,13 @@ impl Default for EvolvePayloadBuilderBuilder { /// The evolve engine payload builder that integrates with the evolve payload builder. #[derive(Debug, Clone)] -pub struct EvolveEnginePayloadBuilder +pub struct EvolveEnginePayloadBuilder where Client: Clone, { pub(crate) evolve_builder: Arc>, pub(crate) config: EvolvePayloadBuilderConfig, + pub(crate) pool: Pool, } impl PayloadBuilderBuilder for EvolvePayloadBuilderBuilder @@ -76,12 +77,12 @@ where + Unpin + 'static, { - type PayloadBuilder = EvolveEnginePayloadBuilder; + type PayloadBuilder = EvolveEnginePayloadBuilder; async fn build_payload_builder( self, ctx: &BuilderContext, - _pool: Pool, + pool: Pool, evm_config: EvolveEvmConfig, ) -> eyre::Result { let chain_spec = ctx.chain_spec(); @@ -114,11 +115,12 @@ where Ok(EvolveEnginePayloadBuilder { evolve_builder, config, + pool, }) } } -impl PayloadBuilder for EvolveEnginePayloadBuilder +impl PayloadBuilder for EvolveEnginePayloadBuilder where Client: reth_ethereum::provider::StateProviderFactory + ChainSpecProvider @@ -127,12 +129,15 @@ where + Send + Sync + 'static, + Pool: TransactionPool> + + Unpin + + 'static, { type Attributes = EvolveEnginePayloadBuilderAttributes; type BuiltPayload = EvBuiltPayload; #[instrument(skip(self, args), fields( - tx_count = args.config.attributes.transactions.len(), + tx_count = tracing::field::Empty, payload_id = %args.config.attributes.payload_id(), duration_ms = tracing::field::Empty, ))] @@ -174,8 +179,30 @@ where } } + // Use transactions from Engine API attributes if provided, otherwise pull from the pool + // (e.g. in --dev mode where LocalMiner sends empty attributes). + let transactions = if attributes.transactions.is_empty() { + let pool_txs: Vec = self + .pool + .pending_transactions() + .into_iter() + .map(|tx| tx.transaction.clone_into_consensus().into_inner()) + .collect(); + if !pool_txs.is_empty() { + info!( + pool_tx_count = pool_txs.len(), + "pulling transactions from pool" + ); + } + pool_txs + } else { + attributes.transactions.clone() + }; + + tracing::Span::current().record("tx_count", transactions.len()); + let evolve_attrs = EvolvePayloadAttributes::new( - attributes.transactions.clone(), + transactions, Some(effective_gas_limit), attributes.timestamp(), attributes.prev_randao(), @@ -288,6 +315,7 @@ mod tests { use super::*; use crate::{ config::EvolvePayloadBuilderConfig, executor::EvolveEvmConfig, test_utils::SpanCollector, + txpool::EvPooledTransaction, }; use alloy_primitives::B256; use alloy_rpc_types::engine::PayloadAttributes as RpcPayloadAttributes; @@ -297,6 +325,7 @@ mod tests { use reth_primitives_traits::SealedHeader; use reth_provider::test_utils::MockEthProvider; use reth_revm::{cached::CachedReads, cancelled::CancelOnDrop}; + use reth_transaction_pool::noop::NoopTransactionPool; #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn try_build_span_has_expected_fields() { @@ -348,6 +377,7 @@ mod tests { let engine_builder = EvolveEnginePayloadBuilder { evolve_builder, config, + pool: NoopTransactionPool::::new(), }; let rpc_attrs = RpcPayloadAttributes { diff --git a/justfile b/justfile index 9e23330..935c3a0 100644 --- a/justfile +++ b/justfile @@ -74,6 +74,14 @@ run: build-dev run-dev: build-dev RUST_LOG=debug ./{{target_dir}}/debug/{{binary}} node +# Build the ev-dev binary in release mode +build-ev-dev: + {{cargo}} build --release --bin ev-dev + +# Build and run the local dev chain +dev-chain: build-ev-dev + ./{{target_dir}}/release/ev-dev + # Format code using rustfmt (nightly) fmt: {{cargo}} +nightly fmt --all