diff --git a/Cargo.lock b/Cargo.lock index 5f7f4c74d3..80d291c063 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3039,6 +3039,7 @@ dependencies = [ "sp-consensus-grandpa", "sp-core", "sp-inherents", + "sp-keyring", "sp-offchain", "sp-runtime", "sp-session", diff --git a/docs/accounts.md b/docs/accounts.md index e0b278c408..89dca33ba9 100644 --- a/docs/accounts.md +++ b/docs/accounts.md @@ -11,8 +11,45 @@ The Runtime's `Signature` type is configured as [`sp_runtime::MultiSignature`](h - `Ed25519` - `ECDSA` -# Native H160 via AccountId20 +# Unified Accounts -The second strategy consists of using `fp-account` so that `AccountId20` is the Account type used for `frame_system::pallet::Config::AccountId`. +The second strategy was [originally implemented by Moonbeam](https://docs.moonbeam.network/learn/features/unified-accounts/). +It consists of using `fp-account` so that `AccountId20` is the Account type used for +`frame_system::pallet::Config::AccountId`. -The Runtime's `Signature` type is configured as `EthereumSigner`, which means only `ECDSA` signatures are supported. \ No newline at end of file +The Runtime's `Signature` type is configured as `EthereumSigner`, which means only `ECDSA` signatures are supported. + +Frontier's Template will pre-fund several well-known addresses that (mostly) contain the letters "th" in their names to remind you that they are for ethereum-compatible usage. These addresses are derived from Substrate's canonical mnemonic: `bottom drive obey lake curtain smoke basket hold race lonely fit walk` +``` +# Alith: +- Address: 0xf24FF3a9CF04c71Dbc94D0b566f7A27B94566cac +- PrivKey: 0x5fb92d6e98884f76de468fa3f6278f8807c48bebc13595d45af5bdc4da702133 + +# Baltathar: +- Address: 0x3Cd0A705a2DC65e5b1E1205896BaA2be8A07c6e0 +- PrivKey: 0x8075991ce870b93a8870eca0c0f91913d12f47948ca0fd25b49c6fa7cdbeee8b + +# Charleth: +- Address: 0x798d4Ba9baf0064Ec19eB4F0a1a45785ae9D6DFc +- PrivKey: 0x0b6e18cafb6ed99687ec547bd28139cafdd2bffe70e6b688025de6b445aa5c5b + +# Dorothy: +- Address: 0x773539d4Ac0e786233D90A233654ccEE26a613D9 +- PrivKey: 0x39539ab1876910bbf3a223d84a29e28f1cb4e2e456503e7e91ed39b2e7223d68 + +# Ethan: +- Address: 0xFf64d3F6efE2317EE2807d223a0Bdc4c0c49dfDB +- PrivKey: 0x7dce9bc8babb68fec1409be38c8e1a52650206a7ed90ff956ae8a6d15eeaaef4 + +# Faith: +- Address: 0xC0F0f4ab324C46e55D02D0033343B4Be8A55532d +- PrivKey: 0xb9d2ea9a615f3165812e8d44de0d24da9bbd164b65c4f0573e1ce2c8dbd9c8df +``` + +# Template Runtimes + +Frontier provides two different runtimes, one for each strategy. +You can choose which one want to build by using the `--feature` flag. For example: +``` +$ cargo build # this builds a runtime with H256 -> H160 mapping +$ cargo build --features unified-accounts # this builds a runtime with Unified Accounts \ No newline at end of file diff --git a/template/node/Cargo.toml b/template/node/Cargo.toml index 0a6c5384ba..6da4e4e1f4 100644 --- a/template/node/Cargo.toml +++ b/template/node/Cargo.toml @@ -49,6 +49,7 @@ sp-consensus-aura = { workspace = true, features = ["default"] } sp-consensus-grandpa = { workspace = true, features = ["default"] } sp-core = { workspace = true, features = ["default"] } sp-inherents = { workspace = true, features = ["default"] } +sp-keyring = { workspace = true } sp-offchain = { workspace = true, features = ["default"] } sp-runtime = { workspace = true, features = ["default"] } sp-session = { workspace = true, features = ["default"] } @@ -92,6 +93,7 @@ default = [ "sql", "txpool", ] +unified-accounts = [] rocksdb = [ "sc-cli/rocksdb", "sc-service/rocksdb", diff --git a/template/node/src/benchmarking.rs b/template/node/src/benchmarking.rs index 58d09ca92e..c51a4dd903 100644 --- a/template/node/src/benchmarking.rs +++ b/template/node/src/benchmarking.rs @@ -25,12 +25,27 @@ use scale_codec::Encode; // Substrate use sc_cli::Result; use sc_client_api::BlockBackend; -use sp_core::{ecdsa, Pair}; +#[cfg(feature = "unified-accounts")] +use sp_core::ecdsa; +#[cfg(not(feature = "unified-accounts"))] +use sp_core::sr25519; +use sp_core::Pair; use sp_inherents::{InherentData, InherentDataProvider}; +#[cfg(not(feature = "unified-accounts"))] +use sp_keyring::Sr25519Keyring; use sp_runtime::{generic::Era, OpaqueExtrinsic, SaturatedConversion}; // Frontier +#[cfg(not(feature = "unified-accounts"))] +use fp_account as _; +#[cfg(feature = "unified-accounts")] use fp_account::AccountId20; use frontier_template_runtime::{self as runtime, AccountId, Balance, BalancesCall, SystemCall}; +#[cfg(not(feature = "unified-accounts"))] +use hex_literal as _; +#[cfg(feature = "unified-accounts")] +use sp_keyring as _; +#[cfg(not(feature = "unified-accounts"))] +use sp_runtime::AccountId32; use crate::client::Client; @@ -57,6 +72,7 @@ impl frame_benchmarking_cli::ExtrinsicBuilder for RemarkBuilder { "remark" } + #[cfg(feature = "unified-accounts")] fn build(&self, nonce: u32) -> std::result::Result { let acc = ecdsa::Pair::from_string("//Bob", None).expect("static values are valid; qed"); let extrinsic: OpaqueExtrinsic = create_benchmark_extrinsic( @@ -69,6 +85,20 @@ impl frame_benchmarking_cli::ExtrinsicBuilder for RemarkBuilder { Ok(extrinsic) } + + #[cfg(not(feature = "unified-accounts"))] + fn build(&self, nonce: u32) -> std::result::Result { + let acc = Sr25519Keyring::Bob.pair(); + let extrinsic: OpaqueExtrinsic = create_benchmark_extrinsic( + self.client.as_ref(), + acc, + SystemCall::remark { remark: vec![] }.into(), + nonce, + ) + .into(); + + Ok(extrinsic) + } } /// Generates `Balances::TransferKeepAlive` extrinsics for the benchmarks. @@ -100,6 +130,7 @@ impl frame_benchmarking_cli::ExtrinsicBuilder for TransferKeepAliveBuilder { "transfer_keep_alive" } + #[cfg(feature = "unified-accounts")] fn build(&self, nonce: u32) -> std::result::Result { let acc = ecdsa::Pair::from_string("//Bob", None).expect("static values are valid; qed"); let extrinsic: OpaqueExtrinsic = create_benchmark_extrinsic( @@ -116,8 +147,27 @@ impl frame_benchmarking_cli::ExtrinsicBuilder for TransferKeepAliveBuilder { Ok(extrinsic) } + + #[cfg(not(feature = "unified-accounts"))] + fn build(&self, nonce: u32) -> std::result::Result { + let acc = Sr25519Keyring::Bob.pair(); + let extrinsic: OpaqueExtrinsic = create_benchmark_extrinsic( + self.client.as_ref(), + acc, + BalancesCall::transfer_keep_alive { + dest: self.dest.clone().into(), + value: self.value, + } + .into(), + nonce, + ) + .into(); + + Ok(extrinsic) + } } +#[cfg(feature = "unified-accounts")] /// Create a transaction using the given `call`. /// /// Note: Should only be used for benchmarking. @@ -177,6 +227,66 @@ pub fn create_benchmark_extrinsic( ) } +#[cfg(not(feature = "unified-accounts"))] +/// Create a transaction using the given `call`. +/// +/// Note: Should only be used for benchmarking. +pub fn create_benchmark_extrinsic( + client: &Client, + sender: sr25519::Pair, + call: runtime::RuntimeCall, + nonce: u32, +) -> runtime::UncheckedExtrinsic { + let genesis_hash = client + .block_hash(0) + .ok() + .flatten() + .expect("Genesis block exists; qed"); + let best_hash = client.chain_info().best_hash; + let best_block = client.chain_info().best_number; + + let period = runtime::BlockHashCount::get() + .checked_next_power_of_two() + .map(|c| c / 2) + .unwrap_or(2) as u64; + let extra: runtime::SignedExtra = ( + frame_system::CheckNonZeroSender::::new(), + frame_system::CheckSpecVersion::::new(), + frame_system::CheckTxVersion::::new(), + frame_system::CheckGenesis::::new(), + frame_system::CheckMortality::::from(Era::mortal( + period, + best_block.saturated_into(), + )), + frame_system::CheckNonce::::from(nonce), + frame_system::CheckWeight::::new(), + pallet_transaction_payment::ChargeTransactionPayment::::from(0), + ); + + let raw_payload = runtime::SignedPayload::from_raw( + call.clone(), + extra.clone(), + ( + (), + runtime::VERSION.spec_version, + runtime::VERSION.transaction_version, + genesis_hash, + best_hash, + (), + (), + (), + ), + ); + let signature = raw_payload.using_encoded(|e| sender.sign(e)); + + runtime::UncheckedExtrinsic::new_signed( + call, + AccountId32::from(sender.public()).into(), + runtime::Signature::Sr25519(signature), + extra, + ) +} + /// Generates inherent data for the `benchmark overhead` command. /// /// Note: Should only be used for benchmarking. diff --git a/template/node/src/chain_spec.rs b/template/node/src/chain_spec.rs index 9f2854c13b..77a930f4ca 100644 --- a/template/node/src/chain_spec.rs +++ b/template/node/src/chain_spec.rs @@ -1,13 +1,13 @@ -use std::{collections::BTreeMap, str::FromStr}; - +#[cfg(feature = "unified-accounts")] use hex_literal::hex; use serde::{Deserialize, Serialize}; +use std::{collections::BTreeMap, str::FromStr}; // Substrate use sc_chain_spec::{ChainType, Properties}; use sp_consensus_aura::sr25519::AuthorityId as AuraId; use sp_consensus_grandpa::AuthorityId as GrandpaId; -#[allow(unused_imports)] -use sp_core::ecdsa; +#[cfg(not(feature = "unified-accounts"))] +use sp_core::sr25519; use sp_core::{storage::Storage, Pair, Public, H160, U256}; use sp_runtime::traits::{IdentifyAccount, Verify}; use sp_state_machine::BasicExternalities; @@ -56,7 +56,7 @@ pub fn get_from_seed(seed: &str) -> ::Pu type AccountPublic = ::Signer; /// Generate an account ID from seed. -/// For use with `AccountId32`, `dead_code` if `AccountId20`. +/// For use with `AccountId32`, `dead_code` if `unified-accounts`. #[allow(dead_code)] pub fn get_account_id_from_seed(seed: &str) -> AccountId where @@ -79,6 +79,7 @@ fn properties() -> Properties { const UNITS: Balance = 1_000_000_000_000_000_000; +#[cfg(feature = "unified-accounts")] pub fn development_config(enable_manual_seal: Option) -> DevChainSpec { let wasm_binary = WASM_BINARY.expect("WASM not available"); @@ -126,6 +127,53 @@ pub fn development_config(enable_manual_seal: Option) -> DevChainSpec { ) } +#[cfg(not(feature = "unified-accounts"))] +pub fn development_config(enable_manual_seal: Option) -> DevChainSpec { + let wasm_binary = WASM_BINARY.expect("WASM not available"); + + DevChainSpec::from_genesis( + // Name + "Development", + // ID + "dev", + ChainType::Development, + move || { + DevGenesisExt { + genesis_config: testnet_genesis( + wasm_binary, + // Sudo account + get_account_id_from_seed::("Alice"), + // Pre-funded accounts + vec![ + get_account_id_from_seed::("Alice"), + get_account_id_from_seed::("Bob"), + get_account_id_from_seed::("Alice//stash"), + get_account_id_from_seed::("Bob//stash"), + ], + // Initial PoA authorities + vec![authority_keys_from_seed("Alice")], + // Ethereum chain ID + SS58Prefix::get() as u64, + ), + enable_manual_seal, + } + }, + // Bootnodes + vec![], + // Telemetry + None, + // Protocol ID + None, + // Fork ID + None, + // Properties + Some(properties()), + // Extensions + None, + ) +} + +#[cfg(feature = "unified-accounts")] pub fn local_testnet_config() -> ChainSpec { let wasm_binary = WASM_BINARY.expect("WASM not available"); @@ -172,6 +220,59 @@ pub fn local_testnet_config() -> ChainSpec { ) } +#[cfg(not(feature = "unified-accounts"))] +pub fn local_testnet_config() -> ChainSpec { + let wasm_binary = WASM_BINARY.expect("WASM not available"); + + ChainSpec::from_genesis( + // Name + "Local Testnet", + // ID + "local_testnet", + ChainType::Local, + move || { + testnet_genesis( + wasm_binary, + // Initial PoA authorities + // Sudo account + get_account_id_from_seed::("Alice"), + // Pre-funded accounts + vec![ + get_account_id_from_seed::("Alice"), + get_account_id_from_seed::("Bob"), + get_account_id_from_seed::("Charlie"), + get_account_id_from_seed::("Dave"), + get_account_id_from_seed::("Eve"), + get_account_id_from_seed::("Ferdie"), + get_account_id_from_seed::("Alice//stash"), + get_account_id_from_seed::("Bob//stash"), + get_account_id_from_seed::("Charlie//stash"), + get_account_id_from_seed::("Dave//stash"), + get_account_id_from_seed::("Eve//stash"), + get_account_id_from_seed::("Ferdie//stash"), + ], + vec![ + authority_keys_from_seed("Alice"), + authority_keys_from_seed("Bob"), + ], + 42, + ) + }, + // Bootnodes + vec![], + // Telemetry + None, + // Protocol ID + None, + // Fork ID + None, + // Properties + None, + // Extensions + None, + ) +} + /// Configure initial storage state for FRAME modules. fn testnet_genesis( wasm_binary: &[u8], diff --git a/template/runtime/Cargo.toml b/template/runtime/Cargo.toml index 5547dee3b9..6ca47a08e7 100644 --- a/template/runtime/Cargo.toml +++ b/template/runtime/Cargo.toml @@ -63,6 +63,7 @@ substrate-wasm-builder = { workspace = true, optional = true } [features] default = ["std", "with-rocksdb-weights"] +unified-accounts = [] with-rocksdb-weights = [] with-paritydb-weights = [] std = [ diff --git a/template/runtime/src/lib.rs b/template/runtime/src/lib.rs index f9e70ce0ad..ae5f79dc43 100644 --- a/template/runtime/src/lib.rs +++ b/template/runtime/src/lib.rs @@ -43,6 +43,9 @@ use pallet_grandpa::{ }; use pallet_transaction_payment::{ConstFeeMultiplier, CurrencyAdapter}; // Frontier +#[cfg(not(feature = "unified-accounts"))] +use fp_account as _; +#[cfg(feature = "unified-accounts")] use fp_account::EthereumSignature; use fp_evm::weight_per_gas; use fp_rpc::TransactionStatus; @@ -50,9 +53,13 @@ use pallet_ethereum::{ Call::transact, PostLogContent, Transaction as EthereumTransaction, TransactionAction, TransactionData, }; -use pallet_evm::{ - Account as EVMAccount, EnsureAccountId20, FeeCalculator, IdentityAddressMapping, Runner, -}; +use pallet_evm::{Account as EVMAccount, FeeCalculator, Runner}; +#[cfg(feature = "unified-accounts")] +use pallet_evm::{EnsureAccountId20, IdentityAddressMapping}; +#[cfg(not(feature = "unified-accounts"))] +use pallet_evm::{EnsureAddressTruncated, HashedAddressMapping}; +#[cfg(not(feature = "unified-accounts"))] +use sp_runtime::MultiSignature; // A few exports that help ease life for downstream crates. pub use frame_system::Call as SystemCall; @@ -66,9 +73,14 @@ use precompiles::FrontierPrecompiles; /// Type of block number. pub type BlockNumber = u32; +#[cfg(feature = "unified-accounts")] /// Alias to 512-bit hash when used in the context of a transaction signature on the chain. pub type Signature = EthereumSignature; +#[cfg(not(feature = "unified-accounts"))] +/// Alias to 512-bit hash when used in the context of a transaction signature on the chain. +pub type Signature = MultiSignature; + /// Some way of identifying an account on the chain. We intentionally make it equivalent /// to the public key of our transaction signing scheme. pub type AccountId = <::Signer as IdentifyAccount>::AccountId; @@ -329,6 +341,7 @@ parameter_types! { pub WeightPerGas: Weight = Weight::from_parts(weight_per_gas(BLOCK_GAS_LIMIT, NORMAL_DISPATCH_RATIO, WEIGHT_MILLISECS_PER_BLOCK), 0); } +#[cfg(feature = "unified-accounts")] impl pallet_evm::Config for Runtime { type FeeCalculator = BaseFee; type GasWeightMapping = pallet_evm::FixedGasWeightMapping; @@ -352,6 +365,30 @@ impl pallet_evm::Config for Runtime { type WeightInfo = pallet_evm::weights::SubstrateWeight; } +#[cfg(not(feature = "unified-accounts"))] +impl pallet_evm::Config for Runtime { + type FeeCalculator = BaseFee; + type GasWeightMapping = pallet_evm::FixedGasWeightMapping; + type WeightPerGas = WeightPerGas; + type BlockHashMapping = pallet_ethereum::EthereumBlockHashMapping; + type CallOrigin = EnsureAddressTruncated; + type WithdrawOrigin = EnsureAddressTruncated; + type AddressMapping = HashedAddressMapping; + type Currency = Balances; + type RuntimeEvent = RuntimeEvent; + type PrecompilesType = FrontierPrecompiles; + type PrecompilesValue = PrecompilesValue; + type ChainId = EVMChainId; + type BlockGasLimit = BlockGasLimit; + type Runner = pallet_evm::runner::stack::Runner; + type OnChargeTransaction = (); + type OnCreate = (); + type FindAuthor = FindAuthorTruncated; + type GasLimitPovSizeRatio = GasLimitPovSizeRatio; + type Timestamp = Timestamp; + type WeightInfo = pallet_evm::weights::SubstrateWeight; +} + parameter_types! { pub const PostBlockAndTxnHashes: PostLogContent = PostLogContent::BlockAndTxnHashes; } @@ -396,11 +433,18 @@ impl pallet_base_fee::Config for Runtime { type DefaultElasticity = DefaultElasticity; } +#[cfg(feature = "unified-accounts")] impl pallet_hotfix_sufficients::Config for Runtime { type AddressMapping = IdentityAddressMapping; type WeightInfo = pallet_hotfix_sufficients::weights::SubstrateWeight; } +#[cfg(not(feature = "unified-accounts"))] +impl pallet_hotfix_sufficients::Config for Runtime { + type AddressMapping = HashedAddressMapping; + type WeightInfo = pallet_hotfix_sufficients::weights::SubstrateWeight; +} + // Create the runtime by composing the FRAME pallets that were previously configured. construct_runtime!( pub enum Runtime where