Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,552 changes: 1,439 additions & 113 deletions Cargo.lock

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ members = [
"rpc",
"pallet-epico",
"primitives/*",
"receipt-service"
]
resolver = "2"

Expand All @@ -22,6 +23,7 @@ frame-benchmarking = { git = "https://github.com/paritytech/polkadot-sdk", tag =
futures = { version = "0.3.30" }
jsonrpsee = { version = "0.24.3" }
log = { version = "0.4.21", default-features = false }
tracing = { version = "0.1.41", default-features = false }
sc-network = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-2", default-features = false }
sc-network-sync = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-2", default-features = false }
sc-transaction-pool = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-2", default-features = false }
Expand All @@ -34,6 +36,7 @@ libsecp256k1 = { version = "0.7.1", default-features = false }
sc-client-api = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-2", default-features = false }
sp-consensus = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-2", default-features = false }
sc-offchain = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-2", default-features = false }
sc-service = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-2", default-features = false }
sp-api = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-2", default-features = false }
sp-blockchain = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-2", default-features = false }
sp-core = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-2", default-features = false }
Expand Down
24 changes: 14 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,22 +58,23 @@ epico/
│ ├── self-contained/ # Self-contained logic and primitives
│ ├── xcm/ # Cross-Consensus Messaging (XCM) primitives
├── rpc/ # Remote Procedure Call implementations
├── receipt-service/ # Offchain worker for managing receipts
```

---

## Advantages of Native Execution

1. **Improved Performance and Throughput**
1. **Improved Performance and Throughput**
By bypassing EVM emulation, Ethereum transactions are processed directly by the Substrate runtime, leading to faster and more efficient operations.

2. **Customizability**
2. **Customizability**
Substrate runtimes are known for their flexibility. With native execution, developers can tailor execution logic to their specific projects, something that’s not easily achievable in a strict EVM environment.

3. **Lower Gas Costs**
3. **Lower Gas Costs**
Native execution leverages Substrate’s optimized gas metering. This often results in cheaper fees compared to Ethereum’s traditional gas model.

4. **Enhanced Security**
4. **Enhanced Security**
By avoiding known EVM vulnerabilities (like certain reentrancy attacks or gas-limit exploits), Epico creates a more secure environment for transaction processing.

---
Expand All @@ -92,18 +93,21 @@ As a result, tools like **MetaMask**, **Hardhat**, and other Ethereum-compatible

## How Epico Works: Architecture Overview

Epicos compatibility layer is built on three main components:
Epico's compatibility layer is built on four main components:

1. **Epico RPC**
1. **Epico RPC**
This module, embedded within the client, exposes Ethereum JSON-RPC methods. It acts as a “mask,” presenting a Substrate node as a standard Ethereum node to external clients and tools.
![Substrate Node](./docs/substrate-node.png)

2. **Epico Pallet**
2. **Epico Pallet**
The core logic that validates, decodes, and executes Ethereum transactions within the Substrate environment. It handles storage of execution results so that transaction receipts and execution outcomes can be queried later.

3. **Epico Pallet Runtime APIs**
3. **Epico Pallet Runtime APIs**
These APIs provide an interface between the runtime and the Epico RPC client, retrieving the necessary data (balance, nonce, block info, etc.) for Ethereum-compatible RPC responses.

4. **Epico Receipt Service**
An offchain service that manages transaction receipts in persistent storage. It runs two workers: one stores receipts as "untrusted" on block import, another promotes them to "finalized" on finality events. Features automatic garbage collection that follows the node's state pruning configuration and gap-filling for missed events. Essential for Ethereum RPC compatibility.

In addition, Epico leverages a custom `UncheckedExtrinsic` and `CheckedExtrinsic` flow (inspired by Frontier) to handle validation, pre-dispatch, dispatch, and post-dispatch logic for transactions submitted through the Epico RPC.

---
Expand All @@ -112,10 +116,10 @@ In addition, Epico leverages a custom `UncheckedExtrinsic` and `CheckedExtrinsic

Because Ethereum addresses are **20 bytes** long, any Substrate runtime that leverages Epico must accommodate Ethereum-style accounts. There are two main ways to do this:

1. **Native Ethereum Accounts**
1. **Native Ethereum Accounts**
Adopt a **unified** 20-byte account model as your chain’s default (similar to Moonbeam). This simplifies interoperability because Ethereum and Substrate addresses are the same format.

2. **Account Conversion Mechanism**
2. **Account Conversion Mechanism**
Implement a way to **map** Substrate’s default 32-byte addresses to a 20-byte address. This keeps your node’s default account structure intact while still allowing Ethereum-based tools to connect and interact.

---
Expand Down
51 changes: 46 additions & 5 deletions docs/INTEGRATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ From your project setup, "Epico" consists of various components located under th
- [`ep-rpc`](./rpc)
- [`pallet-epico-runtime-api`](./pallet-epico/runtime-api)
- [`pallet-epico`](./pallet-epico)
- [`epico-receipt-service`](./receipt-service)

Each of these serves a specific purpose (e.g., primitives for fundamental types or logic, RPC components for Ethereum compatibility).

Expand All @@ -34,6 +35,7 @@ ep-ethereum = { path = "../epico/primitives/ethereum", default-features = false
ep-rpc = { path = "../epico/rpc" }
pallet-epico-runtime-api = { path = "../epico/pallet-epico/runtime-api", default-features = false }
pallet-epico = { path = "../epico/pallet-epico", default-features = false }
epico-receipt-service = { path = "../epico/receipt-service" }
```

---
Expand Down Expand Up @@ -213,14 +215,16 @@ The `pallet-epico` module enables Ethereum compatibility features.
U256::from(adjusted_fee_per_gas)
}

fn safe_finality_depth() -> u8 {
<Runtime as pallet_epico::Config>::SafeFinalityDepth::get()
}

fn block_timestamp() -> U256 {
Timestamp::get().into()
}

fn build_receipt_at(block_number: u32) -> Vec<TransactionReceipt<AccountId, Hash>> {
pallet_epico::ExecutedTransaction::<Runtime>::iter_prefix(block_number).map(|(hash, transaction_info)|
pallet_epico::Pallet::<Runtime>::get_transaction_receipt(hash, transaction_info, block_number)
).collect()
}

fn dry_run_eth_transaction(call: TransactionCall) -> Result<(), Vec<u8>> {
Epico::dry_run(call).map_err(|e| format!("{:?}", e).encode())?;
Ok(())
Expand Down Expand Up @@ -518,10 +522,47 @@ The RPC module provides Ethereum JSON-RPC compatibility for interacting with Eth

---

## Step 7: Add Receipt Service for Transaction Receipt Storage

The `epico-receipt-service` is an offchain service that manages transaction receipts in persistent storage. It runs two workers: one stores receipts as "untrusted" on block import, another promotes them to "finalized" on finality events.

1. Add the `epico-receipt-service` dependency in your **node's** `Cargo.toml` file:

```toml
[dependencies]
epico-receipt-service = { workspace = true }
```

2. Initialize and spawn the receipt service in your node's service setup (e.g., `node/src/service.rs`):

```rust
use epico_receipt_service::ReceiptService;

// In your service setup function
let receipt_service = ReceiptService::<_, Block, solochain_template_runtime::Runtime, _>::new(
&config,
client.clone(),
backend
.offchain_storage()
.ok_or_else(|| "Offchain storage not available")?,
);

task_manager.spawn_handle().spawn(
"receipt-service-offchain-workers-runner",
"receipt-service-offchain-worker",
receipt_service.run(task_manager.spawn_handle()).boxed(),
);
```

**Note**: The `build_receipt_at` runtime API method required by this service has already been added to the runtime API implementation in Step 4.

---

## Summary checklist

1. Add `Epico` dependencies to `Cargo.toml`.
2. Integrate AccountId20 into your runtime.
3. Add the `pallet-epico` implementation to the runtime.
4. Integrate the `ep-self-contained` package into your runtime.
5. Set up the RPC for Ethereum compatibility
5. Set up the RPC for Ethereum compatibility.
6. Add Receipt Service for transaction receipt storage.
9 changes: 5 additions & 4 deletions pallet-epico/runtime-api/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use ep_ethereum::{
common::TransactionV2,
ethereum_types::{Address, H160, U256},
};
use pallet_epico::types::TransactionCall;
use pallet_epico::types::{TransactionCall, TransactionReceipt};
use parity_scale_codec::{Decode, Encode};
use sp_runtime::traits::Block as BlockT;
use sp_runtime::Vec;
Expand Down Expand Up @@ -39,12 +39,13 @@ sp_api::decl_runtime_apis! {
/// Get best effort estimation for a gas price.
fn gas_price() -> U256;

/// Returns pallet-epico's configured safe finality depth.
fn safe_finality_depth() -> u8;

/// Retrieves the timestamp for the current block.
fn block_timestamp() -> U256;

/// Runtime api for returning transaction receipts of all `pallet_epico::ExecutedTransaction`
/// in a given block.
fn build_receipt_at(block_number: u32) -> Vec<TransactionReceipt<AccountId, Hash>>;

/// Performs a dry-run of the given Ethereum-style transaction without modifying state.
///
/// Returns `Ok(())` if the transaction would succeed, or an `Err` containing the
Expand Down
Loading