Skip to content

Commit

Permalink
[pallet-revive] Eth RPC integration (paritytech#5866)
Browse files Browse the repository at this point in the history
This PR introduces the necessary changes to pallet-revive for
integrating with our Ethereum JSON-RPC.
The RPC proxy itself will be added in a follow up.

## Changes

- A new pallet::call `Call::eth_transact`. This is used as a wrapper to
accept unsigned Ethereum transaction, valid call will be routed to
`Call::call` or `Call::instantiate_with_code`

- A custom UncheckedExtrinsic struct, that wraps the generic one usually
and add the ability to check eth_transact calls sent from an Ethereum
JSON-RPC proxy.
- Generated types and traits to support implementing a JSON-RPC Ethereum
proxy.

## Flow Overview:
- A user submits a transaction via MetaMask or another
Ethereum-compatible wallet.
- The proxy dry run the transaction and add metadata to the call (gas
limit in Weight, storage deposit limit, and length of bytecode and
constructor input for contract instantiation)
- The raw transaction, along with the additional metadata, is submitted
to the node as an unsigned extrinsic.
- On the runtime, our custom UncheckedExtrinsic define a custom
Checkable implementation that converts the unsigned extrinsics into
checked one
 - It recovers the signer
- validates the payload, and injects signed extensions, allowing the
system to increment the nonce and charge the appropriate fees.
- re-route the call to pallet-revive::Call::call or
pallet-revive::Call::instantiateWithCode

## Dependencies

- paritytech/polkavm#188

## Follow up PRs
- paritytech#5926  
- paritytech#6147 (previously paritytech#5953)
- paritytech#5502

---------

Co-authored-by: Alexander Theißen <[email protected]>
Co-authored-by: Cyrill Leutwiler <[email protected]>
  • Loading branch information
3 people authored Oct 22, 2024
1 parent 356386b commit 21930ed
Show file tree
Hide file tree
Showing 40 changed files with 2,769 additions and 163 deletions.
171 changes: 141 additions & 30 deletions Cargo.lock

Large diffs are not rendered by default.

23 changes: 23 additions & 0 deletions prdoc/pr_5866.prdoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
title: "[pallet-revive] Ethereum JSON-RPC integration"

doc:
- audience: Runtime Dev
description: |
Related PR: https://github.com/paritytech/revive-ethereum-rpc/pull/5

Changes Included:
- A new pallet::call eth_transact.
- A custom UncheckedExtrinsic struct to dispatch unsigned eth_transact calls from an Ethereum JSON-RPC proxy.
- Generated types and traits to support implementing a JSON-RPC Ethereum proxy.
crates:
- name: pallet-revive
bump: major
- name: pallet-revive-fixtures
bump: patch
- name: pallet-revive-mock-network
bump: patch
- name: pallet-revive-uapi
bump: patch
- name: polkadot-sdk
bump: patch

3 changes: 1 addition & 2 deletions substrate/bin/node/cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ futures = { workspace = true }
log = { workspace = true, default-features = true }
rand = { workspace = true, default-features = true }
serde_json = { workspace = true, default-features = true }
subxt-signer = { workspace = true, features = ["unstable-eth"] }

# The Polkadot-SDK:
polkadot-sdk = { features = [
Expand All @@ -56,8 +57,6 @@ polkadot-sdk = { features = [
"generate-bags",
"mmr-gadget",
"mmr-rpc",
"pallet-contracts-mock-network",
"pallet-revive-mock-network",
"pallet-transaction-payment-rpc",
"sc-allocator",
"sc-authority-discovery",
Expand Down
10 changes: 6 additions & 4 deletions substrate/bin/node/cli/benches/block_production.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ use sp_blockchain::{ApplyExtrinsicFailed::Validity, Error::ApplyExtrinsicFailed}
use sp_consensus::BlockOrigin;
use sp_keyring::Sr25519Keyring;
use sp_runtime::{
generic,
transaction_validity::{InvalidTransaction, TransactionValidityError},
AccountId32, MultiAddress, OpaqueExtrinsic,
};
Expand Down Expand Up @@ -120,10 +121,11 @@ fn new_node(tokio_handle: Handle) -> node_cli::service::NewFullBase {
}

fn extrinsic_set_time(now: u64) -> OpaqueExtrinsic {
kitchensink_runtime::UncheckedExtrinsic::new_bare(kitchensink_runtime::RuntimeCall::Timestamp(
pallet_timestamp::Call::set { now },
))
.into()
let utx: kitchensink_runtime::UncheckedExtrinsic = generic::UncheckedExtrinsic::new_bare(
kitchensink_runtime::RuntimeCall::Timestamp(pallet_timestamp::Call::set { now }),
)
.into();
utx.into()
}

fn import_block(client: &FullClient, built: BuiltBlock<node_primitives::Block>) {
Expand Down
33 changes: 31 additions & 2 deletions substrate/bin/node/cli/src/chain_spec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

use polkadot_sdk::*;

use crate::chain_spec::{sc_service::Properties, sp_runtime::AccountId32};
use kitchensink_runtime::{
constants::currency::*, wasm_binary_unwrap, Block, MaxNominations, SessionKeys, StakerStatus,
};
Expand Down Expand Up @@ -291,8 +292,8 @@ fn configure_accounts(
usize,
Vec<(AccountId, AccountId, Balance, StakerStatus<AccountId>)>,
) {
let mut endowed_accounts: Vec<AccountId> = endowed_accounts
.unwrap_or_else(|| Sr25519Keyring::well_known().map(|k| k.to_account_id()).collect());
let mut endowed_accounts: Vec<AccountId> =
endowed_accounts.unwrap_or_else(default_endowed_accounts);
// endow all authorities and nominators.
initial_authorities
.iter()
Expand Down Expand Up @@ -417,12 +418,40 @@ fn development_config_genesis_json() -> serde_json::Value {
)
}

fn props() -> Properties {
let mut properties = Properties::new();
properties.insert("tokenDecimals".to_string(), 12.into());
properties
}

fn eth_account(from: subxt_signer::eth::Keypair) -> AccountId32 {
let mut account_id = AccountId32::new([0xEE; 32]);
<AccountId32 as AsMut<[u8; 32]>>::as_mut(&mut account_id)[..20]
.copy_from_slice(&from.account_id().0);
account_id
}

fn default_endowed_accounts() -> Vec<AccountId> {
Sr25519Keyring::well_known()
.map(|k| k.to_account_id())
.chain([
eth_account(subxt_signer::eth::dev::alith()),
eth_account(subxt_signer::eth::dev::baltathar()),
eth_account(subxt_signer::eth::dev::charleth()),
eth_account(subxt_signer::eth::dev::dorothy()),
eth_account(subxt_signer::eth::dev::ethan()),
eth_account(subxt_signer::eth::dev::faith()),
])
.collect()
}

/// Development config (single validator Alice).
pub fn development_config() -> ChainSpec {
ChainSpec::builder(wasm_binary_unwrap(), Default::default())
.with_name("Development")
.with_id("dev")
.with_chain_type(ChainType::Development)
.with_properties(props())
.with_genesis_config_patch(development_config_genesis_json())
.build()
}
Expand Down
19 changes: 14 additions & 5 deletions substrate/bin/node/cli/src/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -158,12 +158,13 @@ pub fn create_extrinsic(
);
let signature = raw_payload.using_encoded(|e| sender.sign(e));

kitchensink_runtime::UncheckedExtrinsic::new_signed(
generic::UncheckedExtrinsic::new_signed(
function,
sp_runtime::AccountId32::from(sender.public()).into(),
kitchensink_runtime::Signature::Sr25519(signature),
tx_ext,
)
.into()
}

/// Creates a new partial node.
Expand Down Expand Up @@ -866,7 +867,7 @@ mod tests {
use codec::Encode;
use kitchensink_runtime::{
constants::{currency::CENTS, time::SLOT_DURATION},
Address, BalancesCall, RuntimeCall, TxExtension, UncheckedExtrinsic,
Address, BalancesCall, RuntimeCall, TxExtension,
};
use node_primitives::{Block, DigestItem, Signature};
use polkadot_sdk::{sc_transaction_pool_api::MaintainedTransactionPool, *};
Expand All @@ -883,7 +884,7 @@ mod tests {
use sp_keyring::AccountKeyring;
use sp_keystore::KeystorePtr;
use sp_runtime::{
generic::{Digest, Era, SignedPayload},
generic::{self, Digest, Era, SignedPayload},
key_types::BABE,
traits::{Block as BlockT, Header as HeaderT, IdentifyAccount, Verify},
RuntimeAppPublic,
Expand Down Expand Up @@ -1099,8 +1100,16 @@ mod tests {
let signature = raw_payload.using_encoded(|payload| signer.sign(payload));
let (function, tx_ext, _) = raw_payload.deconstruct();
index += 1;
UncheckedExtrinsic::new_signed(function, from.into(), signature.into(), tx_ext)
.into()
let utx: kitchensink_runtime::UncheckedExtrinsic =
generic::UncheckedExtrinsic::new_signed(
function,
from.into(),
signature.into(),
tx_ext,
)
.into();

utx.into()
},
);
}
Expand Down
8 changes: 4 additions & 4 deletions substrate/bin/node/cli/tests/basic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,14 +61,14 @@ pub fn bloaty_code_unwrap() -> &'static [u8] {
/// correct multiplier.
fn transfer_fee(extrinsic: &UncheckedExtrinsic) -> Balance {
let mut info = default_transfer_call().get_dispatch_info();
info.extension_weight = extrinsic.extension_weight();
info.extension_weight = extrinsic.0.extension_weight();
TransactionPayment::compute_fee(extrinsic.encode().len() as u32, &info, 0)
}

/// Default transfer fee, same as `transfer_fee`, but with a weight refund factored in.
fn transfer_fee_with_refund(extrinsic: &UncheckedExtrinsic, weight_refund: Weight) -> Balance {
let mut info = default_transfer_call().get_dispatch_info();
info.extension_weight = extrinsic.extension_weight();
info.extension_weight = extrinsic.0.extension_weight();
let post_info = (Some(info.total_weight().saturating_sub(weight_refund)), info.pays_fee).into();
TransactionPayment::compute_actual_fee(extrinsic.encode().len() as u32, &info, &post_info, 0)
}
Expand Down Expand Up @@ -324,7 +324,7 @@ fn full_native_block_import_works() {

let mut alice_last_known_balance: Balance = Default::default();
let mut fees = t.execute_with(|| transfer_fee(&xt()));
let extension_weight = xt().extension_weight();
let extension_weight = xt().0.extension_weight();
let weight_refund = Weight::zero();
let fees_after_refund = t.execute_with(|| transfer_fee_with_refund(&xt(), weight_refund));

Expand Down Expand Up @@ -427,7 +427,7 @@ fn full_native_block_import_works() {

fees = t.execute_with(|| transfer_fee(&xt()));
let pot = t.execute_with(|| Treasury::pot());
let extension_weight = xt().extension_weight();
let extension_weight = xt().0.extension_weight();
let weight_refund = Weight::zero();
let fees_after_refund = t.execute_with(|| transfer_fee_with_refund(&xt(), weight_refund));

Expand Down
2 changes: 1 addition & 1 deletion substrate/bin/node/cli/tests/fees.rs
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ fn transaction_fee_is_correct() {
balance_alice -= length_fee;

let mut info = default_transfer_call().get_dispatch_info();
info.extension_weight = xt.extension_weight();
info.extension_weight = xt.0.extension_weight();
let weight = info.total_weight();
let weight_fee = IdentityFee::<Balance>::weight_to_fee(&weight);

Expand Down
9 changes: 5 additions & 4 deletions substrate/bin/node/cli/tests/submit_transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ use sp_application_crypto::AppCrypto;
use sp_core::offchain::{testing::TestTransactionPoolExt, TransactionPoolExt};
use sp_keyring::sr25519::Keyring::Alice;
use sp_keystore::{testing::MemoryKeystore, Keystore, KeystoreExt};
use sp_runtime::generic;

pub mod common;
use self::common::*;
Expand All @@ -44,7 +45,7 @@ fn should_submit_unsigned_transaction() {
};

let call = pallet_im_online::Call::heartbeat { heartbeat: heartbeat_data, signature };
let xt = UncheckedExtrinsic::new_bare(call.into());
let xt = generic::UncheckedExtrinsic::new_bare(call.into()).into();
SubmitTransaction::<Runtime, pallet_im_online::Call<Runtime>>::submit_transaction(xt)
.unwrap();

Expand Down Expand Up @@ -130,7 +131,7 @@ fn should_submit_signed_twice_from_the_same_account() {
// now check that the transaction nonces are not equal
let s = state.read();
fn nonce(tx: UncheckedExtrinsic) -> frame_system::CheckNonce<Runtime> {
let extra = tx.preamble.to_signed().unwrap().2;
let extra = tx.0.preamble.to_signed().unwrap().2;
extra.5
}
let nonce1 = nonce(UncheckedExtrinsic::decode(&mut &*s.transactions[0]).unwrap());
Expand Down Expand Up @@ -179,7 +180,7 @@ fn should_submit_signed_twice_from_all_accounts() {
// now check that the transaction nonces are not equal
let s = state.read();
fn nonce(tx: UncheckedExtrinsic) -> frame_system::CheckNonce<Runtime> {
let extra = tx.preamble.to_signed().unwrap().2;
let extra = tx.0.preamble.to_signed().unwrap().2;
extra.5
}
let nonce1 = nonce(UncheckedExtrinsic::decode(&mut &*s.transactions[0]).unwrap());
Expand Down Expand Up @@ -236,7 +237,7 @@ fn submitted_transaction_should_be_valid() {
let source = TransactionSource::External;
let extrinsic = UncheckedExtrinsic::decode(&mut &*tx0).unwrap();
// add balance to the account
let author = extrinsic.preamble.clone().to_signed().clone().unwrap().0;
let author = extrinsic.0.preamble.clone().to_signed().clone().unwrap().0;
let address = Indices::lookup(author).unwrap();
let data = pallet_balances::AccountData { free: 5_000_000_000_000, ..Default::default() };
let account = frame_system::AccountInfo { providers: 1, data, ..Default::default() };
Expand Down
2 changes: 2 additions & 0 deletions substrate/bin/node/runtime/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ scale-info = { features = ["derive", "serde"], workspace = true }
static_assertions = { workspace = true, default-features = true }
log = { workspace = true }
serde_json = { features = ["alloc", "arbitrary_precision"], workspace = true }
sp-debug-derive = { workspace = true, features = ["force-debug"] }

# pallet-asset-conversion: turn on "num-traits" feature
primitive-types = { features = ["codec", "num-traits", "scale-info"], workspace = true }
Expand Down Expand Up @@ -56,6 +57,7 @@ std = [
"primitive-types/std",
"scale-info/std",
"serde_json/std",
"sp-debug-derive/std",
"substrate-wasm-builder",
]
runtime-benchmarks = [
Expand Down
Loading

0 comments on commit 21930ed

Please sign in to comment.